Frozen velocity-consistency candidate v5 (candidate name velocity_consistency_v4)¶
Freeze this contract before running the six-run comparison. Document numbering
follows the contract revision (fifth contract); the candidate it defines is
named velocity_consistency_v4. vehicle_nhc_latched_v1,
velocity_consistency_v1 (contract),
velocity_consistency_v2 (contract) and
velocity_consistency_v3 (contract) and their
records are unchanged and stay selectable. Development data only; no
application holdout is reopened. The production default stays unchanged even
if this candidate passes.
Control: develop default configuration, candidate none. The recorded control
is develop c2eb06f7 (pva_velcons_20261009/ctl). The recorded set is accepted
as the control only if candidate none built from the candidate tree is
bit-identical to it in every deterministic CSV field on all 18 runs (gate 7).
The same check on the merge of develop 526cccaf (PR #565-#567) into this
branch (68c7faa3) was 18/18 identical before this candidate was written (see
the results record).
What this candidate answers¶
velocity_consistency_v3 failed 4 of 558 gates, all
scenario.recovery_gnss_update_s after the 10 s GNSS outage (Tokyo 2, Tokyo 3,
Nagoya 2: 0 -> 0.2 s; Nagoya 1: 0 -> 18 s). Everything else passed. This
candidate changes only what is needed to answer that one failure.
Diagnosis (instrumented replay of v3, outage 60-70 s, six runs)¶
The first epoch after the outage (t = 70.0 s) is an RTK FLOAT epoch. The
fusion's position NIS gate (9 per observation) sees: innovation = RTK FLOAT
minus propagated INS position, innovation covariance = INS position covariance +
reported FLOAT covariance. ENU errors against reference.csv (offline scoring,
never an estimator input); z = error / reported sigma:
| Run | INS error E/N/U m | INS sigma m | INS z | FLOAT error E/N/U m | FLOAT sigma m | FLOAT z | NIS/obs | v3 |
|---|---|---|---|---|---|---|---|---|
| Tokyo 1 | -2.8 / 0.3 / 0.9 | 1.02 / 0.97 / 0.35 | -2.8 / 0.3 / 2.6 | 0.08 / 0.08 / -0.18 | 0.037 / 0.032 / 0.099 | 2.2 / 2.6 / -1.8 | 6.8 | accepted |
| Tokyo 2 | 5.6 / -4.3 / 1.5 | 0.98 / 0.76 / 0.31 | 5.7 / -5.7 / 4.8 | -0.70 / 0.08 / 11.45 | 0.055 / 0.019 / 0.591 | -12.6 / 4.4 / 19.4 | 88.9 | rejected |
| Tokyo 3 | 1.4 / -1.9 / 9.3 | 1.40 / 1.50 / 1.64 | 1.0 / -1.3 / 5.7 | -0.01 / 0.04 / -0.17 | 0.075 / 0.034 / 0.128 | -0.1 / 1.2 / -1.3 | 11.7 | rejected |
| Nagoya 1 | 32.9 / -14.2 / 0.8 | 1.04 / 1.22 / 0.47 | 31.6 / -11.6 / 1.8 | 0.04 / 0.23 / 0.23 | 0.038 / 0.045 / 0.102 | 1.1 / 5.1 / 2.3 | 387.6 | rejected |
| Nagoya 2 | -1.9 / 10.6 / 0.3 | 1.14 / 0.65 / 0.22 | -1.7 / 16.3 / 1.3 | 0.03 / -0.04 / -0.06 | 0.005 / 0.006 / 0.011 | 7.3 / -6.1 / -5.9 | 100.0 | rejected |
| Nagoya 3 | -0.7 / -3.4 / 1.2 | 1.30 / 1.69 / 0.85 | -0.5 / -2.0 / 1.5 | -0.60 / 0.16 / 1.61 | 0.79 / 1.35 / 13.3 | -0.8 / 0.1 / 0.1 | 1.1 | accepted |
(INS error is the fused position at the last outage epoch for the accepted runs and at 70.0 for the rejected ones.)
- The INS covariance is the dominant cause. In all four rejected runs the propagated INS position is 4.8 to 31.6 sigma from the truth (10 s unaided; the 15-state covariance carries only IMU noise, bias random walk and the attitude it believes: sigma about 1 m, attitude sigma 1-4 deg, while the unmodelled terms that actually drive the drift - attitude/heading error already present at the outage start, the roughly 150 deg heading error of Nagoya 1's reverse start, and the SPP-biased vertical state of Tokyo 3 - are not in it). The FLOAT itself is sub-decimetre in all but Tokyo 2 (z within 3 in Tokyo 3, 1.1-5.1 in Nagoya 1).
- The FLOAT reported covariance is also optimistic where the RTK filter has not reconverged: Tokyo 2's first post-outage FLOAT is 19 sigma off in height (11.45 m, equal to the SPP vertical bias of this data set; the reported sigma is 0.59 m) and Nagoya 2's is 6-7 sigma (millimetre sigma). Making the FLOAT sigma honest cannot remove the rejections: Nagoya 1 (z 31.6) and Nagoya 2 (16.3) would still fail on the INS side.
- Process noise cannot be the remedy. To pass the gate at 70.0 the INS position variance would need to be larger by NIS/9 = 1.3 (Tokyo 3), 9.9 (Tokyo 2), 11 (Nagoya 2) and 43 (Nagoya 1) after 10 s, but only after a gap: the same inflation at the 0.2 s fusion cadence would make the INS prior meaningless in normal operation. The error is not a time-uniform noise deficit but a covariance that is unverified when GNSS has been absent.
- Nagoya 1's 18 s: from 70.0 every precise (FLOAT) update is rejected (INS 35 m away, sigma 1.2 m), SPP updates are rejected too (NIS 47), and the FLOAT/FIXED re-anchor of v2 needs 30 consecutive FLOAT rejections.
Design: trust the measurement over an unverified prior (one change)¶
New default-OFF LooseCouplingProcessor::Config::position_reanchor_after_gnss_gap_s
(0 = off, bit-identical). When it is > 0 and a FLOAT/FIXED position update is
rejected by the NIS gate (or its innovation covariance is invalid), and the last
accepted GNSS position update of any class (SPP, FLOAT, FIXED or a re-anchor)
is older than this horizon, the position-only re-anchor that v2 already uses
(reanchorPositionFromFixedSolution) is applied at once: position := the
lever-arm-compensated GNSS antenna position, position covariance := GNSS
covariance + lever-arm attitude term, position cross-covariances cleared;
velocity, attitude and biases untouched. The clock restarts, so an immediately
following rejection is steady state again.
Why this is covariance-consistent: while GNSS keeps being accepted, every
update verifies the prior's covariance (a rejection then means the measurement
is the outlier, and the NIS gate stays in force exactly as before). After a gap
longer than the horizon nothing has verified the prior since, its covariance
is an unchecked extrapolation, and the test statistic of a gate built on it has
lost its meaning; a rejection is then evidence against the prior. The
measurement it is preferred to carries a reported covariance that
ReportedCovarianceMode::SPP_CONSISTENCY_SCALED (candidate v3, e) has made
consistent with the same epoch's SPP, and the re-anchor adopts exactly that
covariance, so the state is as uncertain as the measurement says (Tokyo 2's
height is taken with sigma 0.59 m, not with the prior's). It is not an
unconditional ungated update: it needs a NIS rejection, only applies to the
precise class, and respects max_fixed_position_reanchor_m.
Rejected alternatives, with reasons from the table above: (i) inflate the FLOAT covariance - cannot fix Nagoya 1/2, the INS side fails; (ii) larger process noise - needs x1.3 to x43 after 10 s only (point 3); (iii) scale the whole prior by NIS/9 at a rejection (covariance matching) - turns the gate into a robust update that no longer rejects: with sigma_prior 1 m, sigma_FLOAT 0.1 m and a 100 m outlier the gain is 1/(1 + 11) and the state is pulled 8 m toward the outlier; the re-anchor takes the measurement's own covariance and is only used after a gap; (iv) lower the number of consecutive rejections of v2 - a pure count, and it would defeat the gate in the steady-state wrong-FLOAT regime v3 depends on (Tokyo 1, 57-66 s).
Constant: horizon = float_reanchor_max_coarse_age_s = 1.0 s, the repository's
existing "GNSS position is no longer current" horizon (v2), not a new value
and not tuned on this problem. Data support: in the six normal runs accepted
position updates arrive every 0.2 s, 0.4-1.0 s apart 65-198 times per run
(rejected epochs), and more than 1.0 s apart 1-7 times per run (Tokyo 1 7, Tokyo 2 2, Tokyo 3 1, Nagoya 1 6, Nagoya 2
7, Nagoya 3 1, including the real outages of up to 15, 34 and 94 s), so the
rule can fire only at those few places, and only if the first precise update
after them is NIS-rejected. The 10 s outage is 10x the horizon and 50x the
update interval, so the exact horizon does not decide the outage scenarios; it
matters only for dropouts of 1-10 s in normal runs.
Not covered, by design: a rejected SPP first update after a gap (the coarse class is never re-anchored), and a gap closed by an accepted update.
Candidate velocity_consistency_v4 (one frozen change set, no tuning)¶
- everything in
velocity_consistency_v3(contract: RTK reported covariance mode 3, isolated RTK prior, NIS gates 9, FLOAT/FIXED re-anchor after 30 rejections), unchanged; - (g)
position_reanchor_after_gnss_gap_s = float_reanchor_max_coarse_age_s(1.0 s).
CLI: gnss pva-evaluate|gnss_pva_replay --candidate velocity_consistency_v4.
No change to sensor axes, lever arms, time, initialization window, truth
alignment, RTK settings or any other threshold. Candidate none and
velocity_consistency_v1/v2/v3 code paths are unchanged (the new branch is
disabled when the option is 0; unit tests cover the OFF default and the trigger
logic).
Expected consequence, stated before the comparison: in Tokyo 2 the re-anchor adopts the 11.45 m height error of a just-reconverging FLOAT whose sigma is optimistic (point 2); the fused height then follows the reconverging FLOAT with the same optimism as the control did at that epoch, so Tokyo 2's outage-run fused position is expected to be worse than v3's and similar to the control's for tens of seconds. The gates compare with the control.
Disclosure of development use¶
All six runs were used before this freeze: the instrumented 500-epoch outage
replay of v3 for the diagnosis table (innovation, INS and reported covariance
at each position update; offline errors from errors.csv), and 600-epoch
GNSS-outage replays (60-70 s) of this candidate on all six runs to confirm that
the first post-outage update is applied (recovery 0.0 s on all six; Tokyo 2 as
expected above), and the full normal replays of this candidate on all six runs
(to check that it does not disturb normal operation): the fused output is
bit-identical to velocity_consistency_v3 on Tokyo 1-3 and Nagoya 3 and
differs only after the first real GNSS dropouts of Nagoya 1 (954 s) and Nagoya 2
(1322 s); the RTK columns are untouched. The full outage and IMU-gap scenarios
of this candidate were not run before the freeze. The normal-run full replays of
v3 had been used earlier (v4 contract). No constant was chosen or changed after
seeing results: the horizon is the existing v2 constant; 3, 9, 30 and 1.0 s are
the earlier contracts' values.
Acceptance (unchanged from online_pva_candidate_v4.md, no relaxation)¶
Population: six full normal runs, plus fixed 60-70 s GNSS removal and 60-64 s IMU gap on each (18 control and 18 candidate replays), identical raw input hashes and replay contract. Gates, per run/scenario unless stated:
- RTK/fused position, RTK/fused velocity and full-rotation RMSE and P95 each <= 1.01 x control, on both the all-output and common-valid cohorts.
- Coverage (RTK, fused, velocity, attitude, heading availability) loses at most 0.1 percentage point.
- First fresh attitude and first heading latch no later; scenario GNSS update, fresh-attitude and heading recovery no later (null is censored, never zero).
- Processor P95 <= 2 x control (host contention is reported, not hidden; a
contemporaneous candidate-
nonerun is reported alongside). - At least one normal-run full-rotation RMSE/P95 improvement.
- Missing metrics, scenarios, truth mismatches or input-hash mismatches fail.
- Candidate
nonebuilt from the candidate tree is bit-identical to the control in every deterministic CSV field (all butprocessing_ms) on all 18 runs.
Go only if every gate passes. Otherwise record No-Go with the failed gates, keep
the defaults, and do not tune this candidate after seeing the results.
Additionally reported (not gates): fused position error around the outage, and
per normal run the first epoch at which the candidate output differs from
velocity_consistency_v3.