re(savegame): stage-unlock probes are negative, and the Details panel is not a payload oracle
Four probes against slot 01 (the save the title-menu screens read), each backed up and restored byte-identically: - SHAB[1..7] filled -> MISSION SELECT still locks Stage02-08, cursor will not leave Stage01. The per-stage record table is not the unlock gate; the earlier SHAB[1] attempt used a throwaway slot and could not show this. - GHAD +36/+52/+56 = 5 -> still locked, panel still STAGE 02 / EASY. - GHAD +16 = 4 -> panel still STAGE 02, so it is not a 0-based stage index. - live-RAM writes into the loaded GDHA header -> panel unchanged. The useful part is why the panel cannot answer this: guest RAM at that screen holds NO payload bytes (neither the 4101/4101/79 triple nor the 54-byte develop blob), but it does hold the save's GDHA container header with a summary copy of Points, flight time, clear ratio and a FILETIME. savegame_edit.py copies the donor header verbatim, so a payload edit leaves that summary stale — "the panel did not change" therefore cannot separate "not the stage field" from "the panel never reads the payload". Future stage/difficulty probes must patch the header too, or be judged by what the game does on load. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NptfmpjdpNCKEez6d2xvA9
This commit is contained in:
@@ -466,3 +466,57 @@ strings are reached through a config lookup, not an immediate, so there is no
|
||||
disassembling the save-screen's populate path, not another grep.
|
||||
|
||||
Editing beyond a throwaway slot is still the user's call.
|
||||
|
||||
## Stage unlock: four probes, all negative — and a method correction (2026-08-13)
|
||||
|
||||
The motivating question ("can stage progress be reached without winning
|
||||
missions?") got its first tests **against slot 01**, the save the title-menu
|
||||
screens actually read. Slot 01 was backed up, edited, and restored
|
||||
byte-identically after each run (md5 `142b4f43…`, verified twice).
|
||||
|
||||
| probe (in slot 01) | oracle | result |
|
||||
|---|---|---|
|
||||
| `SHAB[1..7]` filled with record 0's shape | `EXTRAS → MISSION SELECT` | **Stage02–08 stay greyed out**; the cursor will not leave Stage01 (two d-pad steps, no movement) |
|
||||
| GHAD `+36 = +52 = +56 = 5` | MISSION SELECT + LOAD GAME Details for slot 01 | **still locked**, panel **still `STAGE 02`/`EASY`** |
|
||||
| GHAD `+16 = 4` (a 0-based stage index would read `STAGE 05`) | LOAD GAME Details for slot 01 | **still `STAGE 02`** |
|
||||
| live-RAM writes into the loaded container header (Points, and its `+0x14`) | LOAD GAME Details, re-rendered by moving the cursor off and back | **no change** |
|
||||
|
||||
So the per-stage record table is **not** the unlock gate — that is now tested on
|
||||
the save the screens read, where the earlier `SHAB[1]` attempt was written to a
|
||||
throwaway slot and could prove nothing. And `+16` joins `+36/+52/+56` as **not**
|
||||
the displayed stage; the earlier refutation of those three stands, now confirmed
|
||||
against slot 01 itself.
|
||||
|
||||
**The method correction, which is the useful part.** Searching guest RAM at the
|
||||
LOAD GAME screen finds **no payload bytes at all** — neither the `4101/4101/79`
|
||||
triple nor the 54-byte develop blob is anywhere in the guest address space — but
|
||||
it does find the save's **`GDHA` container header**, loaded verbatim, carrying a
|
||||
summary copy:
|
||||
|
||||
```
|
||||
+0x00 'GDHA' +0x04 136 +0x08 FILETIME (2026-07-23 20:07:23)
|
||||
+0x14 2 +0x1c 4101 (Points) +0x20 324773 (flight ms) +0x24 5 (clear ratio)
|
||||
```
|
||||
|
||||
Two consequences:
|
||||
|
||||
1. **The Details panel is not a payload oracle.** The numbers it shows exist in
|
||||
the *header* too, and `savegame_edit.py` copies the donor header verbatim — so a
|
||||
payload edit leaves a stale summary beside it, and "the panel did not change"
|
||||
cannot distinguish *"that field is not the stage"* from *"the panel never reads
|
||||
the payload"*. Any future stage/difficulty probe must patch the header copy as
|
||||
well, or be judged by what the game **does** on load rather than by the panel.
|
||||
(This does not disturb the Points/flight-time/clear-ratio namings: those values
|
||||
sit at both places and the two agree.)
|
||||
2. The panel is **not** live-editable either: writing the header copy in
|
||||
`/dev/shm` and forcing a re-render changed nothing, so the strings are formatted
|
||||
before that copy is consulted again.
|
||||
|
||||
**Where this leaves the question.** Field guessing is now measurably the wrong
|
||||
tool: three of the four candidate fields are eliminated and the remaining ones
|
||||
(`+0`, `+12`, `+20`, `+32 = 79`, `+40 = 2014400`, `+48`, `+60`, `+64 = 09 15 00 00`)
|
||||
have no oracle that a probe can read. The two routes that do have one are
|
||||
**static analysis of the loader** — find what the title reads when it decides the
|
||||
current mission and the selectable set, the same way the serializer at
|
||||
`0x822C00E8` was found — or **clearing a stage in game**, which also settles
|
||||
`Times Cleared` and whether `SHAB[1]` fills.
|
||||
|
||||
Reference in New Issue
Block a user