Nav2 Lyrical vs Kilted: MPPI Trajectory Validation and the New BT Control Nodes

Nav2 Lyrical vs Kilted: MPPI Trajectory Validation and the New BT Control Nodes

Nav2 Lyrical vs Kilted: MPPI Trajectory Validation and the New BT Control Nodes

The trajectory that Nav2’s MPPI controller calls “optimal” was never simulated, never scored, and never checked for collisions. It is an arithmetic average of a few thousand sampled control sequences, and averages of safe things are not automatically safe. Nav2 Lyrical closes that hole with a pluggable OptimalTrajectoryValidator that inspects the winning trajectory before a single velocity command leaves the node. If you have ever cranked an obstacle critic weight to a number you could not justify, this is the release that explains why you were doing it and gives you a better lever. The same release moves path handling out of every controller plugin, adds a behaviour-tree node that finally answers “how do I pause without cancelling”, and wraps the ROS 2 API surface in a Nav2-owned abstraction layer.

What this covers: the MPPI sampling and weighting mechanism in detail, why the weighted mean can be infeasible, exactly what the new validator gates and what happens when it fails, the new control nodes, and a line-by-line migration checklist from a Kilted configuration.

Context and Background

Nav2 is the navigation framework for ROS 2, and its Model Predictive Path Integral controller has been the default recommendation for new differential-drive and Ackermann robots since Humble. MPPI replaced the older DWB and TEB-style local planners for most teams because it tracks a path tightly when the lane is clear and deviates fluidly when it is not, all on CPU. It achieves that with aggressive vectorisation: the package notes that the implementation depends on AVX2 and MFMA instructions, which every processor since roughly 2013 provides.

The Nav2 Lyrical release was announced on the ROS Discourse on 25 August 2026 by maintainer Steve Macenski, targeting the ROS 2 Lyrical Luth distribution that shipped on 22 May 2026 as a long-term-support release on Ubuntu 26.04. We have covered the distro-level move separately in our ROS 2 Kilted to Lyrical migration guide, so this post stays inside Nav2 and inside the controller stack specifically.

The headline items for navigation teams are a new nav2_ros_common package, a substantial batch of MPPI work including open-loop control mode, asymmetric acceleration limits, plugin-based motion models, per-axis delay compensation and trajectory validators, new behaviour-tree control nodes, a vector object server, an adaptive goal checker, and a rewritten documentation site with per-distribution versioning. That is a wide release. The part that changes a production robot’s safety argument, though, is the validator, and understanding why requires understanding what MPPI actually returns.

Teams arriving from a classic sampling planner often assume the controller picks a trajectory. It does not. That single misconception is the root of most MPPI tuning frustration, and it is the reason the validator exists at all.

How MPPI Actually Chooses a Velocity Command

MPPI does not select the best sampled trajectory. It computes a softmax-weighted average of every sampled control sequence, smooths that average with a Savitzky-Golay filter, clamps it to the kinematic limits, and only then integrates it forward to produce the trajectory you see in RViz. The published command is the first entry of that averaged sequence.

Nav2 Lyrical MPPI control loop from noise sampling through softmax averaging to trajectory validation

Figure 1: The MPPI control loop in Nav2 Lyrical, with the validator inserted between trajectory integration and command publication.

The loop runs once per controller tick. It starts from the previous iteration’s optimal control sequence, perturbs it, forward-simulates the perturbations through the configured motion model, scores the resulting rollouts with critic plugins, collapses the scores into weights, and averages. The two boxes at the bottom are the Lyrical addition: the integrated optimal trajectory is handed to a validator plugin, and a rejection sends control back to the top of the loop instead of out to the wheels.

Sampling perturbs a sequence, not a pose

The optimiser holds a control sequence of time_steps entries, each a tuple of forward, lateral and angular velocity. With the defaults that is 56 steps at a model_dt of 0.05 seconds, a 2.8-second prediction horizon. Each tick, a noise generator draws batch_size perturbations from zero-mean Gaussians whose standard deviations are vx_std, vy_std and wz_std, and adds them to the stored sequence.

The defaults are 0.2 for the linear standard deviations and 0.4 for angular. Those numbers are the exploration budget. Set them too low and the batch clusters so tightly around last tick’s answer that the robot never discovers a way around a new obstacle. Set them too high and the angular command chatters, because the average is being pulled around by wild samples that the critics only penalise mildly.

