re: partial field map inside the live definition object, and why the automation failed

Anchoring on a unit whose disc record sets a field locates it in the live object:
Size_X/Y/Z (+0x30/34/38) and HP (+0x54) confirmed across five capital ships and
the player fighter; +0x74 (0.8) and +0x84 (0.97) are probable ThrusterRatio and
ResistanceParalyze but rest on a single anchoring unit; +0x40 varies per unit and
is unidentified.

live_offsets.rs automates the correlation and currently produces one hit which is
false -- YawDragFactor 2.0 collided with the integer 2 at +0x0c stored as a
denormal. Causes recorded: get_f32 is unreliable on default-heavy IDXD records
because the value-before-key pairing shifts, and 96 words only reaches +0x180
while RadarRange/FCSRange sit beyond it. Next run dumps 512 words.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-12 20:36:57 +00:00
parent 3322202732
commit 892f1e3979
3 changed files with 151 additions and 0 deletions

View File

@@ -114,3 +114,42 @@ not addressed. And this run only reaches the units Stage 02 instantiates: the
**not** resolved here, because those fields' offsets in the live object are not
yet known. Finding them is the next run: dump deeper than 96 words and look for
the constants the disc *does* set on the records that set them.
## Locating fields inside the live object
The next step — reading the `…Ratio` / `…Count` family — needs to know *where*
each field sits in the live object. The method is to anchor on a unit whose disc
record **sets** a field and look for that value in its dumped words. Anchoring on
`UN_f106_TCAF_Destroyer` (disc sets `Size_Radius 0.1`, `ThrusterRatio 0.8`,
`ResistanceParalyze 0.97`) and then checking the same offsets across five capital
ships and the player's fighter:
| offset | reading | `f106` | `f105` | `e105` | `e106` | `f101` | Delta Saber |
|---|---|---|---|---|---|---|---|
| `+0x30` | `Size_X` ✅ | 200 | 700 | 600 | 300 | 400 | 10 |
| `+0x34` | `Size_Y` ✅ | 200 | 700 | 600 | 300 | 400 | 7 |
| `+0x38` | `Size_Z` ✅ | 2000 | 3800 | 3800 | 2100 | 1400 | 29 |
| `+0x54` | `HP` ✅ | 10000 | 30000 | 30000 | 10000 | 25000 | 1500 |
| `+0x74` | 🟡 `ThrusterRatio` | 0.8 | 0.8 | 0.8 | 0.8 | 0.8 | 1 |
| `+0x84` | 🟡 `ResistanceParalyze` | 0.97 | 0.97 | 0.97 | 0.97 | 0.97 | 0.97 |
| `+0x40` | ❔ | 0.1 | 0.1 | 1 | 1 | 1 | 0.1 |
`+0x74` and `+0x84` are marked 🟡 because they rest on one anchoring unit: the
value is right for the Destroyer and constant across the others, which is
consistent with a field the rest default, but a second anchoring unit that sets
them to something *different* is what would prove it. `+0x40` is 0.1 on two units
and 1 on three, so it is a real per-unit field — just not identified.
⚠️ **The automated version of this does not work yet.** `examples/live_offsets.rs`
correlates every `get_f32(key)` against every dumped word and keeps offsets all
units agree on; over five units it produced exactly **one** hit
(`YawDragFactor → +0x0c`), and that hit is **wrong**`+0x0c` holds the integer
2 (as a denormal float, `2.8e-45`), which collided with `YawDragFactor = 2.0`.
Two causes, both fixable: the IDXD pool's value-before-key interleaving makes
`get_f32` unreliable on records that default many fields (the pairing shifts), so
most keys yield nothing; and 96 words only reaches `+0x180`, while `RadarRange
60000` / `FCSRange 45000` — both set on the Destroyer — appear nowhere in that
window, so much of the record lies beyond it.
So the next run dumps **512 words** per object, and the disc side needs a reader
that walks the pool with the default-aware rule rather than `get_f32` per key.