Skip to content

lidarslam_ros2 v0.6.0

The deterministic mapping release. Same bag + same config now provably produces the same map: byte-identical loop edges, trajectories and submap streams across runs, enforced by the release gate. The change is architectural — the backend's decision logic moved out of the wall-clock ROS callbacks into pure, unit-tested cores — and it surfaced and fixed several real bugs on the way.

Highlights

Deterministic offline mapping (the headline)

  • Backend: graph_slam_offline_runner replays a recorded odometry bag through the same submap creation, event-driven BackendCore loop search and pose-graph optimization the live node uses — no executor, no wall clock. Three runs are byte-identical on both gate substrates:
  • Livox MID-360 (1079 m, 640 submaps): loop edges + optimized trajectory md5-identical, APE vs reference 7.262851264566504 to the last digit.
  • NTU VIRAL tnp_01: 9 loop edges, Leica-GT APE 0.8460511516520233 to the last digit. Before v0.6 the same byte-identical input produced different loop edges every run (pure scheduling artifacts; see docs/research/determinism-variance-attribution.md).
  • Frontend: scan_matcher_offline_runner drives the real ScanMatcherComponent in lockstep over a raw sensor bag (intra-process pub/sub, drained single-threaded executor, synchronous map update). Three runs over the full NTU tnp_01 bag (5795 clouds) are byte-identical (5795 poses, 133 submaps) — the first determinism coverage for the scanmatcher (NDT) frontend.
  • Gate-enforced: run_release_readiness_checks.sh gains opt-in --offline-determinism-bag and --frontend-determinism-bag stages that fail the release gate on any byte-level mismatch.

Event-driven loop search by default

The backend searches loops on submap arrival instead of a wall-clock timer. The v0.4 deterministic_loop_scheduling experiment is retired — its negative result (a deterministic schedule does not make outcomes deterministic) motivated this architecture. Live evidence for the flip: NTU corrected APE 0.965 (within the ≤ 1.00 profile), MID-360 live replay APE 6.28–6.49 vs the legacy timer's 7.17–7.23. The legacy path remains available behind event_driven_loop_search: false for one release and will be removed in v0.7. Live mode is improved but not byte-deterministic — that contract belongs to the offline runners.

Real bugs fixed along the way

  • A silent ~6% input-frame drop under load (cloud subscription was best-effort QoS with a shallow queue while loop search blocked the executor) — inputs are now synchronized with a deep queue.
  • IMU "rotation" edges weighted the translation block of the g2o error and GNSS "position" anchors weighted the rotation block — GNSS georeferencing had never actually pulled. Both fixed; with GNSS enabled, maps are now georeferenced in the projector's local ENU frame via 2D yaw alignment with gauge release.
  • Missing tie-breaks in five loop-candidate rankings (unordered_map iteration order leaked into results).

Architecture: pure cores, thin shells

  • graph_based_slam_component.cpp 3421 → 2265 lines; the loop-closure pipeline lives in 19 tested pure headers assembled by a ROS-free BackendCore.
  • scanmatcher_component.cpp 2140 → 1694 lines; pose prediction, pose acceptance (including the Tracking/Suspect/Recovery state machine), IMU processing and the map-update policy are pure headers with characterization tests that pin exact log bytes and float arithmetic.
  • Package test count grew past 1100, including determinism contract tests ("same ordered input twice ⇒ bitwise-identical state/output").

Accuracy (unchanged, re-verified throughout)

The RTK-SLAM total-station ground-truth gates from v0.5 stayed green through all 26 PRs of this release; the stadtgarten_seq2 raw APE 0.835 m reproduced bit-identically 19 consecutive times — the strongest behavior-preservation evidence we have ever attached to a refactor.

Sequence Env APE RMSE Gate
Construction Hall 2 indoor 0.154 m blocking, pass ≤ 0.30
Construction Hall 1 indoor (hard) 0.403 m blocking, pass ≤ 0.55
Stadtgarten 2 outdoor park 0.835 m report-only
Stadtgarten 1 outdoor park, 1 km 1.666 m report-only

Upgrade notes

  • deterministic_loop_scheduling no longer exists; remove it from custom param files. event_driven_loop_search defaults to true.
  • The legacy wall-clock loop-search timer (event_driven_loop_search: false) is deprecated and scheduled for removal in v0.7.

Full details: docs/roadmap/v0.6.md (all phases and gates), docs/research/determinism-variance-attribution.md (Phase 0 attribution), CHANGELOG.md.