Skip to content
alexbeh.me
Log in

Tuning log

Controller parameters

What every controller_server parameter on the AMR does, read from the source it configures, with the comments the parameter file carried beside each one.

Read from hmc_nav2_params_tuning_18Sep.yaml (the controller_server block) on 27 Sept 2026. Defaults and behaviour were read from the navigation2_common (nav2_controller, nav2_util) and hmgics_navigation2 (nav2_mppi_controller, nav2_precise_rotation_shim_controller) working trees on the same day, uncommitted changes included. A default is what the code declares when the key is absent from the file.

Parameters

231

in 25 groups

Away from default

115

of 223 with a declared default

Comment lines kept

348

on 82 parameters and 8 group headings

No effect here

33

set, but switched off or never read

Where the comments and the code disagree

Read these before deleting the comments. Each is a comment that describes a value the file no longer has, or a mechanism the code no longer uses. The comment itself is kept on the parameter below.

  1. enforce_path_inversionController server

    The FIXME above it says it "never reaches the critics". In this source it does reach the ones that matter, indirectly: the optimizer turns it into effective_goal, and TerminalGoalCritic and GoalAngleCritic score against that. What does not reach them is FollowPath.enforce_path_inversion. Each critic reads that key but only GoalCritic acts on it, and GoalCritic is disabled. The path itself is cut at the cusp by PathHandler.enforce_path_inversion, a third key.

  2. general_goal_checker.xy_goal_toleranceGoal checker

    The comment says it is "aligned with FollowPath.vertex_tolerance", but vertex_tolerance is 0.02 in this file and this is 0.05. What now holds is containment: the shim’s 2 cm terminal gate sits well inside the checker’s 5 cm.

  3. FollowPath.static_handoff_vel_thresholdRotation shim: engage and hand-over

    The source gives this base’s noise floor as about 0.035 rad/s, which is why the default is 0.05. At 0.02 a quiet base settles quickly. A noisy one waits out the 3 s timeout on every turn. Worth measuring before trusting it (the README’s measure_rotation_braking.py suggests a value).

  4. FollowPath.GoalAngleCritic.threshold_to_considerGoalAngleCritic

    The opening comment says this critic "is active in the last 0.5 m". That was the default. At 1.5 it is active over the last 1.5 m, three times the stretch the argument was made about.

  5. FollowPath.TerminalGoalCritic.terminal_window_timeTerminalGoalCritic

    The opening comment says the critic "only scores the last 0.25 s of each rollout". Two things changed: the window is 0.50 s, and it is centred on each rollout’s closest approach to the goal, not on the end of the horizon. The header explains why scoring the end was dropped.

  6. FollowPath.TerminalGoalCritic.stop_ramp_distanceTerminalGoalCritic

    The comment above says the stop term "fades in only over the final 0.6 m". It is 1.0 here (Kelvin’s value, per the inline note), so it fades in over the last metre.

  7. FollowPath.TerminalGoalCritic.lateral_weightTerminalGoalCritic

    The comment argues "Lateral > longitudinal" because a differential drive cannot fix lateral error close in. At 1.0 against a longitudinal 1.0 there is no longer any anisotropy, so the critic scores position isotropically.

  8. FollowPath.TerminalGoalCritic.stop_vx_weightTerminalGoalCritic

    The comment prices these weights "at the near-goal caps (vx 0.20, wz 0.25)". The near-goal ramp is inert while terminal_braking is on, and its vx cap is 0.10 in this file anyway. The speeds a rollout actually has at arrival come from the braking envelope.

  9. FollowPath.PathAlignCritic.cost_weightPathAlignCritic

    The comment’s measurements pair this weight with PathFollowCritic 3.5 at 45 (17 Sep). It was lowered to 30 on 18 Sep with PathFollow unchanged, and no comment records a measurement of the 30 / 3.5 pair now in the file.

  10. FollowPath.PathAngleCritic.offset_from_furthestPathAngleCritic

    The comment argues for pushing the target further out, "12 -> 25 indices", to quiet the relay. The value is 4, which is the stock default, and the inline note records 12 as an earlier value. The argument above it describes a setting the file no longer has.

Set, but doing nothing

Lines the file sets that change nothing in this configuration: their mechanism is switched off elsewhere, or nothing reads them. Most of them come from one switch.

KeyWhy
general_goal_checker.bypass_y_axis_max_x_errorbypass_y_axis is false.
general_goal_checker.bypass_y_axis_max_y_errorbypass_y_axis is false.
PathHandler.minimum_rotation_angleenforce_path_rotation is false, and the handler zeroes this when it is.
FollowPath.static_handoff_settle_cyclesOnly read when rotation_settle_duration is 0; it is 0.15 here.
FollowPath.rotation_breakaway_timeoutrotation_breakaway_angular_vel is 0, so breakaway is off.
FollowPath.advanced.near_goal_velocity_scalingterminal_braking is on, and the optimizer runs it INSTEAD of the near-goal ramp (it logs a warning saying so).
FollowPath.advanced.near_goal_distanceterminal_braking is on, and the optimizer runs it INSTEAD of the near-goal ramp (it logs a warning saying so).
FollowPath.advanced.near_goal_vx_maxterminal_braking is on, and the optimizer runs it INSTEAD of the near-goal ramp (it logs a warning saying so).
FollowPath.advanced.near_goal_vx_minterminal_braking is on, and the optimizer runs it INSTEAD of the near-goal ramp (it logs a warning saying so).
FollowPath.advanced.near_goal_wz_maxterminal_braking is on, and the optimizer runs it INSTEAD of the near-goal ramp (it logs a warning saying so).
FollowPath.enforce_path_inversionThe only critic that uses it, GoalCritic, is disabled.
FollowPath.transform_toleranceNo getParam for it in the controller server, the shim or MPPI.
FollowPath.AckermannConstraints.min_turning_rThe motion model is a differential drive.
FollowPath.TrajectoryVisualizer.trajectory_stepvisualize is false.
FollowPath.TrajectoryVisualizer.time_stepvisualize is false.
FollowPath.GoalCritic.cost_powerGoalCritic is switched off by its own enabled: false.
FollowPath.GoalCritic.cost_weightGoalCritic is switched off by its own enabled: false.
FollowPath.GoalCritic.threshold_to_considerGoalCritic is switched off by its own enabled: false.
FollowPath.TerminalGoalCritic.threshold_to_consideractivation_start and activation_full are both set, and they replace it.
FollowPath.PreferForwardCritic.cost_powerPreferForwardCritic is switched off by its own enabled: false.
FollowPath.PreferForwardCritic.cost_weightPreferForwardCritic is switched off by its own enabled: false.
FollowPath.PreferForwardCritic.threshold_to_considerPreferForwardCritic is switched off by its own enabled: false.
FollowPath.CurvatureOnlyPathAlignCritic.cost_powerCurvatureOnlyPathAlignCritic is switched off by its own enabled: false.
FollowPath.CurvatureOnlyPathAlignCritic.cost_weightCurvatureOnlyPathAlignCritic is switched off by its own enabled: false.
FollowPath.CurvatureOnlyPathAlignCritic.max_path_occupancy_ratioCurvatureOnlyPathAlignCritic is switched off by its own enabled: false.
FollowPath.CurvatureOnlyPathAlignCritic.trajectory_point_stepCurvatureOnlyPathAlignCritic is switched off by its own enabled: false.
FollowPath.CurvatureOnlyPathAlignCritic.threshold_to_considerCurvatureOnlyPathAlignCritic is switched off by its own enabled: false.
FollowPath.CurvatureOnlyPathAlignCritic.offset_from_furthestCurvatureOnlyPathAlignCritic is switched off by its own enabled: false.
FollowPath.CurvatureOnlyPathAlignCritic.use_path_orientationsCurvatureOnlyPathAlignCritic is switched off by its own enabled: false.
FollowPath.CurvatureOnlyPathAlignCritic.curvature_gainCurvatureOnlyPathAlignCritic is switched off by its own enabled: false.
FollowPath.CurvatureOnlyPathAlignCritic.max_curvatureCurvatureOnlyPathAlignCritic is switched off by its own enabled: false.
FollowPath.CurvatureOnlyPathAlignCritic.max_curvature_extraCurvatureOnlyPathAlignCritic is switched off by its own enabled: false.
FollowPath.CurvatureOnlyPathAlignCritic.curvature_influence_distanceCurvatureOnlyPathAlignCritic is switched off by its own enabled: false.

Contents

  1. Controller server6/13
  2. Progress checker1/4
  3. Goal checker4/10
  4. Path handler2/9
  5. Rotation shim: engage and hand-over12/14
  6. Rotation shim: rotation law3/11
  7. Rotation shim: goals, vertices and turn direction11/17
  8. MPPI: horizon and sampling4/10
  9. MPPI: velocity, acceleration and noise11/12
  10. MPPI advanced: sampling and warm start4/4
  11. MPPI advanced: straight wz hold10/14
  12. MPPI advanced: speed governors8/16
  13. MPPI: motion model, critics and namespace3/6
  14. MPPI: diagnostics2/6
  15. ConstraintCritic0/3
  16. GoalCriticoff
  17. GoalAngleCritic3/6
  18. TerminalGoalCritic9/23
  19. PreferForwardCriticoff
  20. CostCritic3/9
  21. PathAlignCritic4/8
  22. CurvatureOnlyPathAlignCriticoff
  23. PathFollowCritic2/6
  24. PathAngleCritic3/7
  25. TwirlingCritic1/3

The figure is how many of a group’s parameters are set away from the default, out of how many it has.

Controller server

The node itself: how often it runs the control loop, which plugins fill its four slots, and how it filters the odometry it hands to them. Every key at the top of ros__parameters is here, including one that the server never declares.

  • navigation2_common/nav2_controller/src/controller_server.cpp
  • navigation2_common/nav2_controller/include/nav2_controller/controller_server.hpp
  1. controller_frequency

    20.0Hz

    same as the default

    Rate of the control loop. Every plugin under FollowPath sizes its per-cycle quantities on it, and MPPI reads it at start-up to decide whether to shift its control sequence: it does when the period equals model_dt, which at 20 Hz it does.

    controller_frequency: 20.0 ###################

    See alsoFollowPath.model_dtFollowPath.vertex_stop_settle_cyclesFollowPath.static_handoff_settle_cycles

  2. enforce_path_inversion

    True

    default false

    Not a controller-server parameter at all: the server never declares it. MPPI’s optimizer reads it from the node’s ROOT namespace (getParentParam("")). When true, the terminal approach aims at the last pose of the pruned local path, which is the cusp when the path reverses, instead of the goal. That aim point is what TerminalGoalCritic, GoalAngleCritic and terminal braking use.

    ## FIXME: enforce_path_inversion never reaches the critics
    enforce_path_inversion: True

    Source check. The FIXME above it says it "never reaches the critics". In this source it does reach the ones that matter, indirectly: the optimizer turns it into effective_goal, and TerminalGoalCritic and GoalAngleCritic score against that. What does not reach them is FollowPath.enforce_path_inversion. Each critic reads that key but only GoalCritic acts on it, and GoalCritic is disabled. The path itself is cut at the cusp by PathHandler.enforce_path_inversion, a third key.

    See alsoPathHandler.enforce_path_inversionFollowPath.enforce_path_inversion

  3. costmap_update_timeout

    0.30s

    same as the default

    How long a cycle waits for the local costmap to report itself current before the goal fails with ControllerTimedOut.

  4. min_x_velocity_threshold

    0.001m/s

    default 0.0001

    Odometry vx at or below this magnitude is replaced with 0 before it reaches the controller and the goal checker. A deadband on the MEASUREMENT, not on the command.

    See alsogeneral_goal_checker.trans_stopped_velocity

  5. min_y_velocity_threshold

    0.05m/s

    default 0.0001

    The same deadband on odometry vy. On a differential drive vy should read zero anyway, so this only hides lateral slip from the goal checker’s speed test.

  6. min_theta_velocity_threshold

    0.001rad/s

    default 0.0001

    The same deadband on odometry wz.

    See alsogeneral_goal_checker.rot_stopped_velocity

  7. failure_tolerance

    0.3s

    default 0.0

    How long the controller may keep failing to find a valid command, publishing zero velocity meanwhile, before the goal aborts. 0 aborts on the first failure; -1 never aborts.

  8. progress_checker_plugins

    ["progress_checker"]

    same as the default

    Names the progress-checker instance(s). Each name is a namespace whose plugin key picks the class.

  9. goal_checker_plugins

    ["general_goal_checker"]

    default ["goal_checker"]

    Names the goal-checker instance(s). The default name goal_checker is replaced here by general_goal_checker.

  10. controller_plugins

    ["FollowPath"]

    same as the default

    Names the controller instance(s). FollowPath is also the id the behaviour tree requests.

  11. path_handler_plugins

    ["PathHandler"]

    same as the default

    Names the path-handler instance(s).

  12. use_realtime_priority

    false

    same as the default

    Runs the action server’s thread at soft real-time priority. The process needs permission to set it, or the server fails to configure.

  13. speed_limit_topic

    "speed_limit"

    same as the default

    Topic the server subscribes to for speed limits (costmap speed filter, operator). The shim passes a limit on to MPPI, and also sets MPPI’s limit itself during a vertex approach, putting the external one back afterwards.

Progress checker

nav2_controller::PoseProgressChecker

Aborts a goal when the robot has not moved far enough, in position or in heading, within a time allowance. Moving either far enough resets the clock.

Opening comment in the file

# Progress checker parameters
  • navigation2_common/nav2_controller/plugins/pose_progress_checker.cpp
  • navigation2_common/nav2_controller/plugins/simple_progress_checker.cpp
  1. progress_checker.plugin

    "nav2_controller::PoseProgressChecker"

    default "nav2_controller::SimpleProgressChecker"

    Class loaded into the slot. PoseProgressChecker adds a heading test to SimpleProgressChecker, so an in-place turn counts as progress.

  2. progress_checker.required_movement_radius

    0.5m

    same as the default

    Distance from the last baseline pose that counts as progress and restarts the clock.

  3. progress_checker.required_movement_angle

    0.5rad

    same as the default

    Heading change from the baseline that counts as progress. It is what stops a long in-place turn from being read as a stall.

  4. progress_checker.movement_time_allowance

    10.0s

    same as the default

    Time allowed without either kind of progress before the goal aborts. A shim settle, a correction pass and a vertex stop all have to fit inside it.

    See alsoFollowPath.rotation_settle_timeout

Goal checker

nav2_controller::BidirectionalGoalChecker

Decides when a goal is reached: remaining local path under a length, position inside a circle, heading inside a tolerance of EITHER the goal yaw or its opposite, and the robot stopped. Its xy and yaw tolerances are also read by the shim and by several critics, so changing one moves more than the finish line.

Opening comment in the file

# Goal checker parameters
  • navigation2_common/nav2_controller/plugins/bidirectional_goal_checker.cpp
  • navigation2_common/nav2_controller/plugins/simple_goal_checker.cpp
  1. general_goal_checker.plugin

    "nav2_controller::BidirectionalGoalChecker"

    no default: must be set

    Class loaded into the slot. BidirectionalGoalChecker accepts either heading at the goal, matching the bidirectional shim and the symmetric critics. The server only supplies a default class for the default instance name, goal_checker, so under this name the key must be set.

  2. general_goal_checker.xy_goal_tolerance

    0.05m

    default 0.25

    Radius of the position test. It is also the radius inside which the shim starts the goal rotation, and the tolerance TwirlingCritic and TerminalGoalCritic read back from the checker.

    xy_goal_tolerance: 0.05  ## aligned with FollowPath.vertex_tolerance

    Source check. The comment says it is "aligned with FollowPath.vertex_tolerance", but vertex_tolerance is 0.02 in this file and this is 0.05. What now holds is containment: the shim’s 2 cm terminal gate sits well inside the checker’s 5 cm.

    See alsoFollowPath.vertex_toleranceFollowPath.goal_rotation_hysteresis

  3. general_goal_checker.yaw_goal_tolerance

    0.0524rad

    default 0.25

    Heading tolerance, tested against the goal yaw and against its opposite, whichever is nearer. The shim finishes its goal turn at goal_yaw_acceptance_ratio of this.

    yaw_goal_tolerance: 0.0524 ## in radian, [02sep26] 0.0524 = 3 degree

    See alsoFollowPath.goal_yaw_acceptance_ratio

  4. general_goal_checker.path_length_tolerance

    1.0m

    same as the default

    The goal is not even tested while the remaining local path is longer than this.

  5. general_goal_checker.trans_stopped_velocity

    0.01m/s

    default 0.25

    Measured translational speed (after min_x_velocity_threshold) under which the robot counts as stopped. The goal is only accepted once the robot is stopped.

    # [HMGICS Alex 26Sep] 0.02 -> 0.01. MPPI creeps the last few cm into a goal, and at 0.02 that
    # creep already counted as stopped: node 2 on bag mew_test was accepted 3.4-4.3 cm short
    # (inside xy 0.05, creeping at 0.019 m/s), so the shim's 2 cm terminal gate never engaged.
    # At 0.01 the checker waits for the robot to really stop -- normally the shim's settle at
    # vertex_tolerance -- and a robot that stalls short is still accepted where it stopped.
    # CHECK on the real robot: /odom must read under 0.01 m/s at standstill, or goals never finish.
    trans_stopped_velocity: 0.01   # m/s  ## 26Sep Alex: 0.02

    See alsomin_x_velocity_thresholdFollowPath.vertex_tolerance

  6. general_goal_checker.rot_stopped_velocity

    0.02rad/s

    default 0.25

    Measured yaw rate under which the robot counts as stopped.

    See alsomin_theta_velocity_threshold

  7. general_goal_checker.stateful

    True

    same as the default

    Once the position test passes it is latched, and only the yaw and stopped tests are re-run. If the robot is found moving, the latch is dropped.

  8. general_goal_checker.bypass_y_axis

    False

    same as the default

    Accepts a goal that fails the xy test only because of a sideways offset, which a differential drive cannot drive out. Allowed when the error along the goal heading is inside bypass_y_axis_max_x_error and the sideways error inside bypass_y_axis_max_y_error. Always logs a warning when it fires.

    # [NOTE] Disable this during deployment
    bypass_y_axis: False
  9. general_goal_checker.bypass_y_axis_max_x_error

    0.05m

    same as the defaultno effect here

    Largest along-heading error the y bypass accepts.

    No effect here. bypass_y_axis is false.

  10. general_goal_checker.bypass_y_axis_max_y_error

    0.5m

    same as the defaultno effect here

    Largest sideways error the y bypass accepts.

    No effect here. bypass_y_axis is false.

Path handler

nav2_controller::FeasiblePathHandler

Turns the global path into the local one the controller follows: finds the closest pose, prunes what is behind it, cuts the path at the first direction reversal, and keeps the next stretch up to the costmap edge.

  • navigation2_common/nav2_controller/plugins/feasible_path_handler.cpp
  • navigation2_common/nav2_util/src/path_utils.cpp
  1. PathHandler.plugin

    "nav2_controller::FeasiblePathHandler"

    same as the default

    Class loaded into the slot.

  2. PathHandler.prune_distance

    5.0m

    default 2.0

    How far ahead of the closest pose the local path extends. Also stops at the local costmap’s edge, whichever is nearer.

  3. PathHandler.enforce_path_inversion

    True

    default false

    Cuts the path at the first direction reversal, so the controller must reach the cusp before it sees the rest. The remainder is released once the robot is inside inversion_xy_tolerance and inversion_yaw_tolerance of the cusp. A reversal is a turn sharper than 135°, not merely one past 90°.

    See alsoenforce_path_inversion

  4. PathHandler.enforce_path_rotation

    False

    same as the default

    Also cuts the path at the first in-place rotation larger than minimum_rotation_angle.

  5. PathHandler.max_robot_pose_search_dist

    2.0m

    default: half the local costmap’s larger side

    How far along the path the closest-pose search looks, so a looping path cannot match a later stretch.

  6. PathHandler.inversion_xy_tolerance

    0.2m

    same as the default

    Distance to the cusp at which the path beyond it is released.

  7. PathHandler.inversion_yaw_tolerance

    0.4rad

    same as the default

    Heading tolerance for releasing the cusp. It accepts either end of the reversal axis, because the robot arrives facing the old direction.

  8. PathHandler.minimum_rotation_angle

    0.785rad

    same as the defaultno effect here

    Accumulated in-place rotation that counts as a constraint for enforce_path_rotation.

    No effect here. enforce_path_rotation is false, and the handler zeroes this when it is.

  9. PathHandler.reject_unit_path

    False

    same as the default

    Rejects a path of a single pose as invalid instead of trying to follow it.

Rotation shim: engage and hand-over

nav2_precise_rotation_shim_controller::RotationShimController

FollowPath is the shim, and MPPI runs inside it as the primary controller, sharing its parameter namespace. The shim takes the robot whenever the path heading is too far off to drive into, turns it in place, and gives it back. These parameters decide when it takes the robot and how it lets go.

  • hmgics_navigation2/nav2_precise_rotation_shim_controller/src/nav2_precise_rotation_shim_controller.cpp
  • hmgics_navigation2/nav2_precise_rotation_shim_controller/README.md
  1. FollowPath.plugin

    "nav2_precise_rotation_shim_controller::RotationShimController"

    default "dwb_core::DWBLocalPlanner"

    The controller class. The precise-rotation shim is a drop-in fork of nav2_rotation_shim_controller that keeps every feature and replaces only the in-place rotation law. Switching back is changing this string.

  2. FollowPath.static_handoff

    true

    default false

    Keeps the robot after a turn until odometry confirms it has stopped, corrects any residual from rest, then hands over. Off, control is released at angular_disengage_threshold with the robot still turning, and MPPI turns the leftover rate into an arc.

    # ------------------------------------------------------------------
    # Existing rotation shim parameters
    # ------------------------------------------------------------------
    static_handoff: true
  3. FollowPath.static_handoff_vel_threshold

    0.02rad/s

    default 0.05

    Measured yaw rate under which the chassis counts as stopped. It has to clear the odometry noise floor, about 3σ of wz at standstill, or a stop is only ever confirmed by rotation_settle_timeout.

    Source check. The source gives this base’s noise floor as about 0.035 rad/s, which is why the default is 0.05. At 0.02 a quiet base settles quickly. A noisy one waits out the 3 s timeout on every turn. Worth measuring before trusting it (the README’s measure_rotation_braking.py suggests a value).

    See alsoFollowPath.rotation_settle_durationFollowPath.rotation_settle_timeout

  4. FollowPath.static_handoff_settle_cycles

    20cycles

    default 3no effect here

    Legacy stop-confirmation dwell, counted in control cycles.

    # Legacy fallback only when rotation_settle_duration == 0.
    # At controller_frequency=20 Hz, 20 cycles = 1.00 s.
    static_handoff_settle_cycles: 20

    No effect here. Only read when rotation_settle_duration is 0; it is 0.15 here.

    See alsoFollowPath.rotation_settle_durationcontroller_frequency

  5. FollowPath.angular_dist_threshold

    0.90rad

    default 0.785

    Path-heading error above which the shim takes the robot from MPPI and turns it in place. The dividing line for corners too: a gentler corner is left for MPPI to drive through, and is not slowed for. 0.90 rad is 51.6°.

    angular_dist_threshold: 0.90  #0.15 ## 18Sep Alex  ## 0.90

    See alsoFollowPath.angular_disengage_thresholdFollowPath.rest_angular_dist_thresholdFollowPath.vertex_reversal_angular_dist_threshold

  6. FollowPath.angular_disengage_threshold

    0.035rad

    default 0.3925

    Heading error at which a path-heading or vertex turn is accepted. 0.035 rad is 2.0°. The two situational thresholds below are floored at twice this, so a released turn is not grabbed again the next cycle.

    angular_disengage_threshold: 0.035  #0.02 ## 18Sep Alex  ##0.035

    See alsoFollowPath.angular_dist_threshold

  7. FollowPath.rest_angular_dist_threshold

    0.10rad

    default 0.0

    Engage threshold used instead of angular_dist_threshold while the robot is measured AT REST: a leg start, or after a stop. From rest an in-place turn costs nothing. Leaving a sub-threshold error to MPPI means turning while accelerating. Bounded to [2 × angular_disengage_threshold, angular_dist_threshold]; 0 = unset.

    # [HMGICS Alex 25Sep] Path-heading engage threshold while the robot is measured at rest
    # (leg start, or after a stop). Leaving node 16 onto the 20.6 deg edge 16->0, the 20.4 deg
    # error was under angular_dist_threshold (51.6 deg), so MPPI turned it out while
    # accelerating: 7.2 cm right of the edge, then 2.3 deg past the edge heading at 1.5 m/s to
    # come back (bag overshoot_on_inplace_turning). 0.10 turns it in place first. While moving,
    # angular_dist_threshold still applies. Floored at 2 x angular_disengage_threshold, capped
    # at angular_dist_threshold; 0 = old behaviour.
    rest_angular_dist_threshold: 0.10

    See alsoFollowPath.angular_dist_threshold

  8. FollowPath.terminal_goal_approach_threshold

    0.05rad

    same as the default

    At the final pose, if the goal heading differs from the incoming leg by at least this, the goal is handled like a vertex: braked onto, settled, then turned. Below it, MPPI keeps the approach.

  9. FollowPath.rotate_to_heading_angular_vel

    0.30rad/s

    default 1.8

    Peak yaw rate of an in-place turn.

  10. FollowPath.max_angular_accel

    0.35rad/s²

    default 3.2

    Peak ramp-up acceleration of an in-place turn. The ramp is softened to 20% near the target.

  11. FollowPath.max_angular_decel

    0.10rad/s²

    default 0.0

    Deceleration the braking curve plans on. It is a promise that the chassis can still stop in the remaining angle, so it must be a rate the base GUARANTEES at full payload. Set too high, the robot stops past the heading; set too low, it only brakes earlier. 0 = max_angular_accel.

    # IMPORTANT:
    # Current code uses this as the braking model's assumed physical deceleration.
    # Keep 0.10 only if the fully-loaded robot can reliably achieve >= 0.10 rad/s^2.
    max_angular_decel: 0.10

    See alsoFollowPath.max_angular_brake_decelFollowPath.rotation_latency

  12. FollowPath.rotate_to_goal_heading

    true

    default false

    After arriving inside the goal checker’s xy tolerance, turn in place onto the goal yaw. The shim commands wz only, so whatever position error exists at that moment is frozen.

  13. FollowPath.rotate_to_heading_once

    false

    same as the default

    Only turn to the path heading once per goal rather than on every new path.

  14. FollowPath.closed_loop

    false

    default true

    No longer affects rotation (it warns if set). Only chooses the speed source of the vertex approach ramp: measured odometry when true, the last command when false.

    # For vertex-approach velocity source.
    closed_loop: false

