Skip to content

Add fuse odometry localization for the hangar_sim mobile base - #790

Open
bkanator wants to merge 2 commits into
mainfrom
feat/19667-fuse-odometry
Open

Add fuse odometry localization for the hangar_sim mobile base#790
bkanator wants to merge 2 commits into
mainfrom
feat/19667-fuse-odometry

Conversation

@bkanator

@bkanator bkanator commented Jul 17, 2026

Copy link
Copy Markdown

[written by AI]

Closes #19667.

Problem

hangar_sim's mobile base drove on raw MuJoCo odometry — too clean to exercise the real localization stack. To represent a real Ridgeback, the base needs fuse-fused odometry (wheel + IMU) with realistic drift, and beluga_amcl must localize reliably against it during Navigate to Clicked Point — including where the scene is degenerate for scan matching (the smooth fuselage, the unmapped picking boxes).

Approach

  • Fuse on by default (use_fuse=true): fuse fuses wheel odometry + IMU into /odom_filtered.
  • odomworld drift injection: with fuse on, odom_world_drift publishes a live odomworld transform from the fuse estimate, so AMCL sees real drift to correct while worldbase stays ground truth for whole-body planning. odom_topic stays on raw /odom — no manual swap.
  • Slip-aware wheel covariance (slip_aware_odom, new C++ node): republishes wheel odometry with a yaw covariance that grows during sustained in-place spin (mecanum rollers slip), so fuse defers to the IMU while spinning and trusts the wheels when driving straight.
  • AMCL tuning for the mecanum base: OmniMotionModel; alpha1 0.1→0.4 to stop yaw lock-loss during spins; update_min_a 0.1 with resample_interval 3 to correct often without particle depletion.
  • AMCL likelihood relaxation: sigma_hit 0.1→0.25, z_hit 0.9→0.65, z_rand 0.1→0.3 so the unmapped picking boxes read as outliers instead of yanking the whole estimate — the box-divergence axis the fuse fix alone didn't touch.
  • fuse smoother window (lag_duration 0.5→0.3): a shorter fixed-lag window cuts the estimator's output latency so the fused pose tracks the truth more tightly during motion — moving-yaw error 0.70°→0.465° — while staying long enough to smooth transient spikes (0.25 reached a marginally lower median but reintroduced a rare ~125° transient).
  • Degeneracy gate (amcl_odom_gate, new C++ node): where AMCL's scan-match goes degenerate — the base hugging the smooth fuselage (slide-along ambiguity) or crossing the transient unmapped boxes — it holds the last good mapodom and coasts on fuse's odometry through the zone, then blends back once AMCL is trustworthy again. A large correction is neither hard-accepted nor hard-rejected: it is accepted only if it persists over a sliding window (a real fix persists at one pose; an ambiguity teleport thrashes), gated on both position and yaw, with particle-spread hysteresis as a second, independent trigger — so a valid large correction such as recovering from a bad initial-pose seed still gets through. A confident-but-wrong lock is the hard case the sliding window alone cannot catch: sliding along the smooth fuselage, AMCL settles at a wrong pose that also persists, with a severely spread cloud. spread_accept_max closes that gap — a persisted correction is accepted only while the cloud is tight enough, so a real recovery (which converges) is still adopted while a scan-slide divergence (which stays spread) is coasted through instead. AMCL runs tf_broadcast:=false; the gate is the sole mapodom publisher. The pure decision logic (detail::updateGate) has no ROS/TF deps and is unit-tested for every case (transparent tracking, thrashing/yaw teleports held, persistent correction accepted, severe-spread wrong-lock rejected, spread hysteresis, SE(2) math).
  • Latency compensation (latency_compensation_sec on amcl_odom_gate): during motion the mapbase estimate trailed the truth by ~14 cm — not the gate's smoothing but AMCL's own scan-processing lag (~300 ms), which its message stamp under-reports. The gate now buffers odombase history and composes AMCL's correction with the odometry from one lag ago (referenced to the cloud's own stamp), forward-projecting it to the present using real buffered odometry (so it cannot overshoot on sharp turns). 0 reproduces the old compose-at-latest behaviour exactly; hangar_sim sets 0.30, tuned to the measured lag (re-measure per platform). A backward sim-clock jump flushes the buffer so a reset cannot poison it.
  • Removed the per-objective SetInitialPose reseed from the clicked-point Objectives: superseded by slip_aware_odom's cross-controller re-anchoring (which keeps odom continuous across the whole-body↔nav handoff it was added for), and the unconditional reseed could cement a drifting estimate.
  • Turn-rate cap: wz_max 0.6 — below the velocity_smoother's 1.0 cap so it actually binds — keeps commanded spins within AMCL's correction bandwidth so the map tracks instead of lagging.
  • CPU reductions: odom_rate 50 Hz, tf_publish_rate 30 Hz, and the broadcaster update_rates decoupled from the high-rate control loop, giving the localization stack headroom.

Results

On a fresh sim with adequate CPU, a 40-goal aggressive-turning route reaches 40/40 goals at ~0.12 m ATE with no divergence, reproducibly across runs. The map still lags slightly during fast turns (inherent AMCL correction latency), but Navigate to Clicked Point reaches every goal.

The degeneracy gate targets the two failure modes that remained: on a stress test that drives a goal 0.3 m from the smooth fuselage (deep in costmap inflation), bare likelihood_field diverges and never recovers on ~75% of attempts; with the gate that drops to ~10% transient failures that self-recover within 1–2 attempts. The box cluster is contained with no strand. The gate assumes odometry is trustworthy for the (transient) duration of a degenerate zone — a bound documented in the node.

Latency compensation cut the residual in-motion error further: on the fuselage route, moving-pose error dropped 13.8→8.2 cm and moving-yaw 1.2→0.9° (3-run medians, tuning latency_compensation_sec), with at-rest error and objective success unchanged and the wrong-lock protection intact. A long soak (252 goals across the hangar) ran with zero failures.

Localization quality is CPU-bound: a co-scheduled second sim can starve AMCL into divergence even with this config, so run one sim per host when benchmarking.

Docs

Paired documentation PR: PickNikRobotics/moveit_pro#20578 (Localization Tuning + Whole-Body Mobile Architecture guides).

Manual verification

