re: pin the GHAD field base -- it is tag+4, not the tag+8 I guessed

Closes the caveat I flagged in c9dc8dd.  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.
This commit is contained in:
Sylpheed RE agent
2026-08-26 22:39:02 +00:00
parent c9dc8dd4db
commit 67001f04fb

View File

@@ -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