Rotation shim: rotation law

The in-place turn itself. It ramps up, then brakes along the fastest curve from which a base with this much latency can still stop in the angle left, w = 2d / (sqrt(tau² + 2d/a) + tau). It waits for odometry to confirm a stop, and allows at most a set number of slow correction passes.

  • hmgics_navigation2/nav2_precise_rotation_shim_controller/include/nav2_precise_rotation_shim_controller/rotation_controller.hpp
  • hmgics_navigation2/nav2_precise_rotation_shim_controller/README.md
  1. FollowPath.rotation_latency

    0.15s

    default 0.20

    Command-to-motion latency the braking curve plans for: wz dead time plus drive lag (a high percentile, not the mean) plus half a control period. Under-estimate it and the robot stops past the heading; over-estimate it and it stops short. The correction pass removes either.

    # ------------------------------------------------------------------
    # precise-rotation parameters
    # ------------------------------------------------------------------
    # Conservative effective cmd -> physical motion / odom delay.
    # Start with 0.15 s for your ~0.10 s observed delay, then measure properly.
    rotation_latency: 0.15

    See alsoFollowPath.model_delay_wzFollowPath.max_angular_decel

  2. FollowPath.max_angular_brake_decel

    0.10rad/s²

    default 0.0

    Hard cap on how fast braking may pull the command down when the heading says the robot is running long. Braking always sheds at least 0.2 × max_angular_decel per second. 0 = 2 × max_angular_decel.

    # Maximum rate at which BRAKING may pull the command down.
    # For initial testing I would keep this equal to max_angular_decel
    # until actual braking capability is measured.
    max_angular_brake_decel: 0.10

    See alsoFollowPath.max_angular_decel

  3. FollowPath.rotation_stop_margin

    0.003rad

    same as the default

    Angle braking aims to stop short of the heading by.

    # Additional angular stopping margin.
    rotation_stop_margin: 0.003
  4. FollowPath.rotation_momentum_window

    0.15s

    same as the default

    Look-back window for measured momentum. The largest measured rate in it, when above the command, debits the braking distance by its excess times the latency.

    # Look-back window for measured angular momentum.
    rotation_momentum_window: 0.15
  5. FollowPath.rotation_settle_duration

    0.15s

    default 0.0

    Time the measured rate must stay under static_handoff_vel_threshold before a stop is confirmed. Time-based, so it does not change with controller_frequency. 0 = derive it from static_handoff_settle_cycles.

    # Explicit time-based stop confirmation.
    # This overrides static_handoff_settle_cycles.
    rotation_settle_duration: 0.15

    See alsoFollowPath.static_handoff_vel_thresholdFollowPath.static_handoff_settle_cycles

  6. FollowPath.rotation_settle_timeout

    3.0s

    same as the default

    Bound on waiting for a confirmed stop, and on a low-speed pass the chassis never takes up. Stops noisy odometry from holding the robot until the progress checker aborts.

    # Maximum time allowed in settling / low-speed stall handling.
    rotation_settle_timeout: 3.0

    See alsoprogress_checker.movement_time_allowanceFollowPath.static_handoff_vel_threshold

  7. FollowPath.rotation_max_corrections

    1

    same as the default

    Correction passes allowed after the first confirmed stop. The command can only change sign through one of these, after a measured stop.

    # Allow one reduced-speed correction after confirmed physical stop.
    rotation_max_corrections: 1
  8. FollowPath.rotation_correction_angular_vel

    0.08rad/s

    same as the default

    Peak yaw rate of a correction pass.

  9. FollowPath.rotation_breakaway_angular_vel

    0.0rad/s

    same as the default

    Command applied from rest to break static friction on a heavy base. Only applied while the measured rate is still zero, only within rotation_breakaway_timeout of a pass starting, and never while braking. 0 disables.

    # Keep disabled initially.
    # Enable only if you confirm static-friction / breakaway problems.
    rotation_breakaway_angular_vel: 0.0
  10. FollowPath.rotation_breakaway_timeout

    0.5s

    same as the defaultno effect here

    How long after a pass starts breakaway may apply.

    No effect here. rotation_breakaway_angular_vel is 0, so breakaway is off.

  11. FollowPath.goal_yaw_acceptance_ratio

    0.5

    same as the default

    The goal turn finishes at this fraction of yaw_goal_tolerance, so the robot is handed over well inside the tolerance rather than on its edge. The engage test stays at the full tolerance, which gives the hysteresis. Clamped to (0, 1].

    # Goal yaw acceptance = ratio × Nav2 goal checker yaw tolerance.
    # The goal tolerance is 0.0524 rad (~3°), so 0.5 means ~1.5°.
    goal_yaw_acceptance_ratio: 0.5

    See alsogeneral_goal_checker.yaw_goal_tolerance

Rotation shim: goals, vertices and turn direction

Where the robot stops to turn. With rotate_at_vertex the shim finds route-graph corners by their geometry, brakes onto them and turns in place on the vertex, not a sampling distance short of it. The same machinery parks the robot on the final goal before the goal turn.

  • hmgics_navigation2/nav2_precise_rotation_shim_controller/src/nav2_precise_rotation_shim_controller.cpp
  1. FollowPath.goal_rotation_hysteresis

    1.5

    same as the default

    Once the goal turn has started, it is held against the goal checker’s xy tolerance multiplied by this, not the bare tolerance. Without it, localisation noise at the tolerance edge hands the robot back and forth between the shim and MPPI. Clamped to at least 1.

    # ------------------------------------------------------------------
    # Existing remaining shim configuration
    # ------------------------------------------------------------------
    goal_rotation_hysteresis: 1.5

    See alsogeneral_goal_checker.xy_goal_tolerance

  2. FollowPath.rotate_at_vertex

    true

    default false

    Finds route corners by geometry and stops ON them to turn, rather than turning when the sample forward_sampling_distance ahead crosses the vertex, which is a whole sampling distance short.

  3. FollowPath.vertex_search_distance

    3.5m

    default 3.0

    How far ahead the corner search looks, which only decides whether a corner is visible. It is not when the shim takes over. The source records that raising it 2.0 → 3.5 alone made a corner worse, because MPPI also sees the corner earlier and turns into it.

  4. FollowPath.vertex_engage_distance

    1.2m

    default 0.0

    Distance at which the shim commits to a visible corner, whatever the speed. Without it the only trigger is the braking distance, which the shim never wins while MPPI decelerates more gently than vertex_decel. Size it past where MPPI starts its own turn. Clamped to vertex_search_distance; 0 = braking distance only.

  5. FollowPath.vertex_tolerance

    0.02m

    default 0.05

    Distance short of the vertex the approach ramp aims to stop at, and the terminal position gate that captures a goal. Keep it at or under the path’s pose spacing.

    See alsoFollowPath.terminal_position_release_tolerancegeneral_goal_checker.xy_goal_tolerancegeneral_goal_checker.trans_stopped_velocity

  6. FollowPath.terminal_position_release_tolerance

    0.03m

    default 0.0

    Hysteresis on the terminal gate: a goal captured inside vertex_tolerance stays captured until the 2-D error exceeds this. Never below vertex_tolerance; 0 = no hysteresis.

    # [HMGICS Alex 26Sep] Terminal goal: captured at vertex_tolerance (2 cm), released only past
    # this. mew_test node 16: the gate passed at 0.020 m and dropped at 0.021 m a cycle later on
    # AMCL noise, so the settle never finished and MPPI did the goal turn while creeping (true
    # robot ended ~10 cm past the goal). 1 cm above the gate covers the noise and the stop
    # overshoot; a robot really carried past it goes back to MPPI. 0 = no hysteresis.
    terminal_position_release_tolerance: 0.03

    See alsoFollowPath.vertex_tolerance

  7. FollowPath.vertex_decel

    0.5m/s²

    same as the default

    Linear deceleration of the approach ramp, which also sizes how far out the shim must take over to stop on the vertex. Must be a rate the base and the velocity smoother really deliver, or the robot arrives still rolling. Err low.

  8. FollowPath.vertex_stop_vel_threshold

    0.02m/s

    same as the default

    Measured linear speed under which the robot may count as stopped at a vertex.

  9. FollowPath.vertex_stop_settle_cycles

    20cycles

    default 3

    Consecutive low-speed cycles required before the in-place turn at a vertex starts. 20 cycles at 20 Hz is 1.0 s.

    See alsocontroller_frequency

  10. FollowPath.vertex_reversal_angular_dist_threshold

    0.10rad

    default 0.0

    Engage threshold at a vertex where travel REVERSES. bidirectional folds such a hairpin to the small turn that backs onto the next leg, which angular_dist_threshold then declines. Bounded like rest_angular_dist_threshold; 0 = unset.

    # [HMGICS Alex 24Sep] Engage threshold at a vertex where travel REVERSES (forward in and
    # reverse out, or the other way). bidirectional folds such a hairpin to the small turn that
    # backs the robot onto the next leg -- node 0's 159.4 deg reads 20.6 deg -- and against
    # angular_dist_threshold (51.6 deg) the shim never engaged: MPPI stopped 0.16 m short,
    # reversed at once and swung the heading on the way out (bag too_rush_on_corner). 0.10 makes
    # the shim stop ON the vertex, turn in place, then hand over. Anything under it (a
    # near-180 deg turnaround, nothing to turn) stays MPPI's. Floored at
    # 2 x angular_disengage_threshold, capped at angular_dist_threshold; 0 = old behaviour.
    vertex_reversal_angular_dist_threshold: 0.10

    See alsoFollowPath.angular_dist_threshold

  11. FollowPath.use_path_orientations

    true

    default false

    Finish the turn onto the path’s own tangent (route poses carry exact tangents) rather than onto the bearing to the pose forward_sampling_distance ahead.

    # [HMGICS Alex 17Sep] true: finish the rotation onto the path TANGENT (the route's poses carry
    # exact tangents), not onto the bearing to the pose forward_sampling_distance ahead. That
    # bearing left the robot aimed 0.8-1.8 deg across the path at every leg start
    # (nav2_full_20260917_212048: 1.0-1.4 cm offset / 0.5 m), which MPPI then steered out
    # (the +0.02-0.03 rad/s start bump) and which FollowPath.advanced.straight_wz_hold cannot
    # hold. Parallel hand-over + the hold: start wz exactly 0 offline.
    use_path_orientations: true
  12. FollowPath.forward_sampling_distance

    0.50m

    same as the default

    Distance ahead the heading is sampled at. With rotate_at_vertex and use_path_orientations it mostly sets the baseline over which each leg’s direction is measured.

  13. FollowPath.bidirectional

    true

    default false

    Allows turning onto either the path heading or its reverse, whichever is the shorter rotation, so the robot may back along a leg.

  14. FollowPath.bidirectional_threshold

    1.50rad

    default 1.5708

    Heading error the reversed heading must reach before it is weighed at all. At a 90° corner the two rotations tie, so below π/2 square corners reach the tie and preferred_turn_direction settles it. The source suggests 2.0 to keep square corners nose-first; 1.50 deliberately puts them on the preference.

    See alsoFollowPath.preferred_turn_direction

  15. FollowPath.preferred_turn_direction

    "left"

    default "none"

    Tie-break when both rotations come out within preferred_turn_max_penalty of each other: left, right or none. Only reachable when bidirectional is on with a threshold under π/2, which it is.

    See alsoFollowPath.bidirectional_threshold

  16. FollowPath.preferred_turn_max_penalty

    0.10rad

    same as the default

    How much longer the preferred rotation may be and still win. At 0.10 rad only corners within about 6° of square are steered by it; a 60° corner keeps the short way round.

  17. FollowPath.primary_controller

    "nav2_mppi_controller::MPPIController"

    no default: must be set

    The controller the shim wraps. It is configured with the same plugin name, so every MPPI key below lives directly in FollowPath.

