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.
This commit is contained in:
Sylpheed RE agent
2026-08-26 07:39:13 +00:00
parent 89ceb1e97e
commit 5d49f54c92

View File

@@ -573,3 +573,61 @@ visiting missions.
Still open: `+36` and `+56` (both 2, one of which is likely `Difficulty`, since the 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 header carries no difficulty field and the display string does), and the
`+32 = 79` / `+40 = 2014400` / `+64 = 09 15 00 00` fields. `+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 (`0x700x8F`, `0xB00xBF`) | 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.