Files
Sylpheed/docs/re/menu-navigation-semantics.md
sylph-decoder 891c38e87e
Some checks failed
CI / Native — linux (pull_request) Failing after 35m25s
CI / WASM — Web (pull_request) Successful in 31m31s
CI / Formatting (pull_request) Failing after 1m23s
re: fix two stale rows -- NEW GAME's destination, and my own repeat claim
No emulator-requiring state/approved item was open this iteration (issues
#1, #3, #5 all landed and moved to state/needs-human; #25 still awaits
approval). Used the gap for corpus consistency instead of idling, per the
same "a correction that never reaches the row someone reads" failure this
project keeps naming.

1. menu-navigation-semantics.md's own Q4 table still said NEW GAME was
   "not tested" and its own prose said it was "deliberately not pressed"
   and hangs the emulator -- both refuted BY THIS SAME PAGE on 2026-08-28,
   34 lines further down ("NEW GAME -- measured ... it is not a hang. It
   opens DIFFICULTY then SELECT DATA"). The correction never propagated
   backward into the table or the status line above it, so a reader
   stopping at either would come away with the wrong (and already-refuted)
   answer. Fixed in place, struck rather than deleted, with the actual
   destination and an honest note that DIFFICULTY has no id-table name
   match (already refuted separately) while SELECT DATA plausibly matches
   GP_SELECT_STORAGE as a fresh, low-confidence guess.

2. Found the identical failure mode in my own recent work: HANDOFF.md's
   original Q1-Q10 summary table (near the top of a 6600+ line file) still
   quoted the 2026-08-30 "no auto-repeat" finding as current, three commits
   after this same session measured 12 frames delay / 4 frames interval
   through a repeat-capable driver and explained why the earlier negative
   was a driver limitation, not a game fact. A reader who only sees the
   summary table -- which is exactly what a long file trains a reader to
   rely on -- would get the withdrawn answer. Fixed with an explicit note
   pointing at the current entries rather than silently editing the number
   in place, so the correction itself stays visible.

Refutation-shaped either way: two claims ("NEW GAME untested", "no
auto-repeat") checked against this corpus's own newer evidence and found
not to survive, recorded rather than left to be rediscovered.
2026-09-12 13:24:27 +00:00

480 lines
28 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 all **five** main-menu destinations, `NEW GAME`
included (→ `DIFFICULTY``SELECT DATA`, not a hang — corrected 2026-09-12,
see the Q4 table). 🟡 the GamePart *id* behind each destination is a **name
match onto the decoded id table**, not a measurement.
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) |
| **⬆⬇ — 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** | 🔴 **UNSETTLED, corrected 2026-09-12 — do not read this row as "none."** See [`f1-no-repeat-was-the-harness.md`](f1-no-repeat-was-the-harness.md): the driver this measurement drove input through (`--hid=file`'s `GetKeystroke`) is *deliberately* built to deliver exactly one event per press, by design, for other scripts' benefit — so it could not have shown repeat regardless of what the game does. A held direction moving the cursor exactly once was real and reproducible; what it proves is narrower than "no auto-repeat" and may be nothing at all. Kept below for the record | ~~measured 2026-08-30~~, [`data/nav-autorepeat-and-settled-b.txt`](data/nav-autorepeat-and-settled-b.txt). The counter passed its own control (a single 0.12 s tap gives exactly **1** spike, the hold gives **1**, against a 0.00030.0038 noise floor) — the counter was never the problem |
| **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** — 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`](data/b-on-main-menu.txt) · [mid-build-in](captures/title-builds/live-b-on-menu-title-buildin.png) · [settled](captures/title-builds/live-b-on-menu-title-settled.png). 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](captures/title-builds/live-b-on-settled-title-no-effect.png) · [series](data/nav-autorepeat-and-settled-b.txt). ✅ **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 7141520 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-31 — SETTLED: a submenu resets to the item it **opens on**, not to its top item
**measured** — [`data/difficulty-resets-to-named-item.txt`](data/difficulty-resets-to-named-item.txt),
[capture](captures/menu-nav/live-difficulty-opens-normal.png).
`DIFFICULTY` is the screen that separates the two readings, because it opens on
**`NORMAL`** — the second of `EASY` / `NORMAL` / `HARD` / `BACK`. Reproduced on a
fresh boot today rather than inherited from the 2026-08-29 capture.
| | |
|---|---|
| opened on | `NORMAL` |
| after 1 delivery-confirmed DOWN | `HARD` |
| after Ⓑ → main menu → Ⓐ → `DIFFICULTY` | **`NORMAL`** — in-cursor **1.0** from opened vs **93.9** from where left |
✅ **So reset goes to the opening item, and the opening item is a per-screen
default that need not be the first.** `DIFFICULTY` returns to `NORMAL`, not `EASY`.
📌 The other four could not settle it because on `EXTRAS`, `TUTORIAL`, `OPTIONS`
and `LOAD GAME` the opening item **is** the first item, so both readings predict
the same observation. Five screens, and only the fifth carries the distinction —
which is why `sylpheed-port` was right to refuse to promote 4/4 to a rule.
⚠️ **Safety, and it is why the run was possible**: `DIFFICULTY`'s *forward* path
crashes the guest (Ⓐ → `SELECT DATA``PC 0x82307128`). The probe presses Ⓐ to
enter, one DOWN, then Ⓑ to leave, and **never presses Ⓐ inside a submenu**.
⚠️ Reach: one boot, one round trip. Untested: whether the reset target moves once a
difficulty has actually been **confirmed** — this run never confirms one.
## 🔴 2026-08-31 — a submenu's opening item is NOT always its first: `DIFFICULTY` opens on `NORMAL`
**Already in this file, and I missed it.** `sylpheed-port` has been asking for a
submenu whose opening item is not the first one, because that is what separates
*"resets to the named item"* from *"resets to the top item"*. I told them none of
the screens I measured was one — while **line 248 of this page** records
`DIFFICULTY` as *"`EASY` / `NORMAL` / `HARD` / `BACK` … opening focused on
`NORMAL`"*.
**The provenance is clean and it is genuinely an opening state**: the run drove
`NEW GAME` with **no d-pad**, the screen *"sat unchanged for 90 s"*, and
[`s00a-drive-blocked-by-focus.md`](s00a-drive-blocked-by-focus.md) confirms the
step matched the committed capture at **r = +0.999**.
**So `NORMAL` — the second of four — is a default, not a top item.** That refutes
*"a screen opens on its first item"* as a general description, and it means the
distinction the port is asking about is **real in this game**, not academic: on
`EXTRAS`, `TUTORIAL` and `OPTIONS` the two readings coincide only by accident.
**It does not yet settle their question**, which is about **reset** rather than
opening: to decide it, move the cursor in `DIFFICULTY`, leave, re-enter, and see
whether it returns to `NORMAL` (named) or `EASY` (top). One experiment, and
`DIFFICULTY` is the screen that can carry it. ⚠️ Its exit path crashes the guest
(`SELECT DATA`, `PC 0x82307128`), so the run must go **back** rather than forward.
📌 The failure worth naming is mine: **I searched for a case among the screens I
was measuring, and never grepped the page I was editing.** An answer already in the
corpus is only as good as the reader's ability to connect it — the same amplifier
problem as the stale index row, arriving from the other direction.
## ✅ 2026-08-31 — ALL FOUR measured submenus reset. The main menu is the exception.
**measured** — [`data/submenu-focus-all-reset.txt`](data/submenu-focus-all-reset.txt),
`tools/re-capture/submenu_focus_sweep.py`, one boot, fourth attempt.
| screen | verdict | in-cursor: from opened / from where left |
|---|---|---|
| `LOAD GAME` | **RESETS** | 21.6 / 86.3 |
| `TUTORIAL` | **RESETS** | 2.3 / 113.2 |
| `OPTIONS` | **RESETS** | 1.9 / 102.6 |
| `EXTRAS` (2026-08-30) | **RESETS** | 0.8 / 82.8 |
| **main menu** | **PERSISTS** | — |
✅ **So the rule is simple after all, and it is the opposite of what one screen
suggested**: submenus reset; the **main menu alone** remembers. Four of four.
**Screens confirmed by eye**, because an earlier run was fooled about which
screen it was on: [`TUTORIAL`](captures/menu-nav/live-tutorial-submenu.png) — seven
items under `Level 1`/`Level 2` headers plus `BACK` — and
[`LOAD GAME`](captures/menu-nav/live-load-game-slots.png), a **scrolling** slot list
with the selection held at the vertical centre.
📌 `LOAD GAME`'s 21.6 is the only figure not near zero, and the capture explains it:
that list **scrolls** instead of moving a ring, so re-entry restores a scroll
position rather than a cursor sprite. Same verdict, different mechanism.
**Still not separated: "resets to the named item" vs "resets to the top item".**
`sylpheed-port` asked for a submenu whose opening item is not its first. None of
these is one — `TUTORIAL` opens on `BASIC CONTROLS`, `OPTIONS` on `GAME SETTINGS`,
and `LOAD GAME` on slot **01**, which *looks* non-first only because slots 19 and 20
are drawn above it by a wrapping list around a centred selection.
⚠️ Reach: one boot, one round trip per screen, one direction, one entry each.
`NEW GAME` stays deliberately untested.
## ✅ 2026-08-30 — EXTRAS **resets**; the main menu **persists**. They differ.
**measured** — [`data/extras-focus-resets.txt`](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-port` asserted 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 SELECT` is 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.png` is 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 with `boot_menu.sh`'s own closing line and with
[`menu-state-in-memory.md`](menu-state-in-memory.md)'s four-downs-to-`EXTRAS`,
which only counts from `NEW 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`, truth `NEW 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.**
[data](data/menu-focus-reader-offset.txt)
## ✅ 2026-08-30 — the menu REMEMBERS its cursor across menu → title → menu
**measured** — [`data/focus-persists-across-title.txt`](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`](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](harness-title-gate-assumes-a-static-title.md)).
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`](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` | 🔴 **stale row, corrected 2026-09-12** — measured 2026-08-28: `DIFFICULTY` (`EASY`/`NORMAL`/`HARD`/`BACK`) → `SELECT DATA` → guest crash (unrelated cache-manager bug, not a menu fault). The "`NEW GAME` — measured" section further down this same page had the answer already and this table was never updated to match it | [captures](captures/newgame-path/) | `DIFFICULTY`: **no id-table name match** — already tried and refuted (`boot-config-and-gamepart-registry.md`: neither `DIFFICULTY` nor `EXTRA_MENU` survive as call-site strings). `SELECT DATA`: 🟡 `2 GP_SELECT_STORAGE`, a fresh name-guess, same low confidence as every other row here |
| `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.**~~ **Withdrawn — stale, fixed
2026-09-12.** This line said Ⓐ on it hangs the emulator
([`ui-paint-order-third-permutation.md`](ui-paint-order-third-permutation.md)),
which the very next section of this same page already refuted on
2026-08-28: it does not hang, it opens `DIFFICULTY` then `SELECT DATA` before
an unrelated crash. Kept struck rather than deleted — this is the exact
failure this corpus keeps naming, a correction that landed lower in the
same document than the claim it corrected and never caught up to the table
above.
### 🔴 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`](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 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 — 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`](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`](../../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 ~450520 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 ~810 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 ~810 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 28 |
|---|---|---|
| `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 28 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.