batch_size defaults to 1000, and the documentation suggests 1000 at 50 Hz or 2000 at 30 Hz as reasonable operating points. The relationship is not subtle: doubling the batch doubles the vectorised work per tick. iteration_count defaults to 1, and the guidance is unambiguous — prefer a larger batch over more iterations, because each iteration re-runs the whole critic pass on the same horizon.

Rollouts are generated by a motion model plugin

In Lyrical the motion model is loaded through pluginlib rather than selected by a hard-coded enum. You set motion_model to a namespace name, and declare plugin inside that namespace. The three built-in types are mppi::DiffDriveMotionModel, mppi::OmniMotionModel and mppi::AckermannMotionModel, with the Ackermann model taking a min_turning_r parameter that defaults to 0.2 metres.

The motion model is what makes the rollouts physically meaningful. It enforces the vehicle’s constraints during forward simulation, so a differential-drive robot never produces a rollout with lateral motion and an Ackermann vehicle never produces one that violates its turning radius. This matters for the validator’s design: because the optimiser guarantees the result satisfies the motion model, a validator plugin does not need to re-check kinematic or dynamic feasibility. The documentation states this explicitly, which narrows the validator’s job to geometry and progress.

Before noise is applied, the optimiser pins the first entry of the control sequence to a dynamically reachable value given the current speed. When the controller period equals model_dt, the sequence is shifted each tick and the zeroth entry represents “now” rather than the command about to be sent. That shift is why the published command is taken from index one rather than index zero in that configuration.

Critics score rollouts, then the softmax averages them

Critic plugins each add a cost to every rollout in the batch. The stock set is ConstraintCritic, CostCritic, GoalCritic, GoalAngleCritic, PathAlignCritic, PathFollowCritic, PathAngleCritic and PreferForwardCritic, with ObstaclesCritic, TwirlingCritic and VelocityDeadbandCritic available as alternates or additions. Every critic takes a cost_weight and a cost_power, and the weights are the primary tuning surface.

Two critics handle obstacles and you pick one. CostCritic reads the inflated costmap cost directly, with a default cost_weight of 3.81, a critical_cost of 300.0 and a collision_cost of 1,000,000.0. ObstaclesCritic instead converts cost back into an estimated distance from the obstacle, splitting the behaviour into a critical_weight of 20.0 for near-collisions inside collision_margin_distance and a repulsion_weight of 1.5 for general preference toward open space.

After the critic pass, the optimiser adds a control-cost term derived from gamma divided by the square of each axis’s sampling standard deviation, which is the information-theoretic penalty on control energy. Then it subtracts the minimum cost across the batch for numerical stability, exponentiates the negated normalised costs divided by temperature, and normalises the result to sum to one. Those are the weights.

The final step is a matrix multiply: each axis of the new control sequence is the batch of sampled controls for that axis, weighted by the softmax vector. temperature defaults to 0.3 and controls how sharply the weighting concentrates. As it approaches zero the result approaches the single best sample; as it grows large the result approaches the unweighted mean of everything sampled, good and bad alike.

Two post-processing steps move the answer again

The weighted average is not the final control sequence. It passes through a Savitzky-Golay filter whose order is set by sgf_order, an integer that must be 1 or 2 and defaults to 2. First order oversmooths and makes it harder to fit through tight gaps; second order is the recommendation.

After smoothing, the sequence is clamped. Velocities are bounded by vx_max, vx_min, vy_max and wz_max, and successive entries are bounded by the acceleration limits ax_max, ax_min, ay_max, ay_min and az_max. Lyrical’s asymmetric acceleration support shows up here — ax_max defaults to 3.0 and ax_min to -3.0, but a heavy robot that brakes harder than it accelerates can now express that honestly.

Both steps modify the sequence after every critic has finished scoring. That is the crux of the argument in the next section.

Why the Optimal Trajectory Can Be Unsafe

Nothing in the pipeline ever evaluates the final control sequence. Critics score the sampled rollouts; the output is a smoothed, clamped average of those samples. An average of two feasible sequences is not necessarily feasible, and the classic counterexample is trivial to construct.

Why the softmax weighted mean of two safe MPPI samples can cross an obstacle neither sample touched

Figure 2: Averaging two low-cost evasive manoeuvres can produce a mean trajectory that does exactly what both samples avoided.

Picture a pillar directly ahead. Half the batch swerves left, half swerves right, and both halves score well because both clear the obstacle. The samples that drive straight into the pillar score terribly and receive near-zero softmax weight. But the weighted average of a left swerve and a right swerve is approximately straight ahead — the one thing every low-cost sample was trying not to do.

