diff --git a/docs/re/captures/savegame-stage05-probe-details.png b/docs/re/captures/savegame-stage05-probe-details.png new file mode 100644 index 0000000..157d459 Binary files /dev/null and b/docs/re/captures/savegame-stage05-probe-details.png differ diff --git a/docs/re/captures/savegame-stage05-probe-inflight.png b/docs/re/captures/savegame-stage05-probe-inflight.png new file mode 100644 index 0000000..f1a1e46 Binary files /dev/null and b/docs/re/captures/savegame-stage05-probe-inflight.png differ diff --git a/docs/re/structures/savegame-format.md b/docs/re/structures/savegame-format.md index 7a9ad1d..f1757f5 100644 --- a/docs/re/structures/savegame-format.md +++ b/docs/re/structures/savegame-format.md @@ -76,7 +76,7 @@ photographed against this exact save (see "Naming the fields", further down). | +0 | 0 | ❔ | | +4 | **324773** | ✅ **flight time, milliseconds** — the screen shows `Flight Time 000:05:24` and 324773 ms = 5 m 24.773 s | | +8 | 5 | ✅ **clear ratio, percent** — the screen shows `Clear Ratio 5 %`. **It is not a stage counter**: developing one Arsenal weapon stepped it to 6 (see [the develop differential](#the-develop-differential-one-weapon-three-fields)) | -| +12 | 0 | ❔ | +| +12 | 0 | 🟡 **Times Cleared** — mirrored to header `+0x28`, and the panel prints `Times Cleared: 0` | | +16 | 1 | ❔ | | +20 | 0 | ❔ | | +24 | **4101** (`0x1005`) | ✅ **Points — the spendable balance.** Named from the Details panel, and **separated from +28** by the develop differential: spending 4000 P moved *only* this field (4101 → 101) and the panel then read `Points 101 P` | @@ -84,8 +84,8 @@ photographed against this exact save (see "Naming the fields", further down). | +32 | 79 | ❔ | | +36 | 2 | ❌ **not** difficulty and **not** stage — refuted by probe saves (see below) | | +40 (u64) | 2014400 | ❔ | -| +48 | 0 | ❔ (`Times Cleared: 0` is on screen, so it is one of the zero fields) | -| +52 | 2 | ❌ see +36 — refuted | +| +48 | 0 | 🟡 **Game Status enum** — mirrored to header `+0x18`; `0` renders `At Standby`, matching the screen's own `STATE_STAND_BY / STATE_STAGE_CLEAR / STATE_GAME_CLEAR` list | +| +52 | 2 | ✅ **STAGE NUMBER, 1-based** — set it to 5 (with the header mirror, below) and the game reads `STAGE 05 — Star System Escape`, loads it, and **flies Stage 05** | | +56 | 2 | ❌ see +36 — refuted | | +60 | 0 | ❔ | | +64 (raw 4) | `09 15 00 00` | ❔ the trailer's u32 is the **same** value | @@ -520,3 +520,56 @@ have no oracle that a probe can read. The two routes that do have one are 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. + +## ✅ SOLVED: `+52` is the stage, and the save picks the mission (2026-08-13) + +**One `u32` chooses which of the 16 story stages the game plays.** Set GHAD `+52` +(and its header mirror, below) to 5 and the title reads +`STAGE 05 — Star System Escape`, loads it, briefs the **Gallia Asteroid Group**, +and takes off into an asteroid field with a different objective, roster and pilot +chatter — from a save whose only edited bytes are that field and its mirror +([details panel](../captures/savegame-stage05-probe-details.png) · +[in flight](../captures/savegame-stage05-probe-inflight.png)). + +### Why the earlier probes said "not the stage" + +The header builder at `0x822870b4…0x82287128` copies a **summary of the progress +block into the GDHA container header**, and the screens read *that*: + +| header | ← progress block (GHAD offset) | in this save | +|---|---|---| +| `+0x10` | `+0` | 0 | +| `+0x14` | **`+52`** | 2 → the panel's `STAGE 02` | +| `+0x18` | `+48` | 0 → `Game Status: At Standby` | +| `+0x1c` | `+24` | 4101 → `Points` | +| `+0x20` | `+4` | 324773 → `Flight Time` | +| `+0x24` | *computed* (`0x822842B0`, also cached to `+8`) | 5 → `Clear Ratio` | +| `+0x28` | `+12` | 0 → `Times Cleared` | + +`savegame_edit.py` copies the donor header verbatim, so a payload-only edit leaves +that summary stale and the panel keeps showing the **old** stage — which is exactly +what made `+52` look refuted. **Patch both** (`hdr[0x14] = stage` and +`ghad[+52] = stage`) and the whole chain follows. The payload writer +`0x822BF678` walks the block field-by-field from `save+8`, so payload offset == +progress-block offset; the block itself sits at **game-state `+312`** (Points at +`+336` = `312+24`, flight at `+316`), which is how the header mapping was read off +the disassembly rather than guessed. + +The content header's **display string** (`Game01 07/23/2026 21:08 STAGE02 EASY`) +is separate and was *not* patched, so the slot row still reads `STAGE02` while the +Details panel reads `STAGE 05` — harmless, and a useful reminder that the row and +the panel have different sources. + +### What this unlocks + +Any story stage is now reachable without playing to it: write the probe into +slot 01 (back it up first — restore verified byte-identical here) and the existing +`tools/re-capture/launch_mission.sh` route loads it and takes off. That opens +**runtime captures in containers other than `Stage_S02`** — the remaining 85 XBG7 +misses, the 24-vertex box identity, ship-placement generalisation to other classes +— and **per-stage unit definitions** for the Route-B coverage that grows by +visiting missions. + +Still open: `+36` and `+56` (both 2, one of which is likely `Difficulty`, since the +header carries no difficulty field and the display string does), and the +`+32 = 79` / `+40 = 2014400` / `+64 = 09 15 00 00` fields.