ros2 launch hangar_sim and run Navigate to Clicked Point to a goal that requires a large heading change; the robot reaches the clicked point. use_fuse:=false falls back to raw odometry.

Release notes

Enhancement: hangar_sim now localizes its mobile base with fuse odometry (wheel + IMU) and tuned beluga_amcl, with a degeneracy-aware gate that keeps localization stable where the scan match is ambiguous (the smooth fuselage and unmapped boxes) and compensates the estimator's motion latency so the map tracks the base more tightly while driving, so Navigate to Clicked Point reaches goals reliably during simulated navigation.

@bkanator bkanator added this to the 10.0.0 milestone Jul 17, 2026
@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added slip-aware odometry for smoother, continuous navigation pose updates.
    • Added localization gating to reject unreliable AMCL corrections and recover gradually.
    • Added simulation odometry drift tracking using ground-truth pose data.
  • Improvements

    • Enabled sensor fusion by default and refined localization and SLAM launch behavior.
    • Tuned AMCL, navigation, controller, and sensor update rates for improved stability.
    • Updated navigation behaviors to avoid resetting the initial pose between objectives.
  • Testing

    • Added automated coverage for localization gating and odometry drift handling.

Walkthrough

Changes

The PR adds slip-aware odometry, AMCL transform gating, and dynamic odom -> world drift publishing. It connects these nodes to simulation launch, fuse, Nav2, MuJoCo, package dependencies, build targets, and unit tests.

Fuse localization integration

