method lines withdrawn Q5, measured off two boots: up/down move one item and WRAP at both ends on the 5-item main menu and the 3-item EXTRAS alike; left/right do nothing; B goes up one level and restores focus to the item you came from (4/4); B on the main menu returns to the title; B on the title does nothing. The menu opens on TUTORIAL -- the middle item -- 2/2, though a third recorded run implies NEW GAME, so that one is reproducible rather than invariant and says so. Q4 by driving: LOAD GAME opens the save-slot list, TUTORIAL the lesson list, OPTIONS the settings menu, EXTRAS build 6, MISSION SELECT the stage list. NEW GAME is not tested -- A on it hangs the emulator and this iteration needed the session. The GamePart ID behind each is NOT measured: it is the entry of the decoded id table whose name matches the screen I saw, and the page says so rather than wearing the badge. And the withdrawal. Last iteration I wrote that these menus drop d-pad presses shorter than ~0.3s. They do not. Once wrap is measured, every press count I had is exactly right -- four presses moved four steps THROUGH the bottom, which lands one above where a non-wrapping menu would. I invented hardware flakiness instead of testing the ends of the list. METHOD keeps the withdrawal rather than deleting the line. Also: label brightness is not a cursor oracle here -- the background art outshines the highlight on some rows. menu_focus.py reads the focus ring instead, 254 vs <82, no tuning.
4.9 KiB
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 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.
| 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 |
| 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, 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 |
|
| Ⓑ on the title | nothing |
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 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 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 |
🟡 5 GP_EXTRAS |
EXTRAS ▸ MISSION SELECT |
the stage list + Wide Area Space Map | 🟡 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 §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),
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).
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.