Frozen velocity-consistency candidate v4 (candidate name velocity_consistency_v3)¶
Freeze this contract before running the six-run comparison. Document numbering
follows the contract revision (fourth contract); the candidate it defines is
named velocity_consistency_v3. vehicle_nhc_latched_v1,
velocity_consistency_v1 (contract) and
velocity_consistency_v2 (contract, open PR) and
their records are unchanged. 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); develop is now 526cccaf.
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).
Root cause answered by this candidate¶
Earlier candidates worked around the fusion's rejection of the RTK FLOAT stream (declared sigma 0.1 m, actual error 10-150 m). The upstream cause is in the RTK processor, not in the fusion:
RTKProcessor::generateSolution(src/algorithms/rtk.cpp:238before this change) setssolution.position_covariance = 0.01 * Ifor every FLOAT and FIXED solution. It is a constant, not the filter covariance. It is also the only place a DD RTK covariance is produced (rtk.cpp:389is the safe-float continuity path).- Why FLOAT can be 10-150 m off while the filter looks healthy (Tokyo 1, 57 s
onward, debug columns of the online replay): every measurement row is
zeroed by the outlier suppression (
rtk_filter.cpp:458, 30 m threshold;rtk_update.cpp:256). Suppressed rows = 2 x observations (two iterations), NIS 0.000, maximum prefit residual 25 m growing to 113 m, so the epoch is a measurement-free propagation of the INS prior. The prior is the tight predictor that is re-anchored from this very posterior every exact-base epoch (online_rtk_imu.cpp:174,231), a self-confirming loop; the position drifts at the wrong INS velocity (33 -> 125 m in 8 s) and is still emitted as FLOAT because the only divergence gates compare with SPP at 150 m (rtk_epoch.cpp:360). - A second, smaller effect: each epoch runs up to two measurement iterations
that re-apply the same rows to the already updated covariance
(
rtk_epoch.cpp:308-313), so the Kalman marginal is over-confident by up to the iteration count even when nothing else is wrong. - Remaining after both are handled (not addressed): in converged epochs the filter marginal (cm-level) is still 3-6x too small versus decimetre errors (time-correlated code multipath is modelled as white). Fixing that needs a measurement-model change inside the RTK filter and is out of scope.
Measured on the six online replays (control, FLOAT epochs only; error versus the offline reference, ENU z = error / reported sigma): 284-908 FLOAT epochs per run, reported sigma 0.14 m horizontal, RMS z 35-547 per axis, 8-61 % of epochs inside 3 sigma per axis, NEES within the chi-square 99.73 % bound for 7-30 %. SPP epochs are consistent (RMS z 0.3-2.3) and FIXED epochs are consistent.
Design: report an honest covariance, do not touch the filter¶
New default-OFF RTKConfig::reported_covariance_mode
(LEGACY_FIXED_SIGMA = 0, bit-identical). Only
PositionSolution::position_covariance of FLOAT epochs changes; no state,
position, ambiguity or status is modified, so the batch RTK .pos stream is
independent of the mode. Mode SPP_CONSISTENCY_SCALED (3) reports
C = s * v * P1,
P1: Kalman position marginal after the first measurement iteration (a single application of the epoch's rows),v = max(1, NIS_per_observation)of that pass (innovation variance factor),s >= 1: smallest factor for which the FLOAT-minus-SPP differencedis consistent with its own covariance,d' (s v P1 + C_spp)^-1 d <= 3(the expected value of a 3-dof statistic;src/algorithms/rtk_covariance_consistency.cpp). The SPP solution is the one the epoch already computes for its gates (rtk_epoch.cpp:158); no extra SPP run. No evidence (no SPP, non-finite) givess = 1. FIXED is unchanged.
Modes 1 (last-iteration marginal) and 2 (P1 * v, no SPP check) exist for
ablation and are not part of the candidate. CLI for the batch solver:
gnss_solve --rtk-reported-covariance legacy|filter|first-pass|spp-consistency,
--rtk-covariance-log FILE.
Candidate velocity_consistency_v3 (one frozen change set, no tuning)¶
- (e) RTK reported covariance mode 3 (above).
- (f)
OnlineRtkImuProcessor::Config::rtk_prior_fusion: the RTK filter's INS prior is bootstrapped from an isolated loose-coupling filter built from the control fusion configuration and fed the legacy RTK covariance (rtk_reported_covariance_replaced=>0.01 * I). The fused output filter can then gate, re-anchor and use the honest covariance without changing the RTK filter's own output. This is the decoupling recommended by the v3 record ("keep the control's tight predictor for the RTK prior and run the corrected state as a separate output"); it also forgoes any benefit the RTK filter would get from a better fused state. - (c)
max_position_update_nis_per_observation = 9,max_velocity_update_nis_per_observation = 9(as v1/v2). - (d)
float_position_reanchor_after_rejections = 30,float_reanchor_max_coarse_age_s = 1.0(as v2; its code is cherry-picked fromf91eb44b).
Not included: (a) heading-latch velocity re-anchor and (b) independent Doppler velocity. (b) changed the RTK filter's prior and caused the RTK-gate failures of v1/v2. (a) is dropped on development evidence (below): with an honest covariance it changes rotation by up to 2.2 deg, mostly for the worse, and does not help position.
CLI: gnss pva-evaluate|gnss_pva_replay --candidate velocity_consistency_v3.
No change to sensor axes, lever arms, time, initialization window, truth
alignment, RTK settings or any other threshold. Candidate none code paths are
unchanged (all new branches are disabled by default).
Disclosure of development use¶
All six normal runs were used before this freeze, with an instrumented replay
(per-epoch RTK status, covariance, SPP scale, update diagnostics; offline
scoring against reference.csv, never fed to the estimator). Normal-run
ablations on the full runs (fused position/velocity/rotation and RTK columns),
all from this tree: covariance mode 0/1/2/3 alone; mode 3 + {c}, {a,c},
{a,b,c}, {a,b,c,d}; mode 1 + {a,c}; with the isolated RTK prior: mode 3 +
{c}, {a,c}, {c,d}, {a,c,d}. Selection from them: honest covariance is what
removes the attitude failure (rotation 105 -> 4-8 deg on three runs with
covariance alone); {c} makes it uniform (1.7-2.9 deg on five runs); {d} is kept
because without it Nagoya 1 fused position RMSE (54.6 m) is above the control
(49.3 m) while with it 17.7 m; {a} and {b} are dropped as above. The outage and
IMU-gap scenarios were not run on this candidate before the freeze. No constant
was changed after seeing results: 3 (statistic target), 9, 30 and 1.0 s are the
repository's or the earlier contracts' values.
Acceptance (unchanged from online_pva_candidate_v3.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): covariance consistency of the RTK FLOAT
stream before and after (this document's measurement), and the batch RTK
(gnss_solve, PPC native-replay recipe) .pos output with the mode OFF and
mode 3 against the unmodified binary.