The design the sweep could not provide: settle the throttle, measure the settled
speed, pitch for ONE second so speed barely moves inside the burst. Three
throttles, two repeats, row pin CONFIDENT, fresh flight.
LT min burst speed ~105 rate 113.6, 109.5 deg/wall-s
cruise ~383 100.2, 88.4
RT max ~1483 52.2, 70.5
Rate falls monotonically with speed -- 111.5 -> 94.3 -> 61.4 -- at three KNOWN,
SETTLED speeds instead of smeared across a bleeding one.
The decisive comparison needs no clock. Absolute rates depend on the run's clock
ratio, but the min:max RATIO cancels it:
measured min:max = 1.82
AV_PitchMinus_Min/Max 75/40 = 1.88 -> 3.0% apart
AV_PitchPlus_Min/Max 150/70 = 2.14 -> 15.1% apart
Two conclusions, neither resting on a clock measurement:
- _Min/_Max really do mean "at minimum / at maximum speed", with the rate
interpolating between them: shape confirmed to 3%.
- ly+ drives pitch-MINUS, not plus. The craft has asymmetric pitch authority
(75/40 down vs 150/70 up) and the ratio picks the pair cleanly.
Absolute magnitudes remain open: this run did not bracket the HUD clock, so
deg/GAME-second cannot be computed from it, and picking a ratio that makes the
numbers fit would be circular. The probe now screenshots the clock at both ends.
Also: fly_stage.sh now waits for the TAKE-OFF load too. Guarding only the stage
load left a run pressing A into a black screen and then reporting "player entity
not found" from a game that never reached flight.
Runtime-capture harness (sylph-re container)
Screenshot-driven scripts for reading the running retail game's menus under Xenia
Canary + lavapipe, headless. They assume the container helpers screenshot,
vgamepad, pad are on $PATH and HOME=/sylph-home/re.
| Script | What it does |
|---|---|
skip_intro.sh |
Boot → main menu, unattended. Taps A only while the intro movie is actually playing (frame-to-frame RMSE), then once at the PRESS Ⓐ BUTTON title. Static logo screens are left alone, so a stray tap can never land on NEW GAME. |
wait_title.sh |
Older variant: wait for the title (green Ⓐ glyph at px 625,618) and tap A. Superseded by skip_intro.sh. |
step.sh |
One Arsenal navigation step (down/up/next/prev/none) + a compact capture: weapon list stacked over the DATA SHEET. |
sweep.sh |
Walk a whole weapon-type list, capturing only rows that show a DATA SHEET — locked rows (a "Conditions to Develop" panel) are detected by the brightness of the Range Class label box and skipped. |
type.sh |
Change weapon-type tab N times (RB) and report the header strip. |
hp.sh / cyc.sh |
Hangar hard-point carousel: cyc.sh steps it (d-pad down, not left/right) and captures the Name + DATA SHEET. |
Input timing under lavapipe: the game polls input at its own low frame rate, so a 60 ms d-pad tap is dropped roughly half the time. 200 ms is reliable; 300 ms starts to auto-repeat (two rows per press).
Findings produced with these: docs/re/weapon-datasheet-runtime.md.
Guest-memory tools (no screenshots)
| Script | What it does |
|---|---|
gmem.py |
Read the live guest address space out of /dev/shm/xenia_memory_* (Xenia's backing file), addressed by guest VA. find / read / words. |
weapon_runtime.py |
Solve the Weapon/Shell struct layouts against the disc records and read the fields the disc defaults. |
unit_discover.py |
Find which runtime class carries a set of ID strings, assuming no vtable: tallies the word at pointer_site - k across distinct IDs. |
unit_runtime.py |
Same solver for the unit\UN_*.tbl definition objects (vtable 0x820af844). Unions several snapshots — unit definitions are per-stage. |
schema_order.py |
Merge a sub-record's field-declaration order across all tables (topological sort); the layout check that pins fields no table ever values. |
order_check.py |
Test offset = base + 4*index for one table's sub-record against solver output. |
grab_tutorial.sh |
Cold-boot Canary, walk to the Nth TUTORIAL entry, wait for the stage load, snapshot guest RAM. One emulator per capture — backing out of a loaded mission wedges it. |
Snapshot first — cp --sparse=always /dev/shm/xenia_memory_* snap.bin (~2 s) — and
point $GMEM_FILE at the copy; the running emulator pegs every core under lavapipe.