Ring at 347.5 on entry, 427.5 after one confirmed DOWN, 347.5 on re-entry with the frame 0.0 % different from the first entry. sylpheed-port asserted non-persistence when nothing had measured it; the assertion was right and is now measured. MISSION SELECT is therefore a genuine initial focus, because this screen resets -- unlike the main menu, where a single-entry reading measures history. Every control here exists because run 1 failed without it: absolute row checks after every navigation press (a constant offset passes a differential control), screen identity against an in-run reference frame (main menu 327, EXTRAS 324 and OPTIONS 317 all sit in the same glyph window), and raw ring rows inside the submenu so no three-item geometry is assumed. The screen was also confirmed by eye. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
22 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 |
| ⬆⬇ — one item per press | one item per press | ✅ indirect but sound: the wrap montage's 4 presses from EXTRAS landing on OPTIONS only counts out if each press moves exactly one |
| ⬆⬇ — no auto-repeat | ✅ none — a 2.0 s held ⬇ moves the cursor exactly once | measured 2026-08-30, data/nav-autorepeat-and-settled-b.txt. ✅ The counter passes its control: a single 0.12 s tap gives exactly 1 spike, and the hold gives 1. Spike 0.0202–0.0220 against a 0.0003–0.0038 floor. ⚠️ one hold, 2.0 s, ⬇, main menu |
| 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 — measured 2026-08-30, delivery-confirmed, ≤ 0.4 s, and with no loading screen in between. ✅ and the plate IS re-drawn: Ⓑ on the menu at 351.2 s, plate pulse detected at 358.5 s — ~7 s later (2026-08-30) | 1 run — ../re/data/b-on-main-menu.txt · mid-build-in · settled. The footer point below still stands |
| Ⓑ on the title | ✅ nothing — 20 s after a delivery-confirmed Ⓑ the screen is still the title, PRESS Ⓐ BUTTON up |
measured 2026-08-30, capture · series. ✅ This run waited for the plate pulse — the title's own settled signature — before pressing, which is what the earlier attempt failed to do; its press landed during the build-in and was confounded. The 4.3 % of pixels that move are the plate's pulse and the sweeps (glyph 730, inside the 714–1520 pulse band) |
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.
✅ 2026-08-30 — EXTRAS resets; the main menu persists. They differ.
measured — data/extras-focus-resets.txt,
tools/re-capture/extras_focus_persistence.py.
| ring y | item | |
|---|---|---|
| EXTRAS opened on | 347.5 | MISSION SELECT |
| after 1 delivery-confirmed DOWN | 427.5 (+80.0) | MOVIE THEATER |
| after Ⓑ → main menu → Ⓐ → EXTRAS | 347.5 | MISSION SELECT |
Re-entry is 0.0 % different from the first entry. So:
- ✅ EXTRAS resets.
sylpheed-portasserted this in their contract-check when nothing had measured it, and I flagged the assertion as an absence of evidence encoded as a positive claim. They were right, and it is now measured. - ✅
MISSION SELECTis a genuine INITIAL focus, precisely because this screen resets — unlike the main menu, where a reading not taken on a fresh boot's first entry measures history. - 🔴 The two screens behave differently, so there is no menu-wide rule. The main menu persists; EXTRAS does not. A generalisation in either direction would be wrong, which is why wrap (measured on two screens) generalises and this does not.
- ✅ The screen was confirmed by eye —
E1.pngis EXTRAS, MISSION SELECT / MOVIE THEATER / BACK — because the previous run was fooled about which screen it was on. And the 80.0 px step independently matches the main menu's separately calibrated 79.25.
⚠️ Reach: one run, one round trip, one submenu. OPTIONS, LOAD GAME and
TUTORIAL are untested, as is whether the reset is to MISSION SELECT or simply
to the top item — those coincide here.
🔴 2026-08-30 (later) — the item NAMES below were wrong, and initial focus is NEW GAME
The reader was broken and my control could not see it. menu_focus.py's row
centres are design-space rows read off screenshot output; my probes fed it
whole-display x11grab frames, which carry Xenia's title bar and menu bar and
show the game surface scaled by 1.060. Caught by ground truth, not by a
control: the probe announced "on EXTRAS", pressed Ⓐ, and opened OPTIONS.
Ring rows measured directly (tools/re-capture/ring_row.py), no assumed geometry:
| frame | ring y | true item |
|---|---|---|
| first menu entry, fresh boot | 225.5 | NEW GAME |
| after 2 delivery-confirmed DOWNs | 384.0 | TUTORIAL |
| after Ⓑ → title → Ⓐ → menu | 385.5 | TUTORIAL |
- ✅ Initial focus on a fresh boot is
NEW GAME— 2/2 fresh boots, both the first menu entry. That agrees withboot_menu.sh's own closing line and withmenu-state-in-memory.md's four-downs-to-EXTRAS, which only counts fromNEW GAME."initial focus, main menu — TUTORIAL"in the Q5 table below is withdrawn: it is the outlier, and this reading is the direct one. - ✅ Persistence stands, and is now geometry-free: 384.0 vs 385.5, 1.5 px apart. An equality test is immune to a constant offset, which is why that conclusion survived a broken reader when the labels did not.
- 🔴 The names I published for it were two positions out — reported
TUTORIAL → EXTRAS → EXTRAS, truthNEW GAME → TUTORIAL → TUTORIAL. - ⚠️ The control was structurally blind to this. "Two DOWNs must move the cursor exactly two items" tests relative motion, and a constant offset preserves relative motion exactly. A control that only checks differences cannot see an error in the origin.
✅ 2026-08-30 — the menu REMEMBERS its cursor across menu → title → menu
measured — data/focus-persists-across-title.txt,
tools/re-capture/focus_persistence.py. One run, control passed:
| step | focus | ring vector |
|---|---|---|
| F1, on the menu | TUTORIAL |
130 76 254 69 64 |
| F2, after 2× DOWN (both delivery-confirmed) | EXTRAS |
130 76 66 69 254 |
| F3, after Ⓑ → title → Ⓐ → menu | EXTRAS |
130 76 66 69 254 |
F3 == F2, so focus persists — the menu is re-entered on the item you left, not on a fixed one.
📌 And this reframes the disagreement below rather than settling it. If focus persists, then any "initial focus" reading not taken on a fresh boot's first menu entry is measuring history. The records need not disagree about the game at all — they may differ in what the cursor had already been moved to. Nothing here says what the menu opens on; this run's F1 was itself carried over from a prior probe's press.
⚠️ Reach: one boot, one round trip, one direction. Persistence across a reboot is untested, and is the reading that would matter for authoring a default.
⚠️ 2026-08-30 — and the sources DISAGREE, which nothing here had noticed.
boot_menu.sh's own closing line says "AT MAIN MENU (cursor on NEW GAME)", and
menu-state-in-memory.md's run reaches EXTRAS in
four downs, which only counts from NEW GAME. Two sources say NEW GAME;
this table says TUTORIAL 2/2. skip_intro.sh presses Ⓐ once and no d-pad, so
the harness is not moving the cursor and that does not explain it.
🔴 I tried to settle it and could not: the boot never reached the menu, twice —
the title gate cannot fire on this title
(harness-title-gate-assumes-a-static-title).
So the disagreement stands, and the round trip that would answer the persistence
question — menu → Ⓑ → title → Ⓐ → menu, is focus where you left it or reset? — has
still never been run. tools/re-capture/focus_persistence.sh is written and ready
for a harness that can reach the menu.
🟡 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 — 8 rows visible of 16, and rows below the first are locked on a fresh save (below) | 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 §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" was a dead route — WITHDRAWN 2026-08-29
This section used to say: 0x828A690C holds a live screen id (1 title, 3 main
menu, 4 extras) and 0x828F38AC the cursor; read them while pressing Ⓐ and the
transition becomes a measurement, under --gpu=null with no screenshots.
Do not do that. Those words are monotonic counters, not state.
menu-state-in-memory.md withdrew that identity on
the same day it was published — driving deep and then pressing Ⓑ three times
sends the "cursor" 36 → 38 → 40 → 41, and a cursor returns when you go back.
The values match across runs because the same key sequence produces the same
count, not because 3 means main menu. With the sign-in fix the sequence runs
3 → 5, skipping 4 entirely, so "4 = extras" was never a screen at all.
This page kept recommending the route for three days after the page it cited had killed it. ⚠️ A cross-reference is not a citation unless you re-read the target — the two documents disagreed in the corpus and either would have been believed on its own.
✅ What those words are still good for: all three advance if and only if the game responds to input, and are stable when it does not. Read them as "did the game react?", never as "which screen is this?". Screen identity still has to come off the framebuffer.
❔ So a measured button→GamePart-id binding remains unfinished, and for the reason it always was: no screen/state enum has been located in guest memory. The ids in the table below stay a name match.
✅ 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 onNORMAL(capture). 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 headedCurrent Storage: Dummy HDD, prompting to pick a file for the auto-save. - Then the guest throws, and Xenia pauses with
PC: 0x82307128(capture).
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, 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 — six data points now, 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.
✅ Two more, 2026-08-29, and the split is now even. A drive that pressed Ⓐ
with no d-pad movement ended in a tutorial mission (+0.960 against
tutorial-mission-reached-then-crash.png), so that boot opened on TUTORIAL. A
later boot read NEW GAME from a focus detector on the first menu frame.
Tally: TUTORIAL ×3, NEW GAME ×3. Six boots, same harness, no other item
ever seen. ⚠️ It is not uniform across the five buttons — only these two occur —
which is a constraint on whatever selects it, and something a future explanation
has to account for. Still do not hardcode it, and note that
newgame_path.sh's "NEW GAME is the first item so no d-pad is needed" is wrong
half the time — see s00a-drive-blocked-by-focus.md.
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
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 Ⓐ BUTTONafter 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).
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.
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: "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.