Layer / File(s) Summary
Ground-truth drift publisher
src/hangar_sim/description/ur5e_ridgeback.xml, src/hangar_sim/include/hangar_sim/odom_world_drift_logic.hpp, src/hangar_sim/include/hangar_sim/se2.hpp, src/hangar_sim/src/odom_world_drift.cpp, src/hangar_sim/test/test_odom_world_drift.cpp
Adds the base_gt site, rail-joint index resolution, shared SE(2) operations, dynamic odom -> world publication, and rail-joint tests.
Slip-aware odometry relay
src/hangar_sim/src/slip_aware_odom.cpp, src/hangar_sim/config/fuse/fuse.yaml
Adds continuous odometry relaying, source handoff handling, slip tracking, stale-source bridging, and the /odom_slip_aware fuse input.
AMCL odometry gate and tests
src/hangar_sim/include/hangar_sim/amcl_odom_gate_logic.hpp, src/hangar_sim/src/amcl_odom_gate_logic.cpp, src/hangar_sim/src/amcl_odom_gate.cpp, src/hangar_sim/test/test_amcl_odom_gate.cpp
Adds buffered odometry interpolation, gated AMCL correction logic, transform broadcasting, and unit coverage for persistence, hysteresis, and pose handling.
Fuse launch and localization configuration
src/hangar_sim/CMakeLists.txt, src/hangar_sim/package.xml, src/hangar_sim/launch/sim/robot_drivers_to_persist_sim.launch.py, src/hangar_sim/params/nav2_params.yaml, src/hangar_sim/config/control/picknik_ur.ros2_control.yaml, src/hangar_sim/config/fuse/fuse.yaml, src/hangar_sim/description/picknik_ur_mujoco_ros2_control.xacro, src/hangar_sim/objectives/*.xml
Builds and installs the new nodes, enables fuse by default, selects conditional TF publishers, retunes localization and controller rates, and removes navigation-objective initial-pose actions.

Possibly related PRs

Suggested reviewers: griswaldbrooks


Caution

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

  • Ignore (reviewers only)

❌ Failed checks (1 error)

Check name Status Explanation Resolution
Human Review Check ❌ Error The PR adds about 2,056 lines across launch orchestration, odometry, AMCL localization, TF, Nav2/configuration, build metadata, objectives, and tests, so it is a large cross-cutting change. This PR requires review by a requested human reviewer. After review, a non-author requested reviewer should override this pre-merge check.
✅ Passed checks (3 passed)
Check name Status Explanation
Description check ✅ Passed The description directly explains the fuse-based odometry, AMCL tuning, localization gating, testing, and simulation changes in the pull request.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Comment @coderabbitai help to get the list of available commands.

@bkanator
bkanator force-pushed the feat/19667-fuse-odometry branch 3 times, most recently from ecb0e20 to 98ef35f Compare July 19, 2026 15:34
@bkanator
bkanator marked this pull request as ready for review July 21, 2026 11:47

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/hangar_sim/launch/sim/robot_drivers_to_persist_sim.launch.py`:
- Around line 319-335: Update the odom_world_drift and slip_aware_odom Node
definitions to pass the use_sim_time LaunchConfiguration as their ROS parameter,
ensuring both nodes use simulation time consistently when stamping outputs.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 6834a0a6-300e-4af5-bd44-d25a11fda468

📥 Commits

Reviewing files that changed from the base of the PR and between 6cfc8c0 and 98ef35f.

📒 Files selected for processing (10)
  • src/hangar_sim/CMakeLists.txt
  • src/hangar_sim/config/control/picknik_ur.ros2_control.yaml
  • src/hangar_sim/config/fuse/fuse.yaml
  • src/hangar_sim/description/picknik_ur_mujoco_ros2_control.xacro
  • src/hangar_sim/description/ur5e_ridgeback.xml
  • src/hangar_sim/launch/sim/robot_drivers_to_persist_sim.launch.py
  • src/hangar_sim/package.xml
  • src/hangar_sim/params/nav2_params.yaml
  • src/hangar_sim/script/odom_world_drift.py
  • src/hangar_sim/src/slip_aware_odom.cpp

Comment thread src/hangar_sim/launch/sim/robot_drivers_to_persist_sim.launch.py Outdated
@bkanator
bkanator force-pushed the feat/19667-fuse-odometry branch from 98ef35f to b3952a6 Compare July 21, 2026 12:59

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/hangar_sim/launch/sim/robot_drivers_to_persist_sim.launch.py`:
- Around line 190-193: Update the state estimator launch configuration
associated with the use_fuse argument to pass the use_sim_time launch value as
the node parameter {"use_sim_time": use_sim_time} alongside the existing
fuse.yaml configuration, ensuring Fuse uses the simulation clock.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 137c1df9-a38e-4fc2-bd73-ece87806e72f

📥 Commits

Reviewing files that changed from the base of the PR and between 153262c and b3952a6.

📒 Files selected for processing (10)
  • src/hangar_sim/CMakeLists.txt
  • src/hangar_sim/config/control/picknik_ur.ros2_control.yaml
  • src/hangar_sim/config/fuse/fuse.yaml
  • src/hangar_sim/description/picknik_ur_mujoco_ros2_control.xacro
  • src/hangar_sim/description/ur5e_ridgeback.xml
  • src/hangar_sim/launch/sim/robot_drivers_to_persist_sim.launch.py
  • src/hangar_sim/package.xml
  • src/hangar_sim/params/nav2_params.yaml
  • src/hangar_sim/script/odom_world_drift.py
  • src/hangar_sim/src/slip_aware_odom.cpp

Comment thread src/hangar_sim/launch/sim/robot_drivers_to_persist_sim.launch.py
@bkanator
bkanator force-pushed the feat/19667-fuse-odometry branch from 995a373 to e9fa7da Compare July 23, 2026 23:20

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/hangar_sim/launch/sim/robot_drivers_to_persist_sim.launch.py (1)

401-411: 🗄️ Data Integrity & Integration | 🟠 Major

fuse_state_estimator still doesn't pass use_sim_time — this was already flagged in a prior review round and appears unaddressed here.

fuse_state_estimator only loads fuse.yaml; with use_fuse now defaulting to true, Fuse stays on the wall clock while the rest of the sim stack (including the new odom_world_drift, slip_aware_odom, and amcl_odom_gate nodes just above, which now all correctly receive {"use_sim_time": use_sim_time}) runs on sim time. This is the same gap previously raised on state_estimator.

Suggested fix
     fuse_state_estimator = Node(
         package="fuse_optimizers",
         executable="fixed_lag_smoother_node",
         name="state_estimator",
         parameters=[
-            PathJoinSubstitution([hangar_sim_pkg, "config", "fuse", "fuse.yaml"])
+            PathJoinSubstitution([hangar_sim_pkg, "config", "fuse", "fuse.yaml"]),
+            {"use_sim_time": use_sim_time},
         ],
         output="screen",
         condition=IfCondition(LaunchConfiguration("use_fuse")),
     )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hangar_sim/launch/sim/robot_drivers_to_persist_sim.launch.py` around
lines 401 - 411, Update the fuse_state_estimator Node parameters to include the
launch use_sim_time value alongside fuse.yaml, matching the {"use_sim_time":
use_sim_time} configuration used by the surrounding simulation nodes. Preserve
the existing package, executable, condition, and output settings.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/hangar_sim/launch/sim/robot_drivers_to_persist_sim.launch.py`:
- Around line 340-349: The launch configuration must provide a map->odom
publisher when localization is enabled and use_fuse is false. Update the
amcl_odom_gate/AMCL launch conditions or add a fallback publisher so the
use_fuse=false localization path receives map->odom, while preserving the
existing static_tf_map_to_odom behavior for localization=false.

In `@src/hangar_sim/package.xml`:
- Around line 44-48: Add the missing test dependency declaration for
ament_cmake_gmock in src/hangar_sim/package.xml, alongside the existing
dependency entries, so the test_amcl_odom_gate.cpp gmock target configured by
ament_add_gmock has its required package dependency.

---

Outside diff comments:
In `@src/hangar_sim/launch/sim/robot_drivers_to_persist_sim.launch.py`:
- Around line 401-411: Update the fuse_state_estimator Node parameters to
include the launch use_sim_time value alongside fuse.yaml, matching the
{"use_sim_time": use_sim_time} configuration used by the surrounding simulation
nodes. Preserve the existing package, executable, condition, and output
settings.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b7ab5b22-211b-48ce-90f4-5c1623ea3a3c

📥 Commits

Reviewing files that changed from the base of the PR and between f0634e4 and 995a373.

📒 Files selected for processing (11)
  • src/hangar_sim/CMakeLists.txt
  • src/hangar_sim/launch/sim/robot_drivers_to_persist_sim.launch.py
  • src/hangar_sim/objectives/navigate_to_clicked_point.xml
  • src/hangar_sim/objectives/navigate_to_clicked_point_with_replanning.xml
  • src/hangar_sim/package.xml
  • src/hangar_sim/params/nav2_params.yaml
  • src/hangar_sim/script/odom_world_drift.py
  • src/hangar_sim/src/amcl_odom_gate.cpp
  • src/hangar_sim/src/amcl_odom_gate_logic.hpp
  • src/hangar_sim/src/slip_aware_odom.cpp
  • src/hangar_sim/test/test_amcl_odom_gate.cpp
💤 Files with no reviewable changes (2)
  • src/hangar_sim/objectives/navigate_to_clicked_point.xml
  • src/hangar_sim/objectives/navigate_to_clicked_point_with_replanning.xml
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/hangar_sim/params/nav2_params.yaml
  • src/hangar_sim/script/odom_world_drift.py
  • src/hangar_sim/src/slip_aware_odom.cpp

Comment thread src/hangar_sim/launch/sim/robot_drivers_to_persist_sim.launch.py
Comment thread src/hangar_sim/package.xml Outdated
@bkanator
bkanator force-pushed the feat/19667-fuse-odometry branch 5 times, most recently from c57b768 to 72b4afe Compare July 28, 2026 23:18
@bkanator

Copy link
Copy Markdown
Author

[written by AI]

Addressed the CodeRabbit review comments (force-pushed; the branch was also squashed to a single commit and rebased onto latest main):

  • use_sim_time on state_estimator — added {"use_sim_time": use_sim_time} to the fuse_state_estimator node so Fuse stays on the sim clock. (Real gap — fixed.)
  • use_sim_time on odom_world_drift / slip_aware_odom — both already pass {"use_sim_time": use_sim_time} in the current code; no change needed.
  • use_fuse:=false still needs a map->odom sourceamcl.tf_broadcast is now rewritten via param_substitutions to true when use_fuse:=false (no amcl_odom_gate) and false when use_fuse:=true (gate is sole publisher), so a use_fuse:=false + localization:=true run has AMCL broadcast map->odom itself instead of nothing. (Real gap — fixed.)
  • Missing ament_cmake_gmock test_depend — added to package.xml. (Fixed.)

@coderabbitai full review

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (3)
src/hangar_sim/src/amcl_odom_gate.cpp (1)

115-115: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Wall timer paired with sim-time stamps.

create_wall_timer is unaffected by /clock, but publish() stamps with now() (ROS time under use_sim_time). If the sim runs slower than realtime, consecutive broadcasts can carry identical or barely-advancing stamps; faster-than-realtime under-publishes. rclcpp::create_timer(this, get_clock(), rclcpp::Duration::from_seconds(kPubPeriod), cb) ties the period to the same clock the stamps come from.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hangar_sim/src/amcl_odom_gate.cpp` at line 115, Replace the wall-clock
timer assigned to timer_ in the constructor with an rclcpp timer created using
get_clock() and an rclcpp::Duration derived from kPubPeriod, while preserving
the existing publish() callback.
src/hangar_sim/CMakeLists.txt (1)

11-12: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Set cxx_std_17 on slip_aware_odom too.

src/slip_aware_odom.cpp uses std::clamp (C++17), but unlike amcl_odom_gate this target doesn't declare the standard and relies on whatever the toolchain/dependency defaults provide.

♻️ Proposed change
 add_executable(slip_aware_odom src/slip_aware_odom.cpp)
+target_compile_features(slip_aware_odom PRIVATE cxx_std_17)
 ament_target_dependencies(slip_aware_odom rclcpp nav_msgs)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hangar_sim/CMakeLists.txt` around lines 11 - 12, Update the
slip_aware_odom target declaration to require C++17, matching the existing
amcl_odom_gate target configuration, so src/slip_aware_odom.cpp can use
std::clamp consistently across toolchains.
src/hangar_sim/src/slip_aware_odom.cpp (1)

66-69: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider exposing kHoldGap/kHoldPeriod as node parameters.

The hold threshold (15 ms) is sized against the controller's measured ~500 Hz rate while its config declares publish_rate: 50.0. If the fork is ever fixed to honour that param, every inter-message interval (20 ms) exceeds kHoldGap and the node starts injecting fabricated zero-velocity samples into fuse during normal driving. Making these declarable parameters (or deriving the gap from the observed inter-message interval) removes the hidden coupling.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hangar_sim/src/slip_aware_odom.cpp` around lines 66 - 69, Expose kHoldGap
and kHoldPeriod as configurable node parameters, and use the resolved values in
the silent-source hold logic instead of fixed constants. Preserve the current
defaults (0.015 and 0.02 seconds) so existing behavior remains unchanged unless
parameters are explicitly overridden.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/hangar_sim/launch/sim/robot_drivers_to_persist_sim.launch.py`:
- Around line 94-105: Update the localization include in the launch flow, rather
than only the nav2_container parameters, so localization_launch.py receives
params with the tf_broadcast rewrite from param_substitutions. Ensure
use_fuse:=false produces AMCL tf_broadcast=true and use_fuse:=true preserves
false, either by passing the rewritten YAML or applying the equivalent
RewrittenYaml transformation inside localization_launch.py.

In `@src/hangar_sim/script/odom_world_drift.py`:
- Around line 97-110: Update _joints so the incoming name list is cached even
when resolving RAIL_JOINTS fails, and guard the position lookup against self.idx
being None. Preserve the early return for messages missing rail joints while
ensuring repeated messages with the same names cannot iterate over a cleared
index cache.

In `@src/hangar_sim/test/test_amcl_odom_gate.cpp`:
- Around line 165-176: Update InterpolateOdomDuplicateStampsNoNan so it no
longer claims to exercise the unreachable zero-span interpolation guard: either
revise the test comment and assertions to verify the target-at-back clamp
behavior, or remove the test while retaining the span guard as defensive code.

---

Nitpick comments:
In `@src/hangar_sim/CMakeLists.txt`:
- Around line 11-12: Update the slip_aware_odom target declaration to require
C++17, matching the existing amcl_odom_gate target configuration, so
src/slip_aware_odom.cpp can use std::clamp consistently across toolchains.

In `@src/hangar_sim/src/amcl_odom_gate.cpp`:
- Line 115: Replace the wall-clock timer assigned to timer_ in the constructor
with an rclcpp timer created using get_clock() and an rclcpp::Duration derived
from kPubPeriod, while preserving the existing publish() callback.

In `@src/hangar_sim/src/slip_aware_odom.cpp`:
- Around line 66-69: Expose kHoldGap and kHoldPeriod as configurable node
parameters, and use the resolved values in the silent-source hold logic instead
of fixed constants. Preserve the current defaults (0.015 and 0.02 seconds) so
existing behavior remains unchanged unless parameters are explicitly overridden.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d46e613a-c8d7-412f-b899-1395492dfccf

📥 Commits

Reviewing files that changed from the base of the PR and between 14ef4a1 and 72b4afe.

📒 Files selected for processing (16)
  • src/hangar_sim/CMakeLists.txt
  • src/hangar_sim/config/control/picknik_ur.ros2_control.yaml
  • src/hangar_sim/config/fuse/fuse.yaml
  • src/hangar_sim/description/picknik_ur_mujoco_ros2_control.xacro
  • src/hangar_sim/description/ur5e_ridgeback.xml
  • src/hangar_sim/include/hangar_sim/amcl_odom_gate_logic.hpp
  • src/hangar_sim/launch/sim/robot_drivers_to_persist_sim.launch.py
  • src/hangar_sim/objectives/navigate_to_clicked_point.xml
  • src/hangar_sim/objectives/navigate_to_clicked_point_with_replanning.xml
  • src/hangar_sim/package.xml
  • src/hangar_sim/params/nav2_params.yaml
  • src/hangar_sim/script/odom_world_drift.py
  • src/hangar_sim/src/amcl_odom_gate.cpp
  • src/hangar_sim/src/amcl_odom_gate_logic.cpp
  • src/hangar_sim/src/slip_aware_odom.cpp
  • src/hangar_sim/test/test_amcl_odom_gate.cpp
💤 Files with no reviewable changes (2)
  • src/hangar_sim/objectives/navigate_to_clicked_point_with_replanning.xml
  • src/hangar_sim/objectives/navigate_to_clicked_point.xml

Comment thread src/hangar_sim/launch/sim/robot_drivers_to_persist_sim.launch.py Outdated
Comment thread src/hangar_sim/script/odom_world_drift.py Outdated
Comment thread src/hangar_sim/test/test_amcl_odom_gate.cpp Outdated
@bkanator
bkanator force-pushed the feat/19667-fuse-odometry branch 2 times, most recently from 3dd5f34 to 6a0255e Compare July 29, 2026 14:34

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/hangar_sim/description/ur5e_ridgeback.xml`:
- Around line 218-221: Update the base_gt site’s rgba value to use a visible
alpha while preserving its red color and existing position, size, and TF
behavior.

In `@src/hangar_sim/params/nav2_params.yaml`:
- Around line 56-58: Update the AMCL likelihood-field weights in the parameter
block so the active values z_hit and z_rand sum to 1.0, increasing one by 0.05;
keep z_short and z_max unchanged because they are inactive for this laser model.

In `@src/hangar_sim/src/slip_aware_odom.cpp`:
- Around line 91-108: Replace the wall-clock timer creation in SlipAwareOdom’s
constructor with a node-clock/ROS-time timer so the hold-gap logic follows
simulated time. Apply the same timer change in amcl_odom_gate.cpp for its 30 Hz
publishing and stale-check timers; update both affected sites, preserving their
existing periods and callbacks.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c9118a06-f1cf-43b6-ad24-2a778c5bae8e

📥 Commits

Reviewing files that changed from the base of the PR and between 2e74b69 and 6a0255e.

📒 Files selected for processing (16)
  • src/hangar_sim/CMakeLists.txt
  • src/hangar_sim/config/control/picknik_ur.ros2_control.yaml
  • src/hangar_sim/config/fuse/fuse.yaml
  • src/hangar_sim/description/picknik_ur_mujoco_ros2_control.xacro
  • src/hangar_sim/description/ur5e_ridgeback.xml
  • src/hangar_sim/include/hangar_sim/amcl_odom_gate_logic.hpp
  • src/hangar_sim/launch/sim/robot_drivers_to_persist_sim.launch.py
  • src/hangar_sim/objectives/navigate_to_clicked_point.xml
  • src/hangar_sim/objectives/navigate_to_clicked_point_with_replanning.xml
  • src/hangar_sim/package.xml
  • src/hangar_sim/params/nav2_params.yaml
  • src/hangar_sim/script/odom_world_drift.py
  • src/hangar_sim/src/amcl_odom_gate.cpp
  • src/hangar_sim/src/amcl_odom_gate_logic.cpp
  • src/hangar_sim/src/slip_aware_odom.cpp
  • src/hangar_sim/test/test_amcl_odom_gate.cpp
💤 Files with no reviewable changes (2)
  • src/hangar_sim/objectives/navigate_to_clicked_point.xml
  • src/hangar_sim/objectives/navigate_to_clicked_point_with_replanning.xml

Comment thread src/hangar_sim/description/ur5e_ridgeback.xml Outdated
Comment thread src/hangar_sim/params/nav2_params.yaml
Comment thread src/hangar_sim/src/slip_aware_odom.cpp
@bkanator
bkanator force-pushed the feat/19667-fuse-odometry branch 4 times, most recently from c055aa7 to 1ea32dc Compare July 31, 2026 15:56
@PickNikRobotics PickNikRobotics deleted a comment from github-actions Bot Jul 31, 2026
@PickNikRobotics PickNikRobotics deleted a comment from github-actions Bot Jul 31, 2026
@PickNikRobotics PickNikRobotics deleted a comment from github-actions Bot Jul 31, 2026
@PickNikRobotics PickNikRobotics deleted a comment from github-actions Bot Jul 31, 2026
@PickNikRobotics PickNikRobotics deleted a comment from github-actions Bot Jul 31, 2026
@PickNikRobotics PickNikRobotics deleted a comment from github-actions Bot Jul 31, 2026
@PickNikRobotics PickNikRobotics deleted a comment from github-actions Bot Jul 31, 2026
@PickNikRobotics PickNikRobotics deleted a comment from github-actions Bot Jul 31, 2026
@PickNikRobotics PickNikRobotics deleted a comment from github-actions Bot Jul 31, 2026
@PickNikRobotics PickNikRobotics deleted a comment from github-actions Bot Jul 31, 2026
@PickNikRobotics PickNikRobotics deleted a comment from coderabbitai Bot Jul 31, 2026
@PickNikRobotics PickNikRobotics deleted a comment from coderabbitai Bot Jul 31, 2026
@bkanator

Copy link
Copy Markdown
Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/hangar_sim/launch/sim/robot_drivers_to_persist_sim.launch.py`:
- Around line 375-401: Update the condition in amcl_odom_gate to require slam to
be false in addition to use_fuse and localization being true. Match the existing
static_tf_map_to_odom condition’s slam check so amcl_odom_gate is not launched
during SLAM mode.

In `@src/hangar_sim/src/slip_aware_odom.cpp`:
- Around line 131-135: Update the timing logic in the slip update block so decay
uses the full nonnegative elapsed time since last_t_, while the
|m.twist.twist.angular.z| growth term uses the existing kMaxDt-clamped duration.
Preserve the initial-timestamp behavior and continue updating last_t_ with the
current timestamp.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ae0e174d-9450-42aa-af7e-992aa274f77d

📥 Commits

Reviewing files that changed from the base of the PR and between c2c4502 and 1ea32dc.

📒 Files selected for processing (19)
  • src/hangar_sim/CMakeLists.txt
  • src/hangar_sim/config/control/picknik_ur.ros2_control.yaml
  • src/hangar_sim/config/fuse/fuse.yaml
  • src/hangar_sim/description/picknik_ur_mujoco_ros2_control.xacro
  • src/hangar_sim/description/ur5e_ridgeback.xml
  • src/hangar_sim/include/hangar_sim/amcl_odom_gate_logic.hpp
  • src/hangar_sim/include/hangar_sim/odom_world_drift_logic.hpp
  • src/hangar_sim/include/hangar_sim/se2.hpp
  • src/hangar_sim/launch/sim/robot_drivers_to_persist_sim.launch.py
  • src/hangar_sim/objectives/navigate_to_clicked_point.xml
  • src/hangar_sim/objectives/navigate_to_clicked_point_with_replanning.xml
  • src/hangar_sim/package.xml
  • src/hangar_sim/params/nav2_params.yaml
  • src/hangar_sim/src/amcl_odom_gate.cpp
  • src/hangar_sim/src/amcl_odom_gate_logic.cpp
  • src/hangar_sim/src/odom_world_drift.cpp
  • src/hangar_sim/src/slip_aware_odom.cpp
  • src/hangar_sim/test/test_amcl_odom_gate.cpp
  • src/hangar_sim/test/test_odom_world_drift.cpp
💤 Files with no reviewable changes (2)
  • src/hangar_sim/objectives/navigate_to_clicked_point_with_replanning.xml
  • src/hangar_sim/objectives/navigate_to_clicked_point.xml

Comment thread src/hangar_sim/launch/sim/robot_drivers_to_persist_sim.launch.py
Comment thread src/hangar_sim/src/slip_aware_odom.cpp Outdated
@bkanator
bkanator force-pushed the feat/19667-fuse-odometry branch from 1ea32dc to 7f95724 Compare July 31, 2026 16:37
@bkanator

Copy link
Copy Markdown
Author

[written by AI]

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

@bkanator I will perform a new full review of pull request #790.

✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (4)
src/hangar_sim/include/hangar_sim/amcl_odom_gate_logic.hpp (1)

101-104: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Trim the buffer with a single range erase.

The loop erases one element at a time from the front. Each erase shifts the remaining elements. The buffer holds about 30 samples, so the cost is small, but a single range erase states the intent more clearly.

♻️ Proposed refactor
-  const double cutoff = s.t - keep_window_sec;
-  while (buf.size() > 1 && buf.front().t < cutoff)
-  {
-    buf.erase(buf.begin());
-  }
+  const double cutoff = s.t - keep_window_sec;
+  // Keep at least the newest sample so interpolateOdom always has an anchor.
+  const auto last_keep = buf.end() - 1;
+  const auto first_keep = std::find_if(buf.begin(), last_keep, [cutoff](const OdomSample& o) { return o.t >= cutoff; });
+  buf.erase(buf.begin(), first_keep);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hangar_sim/include/hangar_sim/amcl_odom_gate_logic.hpp` around lines 101
- 104, Update the buffer-trimming logic in the surrounding AMCL odometry gate
code to identify the first sample at or after cutoff and remove all older
samples with a single range erase. Preserve the existing buf.size() > 1 guard
and cutoff boundary behavior.
src/hangar_sim/src/amcl_odom_gate.cpp (1)

257-260: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Warn when have_map_odom_ stays false.

This node is the sole map->odom publisher when fuse is enabled. If /particle_cloud never arrives, or the odom->ridgeback_base_link lookup keeps failing, publish() returns here on every tick and the node emits nothing. The kStaleInputSeconds watchdog below cannot report that case, because it sits after this guard. Add a throttled warning before the return so a startup failure is visible.

🔍 Proposed diagnostic
     if (!have_map_odom_)
     {
+      RCLCPP_WARN_THROTTLE(get_logger(), *get_clock(), kStaleWarnThrottleMs,
+                           "amcl_odom_gate: no map->odom yet -- waiting for /particle_cloud and the "
+                           "odom->ridgeback_base_link TF. No map->odom is being broadcast.");
       return;
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hangar_sim/src/amcl_odom_gate.cpp` around lines 257 - 260, Add a
throttled warning immediately before the early return guarded by have_map_odom_
in publish(), so repeated missing map-to-odom state is visible during startup or
lookup failures. Preserve the existing return behavior and use the node’s
established logging/throttling mechanism.
src/hangar_sim/test/test_amcl_odom_gate.cpp (1)

427-441: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

The "just below the cap" case passes with a thin margin.

p.spread_accept_max - kEps is 2.9, which also exceeds spread_hold (1.5) and never falls below spread_resume (0.6). So the gate accepts the correction through persistence, ramps alpha, and then latches spread_holding the moment the innovation drops under jump_hold. alpha snaps to 0 and freezes held part-way.

Tracing the default alpha_slew of 0.05: the residual distance reaches about 0.52 m, then about 0.26 m on the next step, where the gate freezes. The assertion threshold is 0.3, so the margin is about 0.04 m. A small change to alpha_slew or jump_hold breaks this test for a reason unrelated to spread_accept_max.

Isolate the boundary under test. Lower spread_hold interference by raising spread_hold/spread_resume above the probed spread, or assert on s.alpha reaching 1.0 instead of on the converged distance.

💚 Proposed adjustment
   // GIVEN spread just UNDER spread_accept_max: a persistent correction is accepted.
   {
     GateParams p;  // spread_accept_max = 3.0
+    // Keep the spread-hysteresis trigger out of the way so this test probes spread_accept_max alone.
+    p.spread_hold = p.spread_accept_max + 1.0;
+    p.spread_resume = p.spread_accept_max;
     GateState s;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hangar_sim/test/test_amcl_odom_gate.cpp` around lines 427 - 441, Update
the “spread just UNDER spread_accept_max” test to isolate the acceptance
boundary by configuring GateParams spread_hold and spread_resume above
p.spread_accept_max, preventing jump-hold latching and alpha freezing from
affecting the result. Keep the existing spread_accept_max probe and acceptance
assertion focused on boundary behavior.
src/hangar_sim/launch/sim/robot_drivers_to_persist_sim.launch.py (1)

335-335: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract use_fuse into a named LaunchConfiguration variable.

LaunchConfiguration("use_fuse") is constructed inline at each use site (Lines 335, 344, 358, 386, 481) instead of once, unlike slam, localization, and use_sim_time, which are assigned to local variables near the top of generate_launch_description. This file's PythonExpression condition strings have needed several fixes in past reviews. Declaring use_fuse once reduces the risk of a future typo breaking one of these conditions silently.

♻️ Proposed refactor
     use_sim_time = LaunchConfiguration("use_sim_time")
     params_file = LaunchConfiguration("params_file")
     autostart = LaunchConfiguration("autostart")
     use_composition = LaunchConfiguration("use_composition")
     use_respawn = LaunchConfiguration("use_respawn")
     log_level = LaunchConfiguration("log_level")
+    use_fuse = LaunchConfiguration("use_fuse")

Then replace each inline LaunchConfiguration("use_fuse") occurrence with use_fuse.

Also applies to: 344-344, 358-358, 380-392, 481-481

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hangar_sim/launch/sim/robot_drivers_to_persist_sim.launch.py` at line
335, In generate_launch_description, assign LaunchConfiguration("use_fuse") to a
named local variable alongside slam, localization, and use_sim_time, then
replace every inline use_fuse LaunchConfiguration construction in the listed
condition sites with that variable.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/hangar_sim/src/amcl_odom_gate_logic.cpp`:
- Around line 85-97: Update the persistence-timer logic in updateGate so a now
value earlier than s.provisional_since re-anchors the timer by setting
s.provisional_since to now before evaluating persist_time. Preserve the existing
follow behavior for non-backward timestamps and keep the candidate persistence
state intact.

In `@src/hangar_sim/src/amcl_odom_gate.cpp`:
- Around line 89-98: Validate the declared gate parameters after the declaration
block in the node initialization flow, including requiring alpha_slew to be
positive and spread_resume to be less than spread_hold. Follow the existing
warning treatment used for latency_compensation_sec >= kOdomBufferSec, and
ensure invalid configurations are reported without silently allowing a frozen
alpha or ineffective hysteresis.

---

Nitpick comments:
In `@src/hangar_sim/include/hangar_sim/amcl_odom_gate_logic.hpp`:
- Around line 101-104: Update the buffer-trimming logic in the surrounding AMCL
odometry gate code to identify the first sample at or after cutoff and remove
all older samples with a single range erase. Preserve the existing buf.size() >
1 guard and cutoff boundary behavior.

In `@src/hangar_sim/launch/sim/robot_drivers_to_persist_sim.launch.py`:
- Line 335: In generate_launch_description, assign
LaunchConfiguration("use_fuse") to a named local variable alongside slam,
localization, and use_sim_time, then replace every inline use_fuse
LaunchConfiguration construction in the listed condition sites with that
variable.

In `@src/hangar_sim/src/amcl_odom_gate.cpp`:
- Around line 257-260: Add a throttled warning immediately before the early
return guarded by have_map_odom_ in publish(), so repeated missing map-to-odom
state is visible during startup or lookup failures. Preserve the existing return
behavior and use the node’s established logging/throttling mechanism.

In `@src/hangar_sim/test/test_amcl_odom_gate.cpp`:
- Around line 427-441: Update the “spread just UNDER spread_accept_max” test to
isolate the acceptance boundary by configuring GateParams spread_hold and
spread_resume above p.spread_accept_max, preventing jump-hold latching and alpha
freezing from affecting the result. Keep the existing spread_accept_max probe
and acceptance assertion focused on boundary behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 4799d7cb-5e12-4484-acbf-10cbd29fdf69

📥 Commits

Reviewing files that changed from the base of the PR and between c2c4502 and 7f95724.

📒 Files selected for processing (19)
  • src/hangar_sim/CMakeLists.txt
  • src/hangar_sim/config/control/picknik_ur.ros2_control.yaml
  • src/hangar_sim/config/fuse/fuse.yaml
  • src/hangar_sim/description/picknik_ur_mujoco_ros2_control.xacro
  • src/hangar_sim/description/ur5e_ridgeback.xml
  • src/hangar_sim/include/hangar_sim/amcl_odom_gate_logic.hpp
  • src/hangar_sim/include/hangar_sim/odom_world_drift_logic.hpp
  • src/hangar_sim/include/hangar_sim/se2.hpp
  • src/hangar_sim/launch/sim/robot_drivers_to_persist_sim.launch.py
  • src/hangar_sim/objectives/navigate_to_clicked_point.xml
  • src/hangar_sim/objectives/navigate_to_clicked_point_with_replanning.xml
  • src/hangar_sim/package.xml
  • src/hangar_sim/params/nav2_params.yaml
  • src/hangar_sim/src/amcl_odom_gate.cpp
  • src/hangar_sim/src/amcl_odom_gate_logic.cpp
  • src/hangar_sim/src/odom_world_drift.cpp
  • src/hangar_sim/src/slip_aware_odom.cpp
  • src/hangar_sim/test/test_amcl_odom_gate.cpp
  • src/hangar_sim/test/test_odom_world_drift.cpp
💤 Files with no reviewable changes (2)
  • src/hangar_sim/objectives/navigate_to_clicked_point.xml
  • src/hangar_sim/objectives/navigate_to_clicked_point_with_replanning.xml

Comment thread src/hangar_sim/src/amcl_odom_gate_logic.cpp Outdated
Comment thread src/hangar_sim/src/amcl_odom_gate.cpp
@PickNikRobotics PickNikRobotics deleted a comment from github-actions Bot Jul 31, 2026
@bkanator
bkanator force-pushed the feat/19667-fuse-odometry branch from 7f95724 to e1f65a4 Compare July 31, 2026 17:41
@bkanator

Copy link
Copy Markdown
Author

[written by AI]

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

@bkanator I will perform a new full review of pull request #790.

✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/hangar_sim/launch/sim/robot_drivers_to_persist_sim.launch.py (1)

476-494: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider emitting this guard at warning severity.

The message text says UNSUPPORTED, and the comment says "Fail loudly". LogInfo prints at INFO level, so the line blends into normal launch output. Launch has no LogWarn action, so use an OpaqueFunction (or LogInfo plus a launch.logging warning call) if you want the higher severity.

♻️ Example: log at warning level
def _warn_unsupported(context):
    launch.logging.get_logger("hangar_sim").warning(
        "UNSUPPORTED: use_fuse:=false with localization:=true -- ..."
    )
    return []

warn_unsupported_localization = OpaqueFunction(
    function=_warn_unsupported,
    condition=IfCondition(
        PythonExpression(
            [
                "'",
                LaunchConfiguration("use_fuse"),
                "'.lower() == 'false' and '",
                localization,
                "'.lower() == 'true'",
            ]
        )
    ),
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hangar_sim/launch/sim/robot_drivers_to_persist_sim.launch.py` around
lines 476 - 494, Update warn_unsupported_localization to emit the existing
UNSUPPORTED message at warning severity instead of using LogInfo. Define an
OpaqueFunction callback that logs through
launch.logging.get_logger("hangar_sim").warning and returns no actions, while
preserving the current IfCondition and message text.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/hangar_sim/launch/sim/robot_drivers_to_persist_sim.launch.py`:
- Around line 476-494: Update warn_unsupported_localization to emit the existing
UNSUPPORTED message at warning severity instead of using LogInfo. Define an
OpaqueFunction callback that logs through
launch.logging.get_logger("hangar_sim").warning and returns no actions, while
preserving the current IfCondition and message text.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 470c6147-a102-41c3-810c-dfc613dd6ffa

📥 Commits

Reviewing files that changed from the base of the PR and between c2c4502 and e1f65a4.

📒 Files selected for processing (19)
  • src/hangar_sim/CMakeLists.txt
  • src/hangar_sim/config/control/picknik_ur.ros2_control.yaml
  • src/hangar_sim/config/fuse/fuse.yaml
  • src/hangar_sim/description/picknik_ur_mujoco_ros2_control.xacro
  • src/hangar_sim/description/ur5e_ridgeback.xml
  • src/hangar_sim/include/hangar_sim/amcl_odom_gate_logic.hpp
  • src/hangar_sim/include/hangar_sim/odom_world_drift_logic.hpp
  • src/hangar_sim/include/hangar_sim/se2.hpp
  • src/hangar_sim/launch/sim/robot_drivers_to_persist_sim.launch.py
  • src/hangar_sim/objectives/navigate_to_clicked_point.xml
  • src/hangar_sim/objectives/navigate_to_clicked_point_with_replanning.xml
  • src/hangar_sim/package.xml
  • src/hangar_sim/params/nav2_params.yaml
  • src/hangar_sim/src/amcl_odom_gate.cpp
  • src/hangar_sim/src/amcl_odom_gate_logic.cpp
  • src/hangar_sim/src/odom_world_drift.cpp
  • src/hangar_sim/src/slip_aware_odom.cpp
  • src/hangar_sim/test/test_amcl_odom_gate.cpp
  • src/hangar_sim/test/test_odom_world_drift.cpp
💤 Files with no reviewable changes (2)
  • src/hangar_sim/objectives/navigate_to_clicked_point_with_replanning.xml
  • src/hangar_sim/objectives/navigate_to_clicked_point.xml

@PickNikRobotics PickNikRobotics deleted a comment from github-actions Bot Jul 31, 2026
@bkanator
bkanator force-pushed the feat/19667-fuse-odometry branch from e1f65a4 to c8c2181 Compare July 31, 2026 21:16
@PickNikRobotics PickNikRobotics deleted a comment from github-actions Bot Jul 31, 2026
Give hangar_sim's mobile base a realistic localization stack so beluga_amcl is
exercised against fuse-fused odometry (wheel + IMU) with real drift, including
where the scene is degenerate for scan matching (the smooth fuselage, the
unmapped picking boxes).

- Fuse on by default (use_fuse=true) -> /odom_filtered; odom_world_drift injects
  a live odom->world from the fuse estimate so AMCL sees drift to correct while
  world->base stays ground truth for whole-body planning.
- slip_aware_odom (new C++ node): grows wheel-yaw covariance during sustained
  in-place spin (mecanum roller slip) so fuse defers to the IMU while spinning
  and trusts the wheels driving straight; also re-anchors odom across the
  whole-body<->nav controller handoff.
- AMCL tuning: OmniMotionModel; alpha1 0.1->0.4; update_min_a 0.1 with
  resample_interval 3; likelihood relaxed (sigma_hit 0.1->0.25, z_hit 0.9->0.65,
  z_rand 0.1->0.3) so unmapped boxes read as outliers instead of yanking the pose.
- fuse lag_duration 0.5->0.3: shorter smoother window cuts output latency
  (moving-yaw 0.70->0.465deg) while still smoothing transient spikes.
- amcl_odom_gate (new C++ node): sole map->odom publisher (AMCL tf_broadcast
  false). Holds the last good map->odom and coasts on fuse odom where AMCL is
  degenerate, blending back when trustworthy. A large correction is accepted
  only if it persists over a sliding window (gated on position AND yaw) with
  particle-spread hysteresis; spread_accept_max additionally rejects a
  confident-but-WRONG lock (which persists yet stays spread) so a scan-slide
  divergence is coasted through while a real recovery (which converges) is still
  adopted. latency_compensation_sec composes the correction with odom->base from
  one estimator-lag ago (referenced to the cloud stamp), forward-projecting it
  with real buffered odometry -> moving-pose error 13.8->8.2cm, moving-yaw
  1.2->0.9deg, no overshoot on turns; 0 disables, hangar_sim uses 0.30. The pure
  decision logic (detail::updateGate, interpolateOdom, appendOdomSample) has no
  ROS/TF deps and is fully unit-tested.
- Removed the per-objective SetInitialPose reseed from the clicked-point
  Objectives (superseded by slip_aware_odom re-anchoring; the unconditional
  reseed could cement a drifting estimate).
- wz_max 0.6 (below the velocity_smoother cap so it binds) keeps spins within
  AMCL's correction bandwidth; odom_rate 50Hz / tf_publish_rate 30Hz and the
  broadcaster rates decoupled from the control loop give the stack CPU headroom.

Closes #19667.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@bkanator
bkanator force-pushed the feat/19667-fuse-odometry branch from c8c2181 to 32da10b Compare August 6, 2026 16:00
@PickNikRobotics PickNikRobotics deleted a comment from github-actions Bot Aug 6, 2026
@bkanator bkanator self-assigned this Aug 6, 2026
…eness guards

Extracts slip_aware_odom's re-anchor/covariance-ramp logic into a pure,
unit-tested header (mirroring amcl_odom_gate's existing pattern), and adds
staleness guards flagged by review: odom_world_drift now withholds
odom->world when /odom_filtered goes stale instead of broadcasting a frozen
estimate with a fresh timestamp; amcl_odom_gate now treats a stale odom
buffer as unavailable (with a dedicated warning) instead of silently
clamping to an old sample; and slip_aware_odom's hold() now stops (and
warns) after a bounded gap instead of fabricating "parked" odometry forever
if a controller dies. Routes the three new nodes' logs to console
(output=both) so their watchdog warnings are actually visible, and drops
internal issue-number references from code comments per
.claude/rules/cpp-style.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@bkanator
bkanator force-pushed the feat/19667-fuse-odometry branch from c84e2d5 to 84e1708 Compare August 6, 2026 19:04
@PickNikRobotics PickNikRobotics deleted a comment from github-actions Bot Aug 6, 2026
@bkanator
bkanator requested a review from griswaldbrooks August 6, 2026 19:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant