From 5d49f54c923b32c2ed2f2ff0b671aa772b0069bf Mon Sep 17 00:00:00 2001 From: Sylpheed RE agent Date: Wed, 26 Aug 2026 07:39:13 +0000 Subject: [PATCH] re: the save embeds live guest pointers, and a three-way diff of the captures is invalid Comparing the two same-state saves (both 276 bytes), only 12 of 69 u32 words differ, and nine of those carry guest addresses -- 0xBC/0xBD/0x70 prefixes, the same regions the runtime work uses. Three differ by exactly 0x101080 and two by exactly 0x300000: a shared constant offset is what a relocated heap does to a pointer and not what data does. The split is sharp: pointer-shaped words are 26% of the file and 44% of them differ; non-pointer words are 8%. So a quarter of this structure is captured heap addresses that change run to run regardless of play. That is the concrete form of the page's existing 'much of the rest is uninitialised memory', now demonstrated by constant deltas rather than inferred from odd-looking values. Also records that my first pass was wrong: a byte-wise three-way diff reported 135 of 276 bytes differing, but game03 is 280 bytes with a ZERO-byte common suffix, so offsets do not correspond past 0x00A. The valid equal-length pair gives 30 bytes in 12 words. The inflated figure looked plausible -- half a save changing is what dense state would do -- which is why it is written down. --- docs/re/structures/savegame-format.md | 58 +++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/docs/re/structures/savegame-format.md b/docs/re/structures/savegame-format.md index f1757f5d..9c0c4345 100644 --- a/docs/re/structures/savegame-format.md +++ b/docs/re/structures/savegame-format.md @@ -573,3 +573,61 @@ 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. + +## ✅ The save embeds live guest pointers — which is why diffing saves misleads + +**2026-08-26.** Comparing the two committed saves that represent the **same game +state** (`savedata-stage02-5pct.bin` and `savedata-game02-samestate.bin`, both +276 bytes), only **12 of 69 `u32` words differ** — and most of those are not +data. + +**Nine carry guest addresses**: values with a `0xBC…`, `0xBD…` or `0x70…` +prefix, the same regions the runtime work uses (`0xbdb59668` for the objective +counter, `0x820af030` for entity records). Three of them differ by **exactly +`0x101080`** and two by exactly `0x300000` — a shared constant offset is what a +relocated heap does to a pointer, and it is not what data does. + + +0x04C 0xBD4B6200 -> 0xBD3B5180 delta 0x101080 + +0x064 0xBD4B6330 -> 0xBD3B52B0 delta 0x101080 + +0x06C 0xBD4B6338 -> 0xBD3B52B8 delta 0x101080 + +0x070 0x70D8FD50 -> 0x70A8FD50 delta 0x300000 + +0x080 0x70D8FD60 -> 0x70A8FD60 delta 0x300000 + +The split is sharp across the whole file: + +| | words | differ between same-state saves | +|---|---|---| +| pointer-shaped (`0x70–0x8F`, `0xB0–0xBF`) | 18 of 69 (26 %) | **8 (44 %)** | +| everything else | 51 | **4 (8 %)** | + +So **a quarter of this structure is captured heap addresses**, and they change +run to run regardless of what the player did. This is the concrete form of the +page's existing "much of the rest is uninitialised memory" — now demonstrated by +the constant deltas rather than inferred from the values looking odd. + +**Practical consequence**: a byte diff between two saves is dominated by pointer +churn. Only the 51 non-pointer words carry state worth attributing, and of those +just four move between identical states: + + +0x008 0x01DD1ADF -> 0x01DD2956 high half `0x01DD` stable, low half moves + +0x00C 0x144F6CDA -> 0x120EAB37 + +0x038 0xDD0FF734 -> 0xAF0C96D7 + +0x084 0x006800C0 -> 0x006E3A40 + +🟡 `+0x008` looks like a counter or timestamp — a stable high half with a moving +low half is that shape. The other three look unstructured; a checksum would fit +but nothing here tests it. + +### ⚠️ A three-way diff of these files is invalid + +My first pass diffed all three saves byte-wise and reported **135 of 276 bytes +(48.9 %) differing**. That number is wrong. `savedata-game03-developed-mg1.bin` +is **280 bytes**, not 276, and the longest common suffix with the others is +**zero bytes** — so the files are not a common layout with an insertion, and +comparing offset *i* across them is meaningless past the first divergence at +`0x00A`. The valid comparison is the equal-length pair, and it gives **30 bytes +in 12 words**, not 135. + +Worth stating because the inflated figure looked plausible: half the file +changing between saves is exactly what one would expect if saves were dense +state, and nothing about it invites a second look.