This is not a hypothetical failure of a toy configuration. It is the structural consequence of the algorithm, and it shows up whenever the cost landscape is bimodal. Narrow doorways with two viable approach angles, a corridor with a person standing in the middle, an obstacle straddling the path centreline, a junction where turning either way is acceptable — all of these are bimodal.

How teams papered over it before Lyrical

The standard mitigation was to make the cost landscape unimodal by brute force. Raise the obstacle critic’s weight until the straight-ahead samples are so catastrophically expensive that the swerving samples in one direction dominate the softmax entirely, collapsing the average onto a single mode. It works. It also produces a robot that refuses to enter lightly-costed space at all, slows to a crawl on entering any inflated region, and wobbles in narrow corridors chasing marginal cost differences.

The Nav2 documentation describes those exact symptoms in its tuning notes, attributing them to an obstacle critic weighted disproportionately against the path-following critics. The advice is to raise the path-follow weight to compensate, which is a balancing act between two numbers neither of which is addressing the real problem. What you actually wanted was a feasibility check on the output, and until Lyrical there was not one.

The second mitigation was to lean on the collision monitor. That works too, but it is a different safety layer with different latency, it operates on raw sensor data rather than the costmap, and reacting to an imminent collision by emergency-stopping is a worse outcome than never generating the command. A stop in a warehouse aisle is a recoverable annoyance; it is still an unplanned stop that a fleet-management system has to reason about.

What the validator changes about the argument

With a validator in the loop, the obstacle critic goes back to being what it should be: a shaping term that expresses preference. Preference for distance from obstacles, preference for the centre of a corridor, preference for smooth motion. Hard safety moves to a separate check with a binary verdict, which is a far easier thing to reason about in a safety case and a far easier thing to test.

The separation also makes the tuning tractable. When a critic weight has to do double duty as both a preference and a guarantee, you cannot lower it to fix wobble without weakening the guarantee. Once the guarantee lives elsewhere, the weight is free to be tuned purely for motion quality.

The OptimalTrajectoryValidator Plugin in Practice

The validator is a pluginlib interface. The optimiser declares TrajectoryValidator.plugin under the controller’s namespace, defaulting to mppi::DefaultOptimalTrajectoryValidator, loads the class through a pluginlib::ClassLoader for the base type mppi::OptimalTrajectoryValidator, and initialises it with the costmap, the parameter handler, the TF buffer and the optimiser settings.

The default implementation checks the final optimal trajectory for collisions. The documentation notes that additional validator plugins can be written to enforce maximum deviation from the path, an obstacle margin, progress being made, and similar constraints — the interface is deliberately open. The default takes two parameters: collision_lookahead_time, a double defaulting to 2.0 seconds, and consider_footprint, a boolean defaulting to false.

Those two parameters deserve thought rather than acceptance. collision_lookahead_time at 2.0 seconds against a default 2.8-second horizon means the last 0.8 seconds of the trajectory are not collision-checked. That is usually the right call, because the far end of an MPPI horizon is speculative and will be replaced next tick anyway. But if you have lengthened time_steps for a fast robot, check that the lookahead still covers the distance the robot actually commits to between ticks.

consider_footprint defaults to false, meaning the check uses the centre point cost and assumes a circular robot. For a long or rectangular platform — a tugger, a forklift, anything with a tail that swings — a point check on the centre is not a collision check. Turning this on costs compute, because an SE2 footprint check is substantially more expensive than a single cell lookup, but for a non-circular robot it is the difference between a real gate and a decorative one.

Configuring it

controller_server:
  ros__parameters:
    controller_frequency: 30.0
    FollowPath:
      plugin: "nav2_mppi_controller::MPPIController"
      time_steps: 56
      model_dt: 0.05
      batch_size: 2000
      vx_std: 0.2
      vy_std: 0.2
      wz_std: 0.4
      vx_max: 0.5
      vx_min: -0.35
      vy_max: 0.5
      wz_max: 1.9
      ax_max: 3.0
      ax_min: -3.0
      ay_max: 3.0
      ay_min: -3.0
      az_max: 3.5
      iteration_count: 1
      temperature: 0.3
      gamma: 0.015
      motion_model: "diff_drive"
      diff_drive:
        plugin: "mppi::DiffDriveMotionModel"
      visualize: false
      critic_index_to_visualize: 0
      regenerate_noises: false
      sgf_order: 2
      TrajectoryVisualizer:
        trajectory_step: 5
        time_step: 3
      TrajectoryValidator:
        plugin: "mppi::DefaultOptimalTrajectoryValidator"
        collision_lookahead_time: 2.0
        consider_footprint: false

