Closes the caveat I flagged in 4ae63cb. savegame.rs documents the chunk stream
(read off the title's own serializer at 0x822C00E8) as GDAA / phase string /
'GHAD' + 122 bytes / SHAB table, and FieldSpec.offset is "offset within the
122-byte GHAD block" -- so the base is the byte after the tag, tag+4.
Reading there yields three fields, two corroborated independently elsewhere in the
corpus: Stage +52 = 2, Points +24 = 4101 (weapon-datasheet-runtime.md's "4101 P"),
FlightTime +4 = 324773 (the 05:24.77 Stage-01 best time). tag+8 reproduced only
the stage. The payload's phase string reads GP_BUNK.
Left open: the .header mirror does not reproduce -- savegame.rs lists Stage at
header offset 0x14 but a BE u32 there reads 2097200 (00 20 00 30), which looks
like UTF-16 text. Not chased; the payload reading does not depend on it.
5.3 KiB
✅ The stage drift is my NAVIGATION, not the save — slot 01 is deterministically Stage 02
Settled 2026-08-26, statically — no emulator needed.
❌ Refuted: "slot 01 is the auto-save, so the restored mission moves between runs"
The previous note blamed save drift for one run
loading the S01 training area (with UN_S01_Asteroid_cmesh_*) and another
loading a Stage-02-style escort roster. The file says otherwise:
2026-08-23 16:37 278 .../535107D4/00000001/game01/savedata
2026-08-26 22:18 26596 .../FFFE07D1/.../535107D4.gpd
savedata has not been written since 23 August. Every run today left it
untouched; only the .gpd profile/achievement files moved. And there is exactly
one save (game01), so a wandering save-list cursor cannot explain it either.
✅ What the save actually holds: Stage 02
The save is GDHA + a zlib payload at 0x92 (545 bytes decompressed), carrying
GDAA, GHAD, NETA, 2 × BUNK and the 16-record SHAB table.
rec 0 (stage 1): (2, 4101, 324773, ...)
rec 1 (stage 2): (0, 0, 0, ...)
...
rec 15 (stage 16): (0, 0, 0, ...)
Only record 0 carries clear data; records 1–15 have zeros in all three result fields. Three independent readings agree on what that means:
324773ms = 05:24.77, whichSESSION-2026-08-11.mddocuments as exactly what MISSION SELECT displays as the Stage 01 best time.4101matchesstructures/weapon-datasheet-runtime.md, whose capture session is recorded as "Stage 02, 'At Standby', 5 % clear, 4101 P" — the same save, already described in the corpus.- Reading the
GHADfield block at tag+4 gives+52= 2, the documented 1-basedStagefield.
So LOAD GAME → slot 01 restores Stage 02 every time. It is deterministic.
✅ The base is pinned — and it was not tag+8
The caveat here originally said brute force gave seven candidate offsets and that I had picked tag+8 because it agreed with the other readings rather than because the search isolated it. The authoritative parser settles it:
crates/sylpheed-formats/src/savegame.rsdocuments the chunk stream, read off the title's own serializer at0x822C00E8, as'GDAA' payload magic u32 len, char[len] current game phase, e.g. "GP_BUNK" 'GHAD' + 122 bytes the progress block u32 16, 16 x ('SHAB' + 5 x u32) the per-stage record tableand
FieldSpec.offsetis "Offset within the 122-byte GHAD block" — so the base is tag+4, where the block begins. Reading there gives three fields at once, two of which match values documented independently elsewhere in the corpus:
field GHAD offset value corroboration Stage+52 2 Stage 02 = the Acropolis escort mission Points+24 4101 weapon-datasheet-runtime.md: "4101 P"FlightTime+4 324773 the 05:24.77 Stage-01 best time Two independently-documented values landing at once is what a correct base looks like; tag+8 reproduced only the stage. The phase string in the same payload reads
GP_BUNK— the save sits in the bunk between missions.❔ The
.headermirror did not reproduce:savegame.rslistsStageat header offset0x14, but reading a big-endian u32 there gives2097200(00 20 00 30), which looks like UTF-16 text rather than a mirrored word. Not chased — the payload reading is confirmed and does not depend on it.
❌ And "stage = filled SHAB count + 1" must not be used: all 16 records are
present, and the corpus already lists that rule as refuted. Presence is not
filledness.
✅ So the S01 asteroid run was never the save
Stage 02 is the Acropolis escort mission — which is precisely the roster the
most recent run produced (UN_f101_TCAF_Acropolis, UN_bf001_TCAF_SchlosBase,
cruisers, frigates). The odd run out is the earlier one, whose briefing read
"Glasner Training Area" — a tutorial, not a story stage, and
structures/unit-struct-runtime.md confirms
tutorials are separately reachable ("all six tutorials and Stage 02").
The cause is in my own driving: that run's navigation log shows the menu probe
reading other repeatedly and the dpad down + Ⓐ being issued from a state that
was not confirmed to be the main menu, so the selection landed on a tutorial
entry instead of LOAD GAME. The nav taps a fixed sequence and never verifies
what it selected.
Consequence — the cheap fix
Runtime rosters are reproducible after all, provided the route is verified. Two one-line guards, neither of which needs new tooling:
- Confirm
screen_id.pyreadsmenuimmediately before thedpad down, not merely at some point earlier in the loop. - After reaching flight, assert the definition table contains
UN_f101_TCAF_Acropolis(Stage 02's signature) before trusting any comparison against an earlier run.
❔ Not settled: I did not re-run the emulator to demonstrate the tutorial mis-selection directly. The evidence here is the save file plus the earlier run's own screen log and briefing title, which is strong but circumstantial about which menu entry was hit.