# The title menu โ€” how it moves, and where each button goes **Status:** โœ… `CONFIRMED` (**measured**, by driving the running game) for the movement rules and for four of the five main-menu destinations. ๐ŸŸก the GamePart *id* behind each destination is a **name match onto the decoded id table**, not a measurement. โ” `NEW GAME` deliberately untested. Answers [MISSION Q5](../port/MISSION.md) and most of Q4. Nothing here is on the disc in any form found so far โ€” the port is **authoring** these rules from this page, not transcribing a field. ## Q5 โ€” movement Two boots via `tools/re-capture/boot_menu.sh`, cursor read off the focus ring with [`tools/re-capture/menu_focus.py`](../../tools/re-capture/menu_focus.py). | | behaviour | evidence | |---|---|---| | **initial focus, main menu** | **`TUTORIAL`** โ€” the *middle* item, not the top | 2/2 boots, the first frame after the menu appears | | **initial focus, `EXTRAS`** | `MISSION SELECT` โ€” the top item | [`extras-wrap.png`](captures/menu-nav/extras-wrap.png) | | **up / down** | one item per press, no auto-repeat at the durations tried | | | **wrap at the top** | โฌ† from the first item goes to the **last** | [`wrap-montage.png`](captures/menu-nav/wrap-montage.png), panels 1โ†’2 | | **wrap at the bottom** | โฌ‡ from the last item goes to the **first** | same, panels 3โ†’4, and 4 presses from `EXTRAS` landing on `OPTIONS` โ€” i.e. wrapping โ€” is what makes the count come out | | **left / right** | **nothing**, on the main menu | cursor unmoved across one โฌ… and one โžก | | **โ’ท on a submenu** | returns to the parent **with focus restored to the item you entered from** โ€” `LOAD GAME`โ†’`LOAD GAME`, `TUTORIAL`โ†’`TUTORIAL`, `OPTIONS`โ†’`OPTIONS`, `EXTRAS`โ†’`EXTRAS` | 4/4 | | **โ’ท on the main menu** | ๐ŸŸก goes to the **title**, which re-draws `PRESS โ’ถ BUTTON` after a beat | **none** โ€” and the main menu's own footer does not advertise โ’ท; [downgraded below](#-refutation-attempt-2026-08-29--the-main-menus-own-footer-does-not-advertise-โ“‘) | | **โ’ท on the title** | ๐ŸŸก **nothing** | **none** | Wrap holds on both screens tested โ€” the 5-item main menu and the 3-item `EXTRAS` submenu โ€” so it is a menu rule, not a per-screen table. **๐ŸŸก Initial focus is reproducible but not established as invariant.** Both of my boots opened on `TUTORIAL`, and both used `boot_menu.sh`. The run recorded in [`menu-state-in-memory.md`](menu-state-in-memory.md) reached `EXTRAS` with *four* downs from the main menu, which only works from `NEW GAME`. Either the harness path matters or something persists. **Do not hardcode `TUTORIAL` without re-testing it**; what is solid is that the menu does *not* open on the top item in this harness. ## Q4 โ€” where each button goes Measured by driving: focus the item, press โ’ถ, read the screen's own title. | button | screen it opens | evidence | GamePart id | |---|---|---|---| | `NEW GAME` | โ” **not tested** | โ€” | โ€” | | `LOAD GAME` | the save-slot list, `LOAD GAME` / `Current Storage` | [`q4-destinations.png`](captures/menu-nav/q4-destinations.png) left | ๐ŸŸก `3 GP_LOAD` | | `TUTORIAL` | the lesson list, `TUTORIAL`, Level 1 / Level 2 | same, middle | ๐ŸŸก `25 GP_TUTORIAL` | | `OPTIONS` | `OPTIONS` โ€” GAME / CONTROL / SOUND / SCREEN SETTINGS / BACK | same, right | ๐ŸŸก `8 GP_OPTIONS` | | `EXTRAS` | **`GP_TITLE.pak` build 6** โ€” MISSION SELECT / MOVIE THEATER / BACK | [`ui-title-build-map.md`](ui-title-build-map.md) | ๐ŸŸก `5 GP_EXTRAS` | | `EXTRAS โ–ธ MISSION SELECT` | the stage list + Wide Area Space Map โ€” **8 rows visible of 16**, and rows below the first are **locked** on a fresh save ([below](#-mission-select-the-cursor-was-stuck-because-the-stages-were-locked)) | [`mission-select-stage01-only.png`](captures/mission-select-stage01-only.png) | ๐ŸŸก `7 GP_MISSION_SELECT` | | `EXTRAS โ–ธ MOVIE THEATER` | โ” not tested | | ๐ŸŸก `6 GP_MOVIE_THEATER` | **Say which, as the gate asks.** The *screen* each button opens is **measured** โ€” I pressed the button and read the title off the framebuffer. The **GamePart id is not measured**: it is the entry of the decoded 29-id table at `.rdata 0x820A1630` ([`challenge-mission-gate.md`](challenge-mission-gate.md) ยง3) whose *name* matches the screen I saw. The table is decoded; the *binding* of a button to an entry in it is a name match I made by eye. The port should treat these ids as authored. Worth noting that the ids and the paks are not one-to-one: `GP_EXTRAS` is id 5 with **no pak of its own** โ€” its artwork is a build inside `GP_TITLE.pak`. **โ” `NEW GAME` was deliberately not pressed.** โ’ถ on it leads to a standing black-screen hang that ends the run ([`ui-paint-order-third-permutation.md`](ui-paint-order-third-permutation.md)), and this iteration needed the session. It is the one destination still unmeasured. ### The cheap way to finish this, and to make it a measurement `0x828A690C` holds a **live screen id** โ€” `1` title, `3` main menu, `4` extras โ€” and `0x828F38AC` the cursor ([`menu-state-in-memory.md`](menu-state-in-memory.md)). Reading those while pressing โ’ถ turns "the screen said OPTIONS" into a measured transition, and works under `--gpu=null` with no screenshots at all. โš ๏ธ Note that those values are **not** GamePart ids โ€” `GP_TITLE` is GamePart 0 and `GP_EXTRAS` is 5, but the word reads 1 and 4 โ€” so it is a third enumeration and mapping it to the id table is itself unfinished work. ## โœ… `NEW GAME` โ€” measured 2026-08-28, and it is not a hang The one destination this page could not test has been driven. **โ’ถ on `NEW GAME` does not hang.** It opens two more menus first: ``` NEW GAME -> DIFFICULTY -> SELECT DATA -> guest crash ``` * **`DIFFICULTY`** โ€” `EASY` / `NORMAL` / `HARD` / `BACK`, footer `โ’ถ : OK โ’ท : Back`, opening focused on **`NORMAL`** ([capture](captures/newgame-path/newgame-difficulty.png)). It sat unchanged for 90 s with the emulator healthy โ€” a menu waiting for input, which is exactly what "a standing hang" looks like to a screenshot loop that never presses anything. * โ’ถ on `NORMAL` โ†’ **`SELECT DATA`**, a save-slot picker headed `Current Storage: Dummy HDD`, prompting to pick a file for the auto-save. * Then the guest throws, and Xenia pauses with `PC: 0x82307128` ([capture](captures/newgame-path/newgame-selectdata-crash.png)). **That crash is already in the corpus and is not new**: `0x82307128` is inside `sub_823070B0`, the cache-manager STL erase documented in [`title-crash-stl-tree.md`](title-crash-stl-tree.md), whose trigger is an incomplete on-disc shader/code cache โ€” not the menu path. The corpus also already holds `captures/select-data-crash.png`. So Q4's last row closes as **measured**: | button | screen it opens | |---|---| | `NEW GAME` | **`DIFFICULTY`** (then `SELECT DATA`) | ๐ŸŸก The GamePart ids for these two are unmeasured, like the rest โ€” `24 GP_DIALOG` and `3 GP_LOAD` / `2 GP_SELECT_STORAGE` are name-match candidates and nothing more. ### Initial focus โ€” a fourth data point, and it still varies This boot opened the main menu on **`NEW GAME`**. Running tally across four boots of the same harness: `TUTORIAL`, `TUTORIAL`, `NEW GAME`, `NEW GAME`. Unchanged conclusion: **do not hardcode it.** ### The `NEW GAME` path completes โ€” the `SELECT DATA` crash is state, not path A second run of the same path, 2026-08-28, **did not crash**: ``` NEW GAME โ†’ DIFFICULTY โ†’ (โ’ถ on NORMAL) โ†’ SELECT DATA โ†’ (โ’ถ on a slot) โ†’ a movie plays ``` The previous run's throw at `PC 0x82307128` is therefore **not inherent to this menu path** โ€” the same six presses got through it. That is consistent with the trigger `title-crash-stl-tree.md` already names (an incomplete on-disc cache) and with nothing about `NEW GAME`. ๐ŸŸก n = 1 either way; do not read it as "fixed". Worth recording because the first observation could easily have hardened into "the new-game path crashes", which is what "A on NEW GAME hangs" had already become once. --- ## ๐ŸŸก Refutation attempt 2026-08-29 โ€” the main menu's own footer does **not** advertise โ’ท **Attempted claim:** this page's row *"โ’ท on the main menu goes to the title, which re-draws `PRESS โ’ถ BUTTON` after a beat"*. **Why this row and not another.** It is one of only **two** rows in the Q5 table with an **empty evidence cell** (the other is "โ’ท on the title โ†’ nothing"); every row that cites a capture cites one. And it is a rule the port will build on directly โ€” it is the only way out of the main menu. **The measurement** โ€” whole-frame colour test for the pad-glyph discs, run by [`tools/re-capture/footer_and_locked_rows.py`](../../tools/re-capture/footer_and_locked_rows.py) against the committed captures: | capture | โ’ถ glyph px | โ’ท glyph px | |---|---|---| | `live-main-menu.png` | 438 | **0** | | `live-main-menu-options-focused.png` | 438 | **0** | | `live-extras.png` | 440 | 514 | | `difficulty-screen.png` | 438 | 518 | **The control passes twice over.** The same detector, unchanged, finds the red โ’ท on the two screens that visibly have one; and the **โ’ถ** count is 438/438/440/438 across all four, i.e. the same glyph asset at the same size on every screen โ€” so a โ’ท of that family would have been ~450โ€“520 px and cannot have fallen under a threshold. The negative is over the **whole frame**, not a guessed footer band: `live-main-menu.png` contains **zero** red-glyph pixels anywhere. So the main menu's legend reads `โŠ™ : Select โ’ถ : OK` where every submenu reads `โŠ™ : Select โ’ถ : OK โ’ท : Back`. **Verdict: the claim SURVIVES, at reduced confidence, and the row is downgraded to ๐ŸŸก.** A legend is not behaviour โ€” a game may accept an unadvertised โ’ท โ€” so an absent glyph cannot refute a press that was actually observed. But: * the observation has **no capture behind it**, and it is now the only Q5 row contradicted by the game's own on-screen text; * there is a **named confound**: the title-side screens auto-return on idle, and "I pressed โ’ท and ended up at the title, which drew `PRESS โ’ถ BUTTON` after a beat" is also exactly what an idle timeout looks like to an observer who does not hold the two apart. The corpus documents that timeout at ~8โ€“10 s ([`boot-config-and-gamepart-registry.md`](boot-config-and-gamepart-registry.md)). **What would settle it:** press โ’ท on the main menu and read `0x828A690C`, the live screen id (`1` title, `3` main menu, `4` extras) โ€” a transition inside a second is a โ’ท, one at ~8โ€“10 s regardless of the press is the timeout. Cheap, and it needs no screenshots. ๐Ÿ”ด **Not runnable here.** This container has no disc and no ISO, so there is no oracle at all โ€” see [`capture-harness-status.md`](capture-harness-status.md#-2026-08-29--the-disc-is-not-in-the-decoder-container-at-all). **For the port:** โ’ท from the main menu to the title is **measured, single observation, uncited, and unadvertised by the game**. Implement it โ€” it is the only exit โ€” but treat it as authored rather than transcribed, and do not also build the idle-return on the assumption that the two are distinct until someone has separated them. --- ## โœ… MISSION SELECT: the cursor was stuck because the stages were **locked** **Measured 2026-08-29 from committed captures**, no disc needed. Settles the โš ๏ธ open in [`../game/navigation.md`](../game/navigation.md): *"sixteen d-pad presses never left Stage 01 โ€” whether that is because only one stage was unlocked, or because the list is driven some other way, is unknown"*. The stage list has **three** label brightnesses, not two, and that is what discriminates. Sampling the label strip (x 190โ€ฆ320) of each of the 8 visible rows, 95th percentile of luminance: | capture | row 1 | rows 2โ€“8 | |---|---|---| | `mission-select-stage01-only.png` | **254** | **104** | | `mission-select-all-story-unlocked.png` | **254** | **183** | | `mission-select-ends-at-stage16.png` | 183 | 183 ร—6, then **254** on row 8 | * **254** = focused (the row carrying the spinning focus ring) * **183** = unlocked, not focused * **104** = **locked** The all-unlocked capture is the control: it holds row 1 focused at the identical 254 while rows 2โ€“8 move 104 โ†’ 183 as one uniform step. So the dim rows in the Stage01-only capture are **not** "unfocused"; unfocused is 183, and they are 79 levels below it. **And the cursor does move when they are unlocked.** In `mission-select-ends-at-stage16.png` the list has scrolled to show Stage09โ€ฆ16, the scrollbar thumb is at the bottom, and the focus ring is on **Stage16** โ€” the last row. Locked list: 16 presses, no movement. Unlocked list: the cursor reaches the end. | | | |---|---| | **rows visible at once** | **8** | | **list length** | **16** (`Stage01`โ€ฆ`Stage16`; the scrollbar bottoms out at 16) | | **why 16 presses did nothing** | every row below the first was locked | โš ๏ธ **Reach.** This is a still image, so it says the cursor *reached* Stage16, not how it got there and not whether the list wraps โ€” the scroll thumb bottoming out at row 16 is consistent with either. Whether a *locked* row is skipped or simply unreachable is likewise not separated: with only row 1 unlocked the two are the same observation.