Follow-up to your ps_methods_gist. I ran all three of your methods against a real
log and tested a fuller force/IMU form. Short version: your energy method is the right
cockpit signal — confirmed against an independent air-frame truth recovered from the
flight-test boom (§5a), it beats the force form on both shape and scale; your Method 1
V(F − sinα) is a good correlate but ~15–25 % under-scaled; and the "exact" force form
I'd sketched has one wrong term — an extra +V·sinγ that double-counts the climb,
because on this box the accelerometer already contains gravity. Drop that term and
V(ForwardG·cosα − VerticalG·sinα) is the best force projection, and the right
transient feed-forward for the display. §5 works through the reference problem
(ground vs air frame, the wind circularity, and how the boom breaks it) — the path there
matters as much as the answer, because a single correlation number misled me twice.
Data: RV-4 / VN-300, flight 2026-07-06B second sortie, log_010.csv
(2.39 M rows @ 416 Hz ≈ 95 min; decimated 8× → ~52 Hz for the fit; 4× → ~104 Hz for the
lag work in §5).
Truth: the air-data energy method ḣ + (V/g)·V̇ for the form comparison (§2–4); an
independent VN-300 inertial energy Ps for the timing/shape analysis (§5).
Every sign in a force form depends on the accelerometer convention, and the arbiter is the static ground reading, not the algebra. From 272 dead-still rows (IAS < 1.5 kt, body rates < 1°/s, |g-vector| = 0.994 ≈ 1 g, so it's genuine gravity):
| At rest | reads | means |
|---|---|---|
| Pitch (taildragger) | +10.9° nose-up | tail down |
| ForwardG | +0.178 = +sin(10.9°) | body-x is nose-up-POSITIVE |
| VerticalG | +0.977 | body-z is up-positive (≈ +1 level) |
Two consequences, both of which I'd previously guessed wrong:
- A nose-up taildragger reads positive ForwardG, not negative. So
ForwardGis already in the frame the wind-axis rotation wants — no sign flip on the forward channel. VerticalGis up-positive, so a down-positive normal term enters as−VerticalG.
Confirmed from the firmware source: the logged ForwardG/VerticalG are raw body
specific force, gravity included (no gravity removal) — remember this, it's the whole
story in §3.
- Energy (your M2, the truth):
Ps = ḣ + (V/g)·V̇ - Lenny M1 (literal):
Ps = V·(ForwardG − sinα) - Force form, corrected:
Ps = V·(ForwardG·cosα − VerticalG·sinα) - Force form + climb (the WRONG one):
Ps = V·sinγ + V·(ForwardG·cosα − VerticalG·sinα)
α = AngleofAttack, γ = FlightPath (which equals asin(VSI/TAS) to 0.05°),
V = TAS, ḣ = VSI.
The fuller force form I'd written added V·sinγ for the climb on top of the
accelerometer projection. That's wrong for this box, and the reason is the input,
not the algebra:
ForwardGalready contains gravity as−sinθ— that's literally why a static nose-up taildragger reads+sinθ(§1). The flight-path/gravity contribution is already inside the accelerometer. AddingV·sinγcounts it twice.
Smoking-gun test on the high-energy segments: the residual of the +V·sinγ form
correlates +0.919 with V·sinγ — the error is that term. Drop it and the
correlation collapses to −0.03. In a dive the +V·sinγ form swings to −40 m/s
where the truth only reaches −18; the corrected form tracks the truth.
The +V·sinγ term would be correct if fed a gravity-removed channel (e.g. the
VN-300's vnLinAcc*, which subtract gravity) — then you'd add sinγ back and drop the
gravity that's no longer in the accel. With the raw ForwardG/VerticalG this box
logs, you must not add it. This is exactly what the gravity-collapse check is for: any
version that doesn't reduce to −sinγ in pure gravity has a term wrong.
Benign flight (rates < 10°/s, |Vg| < 1.5 g), n = 244,745:
| Form | r | slope | rmse (m/s) |
|---|---|---|---|
Lenny M1 V(F − sinα) |
0.908 | 0.845 | 1.79 |
Force, corrected V(Fcosα − Vg·sinα) |
0.910 | 0.857 | 1.80 |
Force + climb + V·sinγ (wrong) |
0.806 | 1.349 | 4.29 |
High-energy (|Ps_true| > 5 m/s — zooms & dives), n = 64,620:
| Form | r | slope | rmse |
|---|---|---|---|
| Lenny M1 | 0.914 | 0.767 | 2.88 |
| Force, corrected | 0.931 | 0.834 | 2.59 |
| Force + climb (wrong) | 0.839 | 1.335 | 6.66 |
Maneuvering (|Vg| > 1.8 g), n = 4,383:
| Form | r | slope | rmse |
|---|---|---|---|
| Lenny M1 | 0.593 | 0.420 | 7.15 |
| Force, corrected | 0.699 | 0.710 | 5.09 |
| Force + climb (wrong) | 0.450 | 1.418 | 17.63 |
Top: a pushover-dive-recover segment — energy truth (black), Method 1 (blue,
undershoots), corrected force form (green, tracks truth), the wrong +V·sinγ form
(red, blows out to −40). Middle: high-energy scatter vs 1:1. Bottom: calibration
curve — the corrected form sits on 1:1 near the origin; Method 1's slope < 1 flattens
in the wings.
Reading it: the corrected force form wins on correlation in every regime and is
the only force form that stays sane above 1.8 g (rmse 5.1 vs 7.2 vs 17.6). That
maneuvering robustness is exactly what the −VerticalG·sinα normal term buys you —
and exactly what Method 1 gives up by dropping it (its slope falls to 0.42, half the
maneuver amplitude). In calm cruise Method 1 is essentially tied and slightly less
biased, because there VerticalG ≈ 1 so −VerticalG·sinα ≈ −sinα and the two forms
nearly coincide.
First, a methodology correction — because I got this wrong at first and it matters.
My initial pass scored each candidate against the air-data energy form and reported a
single r. That's circular: the air-data energy form is itself built from laggy
sensors (baro VSI, differentiated TAS), so using it as the reference made the force
form look like it "led by 0.8 s." It doesn't. A single correlation number conflates
three different defects — wrong shape, wrong timing, wrong scale — and you
can't tell a perfect-but-delayed signal from a genuinely-bad one by r alone.
Fix: use a reference from a different sensor chain (the VN-300 inertial NED velocity — shares none of the pitot/baro lag), and report three numbers.
But be clear about what that reference is and isn't good for — I over-claimed it as "better truth" at first; it isn't, across the board. Credibility checks on the VN itself:
- Timing — trustworthy.
d/dt(VN speed)lines up with the VN's own gravity-removed body accel at lag +0.01 s, r = 0.71 (INS internally self-consistent), and the VN EKF velocity leads raw GNSS by 0.23 s (r = 1.000) — the fusion is fast, not laggy. So for timing questions the VN is a legitimate independent reference. - Scale / absolute value — NOT trustworthy. VN velocity is ground/inertial
speed; Ps energy is properly defined on air speed. This flight had ~30 kt of
wind (vnWindSpd mean 15.4 m/s, 90th pct 21 m/s), and
Vground − TASruns −2.8 ± 5.4 m/s. So the VN measures a different energy whenever there's wind — which is most of the time here. It cannot be an absolute-scale truth, and itsd/dtadds its own noise.
Net: trust the VN for timing, distrust it for scale. The table below is read that way — the lag column is solid, the slope column is contaminated by the wind frame.
Report three numbers:
- best-lag r (
r_max): shape quality, after allowing a time shift. "Could it be good if we fixed the timing?" - lag τ*: the timing error itself.
- post-alignment slope + RMSE: scale + residual, once shape and timing are removed.
r_max − r₀ is exactly how much the lag alone costs you.
vs the lag-free inertial truth ḣ_vn + (V/g)·V̇_vn, high-energy segments:
| Live form | r₀ (as-is) | r_max (shape) | lag τ* | slope | RMSE raw→aligned |
|---|---|---|---|---|---|
air-data energy ḣ_baro+(V/g)V̇_air |
0.62 | 0.69 | ~0.00 s | 0.48 | 7.2 → 5.9 |
force form V(Fcosα−Vg sinα) |
0.72 | 0.79 | ~0.00 s | 0.48 | 6.6 → 5.0 |
What this actually says (correcting the earlier claim):
- Timing is ~0 for BOTH against inertial truth. Neither is really a lead/lag
problem — de-lagging barely helps (
r_max − r₀≈ 0.07). My earlier "energy lags 0.6 s / force leads 0.8 s" was an artifact of the circular reference. This is the answer to "how would we tell": measure against a reference that doesn't share the suspect signal's lag source. - The force form appears to win on SHAPE —
r_max0.79 vs 0.69, lower aligned-RMSE. This is suggestive, not settled: "shape" against a ground-frame reference is contaminated by the 30 kt wind, so some of the difference could be which form happens to track ground-energy better, not air-energy. It leans force-form, but I wouldn't bank the cockpit decision on this line alone. - The slope ≈ 0.48 is NOT a real scale finding — it's mostly the ground-vs-air
frame difference in the reference (30 kt wind), plus VN
d/dtnoise. The air-frame scale gap is the ~15 % from §2/§6, measured against the air-data energy truth, and that's the number to trust for scale.
Why I can't settle the air-frame ranking on this flight — the wind is circular.
The obvious fix for the ground-vs-air-frame problem is to wind-correct the VN velocity
(V_air = V_ground − wind) and rebuild the truth in the air frame. That doesn't
work here, and it's worth being explicit about why. The VN-300 wire frame carries no
airspeed and no wind — the vnWind* log columns are computed on the OnSpeed box by a
wind-triangle solver (onspeed_core/src/aero/WindTriangle.h):
V_wind = V_ground − V_air, whereV_airis[TAS, 0, 0]rotated into NED.
That TAS is our own pitot. So V_air_reconstructed = V_ground − V_wind = V_ground − (V_ground − TAS) = TAS. Wind-correcting the VN gives our own TAS back,
laundered through the VN — zero independent information, any agreement tautological.
So within this log there is no independent air-frame truth: the only lag-free
inertial signal (VN velocity) is ground-frame, and the only thing that would make it
air-frame (wind) is derived from the TAS we're trying to check. The vnVelNed timing
is still independent (parsed raw off the wire, owes nothing to TAS) — which is why the
timing result above stands — but scale/shape in the air frame cannot be
adjudicated from 2026-07-06B.
The independent airspeed the previous paragraph said we'd need is already in this
log — the flight-test boom (AirDAQ probe), a separate air-data source wired
independently of the main pitot that feeds TAS and the wind triangle. boomIAS is
hard-zeroed on-box (BoomSerial.cpp: staging.IAS = 0; — the firmware never derives it),
but the raw dynamic/static pressure counts are logged. Recovering IAS from them with
the firmware's own conversions (BoomDynamicConvert counts→mbar, then the firmware CAS
inversion PitotPsiToIasKt):
- boom IAS vs main IAS: r = 0.9988, slope 1.014, −5.9 ± 1.4 kt offset — near-perfect
shape, a small fixed boom position/cal bias. It's a genuine, independent air-data
chain, so
Ps_boom = ḣ + (V_boom/g)·V̇_boomis an air-frame truth that owes nothing to the main pitot or the wind triangle. The circularity is broken.
vs the boom air-frame truth (this reverses the VN-based lean above):
| Form | r₀ | r_max (shape) | lag τ* | slope | RMSE (m/s) |
|---|---|---|---|---|---|
air-data energy ḣ+(V/g)V̇ |
0.93 | 0.955 | −0.3 s | 0.85 | 1.6–2.8 |
force form V(Fcosα−Vg sinα) |
0.85 | 0.88–0.90 | +0.8…+1.1 s | 0.72 | 2.3–4.1 |
Boom air-frame truth (black) vs the two estimators over the same maneuver. The energy form (green) tracks the broad envelope; the force form (blue) runs early and over-swings on the way down. At the sharp ~t=4263 transient neither catches the boom's −42 peak, but the force form gets closer. Bottom: high-energy scatter vs 1:1 — energy (green) hugs the line, force (blue) compresses.
- The air-data energy form wins on shape and scale against air-frame truth — r_max 0.955 vs 0.90, slope 0.85 vs 0.72 — in both calm and high-energy.
- The force form genuinely leads by ~1 s in the air frame. So the fighter-HUD intuition had the direction right (the accelerometer does lead the air-relative energy), but here that lead hurts a steady readout — it reacts to the stick before the energy state moves.
- This is the opposite of §5's VN result, and now the discrepancy is explained: the 30 kt wind contaminated the VN ground-frame reference in a way that flattered the force form. The boom removes that contamination, and the energy form's better shape shows through.
- One place the force form still helps: at a sharp transient (a ~½-second Ps spike to ≈ −42 m/s in the record — likely a stall break) neither form tracks the boom peak, but the force form gets closer (−28 vs −19), because it doesn't differentiate and catches the fast edge the smoothed energy form blurs. Argues for it as a transient/maneuver feed-forward, not the primary readout.
Caveats kept honest: the boom truth itself leads the main air-data by ~0.36 s (probe
placement + filtering), so absolute lag numbers carry ±0.3 s of reference uncertainty —
but that doesn't touch the shape/scale ranking, which the energy form wins outright.
And the −42 m/s transient peak is partly the boom's own d/dt noise on a pressure
spike. One flight, still worth a second maneuver-heavy log to confirm.
Cockpit recommendation (now settled for the air frame): show the air-data energy
form ḣ + (V/g)·V̇ (TAS-based, causal ~0.3 s smooth on V̇). It has the right frame,
best shape, best scale, and no meaningful lag against independent air-frame truth. Keep
the corrected force form V(ForwardG·cosα − VerticalG·sinα) as a transient/maneuver
feed-forward (it catches fast edges and needs no differentiation) — blend it in above
~1.5 g or during rapid Ps changes, energy form as the primary number.
Both force forms sit at slope ≈ 0.83–0.85 vs the air-data energy truth (§2), and ≈ 0.48 vs the inertial truth (§5), even after all sign/term fixes. That it's shared across forms says it's a common systematic, not a formula bug — most likely AOA/pitot lag vs the IMU, the TAS-rate smoothing, or a real accelerometer installation-cal scale. It's a fixed gain, easy to calibrate out per airframe. (Part of the inertial-reference slope is the ground-vs-air-frame difference in the reference, not the estimator.)
Net: show the air-data energy form ḣ + (V/g)·V̇. Confirmed against an
independent air-frame truth (§5a, boom pressures), it wins shape (r_max 0.955 vs 0.90)
and scale (slope 0.85 vs 0.72) over the force form, with no meaningful lag. Method 1
V(F−sinα) is a fine quick correlate but under-scaled and maneuver-fragile. The exact
force form's structure is correct — delete only the +V·sinγ term (gravity already
lives in ForwardG on this box); corrected, V(ForwardG·cosα − VerticalG·sinα) is the
best force projection and the right transient feed-forward — it catches fast edges
(e.g. a stall break) the differentiated energy form blurs, so blend it in above ~1.5 g
with the energy form as the primary number.
A note on method, since the conclusion flipped twice: a single correlation r
conflates shape, timing, and scale, and using a lag-laden or wrong-frame reference
silently corrupts it. The fix that finally settled this: (1) report shape (best-lag r),
timing (the lag), and scale (post-alignment slope) separately; (2) validate the
reference itself — the VN velocity is trustworthy for timing but ground-frame (wrong in
30 kt wind), and its wind estimate is circular (computed from our own TAS); (3) find a
reference from a genuinely independent sensor chain — here, the flight-test boom's own
pressures. Each of those steps changed the answer.



