diff --git a/tools/re-capture/fly_stage.sh b/tools/re-capture/fly_stage.sh index 91f657d..e84cf12 100755 --- a/tools/re-capture/fly_stage.sh +++ b/tools/re-capture/fly_stage.sh @@ -76,12 +76,20 @@ sleep 1; shot 03-selected pad tap A 0.20; sleep 3; shot 04-after-A pad tap A 0.20; sleep 3; shot 05-after-A2 -say "waiting for flight (up to 240s) — snapshotting when the guest settles" -for i in $(seq 1 240); do - sleep 1 - [ $((i % 30)) -eq 0 ] && shot "06-wait-$i" -done -shot 07-final +# Selecting a stage lands on the mission BRIEFING (A: Continue), which leads to the +# READY ROOM (TAKE OFF / BRIEFINGS / HANGAR / ...) with BRIEFINGS pre-highlighted. +# A snapshot taken at the briefing yields ZERO unit-definition objects — they are +# instantiated at stage load proper — so the run has to reach flight. +say "clearing the briefing" +for _ in $(seq 1 10); do pad tap A 0.20; sleep 2.5; done +shot 06-readyroom +say "READY ROOM -> TAKE OFF (one up from the pre-highlighted BRIEFINGS)" +pad dpad up 0.06; sleep 1; shot 07-takeoff-hl +pad tap A 0.20; sleep 8 +for _ in $(seq 1 6); do pad tap A 0.20; sleep 4; done +say "waiting for flight to settle" +sleep 30 +shot 08-flight SHM=$(ls /dev/shm/xenia_memory_* 2>/dev/null | head -1) if [ -n "$SHM" ]; then OUT="$HOME/snap-stage$STAGE.bin" diff --git a/tools/re-capture/roll_axis.py b/tools/re-capture/roll_axis.py index 9315309..ff241b9 100644 --- a/tools/re-capture/roll_axis.py +++ b/tools/re-capture/roll_axis.py @@ -18,8 +18,27 @@ import json, math, os, struct, subprocess, sys, time sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) import speed_law -def pad(*a): - subprocess.run(["vgamepad", *a], capture_output=True) +PAD_FILE = os.environ.get("XENIA_PAD_FILE", "/tmp/xenia_pad.txt") + + +def pad_state(**kw): + """Write the WHOLE pad state at once. + + The file pad is a snapshot, not a set of independent channels: each write + replaces everything, so holding a trigger *while* deflecting a stick has to be + one write. (`vgamepad`'s per-channel calls are what this replaces; that device + also leaked to the host, see the driver header.) Values are exact — no virtual + stick quantisation — which is what makes a single-axis roll hold trustworthy. + """ + parts = [f"{k}={v}" for k, v in kw.items() if v is not None] + tmp = PAD_FILE + ".tmp" + with open(tmp, "w") as f: + f.write(" ".join(parts)) + os.replace(tmp, PAD_FILE) + + +def pad_neutral(): + pad_state() def shot(n): subprocess.run(["screenshot", f"/sylph-home/re/shots/{n}.png"], capture_output=True) @@ -48,11 +67,13 @@ def main(): u_i, w_i = [r for r in (0, 1, 2) if r != f_i] rows = [] for label, (trig, tv) in (("slow", ("LT", 1.0)), ("fast", ("RT", 1.0))): - pad("trig", "RT", "0.0"); pad("trig", "LT", "0.0"); pad("axis", "LX", "0.0") - pad("trig", trig, str(tv)) + # settle at the target speed with the stick CENTRED (5 s: 2 s was shown + # wrong twice this session) + pad_state(**{trig.lower(): 255}) time.sleep(5.0) shot(f"rollax_{label}_a") - pad("axis", "LX", "1.0") + # full left-stick X, everything else exactly zero, trigger still held + pad_state(**{trig.lower(): 255, "lx": 32767}) t0, prev, swept = time.time(), rows_at(w.fd, off, cfg), 0.0 seq = [] while time.time() - t0 < dwell: @@ -63,13 +84,13 @@ def main(): swept += abs(d) seq.append((round(time.time() - t0, 3), round(d, 4))) prev = cur - pad("axis", "LX", "0.0") + pad_state(**{trig.lower(): 255}) wall = seq[-1][0] shot(f"rollax_{label}_b") rows += [(label, *s) for s in seq] print(f"# {label:<5} wall {wall:5.2f}s roll swept {swept:7.1f}deg " f"rate {swept / wall:6.1f} deg/wall-s") - pad("trig", "RT", "0.0"); pad("trig", "LT", "0.0"); pad("reset") + pad_neutral() with open(out_csv, "w") as f: f.write("phase,t,droll_deg\n") for r in rows: