re(flight): withdraw the roll result — the probe was measuring pitch as much as roll

Re-running roll with 5 s settles and in-run clock brackets:

  this run's clock: TIME 00:34.93 -> 00:45.97 = 11.04 s game in 7.98 s wall = 1.383
  min speed: 90.6 deg/wall-s /1.383 -> 65.5 deg/game-s  (AV_Roll_Min 200)
  max speed: 59.1            /1.383 -> 42.7             (AV_Roll_Max 125)

The corrected numbers are within a few per cent of the PITCH run's 67.8 and 40.9 —
two different stick axes cannot produce the same rates, so the probe is not
separating them. Cause: watching a non-forward matrix row sees any rotation that
moves that row, and pitch moves it as much as roll. The correct measure is rotation
ABOUT the forward axis (project the row onto the plane perpendicular to forward and
track that angle).

So "roll shows no speed dependence, unlike pitch" is withdrawn: it rested on 2 s
settles and a row that mixes axes, and the two runs disagree with each other
(144/150 then, 90.6/59.1 now). AV_Roll_{Min,Max} are not confirmed and the axis
question is open.

The clock ratio is now measured three times in three flights: 1.260, 1.311, 1.383 —
a property of the moment, not the machine, so every rate probe must bracket its own
phases.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NptfmpjdpNCKEez6d2xvA9
This commit is contained in:
2026-08-13 18:12:20 +00:00
parent 8b10c0451f
commit eac92c3e44
3 changed files with 424 additions and 0 deletions

View File

@@ -0,0 +1,74 @@
#!/usr/bin/env python3
"""Is the clock factor universal, or does it apply only to linear motion?
The mission clock runs ~1.26x wall time, which fully explains the linear
measurements. Applied to the angular ones it predicts a wall-time pitch rate of
~94 deg/s for `AV_Roll_Min` 75 — but the settled measurement read 74.9.
Either that was luck inside a noisy sample or angular integration is frame-based.
The flaw in comparing across runs is that the emulator's speed depends on scene
load, so the clock ratio is not a constant of the machine. This measures BOTH in
the same run: a HUD screenshot brackets each turn phase, so the mission clock's
own advance over that phase converts wall seconds to game seconds.
Usage: roll_gametime.py <out.csv> [dwell_s] (then read the HUD TIME off the shots)
"""
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)
def shot(n):
subprocess.run(["screenshot", f"/sylph-home/re/shots/{n}.png"], capture_output=True)
def row_at(fd, off, cfg, row):
at = off + cfg["rot_delta"] + row * cfg["rot_stride"]
v = struct.unpack(">3f", os.pread(fd, 12, at))
n = math.sqrt(sum(c * c for c in v)) or 1.0
return tuple(c / n for c in v)
def ang(a, b):
return math.degrees(math.acos(max(-1.0, min(1.0, sum(a[i] * b[i] for i in range(3))))))
def main():
out_csv = sys.argv[1]
dwell = float(sys.argv[2]) if len(sys.argv) > 2 else 8.0
cfg = json.load(open("/tmp/nav-live.json"))
w, off, nm = speed_law.find_player()
if not w:
sys.exit("player entity not found after retries")
print(f"# locked on {nm}")
# Roll turns the craft about its FORWARD axis, so the forward row barely moves —
# watch another row of the rotation matrix instead.
fwd = 1 if cfg.get("fwd_row", 0) != 1 else 2
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))
time.sleep(5.0)
shot(f"roll_{label}_a") # HUD TIME at the start of the phase
t0 = time.time()
pad("axis", "LY", "1.0") # nose down = Roll
seq = []
while time.time() - t0 < dwell:
seq.append((round(time.time() - t0, 3), *row_at(w.fd, off, cfg, fwd)))
time.sleep(0.05)
pad("axis", "LX", "0.0")
wall = seq[-1][0]
shot(f"roll_{label}_b") # HUD TIME at the end
rows += [(label, *s) for s in seq]
total = sum(ang(seq[i][1:], seq[i + 1][1:]) for i in range(len(seq) - 1))
print(f"# {label:<5} wall {wall:5.2f}s swept {total:7.1f}deg "
f"rate {total / wall:6.1f} deg/wall-s "
f"(x1.26 -> {total / wall * 1.26:6.1f} deg/game-s if the clock applies)")
pad("trig", "RT", "0.0"); pad("trig", "LT", "0.0"); pad("reset")
with open(out_csv, "w") as f:
f.write("phase,t,fx,fy,fz\n")
for r in rows:
f.write(",".join(str(x) for x in r) + "\n")
print("# read HUD TIME off roll_slow_a/b and roll_fast_a/b to get the run's own ratio")
if __name__ == "__main__":
main()