Some checks failed
Orchestrator / Commit Message Validation (push) Has been skipped
Orchestrator / Lint (push) Failing after 1m52s
Orchestrator / Windows (x86-64) (push) Has been skipped
Orchestrator / Linux (x86-64) (push) Has been skipped
Orchestrator / Create Release (push) Has been skipped
Reading guest RAM from outside the emulator is easy -- Canary backs it with a
/dev/shm file, so a host process can just pread it -- but it is UNSYNCHRONISED.
The reader has no idea where the guest is in its update, so successive reads are
separated by an unknown, jittering number of guest updates.
That is not a theoretical concern. Measuring the player craft's angular velocity
this way produced 3x swings between adjacent 0.25 s windows, which made it
impossible to tell "the rate ramps up after the stick goes over" from "the
sampler is aliasing" -- the one question standing between a confirmed law shape
and its absolute scale. Widening the window does not rescue it either: a hold
long enough to average bleeds the speed, and speed is the variable under test.
So sample from inside, in VdSwap: the guest's own thread, once per guest frame,
at a fixed point in it. Consecutive lines are then exactly one frame apart by
construction and the frame counter is exact.
Which bytes to sample is not known at launch -- object addresses are found by
scanning at runtime -- so the regions come from a small control file that is
re-read whenever its mtime changes, the same trick the file input pad uses:
0x40D10590 128 a guest VA and a byte count, one region per line
and each frame appends
F <frame> H <host_ns> G <guest_ticks> R0 <hex> R1 <hex> ...
Reads are bounds-checked through LookupHeap/QueryProtect and copied out before
printing, so an unmapped or stale address logs "-" instead of taking the
emulator down. Header-only and off unless --frame_probe_log is set, so it adds
no build target and no cost to anyone not using it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>