#!/usr/bin/env python3 """Drive a scripted input sequence in flight while sampling guest RAM, so the player craft's live transform can be *derived* rather than guessed. The point is correlation: each sample is tagged with the stick/trigger state that produced it, so afterwards we can ask "which floats move only while the yaw axis is deflected?" and "which triple integrates to the velocity?". Writes a .npz-ish flat binary: a header of (va, offset, name) per instance, then per frame a timestamp, the input vector, and every instance's raw window. Usage: flight_probe.py [seconds] """ import os import struct import sys import time sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) import gworld # noqa: E402 FIFO = "/tmp/sylph-vgamepad.fifo" class Pad: """Talk to the vgamepad server directly over its FIFO. The `vgamepad` CLI spawns a process per command (~20 ms); a control loop cannot afford that, and `tap`/`hold` additionally sleep *inside* the server. Writing lines to the FIFO ourselves keeps a tick under a millisecond. """ def __init__(self): self.f = open(FIFO, "w", buffering=1) self.state = {"LX": 0.0, "LY": 0.0, "RX": 0.0, "RY": 0.0, "LT": 0.0, "RT": 0.0} def axis(self, name, v): self.state[name] = v self.f.write(f"axis {name} {v:.3f}\n") def trig(self, name, v): self.state[name] = v self.f.write(f"trig {name} {v:.3f}\n") def press(self, b): self.f.write(f"press {b}\n") def release(self, b): self.f.write(f"release {b}\n") def reset(self): self.f.write("reset\n") for k in self.state: self.state[k] = 0.0 def vector(self): return [self.state[k] for k in ("LX", "LY", "RX", "RY", "LT", "RT")] # (duration_s, description, action) SCRIPT = [ (3.0, "idle", lambda p: p.reset()), (4.0, "pitch-up", lambda p: p.axis("LY", -0.9)), (2.0, "idle2", lambda p: p.reset()), (4.0, "yaw-right", lambda p: p.axis("LX", 0.9)), (2.0, "idle3", lambda p: p.reset()), (4.0, "yaw-left", lambda p: p.axis("LX", -0.9)), (2.0, "idle4", lambda p: p.reset()), (5.0, "boost", lambda p: p.trig("RT", 1.0)), (3.0, "idle5", lambda p: p.reset()), ] def main(): out = sys.argv[1] w = gworld.World() inst = w.refresh() print(f"# {len(inst)} instances", flush=True) if not inst: sys.exit("no instances — not in a mission?") pad = Pad() hz = 10.0 with open(out, "wb") as f: f.write(b"SYLPHPRB") f.write(struct.pack("