Omitting the TrajectoryValidator block entirely is safe. The optimiser declares the plugin parameter if it has not been declared, so a Kilted configuration that says nothing about validation gets the default collision-checking validator automatically. This is the rare migration where the new safety feature is opt-out rather than opt-in.

What happens when validation fails

The validator’s validateTrajectory call returns one of three results, and the optimiser’s control loop branches on them. SUCCESS publishes the command. SOFT_RESET logs a warning reading “Soft reset triggered by trajectory validator” and marks the trajectory invalid, which drops control into the fallback path. FAILURE throws nav2_core::NoValidControl immediately with a message about a hard reset being triggered — no retry.

Nav2 Lyrical sequence from MPPI trajectory validation failure through soft reset to behavior tree recovery

Figure 3: Validation failure escalation, from soft reset through hard failure to the behaviour tree’s recovery branch.

The fallback path is shared with the existing critic failure flag, so it behaves the way MPPI veterans already expect. It resets the optimiser state — clearing the control sequence, the command history and the noise distribution, while deliberately preserving any active zone-based speed limit — and increments a retry counter. If the counter exceeds retry_attempt_limit, which defaults to 1, it throws NoValidControl with the message “Optimizer fail to compute path”. Otherwise it loops and resamples from a cleared state.

That default of 1 means you get exactly one retry before the controller reports failure to the behaviour tree. Resetting the control sequence is the point: the stale sequence is what seeded the bad average, so re-sampling around a cleared sequence genuinely explores different modes rather than re-deriving the same mean. Raising the limit buys more attempts at the cost of a longer stall before recovery behaviours engage, and each attempt is a full optimisation pass.

From the behaviour tree’s perspective a NoValidControl surfaces as a controller error code, which the standard navigation trees route into their recovery subtree — clear costmaps, spin, back up, wait. If validator-triggered soft resets start appearing in your logs at any meaningful rate, that is a signal about your critic tuning, not a reason to disable the validator.

PauseResumeController and the Other New BT Nodes

“How do I pause navigation without cancelling the goal” has been asked on Nav2 forums for years, and the honest answer was that you cancelled and re-sent, losing progress through a multi-goal sequence. Lyrical answers it properly with two control nodes contributed in the same pull request: PauseResumeController and PersistentSequence.

PauseResumeController has two input ports, pause_service_name and resume_service_name, both strings with no default. It advertises services under those names and holds a state machine. The node takes one mandatory child for the RESUMED state and three optional children: a PAUSED branch, an ON_PAUSE branch and an ON_RESUME branch.

Execution begins in RESUMED, ticking that child until it succeeds. A call to the pause service ticks ON_PAUSE to completion, then switches to PAUSED, where the PAUSED child is ticked until the state changes or it returns failure. A call to the resume service ticks ON_RESUME to completion and returns to RESUMED. The node returns success only when the RESUMED child succeeds, returns failure if any child fails, and returns running otherwise.

The failure semantics are worth pausing on. If an ON_PAUSE or ON_RESUME branch fails, the controller returns failure, halts, and resets to RESUMED. The documentation suggests wrapping those transition branches in a RetryUntilSuccessful node with a few attempts, because a transient failure during a pause transition should not tear down the whole tree. That is a practical pattern, not a framework limitation.

<PauseResumeController pause_service_name="/pause" resume_service_name="/resume">
    <!-- RESUMED branch -->

    <!-- PAUSED branch -->

    <RetryUntilSuccessful num_attempts="3">
        <!-- ON_PAUSE branch -->
    </RetryUntilSuccessful>

    <RetryUntilSuccessful num_attempts="3">
        <!-- ON_RESUME branch -->
    </RetryUntilSuccessful>
</PauseResumeController>

PersistentSequence is the companion piece and the reason pause is actually useful for multi-step missions. It exposes its current child index to the blackboard through a bidirectional port, so a sequence that was paused midway can resume at the step it was on rather than restarting. Put a PersistentSequence inside the RESUMED branch of a PauseResumeController and a paused pick-and-place mission picks up where it left off.

Two other nodes landed alongside. NonblockingSequence ticks every child in a sequence even when an earlier child returns RUNNING, which prevents a long-running child from blocking the rest of the sequence — useful for ticking a condition check in parallel with a navigation action. IsWithinPathTrackingBounds is a condition node that reports whether the robot is inside configured bounds of the path, which pairs with the new path-tracking-bounds measurement in the controller server to drive replanning decisions from the tree rather than from inside a plugin.

nav2_ros_common as an API Stability Story

The nav2_ros_common package is the least glamorous item in the release and possibly the most consequential for anyone maintaining downstream Nav2 plugins. It collects Nav2’s base lifecycle node, action server, service server, service client and node thread into one header-only package, and wraps the ROS 2 interfaces behind Nav2 types in the nav2:: namespace.

Concretely, nav2_util::LifecycleNode becomes nav2::LifecycleNode, and the node exposes factories that return Nav2 objects rather than rclcpp ones: create_client returns a nav2::ServiceClient, create_service a nav2::ServiceServer, create_publisher a nav2::Publisher, create_subscriber a nav2::Subscriber, create_action_server a nav2::SimpleActionServer, and create_action_client a nav2::ActionClient.

Today several of those are typedefs over the rclcpp equivalents, which makes the change look cosmetic. It is not. The point is that Nav2 now owns a chokepoint where it can add tracing, swap underlying APIs, control QoS profiles or enable lifecycle-aware subscriptions without touching every package in the stack. The documentation flags lifecycle support for subscriptions as a planned addition, which is exactly the kind of feature that would otherwise require another breaking change across every plugin.

QoS gets the same treatment. There are now named profiles — nav2::qos::StandardTopicQoS for reliable topics with a depth of 10, nav2::qos::LatchedPublisherQoS and nav2::qos::LatchedSubscriberQoS for latched data, and nav2::qos::SensorDataQoS for best-effort sensor streams. The guidance is to use these instead of rclcpp profiles, and specifically to avoid SystemDefaultsQoS, which is the setting most likely to produce a subtle incompatibility between two nodes that each looked correct in isolation.

The one API change that will bite during a port is argument ordering on subscriptions. QoS is now optional and has moved to the end, so a subscription is (topic, callback, QoS) rather than (topic, QoS, callback). Publishers that pass an explicit QoS profile need no change, though a publisher constructed with a bare depth must now state a policy explicitly. Service callbacks also gain a third placeholder, because the rmw_request_id_t header is now passed through.

Trade-offs, Gotchas, and What Goes Wrong

The controller period and model_dt relationship is a hard constraint, not a suggestion. If the controller period is greater than model_dt the optimiser throws a ControllerException at configuration time with a message telling you to set them equal. If the period is less than model_dt you get a warning. Only exact equality enables control sequence shifting, which changes which index of the sequence is published — a detail that silently alters behaviour if you change controller_frequency without revisiting model_dt.

Dynamic parameter updates to the kinematic limits are now rejected while a speed limit is active. The optimiser installs a guard on vx_max, vx_min, vy_max and wz_max that fails the update with a message telling you to clear the speed limit first. If you have tooling that pushes velocity limits at runtime while also using speed-limit costmap filters, that tooling will start getting rejections it did not get on Kilted.

Per-axis delay compensation is a genuinely useful feature with a documentation wrinkle. The parameters shift the control sequence by the delay divided by model_dt and replay recently published commands across the delay window, which is the right way to handle actuator latency. The configuration guide’s heading for the angular axis reads model_delay_wx, while the optimiser source reads the key model_delay_wz — if you set the axis delay and see no effect, that mismatch is the first thing to check, and the forward and lateral keys are unambiguous.

Enabling visualisation is expensive enough to change your control rate. The guide is blunt that publishing candidate trajectories can slow the controller substantially, giving 1,000 batches of 56 points at 30 Hz as an example of how much data that is. Treat visualize as a bench tool. The same applies to publish_critics_stats, which the documentation explicitly says should not be enabled for general runtime use.

Finally, the validator is not a substitute for a collision monitor. It checks the planned trajectory against the costmap, so it inherits every limitation of the costmap — sensor blind spots, stale observations, an inflation radius that does not match reality. A dynamic obstacle that enters the robot’s path between the costmap update and the next controller tick is invisible to the validator and visible to the collision monitor. Run both.

Practical Recommendations

