WIP toward the residual flight-speed-law question (does a 1 s burst reach the
steady angular rate, or is there a per-axis multiplier?). The write-up already
concluded that host-side polling cannot answer it and named a Canary-side hook
as the tool required; that hook now exists (--frame_probe_log, committed as
auto/re-frame-probe in xenia-canary-native) and this is the harness for it.
- `rebuild_canary.sh` -- the surgical rebuild the box can actually do, kept in
the repo this time instead of in /tmp: compile only the changed objects, `ar`
them into their archive, and re-run the link command lifted out of the
generated ninja. A full `ninja` is impossible here (several TUs need dev
headers the image lacks) and the build cache cannot be re-configured. 31 s.
- `frame_burst.py` -- points the probe at the player craft's transform block
(pos-112, the three 16-byte-strided rows plus the position) and drives full
stick holds, recording each hold's start and end in the same clock the probe
stamps its lines with.
- `frame_session.sh` -- the whole run as ONE blocking foreground call, per the
session-lifetime rule; REUSE=1 drives a Canary that is already up.
- `nav_to_flight.sh` -- fly_stage.sh's navigation, split out so a live emulator
can be re-used. A boot to the title costs minutes under lavapipe and a run
that only failed to NAVIGATE should not pay for it twice.
The navigation change is the one worth reading. Every screen oracle here tested
named pixels ("648,221 is white"), which is only valid while the game image sits
at a known place on the root window -- and it does not: xenia's GTK window has a
menu bar, so on this display the image is ~25 px lower and every constant reads
the wrong row. Nothing errors. One run sat 300 s in front of a plainly visible
MAIN MENU reporting "no main menu"; the next missed the title screen entirely
and let the attract movie loop for ten minutes.
So `screen_id.py` identifies screens by WHOLE-IMAGE statistics instead -- the
fraction of green UI-text pixels, the fraction of near-white pixels, and the
per-channel means -- which no vertical shift, scale or letterbox can move. It is
calibrated against known-good captures and classifies all of them correctly:
title, three different menu screens, in-flight, and four movie frames as
"other". `bin/screenshot` additionally crops the menu bar off saved evidence
shots, deriving the offset from the window's own height rather than a constant.
Not yet a finding: the run has not reached flight, so no rate has been measured.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.