MPPI: horizon and sampling

nav2_mppi_controller::MPPIController

MPPI samples batch_size noisy control sequences over a horizon of time_steps × model_dt, rolls each through the motion model, scores it with the critics, and takes a softmax-weighted mean.

  • hmgics_navigation2/nav2_mppi_controller/src/optimizer.cpp
  • hmgics_navigation2/nav2_mppi_controller/README.md
  1. FollowPath.time_steps

    50

    default 56

    Points per rollout. With model_dt this is the horizon: 50 × 0.05 = 2.5 s.

  2. FollowPath.model_dt

    0.05s

    same as the default

    Time between rollout points. Equal to the control period at 20 Hz, which switches control-sequence shifting on.

    See alsocontroller_frequency

  3. FollowPath.batch_size

    600

    default 1000

    Rollouts sampled per cycle. Must be even with antithetic_wz_sampling.

    batch_size: 600 ## 1200

    See alsoFollowPath.advanced.antithetic_wz_sampling

  4. FollowPath.model_delay_vx

    0.10s

    default 0.0

    Command delay the motion model applies to vx when rolling out, as a whole number of model_dt steps (rounded). 0.10 s is two steps.

  5. FollowPath.model_delay_vy

    0.0s

    same as the default

    The same for vy.

  6. FollowPath.model_delay_wz

    0.10s

    default 0.0

    The same for wz.

    See alsoFollowPath.rotation_latency

  7. FollowPath.iteration_count

    1

    same as the default

    Optimisation iterations per cycle. The README recommends 1 and more batches.

  8. FollowPath.temperature

    0.3

    same as the default

    Softmax selectiveness over rollout costs. Critics are balanced here on their cost SPREAD against this: a critic whose costs_std is 0.5–2.0 × temperature is shaping the result.

    See alsoFollowPath.advanced.straight_hold_obstacle_influence

  9. FollowPath.gamma

    0.015

    same as the default

    Trade-off between smoothness and low energy in the control cost.

  10. FollowPath.regenerate_noises

    false

    same as the default

    Redraw the noise every cycle rather than reusing one draw from start-up.

MPPI: velocity, acceleration and noise

Hard limits the rollouts and the command are clamped to, and the standard deviation of the noise each axis is sampled with. The robot is a differential drive, so every vy key is zero.

  • hmgics_navigation2/nav2_mppi_controller/src/optimizer.cpp
  • hmgics_navigation2/nav2_mppi_controller/include/nav2_mppi_controller/models/constraints.hpp
  1. FollowPath.ax_max

    2.0m/s²

    default 3.0

    Largest forward acceleration a rollout or the command may use.

    ## acceleration
    ax_max: 2.0   #[15Sep Tete] #3.0
  2. FollowPath.ax_min

    -2.0m/s²

    default -3.0

    Largest braking. Must be negative; a positive value is flipped with a warning.

    ax_min: -2.0  #[15Sep Tete] #-2.0
  3. FollowPath.vx_std

    0.50m/s

    default 0.2

    Standard deviation of the vx sampling noise.

    ## linear velocity x
    vx_std: 0.50 #### 0.65
  4. FollowPath.vx_max

    1.7m/s

    default 0.5

    Top forward speed.

  5. FollowPath.vx_min

    -1.7m/s

    default -0.35

    Top reverse speed, as a negative number. Full speed both ways, because the robot is bidirectional.

  6. FollowPath.vy_std

    0.0m/s

    default 0.2

    Sampling noise on vy. Zero: the base cannot move sideways.

    ## linear velocity y
    vy_std: 0.0
  7. FollowPath.vy_max

    0.0m/s

    default 0.5

    Top lateral speed. Zero, and together with the motion model this is why isHolonomic() is false.

  8. FollowPath.ay_max

    0.0m/s²

    default 3.0

    Lateral acceleration limit. Zero on a differential drive.

  9. FollowPath.ay_min

    0.0m/s²

    default -3.0

    Lateral deceleration limit. Zero on a differential drive.

  10. FollowPath.wz_std

    0.25rad/s

    default 0.4

    Standard deviation of the wz sampling noise at rest. advanced.wz_std_decay_* shrinks it with speed.

    ## angular velocity
    wz_std: 0.25
  11. FollowPath.wz_max

    1.2rad/s

    default 1.9

    Top yaw rate for MPPI. The shim has its own, rotate_to_heading_angular_vel.

    wz_max: 1.2 #### NOTE: min 0.9 to turn smoothly or 2.0 corner
  12. FollowPath.az_max

    3.5rad/s²

    same as the default

    Largest yaw acceleration.

MPPI advanced: sampling and warm start

Changes to how the noise is drawn and how the previous cycle’s solution is carried into the next. Two of the four are HMGICS additions to the fork.

  • hmgics_navigation2/nav2_mppi_controller/src/noise_generator.cpp
  • hmgics_navigation2/nav2_mppi_controller/src/optimizer.cpp
  1. FollowPath.advanced.antithetic_wz_sampling

    true

    default false

    Draws the batch in pairs with the same vx noise and opposite wz noise. Where the critics cannot tell the two signs apart, the pair gets equal weight and its wz noise cancels instead of leaking into the command. Needs an even batch_size. Changing it live triggers a reset.

    # [HMGICS Alex 17Sep] Samples drawn in pairs: same vx noise, opposite wz noise.
    # Removes the small wz wiggle on straight paths while accelerating / decelerating
    # (low speed -> critics can't see wz, only ~40-100 effective samples -> their random
    # wz leaks into the command). Offline: accel wz 0.098 -> 0.042 rad/s, decel
    # 0.042 -> 0.023, straight CTE mean halved, same accel / travel time. Needs an even
    # batch_size. Dynamic: `ros2 param set /controller_server
    # FollowPath.advanced.antithetic_wz_sampling false` to A/B live (triggers reset).
    antithetic_wz_sampling: true

    See alsoFollowPath.batch_size

  2. FollowPath.advanced.warm_start_timeout

    0.5s

    default 0.0

    Longest gap between evaluations over which the previous solution is kept. Beyond it, the control sequence, smoothing history and model-delay history are zeroed as on a new goal. This is what detects the shim handing back after it owned a vertex. 0 = keep for ever (stock).

    # [HMGICS Alex 25Sep] The shim does not call MPPI while it owns a vertex, so leaving a
    # corner MPPI resumed from the approach it was running before it. overshoot_at_node0_1,
    # after ~7 s at node 0: +0.24 m/s FORWARD for 1-2 cycles before reversing (0.5-0.7 cm the
    # wrong way). After a gap longer than this (s) the control sequence, smoothing history
    # and model-delay history are zeroed as on a new goal. 0.5 s = 10 missed 20 Hz cycles:
    # far above a cycle overrun, well below the seconds a shim-owned corner takes.
    # Logs [WARM_START]. 0 = off (stock).
    warm_start_timeout: 0.5

    See alsoFollowPath.advanced.straight_hold_handover_after_pause

  3. FollowPath.advanced.wz_std_decay_strength

    2.0

    default -1.0

    Shrinks wz_std as speed rises: (wz_std − decay_to) · e^(−strength · v) + decay_to. At these values, wz_std is 0.13 at 0.5 m/s and 0.066 at 1.7 m/s. Negative disables.

    wz_std_decay_strength: 2.0 ## original 2.0  ### set -1.0 to disable
  4. FollowPath.advanced.wz_std_decay_to

    0.06rad/s

    default 0.0

    The wz_std the decay approaches at high speed. Between 0 and wz_std.

    wz_std_decay_to: 0.06 # 0.06 for 1.0 m/s max

MPPI advanced: straight wz hold

