Files
Syplheed-Reborn/docs/re/live-unit-definitions.md
Claude (auto-RE) a836f8e9ea re: live unit definitions from a running Stage 02, and the recipe that gets there
The runtime track is unblocked. Two environment facts cost most of the effort:
audio must be --audio --apu=sdl WITH SDL_AUDIODRIVER=dummy (muting with --apu=nop
fills the log with CreateDriver failures and the guest never passes the movie),
and everything must happen inside ONE blocking foreground call because anything
outliving its call is reaped. Boot is 25s to the flight HUD once caches are warm,
which is what makes launch-and-dump fit in one call. numpy/PIL are absent so the
analysis scripts do not run; gmem.py is stdlib and does.

Result: 14 live definition objects dumped (vtable 0x820af844, 96 words each).
The +0x54 column reads as HP across three orders of magnitude and +0x30..0x38 as
Size_X/Y/Z, corroborating the earlier single-snapshot layout, and five of the
fourteen have Size_Y == Size_X exactly, supporting the inheritance default rule.
Open: no name/id field in the first 96 words, so objects are not yet matched to
their disc records.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 20:18:32 +00:00

74 lines
3.7 KiB
Markdown

# Live unit definitions from a running Stage 02 (2026-08-12)
The Route-B question — what value does a field that the disc leaves **defaulted**
actually take at runtime — needs the game running. This is the first snapshot of
the parsed definition objects taken straight out of guest RAM, plus the recipe
that works in the box today, because getting there was most of the work.
## The recipe that works
Everything must happen inside **one blocking foreground call**. A process that
outlives the call that started it is reaped — `nohup`, a harness background job
and a `wait`-holding wrapper were all tried and all died within a minute of the
turn ending (see the session-lifetime notes).
```bash
tools/re-capture/launch_mission.sh fly # title → LOAD → slot 01 → READY ROOM → TAKE OFF
python3 tools/re-capture/gmem.py find hex:820af844 400 # live definition objects
python3 tools/re-capture/gmem.py words <va> 96 # dump each one
```
Two environment facts that cost an hour each:
- **Audio must be `--audio --apu=sdl` with `SDL_AUDIODRIVER=dummy`.** There is no
PulseAudio server in the box, so muting (`--apu=nop`, `run-canary`'s default)
looks like the safe choice. It is not: the log then fills with
`AudioSystem::RegisterClient: CreateDriver failed for index=0`, the guest never
gets past the movie, and the window stays black for 8+ minutes. `nav_probe.sh`
and `launch_mission.sh` already pass the right pair — do not "fix" them.
- **Boot is fast once the caches are warm.** The first boot took minutes; with
the shader/code cache populated, `launch_mission.sh` reached the flight HUD in
**25 s**, which is what makes a launch-and-dump fit in a single call.
`entities2.py`, `flight_probe.py` and the other analysis scripts **do not run
here** — no `numpy` (and no `PIL` for image work). `gmem.py` is pure stdlib and
works, so the pattern is: dump raw words to disc inside the call, analyse
afterwards.
## What came out
14 live definition objects (vtable `0x820af844`), 96 words each:
[`captures/stage02-live-unit-definitions.txt`](captures/stage02-live-unit-definitions.txt),
with the flight screenshot they were taken from.
| object | `+0x30` | `+0x34` | `+0x38` | `+0x54` |
|---|---|---|---|---|
| `0xbd3b6a00` | 2500 | 3100 | 2600 | 10000 |
| `0xbd3d2d80` | 80 | 80 | 350 | 4000 |
| `0xbd3da100` | 10 | 7 | 29 | 1500 |
| `0xbd3db980` | 350 | 70 | 200 | 10000 |
| `0xbd3dbd00` | 10 | 5 | 20 | 300 |
| `0xbd3dea80` | 20 | 9 | 22 | 100 |
| `0xbd3dfc00` | 10 | 7 | 29 | 1000 |
| `0xbd3e0680` | 300 | 300 | 2800 | 3000 |
| `0xbd3e1f00` | 400 | 400 | 1400 | 25000 |
| `0xbd3e6f80` | 300 | 300 | 2100 | 10000 |
| `0xbd3ee300` | 200 | 200 | 2000 | 10000 |
| `0xbd3faa80` | 100 | 40 | 50 | 500 |
| `0xbd3fd800` | 600 | 600 | 3800 | 30000 |
| `0xbd40e200` | 700 | 700 | 3800 | 30000 |
This **corroborates** the layout solved earlier from a single in-mission
snapshot (`+0x054` = HP, `+0x30…0x38` = `Size_X/Y/Z`) rather than re-proving it:
the `+0x54` column reads as hull values across three orders of magnitude
(100 for something small, 30 000 twice for capital ships), and the sizes scale
with them. It also gives independent support to the **`Size_Y` inherits `Size_X`**
default rule — five of the fourteen have `+0x30 == +0x34` exactly
(300/300, 400/400, 200/200, 600/600, 700/700) while the rest differ.
**Not yet resolved: which unit each object is.** Nothing in the first 96 words
is an obvious name pointer, so these cannot be matched to their disc records yet
— and that match is what turns the snapshot into defaulted-field *values*. The
next step is to dump further into the object (and the entity that points at it)
looking for the id or name field, which is one more single-call run.