#!/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 [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()