On a straight path, while the lateral offset now and after a projected distance stays inside a band, every rollout is sampled with wz held to zero (or to a small limit). The critics then choose only the speed, and the robot runs parallel to the path. Any offset is corrected in the next bend, where the robot turns anyway. It exists to remove the small wz weave on straights.

  • hmgics_navigation2/nav2_mppi_controller/include/nav2_mppi_controller/tools/straight_wz_hold.hpp
  • hmgics_navigation2/nav2_mppi_controller/src/optimizer.cpp
  • hmgics_navigation2/nav2_mppi_controller/src/straight_wz_hold_visualizer.cpp
  1. FollowPath.advanced.straight_wz_hold

    true

    default false

    Switches the straight-line wz hold on. Logs [STRAIGHT_WZ_HOLD] engaged / released with the reason.

    # [HMGICS Alex 17Sep] Straight wz hold (nav2_mppi_controller/tools/straight_wz_hold.hpp).
    # On a straight path, while the lateral offset (now, and 1 m ahead at the current heading
    # error) stays inside the band, every rollout keeps wz = 0: the command's wz is EXACTLY 0
    # and the critics only choose the speed. The offset is corrected in the next bend, where
    # the robot turns anyway. What remains after antithetic sampling is correction of real
    # error (hand-over 1.0-1.4 cm off the path, bend-exit residue), which a diff drive cannot
    # do without wz. Band 1 cm; 2 cm from the shim's hand-over until the first release, since
    # hand-over offset + the turn's 0.1-0.3 deg residual need ~2 cm over the 4 m start straight.
    # Released 2 s of travel before a bend, and by CostCritic wanting a steer.
    # Offline, route replay forward x10 seeds, hand-over 1.0 / 1.44 cm parallel, AMCL-level noise:
    #   start (first 3 s) wz rms 0.021-0.028 -> 0 (100% of cycles exactly 0)
    #   last 1.4 m: 81-95% of cycles exactly 0, rms 0.0031-0.0041 -> 0.0016-0.0035,
    #     remaining corrections peak 0.020-0.026 (off 0.012-0.022)
    #   max CTE straight 1.0-1.45 -> 1.0-1.64 cm, bend 2.2-2.3 -> 2.2-2.4, goal 0.3 -> 0.7-1.0
    #   route time unchanged. Logs [STRAIGHT_WZ_HOLD] engaged / released (reason).
    straight_wz_hold: true
  2. FollowPath.advanced.straight_hold_lateral_tolerance

    0.015m

    default 0.01

    Lateral band left uncorrected on a straight. After any release, re-engaging needs straight_hold_engage_ratio (0.5) times this, so a correction that has started is finished.

    straight_hold_lateral_tolerance: 0.015          ## 18Sep Alex  ### original 0.01
  3. FollowPath.advanced.straight_hold_handover_lateral_tolerance

    0.050m

    default -1.0

    Wider band used from a controller reset (the shim’s hand-over) until the hold is first released. Lets a hand-over offset ride to the first bend instead of being steered out while accelerating. Not applied within 1.5 m of the path’s end, nor after straight_hold_handover_distance. At or below the normal band = off.

    straight_hold_handover_lateral_tolerance: 0.050 ## 24Sep Alex: 0.015
  4. FollowPath.advanced.straight_hold_prediction_distance

    1.5m

    default 1.0

    Distance, capped at the remaining path, over which the heading error is projected into lateral error for the band check. This predicted offset is what usually releases the hold on a long straight.

    straight_hold_prediction_distance: 1.5          ## 24Sep Alex: 3.0
  5. FollowPath.advanced.straight_hold_filter_time

    0.3s

    same as the default

    While held, lateral offset and heading are low-pass filtered with this time constant, so localisation noise read through the prediction distance does not release the hold at random. 0 disables.

    straight_hold_filter_time: 0.3                  ## 18Sep Alex: original 0.3
  6. FollowPath.advanced.straight_hold_obstacle_influence

    1.0

    default 0.5

    Releases the hold, and re-optimises that cycle with wz sampled, when CostCritic’s cost range over the held rollouts exceeds this × temperature, meaning obstacles want a steer the hold forbids. Negative disables.

    straight_hold_obstacle_influence: 1.0           ## 18Sep Alex  ### original 0.5

    See alsoFollowPath.temperature

  7. FollowPath.advanced.straight_hold_lookahead_time

    2.0s

    default 1.0

    Straightness is checked over max(0.5 m, |vx| × this) of path ahead, so the hold releases this long before a bend and MPPI settles any offset before entering it.

  8. FollowPath.advanced.straight_hold_max_wz

    0.015rad/s

    default 0.0

    Yaw rate a held cycle may still command. 0 is the original rule, an exactly zero command. A small limit lets the optimiser trim the offset instead of banking it until the hold releases.

  9. FollowPath.advanced.straight_hold_handover_distance

    3.0m

    default 0.0

    Travel after a reset over which the hand-over band applies, integrated from measured |vx|. After it the normal band does, even if the hold has not released. 0 = until the first release.

    # [HMGICS Alex 25Sep] The 5 cm hand-over band applies only over the first 3 m of travel
    # after the shim's hand-over (about the distance to reach cruise), then the 1.5 cm band
    # does. overshoot_at_node0_1: on the 14 m edge 16 -> 0 the 5 cm band held wz = 0 for 12 m
    # at the turn's -0.3 deg residual, drifted -3.4 cm, released only 1.5 m before node 0,
    # and the shim carried 2.6 cm through the vertex into the next edge (the S-correction).
    # On that bag's readings the 1.5 cm band would have released at ~4.9 m, 0.8 cm off, at
    # cruise. 0 = no limit (the old behaviour).
    straight_hold_handover_distance: 3.0
  10. FollowPath.advanced.straight_hold_handover_after_pause

    true

    default false

    When warm_start_timeout discards the warm start (the shim owned the robot, for example at a vertex), also start a fresh hand-over for the hold. False only re-judges the hold as an engage.

    # [HMGICS Alex 26Sep] The shim's hand-over after a vertex turn (node 0) gets the same
    # hand-over band as after a goal. mew_test: after node 0 the hold was on its strict
    # re-engage band (0.75 cm), so the 0.9-1.6 cm the shim delivered was steered out at
    # pull-away with a 0.9-1.8 deg yaw swing -- the "early oscillation". Now it rides the 5 cm
    # band for 3 m and is corrected at cruise, if at all. Needs warm_start_timeout > 0 (that is
    # what detects the hand-over). false = the hold is only re-judged there.
    straight_hold_handover_after_pause: true

    See alsoFollowPath.advanced.warm_start_timeout

  11. FollowPath.advanced.straight_hold_publish_debug_markers

    true

    default falsediagnostic

    Publishes an RViz view of the hold on straight_wz_hold_markers: the band being judged, the offset now and the predicted offset. Nothing is built unless something subscribes. Static.

    # Shows the band the hold is judging against, the robot's offset from the path line,
    # and the PREDICTED offset after straight_hold_prediction_distance of travel
    # which is what usually releases the hold on a long straight,
    # while the robot itself is still well inside.
    straight_hold_publish_debug_markers: true
  12. FollowPath.advanced.straight_hold_debug_text_size

    0.16m

    same as the defaultdiagnostic

    Height of the hold’s readout text.

  13. FollowPath.advanced.straight_hold_debug_readout_offset_x

    0.0m

    same as the defaultdiagnostic

    Offset of the readout from the robot, in the local costmap frame.

  14. FollowPath.advanced.straight_hold_debug_readout_offset_y

    -2.2m

    same as the defaultdiagnostic

    Offset of the readout from the robot, in the local costmap frame. The default keeps it clear of the TerminalGoalCritic readout.

MPPI advanced: speed governors

Caps on vx computed every cycle before sampling: a latency-aware braking envelope toward the goal, a legacy near-goal ramp, and a curvature limit that slows the robot ahead of arcs. When terminal_braking is on, the optimizer applies the braking envelope INSTEAD of the near-goal ramp. The curvature cap applies on top of whichever one runs.

  • hmgics_navigation2/nav2_mppi_controller/src/optimizer.cpp
  1. FollowPath.advanced.near_goal_velocity_scaling

    true

    default falseno effect here

    Legacy ramp: narrows the vx/wz caps linearly from full at near_goal_distance of remaining path to the near_goal_* values at the goal.

    # Terminal translation is governed by the latency-aware MPPI braking envelope below.
    near_goal_velocity_scaling: true

    No effect here. terminal_braking is on, and the optimizer runs it INSTEAD of the near-goal ramp (it logs a warning saying so).

    See alsoFollowPath.advanced.terminal_braking

  2. FollowPath.advanced.near_goal_distance

    1.5m

    default 0.0no effect here

    Remaining path length over which the near-goal ramp runs.

    near_goal_distance: 1.5 #1.0 for 1.0 m/s max

    No effect here. terminal_braking is on, and the optimizer runs it INSTEAD of the near-goal ramp (it logs a warning saying so).

  3. FollowPath.advanced.near_goal_vx_max

    0.10m/s

    default 0.25no effect here

    Forward cap at the goal under the near-goal ramp.

    No effect here. terminal_braking is on, and the optimizer runs it INSTEAD of the near-goal ramp (it logs a warning saying so).

  4. FollowPath.advanced.near_goal_vx_min

    -0.10m/s

    default 0.0no effect here

    Reverse cap at the goal under the near-goal ramp.

    No effect here. terminal_braking is on, and the optimizer runs it INSTEAD of the near-goal ramp (it logs a warning saying so).

  5. FollowPath.advanced.near_goal_wz_max

    0.25rad/s

    default 0.5no effect here

    Yaw-rate cap at the goal under the near-goal ramp.

    No effect here. terminal_braking is on, and the optimizer runs it INSTEAD of the near-goal ramp (it logs a warning saying so).

  6. FollowPath.advanced.near_goal_vx_std

    # 0.033m/s

    commented out, so the code uses -1.0

    Narrows the vx SAMPLING toward the goal to match the narrowed cap, so the terminal batch is not mostly outside the executable band. Keeping near_goal_vx_std / near_goal_vx_max = vx_std / vx_max holds the ratio constant. -1 = off.

    # near_goal_vx_std / near_goal_vx_max == vx_std / vx_max
    # near_goal_vx_std: 0.033   # as-is: 0.5 * 0.10 / 1.5
  7. FollowPath.advanced.terminal_braking

    true

    same as the default

    Caps |vx| each cycle at the speed from which the robot can still stop at the terminal goal: a · (sqrt(τ² + 2d/a) − τ) over the remaining distance d, less terminal_stop_margin. Replaces the near-goal ramp when on.

    See alsoFollowPath.advanced.near_goal_velocity_scalingFollowPath.TerminalGoalCritic.stop_vx_weight

  8. FollowPath.advanced.terminal_brake_decel

    0.5m/s²

    same as the default

    Deceleration a of the braking envelope. TerminalGoalCritic charges rollouts that exceed the same curve.

    terminal_brake_decel: 0.5     # replace with loaded-robot measurement before final tuning
  9. FollowPath.advanced.terminal_brake_latency

    0.10s

    same as the default

    Latency τ the envelope plans for: the command already in flight.

  10. FollowPath.advanced.terminal_stop_margin

    0.01m

    same as the default

    Distance short of the goal the envelope aims to stop at.

  11. FollowPath.advanced.terminal_heading_lookback

    0.10m

    same as the default

    Path length behind the goal used to measure the approach direction, which defines longitudinal and lateral at the goal.

  12. FollowPath.advanced.curvature_velocity_scaling

    true

    default false

    Caps vx so the lateral acceleration on any arc ahead stays under max_lateral_accel, back-propagated through curvature_decel so the slowdown starts early enough to be reachable.

  13. FollowPath.advanced.max_lateral_accel

    0.17m/s²

    default 0.0

    Lateral acceleration allowed on an arc: the corner speed is sqrt(max_lateral_accel / curvature). 0 disables the cap.

    max_lateral_accel: 0.17 # [HMGICS Alex 17Sep] 0.30 = faster arcs, max CTE 3.2 -> 3.8 cm offline
  14. FollowPath.advanced.curvature_decel

    0.3m/s²

    default 1.0

    Deceleration the corner speed is back-propagated with. Lower starts slowing further out.

    # [HMGICS Alex 17Sep] 1.0 -> 0.3: start slowing ~2.5 m before an arc instead of ~0.7 m.
    # At 1.0 the robot reached the arc at 1.2-1.4 m/s; the rollouts (one speed cap for the
    # whole horizon) then crossed the arc at that speed, and PathFollow's progress reward
    # made cutting in cheapest: 10 cm off before the arc, 15 cm inside it. Offline on the
    # route from nav2_full_20260917_202927, with PathAlign 45 / PathFollow 3.5 below.
    curvature_decel: 0.3
  15. FollowPath.advanced.curvature_sample_distance

    0.2m

    same as the default

    Arc length either side of a point used to measure curvature, so the planner’s discretisation does not read as curvature.

  16. FollowPath.advanced.curvature_vx_min

    0.15m/s

    same as the default

    Floor on the curvature cap, so a sharp corner cannot stop the robot.

MPPI: motion model, critics and namespace

Which motion model the rollouts are integrated with, which critics score them and in what order, plus two keys that sit in this namespace without doing what their names suggest.

  • hmgics_navigation2/nav2_mppi_controller/src/optimizer.cpp
  • hmgics_navigation2/nav2_mppi_controller/include/nav2_mppi_controller/motion_models.hpp
  • hmgics_navigation2/nav2_mppi_controller/src/critic_manager.cpp
  1. FollowPath.enforce_path_inversion

    True

    default falseno effect here

    Read by every critic through its parent namespace (FollowPath). Only GoalCritic acts on it, aiming at the cusp rather than the goal.

    ## FIXME: enforce_path_inversion never reaches the critics
    enforce_path_inversion: True

    No effect here. The only critic that uses it, GoalCritic, is disabled.

    See alsoenforce_path_inversion

  2. FollowPath.transform_tolerance

    0.1

    no default: must be setno effect here

    Nothing in this build reads it. The controller server takes its transform tolerance from the local costmap, and this fork of MPPI no longer declares one.

    Not read by the code. No getParam for it in the controller server, the shim or MPPI.

  3. FollowPath.motion_model

    "diff_drive"

    default "DiffDrive"

    Name of the motion-model PLUGIN instance. In this fork the model is loaded through pluginlib from FollowPath.<motion_model>.plugin, so this name and the diff_drive block below must agree. The commented-out "DiffDrive" is the stock spelling, which would look for FollowPath.DiffDrive.plugin.

    # motion_model: "DiffDrive"
    motion_model: "diff_drive"

    See alsoFollowPath.diff_drive.plugin

  4. FollowPath.diff_drive.plugin

    "mppi::DiffDriveMotionModel"

    no default: must be set

    Class of the motion model: non-holonomic, integrates vx and wz only.

    See alsoFollowPath.motion_model

  5. FollowPath.AckermannConstraints.min_turning_r

    0.2m

    same as the defaultno effect here

    Minimum turning radius for the Ackermann model.

    No effect here. The motion model is a differential drive.

  6. FollowPath.critics

    ["ConstraintCritic", "CostCritic", "GoalCritic", "GoalAngleCritic", "TerminalGoalCritic", "PathAlignCritic", "CurvatureOnlyPathAlignCritic", "PathFollowCritic", "PathAngleCritic", "PreferForwardCritic", "TwirlingCritic"]

    default []

    Critics loaded, in scoring order. A critic listed here still does nothing while its own enabled is false, which is how the file switches critics off without removing them. Left empty, MPPI loads no critics at all.

    critics:
      [
        "ConstraintCritic",               #0
        "CostCritic",                     #1
        "GoalCritic",                     #2
        "GoalAngleCritic",                #3
        "TerminalGoalCritic",             #4
        "PathAlignCritic",                #5
        "CurvatureOnlyPathAlignCritic",   #6
        "PathFollowCritic",               #7
        "PathAngleCritic",                #8
        "PreferForwardCritic",            #9
        "TwirlingCritic"                  #10
      ]

