The names in menu-state-in-memory.md are wrong for at least two of the three
words, and the test that shows it is going backwards. Driving deep into the
menus, then three presses of B:
0x828F38AC "cursor" 36 -> 38 -> 40 -> 41
0x828F37B4 "misc" 971 -> 1067 -> 1068 -> 1068
0x828A690C "screen" 56 -> 56, 56, 56
A cursor returns when you go back. These only ever increase -- monotonic
counters, with 0x828F38AC advancing about 2 per input. Driving forward produced
1,3,4,5,6,8,10,12,25,29,32,33,50,53,56 for the "screen id", which is an identity
sequence only if the game has 56+ screens and never revisits one -- exactly what
a counter also looks like.
Withdrawn: 0x828A690C as a screen IDENTITY (1 title, 3 main menu, 4 extras). The
values are path-dependent; they matched across runs because the same key sequence
produces the same count, not because 3 means main menu.
Survives: all three advance if and only if the game responds, and are stable when
it does not. That is a real input-progress signal, reproducible across runs and
both GPU backends, and it is what made blind navigation work. Read it as "did
the game react?", never "which screen is this?".
It also retro-confirms the freeze diagnosis: with the sign-in fix the sequence
runs 3 -> 5, skipping 4 entirely, so "4 = extras" was never a screen -- it was the
count at which the game stopped responding. The counter reading explains both
observations.
Open: no mission reached. Counters at 56/41/1068, guest churn 0.190% (alive;
frozen is 0.000%), DEF_VTABLE and INST_VTABLE still 0. Without a real screen
identity, navigation is dead reckoning; finding a genuine state enum is next, and
the snapshot-and-diff method can be repeated with these counters excluded.
102 lines
4.8 KiB
Markdown
102 lines
4.8 KiB
Markdown
# The menu's screen id and cursor, in guest memory
|
||
|
||
**✅ Found and verified 2026-08-26.** Menu navigation in this corpus has always
|
||
been driven by screenshots. That is unusable under `--gpu=null`, which is the
|
||
only configuration where the game does not hit the software-rasterizer freeze
|
||
([`mission-freeze-heap-exhaustion.md`](mission-freeze-heap-exhaustion.md)) — so
|
||
without a memory signal, the one backend that runs is the one that cannot be
|
||
steered.
|
||
|
||
## The words
|
||
|
||
| address | meaning |
|
||
|---|---|
|
||
| **`0x828A690C`** | **screen id** — `1` title, `3` main menu, `4` extras |
|
||
| **`0x828F38AC`** | **menu cursor** (a second copy at `0x828F38BC`) |
|
||
| `0x828F37B4` | tracks the screen, distinct value per menu |
|
||
|
||
## How they were found
|
||
|
||
Snapshot `0x82800000` + 3 MB at each menu of a **rendered** run, then keep the
|
||
4-byte words that (a) differ between screens and (b) hold small integer values.
|
||
Of 786 432 words, exactly **four** qualified — and one of them,
|
||
`0x828A690C`, has the property that matters: it changes on a screen *transition*
|
||
and stays put when only the highlight moves.
|
||
|
||
screen: title -> main menu -> (4x down) -> extras
|
||
828a690c: 1 -> 3 -> 3 -> 4 <- screen id
|
||
828f38ac: 2 -> 4 -> 12 -> 14 <- cursor moves with the highlight
|
||
828f37b4: 12 -> 45 -> 45 -> 49
|
||
|
||
## ✅ Verified against a second run on a different GPU backend
|
||
|
||
The values were then read on a fresh `--gpu=null` run — **no display at all** —
|
||
while driving the same key sequence blind:
|
||
|
||
| step | rendered (lavapipe) | blind (`--gpu=null`) |
|
||
|---|---|---|
|
||
| title | 1 / 2 / 12 | **1 / 2 / 12** |
|
||
| main menu | 3 / 4 / 45 | **3 / 4 / 45** |
|
||
| after 4× down | 3 / 12 / 45 | **3 / 12 / 45** |
|
||
| extras | 4 / 14 / 49 | **4 / 14 / 49** |
|
||
|
||
**4 / 4 exact**, across two runs and two backends. That is the check that matters:
|
||
the words are not run-dependent, unlike the OB counter address this corpus had to
|
||
mark run-dependent earlier.
|
||
|
||
`tools/re-capture/menu_state.py` reads them (`menu_state.py watch` polls and
|
||
prints on change).
|
||
|
||
❔ Still open: the rest of the sequence into a **mission**. Blind driving reached
|
||
`extras` (screen 4) and a further Ⓐ did not move it — `4 / 14 / 49` twice — so
|
||
MISSION SELECT needs a cursor move first. The screen ids beyond 4 are unmapped;
|
||
the same snapshot-and-diff method extends to them, but it must be done on a
|
||
rendered run, and the rendered run **freezes on entering that screen**. The way
|
||
through is to map the ids up to the freeze and step blind past it.
|
||
|
||
---
|
||
|
||
# 🔴 Correction: these are **counters**, not a screen id and a cursor
|
||
|
||
**2026-08-26, same day.** The names above are wrong for at least two of the three
|
||
words, and the test that shows it is going **backwards**.
|
||
|
||
Driving deep into the menus and then pressing Ⓑ (back) three times:
|
||
|
||
| | before | after 3× Ⓑ |
|
||
|---|---|---|
|
||
| `0x828F38AC` ("cursor") | 36 | **38 → 40 → 41** |
|
||
| `0x828F37B4` ("misc") | 971 | **1067 → 1068 → 1068** |
|
||
| `0x828A690C` ("screen id") | 56 | **56, 56, 56** |
|
||
|
||
A cursor returns when you go back. These **only ever increase** — they are
|
||
**monotonic counters**, and `0x828F38AC` advances by ~2 per input. Driving
|
||
forward gave `1, 3, 4, 5, 6, 8, 10, 12, 25, 29, 32, 33, 50, 53, 56` for the
|
||
"screen id": that is a plausible identity sequence *only* if the game has 56+
|
||
screens and you never revisit one, which is precisely what a counter also looks
|
||
like.
|
||
|
||
🔴 **Withdrawn**: "`0x828A690C` = screen id (1 title, 3 main menu, 4 extras)" as
|
||
an *identity*. The values are path-dependent — they match across runs because the
|
||
same key sequence produces the same count, not because 3 *means* main menu.
|
||
`0x828F38AC` is not a cursor and `0x828F37B4` is not a per-menu value.
|
||
|
||
✅ **What survives, and is still useful**: all three advance **if and only if the
|
||
game responds**, and they are stable when it does not. That is a genuine
|
||
input-progress signal, reproducible across runs and both GPU backends, and it is
|
||
what made blind navigation work. Read it as *"did the game react?"*, never as
|
||
*"which screen is this?"*.
|
||
|
||
✅ **And it retro-confirms the freeze diagnosis.** With the sign-in fix the
|
||
sequence runs `3 → 5`, **skipping 4 entirely** — so the old "4 = extras" was never
|
||
a screen at all: it was the count at which the game stopped responding, i.e. the
|
||
blocked state. Two readings of the same number, and the counter reading explains
|
||
both.
|
||
|
||
❔ Still open: a mission is not reached. After a dozen presses the counters are at
|
||
`56 / 41 / 1068`, guest churn is **0.190 %** (alive — frozen is 0.000 %), and
|
||
`DEF_VTABLE` / `INST_VTABLE` scans are still **0 / 0**. Without a real screen
|
||
identity, navigation is dead reckoning; finding an actual screen/state *enum* is
|
||
the next job, and the snapshot-and-diff method that found these counters can be
|
||
repeated with the counters themselves excluded.
|