This repository has been archived on 2026-09-16. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Syplheed-Reborn/docs/re/menu-navigation-semantics.md
Sylpheed RE agent 935f7ecab8 re: one A skips the intro, and NEW GAME was never a hang
Two corpus claims died in one boot, and both were costing us.

Q9's last row: a movie IS skippable with a single A. One tap ~45s into
the boot put the title up at ~57s, against a ~193s no-input baseline
measured over three boots, and Canary's own keystroke counter went 3->4
so exactly one press was delivered. The skipped-to title is fully
functional -- it draws the PRESS A plate and a second A opens the main
menu. What actually breaks the boot is hammering: the 88-press run in the
traps doc. The scripts' "tapping breaks the title" comment is too broad
and costs every scripted boot two and a half minutes.

Q4's last row: A on NEW GAME does not hang. It opens DIFFICULTY
(EASY/NORMAL/HARD/BACK, focus on NORMAL), then SELECT DATA, and only then
does the guest throw -- at PC 0x82307128, which is inside sub_823070B0,
the cache-manager STL erase this corpus already documents and which has
nothing to do with the menu path. The screen sat unchanged for 90s
because it was a menu waiting for input from a loop that never pressed
anything. That is now a METHOD line: a screen that never changes is not
necessarily hung, and the fix is to look at it and press something.

Also METHOD: never run ps -ef in this container -- all three long-lived
processes carry the entire loop prompt as argv.
2026-08-28 18:45:59 +00:00

120 lines
6.7 KiB
Markdown

# 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 | |
| **Ⓑ 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`](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 | | 🟡 `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.**