MPPI: diagnostics

Topics that exist for tuning. None of these change a command, but some cost CPU while they are on, and the file marks two to switch off for deployment.

  • hmgics_navigation2/nav2_mppi_controller/src/controller.cpp
  • hmgics_navigation2/nav2_mppi_controller/src/critic_manager.cpp
  • hmgics_navigation2/nav2_mppi_controller/src/trajectory_visualizer.cpp
  1. FollowPath.visualize

    false

    same as the defaultdiagnostic

    Publishes candidate trajectories. Can slow the controller significantly.

  2. FollowPath.publish_optimal_trajectory

    true

    default falsediagnostic

    Publishes the full optimal trajectory each cycle on optimal_trajectory, when something subscribes.

  3. FollowPath.publish_critics_stats

    true

    default falsediagnostic

    Publishes critics_stats: per-critic cost sums and spreads, plus this fork’s effective sample size. The measurement every weight in this file was balanced on. Static.

  4. FollowPath.publish_path_window

    false

    same as the defaultdiagnostic

    Draws the reference window PathAlignCritic can see on path_align_window and adds its figures to critics_stats. Static.

    # [DIAGNOSTIC] Draws the reference window the path-alignment critic can actually see
    # on ~/path_align_window, and adds the numbers to ~/critics_stats. PathAlignCritic now
    # uses the complete controller-local path, not only the furthest rollout projection.
    # Clamping should therefore occur only when that whole local path is shorter than a
    # rollout (normally near a goal or feasible-path cusp).
    #
    # Watch path_window_length / trajectory_reach_length. Near 1.0 is healthy. The RViz
    # marker is green at >= 80%, amber at 50-80%, and red below 50%; its readout also says
    # whether PathAlign contributed and how much of the rollout was clamped.
    publish_path_window: false       # [NOTE] For path_align_window, Disable this during deployment
  5. FollowPath.TrajectoryVisualizer.trajectory_step

    5

    same as the defaultno effect herediagnostic

    Every n-th candidate trajectory is drawn.

    No effect here. visualize is false.

  6. FollowPath.TrajectoryVisualizer.time_step

    3

    same as the defaultno effect herediagnostic

    Every n-th point of a drawn trajectory.

    No effect here. visualize is false.

ConstraintCritic

mppi::critics::ConstraintCritic

Penalises the part of each rollout that exceeds the velocity limits. Reads vx_max, vx_min and vy_max once at start-up, so a dynamic speed limit never reaches it.

  • hmgics_navigation2/nav2_mppi_controller/src/critics/constraint_critic.cpp
  1. FollowPath.ConstraintCritic.enabled

    true

    same as the default

    Switches the critic on or off without removing it from critics.

  2. FollowPath.ConstraintCritic.cost_power

    1

    same as the default

    Power applied to the weighted cost. 1 = linear.

  3. FollowPath.ConstraintCritic.cost_weight

    4.0

    same as the default

    Weight of the over-limit penalty.

GoalCritic

mppi::critics::GoalCritic

Switched off in this file (enabled: false).

Stock position critic: the mean distance to the goal over the horizon, inside threshold_to_consider of the end of the path. Disabled here, and the block comment says why.

Opening comment in the file

# [DISABLED] Superseded by TerminalGoalCritic, which now owns the whole band
# below 1.4 m. Both are UPPER gates on local_path_length, so leaving this on
# meant two position critics running together below 1.2 m. Three problems with
# that, none of them a disagreement about the optimum -- measured, the two rank
# rollouts identically in every case:
#   1. Double counting. Each critic gets balanced to std/T 0.5-2.0 on its own,
#      so two overlapping position terms put the COMBINED authority near 4 T.
#   2. It dilutes the anisotropy. This critic is isotropic (euclidean), the
#      terminal one weights lateral 1.5x because a diff drive cannot fix lateral
#      error. Measured lateral/longitudinal penalty ratio: 1.87 terminal alone,
#      1.16 this one alone, 1.65 for the sum -- pulled back toward isotropic.
#   3. A second relay. Two gates on a jittery local_path_length means two step
#      changes in total cost during the terminal approach, which is the same
#      gating pattern blamed for the weave everywhere else in this file.
#
# What used to be given up: this critic scores the MEAN distance over the horizon,
# so it rewards arriving early. TerminalGoalCritic now restores that property with
# prompt_progress_weight, using time-averaged 2-D goal error rather than a second
# independently gated position critic.
#
# To revert: set enabled back to true and drop TerminalGoalCritic's
# threshold_to_consider to 1.2 -- but then tune the two AS A PAIR on their
# combined costs_std, never separately.
  • hmgics_navigation2/nav2_mppi_controller/src/critics/goal_critic.cpp
  1. FollowPath.GoalCritic.enabled

    false

    default true

    Switches the critic on or off without removing it from critics.

  2. FollowPath.GoalCritic.cost_power

    1

    same as the defaultno effect here

    Power applied to the weighted cost. 1 = linear.

    No effect here. GoalCritic is switched off by its own enabled: false.

  3. FollowPath.GoalCritic.cost_weight

    5.0

    same as the defaultno effect here

    Weight of the mean-distance-to-goal term.

    No effect here. GoalCritic is switched off by its own enabled: false.

  4. FollowPath.GoalCritic.threshold_to_consider

    1.4m

    same as the defaultno effect here

    The critic runs only while the remaining local path is shorter than this.

    No effect here. GoalCritic is switched off by its own enabled: false.

GoalAngleCritic

mppi::critics::GoalAngleCritic

Penalises heading error to the goal yaw near the end of the path. In this fork it can score the path’s arrival heading instead when the goal yaw is far off it.

Opening comment in the file

# [NOTE] This critic may be spending its weight on the wrong axis. Two facts:
#
# 1. This robot is NONHOLONOMIC -- motion_model is diff_drive and vy_max is 0.0,
#    so DiffDriveMotionModel::isHolonomic() is false. It has two controls, v and
#    w, and cannot translate sideways. The only ways it can change yaw are to
#    curve (v != 0, w != 0), which moves x and y with it, or to stop and spin
#    (v = 0), which surrenders all forward progress -- and under payload the
#    wheel scrub of a spin drags the body anyway. There is no way for it to fix
#    yaw for free. On an omni platform none of this would apply.
#
# 2. rotate_to_goal_heading is true, so the shim redoes terminal yaw regardless,
#    and computeRotateToHeadingCommand() only ever sets twist.angular.z -- it
#    never commands vx. Position error at that handover is therefore FROZEN.
#
# Together: this critic is active in the last 0.5 m while the robot is still
# approaching, so the cheapest way for a rollout to reduce yaw error here is to
# curve -- which displaces the very terminal position the goal tolerance is
# measured on. It buys yaw, which the shim will redo anyway, with position,
# which the shim cannot fix. TerminalGoalCritic carries a small yaw term of its
# own (yaw_weight 0.15) for the one job that does matter: keeping the shim's
# spin short, since a long spin drags xy through slip.
#
# With rotate_to_goal_heading enabled, the shim redoes terminal yaw
# anyway, and on a diff drive robot this critic buys that yaw with POSITION error
# -- which is the one thing the shim cannot fix, because it commands wz only.
# TerminalGoalCritic carries a small yaw term of its own for the same job.
# Once TerminalGoalCritic is tuned, try dropping this to ~1.5 or disabling it
# and compare goal xy p95; do NOT change both in the same run.
  • hmgics_navigation2/nav2_mppi_controller/src/critics/goal_angle_critic.cpp
  1. FollowPath.GoalAngleCritic.enabled

    true

    same as the default

    Switches the critic on or off without removing it from critics.

  2. FollowPath.GoalAngleCritic.cost_power

    1

    same as the default

    Power applied to the weighted cost. 1 = linear.

  3. FollowPath.GoalAngleCritic.cost_weight

    3.0

    same as the default

    Weight of the heading term.

  4. FollowPath.GoalAngleCritic.threshold_to_consider

    1.5m

    default 0.5

    The critic runs only while the remaining local path is shorter than this.

    Source check. The opening comment says this critic "is active in the last 0.5 m". That was the default. At 1.5 it is active over the last 1.5 m, three times the stretch the argument was made about.

  5. FollowPath.GoalAngleCritic.symmetric_yaw_tolerance

    true

    default false

    Accept either heading at the goal, matching the bidirectional goal checker.

  6. FollowPath.GoalAngleCritic.path_heading_yaw_threshold

    0.05rad

    default -1.0

    When the goal yaw differs from the path’s arrival heading by more than this, score the arrival heading instead: the shim turns onto the goal yaw after arrival anyway. -1 disables.

    # [HMGICS Alex 17Sep] Score the path's arrival heading instead of the goal yaw when they
    # differ by more than this (rad). Same switch as TerminalGoalCritic below; the shim
    # turns onto the goal yaw in place after arrival. Offline with a goal yaw 90 deg off
    # the final straight, this critic plus the terminal yaw term turned the chassis up to
    # 90 deg over the last 0.5 m (14.5 cm off the path, 10 s to arrive); with both
    # switched: 0.4 cm, 0.6 deg, 3.9 s. 0.05 (~ yaw_goal_tolerance) also keeps a 15 deg
    # goal from pre-rotating (2.5 -> 0.5 cm). -1 disables.
    path_heading_yaw_threshold: 0.05

    See alsoFollowPath.TerminalGoalCritic.path_heading_yaw_threshold

TerminalGoalCritic

mppi::critics::TerminalGoalCritic

An HMGICS critic that parks the robot on the goal. It scores each rollout around its CLOSEST approach, not its mean or its end. It splits position error into lateral and longitudinal in the goal frame, charges speed after arrival and distance past the goal, and fades in over a band of remaining path so the handover from the path critics is not a step. One cost_weight scales every term, so the other weights here are relative.

Opening comment in the file

