re(units): read definitions with the code-derived layout — defaulted values 1 059 -> 2 351

unit_runtime.py can only place fields the disc values (it scores triples against
disc records): 58 of 153 from one snapshot. data/unit_definition_layout.txt came
from the title's loader instead, so it places all 159 — including the fields no
disc record sets, which is the Route-B target.

tools/re-capture/unit_dump_layout.py reads every field of every live definition
object with that layout and keeps the discipline: a field the disc DOES value is a
check, not a new value. Over two snapshots (19 objects): 700 cross-checks agree,
0 disagree.

  fields placed per object            58 of 153 -> 159
  rows over those 19 units                  609 -> 2 736
  defaulted-on-disc values (whole file)   1 059 -> 2 351

Two traps recorded: the layout table's offsets are DECIMAL while the solver CSV
prints hex (parsing as hex fails the cross-check on everything — which is how it
announced itself), and angle fields can carry a prefix (AB_AA_PitchPlus is still an
angle, so the AV_/AA_ test must match anywhere in the name).

Flagged: UN_f201_TCAF_Tanker's object is not byte-identical between the two
missions — per-mission override or a runtime-mutated field; needs a third snapshot
to separate.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NptfmpjdpNCKEez6d2xvA9
This commit is contained in:
2026-08-13 13:30:49 +00:00
parent d61baddaf0
commit 14d5ed54d1
3 changed files with 2891 additions and 1534 deletions

View File

@@ -414,3 +414,39 @@ craft!) and `UN_mn040_Asteroid_Big` (47 defaulted values, `Size_Z = 2000`).
Regenerating the solver's token file: `idxd_tokens <GP_MAIN_GAME_E.pak> Generic`
emits the `REC`/`F` records `unit_runtime.py` wants — **110** of them are `UN_*`,
which is the disc's full unit count.
### Reading the objects with the code-derived layout, not the solver (2026-08-13)
`unit_runtime.py` places a field only if the **disc** values it somewhere — that is
what lets it score `(field, offset, encoding)` triples — so it resolved 58 of 153
fields from a single-mission snapshot. The layout in
[`data/unit_definition_layout.txt`](../../../crates/sylpheed-formats/data/unit_definition_layout.txt)
came from the title's own loader instead (`sub_82341A20`, where the field *name* of
every store is a string in the image), so it places **all 159**, including the ones
no disc record ever sets — which is precisely the Route-B target.
[`tools/re-capture/unit_dump_layout.py`](../../../tools/re-capture/unit_dump_layout.py)
reads every field of every live definition object with that layout, and keeps the
project's discipline: a field the disc **does** value is a **check**, not a new
value. Over two snapshots (19 objects): **700 disc cross-checks agree, 0 disagree.**
| | solver-derived | layout-derived |
|---|---|---|
| fields placed per object | 58 of 153 | **159** |
| rows over these 19 units | 609 | **2 736** |
| defaulted-on-disc values, disc-wide file | 1 059 | **2 351** |
Two things the run pinned down, both cheap to re-learn the hard way:
* **The layout table's offsets are DECIMAL** (`48 f32 Size_X`), while the solver's
CSV prints hex. Parsing it as hex puts every field `0x18` bytes late — and the
disc cross-check then fails on *everything*, which is exactly how the mistake
announced itself.
* **Angle fields can carry a prefix.** `AB_AA_PitchPlus` (afterburner) is still an
angle, so anchoring the `AV_`/`AA_` test at the start of the name reported two
player-craft fields as contradictions when they were 15°/16° in radians. With the
token matched anywhere, the cross-check is clean.
⚠️ One unit's object is **not** byte-identical between the two missions —
`UN_f201_TCAF_Tanker`. Either a per-mission override or a field the runtime mutates;
the merged CSV holds the later reading, and separating them needs a third snapshot.