Skip to content

Velocity-consistency candidate v2 results: No-Go

velocity_consistency_v1 fails the frozen contract in online_pva_candidate_v2.md (committed before the comparison). Production defaults are unchanged; the candidate remains an explicit opt-in (--candidate velocity_consistency_v1) for reproducibility. Machine-readable gates: online_pva_decision_v2.json.

Control is develop c2eb06f7 (default, Release, native replay SHA256 99c56dcdf357d0a53d065245acbd513d54befb8eb18e3f8b8d2477fb8a02af5f); candidate binary ff02d2cfdec99465ece42c498e61fc3582cb067b7800bbbe571939b625681d86. 36 full replays (six runs x normal, GNSS outage 60-70 s, IMU gap 60-64 s, each control and candidate), all truth-matched, identical input hashes. Candidate none from the candidate tree is bit-identical to the control in all 60 deterministic CSV fields on all 18 runs.

Decision

558 gates, 54 failed (24 per cohort on all-output and common-valid, plus 6 timing gates). Passing: coverage (no loss over 0.1 pp), first fresh attitude, processor P95 (<= 2 x control; normal 5.5-11.5 ms, host contention not controlled), and the targeted rotation improvement. Failed:

  • Nagoya 1, all three scenarios: fused position RMSE 49.3 -> 119.0 m (P95 118.1 -> 178.3), and RTK position 25.7 -> 30.2 m. The GNSS-outage scenario's GNSS-update recovery is also later (0.0 -> 142.8 s).
  • RTK position/velocity P95 regress by 2-9% (the exported RTK filter result, not the fused one) in Tokyo 2 IMU gap, Tokyo 3 normal/outage/gap and Nagoya 1/3. Tokyo 3 outage RTK position RMSE 16.8 -> 25.9 m.
  • Timing: Nagoya 2 first heading 41.8 -> 42.0 s (one 0.2 s epoch later, all three scenarios) and GNSS-update recovery 0.0 -> 0.2 s in Tokyo 2/Nagoya 2 outage.

Every other fused position/velocity/rotation gate passes.

Full-run results (normal), control -> candidate

Run Fused position RMSE / P95 (m) Fused velocity RMSE / P95 (m/s) Rotation RMSE / P95 (deg)
Tokyo 1 71.7 -> 25.9 / 155.2 -> 48.1 7.05 -> 1.20 / 14.62 -> 1.26 105.1 -> 2.9 / 171.1 -> 6.0
Tokyo 2 74.2 -> 13.8 / 179.5 -> 27.0 6.85 -> 0.38 / 14.14 -> 0.78 100.3 -> 1.4 / 170.0 -> 2.6
Tokyo 3 41.9 -> 14.3 / 115.0 -> 26.2 3.22 -> 0.28 / 8.22 -> 0.58 63.5 -> 1.7 / 144.4 -> 3.0
Nagoya 1 49.3 -> 119.0 / 118.1 -> 178.3 4.61 -> 1.30 / 9.84 -> 1.61 88.2 -> 35.4 / 168.7 -> 103.0
Nagoya 2 81.0 -> 18.7 / 168.0 -> 33.8 3.73 -> 0.57 / 9.96 -> 1.08 18.5 -> 3.1 / 39.8 -> 5.5
Nagoya 3 58.9 -> 31.8 / 128.1 -> 66.9 5.54 -> 0.58 / 11.72 -> 0.94 105.0 -> 2.5 / 170.9 -> 4.5

Availability: fused 99.81-99.93% unchanged; heading availability changes by at most 0.04 pp (Nagoya 1, 96.50 -> 96.46). The GNSS-outage and IMU-gap tables (same metrics for the other 12 replays, plus recovery times) are in online_pva_candidate_v2_scenarios.md.

What the failure is

The attitude and velocity fixes work: five of six runs improve position, velocity and rotation by 2-70x, and the GNSS-latch pathology is gone (rotation 1-6 deg RMSE where the control sat near 100 deg). The contract fails on Nagoya 1 and on small RTK-filter regressions. On Nagoya 1 the candidate holds the attitude (rotation 1-5 deg after 300 s) but the fused position locks out: the fraction of epochs with an accepted GNSS position update is 0.81-0.83 before 700 s, 0.07 in 700-780 s and 0.00 from 780 s to the end, leaving a ~170 m offset. This is a position NIS-gate lockout (the gate rejects every update while the prediction is wrong; the trusted-FIXED re-anchor needs 30 consecutive FIXED epochs, which a mostly-FLOAT run never supplies).

Post-hoc diagnostics, not part of the contract and not used to change the candidate (temporary, uncommitted env switches on the same code; one replay each, full run):

Nagoya 1 normal Fused position RMSE (m) Fused velocity RMSE (m/s) Rotation RMSE (deg)
Control 49.3 4.61 88.2
Candidate 119.0 1.30 35.4
Candidate, position gate off 42.9 1.57 105.9
Candidate, velocity gate off 95.5 2.20 107.1

Removing the position gate removes the lockout (position beats the control) but also removes the attitude gain, so on Nagoya 1 the two gates together are what keeps the attitude from collapsing; and with the position gate off Tokyo 3 changes from 14.3 to 4.1 m position and 1.7 to 4.7 deg rotation. The gates are therefore a position/attitude trade-off, not a free improvement. This is one replay per variant and is evidence about mechanism, not a decision.

Validation

Candidate wiring: unit tests OnlineRtkImuTest.VelocityConsistencyCandidateDefaultsAreOff and HeadingLatchReanchorsVelocityAndClearsItsCrossCovariance (new), comparator tests for the candidate name and the v1-only parity gate. The independent-Doppler path (component b) has no synthetic unit test; it is exercised only by the real-data replays above. Release build of the full tree (Python bindings off): run_tests passes 1,495 cases (67 skipped), gnss_online_tests 13/13, python_pva_tests and python_pva_comparison_tests pass. ctest passes 146 of 152 lanes. The six failures are environmental or already failing and not tied to this change: two smartphone lanes (known on develop), python_packaging_tests and python_trajectory_bundle_tests (need the Python binding that this build disabled), python_ros2_node_tests (missing liblibstatistics_collector.so), and python_cli_tests (9 failures after pointing GNSSPP_BUILD_DIR at the build: missing gnss_live/gnss_ppp/station binaries and one QZSS L6 serial case). These were not re-run on an unmodified develop tree. Outputs are under rtklib_v2_ws_output/pva_velcons_20261009/ in the local workspace (ctl, cand, off, decision).

Next experiment (needs a new contract)

Do not reuse this candidate. A defensible follow-up needs a position gate that cannot lock out (for example gating FIXED positions only, or a FLOAT-capable re-anchor after a bounded rejection streak) while keeping the velocity gate and (a)/(b) with its own frozen thresholds and a component ablation (a, b, c separately) on the same 18 runs. Nagoya 1's reverse start and the gyro-bias state learned before the latch remain open.