From 6c7025851ee5a219d7381265905a29ea9e387f4e Mon Sep 17 00:00:00 2001 From: "Claude (auto-RE)" Date: Thu, 13 Aug 2026 22:48:29 +0000 Subject: [PATCH] re(flight): the rate probe measures a MOVING speed -- pitching bleeds it hard Measured pitch with the rows properly pinned, against this craft's own disc caps (AV_PitchPlus_Min 150, AV_PitchPlus_Max 70): min speed (LT) 1391.0 deg / 8.00 s, clock x1.326 -> 131.1 deg/game-s vs 150 max speed (RT) 989.0 deg / 8.05 s, clock x1.318 -> 93.2 deg/game-s vs 70 A rate 33% ABOVE a cap is not a finding, it is a broken instrument. The HUD speed is in the same bracketing screenshots that give the clock, so read it: slow phase 102 -> 18 fast phase 1193 -> 589 The speed is NOT constant during the dwell -- pitching halves it in 8 seconds. The cap is speed-dependent, so as the craft slowed its cap rose, and an 8-second average necessarily lands between the max-speed cap and a mid-speed one. The 133% is entirely the instrument. This also weakens the roll result committed earlier: same method, so 120.9 vs AV_Roll_Max 125 is CONSISTENT but is not a tight test -- the true cap could be lower and still produce that average. Said plainly in the doc rather than left standing as a clean confirmation. Min-speed figures are less affected; there is little speed left to lose. Proper fix, not yet done: dwell ~1-2 s so speed barely moves, or sample HUD speed continuously and fit rate against INSTANTANEOUS speed -- which yields the whole rate-vs-speed curve instead of two points. Separately this is a flight-model finding: TURNING COSTS SPEED, steeply, with the throttle still at maximum. A reimplementation treating the throttle as a speed the craft simply holds will be wrong during manoeuvres. --- docs/re/captures/pitch-rate-speed-bleed.csv | 315 ++++++++++++++++++++ docs/re/flight-speed-law.md | 41 +++ tools/re-capture/rate_probe.py | 94 ++++++ 3 files changed, 450 insertions(+) create mode 100644 docs/re/captures/pitch-rate-speed-bleed.csv create mode 100755 tools/re-capture/rate_probe.py diff --git a/docs/re/captures/pitch-rate-speed-bleed.csv b/docs/re/captures/pitch-rate-speed-bleed.csv new file mode 100644 index 0000000..53911d6 --- /dev/null +++ b/docs/re/captures/pitch-rate-speed-bleed.csv @@ -0,0 +1,315 @@ +phase,t,dpitch_deg +slow,0.051,0.0953 +slow,0.101,1.6885 +slow,0.152,0.8717 +slow,0.202,1.6238 +slow,0.252,-0.0 +slow,0.302,-0.0 +slow,0.352,8.8412 +slow,0.403,5.679 +slow,0.453,5.3839 +slow,0.503,12.9544 +slow,0.553,7.5713 +slow,0.603,4.0593 +slow,0.653,0.0 +slow,0.703,0.0 +slow,0.753,23.7081 +slow,0.804,14.501 +slow,0.854,9.6502 +slow,0.905,9.64 +slow,0.955,2.4036 +slow,1.006,-0.0 +slow,1.056,-0.0 +slow,1.106,24.1631 +slow,1.156,9.7201 +slow,1.206,14.5817 +slow,1.26,7.2861 +slow,1.31,-0.0 +slow,1.36,-0.0 +slow,1.41,21.8962 +slow,1.461,12.1865 +slow,1.511,14.6395 +slow,1.561,4.8866 +slow,1.611,4.8766 +slow,1.661,0.0 +slow,1.712,0.0 +slow,1.762,24.4862 +slow,1.812,12.272 +slow,1.863,14.7764 +slow,1.913,14.8237 +slow,1.964,9.9081 +slow,2.014,-0.0 +slow,2.064,-0.0 +slow,2.116,22.3434 +slow,2.166,4.9684 +slow,2.217,14.8905 +slow,2.267,9.9066 +slow,2.317,2.4656 +slow,2.367,-0.0 +slow,2.417,19.7888 +slow,2.467,7.374 +slow,2.517,9.8264 +slow,2.569,14.7106 +slow,2.619,12.2195 +slow,2.671,0.0 +slow,2.721,0.0 +slow,2.771,0.0 +slow,2.821,24.4044 +slow,2.871,12.1781 +slow,2.951,14.5889 +slow,3.001,14.5786 +slow,3.052,0.0 +slow,3.102,0.0 +slow,3.153,19.432 +slow,3.203,14.5741 +slow,3.253,4.8605 +slow,3.303,19.4596 +slow,3.355,2.4281 +slow,3.405,0.0 +slow,3.455,0.0 +slow,3.506,31.6949 +slow,3.556,9.7829 +slow,3.606,14.7094 +slow,3.656,9.8314 +slow,3.706,7.3808 +slow,3.756,-0.0 +slow,3.807,-0.0 +slow,3.857,19.7355 +slow,3.907,14.8675 +slow,3.958,4.9655 +slow,4.008,17.3918 +slow,4.06,2.4777 +slow,4.11,-0.0 +slow,4.16,-0.0 +slow,4.211,32.264 +slow,4.261,9.866 +slow,4.311,19.6734 +slow,4.361,7.3673 +slow,4.412,7.3404 +slow,4.462,0.0 +slow,4.513,0.0 +slow,4.563,19.5894 +slow,4.613,12.1841 +slow,4.663,4.8719 +slow,4.713,17.0264 +slow,4.765,2.4241 +slow,4.815,0.0 +slow,4.866,0.0 +slow,4.918,26.7496 +slow,4.968,17.0367 +slow,5.019,14.6052 +slow,5.069,7.3143 +slow,5.119,2.4296 +slow,5.17,0.0 +slow,5.222,17.0719 +slow,5.272,12.2148 +slow,5.351,14.6765 +slow,5.401,14.7096 +slow,5.454,2.4482 +slow,5.504,0.0 +slow,5.555,0.0 +slow,5.605,27.0453 +slow,5.655,12.3214 +slow,5.705,14.8308 +slow,5.756,4.9518 +slow,5.806,19.8347 +slow,5.856,2.4736 +slow,5.906,-0.0 +slow,5.957,-0.0 +slow,6.008,24.7941 +slow,6.058,9.8906 +slow,6.108,14.805 +slow,6.159,4.9105 +slow,6.209,-0.0 +slow,6.26,-0.0 +slow,6.31,24.6254 +slow,6.36,7.3425 +slow,6.41,14.6791 +slow,6.461,9.7716 +slow,6.511,7.3144 +slow,6.561,0.0 +slow,6.612,0.0 +slow,6.662,21.9754 +slow,6.713,12.1647 +slow,6.763,9.7355 +slow,6.813,19.4718 +slow,6.864,2.4274 +slow,6.914,0.0 +slow,6.964,0.0 +slow,7.014,29.2378 +slow,7.064,9.7533 +slow,7.114,14.6513 +slow,7.165,4.8902 +slow,7.215,2.4395 +slow,7.266,0.0 +slow,7.316,0.0 +slow,7.368,26.9512 +slow,7.418,14.7524 +slow,7.468,9.8579 +slow,7.518,12.3414 +slow,7.569,2.4662 +slow,7.621,-0.0 +slow,7.671,-0.0 +slow,7.722,19.8018 +slow,7.772,9.92 +slow,7.851,14.8814 +slow,7.901,12.3787 +slow,7.951,2.4669 +slow,8.001,0.0 +fast,0.05,0.0 +fast,0.104,0.0 +fast,0.154,0.0 +fast,0.204,0.2374 +fast,0.254,0.206 +fast,0.306,1.8902 +fast,0.356,1.5885 +fast,0.406,1.4595 +fast,0.456,0.0 +fast,0.506,0.0 +fast,0.556,5.7624 +fast,0.606,2.347 +fast,0.657,3.5323 +fast,0.707,6.1276 +fast,0.757,1.1012 +fast,0.807,-0.0 +fast,0.857,-0.0 +fast,0.909,12.418 +fast,0.959,5.1346 +fast,1.009,7.8218 +fast,1.059,6.5806 +fast,1.109,1.3424 +fast,1.16,0.0 +fast,1.21,10.761 +fast,1.263,4.2102 +fast,1.313,8.2445 +fast,1.363,6.8621 +fast,1.413,6.9892 +fast,1.463,-0.0 +fast,1.542,-0.0 +fast,1.592,11.0635 +fast,1.642,5.9249 +fast,1.692,8.7078 +fast,1.742,2.8665 +fast,1.793,10.1297 +fast,1.843,0.0 +fast,1.893,0.0 +fast,1.943,0.0 +fast,1.993,20.6893 +fast,2.043,3.2503 +fast,2.094,11.2506 +fast,2.144,6.3385 +fast,2.194,7.8452 +fast,2.244,1.556 +fast,2.295,-0.0 +fast,2.345,-0.0 +fast,2.395,18.7454 +fast,2.445,10.3395 +fast,2.495,10.608 +fast,2.545,7.0808 +fast,2.596,7.0815 +fast,2.646,0.0 +fast,2.696,0.0 +fast,2.746,0.0 +fast,2.796,17.7627 +fast,2.847,10.6635 +fast,2.897,7.1571 +fast,2.947,7.1983 +fast,3.002,5.4229 +fast,3.052,0.0 +fast,3.102,0.0 +fast,3.152,0.0 +fast,3.202,25.5105 +fast,3.253,7.3926 +fast,3.303,11.2085 +fast,3.353,3.7679 +fast,3.404,3.7764 +fast,3.455,-0.0 +fast,3.505,-0.0 +fast,3.556,19.0406 +fast,3.642,11.5779 +fast,3.692,11.712 +fast,3.742,3.9222 +fast,3.792,0.0 +fast,3.842,0.0 +fast,3.892,17.7661 +fast,3.942,1.9679 +fast,3.993,17.8611 +fast,4.043,3.9876 +fast,4.093,15.9815 +fast,4.146,0.0 +fast,4.196,0.0 +fast,4.246,0.0 +fast,4.296,21.9362 +fast,4.346,7.908 +fast,4.396,11.8184 +fast,4.447,7.8388 +fast,4.497,1.9466 +fast,4.547,0.0 +fast,4.597,0.0 +fast,4.647,0.0 +fast,4.697,29.0258 +fast,4.747,11.3566 +fast,4.797,11.275 +fast,4.847,3.7393 +fast,4.898,7.4494 +fast,4.948,-0.0 +fast,4.998,-0.0 +fast,5.048,-0.0 +fast,5.098,24.0113 +fast,5.148,10.888 +fast,5.202,10.8585 +fast,5.252,7.225 +fast,5.302,5.4068 +fast,5.352,0.0 +fast,5.403,0.0 +fast,5.453,0.0 +fast,5.504,26.97 +fast,5.554,10.7617 +fast,5.604,10.8137 +fast,5.654,3.6172 +fast,5.709,5.4336 +fast,5.759,-0.0 +fast,5.809,-0.0 +fast,5.862,20.0109 +fast,5.942,10.985 +fast,5.992,11.0845 +fast,6.042,5.5755 +fast,6.092,0.0 +fast,6.142,0.0 +fast,6.192,18.6832 +fast,6.245,1.866 +fast,6.295,15.0963 +fast,6.345,3.8107 +fast,6.395,9.5834 +fast,6.446,1.9237 +fast,6.496,0.0 +fast,6.546,0.0 +fast,6.598,21.2979 +fast,6.651,7.8108 +fast,6.701,13.7645 +fast,6.751,3.9499 +fast,6.802,5.9406 +fast,6.852,0.0 +fast,6.902,15.8604 +fast,6.952,5.9146 +fast,7.002,11.8545 +fast,7.053,7.9034 +fast,7.103,9.8434 +fast,7.153,1.9608 +fast,7.203,-0.0 +fast,7.253,-0.0 +fast,7.303,19.6693 +fast,7.357,13.4968 +fast,7.407,11.5239 +fast,7.457,7.6491 +fast,7.507,11.4147 +fast,7.559,1.8882 +fast,7.642,-0.0 +fast,7.692,15.147 +fast,7.742,5.584 +fast,7.792,11.1374 +fast,7.842,3.6992 +fast,7.892,9.2126 +fast,7.946,1.8328 +fast,7.996,-0.0 +fast,8.046,-0.0 diff --git a/docs/re/flight-speed-law.md b/docs/re/flight-speed-law.md index 4ed16a2..f4f145f 100644 --- a/docs/re/flight-speed-law.md +++ b/docs/re/flight-speed-law.md @@ -504,3 +504,44 @@ should model as such. 🟡 Not covered: the **d-pad** (the tactical map) and the **face buttons** (fire / weapon select). Neither is a plausible flight axis, but neither was measured here. Data: [`captures/axis-probe-rows-pinned.csv`](captures/axis-probe-rows-pinned.csv). + + +## ⚠️ Angular-rate probes measure a MOVING speed — the dwell bleeds it + +Measuring pitch with the rows properly pinned gave, for +`UN_f001_TCAF_DeltaSaber_T_Player` (whose own disc values are +`AV_PitchPlus_Min` **150°**, `AV_PitchPlus_Max` **70°**): + +| phase | swept | clock | per game-s | cap | +|---|---|---|---|---| +| min speed (`LT`) | 1 391.0° / 8.00 s | ×1.326 | **131.1** | 150 | +| max speed (`RT`) | 989.0° / 8.05 s | ×1.318 | **93.2** | 70 — **133 % of cap** | + +A rate 33 % *above* a cap is not a finding, it is a broken instrument, so read the +HUD speed off the same bracketing screenshots that gave the clock: + +``` +slow phase 102 -> 18 +fast phase 1193 -> 589 +``` + +**The speed is not constant during the dwell — pitching bleeds it hard**, halving it +in 8 seconds. The rate cap is itself speed-dependent, so as the craft slowed its cap +*rose*, and the 8-second average necessarily lands between the max-speed cap and a +mid-speed one. The 133 % is fully explained by the instrument. + +**This weakens the roll numbers above as well.** They were taken the same way, so +`120.9` vs `AV_Roll_Max` `125` is *consistent* but is **not a tight test** — the true +max-speed roll cap could be lower and still produce that average. The min-speed +figures are less affected (there is little speed left to lose). + +**How to measure it properly** (not yet done): keep the dwell to ~1–2 s so the speed +barely moves, or sample the HUD speed continuously and fit rate against +*instantaneous* speed rather than assuming the endpoint. The second is strictly +better and gives the whole rate-vs-speed curve rather than two points. + +**And a flight-model finding in its own right:** *turning costs speed*, steeply — +1 193 → 589 under 8 s of full pitch, with the throttle still held at maximum. A +reimplementation that treats the throttle as setting a speed the craft simply holds +will be wrong during manoeuvres. Data: +[`captures/pitch-rate-speed-bleed.csv`](captures/pitch-rate-speed-bleed.csv). diff --git a/tools/re-capture/rate_probe.py b/tools/re-capture/rate_probe.py new file mode 100755 index 0000000..2ca6c01 --- /dev/null +++ b/tools/re-capture/rate_probe.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python3 +"""Angular rate cap for one axis, at minimum and maximum speed. + +Generalises `roll_axis.py`. Two things it does that the probes before it did not: + +* **Pins the matrix rows.** `entities2` measures row 2 = forward against velocity, + but up-vs-right was previously taken from the D3D convention — and measuring + world-Y in flight showed the convention is *backwards* here (up = row 1, right = + row 0). Roll is immune to that (rotation of either non-forward row within their + shared plane is roll either way), but **pitch is not**, so a pitch number taken + without this pinning is naming an axis by assumption. +* **Brackets each phase with the HUD clock.** Rates are per GAME second and the + clock ratio is a property of the moment — 1.260, 1.311, 1.383 and 1.247/1.218 + across five flights — so it cannot be assumed and must be read per phase. + + roll driven by lx, measured as rotation of UP about forward + pitch driven by ly, measured as rotation of FORWARD toward up + +Usage: rate_probe.py [dwell_s] +Then read HUD TIME off the `rate___[ab]` screenshots for the ratio. +""" +import json +import math +import os +import struct +import subprocess +import sys +import time + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +import speed_law # noqa: E402 +from axis_probe import alive, pad_state, pin_rows, rows_at, dot # noqa: E402 + +DRIVER = {"roll": {"lx": 32767}, "pitch": {"ly": 32767}} + + +def shot(n): + subprocess.run(["screenshot", f"/sylph-home/re/shots/{n}.png"], capture_output=True) + + +def main(): + axis = sys.argv[1] + out_csv = sys.argv[2] + dwell = float(sys.argv[3]) if len(sys.argv) > 3 else 8.0 + if axis not in DRIVER: + sys.exit(f"axis must be one of {list(DRIVER)}") + cfg = json.load(open("/tmp/nav-live.json")) + w, off, nm = speed_law.find_player() + if not w: + sys.exit("player entity not found") + print(f"# locked on {nm}, measuring {axis}") + f_i = cfg["fwd_row"] + if not alive(w, off, cfg): + sys.exit("craft is not moving — not in flight, or already dead") + u_i, w_i = pin_rows(w, off, cfg) + + rows = [] + for label, trig in (("slow", "lt"), ("fast", "rt")): + pad_state(**{trig: 255}) + time.sleep(5.0) # settle: 2 s was shown wrong twice + shot(f"rate_{axis}_{label}_a") + prev = rows_at(w.fd, off, cfg) + pad_state(**{trig: 255}, **DRIVER[axis]) + t0, swept, seq = time.time(), 0.0, [] + while time.time() - t0 < dwell: + time.sleep(0.05) + cur = rows_at(w.fd, off, cfg) + if axis == "roll": + d = math.atan2(dot(cur[u_i], prev[w_i]), dot(cur[u_i], prev[u_i])) + else: + d = math.atan2(dot(cur[f_i], prev[u_i]), dot(cur[f_i], prev[f_i])) + d = math.degrees(d) + swept += abs(d) + seq.append((round(time.time() - t0, 3), round(d, 4))) + prev = cur + pad_state(**{trig: 255}) + wall = seq[-1][0] + shot(f"rate_{axis}_{label}_b") + rows += [(label, *s) for s in seq] + print(f"# {label:<5} wall {wall:5.2f}s {axis} swept {swept:7.1f}deg " + f"rate {swept / wall:6.1f} deg/wall-s") + if not alive(w, off, cfg): + print("# CRAFT STOPPED MOVING — aborting rather than reporting zeros") + break + pad_state() + with open(out_csv, "w") as f: + f.write(f"phase,t,d{axis}_deg\n") + for r in rows: + f.write(",".join(str(x) for x in r) + "\n") + print(f"# wrote {out_csv} — read HUD TIME off rate_{axis}_*_[ab] for the clock") + + +if __name__ == "__main__": + raise SystemExit(main())