Skip to content

nav: physics in the navigator's energy (P5): grade, gravity, slope features, slip modulators, an energy route - #692

Merged
transfix merged 2 commits into
masterfrom
feat/nav-physics-energy
Oct 18, 2026
Merged

transfix merged 2 commits into
masterfrom
feat/nav-physics-energy

Conversation

@transfix

Copy link
Copy Markdown
Owner

P5 of the vehicle physics roadmap: physics in the navigator's energy (doc 31 D9, H1–H5). A sim_world can take a height raster and a vehicle's limits. From them it gets terrain in its drive, its governors, its features and its routes. Without a terrain and without modulation gains, every tick is byte-identical to before.

Stacked on P4 (the plant seam), and opened once that merges.

The terrain

  • cvc/nav/terrain.h: terrain_build turns a height raster into three planes:

    • the slope (dz/dx, dz/dy);
    • the grade cost r̃_g = sin θ / sin θ_limit in [0, 1];
    • the infeasible cells: steeper than max_grade, or, with a tip ratio, than tip_margin × the static stability factor.

    Everything is per unit mass, so the trained coefficients keep their meaning.

  • sim_world::set_terrain(heights, config) builds the planes. set_terrain_config rebuilds them from the same heights. infeasible_entries() counts each agent's ticks with its footprint (rear axle or body discs) on an infeasible cell.

  • nav_plant::terrain_config_of(desc, mu) gives a vehicle's limits:

    • its climb is the lesser of tire grip and first-gear pull, with the converter's stall multiplication;
    • its SSF comes from the track and the CG height.

    The M1097A2 climbs 0.68 and tips past 1.23 (CG 0.742 m).

H1–H5

  • H1, the grade force and the hard barrier. The grade cost goes through material_build and the material force path: F = -lam_grade ∇r̃_g plus a material-style barrier off the infeasible cells (terrain_drive). With block_infeasible (the default) the infeasible cells also join every belief plane's occupancy, so the SDF barrier and the governors keep agents off them.

  • H2, gravity in the governors. The drive samples the slope under each agent:

    • the stopping governor plans with a_stop = max(min(a_max, a_max + g_along), 0.1 a_max). That is less braking downhill and never counts on gravity's help uphill, because the command never brakes harder than a_max. An earlier version that did count on it let the kinematic agent tunnel into a wall uphill;
    • the corner cap loses gravity's cross-slope pull;
    • the plant arm's achievable braking gets the same adjustment.

    On the Jolt plant the vehicle's driver model also feeds gravity along the ground forward. It takes the grade from the wheels' contact normals, not the chassis pitch, which biased it. The contact normal is saved and restored, so replays still match.

  • H3, the slope look-ahead feature. coef_feats(..., slope) appends the worst grade cost between the agent and its carrot, after grip and risk.

    • A net that takes it sets kFlagFeatSlope (bit 4) and needs format v3. A pre-v3 host refuses it instead of reading a 6-input slope net as a grip net.
    • has_mu() no longer claims a slope net.
    • The CUDA drive refuses slope nets for now.
  • H4, measured slip and load as modulators.

    • plant_state gains tire_use (the most used tire's combined slip over its peak; 0 below 2 m/s), load_transfer, pitch and roll; nav_plant measures them.
    • plant_modulation scales each agent's gamma past tire_onset / tip_onset, and its hard barriers' weight past tip_onset.
    • The gains belong to the plant (vehicle_plant::modulation(), nav_plant::set_modulation) and are 0 by default.
  • H5, an energy route cost. astar(..., cost, climb) and astar_query::climb charge each move per_metre × its rise. climb_weight(c_rr, cell_w) weighs a metre of rise as 1 / C_rr metres of flat. Downhill costs nothing extra, and the heuristic stays admissible.

Stats and the state tree

  • nav_stats gains infeasible_slope_steps per vehicle and total_infeasible_slope_steps per episode. They appear in the record, the JSON and cvcGL's publish_nav_stats only when a terrain is set (terrain_tracked).

  • sim_world binds its own settings (sim_world::pull / publish), as world_binding and nav_plant do. The code is in src/cvc/nav/sim_world_binding.cpp, kept out of sim_world.cpp so the state tree's platform headers never meet the drive's. Lengths, speeds and accelerations are in nav's world frame (metres), not the normalized one. Each knob is seeded from the world, applied on a change and ignored when invalid:

    • <prefix>.nav.{range_m, n_rays, fov_rad, sense_every, freeze_sense, l_occ, l_free, optimistic, p_thresh, band, ttl_s, reach_tol, sep_radius, sep_gain, min_gap, min_gap_iters}: sensing, belief, arrival and the crowd;
    • <prefix>.nav.vehicle.{vmax, a_max, a_lat_max, wheelbase, delta_max, track_width, k_steer, nsub, allow_reverse, rr, d_hat, body_rr, body_gain, mass, mu_lookahead, mu_probes};
    • <prefix>.nav.material.{lam_soft, lam_hard, lam_soft_scale, lam_hard_scale}, with a material;
    • <prefix>.nav.terrain.{max_grade, tip_ratio, tip_margin, sigma, lam_grade, lam_grade_hard, k_sharp, d_hat_m, block_infeasible, governors}, with a terrain; a change rebuilds its planes.

    publish() writes <prefix>.nav.settings.{rows, cols, min_x, ..., dt, agents, planes, plant}, nav.tick and the terrain's {active, gravity, limit, infeasible_cells}.

  • One metadata form. cvc/state/state_key_meta.h (state_key_meta, describe_state_key, describe_state_keys) is the key-description form world_binding used privately, now in core so nav uses it too. Physics' binding_meta.h aliases it.

  • The plant's keys.

    • nav_plant::pull reads nav_plant.modulation.{tire_onset, tire_gain, tip_onset, tip_gain}.
    • nav_plant::publish adds each vehicle's nav.{tire_use, load_transfer, pitch_rad, roll_rad}.
  • Every new key has a comment, access and unit, and every knob has a recommended min and max.

Gates

nav_plant_test runs on the Jolt plant and nav_terrain_test on the kinematic one. All runs are Windows MSVC with the Humvee.

Gate Measured
(1) Parity goldens Untouched without a terrain or modulation gains; the full suite passes (see below)
(2) Energy per km drops on a hilly route, the path growing under a fixed bound 12 m hill between start and goal. Plain: 135 m over it, 2081 kJ, 15403 kJ/km. With H1, H2 and H5: 194 m round it, 1518 kJ, 7826 kJ/km (bound 1.5 × the path)
(3) Zero entries into infeasible slope A 1.2-slope ridge across the way: 0 ticks on it, Jolt and kinematic

Also measured:

  • H2. Driving at a wall, 15 m out the agent does 6.39 m/s on the flat, 3.37 down a 15% grade and 6.39 up it. It stops 4.45–4.50 m short in every case.
  • H1. The steepest grade cost driven past a hill falls from 0.727 to 0.228.
  • H5. Over a 12 m hill the plain route is 140 m and climbs 11.99 m; the energy route is 194 m and climbs 0.24 m.
  • H4. Round a 30 m circle on μ 0.7 under a 0.9 g corner cap, mean tire use is 1.36 at 15.1 m/s without the modulator and 0.79 at 11.9 m/s with tire_gain 3.

Not in this PR

  • GRL-SNAM's Python twin of H1 and H5.
  • A coefficient net retrained on the slope feature.
  • H3 in the CUDA drive.

Test

  • nav_terrain_test (6), nav_binding_test (4) and nav_plant_test (13) pass.
  • The full Windows ctest: 4091/4095. Three failures are the known local ImageMagick coder lookups (RegistryKeyLookupFailed CoderModulesPath), fixed on master by image: find ImageMagick's coders beside its own DLL; init it before every Magick++ use #673. The fourth is a timing test that fails under -j 8 load and passed five runs alone; it was StateClusterMembership.FailureDetectionSuspect on the last run and StateExecTypesTest.TimeBudgetChargesAStallAtMostASixteenthOfTheSecondHalf on the one before.
  • The two cvcGL files compile under MSVC (/Zs); cvcGL is not built on this box.
  • clang-format is clean.

Terrain enters the drive, the governors, the features and the routes
(doc 31 D9, H1-H5). Without a terrain and without modulation gains
every tick is unchanged.

- cvc/nav/terrain.h: the slope, the grade cost (sin over the sine of
  the vehicle's limit) and the infeasible cells from a height raster;
  sim_world::set_terrain / set_terrain_config / infeasible_entries;
  nav_plant::terrain_config_of gives a vehicle's climb and SSF.
- H1: the grade cost through the material pipeline as a soft force
  and a hard barrier; infeasible cells join the occupancy.
- H2: gravity along the path in the stopping governor (less braking
  downhill, never counting on help uphill) and across it in the corner
  cap; the Jolt driver model feeds the contact normals' grade forward.
- H3: the slope look-ahead feature (kFlagFeatSlope, format v3).
- H4: plant_state gains tire use, load transfer, pitch and roll; the
  plant's plant_modulation scales gamma and the hard barriers.
- H5: astar's climb cost, a metre of rise as 1 / C_rr metres of flat.

nav_stats gains infeasible_slope_steps when a terrain is set. The
terrain knobs (nav_terrain.*, cvc/physics/nav_terrain_binding.h), the
modulation knobs (nav_plant.modulation.*) and each vehicle's measured
state are in the state tree, every key described with a comment,
access, unit and recommended min/max.

On the Jolt Humvee: over a 12 m hill 7826 kJ/km against 15403 for a
path 1.44x as long; 0 ticks on a 1.2-slope ridge; round a 30 m circle
on mu 0.7 mean tire use 0.79 against 1.36 with the modulator.
sim_world::pull / publish put the world's settings in the state tree,
as world_binding and nav_plant do, under <prefix>.nav:
- the live knobs: sensing, belief, arrival and the crowd
  (set_live_knobs' set), veh_params' scalars under nav.vehicle, the
  material weights under nav.material and the terrain under
  nav.terrain (a change rebuilds its planes);
- publish(): nav.settings (the grid, the frame, dt, agents, planes,
  the plant), nav.tick and the terrain's telemetry.
Lengths, speeds and accelerations are in nav's world frame (metres),
not the normalized one. Each knob is seeded from the world, applied on
a change and ignored when invalid.

The physics-side terrain binding (cvc/physics/nav_terrain_binding.h)
is gone: sim_world binds its terrain itself. The key-description form
moves into core as cvc/state/state_key_meta.h, which physics'
binding_meta.h now aliases. nav_binding_test covers the seeding, the
units, a change reaching the drive, the material and terrain groups
and every key's metadata.
@transfix
transfix merged commit 37f09c3 into master Oct 18, 2026
15 checks passed
@transfix
transfix deleted the feat/nav-physics-energy branch October 18, 2026 17:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant