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>
This commit is contained in:
73
docs/re/live-unit-definitions.md
Normal file
73
docs/re/live-unit-definitions.md
Normal file
@@ -0,0 +1,73 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user