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-state-in-memory.md
Sylpheed RE agent 534da24658 re: find the menu screen id and cursor in guest memory
Menu navigation here has always been screenshot-driven, which is unusable under
--gpu=null -- the only configuration where the game does not hit the
software-rasterizer freeze.  Without a memory signal, the one backend that runs
is the one that cannot be steered.

Snapshotting 0x82800000+3 MB at each menu of a rendered run and keeping the
4-byte words that differ between screens and hold small integers leaves exactly
four of 786 432.  One has the property that matters -- it changes on a screen
transition and holds steady when only the highlight moves:

    0x828A690C  screen id     1 title, 3 main menu, 4 extras
    0x828F38AC  menu cursor   (second copy at 0x828F38BC)
    0x828F37B4  per-menu value

Verified on a fresh --gpu=null run with no display at all, driving the same keys
blind: title 1/2/12, main menu 3/4/45, after 4x down 3/12/45, extras 4/14/49 --
4/4 exact against the rendered run, across two runs and two GPU backends.  That
is the check that matters, since this corpus has already had to mark one runtime
address run-dependent.

tools/re-capture/menu_state.py reads them; `menu_state.py watch` prints on
change.

Open: the rest of the sequence into a mission.  Blind driving reached extras
(screen 4) and a further A did not move it, so MISSION SELECT needs a cursor
move first.  Screen ids beyond 4 are unmapped, and the rendered run freezes on
entering that screen -- so map ids up to the freeze, then step blind past it.
2026-08-26 18:21:04 +00:00

2.5 KiB
Raw Blame History

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) — so without a memory signal, the one backend that runs is the one that cannot be steered.

The words

address meaning
0x828A690C screen id1 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.