re(flight): the ramp test is inconclusive, and names the tool the residual needs
To separate "a 1 s burst never reaches the steady rate" from "a per-axis
multiplier", measure inside ONE hold: successive 0.25 s windows of a single 3 s
press, holding speed, attitude and starting conditions constant by construction.
rep0 rate 16 59 325 209 130 322 238 110 310 151 183
rep1 rate 52 181 231 246 236 169 368 195 181 255 182
Not usable. At 0.25 s the windows do not contain enough guest updates to average,
so rate and speed both swing 3x window to window -- the same aliasing that once
manufactured a rate-vs-speed curve, reappearing at finer resolution. The first
window is lowest in BOTH repeats, which is what a ramp would look like, but the
sequence never plateaus, so the signal cannot be separated from the sampling. No
claim either way.
Widening the window does not rescue it: 0.5 s averages well enough, but a hold
long enough to contain several 0.5 s windows bleeds speed -- and speed is the
variable under test. The two effects are entangled at this observation rate.
So the residual needs a different INSTRUMENT, not another script. Live-RAM polling
samples an unsynchronised snapshot; the question wants the craft's angular
velocity as the guest computes it, once per frame. That is a Canary-side hook --
the same shape as the existing F10 ship-capture patch -- and the rebuild toolchain
already makes it cheap. Recorded as the recommendation rather than attempted as a
seventh variation of the same measurement.
This commit is contained in:
23
docs/re/captures/roll-ramp-inconclusive.csv
Normal file
23
docs/re/captures/roll-ramp-inconclusive.csv
Normal file
@@ -0,0 +1,23 @@
|
||||
rep,t,speed,rate_deg_per_wall_s
|
||||
0,0.27,390.1,15.5
|
||||
0,0.54,233.9,58.9
|
||||
0,0.81,573.5,325.0
|
||||
0,1.07,341.0,209.1
|
||||
0,1.34,222.8,130.3
|
||||
0,1.61,525.5,321.8
|
||||
0,1.87,386.6,237.6
|
||||
0,2.14,190.9,110.4
|
||||
0,2.41,511.3,310.2
|
||||
0,2.66,246.7,151.1
|
||||
0,2.94,312.3,183.0
|
||||
1,0.27,446.6,51.6
|
||||
1,0.54,372.5,181.1
|
||||
1,0.8,377.7,231.0
|
||||
1,1.07,417.7,246.3
|
||||
1,1.32,385.0,236.1
|
||||
1,1.59,232.9,169.4
|
||||
1,1.85,590.3,368.1
|
||||
1,2.11,307.0,194.7
|
||||
1,2.38,311.9,180.7
|
||||
1,2.64,418.6,255.2
|
||||
1,2.9,306.5,181.9
|
||||
|
@@ -702,3 +702,37 @@ so the row pin came back **WEAK** (margin 0.109). That refusal is correct for pi
|
||||
and yaw — but **roll is immune to the up/right labelling**, which is exactly what the
|
||||
guard's own message says, so `ALLOW_WEAK_PIN=1` is the documented and legitimate
|
||||
override there. It is worth having a guard that states its own exception.
|
||||
|
||||
|
||||
## The ramp test is inconclusive — and says what the next tool must be 🟡
|
||||
|
||||
To separate "a 1 s burst never reaches the steady rate" from "a per-axis multiplier",
|
||||
the cheapest test measures *inside* one hold: successive 0.25 s windows of a single
|
||||
3 s press, so speed, attitude and starting conditions are held constant by
|
||||
construction. Two repeats, roll, at cruise:
|
||||
|
||||
```
|
||||
rep0 rate 16 59 325 209 130 322 238 110 310 151 183
|
||||
speed 390 234 574 341 223 526 387 191 511 247 312
|
||||
rep1 rate 52 181 231 246 236 169 368 195 181 255 182
|
||||
speed 447 372 378 418 385 233 590 307 312 419 306
|
||||
```
|
||||
|
||||
**Not usable.** At 0.25 s the windows do not contain enough guest updates to average,
|
||||
so both channels swing by 3× window to window — the same aliasing that once
|
||||
manufactured a rate-vs-speed curve, reappearing at finer resolution. The first window
|
||||
is the lowest in *both* repeats, which is what a ramp would look like, but the
|
||||
sequence never plateaus, so the signal cannot be separated from the sampling.
|
||||
|
||||
**Widening the window does not rescue it**: 0.5 s windows average well enough (that is
|
||||
what the earlier sweep used), but a hold long enough to contain several of them bleeds
|
||||
speed, and speed is the very variable under test. The two effects are entangled at
|
||||
this observation rate.
|
||||
|
||||
**So this residual needs a different instrument, not another script.** Live-RAM polling
|
||||
samples an unsynchronised snapshot; what the question wants is the craft's angular
|
||||
velocity *as the guest computes it, once per frame*. That is a Canary-side hook —
|
||||
the same shape as the existing F10 ship-capture patch — and the rebuild toolchain
|
||||
already makes it cheap. Recorded as the recommendation rather than attempted as a
|
||||
seventh variation of the same measurement.
|
||||
Data: [`captures/roll-ramp-inconclusive.csv`](captures/roll-ramp-inconclusive.csv).
|
||||
|
||||
94
tools/re-capture/ramp_probe.py
Executable file
94
tools/re-capture/ramp_probe.py
Executable file
@@ -0,0 +1,94 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Does the angular rate RAMP after the stick goes over?
|
||||
|
||||
Burst measurements came out a strikingly flat 0.60-0.64x of the roll cap and
|
||||
1.07-1.21x of the pitch cap. A clock error cannot do that (it would move both the
|
||||
same way), so something per-axis is going on, and the cheapest candidate is that a
|
||||
short burst never reaches the steady rate — an angular-acceleration ramp would make
|
||||
a 1-second burst under-read.
|
||||
|
||||
Rather than compare separate bursts (which differ in speed, attitude and starting
|
||||
conditions), measure INSIDE one hold: successive 0.25 s windows of a single 3 s
|
||||
press. A ramp shows up as the first windows reading low and the later ones
|
||||
plateauing, with everything else held constant by construction.
|
||||
|
||||
Windows, never per-read deltas: polling outruns the guest's update of these fields.
|
||||
|
||||
Usage: ramp_probe.py <roll|pitch> <out.csv> [hold_s] [repeats]
|
||||
"""
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
import struct
|
||||
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}}
|
||||
WIN = 0.25
|
||||
|
||||
|
||||
def pos_at(fd, off):
|
||||
return struct.unpack(">3f", os.pread(fd, 12, off))
|
||||
|
||||
|
||||
def main():
|
||||
axis, out_csv = sys.argv[1], sys.argv[2]
|
||||
hold = float(sys.argv[3]) if len(sys.argv) > 3 else 3.0
|
||||
reps = int(sys.argv[4]) if len(sys.argv) > 4 else 2
|
||||
cfg = json.load(open("/tmp/nav-live.json"))
|
||||
w, off, nm = speed_law.find_player()
|
||||
if not w:
|
||||
sys.exit("player entity not found")
|
||||
f_i = cfg["fwd_row"]
|
||||
if not alive(w, off, cfg):
|
||||
sys.exit("craft is not moving")
|
||||
u_i, w_i = pin_rows(w, off, cfg)
|
||||
print(f"# locked on {nm}, {axis} ramp over {hold}s in {WIN}s windows")
|
||||
|
||||
rows = []
|
||||
for rep in range(reps):
|
||||
pad_state() # cruise: least speed bleed of the three
|
||||
time.sleep(6.0)
|
||||
prev_r, prev_p = rows_at(w.fd, off, cfg), pos_at(w.fd, off)
|
||||
pad_state(**DRIVER[axis])
|
||||
t0 = wt0 = time.time()
|
||||
path = swept = 0.0
|
||||
seq = []
|
||||
while time.time() - t0 < hold:
|
||||
time.sleep(0.02)
|
||||
now = time.time()
|
||||
cur_r, cur_p = rows_at(w.fd, off, cfg), pos_at(w.fd, off)
|
||||
path += math.dist(cur_p, prev_p)
|
||||
if axis == "roll":
|
||||
d = math.atan2(dot(cur_r[u_i], prev_r[w_i]), dot(cur_r[u_i], prev_r[u_i]))
|
||||
else:
|
||||
d = math.atan2(dot(cur_r[f_i], prev_r[u_i]), dot(cur_r[f_i], prev_r[f_i]))
|
||||
swept += abs(math.degrees(d))
|
||||
prev_r, prev_p = cur_r, cur_p
|
||||
if now - wt0 >= WIN:
|
||||
span = now - wt0
|
||||
seq.append((round(now - t0, 2), round(path / span, 1), round(swept / span, 1)))
|
||||
wt0, path, swept = now, 0.0, 0.0
|
||||
pad_state()
|
||||
for t, sp, rt in seq:
|
||||
rows.append((rep, t, sp, rt))
|
||||
print(f"# rep{rep}: " + " ".join(f"{rt:.0f}" for _, _, rt in seq))
|
||||
print(f"# speed " + " ".join(f"{sp:.0f}" for _, sp, _ in seq))
|
||||
if not alive(w, off, cfg):
|
||||
print("# CRAFT STOPPED MOVING — aborting")
|
||||
break
|
||||
time.sleep(2.0)
|
||||
|
||||
with open(out_csv, "w") as f:
|
||||
f.write("rep,t,speed,rate_deg_per_wall_s\n")
|
||||
for r in rows:
|
||||
f.write(",".join(str(x) for x in r) + "\n")
|
||||
print(f"# wrote {out_csv}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user