# Parks the robot ON the goal over the last metre.
#
# [!] cost_weight below is a STARTING POINT, not a tuned value. Balance it the
# way every other critic here was: read costs_std off critics_stats, divide by
# cost_weight to get the raw-unit spread, and pick the weight that lands
#   std/T = cost_weight * raw_spread / 0.3
# in the 0.5-2.0 band. This critic only scores the last 0.25 s of each rollout,
# so its raw spread will NOT resemble GoalCritic's -- measure, do not scale.
#
# This fork also publishes effective sample size on critics_stats. Watch it
# during the terminal approach: collapsing toward 1 means the softmax has
# narrowed onto a single sampled rollout and the command is a fresh random draw
# each cycle (jitter at the goal); sitting near batch_size means nothing is
# steering. That is the fastest signal that this weight is too high or too low.
  • hmgics_navigation2/nav2_mppi_controller/src/critics/terminal_goal_critic.cpp
  • hmgics_navigation2/nav2_mppi_controller/include/nav2_mppi_controller/critics/terminal_goal_critic.hpp
  1. FollowPath.TerminalGoalCritic.enabled

    true

    same as the default

    Switches the critic on or off without removing it from critics.

  2. FollowPath.TerminalGoalCritic.cost_power

    1

    same as the default

    Power applied to the weighted cost. 1 = linear.

  3. FollowPath.TerminalGoalCritic.cost_weight

    8.0

    same as the default

    One weight over every term below; balance it on the measured costs_std, not by analogy.

  4. FollowPath.TerminalGoalCritic.threshold_to_consider

    1.4m

    default 1.2no effect here

    Legacy handover distance. Only used as the default for activation_full, which is set explicitly here.

    # Legacy fallback for older TerminalGoalCritic binaries. The explicit activation
    # band below is used by this source version.
    threshold_to_consider: 1.4

    No effect here. activation_start and activation_full are both set, and they replace it.

  5. FollowPath.TerminalGoalCritic.activation_start

    2.5m

    default: threshold_to_consider + 0.4

    Remaining-path distance at which the critic starts to fade in.

    # Fade terminal position/yaw in while PathFollow is still active, then reach full
    # authority exactly where PathFollow gates off (1.4 m). This overlap removes the
    # one-cycle critic handoff without leaving a goal-seeking gap.
    activation_start: 2.5  ## Beh: 1.8  ## Kelvin: 2.5
  6. FollowPath.TerminalGoalCritic.activation_full

    1.4m

    default: threshold_to_consider

    Distance at which it reaches full authority. Set equal to the path critics’ threshold_to_consider (1.4), so it is at full strength exactly where they switch off.

    See alsoFollowPath.PathFollowCritic.threshold_to_considerFollowPath.PathAlignCritic.threshold_to_consider

  7. FollowPath.TerminalGoalCritic.terminal_window_time

    0.50s

    default 0.25

    Width of the scoring windows in physical time, independent of model_dt. Position and yaw use a window centred on closest approach; velocity uses one starting there.

    # Physical time, independent of model_dt. Position/yaw use a centred window;
    # velocity uses a forward-only window beginning at closest approach.
    terminal_window_time: 0.50

    Source check. The opening comment says the critic "only scores the last 0.25 s of each rollout". Two things changed: the window is 0.50 s, and it is centred on each rollout’s closest approach to the goal, not on the end of the horizon. The header explains why scoring the end was dropped.

  8. FollowPath.TerminalGoalCritic.stop_ramp_distance

    1.0m

    default 0.6

    Remaining distance over which the post-arrival stop term fades in, reaching full strength at the goal.

    # Do not reward crawling from 1.4 m away. The stop/dwell term fades in only over
    # the final 0.6 m and reaches full strength at the goal.
    stop_ramp_distance: 1.0  #0.6  ##1.0 kelvin

    Source check. The comment above says the stop term "fades in only over the final 0.6 m". It is 1.0 here (Kelvin’s value, per the inline note), so it fades in over the last metre.

  9. FollowPath.TerminalGoalCritic.lateral_weight

    1.0

    default 1.5

    Weight on lateral error in the goal frame.

    # Lateral > longitudinal: a diff drive drives longitudinal error out on the
    # spot, but lateral error needs a manoeuvre it has no room for this close in.
    lateral_weight: 1.0  ##1.5

    Source check. The comment argues "Lateral > longitudinal" because a differential drive cannot fix lateral error close in. At 1.0 against a longitudinal 1.0 there is no longer any anisotropy, so the critic scores position isotropically.

  10. FollowPath.TerminalGoalCritic.longitudinal_weight

    1.0

    same as the default

    Weight on error along the goal heading.

  11. FollowPath.TerminalGoalCritic.yaw_weight

    0.15

    same as the default

    Weight on yaw error. Kept small because the shim owns the terminal yaw; this only keeps its in-place spin short.

    # Deliberately small -- the rotation shim owns terminal yaw. This only keeps
    # the shim's in-place spin short, because that spin drags xy through slip.
    yaw_weight: 0.15  ## 18Sep Alex ## original 1.0
  12. FollowPath.TerminalGoalCritic.braking_vx_weight

    1.0

    same as the default

    Charges rollout speed above the braking curve (advanced.terminal_brake_*) before arrival.

  13. FollowPath.TerminalGoalCritic.prompt_progress_weight

    1.0

    same as the default

    Weight on time-averaged 2-D goal error, which makes arriving earlier cheaper than scheduling the same arrival at the horizon’s end. Not a minimum-speed target.

    # Prevent receding-horizon procrastination: reducing 2-D goal error early is cheaper
    # than scheduling the same arrival at the horizon end. The 2-D form stays active for
    # lateral and already-past recovery. This is not a minimum-speed target; braking and
    # dwell costs still own the smooth stop.
    prompt_progress_weight: 1.0
  14. FollowPath.TerminalGoalCritic.stop_vx_weight

    0.15

    default 0.5

    Charges forward/backward speed after closest approach, which separates parking from passing through.

    # What separates "park here" from "pass through here". Units are m/s and rad/s
    # against metres of position error; at the near-goal caps (vx 0.20, wz 0.25)
    # they are worth about 0.10 m and 0.05 m of equivalent position error.
    stop_vx_weight: 0.15

    Source check. The comment prices these weights "at the near-goal caps (vx 0.20, wz 0.25)". The near-goal ramp is inert while terminal_braking is on, and its vx cap is 0.10 in this file anyway. The speeds a rollout actually has at arrival come from the braking envelope.

    See alsoFollowPath.advanced.terminal_braking

  15. FollowPath.TerminalGoalCritic.stop_wz_weight

    0.2

    same as the default

    Charges yaw rate after closest approach.

  16. FollowPath.TerminalGoalCritic.overshoot_weight

    1.0

    default 0.0

    Charges each sample’s distance PAST the goal along the incoming direction, averaged over the whole horizon. 0 disables.

    # Charges the part of each rollout PAST the goal, along the path's incoming
    # direction, averaged over the whole horizon (metres, like position error).
    # Nothing above looks beyond a 0.25 s window at closest approach, so the optimal
    # path ran 0.34-0.38 m past the goal with the robot still 7-8 cm short
    # (nav2_full_20260915_072506). At 1.0, sitting 1 cm past costs the same as
    # stopping 1 cm short. Tune on the goal checker's x_error: trending negative
    # (parking short) -> lower; plan still running past the goal -> raise. 0 disables.
    overshoot_weight: 1.0
  17. FollowPath.TerminalGoalCritic.symmetric_yaw_tolerance

    true

    same as the default

    Accept either heading at the goal, matching the checker and the shim.

    # Match BidirectionalGoalChecker and the shim's selectBidirectionalHeading.
    symmetric_yaw_tolerance: true
  18. FollowPath.TerminalGoalCritic.path_heading_yaw_threshold

    0.05rad

    same as the default

    Score the path’s arrival heading instead of the goal yaw when they differ by more than this. Set together with GoalAngleCritic’s. -1 disables.

    # [HMGICS Alex 17Sep] Yaw term scores the path's arrival heading when the goal yaw is
    # more than this (rad) off it. See GoalAngleCritic above; set both together. -1 disables.
    path_heading_yaw_threshold: 0.05

    See alsoFollowPath.GoalAngleCritic.path_heading_yaw_threshold

  19. FollowPath.TerminalGoalCritic.publish_debug_markers

    false

    same as the defaultdiagnostic

    Publishes the critic’s terminal diagnostics as RViz markers. Static.

    # path_follow_debug_markers: true # [NOTE] Disable this during deployment
    publish_debug_markers: false  # [NOTE] Disable this during deployment
  20. FollowPath.TerminalGoalCritic.debug_readout_offset_x

    -1.5m

    default 0.0diagnostic

    Readout position relative to the robot.

    # Large saturated text so the terminal diagnostics remain legible over a white map.
    debug_readout_offset_x: -1.5
  21. FollowPath.TerminalGoalCritic.debug_readout_offset_y

    -1.0m

    default -1.5diagnostic

    Readout position relative to the robot.

  22. FollowPath.TerminalGoalCritic.debug_readout_text_size

    0.22m

    same as the defaultdiagnostic

    Readout text height.

  23. FollowPath.TerminalGoalCritic.debug_reached_hold_time

    -1.0s

    default 8.0diagnostic

    How long the "reached" readout stays up. Negative keeps the final frame until a new goal clears it.

    # Once XY tolerance is reached, keep the complete final marker frame indefinitely.
    # Use a positive duration for timed expiry; -1 means no automatic RViz expiry.
    # A later inactive/new-goal update can still clear it explicitly with DELETEALL.
    debug_reached_hold_time: -1.0

PreferForwardCritic

mppi::critics::PreferForwardCritic

Switched off in this file (enabled: false).

Penalises reversing. Disabled: the robot is bidirectional, and the shim picks the direction.

  • hmgics_navigation2/nav2_mppi_controller/src/critics/prefer_forward_critic.cpp
  1. FollowPath.PreferForwardCritic.enabled

    false

    default true

    Switches the critic on or off without removing it from critics.

  2. FollowPath.PreferForwardCritic.cost_power

    1

    same as the defaultno effect here

    Power applied to the weighted cost. 1 = linear.

    No effect here. PreferForwardCritic is switched off by its own enabled: false.

  3. FollowPath.PreferForwardCritic.cost_weight

    5.0

    same as the defaultno effect here

    Weight of the reversing penalty.

    No effect here. PreferForwardCritic is switched off by its own enabled: false.

  4. FollowPath.PreferForwardCritic.threshold_to_consider

    0.5m

    same as the defaultno effect here

    Switches off within this remaining path length.

    No effect here. PreferForwardCritic is switched off by its own enabled: false.

CostCritic

mppi::critics::CostCritic

Scores rollouts on the local costmap: a collision costs collision_cost, a point at or above near_collision_cost adds critical_cost, and anything else adds the cell cost, except near the goal.

  • hmgics_navigation2/nav2_mppi_controller/src/critics/cost_critic.cpp
  1. FollowPath.CostCritic.enabled

    true

    same as the default

    Switches the critic on or off without removing it from critics.

  2. FollowPath.CostCritic.cost_power

    1

    same as the default

    Power applied to the weighted cost. 1 = linear.

  3. FollowPath.CostCritic.cost_weight

    3.0

    default 3.81

    Weight of the costmap term, divided by the number of points scored.

  4. FollowPath.CostCritic.near_collision_cost

    253

    same as the default

    Cell cost at or above which a point adds critical_cost. 253 is the inscribed-inflated cost; a higher value is accepted with a warning.

  5. FollowPath.CostCritic.critical_cost

    60.0

    default 300.0

    Added per point at or above near_collision_cost.

  6. FollowPath.CostCritic.consider_footprint

    false

    same as the default

    Collision-check the full footprint instead of the centre point. Scoring always uses the centre cost.

  7. FollowPath.CostCritic.collision_cost

    1000000.0

    same as the default

    Cost of a rollout that collides.

  8. FollowPath.CostCritic.near_goal_distance

    1.0m

    default 0.5

    Inside this remaining path the ordinary cell cost is no longer added, so the robot can reach a goal near obstacles. Collisions still count.

  9. FollowPath.CostCritic.trajectory_point_step

    2

    same as the default

    Scores every n-th rollout point.

PathAlignCritic

mppi::critics::PathAlignCritic

Penalises how far each rollout’s points lie from the path. In this fork it scores against the whole controller-local path. It is the main term holding the robot on the line.

Opening comment in the file

## Default value
# cost_weight: 14.0
# max_path_occupancy_ratio: 0.05
# threshold_to_consider: 0.5
# offset_from_furthest: 20
  • hmgics_navigation2/nav2_mppi_controller/src/critics/path_align_critic.cpp
  1. FollowPath.PathAlignCritic.enabled

    true

    same as the default

    Switches the critic on or off without removing it from critics.

  2. FollowPath.PathAlignCritic.cost_power

    1

    same as the default

    Power applied to the weighted cost. 1 = linear.

  3. FollowPath.PathAlignCritic.cost_weight

    30.0

    default 10.0

    Weight of the distance-from-path term. The heaviest in the file.

    # Balance critics on the SPREAD of their cost across the batch (critics_stats
    # costs_std), not costs_sum: MPPI softmaxes over (cost - min), so a term that shifts
    # every trajectory equally has no effect. Target std ~= 0.5-2.0 x temperature (0.3).
    #
    # Clean post-fix A/B with PathFollow=1.7:
    #   weight 23: influence 2.51, CTE 2.35 cm RMS / 3.59 cm p95 / 15.6 s
    #   weight 14: influence 1.44, CTE 4.45 cm RMS / 9.99 cm p95 / 12.2 s
    # Influence is magnitude, not correctness: 14 looks more conventionally balanced but
    # cuts the curve badly. The precision-first requirement therefore keeps 23.
    #
    # [HMGICS Alex 17Sep] 23 -> 45, with PathFollow 5.0 -> 3.5 and curvature_decel 0.3.
    # Offline, real MPPI on the Gazebo route (2 x 90 deg arcs R 2.06 m, reverse), 6 seeds,
    # max CTE: arc 15.0 -> 3.2 cm, curve entry 10.0 -> 1.3, exit 6.4 -> 2.5, straight
    # 1.3 -> 0.7; route time 37.3 -> 33.7 s (goal-yaw fix included). Arc n_eff 37 -> 136.
    # PathFollow kept at 5.0 with this weight was not robust (one seed cut 10.8 cm, arc
    # n_eff 27). Re-check live: CTE by section with tools/analyze_wz.py.
    cost_weight: 30.0 ## 18Sep Alex  ### original 45.0

    Source check. The comment’s measurements pair this weight with PathFollowCritic 3.5 at 45 (17 Sep). It was lowered to 30 on 18 Sep with PathFollow unchanged, and no comment records a measurement of the 30 / 3.5 pair now in the file.

    See alsoFollowPath.PathFollowCritic.cost_weight

  4. FollowPath.PathAlignCritic.max_path_occupancy_ratio

    0.10

    default 0.07

    The critic stands down when more than this fraction of the path is in occupied space, so obstacle avoidance can win.

  5. FollowPath.PathAlignCritic.trajectory_point_step

    4

    same as the default

    Scores every n-th rollout point.

  6. FollowPath.PathAlignCritic.threshold_to_consider

    1.4m

    default 0.5

    The critic stops scoring once the goal is nearer than this, by remaining path or remaining motion, whichever is larger.

    # threshold_to_consider: 0.15
    threshold_to_consider: 1.4

    See alsoFollowPath.TerminalGoalCritic.activation_full

  7. FollowPath.PathAlignCritic.offset_from_furthest

    4

    default 20

    A GATE, not an offset: the critic skips the cycle unless the furthest path point any rollout reached is at least this index.

    # NOTE: this is a GATE, not an offset: the critic returns early when
    # furthest_reached_path_point < offset_from_furthest. That index was measured
    # oscillating around 20 at 10 Hz, so at 20 a weight-24.8 term switched fully on and
    # off every control cycle. Keep it well below the usual value of furthest.
    offset_from_furthest: 4
  8. FollowPath.PathAlignCritic.use_path_orientations

    false

    same as the default

    Also score heading against the path’s orientations.