Start the migration by deleting rather than adding. The six path-handling parameters removed from MPPI — transform_tolerance, prune_distance, max_robot_pose_search_dist, enforce_path_inversion, inversion_xy_tolerance and inversion_yaw_tolerance — now live in a PathHandler block at the controller server level, where nav2_controller::FeasiblePathHandler accepts prune_distance, enforce_path_inversion, enforce_path_rotation, inversion_xy_tolerance, inversion_yaw_tolerance, minimum_rotation_angle and reject_unit_path. Move the values across, do not retype them from memory.

Then add the validator block explicitly even though the default applies without it. Writing it into the config makes the safety check visible to the next engineer reading the file, and gives you a place to put a comment explaining the consider_footprint decision. For any non-circular robot, turn footprint checking on and measure the resulting control-loop time before shipping.

Nav2 Lyrical migration map showing which Kilted MPPI parameters move, which are new, and which keep working

Figure 4: What a Kilted Nav2 configuration needs changed for Lyrical, and what carries over untouched.

The migration checklist, in order:

  • Delete the six removed path-handling parameters from every MPPI block and add a PathHandler block to the controller server.
  • Add a TrajectoryValidator block with an explicit plugin, collision_lookahead_time and consider_footprint.
  • Remove any explicit bond_heartbeat_period: 0.1 override; the default moved to 0.25 seconds and is now also set on the lifecycle manager.
  • Convert motion model selection to the plugin form, declaring plugin inside the motion model’s namespace.
  • Rebuild every custom plugin against nav2::LifecycleNode, switching to the create_* factories and the nav2::qos profiles.
  • Update setPlan overrides to newPathReceived, and add the transformed plan and goal arguments to computeVelocityCommands and isGoalReached.
  • Re-tune obstacle critic weights downward now that hard safety has moved to the validator, and verify on your hardest bimodal scenario.
  • Leave the critic list, weights, velocity limits and acceleration limits alone unless a specific behaviour justifies changing them.

Frequently Asked Questions

Is the MPPI optimal trajectory actually collision-free?

Not inherently, and not before Lyrical. The optimiser produces a softmax-weighted average of all sampled control sequences, then smooths and clamps it. No critic ever scores that averaged result, so when the cost landscape has two good modes — swerve left, swerve right — the average between them can pass through the obstacle both modes avoided. The OptimalTrajectoryValidator in Nav2 Lyrical exists specifically to check the averaged result before the command is published.

Do I need to change my config to get the trajectory validator?

No. The optimiser declares the TrajectoryValidator.plugin parameter if it has not already been declared and defaults it to mppi::DefaultOptimalTrajectoryValidator, so an unmodified Kilted MPPI block gets collision validation automatically on Lyrical. You should still add the block explicitly, because the default uses a centre-point check rather than the full footprint, and a long or rectangular robot needs consider_footprint set to true to get a meaningful check.

What happens when the validator rejects a trajectory?

It depends on the result code. A SOFT_RESET clears the optimiser state and resamples, up to retry_attempt_limit times, which defaults to one retry. A FAILURE throws nav2_core::NoValidControl immediately without retrying. Either escalation path eventually surfaces to the behaviour tree as a controller error code, which the standard navigation trees route into their recovery subtree — clearing costmaps, spinning, or backing up.

How do I pause Nav2 without cancelling the goal?

Use the PauseResumeController behaviour-tree control node added in Lyrical. It advertises pause and resume services named by its pause_service_name and resume_service_name input ports, and holds RESUMED and PAUSED states with optional ON_PAUSE and ON_RESUME transition branches. Pair it with a PersistentSequence inside the RESUMED branch so a paused multi-step mission resumes at the step it was on rather than restarting from the beginning.

Should I lower my obstacle critic weight after migrating?

Probably, if you raised it to suppress the averaging problem rather than to express a genuine preference. Symptoms of an over-weighted obstacle critic include refusing to enter lightly-costed space, slowing sharply on crossing from free space into an inflated region, and wobbling in narrow corridors. With hard collision checking moved to the validator, the critic weight is free to be tuned for motion quality alone. Re-verify on your hardest scenario after each change.

What does nav2_ros_common break in my custom plugin?

All plugins must now use nav2::LifecycleNode in place of rclcpp_lifecycle::LifecycleNode or nav2_util::LifecycleNode, and should create interfaces through the node’s create_* factories. The subscription signature reordered to put the optional QoS last, service callbacks gained a third placeholder for the request header, and the controller interface replaced setPlan with newPathReceived while adding the transformed plan and goal pose to computeVelocityCommands.

Further Reading

By Riju — about

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *