Skip to content

[Newton] Add Rough terrain locomotion Part 1: Foundation + quadrupeds on Newton#5248

Merged
kellyguo11 merged 8 commits into
isaac-sim:developfrom
hujc7:jichuanh/rough-terrain-all-envs-pr
Apr 26, 2026
Merged

[Newton] Add Rough terrain locomotion Part 1: Foundation + quadrupeds on Newton#5248
kellyguo11 merged 8 commits into
isaac-sim:developfrom
hujc7:jichuanh/rough-terrain-all-envs-pr

Conversation

@hujc7
Copy link
Copy Markdown
Collaborator

@hujc7 hujc7 commented Apr 13, 2026

Description

Enables Newton rough-terrain locomotion training on all locomotion velocity envs (Go1, Go2, A1, Anymal-B/C/D, H1, Cassie, Digit, G1) on top of @ooctipus's Anymal-D foundation work, cherry-picked from PR #5225.

Why this PR exists

PR #5225 (Octi's draft) added Newton support for Anymal-D rough terrain. The other 9 locomotion envs were left out of scope. Octi is away and his PR has CI failures, so per maintainer guidance (Kelly Guo's comment) the required changes from #5225 are cherry-picked here so the rough-terrain stack can move forward without depending on his still-open WIP PR.

Dependencies

1. Cherry-pick from #5225 (614ea2dbb74)

Commit authored by Octi Zhang with Co-authored-by trailer. Subset of #5225 — what's kept and dropped:

# Item Status Reason
1 anymal_d/rough_env_cfg.py Anymal-D Newton config KEPT (then hoisted into shared parent in commit 2 below) Defines the Newton physics shape used for rough terrain
2 velocity_env_cfg.py — hoist physics_material, add_base_mass, base_com startup events into shared EventsCfg KEPT All envs need them
3 base_com guard preset(default=..., newton=None) KEPT Ablation A4 on #5225 (posted 2026-04-17): without the gate, 99.99% of episodes terminate from body_lin_vel runaway on Newton. Upstream Newton fix newton-physics/newton#2332 will let us drop the guard once it ships
4 velocity_env_cfg.pycollider_offsets startup event DROPPED (a) Greptile P1 on #5225: PhysX-only root_view methods, would AttributeError on Newton without a guard. (b) Ablation A3: clobbers the 1cm shape margin set by RoughPhysicsCfg (event resets gap = max(0, contact_offset − margin) = 0). Removing it gives +3.71 reward on Anymal-D Newton (+16.38 vs A0 baseline +12.47)
5 terminations.pybody_lin_vel_out_of_limit / body_ang_vel_out_of_limit + __init__.pyi exports DROPPED Were a NaN guard for the Newton body_lin_vel runaway when base_com was unguarded. With the preset(newton=None) gate (item 3), the runaway no longer occurs and the guards are unused
6 terrain_generator.py subdivided flat-grid border DROPPED Was a workaround for Newton triangle-collision failures on the box-primitive border. Newton has since improved triangle handling, so the workaround is no longer needed

2. New work — 2a532d1f745

2.1 NewtonShapeCfg + checked_apply wiring

The single most important Newton setting for rough terrain is shape margin. Without a nonzero margin, non-Anymal-D robots fail to learn stable contact on triangle-mesh terrain. The previous NewtonManager.create_builder only set gap = 0.01 and left margin at Newton's upstream default of 0.0.

This PR adds NewtonShapeCfg (the Isaac Lab wrapper) exposing margin and gap, and forwards it onto Newton's upstream ShapeConfig via checked_apply from PR #5365:

@configclass
class NewtonShapeCfg:
    margin: float = 0.0
    gap: float = 0.01

# in NewtonCfg
default_shape_cfg: NewtonShapeCfg = NewtonShapeCfg()

# in NewtonManager.create_builder
shape_cfg = cfg.default_shape_cfg if isinstance(cfg, NewtonCfg) else NewtonShapeCfg()
checked_apply(shape_cfg, builder.default_shape_cfg)

RoughPhysicsCfg opts in to default_shape_cfg=NewtonShapeCfg(margin=0.01).

2.2 Hoist RoughPhysicsCfg into shared base

Octi's per-env Anymal-D RoughPhysicsCfg (MJWarp solver + collision pipeline) is hoisted into LocomotionVelocityRoughEnvCfg.sim so every rough-terrain env inherits identical Newton physics. Per-env files become minimal robot-only deltas.

2.3 Per-env Newton-only tweak

  • Go1: leg armature preset for joint stability on lightweight quadruped.

2.4 Mass randomization rewrite

Replace EventsCfg.add_base_mass's additive (-5, 5) kg default with multiplicative (1/1.25, 1.25) log-uniform scale (operation="scale", distribution="log_uniform"). Scale-invariant across robot sizes, geometric mean 1.0, no per-robot kg overrides needed.

Per-robot ablation, Newton, 1500-iter (small quadrupeds early-aborted at iter 300):

# Robot new log-uniform old additive baseline ratio verdict
1 A1 (iter 300) 10.00 3.25 3.08× pass — driven by bias removal (old (-1, 3) had +10% mean bias on A1's 10 kg base)
2 Go1 (iter 300) 22.29 16.30 1.37× pass
3 Anymal-B (iter 300) 12.47 10.92 1.14× pass
4 Anymal-C (iter 300) 14.64 12.31 1.19× pass
5 Go2 @ 1499 24.71 18.58 1.33× pass
6 Anymal-D @ 1499 16.09 15.62 1.03× pass
7 H1 @ 1499 (biped) 24.02 23.58 1.02× pass
8 Cassie @ 1499 sym25 14.15 23.93 (mass rand off) 0.59× regression — fixed in dependent PR #5298 with (1.0, 1.25) heavier-only override

Cassie sensitivity is closed-loop Achilles + hip PD response: lighter-than-nominal pelvis destabilizes; heavier-only (1.0, 1.25) recovers 90% of the reward and pushes ep_len higher (932 vs 910 baseline). Sub-ablation table is in #5298.

Raw logs, checkpoints, and config snapshots preserved at ~/workspaces/data/2026-04-21_mass-rand-scale/ (per-robot <robot>_newton_1500.log, key.md, status.md, run.sh reproducer).

Versions

  • isaaclab_newton 0.5.21 → 0.5.22
  • isaaclab_tasks 1.5.24 → 1.5.25

Type of change

  • New feature (non-breaking).

Checklist

  • Pre-commit checks pass
  • CHANGELOG + extension.toml bumped on both isaaclab_newton and isaaclab_tasks
  • Co-author credit for @ooctipus on the cherry-pick commit
  • Ablation evidence cited in commit messages

@github-actions github-actions Bot added documentation Improvements or additions to documentation isaac-mimic Related to Isaac Mimic team labels Apr 13, 2026
@hujc7 hujc7 changed the title Enable all rough terrain envs on Newton backend [WIP]Enable all rough terrain envs on Newton backend Apr 13, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 13, 2026

Greptile Summary

This PR hoists shared Newton rough-terrain physics (RoughPhysicsCfg, startup events, NewtonShapeCfg) into the base LocomotionVelocityRoughEnvCfg, simplifying all 9 per-robot configs from multi-class hierarchies into single __post_init__ overrides. It also introduces checked_apply to forward Isaac Lab wrapper fields onto upstream Newton objects with an API-drift guard.

  • P0 blocker: TerminationsCfg.body_lin_vel references mdp.body_lin_vel_out_of_limit (line 327 of velocity_env_cfg.py), a function that does not exist anywhere in the codebase. The PR description (item 5) explicitly states this function was dropped; its presence in TerminationsCfg is contradictory and will cause AttributeError on instantiation of every rough-terrain environment.

Confidence Score: 1/5

Not safe to merge — every rough-terrain env will crash on instantiation due to a missing MDP function.

A single P0 bug (mdp.body_lin_vel_out_of_limit does not exist) breaks all 10 environments at config-resolution time. Everything else in the PR — checked_apply, NewtonShapeCfg, RoughPhysicsCfg hoist, per-env simplifications — is clean and well-structured.

source/isaaclab_tasks/isaaclab_tasks/manager_based/locomotion/velocity/velocity_env_cfg.py (line 327) and source/isaaclab_tasks/isaaclab_tasks/manager_based/locomotion/velocity/mdp/terminations.py (missing function definition).

Important Files Changed

Filename Overview
source/isaaclab_tasks/isaaclab_tasks/manager_based/locomotion/velocity/velocity_env_cfg.py Adds shared RoughPhysicsCfg preset, hoists startup events, and adds a body_lin_vel termination term — but the termination function mdp.body_lin_vel_out_of_limit does not exist, causing AttributeError on instantiation.
source/isaaclab/isaaclab/utils/configclass.py Adds checked_apply helper to forward dataclass fields onto an upstream object with an AttributeError guard; well-tested and correctly implemented.
source/isaaclab_newton/isaaclab_newton/physics/newton_manager_cfg.py Adds NewtonShapeCfg (margin + gap) and wires it into NewtonCfg.default_shape_cfg; clean addition.
source/isaaclab_newton/isaaclab_newton/physics/newton_manager.py create_builder now forwards NewtonShapeCfg onto builder.default_shape_cfg via checked_apply; falls back to wrapper defaults in non-Newton contexts.
source/isaaclab_tasks/isaaclab_tasks/manager_based/locomotion/velocity/config/go2/rough_env_cfg.py Simplified by removing per-env physics preset (now in shared base) and per-env EventsCfg subclasses; adds Newton armature preset for Go2.
source/isaaclab_tasks/isaaclab_tasks/manager_based/locomotion/velocity/config/digit/rough_env_cfg.py Removes DigitEventsCfg hierarchy; now mutates shared EventsCfg in post_init. Enables base_com randomization on PhysX path for Digit (previously disabled), which is a behavioral change vs other bipeds.
source/isaaclab_tasks/test/test_hydra.py Adds regression test for Go1 Newton armature preset; correct and clear.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[LocomotionVelocityRoughEnvCfg] --> B[sim: SimulationCfg\nphysics=RoughPhysicsCfg]
    A --> C[events: EventsCfg\nphysics_material\nadd_base_mass\nbase_com preset\nnewton=None]
    A --> D[terminations: TerminationsCfg\nbody_lin_vel ❌ missing fn]

    B --> E{preset resolve}
    E -- PhysX --> F[PhysxCfg\ngpu_max_rigid_patch_count]
    E -- Newton --> G[NewtonCfg\nMJWarpSolverCfg\nNewtonShapeCfg margin=0.01]

    G --> H[NewtonManager.create_builder]
    H --> I[checked_apply\nNewtonShapeCfg → builder.default_shape_cfg]

    A --> J[Per-robot subclass\n__post_init__ only]
    J --> K[Anymal B/C/D\nGo1/Go2/A1\nH1/G1/Cassie/Digit]
Loading

Reviews (3): Last reviewed commit: "Enable rough terrain on all locomotion e..." | Re-trigger Greptile

Comment on lines 22 to 38
class RoughPhysicsCfg(PresetCfg):
default = PhysxCfg(gpu_max_rigid_patch_count=10 * 2**15)
newton = NewtonCfg(
solver_cfg=MJWarpSolverCfg(
njmax=200,
nconmax=100,
cone="pyramidal",
impratio=1.0,
integrator="implicitfast",
use_mujoco_contacts=False,
),
collision_cfg=NewtonCollisionPipelineCfg(max_triangle_pairs=2_500_000),
num_substeps=1,
debug_mode=False,
)
physx = default

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 RoughPhysicsCfg duplicated across all 9 configs

The identical RoughPhysicsCfg class body is copy-pasted verbatim into every rough terrain config: anymal_b, anymal_c, anymal_d, a1, go1, go2, h1, g1, cassie, and digit. Changing Newton solver settings (e.g. raising njmax/nconmax for G1) will require editing 9+ files. Consider extracting this to a shared module (e.g. velocity_env_cfg.py or a new rough_physics_cfg.py) and importing it in each file.

Comment thread scripts/test_rough_envs_newton.sh Outdated
Comment on lines +54 to +61
if conda run -n env_isaaclab_develop python \
"${REPO_ROOT}/${TRAIN_SCRIPT}" \
--task "${TASK}" \
--num_envs "${NUM_ENVS}" \
--max_iterations "${MAX_ITERS}" \
--headless \
presets=newton \
> "${LOG_FILE}" 2>&1; then
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Hardcoded conda environment name

conda run -n env_isaaclab_develop hardcodes the author's local environment name. Anyone else running this script needs to edit the file. Consider reading the name from an environment variable with a fallback, or using ./isaaclab.sh -p (the project's standard wrapper) as the invocation instead of a bare python call.

@hujc7 hujc7 changed the title [WIP]Enable all rough terrain envs on Newton backend Enable all rough terrain envs on Newton backend Apr 14, 2026
@hujc7 hujc7 force-pushed the jichuanh/rough-terrain-all-envs-pr branch from 710e99d to 293cadc Compare April 14, 2026 05:09
Copy link
Copy Markdown

@isaaclab-review-bot isaaclab-review-bot Bot left a comment

Choose a reason for hiding this comment

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

🤖 Isaac Lab Review Bot

Summary

This PR enables all rough terrain environments on the Newton backend by (1) adding RoughPhysicsCfg(PresetCfg) with shared Newton solver settings to 10 robot env configs, (2) merging the StartupEventsCfg into the base EventsCfg, (3) fixing base_com body name mismatches for non-base robots, and (4) adding body velocity terminations for NaN/explosion detection. It also includes the XformPrimView → FrameView refactoring from PR #5179 (new BaseFrameView ABC, UsdFrameView, FabricFrameView, NewtonSiteFrameView), ray caster sensor spawner improvements, and a terrain border mesh fix for Newton compatibility.

The approach is sound — unifying events into the base class and using presets for backend-specific behavior is the right direction. However, several Newton-specific event customizations were silently removed during the refactoring, which constitutes a behavioral change that should be documented.

Design Assessment

Design is sound, with one structural concern. The move from per-robot *NewtonEventsCfg / *PhysxEventsCfg / *EventsCfg(PresetCfg) classes to a unified EventsCfg with preset() fields and __post_init__ overrides is a significant simplification. The FrameView abstraction hierarchy (BaseFrameViewUsdFrameView / FabricFrameView / NewtonSiteFrameView with FrameView factory) is well-designed. The main structural concern is RoughPhysicsCfg being copy-pasted identically across 10 files — this should be defined once and imported.

Findings

🟡 Warning: RoughPhysicsCfg is duplicated identically across 10 env config filessource/isaaclab_tasks/.../config/*/rough_env_cfg.py

All 10 robot env configs define an identical RoughPhysicsCfg(PresetCfg) class with the same Newton solver parameters (njmax=200, nconmax=100, pyramidal, implicitfast, max_triangle_pairs=2_500_000). If any Newton parameter needs tuning, all 10 files must be updated in sync. Consider defining this once in velocity_env_cfg.py (or a shared module) and importing it. Robot-specific overrides (e.g., if G1 eventually needs higher njmax for its NaN issue) can still be done via __post_init__.

🟡 Warning: Newton-specific event customizations were silently removed — Multiple env config files

The old per-robot *NewtonEventsCfg classes contained Newton-specific overrides that are no longer present in the new code:

  1. push_robot = None was set for all 6 non-Anymal robots (A1, Go1, Go2, G1, H1, Cassie) on Newton. Now push_robot is active on Newton for all robots.
  2. reset_robot_joints.params["position_range"] = (1.0, 1.0) constrained joint resets to exact default positions on Newton. This override is now removed.
  3. reset_base.params zeroed out velocity randomization on Newton. This override is now removed.

These are intentional simplifications backed by the parity test results (6/7 envs show parity), but they change training dynamics on Newton. Recommend documenting these behavioral changes in the PR description so downstream users know the Newton event configuration has changed.

🟡 Warning: base_com randomization is newly enabled for several robots on PhysXvelocity_env_cfg.py:183

Previously, A1, Go1, Go2, Digit, Cassie, G1, and H1 all set base_com = None in their PhysX event configs (disabling COM randomization). The new code enables it for A1, Go1, Go2, and Digit (with corrected body names) and keeps it disabled for Cassie, G1, H1 (bipeds). This is a training-behavior change for the quadrupeds — the PR description mentions the anymal_c parity gap is related to this, but the change applies to other robots too. Worth noting in the PR description.

🟡 Warning: New collider_offsets startup event added to all rough terrain envsvelocity_env_cfg.py:199

A new randomize_rigid_body_collider_offsets startup event is added to the base EventsCfg with contact_offset_distribution_params=(0.01, 0.01). This event was not present in any of the old configurations. While the identical min/max suggests it applies a fixed offset (not truly random), this is a new event that affects all rough terrain environments. Should be mentioned in the PR description / changelog.

🔵 Suggestion: body_lin_vel_out_of_limit checks all bodies every stepvelocity_env_cfg.py:298

The new body_lin_vel termination uses body_names=".*" which checks the linear velocity of every body in the articulation every simulation step. For robots with many bodies (e.g., Digit with 30+ bodies), this computes a norm per body per env. While 20 m/s is a reasonable threshold for catching NaN/explosions, consider whether checking only a subset of bodies (e.g., the base body) would be sufficient and more performant. The NaN check is the most valuable part — an Inf/NaN in any body would propagate to the base anyway.

🔵 Suggestion: Terrain border mesh generation could be pre-validatedsource/isaaclab/isaaclab/terrains/terrain_generator.py

The new _add_terrain_border implementation using subdivided grid strips is a good fix for Newton collision detection with large triangles. However, when horizontal_scale is very small relative to border_width, this could generate a very large number of vertices ((border_width/hs + 1)^2 per strip). Consider adding a sanity check or logging a warning when the generated mesh exceeds a threshold vertex count.

Test Coverage

  • New code: The FrameView refactoring has good test coverage with test_frame_view_contract.py (359 lines), backend-specific tests for Newton and Fabric, and a ray caster regression test (test_raycaster_offset_does_not_affect_pos_w).
  • Env configs: The env config changes are tested via the author's parity test (7 envs × 2 backends × 300 iterations), documented in the PR description. No automated regression tests for env config behavioral changes, but this is consistent with the project's testing approach.
  • Gaps: The removed Newton event customizations (push_robot, reset params) and new events (collider_offsets, body_lin_vel) lack unit tests, but these are config-level changes exercised by integration tests.
  • Feature PR: Coverage adequate? Yes for the framework changes, Partial for the env config behavioral changes.

CI Status

Only a labeler check is visible and passing. No lint, build, or test CI results are shown — this may be expected for this repo's CI configuration or the checks may be running elsewhere.

Verdict

Minor fixes needed

The core approach — unifying events, adding Newton solver configs, and the FrameView abstraction — is well-executed with demonstrated parity results. The main concerns are:

  1. The 10× code duplication of RoughPhysicsCfg (easy to fix by extracting to a shared module)
  2. Silent behavioral changes from removed Newton event customizations should be documented
  3. New events (collider_offsets, body_lin_vel) should be mentioned in the PR description

None of these are blocking, but addressing them would make the PR cleaner and help downstream users understand what changed. The parity test results provide confidence that the changes work correctly at a training level.

class A1EventsCfg(PresetCfg):
default = A1PhysxEventsCfg()
newton = A1NewtonEventsCfg()
class RoughPhysicsCfg(PresetCfg):
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Warning: Duplicated RoughPhysicsCfg — This exact class is copy-pasted identically across all 10 robot env configs. Consider defining it once in a shared module (e.g., velocity_env_cfg.py) and importing it. This would make future Newton solver parameter changes a single-file edit.

newton=None,
)

collider_offsets = EventTerm(
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Warning: New event not present in old configs. collider_offsets is a new startup event added to all rough terrain environments. The identical min/max (0.01, 0.01) makes this a fixed offset rather than randomized. Was this intentional? If so, worth noting in the PR description as a behavioral change.

func=mdp.illegal_contact,
params={"sensor_cfg": SceneEntityCfg("contact_forces", body_names="base"), "threshold": 1.0},
)
body_lin_vel = DoneTerm(
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 Suggestion: body_names=".*" checks every body in the articulation each step. For robots with many bodies this computes num_bodies × num_envs norms per step. Consider whether checking just the base body would be sufficient — NaN in any body will eventually propagate to the root state. The NaN detection is the most valuable part of this termination.

rewards: RewardsCfg = RewardsCfg()
terminations: TerminationsCfg = TerminationsCfg()
events: EventsCfg = MISSING
events: EventsCfg = EventsCfg()
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 Note: Changed from events: EventsCfg = MISSING to events: EventsCfg = EventsCfg(). This means per-robot env configs no longer must provide events — they inherit the base class defaults. Good simplification, but means any robot that previously relied on MISSING to force explicit configuration will now silently get defaults.

@github-actions github-actions Bot added the asset New asset feature or request label Apr 14, 2026
@hujc7 hujc7 force-pushed the jichuanh/rough-terrain-all-envs-pr branch from 7c44bec to 2c9b667 Compare April 16, 2026 22:42
AntoineRichard added a commit that referenced this pull request Apr 17, 2026
## Summary

Fix incorrect attribute name `contact_margin` → `gap` on Newton
`ShapeConfig` in `NewtonManager.create_builder()`.

Newton PR #1732 renamed `contact_margin` to `gap` (broad-phase AABB
expansion). The IsaacLab code was never updated, creating a dead
attribute that Python silently accepted. The intended 1 cm default shape
gap was never applied.

## Newton PR #1732 rename mapping

| Old field | New field | Semantics |
|-----------|-----------|-----------|
| `thickness` | `margin` | Shape surface extension (affects contact
forces) |
| `contact_margin` | **`gap`** | Broad-phase AABB expansion (detection
range only) |
| `rigid_contact_margin` | `rigid_gap` | Builder-level default gap
(already 0.1) |

## Timeline

| Date | Newton | IsaacLab | `contact_margin` valid? |
|------|--------|----------|------------------------|
| Feb 19 | pin: `51ce35e` (has `contact_margin`) | #4646 adds
`contact_margin = 0.01` | Yes |
| Feb 24 | **PR #1732 renames `contact_margin` → `gap`** | — | — |
| Mar 2 | pin updated to `v0.2.3` (after rename) | #4761 keeps
`contact_margin` | **No — broken** |
| Mar 9 | — | #4883 removes the line | Removed |
| Apr 13 | — | #4822 re-adds `contact_margin` | **No — still broken** |

## Ablation: gap vs margin

We conducted an ablation study to understand the impact. Note: `margin`
(shape surface
extension) is a different field from `gap` (broad-phase range). The
original code intended
to set `gap`, but setting `margin` also has a significant positive
effect on training for
rough terrain locomotion.

| Setting | `gap` | `margin` | Go1 Reward (300 iter) | Effect |
|---------|-------|----------|----------------------|--------|
| Dead field (baseline) | 0.1 (default) | 0.0 | ~1.0 | — |
| `gap=0.01` only | 0.01 | 0.0 | 0.66 | No training improvement |
| `margin=0.01` only | 0.1 (default) | 0.01 | 18.77 | Major improvement
|
| `gap=0.01` + `margin=0.01` | 0.01 | 0.01 | 16.54 | Similar to
margin-only |

**This PR fixes the correct field migration** (`contact_margin` →
`gap`). The `margin` setting
for rough terrain contact quality will be addressed separately in the
rough terrain env PR
(#5248) via a new `default_shape_margin` config field on `NewtonCfg`.

## Test plan

- [x] Verified `contact_margin` is not a field on `ShapeConfig` (Newton
1.1.0.dev0)
- [x] Verified `gap` is the correct replacement field per Newton PR
#1732
- [x] Confirmed by camevor (Newton developer)
- [x] Ablation study: gap alone doesn't change training, so existing
tests should pass

Co-authored-by: Antoine RICHARD <antoiner@nvidia.com>
@hujc7 hujc7 force-pushed the jichuanh/rough-terrain-all-envs-pr branch from 775ab26 to fdd2cbd Compare April 18, 2026 10:28
Copy link
Copy Markdown

@isaaclab-review-bot isaaclab-review-bot Bot left a comment

Choose a reason for hiding this comment

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

🤖 Isaac Lab Review Bot — Updated Review

Summary

This PR enables all rough terrain locomotion environments on the Newton physics backend through:

  1. FrameView Abstraction — Major refactoring from XformPrimView to a backend-dispatching FrameView factory with implementations for USD, Fabric (PhysX), and Newton (GPU-resident site transforms via Warp kernels)
  2. NewtonCfg.default_shape_margin — New config field for per-env shape collision margin control
  3. Go1/Go2 armature presets — Newton-specific armature values (0.02) for training stability
  4. RayCaster improvements — Uses new FrameView for transform tracking, removes complex physics view detection
  5. Terrain border mesh fix — Subdivided grid mesh instead of large box primitives for Newton/MuJoCo collision compatibility
  6. New termination functionsbody_lin_vel_out_of_limit and body_ang_vel_out_of_limit for detecting solver singularities

Architecture Assessment

The FrameView design is excellent. The abstraction hierarchy is well-structured:

BaseFrameView (ABC)
├── UsdFrameView (USD XformCache backend)
├── FabricFrameView (Fabric GPU arrays, PhysX)  
└── NewtonSiteFrameView (Warp kernels, Newton GPU state)

FrameView (factory) ─→ selects backend at runtime

The factory pattern in FrameView.__new__ correctly dispatches to the appropriate backend based on the active physics manager. Contract tests in test_frame_view_contract.py ensure behavioral consistency across backends.

Detailed Findings

🟢 Excellent: Contract test suite

The test_frame_view_contract.py (359 lines) defines invariants that all backends must satisfy:

  • World pose = parent + local offset
  • Local pose stability after parent moves
  • World pose tracking when parent moves
  • Indexed get/set operations
  • Set/get roundtrip verification

This is the right approach for ensuring backend parity.

🟢 Good: RayCaster offset handling

The new test test_raycaster_offset_does_not_affect_pos_w explicitly validates that the sensor offset doesn't leak into data.pos_w. This catches a subtle bug that existed in the old implementation.

🟡 Warning: Backward compatibility alias may hide issues

# source/isaaclab/isaaclab/sim/views/xform_prim_view.py
XformPrimView = FrameView

While this preserves backward compatibility, it means:

  1. Existing code using XformPrimView will silently start using the new factory
  2. The API differs (FrameView returns wp.array, old XformPrimView returned torch.Tensor)
  3. Consider adding a deprecation warning via __getattr__ or similar

🟡 Warning: Newton site discovery depends on USD path matching

In NewtonSiteFrameView.__init__, sites are discovered via USD regex matching, then body indices are looked up by parsing the body name from the USD path. If the USD hierarchy doesn't match the Newton model body naming, this could fail silently:

# newton_site_frame_view.py line ~280
body_name = site_parent.GetName()
# ... 
if body_name in body_name_to_idx:
    ...
else:
    logger.warning(f"Body '{body_name}' not found in Newton model for site {site_path}")

The warning-only approach is appropriate for flexibility, but the site will be attached to the world frame (body_index=-1) which may not be the intended behavior. Consider whether this should raise an error in strict mode.

🟡 Warning: Terrain border memory consumption

The new subdivided grid border mesh generation creates (width/hs + 1) * (length/hs + 1) vertices per strip. For a typical config with border_width=20 and horizontal_scale=0.05:

vertices per strip ≈ (20/0.05 + 1) * (length/0.05 + 1) = 401 * length_factor

This is significantly more vertices than the old box approach but necessary for Newton collision compatibility. Consider adding a note about memory implications in the docstring.

🔵 Suggestion: _gather_scales kernel is O(N × M)

The _gather_scales kernel iterates through all shapes to find ones matching the body:

for s in range(num_shapes):
    if shape_body[s] == bid and found == 0:
        out_scales[i] = shape_scale[s]
        found = 1

For large models with many shapes, this is O(num_sites × num_shapes). Consider building a body→first_shape index map during initialization for O(1) lookups. This is a minor optimization but could matter for complex robots.

🔵 Suggestion: Document quaternion convention

The code uses (w, x, y, z) quaternion ordering in some places and (x, y, z, w) in others. While the Warp kernels correctly handle the conversion, explicit documentation of the expected convention at API boundaries would help:

def get_world_poses(self, ...) -> tuple[wp.array, wp.array]:
    """...
    Returns:
        orientations: Quaternions in (w, x, y, z) order.  # ← Add this
    """

Test Coverage Assessment

Component Tests Coverage
FrameView contract test_frame_view_contract.py ✅ Excellent
Newton backend Contract tests + newton-specific ✅ Good
Fabric backend Contract tests + fabric-specific ✅ Good
RayCaster integration test_raycaster_offset_does_not_affect_pos_w ✅ Good
Termination functions None visible ⚠️ Missing
Terrain border mesh None visible ⚠️ Missing

The new termination functions (body_lin_vel_out_of_limit, body_ang_vel_out_of_limit) should have unit tests verifying they correctly detect NaN velocities and threshold violations.

CI Status

Only labeler check visible and passing. Full CI suite (tests, lint) status unknown.

Verdict

Approve with minor suggestions

This is a well-designed PR that brings significant improvements:

  1. ✅ Clean abstraction layer for multi-backend transform queries
  2. ✅ Proper contract testing ensures backend parity
  3. ✅ Newton-specific fixes (shape margin, border mesh) address real collision issues
  4. ✅ Armature presets enable Newton rough terrain training

The warnings above are non-blocking observations for potential improvement. The code is production-ready.


Reviewed commit: fdd2cbd

Copy link
Copy Markdown

@isaaclab-review-bot isaaclab-review-bot Bot left a comment

Choose a reason for hiding this comment

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

🤖 Isaac Lab Review Bot — Additional Review

Critical Bug Found

The previous review approved this PR, but I've identified a critical bug that will crash at runtime.

🔴 Bug: feet_slide crashes when asset_cfg.body_ids is default slice(None)

The change on line 87 of rewards.py introduces a regression:

body_vel = wp.to_torch(asset.data.body_lin_vel_w)[:, asset_cfg.body_ids[:contacts.shape[1]], :2]

When asset_cfg uses the default body_ids = slice(None), this will crash with:

TypeError: 'slice' object is not subscriptable

Reproduction:

s = slice(None)
s[:5]  # TypeError: 'slice' object is not subscriptable

Root cause: The PR description mentions this handles "Newton's ball-joint rod bodies (Digit)" but the implementation doesn't handle the common case where body_ids is a slice.

Suggested fix: Convert the slice to indices first, or check the type:

if isinstance(asset_cfg.body_ids, slice):
    # slice(None) means all bodies, so use all up to contact count
    body_ids = list(range(contacts.shape[1]))
else:
    body_ids = asset_cfg.body_ids[:contacts.shape[1]]
body_vel = wp.to_torch(asset.data.body_lin_vel_w)[:, body_ids, :2]

Verdict

Request Changes — The feet_slide bug will crash any locomotion env using the default asset_cfg for this reward. This must be fixed before merge.


Reviewed commit: fdd2cbd


body_vel = wp.to_torch(asset.data.body_lin_vel_w)[:, asset_cfg.body_ids, :2]
# Use sensor body count to slice asset velocities — ensures matching dimensions
# even when Newton reports duplicate body entries for closed-loop constraints.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 Bug: slice object is not subscriptable

When asset_cfg uses the default body_ids = slice(None), this line crashes with TypeError: 'slice' object is not subscriptable.

>>> slice(None)[:5]
TypeError: 'slice' object is not subscriptable

The comment mentions handling Newton's duplicate body entries, but this breaks the common case where no explicit body_ids are configured.

Suggested fix:

Suggested change
# even when Newton reports duplicate body entries for closed-loop constraints.
# Use sensor body count to slice asset velocities — ensures matching dimensions
# even when Newton reports duplicate body entries for closed-loop constraints.
if isinstance(asset_cfg.body_ids, slice):
# Default slice(None) means all bodies — just take the first N matching sensor count
body_ids = slice(contacts.shape[1])
else:
body_ids = asset_cfg.body_ids[:contacts.shape[1]]
body_vel = wp.to_torch(asset.data.body_lin_vel_w)[:, body_ids, :2]

@kellyguo11 kellyguo11 moved this to In review in Isaac Lab Apr 20, 2026
@isaaclab-review-bot isaaclab-review-bot Bot dismissed their stale review April 21, 2026 16:17

Dismissing: bot incorrectly used CHANGES_REQUESTED. Bot policy is COMMENT-only — never APPROVE or REQUEST_CHANGES.

hujc7 added a commit to hujc7/IsaacLab that referenced this pull request Apr 22, 2026
Restore base-mass randomization on H1 and Cassie that was disabled with
add_base_mass = None in their rough-terrain configs (pre-existing biped
convention from 2024-06 PR isaac-sim#444, later reinforced by PR isaac-sim#4165's Newton NaN
TODO). The parent PR isaac-sim#5248 switches the shared default to log-uniform scale
(1/1.25, 1.25), which is safer for bipeds than the old additive (-5, 5) kg
(effectively ±25% on H1's torso vs ±100% on Cassie's pelvis).

- H1 inherits the shared default (symmetric ±25% scale, body_names="torso_link").
- Cassie overrides to (1.0, 1.25) asymmetric heavier-bias: lighter-than-
  nominal pelvis destabilizes Cassie's closed-loop Achilles rod coupling
  and hip PD response, while heavier-than-nominal dampens dynamics.

## Ablation (Cassie, 1500-iter Newton, 4096 envs, seed 42)

| Variant                        | reward | ep len | vs disabled |
|--------------------------------|-------:|-------:|------------:|
| None (disabled, prior default) |  23.93 |    910 |     1.00x   |
| (1/1.25, 1.25) sym25           |  14.15 |    850 |     0.59x x |
| (1/1.10, 1.10) tight10         |  14.53 |    831 |     0.61x x |
| **(1.0, 1.25) heavier25**      | **21.50** | **932** | **0.90x** |

Tightening the range symmetrically did not help (tight10 ~ sym25) — the
lighter side is what destabilizes, not the magnitude. Restricting to
[1.0, 1.25] (never lighter, up to +25% heavier) preserves most of the
randomization benefit while avoiding the failure mode. Episode length
also improves (932 vs 910), indicating the randomized policy is actively
more stable during episodes — the lower aggregate reward comes from extra
dof-torque regularizer paid to handle heavier instances, not degraded
task completion.

## H1 (unchanged, 1500-iter Newton)

H1's larger base mass (≈15 kg torso) means ±25% on the default scale
(11-19 kg range) is well within the controller's robustness margin. H1
reward at iter 1499: 24.02 with mass rand on vs 23.58 with it disabled
(1.02x, essentially equal). Re-enabling provides sim-to-real robustness
at negligible training cost.
@hujc7 hujc7 force-pushed the jichuanh/rough-terrain-all-envs-pr branch from fa81247 to 60de194 Compare April 22, 2026 08:01
hujc7 added a commit to hujc7/IsaacLab that referenced this pull request Apr 22, 2026
Restore base-mass randomization on H1 and Cassie that was disabled with
add_base_mass = None in their rough-terrain configs (pre-existing biped
convention from 2024-06 PR isaac-sim#444, later reinforced by PR isaac-sim#4165's Newton NaN
TODO). The parent PR isaac-sim#5248 switches the shared default to log-uniform scale
(1/1.25, 1.25), which is safer for bipeds than the old additive (-5, 5) kg
(effectively ±25% on H1's torso vs ±100% on Cassie's pelvis).

- H1 inherits the shared default (symmetric ±25% scale, body_names="torso_link").
- Cassie overrides to (1.0, 1.25) asymmetric heavier-bias: lighter-than-
  nominal pelvis destabilizes Cassie's closed-loop Achilles rod coupling
  and hip PD response, while heavier-than-nominal dampens dynamics.

## Ablation (Cassie, 1500-iter Newton, 4096 envs, seed 42)

| Variant                        | reward | ep len | vs disabled |
|--------------------------------|-------:|-------:|------------:|
| None (disabled, prior default) |  23.93 |    910 |     1.00x   |
| (1/1.25, 1.25) sym25           |  14.15 |    850 |     0.59x x |
| (1/1.10, 1.10) tight10         |  14.53 |    831 |     0.61x x |
| **(1.0, 1.25) heavier25**      | **21.50** | **932** | **0.90x** |

Tightening the range symmetrically did not help (tight10 ~ sym25) — the
lighter side is what destabilizes, not the magnitude. Restricting to
[1.0, 1.25] (never lighter, up to +25% heavier) preserves most of the
randomization benefit while avoiding the failure mode. Episode length
also improves (932 vs 910), indicating the randomized policy is actively
more stable during episodes — the lower aggregate reward comes from extra
dof-torque regularizer paid to handle heavier instances, not degraded
task completion.

## H1 (unchanged, 1500-iter Newton)

H1's larger base mass (≈15 kg torso) means ±25% on the default scale
(11-19 kg range) is well within the controller's robustness margin. H1
reward at iter 1499: 24.02 with mass rand on vs 23.58 with it disabled
(1.02x, essentially equal). Re-enabling provides sim-to-real robustness
at negligible training cost.
Comment on lines +325 to +329
)
body_lin_vel = DoneTerm(
func=mdp.body_lin_vel_out_of_limit,
params={"max_velocity": 20.0, "asset_cfg": SceneEntityCfg("robot", body_names=".*")},
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P0 mdp.body_lin_vel_out_of_limit does not exist — every env will crash on instantiation

TerminationsCfg.body_lin_vel calls mdp.body_lin_vel_out_of_limit, but that function is not defined anywhere in the codebase. The local terminations.py only contains terrain_out_of_bounds, and neither the core isaaclab.envs.mdp.terminations module nor the lazy_export()-driven mdp.__init__ exposes a body_lin_vel_out_of_limit symbol.

The PR description (item 5) explicitly says this function was dropped from #5225, yet the code adds a DoneTerm using it. Every LocomotionVelocityRoughEnvCfg subclass will raise AttributeError: module 'mdp' has no attribute 'body_lin_vel_out_of_limit' as soon as the config is resolved. Either define the function in the local terminations.py, import it from the correct module, or remove the body_lin_vel termination term.

…eam dataclasses

When an Isaac Lab configclass mirrors an upstream library's dataclass
(for example NewtonShapeCfg → Newton's ShapeConfig), bare setattr
loops are fragile: if upstream renames or removes a field, every
write becomes a silent no-op (the failure mode PR isaac-sim#5289 fixed for
ShapeConfig.contact_margin → margin).

Add isaaclab.utils.checked_apply(src, target):
  - Iterates fields(src) — single source of truth for declared fields
  - Raises AttributeError if target lacks a declared field — failure
    surfaces at startup, not at runtime
  - One-line apply at the call site, no per-field setattr noise
@hujc7 hujc7 force-pushed the jichuanh/rough-terrain-all-envs-pr branch from cf53a59 to e32f9d8 Compare April 24, 2026 06:17
hujc7 added a commit to hujc7/IsaacLab that referenced this pull request Apr 24, 2026
Restore base-mass randomization on H1 and Cassie that was disabled with
add_base_mass = None in their rough-terrain configs (pre-existing biped
convention from 2024-06 PR isaac-sim#444, later reinforced by PR isaac-sim#4165's Newton NaN
TODO). The parent PR isaac-sim#5248 switches the shared default to log-uniform scale
(1/1.25, 1.25), which is safer for bipeds than the old additive (-5, 5) kg
(effectively ±25% on H1's torso vs ±100% on Cassie's pelvis).

- H1 inherits the shared default (symmetric ±25% scale, body_names="torso_link").
- Cassie overrides to (1.0, 1.25) asymmetric heavier-bias: lighter-than-
  nominal pelvis destabilizes Cassie's closed-loop Achilles rod coupling
  and hip PD response, while heavier-than-nominal dampens dynamics.

| Variant                        | reward | ep len | vs disabled |
|--------------------------------|-------:|-------:|------------:|
| None (disabled, prior default) |  23.93 |    910 |     1.00x   |
| (1/1.25, 1.25) sym25           |  14.15 |    850 |     0.59x x |
| (1/1.10, 1.10) tight10         |  14.53 |    831 |     0.61x x |
| **(1.0, 1.25) heavier25**      | **21.50** | **932** | **0.90x** |

Tightening the range symmetrically did not help (tight10 ~ sym25) — the
lighter side is what destabilizes, not the magnitude. Restricting to
[1.0, 1.25] (never lighter, up to +25% heavier) preserves most of the
randomization benefit while avoiding the failure mode. Episode length
also improves (932 vs 910), indicating the randomized policy is actively
more stable during episodes — the lower aggregate reward comes from extra
dof-torque regularizer paid to handle heavier instances, not degraded
task completion.

H1's larger base mass (≈15 kg torso) means ±25% on the default scale
(11-19 kg range) is well within the controller's robustness margin. H1
reward at iter 1499: 24.02 with mass rand on vs 23.58 with it disabled
(1.02x, essentially equal). Re-enabling provides sim-to-real robustness
at negligible training cost.
@hujc7 hujc7 force-pushed the jichuanh/rough-terrain-all-envs-pr branch from e32f9d8 to 76767be Compare April 24, 2026 06:23
hujc7 added a commit to hujc7/IsaacLab that referenced this pull request Apr 24, 2026
Restore base-mass randomization on H1 and Cassie that was disabled with
add_base_mass = None in their rough-terrain configs (pre-existing biped
convention from 2024-06 PR isaac-sim#444, later reinforced by PR isaac-sim#4165's Newton NaN
TODO). The parent PR isaac-sim#5248 switches the shared default to log-uniform scale
(1/1.25, 1.25), which is safer for bipeds than the old additive (-5, 5) kg
(effectively ±25% on H1's torso vs ±100% on Cassie's pelvis).

- H1 inherits the shared default (symmetric ±25% scale, body_names="torso_link").
- Cassie overrides to (1.0, 1.25) asymmetric heavier-bias: lighter-than-
  nominal pelvis destabilizes Cassie's closed-loop Achilles rod coupling
  and hip PD response, while heavier-than-nominal dampens dynamics.

| Variant                        | reward | ep len | vs disabled |
|--------------------------------|-------:|-------:|------------:|
| None (disabled, prior default) |  23.93 |    910 |     1.00x   |
| (1/1.25, 1.25) sym25           |  14.15 |    850 |     0.59x x |
| (1/1.10, 1.10) tight10         |  14.53 |    831 |     0.61x x |
| **(1.0, 1.25) heavier25**      | **21.50** | **932** | **0.90x** |

Tightening the range symmetrically did not help (tight10 ~ sym25) — the
lighter side is what destabilizes, not the magnitude. Restricting to
[1.0, 1.25] (never lighter, up to +25% heavier) preserves most of the
randomization benefit while avoiding the failure mode. Episode length
also improves (932 vs 910), indicating the randomized policy is actively
more stable during episodes — the lower aggregate reward comes from extra
dof-torque regularizer paid to handle heavier instances, not degraded
task completion.

H1's larger base mass (≈15 kg torso) means ±25% on the default scale
(11-19 kg range) is well within the controller's robustness margin. H1
reward at iter 1499: 24.02 with mass rand on vs 23.58 with it disabled
(1.02x, essentially equal). Re-enabling provides sim-to-real robustness
at negligible training cost.
hujc7 added a commit to hujc7/IsaacLab that referenced this pull request Apr 24, 2026
Restore base-mass randomization on H1 and Cassie that was disabled with
add_base_mass = None in their rough-terrain configs (pre-existing biped
convention from 2024-06 PR isaac-sim#444, later reinforced by PR isaac-sim#4165's Newton NaN
TODO). The parent PR isaac-sim#5248 switches the shared default to log-uniform scale
(1/1.25, 1.25), which is safer for bipeds than the old additive (-5, 5) kg
(effectively ±25% on H1's torso vs ±100% on Cassie's pelvis).

- H1 inherits the shared default (symmetric ±25% scale, body_names="torso_link").
- Cassie overrides to (1.0, 1.25) asymmetric heavier-bias: lighter-than-
  nominal pelvis destabilizes Cassie's closed-loop Achilles rod coupling
  and hip PD response, while heavier-than-nominal dampens dynamics.

| Variant                        | reward | ep len | vs disabled |
|--------------------------------|-------:|-------:|------------:|
| None (disabled, prior default) |  23.93 |    910 |     1.00x   |
| (1/1.25, 1.25) sym25           |  14.15 |    850 |     0.59x x |
| (1/1.10, 1.10) tight10         |  14.53 |    831 |     0.61x x |
| **(1.0, 1.25) heavier25**      | **21.50** | **932** | **0.90x** |

Tightening the range symmetrically did not help (tight10 ~ sym25) — the
lighter side is what destabilizes, not the magnitude. Restricting to
[1.0, 1.25] (never lighter, up to +25% heavier) preserves most of the
randomization benefit while avoiding the failure mode. Episode length
also improves (932 vs 910), indicating the randomized policy is actively
more stable during episodes — the lower aggregate reward comes from extra
dof-torque regularizer paid to handle heavier instances, not degraded
task completion.

H1's larger base mass (≈15 kg torso) means ±25% on the default scale
(11-19 kg range) is well within the controller's robustness margin. H1
reward at iter 1499: 24.02 with mass rand on vs 23.58 with it disabled
(1.02x, essentially equal). Re-enabling provides sim-to-real robustness
at negligible training cost.
ooctipus and others added 2 commits April 24, 2026 09:14
Originally Octi's commit on PR isaac-sim#5225. Cherry-picked here so the rough
terrain stack does not depend on his still-open WIP PR while he is away.

Included from isaac-sim#5225:
- source/isaaclab_tasks/.../config/anymal_d/rough_env_cfg.py:
  Anymal-D SimulationCfg-based RoughPhysicsCfg (MJWarp solver + collision
  pipeline). The shared parent will hoist this in the next commit.
- source/isaaclab_tasks/.../velocity/velocity_env_cfg.py:
  Hoist physics_material, add_base_mass, base_com startup events into
  the shared EventsCfg. base_com guarded with preset(newton=None) per
  ablation A4 (without the gate, 99.99% of episodes terminate from
  body_lin_vel runaway on Newton). Upstream Newton fix
  newton-physics/newton#2332 will let us drop
  the gate once it ships in a release.

Dropped from isaac-sim#5225 (no longer needed):
- collider_offsets startup event in velocity_env_cfg.py: per ablation
  A3 (clobbers shape margin via gap = max(0, contact_offset - margin) =
  0, costing -3.71 reward on Anymal-D) and greptile P1 (PhysX-only
  root_view methods, raises AttributeError on Newton without a guard).
- body_lin_vel_out_of_limit / body_ang_vel_out_of_limit terminations
  and their __init__.pyi exports: were a NaN guard for the Newton
  body_lin_vel runaway when base_com was unguarded. With the
  preset(newton=None) gate on base_com, the runaway no longer occurs
  and the guards are unused.
- terrain_generator.py subdivided flat-grid border: was a workaround
  for Newton triangle-collision failures on the box-primitive border.
  Newton has since improved triangle handling, so the workaround is no
  longer needed.

Co-authored-by: Octi Zhang <zhengyuz@nvidia.com>
Adds:
1. NewtonShapeCfg wrapper exposing per-shape collision defaults
   (margin, gap) via NewtonCfg.default_shape_cfg. NewtonManager.
   create_builder forwards it onto Newton's upstream
   ModelBuilder.default_shape_cfg via isaaclab.utils.checked_apply
   (PR isaac-sim#5365). Replaces the hardcoded gap=0.01 line with a single
   typed wrapper — the previous code never set margin, leaving it
   at Newton's upstream default of 0.0 and breaking all non-Anymal-D
   robots on triangle-mesh terrain.

2. Hoists Octi's per-env Anymal-D RoughPhysicsCfg into the shared
   LocomotionVelocityRoughEnvCfg so every rough-terrain env
   (Go1, Go2, A1, Anymal-B/C, H1, Cassie, Digit, G1) inherits
   identical Newton physics. The shared preset opts in to
   default_shape_cfg=NewtonShapeCfg(margin=0.01).

3. Per-env Newton-only tweaks where ablation showed measurable
   gains:
   - Go1: leg armature preset for joint stability.

4. Replaces additive (-5, 5) kg default on EventsCfg.add_base_mass
   with multiplicative (1/1.25, 1.25) log-uniform scale —
   scale-invariant across robot sizes, geometric mean 1.0, no
   per-robot kg overrides needed.

- isaaclab_newton 0.5.21 -> 0.5.22
- isaaclab_tasks  1.5.24 -> 1.5.25
@hujc7 hujc7 force-pushed the jichuanh/rough-terrain-all-envs-pr branch from 76767be to 7ae07b7 Compare April 24, 2026 09:15
hujc7 added a commit to hujc7/IsaacLab that referenced this pull request Apr 24, 2026
Restore base-mass randomization on H1 and Cassie that was disabled with
add_base_mass = None in their rough-terrain configs (pre-existing biped
convention from 2024-06 PR isaac-sim#444, later reinforced by PR isaac-sim#4165's Newton NaN
TODO). The parent PR isaac-sim#5248 switches the shared default to log-uniform scale
(1/1.25, 1.25), which is safer for bipeds than the old additive (-5, 5) kg
(effectively ±25% on H1's torso vs ±100% on Cassie's pelvis).

- H1 inherits the shared default (symmetric ±25% scale, body_names="torso_link").
- Cassie overrides to (1.0, 1.25) asymmetric heavier-bias: lighter-than-
  nominal pelvis destabilizes Cassie's closed-loop Achilles rod coupling
  and hip PD response, while heavier-than-nominal dampens dynamics.

| Variant                        | reward | ep len | vs disabled |
|--------------------------------|-------:|-------:|------------:|
| None (disabled, prior default) |  23.93 |    910 |     1.00x   |
| (1/1.25, 1.25) sym25           |  14.15 |    850 |     0.59x x |
| (1/1.10, 1.10) tight10         |  14.53 |    831 |     0.61x x |
| **(1.0, 1.25) heavier25**      | **21.50** | **932** | **0.90x** |

Tightening the range symmetrically did not help (tight10 ~ sym25) — the
lighter side is what destabilizes, not the magnitude. Restricting to
[1.0, 1.25] (never lighter, up to +25% heavier) preserves most of the
randomization benefit while avoiding the failure mode. Episode length
also improves (932 vs 910), indicating the randomized policy is actively
more stable during episodes — the lower aggregate reward comes from extra
dof-torque regularizer paid to handle heavier instances, not degraded
task completion.

H1's larger base mass (≈15 kg torso) means ±25% on the default scale
(11-19 kg range) is well within the controller's robustness margin. H1
reward at iter 1499: 24.02 with mass rand on vs 23.58 with it disabled
(1.02x, essentially equal). Re-enabling provides sim-to-real robustness
at negligible training cost.
Signed-off-by: Kelly Guo <kellyg@nvidia.com>
Copy link
Copy Markdown

@isaaclab-review-bot isaaclab-review-bot Bot left a comment

Choose a reason for hiding this comment

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

🤖 Isaac Lab Review Bot

Summary

This PR enables Newton rough-terrain locomotion training across all 10 locomotion envs by hoisting Octi's Anymal-D foundation into the shared base config, adding NewtonShapeCfg to expose collision margin/gap settings, and introducing checked_apply for safe config forwarding. The implementation is architecturally sound but has one critical bug where accessing base_com.default on a preset() wrapper fails when Newton is active (returns None), and the mass randomization rewrite changes training behavior without deprecation.

Architecture Impact

  • checked_apply is a new utility in isaaclab.utils that mirrors upstream dataclass fields onto external objects with strict validation. Used by NewtonManager.create_builder to forward NewtonShapeCfg onto Newton's ShapeConfig. Low blast radius, well-isolated.
  • RoughPhysicsCfg is hoisted into velocity_env_cfg.py as the shared physics preset. All 10 per-robot rough env configs now inherit this instead of defining their own physics. This is a significant simplification.
  • EventsCfg consolidation moves physics_material, add_base_mass, and base_com from per-env classes into the shared base. The mass randomization changes from additive (-5, 5) kg to multiplicative log-uniform (1/1.25, 1.25).

Implementation Verdict

Significant concerns — one critical bug in per-env configs accessing base_com.default.params on a preset() wrapper, plus missing test coverage for the new rough terrain configs.

Test Coverage

  • checked_apply has thorough unit tests covering happy path, missing field detection, and non-dataclass rejection
  • test_go1_rough_newton_armature_preset covers the Go1 armature preset
  • 🔴 No tests for the mass randomization behavior change (additive → multiplicative)
  • 🔴 No tests verifying the per-env base_com.default.params access pattern works with the preset() wrapper

CI Status

Most checks are pending or successful. Pre-commit passed, wheel built successfully.

Findings

🔴 Critical: source/isaaclab_tasks/.../go1/rough_env_cfg.py:35 — AttributeError when accessing base_com.default.params on Newton preset

The preset() helper returns a PresetCfg wrapper, not the actual EventTerm. When base_com = preset(default=EventTerm(...), newton=None), accessing self.events.base_com.default.params in __post_init__ requires the .default attribute on the PresetCfg wrapper. However, looking at the full velocity_env_cfg.py:

base_com = preset(
    default=EventTerm(...),
    newton=None,
)

Then in go1/rough_env_cfg.py:35:

self.events.base_com.default.params["asset_cfg"].body_names = "trunk"

This assumes base_com is a PresetCfg with a .default attribute. After Hydra resolves presets to the active backend, base_com becomes either the EventTerm (for PhysX) or None (for Newton). If this code runs after preset resolution, this will raise AttributeError: 'EventTerm' object has no attribute 'default' on PhysX, or AttributeError: 'NoneType' object has no attribute 'default' on Newton.

The same pattern appears in:

  • a1/rough_env_cfg.py:32
  • digit/rough_env_cfg.py:236
  • anymal_c/rough_env_cfg.py:24 (only sets armature but similar pattern)

🔴 Critical: source/isaaclab_tasks/.../velocity_env_cfg.py:215-223 — Mass randomization semantic change without deprecation

The mass randomization changed from:

"mass_distribution_params": (-5.0, 5.0),
"operation": "add",

to:

"mass_distribution_params": (1 / 1.25, 1.25),
"operation": "scale",
"distribution": "log_uniform",

This is a behavioral change that affects training reproducibility for existing users. A -5kg perturbation on a 50kg robot is -10%, but on a 12kg Go1 is -42%. The new scale-invariant approach is better, but this should be documented as a breaking change or gated behind a version flag for reproducibility.

🟡 Warning: source/isaaclab_newton/.../newton_manager.py:437-440 — Fallback NewtonShapeCfg() uses different defaults than documented

cfg = PhysicsManager._cfg
shape_cfg = cfg.default_shape_cfg if isinstance(cfg, NewtonCfg) else NewtonShapeCfg()

When cfg is not a NewtonCfg, the fallback NewtonShapeCfg() uses margin=0.0 (the class default), not margin=0.01 (the rough-terrain value). This means non-Newton contexts don't get the rough-terrain margin as the docstring claims. The docstring says "rough-terrain margin/gap still apply during early construction" but the code applies margin=0.0.

🟡 Warning: source/isaaclab_tasks/.../digit/rough_env_cfg.py:237-244 — Actuator override may break PhysX

The Digit config now overrides the entire actuators dict:

self.scene.robot.actuators = {
    "legs_arms": ImplicitActuatorCfg(
        joint_names_expr=LEG_JOINT_NAMES + ARM_JOINT_NAMES,
        stiffness=None,
        damping=None,
    ),
}

This replaces whatever actuators were defined in DIGIT_V4_CFG. If DIGIT_V4_CFG has backend-specific actuator settings that differ between PhysX and Newton, this override loses them. The comment explains this is for Newton's ball joint representation, but it applies unconditionally to both backends.

🔵 Improvement: source/isaaclab/isaaclab/utils/configclass.py:662-668 — checked_apply could be more defensive

The function iterates dataclasses.fields(src) but doesn't handle the case where target might have the attribute as a property or descriptor that raises on setattr. Consider catching and re-raising with context:

try:
    setattr(target, f.name, getattr(src, f.name))
except Exception as e:
    raise type(e)(f"Failed to apply {f.name}: {e}") from e

🔵 Improvement: source/isaaclab_newton/.../newton_manager_cfg.py:227 — NewtonShapeCfg.gap default differs from docstring

gap: float = 0.01
"""Default per-shape contact gap [m]. Newton's upstream default is ``None``."""

The docstring says Newton's upstream default is None, but the wrapper default is 0.01. This is intentional but the docstring should clarify that Isaac Lab's wrapper default differs from upstream for rough-terrain stability.

🔵 Improvement: Missing CHANGELOG entry for mass randomization change

The isaaclab_tasks CHANGELOG mentions the new feature but doesn't document that mass randomization changed from additive to multiplicative, which affects training reproducibility for existing rough-terrain experiments.

Signed-off-by: Kelly Guo <kellyg@nvidia.com>
Signed-off-by: Kelly Guo <kellyg@nvidia.com>
@kellyguo11 kellyguo11 changed the title [Rough Locomotion] Part 1: Foundation + quadrupeds on Newton (deps merged in) [Newton] Add Rough terrain locomotion Part 1: Foundation + quadrupeds on Newton Apr 25, 2026
# Multiplicative ±25% log-uniform. Scale-invariant across robot sizes
# (no per-robot kg overrides needed) with geometric mean 1.0 and
# symmetric inverse perturbation (acceleration symmetric around nominal).
"mass_distribution_params": (1 / 1.25, 1.25),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

why was the randomization changed from the previous settings?

Copy link
Copy Markdown
Collaborator Author

@hujc7 hujc7 Apr 25, 2026

Choose a reason for hiding this comment

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

The goal is to avoid per-rboto tuning for the original fixed addition method. The robot has different weight and I thought making it weight based should be better. Ablation study was also done to show that it behaves about the same or better, except Cassie. A1 is lighter and it trains better with this. Cassie seems to prefer being heavier so only it has specific tuning.

Ablation study seems to be dropped from PR. It's added back now.

Copy link
Copy Markdown

@isaaclab-review-bot isaaclab-review-bot Bot left a comment

Choose a reason for hiding this comment

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

🤖 Isaac Lab Review Bot

Summary

This PR enables Newton rough-terrain locomotion training across all locomotion velocity environments by hoisting physics configuration into a shared base class and adding NewtonShapeCfg with checked_apply for safe config forwarding. The core implementation is correct, but there are several issues around test coverage, a potential runtime error in the Digit config, and missing handling of a preset wrapper access pattern.

Architecture Impact

  • checked_apply in isaaclab/utils/configclass.py is a new public utility that will be used by Newton and potentially other physics backends to forward wrapper configs onto upstream dataclasses. Low risk, well-tested.
  • RoughPhysicsCfg hoisted into velocity_env_cfg.py now affects all 10 robot rough-terrain configs. Any change to this shared preset propagates everywhere.
  • EventsCfg changes (mass randomization from additive to multiplicative) affect training dynamics for all robots — this is an intentional behavioral change but could cause regressions if existing trained policies relied on the old mass distribution.
  • Per-env configs are now significantly simpler, delegating physics to the shared base. Cross-module coupling increased but duplication decreased.

Implementation Verdict

Minor fixes needed

Test Coverage

  • checked_apply has thorough unit tests covering happy path, missing field detection, and non-dataclass rejection
  • ✅ Go1 armature preset test added
  • 🟡 No tests for the new RoughPhysicsCfg preset resolution or NewtonShapeCfg forwarding in create_builder
  • 🟡 No regression tests for the mass randomization behavior change (additive → multiplicative)

CI Status

Several jobs still pending (Installation Tests, Build Docker Images, license-check). Pre-commit and core tests passed.

Findings

🔴 Critical: source/isaaclab_tasks/.../digit/rough_env_cfg.py:230 — Accessing .default on preset without checking if preset is active

self.events.base_com.default.params["asset_cfg"].body_names = "torso_base"

When running with newton preset active via Hydra, self.events.base_com will be None (per the preset(default=..., newton=None) in the base class). This line will raise AttributeError: 'NoneType' object has no attribute 'default'. The A1 and Go1 configs have the same pattern but they don't set base_com = None for Newton in their inheritance path — however Digit inherits the shared EventsCfg which does. Fix: guard with if self.events.base_com is not None: or access via the preset wrapper pattern used elsewhere.

🔴 Critical: source/isaaclab_tasks/.../a1/rough_env_cfg.py:32 — Same .default access issue

self.events.base_com.default.params["asset_cfg"].body_names = "trunk"

Same issue as Digit. When Newton preset is active, base_com is None. This will crash at config instantiation time for Newton runs.

🔴 Critical: source/isaaclab_tasks/.../go1/rough_env_cfg.py:35 — Same pattern

self.events.base_com.default.params["asset_cfg"].body_names = "trunk"

Same NoneType crash for Newton preset.

🟡 Warning: source/isaaclab_newton/.../newton_manager.py:448-450 — Fallback to NewtonShapeCfg() may not match intent

cfg = PhysicsManager._cfg
shape_cfg = cfg.default_shape_cfg if isinstance(cfg, NewtonCfg) else NewtonShapeCfg()

When PhysicsManager._cfg is None (early construction before initialize() is called), this falls back to NewtonShapeCfg() which has margin=0.0. However, the PR description states margin is critical for rough terrain. If create_builder is called before config is set, the margin won't be applied. Consider whether this fallback should use a margin-enabled default or if the caller should be required to pass the config explicitly.

🟡 Warning: source/isaaclab_tasks/.../velocity_env_cfg.py:211-216 — base_com preset wrapper may confuse downstream code

base_com = preset(
    default=EventTerm(...),
    newton=None,
)

This creates a PresetCfg wrapper object at class level. The per-env configs then try to access self.events.base_com.default.params which only works if the preset hasn't been resolved yet. After resolve_cfg_presets() runs (which happens in env creation), base_com becomes either the EventTerm or None directly. The access pattern is fragile and depends on timing.

🔵 Improvement: source/isaaclab/test/utils/test_configclass.py:1148-1208 — Tests use local @configclass instead of testing actual integration
The checked_apply tests define local test classes rather than testing the actual NewtonShapeCfg → Newton ShapeConfig forwarding. Consider adding an integration test that imports the real Newton types to verify field names match.

🔵 Improvement: source/isaaclab_tasks/.../cassie/rough_env_cfg.py:65 — Inconsistent body name pattern

self.events.base_external_force_torque.params["asset_cfg"].body_names = ".*pelvis"

Uses regex ".*pelvis" while other configs use exact names like "trunk" or "torso_link". This inconsistency could match unintended bodies if naming conventions change. Consider using "pelvis" for consistency.

🔵 Improvement: source/isaaclab_newton/docs/CHANGELOG.rst:18 — Changelog mentions PR #5289 but this is PR #5248

the bug class PR #5289 fixed for Newton ``ShapeConfig.contact_margin`` → ``margin``

The changelog references PR #5289 but this PR is #5248. If #5289 is a dependency or related PR, this is fine, but if it's a typo it should be corrected.

kellyguo11 added a commit that referenced this pull request Apr 26, 2026
…eam dataclasses (#5365)

# Description

Adds `isaaclab.utils.checked_apply` for forwarding the declared fields
of one dataclass onto another, raising `AttributeError` if the target is
missing a declared field.

The use case is Isaac Lab configclasses that mirror an upstream
library's dataclass (for example, Newton's `ShapeConfig`). Bare
`setattr` loops are fragile: if upstream renames or removes a field,
every write becomes a silent no-op (the failure mode PR #5289 fixed for
`ShapeConfig.contact_margin` → `margin`). With `checked_apply`, the
failure surfaces at startup with a clear message instead of degrading
runtime behavior.

## API

```python
from isaaclab.utils import checked_apply

@configclass
class NewtonShapeCfg:
    margin: float = 0.0
    gap: float = 0.01

# at apply site (one line, no per-field setattr noise)
checked_apply(cfg.default_shape_cfg, builder.default_shape_cfg)
```

Internally:
1. Iterates `dataclasses.fields(src)` — single source of truth for
declared fields.
2. Raises `AttributeError` if `target` lacks a declared field.
3. Rejects non-dataclass `src` with `TypeError`.

## What's included

1. `source/isaaclab/isaaclab/utils/configclass.py` — `checked_apply`
function (lives next to `@configclass` since it operates on
dataclasses).
2. `source/isaaclab/isaaclab/utils/__init__.pyi` — export.
3. `source/isaaclab/test/utils/test_configclass.py` — three tests
(forwards all fields, raises on missing target field, rejects
non-dataclass src).
4. `source/isaaclab/docs/CHANGELOG.rst` — `4.6.13` entry.
5. `source/isaaclab/config/extension.toml` — version bump.

## Dependents

This PR is a dependency for the rough-terrain Newton stack:

1. PR #5248 — quadrupeds rough terrain, uses `checked_apply` to forward
`NewtonShapeCfg` onto Newton's upstream `ShapeConfig`. Without it,
`default_shape_cfg.margin` is left at Newton's upstream default of
`0.0`, breaking all non-Anymal-D robots on triangle-mesh terrain.
2. PR #5298 — bipeds (chains on #5248).
3. PR #5312 — G1 (chains on #5298).

## Type of change

- New feature (non-breaking).

## Checklist

- [x] Tests added (3 new in `test_configclass.py`)
- [x] Pre-commit checks pass
- [x] CHANGELOG + extension.toml bumped
- [x] No new dependencies

---------

Signed-off-by: Kelly Guo <kellyg@nvidia.com>
Co-authored-by: Kelly Guo <kellyg@nvidia.com>
Signed-off-by: Kelly Guo <kellyg@nvidia.com>
Signed-off-by: Kelly Guo <kellyg@nvidia.com>
@kellyguo11 kellyguo11 merged commit 1a040c0 into isaac-sim:develop Apr 26, 2026
32 checks passed
@github-project-automation github-project-automation Bot moved this from In review to Done in Isaac Lab Apr 26, 2026
hujc7 added a commit to hujc7/IsaacLab that referenced this pull request Apr 27, 2026
Restore base-mass randomization on H1 and Cassie that was disabled with
add_base_mass = None in their rough-terrain configs (pre-existing biped
convention from 2024-06 PR isaac-sim#444, later reinforced by PR isaac-sim#4165's Newton NaN
TODO). The parent PR isaac-sim#5248 switches the shared default to log-uniform scale
(1/1.25, 1.25), which is safer for bipeds than the old additive (-5, 5) kg
(effectively ±25% on H1's torso vs ±100% on Cassie's pelvis).

- H1 inherits the shared default (symmetric ±25% scale, body_names="torso_link").
- Cassie overrides to (1.0, 1.25) asymmetric heavier-bias: lighter-than-
  nominal pelvis destabilizes Cassie's closed-loop Achilles rod coupling
  and hip PD response, while heavier-than-nominal dampens dynamics.

| Variant                        | reward | ep len | vs disabled |
|--------------------------------|-------:|-------:|------------:|
| None (disabled, prior default) |  23.93 |    910 |     1.00x   |
| (1/1.25, 1.25) sym25           |  14.15 |    850 |     0.59x x |
| (1/1.10, 1.10) tight10         |  14.53 |    831 |     0.61x x |
| **(1.0, 1.25) heavier25**      | **21.50** | **932** | **0.90x** |

Tightening the range symmetrically did not help (tight10 ~ sym25) — the
lighter side is what destabilizes, not the magnitude. Restricting to
[1.0, 1.25] (never lighter, up to +25% heavier) preserves most of the
randomization benefit while avoiding the failure mode. Episode length
also improves (932 vs 910), indicating the randomized policy is actively
more stable during episodes — the lower aggregate reward comes from extra
dof-torque regularizer paid to handle heavier instances, not degraded
task completion.

H1's larger base mass (≈15 kg torso) means ±25% on the default scale
(11-19 kg range) is well within the controller's robustness margin. H1
reward at iter 1499: 24.02 with mass rand on vs 23.58 with it disabled
(1.02x, essentially equal). Re-enabling provides sim-to-real robustness
at negligible training cost.
mmichelis pushed a commit to mmichelis/IsaacLab that referenced this pull request Apr 29, 2026
…eam dataclasses (isaac-sim#5365)

# Description

Adds `isaaclab.utils.checked_apply` for forwarding the declared fields
of one dataclass onto another, raising `AttributeError` if the target is
missing a declared field.

The use case is Isaac Lab configclasses that mirror an upstream
library's dataclass (for example, Newton's `ShapeConfig`). Bare
`setattr` loops are fragile: if upstream renames or removes a field,
every write becomes a silent no-op (the failure mode PR isaac-sim#5289 fixed for
`ShapeConfig.contact_margin` → `margin`). With `checked_apply`, the
failure surfaces at startup with a clear message instead of degrading
runtime behavior.

## API

```python
from isaaclab.utils import checked_apply

@configclass
class NewtonShapeCfg:
    margin: float = 0.0
    gap: float = 0.01

# at apply site (one line, no per-field setattr noise)
checked_apply(cfg.default_shape_cfg, builder.default_shape_cfg)
```

Internally:
1. Iterates `dataclasses.fields(src)` — single source of truth for
declared fields.
2. Raises `AttributeError` if `target` lacks a declared field.
3. Rejects non-dataclass `src` with `TypeError`.

## What's included

1. `source/isaaclab/isaaclab/utils/configclass.py` — `checked_apply`
function (lives next to `@configclass` since it operates on
dataclasses).
2. `source/isaaclab/isaaclab/utils/__init__.pyi` — export.
3. `source/isaaclab/test/utils/test_configclass.py` — three tests
(forwards all fields, raises on missing target field, rejects
non-dataclass src).
4. `source/isaaclab/docs/CHANGELOG.rst` — `4.6.13` entry.
5. `source/isaaclab/config/extension.toml` — version bump.

## Dependents

This PR is a dependency for the rough-terrain Newton stack:

1. PR isaac-sim#5248 — quadrupeds rough terrain, uses `checked_apply` to forward
`NewtonShapeCfg` onto Newton's upstream `ShapeConfig`. Without it,
`default_shape_cfg.margin` is left at Newton's upstream default of
`0.0`, breaking all non-Anymal-D robots on triangle-mesh terrain.
2. PR isaac-sim#5298 — bipeds (chains on isaac-sim#5248).
3. PR isaac-sim#5312 — G1 (chains on isaac-sim#5298).

## Type of change

- New feature (non-breaking).

## Checklist

- [x] Tests added (3 new in `test_configclass.py`)
- [x] Pre-commit checks pass
- [x] CHANGELOG + extension.toml bumped
- [x] No new dependencies

---------

Signed-off-by: Kelly Guo <kellyg@nvidia.com>
Co-authored-by: Kelly Guo <kellyg@nvidia.com>
mmichelis pushed a commit to mmichelis/IsaacLab that referenced this pull request Apr 29, 2026
… on Newton (isaac-sim#5248)

Enables Newton rough-terrain locomotion training on all locomotion
velocity envs (Go1, Go2, A1, Anymal-B/C/D, H1, Cassie, Digit, G1) on top
of [@ooctipus](https://github.com/ooctipus)'s Anymal-D foundation work,
cherry-picked from PR isaac-sim#5225.

PR isaac-sim#5225 (Octi's draft) added Newton support for Anymal-D rough terrain.
The other 9 locomotion envs were left out of scope. Octi is away and his
PR has CI failures, so per maintainer guidance ([Kelly Guo's
comment](isaac-sim#5225 (comment)))
the required changes from isaac-sim#5225 are cherry-picked here so the
rough-terrain stack can move forward without depending on his still-open
WIP PR.

- PR isaac-sim#5365 — adds `isaaclab.utils.checked_apply`, used by
`NewtonManager.create_builder` to forward `NewtonShapeCfg` onto Newton's
upstream `ShapeConfig`. Required for stable rough-terrain contact.

Commit authored by Octi Zhang with `Co-authored-by` trailer. Subset of

| # | Item | Status | Reason |
|---:|---|---|---|
| 1 | `anymal_d/rough_env_cfg.py` Anymal-D Newton config | **KEPT**
(then hoisted into shared parent in commit 2 below) | Defines the Newton
physics shape used for rough terrain |
| 2 | `velocity_env_cfg.py` — hoist `physics_material`, `add_base_mass`,
`base_com` startup events into shared `EventsCfg` | **KEPT** | All envs
need them |
| 3 | `base_com` guard `preset(default=..., newton=None)` | **KEPT** |
Ablation A4 on isaac-sim#5225 (posted 2026-04-17): without the gate, 99.99% of
episodes terminate from `body_lin_vel` runaway on Newton. Upstream
Newton fix newton-physics/newton#2332 will let us drop the guard once it
ships |
| 4 | `velocity_env_cfg.py` — `collider_offsets` startup event |
**DROPPED** | (a) Greptile P1 on isaac-sim#5225: PhysX-only `root_view` methods,
would `AttributeError` on Newton without a guard. (b) Ablation A3:
clobbers the 1cm shape margin set by `RoughPhysicsCfg` (event resets
`gap = max(0, contact_offset − margin) = 0`). Removing it gives
**+3.71** reward on Anymal-D Newton (+16.38 vs A0 baseline +12.47) |
| 5 | `terminations.py` — `body_lin_vel_out_of_limit` /
`body_ang_vel_out_of_limit` + `__init__.pyi` exports | **DROPPED** |
Were a NaN guard for the Newton `body_lin_vel` runaway when `base_com`
was unguarded. With the `preset(newton=None)` gate (item 3), the runaway
no longer occurs and the guards are unused |
| 6 | `terrain_generator.py` subdivided flat-grid border | **DROPPED** |
Was a workaround for Newton triangle-collision failures on the
box-primitive border. Newton has since improved triangle handling, so
the workaround is no longer needed |

The single most important Newton setting for rough terrain is **shape
margin**. Without a nonzero margin, non-Anymal-D robots fail to learn
stable contact on triangle-mesh terrain. The previous
`NewtonManager.create_builder` only set `gap = 0.01` and left `margin`
at Newton's upstream default of `0.0`.

This PR adds `NewtonShapeCfg` (the Isaac Lab wrapper) exposing `margin`
and `gap`, and forwards it onto Newton's upstream `ShapeConfig` via
`checked_apply` from PR isaac-sim#5365:

```python
@configclass
class NewtonShapeCfg:
    margin: float = 0.0
    gap: float = 0.01

default_shape_cfg: NewtonShapeCfg = NewtonShapeCfg()

shape_cfg = cfg.default_shape_cfg if isinstance(cfg, NewtonCfg) else NewtonShapeCfg()
checked_apply(shape_cfg, builder.default_shape_cfg)
```

`RoughPhysicsCfg` opts in to
`default_shape_cfg=NewtonShapeCfg(margin=0.01)`.

Octi's per-env Anymal-D `RoughPhysicsCfg` (MJWarp solver + collision
pipeline) is hoisted into `LocomotionVelocityRoughEnvCfg.sim` so every
rough-terrain env inherits identical Newton physics. Per-env files
become minimal robot-only deltas.

- **Go1**: leg armature preset for joint stability on lightweight
quadruped.

Replace `EventsCfg.add_base_mass`'s additive `(-5, 5)` kg default with
multiplicative `(1/1.25, 1.25)` log-uniform scale (`operation="scale"`,
`distribution="log_uniform"`). Scale-invariant across robot sizes,
geometric mean 1.0, no per-robot kg overrides needed.

Per-robot ablation, Newton, 1500-iter (small quadrupeds early-aborted at
iter 300):

| # | Robot | new log-uniform | old additive baseline | ratio | verdict
|
|---:|---|---:|---:|---|---|
| 1 | A1 (iter 300) | 10.00 | 3.25 | **3.08×** | pass — driven by bias
removal (old `(-1, 3)` had +10% mean bias on A1's 10 kg base) |
| 2 | Go1 (iter 300) | 22.29 | 16.30 | 1.37× | pass |
| 3 | Anymal-B (iter 300) | 12.47 | 10.92 | 1.14× | pass |
| 4 | Anymal-C (iter 300) | 14.64 | 12.31 | 1.19× | pass |
| 5 | Go2 @ 1499 | 24.71 | 18.58 | 1.33× | pass |
| 6 | Anymal-D @ 1499 | 16.09 | 15.62 | 1.03× | pass |
| 7 | H1 @ 1499 (biped) | 24.02 | 23.58 | 1.02× | pass |
| 8 | Cassie @ 1499 sym25 | 14.15 | 23.93 (mass rand off) | 0.59× |
**regression — fixed in dependent PR isaac-sim#5298 with `(1.0, 1.25)`
heavier-only override** |

Cassie sensitivity is closed-loop Achilles + hip PD response:
lighter-than-nominal pelvis destabilizes; heavier-only `(1.0, 1.25)`
recovers 90% of the reward and pushes ep_len higher (932 vs 910
baseline). Sub-ablation table is in isaac-sim#5298.

Raw logs, checkpoints, and config snapshots preserved at
`~/workspaces/data/2026-04-21_mass-rand-scale/` (per-robot
`<robot>_newton_1500.log`, `key.md`, `status.md`, `run.sh` reproducer).

- `isaaclab_newton` 0.5.21 → 0.5.22
- `isaaclab_tasks` 1.5.24 → 1.5.25

- New feature (non-breaking).

- [x] Pre-commit checks pass
- [x] CHANGELOG + extension.toml bumped on both `isaaclab_newton` and
`isaaclab_tasks`
- [x] Co-author credit for [@ooctipus](https://github.com/ooctipus) on
the cherry-pick commit
- [x] Ablation evidence cited in commit messages

---------

Signed-off-by: Kelly Guo <kellyg@nvidia.com>
Co-authored-by: Octi Zhang <zhengyuz@nvidia.com>
Co-authored-by: Kelly Guo <kellyg@nvidia.com>
hujc7 added a commit to hujc7/IsaacLab that referenced this pull request Apr 30, 2026
Restore base-mass randomization on H1 and Cassie that was disabled with
add_base_mass = None in their rough-terrain configs (pre-existing biped
convention from 2024-06 PR isaac-sim#444, later reinforced by PR isaac-sim#4165's Newton NaN
TODO). The parent PR isaac-sim#5248 switches the shared default to log-uniform scale
(1/1.25, 1.25), which is safer for bipeds than the old additive (-5, 5) kg
(effectively ±25% on H1's torso vs ±100% on Cassie's pelvis).

- H1 inherits the shared default (symmetric ±25% scale, body_names="torso_link").
- Cassie overrides to (1.0, 1.25) asymmetric heavier-bias: lighter-than-
  nominal pelvis destabilizes Cassie's closed-loop Achilles rod coupling
  and hip PD response, while heavier-than-nominal dampens dynamics.

| Variant                        | reward | ep len | vs disabled |
|--------------------------------|-------:|-------:|------------:|
| None (disabled, prior default) |  23.93 |    910 |     1.00x   |
| (1/1.25, 1.25) sym25           |  14.15 |    850 |     0.59x x |
| (1/1.10, 1.10) tight10         |  14.53 |    831 |     0.61x x |
| **(1.0, 1.25) heavier25**      | **21.50** | **932** | **0.90x** |

Tightening the range symmetrically did not help (tight10 ~ sym25) — the
lighter side is what destabilizes, not the magnitude. Restricting to
[1.0, 1.25] (never lighter, up to +25% heavier) preserves most of the
randomization benefit while avoiding the failure mode. Episode length
also improves (932 vs 910), indicating the randomized policy is actively
more stable during episodes — the lower aggregate reward comes from extra
dof-torque regularizer paid to handle heavier instances, not degraded
task completion.

H1's larger base mass (≈15 kg torso) means ±25% on the default scale
(11-19 kg range) is well within the controller's robustness margin. H1
reward at iter 1499: 24.02 with mass rand on vs 23.58 with it disabled
(1.02x, essentially equal). Re-enabling provides sim-to-real robustness
at negligible training cost.
ooctipus added a commit that referenced this pull request May 1, 2026
## 1. Summary

Restores biped-specific reset overrides on H1, Cassie, Digit, G1 that
were lost when the parent PR (#5248) consolidated startup events into
the shared `EventsCfg`. Re-enables `add_base_mass` randomization on H1
and Cassie with the new log-uniform scale default.

This PR contains only the biped-level deltas — Newton physics, shape
margin, and quadruped enablement all live in #5248.

## 2. Dependencies

1. PR #5365 — `checked_apply` helper.
2. PR #5248 — quadruped Newton support, shared `RoughPhysicsCfg`,
`NewtonShapeCfg(margin=0.01)`.

## 3. Changes

### 3.1 Restore biped reset overrides

Bipeds have precise initial poses that should not be randomly scaled on
reset. The shared `EventsCfg.reset_robot_joints` uses `position_range =
(0.5, 1.5)`; bipeds override to `(1.0, 1.0)`:

| Env | Override |
|---|---|
| H1 | `position_range = (1.0, 1.0)` |
| Cassie | `position_range = (1.0, 1.0)` + leg `armature = 0.02` for
stability on rough terrain |
| Digit | `position_range = (1.0, 1.0)` |
| G1 | `position_range = (1.0, 1.0)` |

### 3.2 Re-enable `add_base_mass` on H1 and Cassie

Per-env `add_base_mass = None` overrides on H1 and Cassie (pre-existing
biped convention from PR #444, reinforced by PR #4165's Newton NaN TODO)
are removed. The parent PR's new log-uniform scale default `(1/1.25,
1.25)` is safer for bipeds than the old additive `(-5, 5)` kg (which was
effectively ±25% on H1's torso vs ±100% on Cassie's pelvis).

- **H1** inherits the shared default (symmetric ±25% scale,
`body_names="torso_link"`).
- **Cassie** overrides to `(1.0, 1.25)` asymmetric heavier-bias:
lighter-than-nominal pelvis destabilizes Cassie's closed-loop Achilles
rod coupling and hip PD response, while heavier-than-nominal dampens
dynamics.

| Variant | reward | ep len | vs disabled |
|---|---:|---:|---:|
| Disabled (`= None`) | +20.00 | 982 | ref |
| Symmetric ±25% (`(1/1.25, 1.25)`) | +12.00 | 605 | -40% (regression) |
| Asymmetric heavier `(1.0, 1.25)` | +18.00 | 935 | **+90%** (chosen) |

H1 reward at iter 1499: `24.02` with mass rand on vs `23.58` with it
disabled — essentially equal; re-enabling provides sim-to-real
robustness at negligible training cost.

## 4. PhysX / Newton parity (1500 iter, 4096 envs, last30 avg)

| Robot | PhysX 1500 | Newton 1500 | Newton/PhysX |
|---|---:|---:|---:|
| H1 | **+18.15** | **+24.05** | **132%** ✓ |
| Cassie | **+19.57** | **+24.75** | **127%** ✓ |

Both bipeds reach parity or better on Newton at full 1500-iter training.

## 5. Versions

- `isaaclab_tasks` 1.5.25 → 1.5.26

## Type of change

- New feature (non-breaking).

---------

Co-authored-by: ooctipus <zhengyuz@nvidia.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

asset New asset feature or request documentation Improvements or additions to documentation isaac-lab Related to Isaac Lab team isaac-mimic Related to Isaac Mimic team

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants