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>
3.7 KiB
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).
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=sdlwithSDL_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 withAudioSystem::RegisterClient: CreateDriver failed for index=0, the guest never gets past the movie, and the window stays black for 8+ minutes.nav_probe.shandlaunch_mission.shalready 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.shreached 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,
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.