CurvatureOnlyPathAlignCritic

mppi::critics::CurvatureOnlyPathAlignCritic

Switched off in this file (enabled: false).

An HMGICS critic that adds only a curvature-weighted extra alignment cost, spread along the path either side of each curved point. Disabled here.

  • hmgics_navigation2/nav2_mppi_controller/src/critics/curvature_only_path_align_critic.cpp
  1. FollowPath.CurvatureOnlyPathAlignCritic.enabled

    false

    default true

    Switches the critic on or off without removing it from critics.

  2. FollowPath.CurvatureOnlyPathAlignCritic.cost_power

    1

    same as the defaultno effect here

    Power applied to the weighted cost. 1 = linear.

    No effect here. CurvatureOnlyPathAlignCritic is switched off by its own enabled: false.

  3. FollowPath.CurvatureOnlyPathAlignCritic.cost_weight

    8.0

    same as the defaultno effect here

    Weight of the curvature-weighted alignment term.

    # Measured raw-unit spread 0.0339, so std/T = cost_weight * 0.0339 / 0.3.
    # At 3.0 this is 0.34, close to the ~0.2 floor below which a critic stops
    # influencing the result at all. 8.0 -> 0.90.
    cost_weight: 8.0

    No effect here. CurvatureOnlyPathAlignCritic is switched off by its own enabled: false.

  4. FollowPath.CurvatureOnlyPathAlignCritic.max_path_occupancy_ratio

    0.07

    same as the defaultno effect here

    As for PathAlignCritic.

    No effect here. CurvatureOnlyPathAlignCritic is switched off by its own enabled: false.

  5. FollowPath.CurvatureOnlyPathAlignCritic.trajectory_point_step

    2

    default 4no effect here

    Scores every n-th rollout point.

    No effect here. CurvatureOnlyPathAlignCritic is switched off by its own enabled: false.

  6. FollowPath.CurvatureOnlyPathAlignCritic.threshold_to_consider

    0.5m

    same as the defaultno effect here

    Stops scoring near the goal.

    No effect here. CurvatureOnlyPathAlignCritic is switched off by its own enabled: false.

  7. FollowPath.CurvatureOnlyPathAlignCritic.offset_from_furthest

    8

    default 20no effect here

    Same gate as PathAlignCritic’s.

    No effect here. CurvatureOnlyPathAlignCritic is switched off by its own enabled: false.

  8. FollowPath.CurvatureOnlyPathAlignCritic.use_path_orientations

    false

    same as the defaultno effect here

    Also score heading against the path’s orientations.

    No effect here. CurvatureOnlyPathAlignCritic is switched off by its own enabled: false.

  9. FollowPath.CurvatureOnlyPathAlignCritic.curvature_gain

    2.0

    default 3.0no effect here

    Extra alignment weight per unit curvature.

    No effect here. CurvatureOnlyPathAlignCritic is switched off by its own enabled: false.

  10. FollowPath.CurvatureOnlyPathAlignCritic.max_curvature

    1.51/m

    default 2.0no effect here

    Curvature is clamped here before the gain applies.

    No effect here. CurvatureOnlyPathAlignCritic is switched off by its own enabled: false.

  11. FollowPath.CurvatureOnlyPathAlignCritic.max_curvature_extra

    2.0

    default 3.0no effect here

    Cap on the extra weight a point can carry.

    No effect here. CurvatureOnlyPathAlignCritic is switched off by its own enabled: false.

  12. FollowPath.CurvatureOnlyPathAlignCritic.curvature_influence_distance

    2.4851m

    default 0.0no effect here

    Arc length either side of a curved point its weight is spread over, tapering to zero, so the run-out after a corner discriminates between rollouts.

    No effect here. CurvatureOnlyPathAlignCritic is switched off by its own enabled: false.

PathFollowCritic

mppi::critics::PathFollowCritic

Rewards progress: distance from each rollout’s end to a point offset_from_furthest poses beyond the furthest point any rollout reached. The only critic that makes standing still expensive.

Opening comment in the file

## Default value
# cost_weight: 5.0
# offset_from_furthest: 5
  • hmgics_navigation2/nav2_mppi_controller/src/critics/path_follow_critic.cpp
  1. FollowPath.PathFollowCritic.enabled

    true

    same as the default

    Switches the critic on or off without removing it from critics.

  2. FollowPath.PathFollowCritic.publish_debug_markers

    false

    same as the defaultdiagnostic

    Publishes the carrot and rollout diagnostics on path_follow_debug_markers. Static.

    publish_debug_markers: false # [NOTE] Disable this during deployment
  3. FollowPath.PathFollowCritic.cost_power

    1

    same as the default

    Power applied to the weighted cost. 1 = linear.

  4. FollowPath.PathFollowCritic.cost_weight

    3.5

    default 5.0

    Weight of the progress term. Too low and standing still is cheapest; too high and it cuts corners, because it only constrains the rollout’s END point.

    # The only critic that rewards making progress along the path, so it must stay strong
    # enough that standing still is never the cheapest option. But it only constrains the
    # trajectory's END POINT -- it says nothing about the shape in between, so letting it
    # dominate makes the trajectory cut corners and drift off the path.
    #
    # Clean node18 -> node12 -> node11 live A/B (same route and old loaded PathAlign binary):
    #   weight 4.5: 5.09 cm RMS / 10.95 cm p95 / 9.30 s
    #   weight 3.0: 3.90 cm RMS /  8.42 cm p95 / 11.25 s
    #   weight 1.7: 2.25 cm RMS /  3.64 cm p95 / 15.75 s
    # The stated requirement prioritises sticking to the path, so keep 1.7. A post-restart
    # full-reference run measured 2.35 cm RMS / 3.59 cm p95 at PathAlign=23; the time remained
    # about 15.6 s, so repeat both directions before accepting the throughput trade-off.
    # [HMGICS Alex 17Sep] 5.0 -> 3.5 together with PathAlign 45 (see there). 1.7 tracks best
    # (max CTE 5.3 cm with PathAlign 23) but is ~25% slower; 3.5 / 45 is tighter and faster.
    cost_weight: 3.5

    See alsoFollowPath.PathAlignCritic.cost_weight

  5. FollowPath.PathFollowCritic.offset_from_furthest

    5

    default 6

    Path points beyond the furthest reached point that the carrot is placed at.

  6. FollowPath.PathFollowCritic.threshold_to_consider

    1.4m

    same as the default

    The critic stops once the goal is nearer than this, where TerminalGoalCritic is at full authority.

    See alsoFollowPath.TerminalGoalCritic.activation_full

PathAngleCritic

mppi::critics::PathAngleCritic

Penalises heading away from a target point further along the path, but only when the robot’s angle to that point exceeds max_angle_to_furthest. A corrective term for large misalignment, not a tracking term.

Opening comment in the file

## Default value
# offset_from_furthest: 4
# threshold_to_consider: 0.5
# max_angle_to_furthest: 1.0
# The only critic that reads trajectory yaws. Without it, at v ~ 0 every remaining
# critic gives an identical trajectory for w = -1.0 and w = +1.0, so the cost is flat
# in angular velocity and the robot has no gradient to rotate itself out of a stall.
  • hmgics_navigation2/nav2_mppi_controller/src/critics/path_angle_critic.cpp
  1. FollowPath.PathAngleCritic.enabled

    true

    same as the default

    Switches the critic on or off without removing it from critics.

  2. FollowPath.PathAngleCritic.cost_power

    1

    same as the default

    Power applied to the weighted cost. 1 = linear.

  3. FollowPath.PathAngleCritic.cost_weight

    2.0

    default 2.2

    Weight of the angle term. Its cost is in radians, so small weights already matter.

    # Corrective, not tracking -- keep low. Its cost is an angle in radians and the batch
    # terminal-yaw spread is ~0.3-0.5 rad, so 2.0 already lands at std/T ~1.3-2.2.
    # Do NOT raise above ~3: at 5 it saturates the softmax and drives the oscillation.
    cost_weight: 2.0
  4. FollowPath.PathAngleCritic.offset_from_furthest

    4

    same as the default

    Path points beyond the furthest reached point that the target is placed at.

    # The gate below is evaluated once at the robot's CURRENT pose, so this critic is
    # all-on or all-off for the whole batch and behaves as a relay with no hysteresis --
    # the oscillation source. Pushing the target point further out (12 -> 25 indices, so
    # ~0.6 m -> ~1.25 m past furthest_reached_path_point at 0.05 m path density) makes the
    # bearing far less sensitive to that index's jitter, which is what chatters the relay.
    offset_from_furthest: 4 #### 12

    Source check. The comment argues for pushing the target further out, "12 -> 25 indices", to quiet the relay. The value is 4, which is the stock default, and the inline note records 12 as an earlier value. The argument above it describes a setting the file no longer has.

  5. FollowPath.PathAngleCritic.threshold_to_consider

    0.5m

    same as the default

    Switches off within this remaining path length.

  6. FollowPath.PathAngleCritic.max_angle_to_furthest

    0.6rad

    default 0.785398

    The critic only fires when the robot’s angle to the target exceeds this. The test is made once, at the robot’s pose, so the whole batch is on or off together.

    # Do NOT widen to quiet the oscillation: at the measured stall the robot-to-target
    # angle was 0.68 rad, so at 0.6 the critic fires and at 0.8 it would never engage.
    max_angle_to_furthest: 0.6
  7. FollowPath.PathAngleCritic.mode

    1

    default 0

    0 prefers forward, 1 has no directional preference (either heading along the path is fine), 2 follows the path’s own orientations. 1 suits a bidirectional robot.

    mode: 1 #### 2: consider feasible path orientation

TwirlingCritic

mppi::critics::TwirlingCritic

Penalises the mean |wz| of each rollout, and switches off once the remaining path is inside the goal checker’s xy tolerance.

  • hmgics_navigation2/nav2_mppi_controller/src/critics/twirling_critic.cpp
  1. FollowPath.TwirlingCritic.enabled

    true

    same as the default

    Switches the critic on or off without removing it from critics.

  2. FollowPath.TwirlingCritic.cost_power

    1

    same as the default

    Power applied to the weighted cost. 1 = linear.

  3. FollowPath.TwirlingCritic.cost_weight

    2.0

    default 10.0

    Weight of the mean-|wz| penalty.

    cost_weight: 2.0 ## 24Sep Alex: 25.0

Everything on this page describes hmc_nav2_params_tuning_18Sep.yaml as it stood on 27 Sept 2026. A value changed in the file since then is not reflected here. The version log is the record of what the robot actually ran.