diff --git a/docs/re/stage-drift-is-navigation-not-save.md b/docs/re/stage-drift-is-navigation-not-save.md index 83fea9a4..867875e7 100644 --- a/docs/re/stage-drift-is-navigation-not-save.md +++ b/docs/re/stage-drift-is-navigation-not-save.md @@ -39,15 +39,44 @@ fields. Three independent readings agree on what that means: [`structures/weapon-datasheet-runtime.md`](structures/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. -3. Reading the `GHAD` field block at tag+8 (i.e. past the tag and its length word) - gives `+52` = **2**, the documented 1-based `Stage` field. +3. Reading the `GHAD` field block at **tag+4** gives `+52` = **2**, the + documented 1-based `Stage` field. So `LOAD GAME → slot 01` restores **Stage 02** every time. It is deterministic. -🟡 One caveat, stated rather than smoothed over: brute-forcing the `GHAD` field -base gives **seven** candidate offsets whose `+52` is a plausible stage, three of -them yielding 2. I picked tag+8 because it is the natural layout and it agrees -with the two independent readings above — not because the search isolated it. +> ### ✅ 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.rs` documents the chunk stream, read off +> the title's own serializer at `0x822C00E8`, as +> +> ```text +> '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 table +> ``` +> +> and `FieldSpec.offset` is "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 `.header` **mirror** did not reproduce: `savegame.rs` lists `Stage` at +> header offset `0x14`, but reading a big-endian u32 there gives `2097200` +> (`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