Skip to content

Physics-rate PD actuators, named collision geoms, lite_biped_debug variant - #13

Merged
T-K-233 merged 1 commit into
mainfrom
feat/position-actuators-and-biped-debug
Jul 27, 2026
Merged

Physics-rate PD actuators, named collision geoms, lite_biped_debug variant#13
T-K-233 merged 1 commit into
mainfrom
feat/position-actuators-and-biped-debug

Conversation

@T-K-233

@T-K-233 T-K-233 commented Jul 27, 2026

Copy link
Copy Markdown
Member

Three related changes to the MJCF generator plus one new variant. Every variant's MJCF is regenerated; 1531 tests pass.

1. <position> actuators instead of <motor>

The joint-level PD belongs inside the physics step. MuJoCo evaluates actuators every mj_step, which mirrors the real RobStride drive closing its onboard loop at ~1 kHz independently of the host rate, and lets implicitfast integrate the damping term implicitly — the reason a stiff PD stays stable at a coarse timestep, and how mjlab trains. A <motor> makes ctrl a raw torque, forcing the host to sample the PD once per controller_manager tick, which made the sim's PD rate an artifact of the CM rate.

Measured downstream in the arms sim (worst-joint peak-to-peak after standby settles):

p2p behaviour
200 Hz, PD in mj_step 0.00065 rad quiescent
200 Hz, PD in write() 0.0371 rad 25–37 Hz wrist chatter
400 Hz, PD in write() 0.0024 rad quiescent

Both chatter-free and 3.6× quieter than the previous best arrangement.

kp/kv are left at MuJoCo's defaults deliberately: the operative gains are mode-dependent (standby, policy, damping all differ) and arrive over ros2_control's stiffness/damping command interfaces, so MujocoSystem overwrites gainprm/biasprm every tick. Baked values would only mislead.

No ctrlrange/ctrllimited is emitted, and that is load-bearing: an RL policy is allowed to command a position setpoint beyond the mechanical joint limit in order to hold a saturating torque, so clamping the setpoint would silently cap the achievable torque. forcerange still bounds the output, which is the limit that is physically real.

2. Name collision geoms <body>_collision_<i>

MuJoCo leaves URDF-imported geoms anonymous, but the training env selects them by name — mjlab's friction DR matches r"^(left|right)_foot_collision_\d+$" — and an unnamed geom can never match, so that randomization was silently doing nothing. Only fills blanks; an explicit name in the MJCF still wins.

Result: left_foot_collision_0..10 on lite_biped (11 spheres), left_foot_collision_0 on lite_biped_debug (1 box). Variants with no scad colliders are unaffected.

3. New variant: lite_biped_debug

Identical to lite_biped except for a deliberately simplified foot collider — one 70×130×20 mm box per foot, replacing 11 spheres. Verified flush with the sole (gap 0.0 mm, with the origin rpy applied) and covering the full 130 mm mesh length at 70 mm wide vs 72 mm mesh.

Parity that matters for downstream configs: link and joint names match lite_biped exactly (the three new CAD parts — foot_palm_debug, foot_front, foot_rear — merge into the existing left_foot/right_foot links), so ros2_control.json, the MJCF body list and the training-side tracked-body list all still resolve. physics.json is byte-identical, keeping timestep 0.005 and the solver settings matched to mjlab's training config. The four-bar ankle linkage params carry through to the ros2_control xacro.

⚠️ The debug foot is much smaller than lite_biped's (130 mm vs 213 mm long, no toe upturn) — roughly a third of the contact area. A policy will not transfer between the two variants unchanged; that is the point of the variant, but worth stating.

Companion changes in other repos

A PR cannot span repos, so the rest of this work lands separately:

  • mujoco_ros2_controlMujocoSystem must drive ctrl + gainprm/biasprm instead of computing the PD into qfrc_applied; without it the <position> actuators here are inert. Still uncommitted.
  • humanoid_control — the 200 Hz CM rate and the sim/real rate unification that the measurements above come from: Berkeley-Humanoids/humanoid_control_ros2#24.

🤖 Generated with Claude Code

https://claude.ai/code/session_012QKJQ9hze1hMWXJpdPcjp3

Three related changes to the MJCF generator and one new variant.

1. <position> actuators instead of <motor>

The joint-level PD belongs inside the physics step. MuJoCo evaluates
actuators every mj_step, which mirrors the real RobStride drive closing its
onboard loop at ~1 kHz independently of the host rate, and lets implicitfast
integrate the damping term implicitly -- the reason a stiff PD stays stable at
a coarse timestep, and how mjlab trains. A <motor> makes ctrl a raw torque,
forcing the host to sample the PD once per controller_manager tick, which made
the sim's PD rate an artifact of the CM rate.

Measured downstream in the arms sim (worst-joint peak-to-peak after standby
settles): 0.00065 rad at 200 Hz with the PD in mj_step, vs 0.0371 rad of
25-37 Hz wrist chatter when that same 200 Hz loop computed the PD itself, and
0.0024 rad at 400 Hz that way. So it is both chatter-free and 3.6x quieter
than the previous best arrangement.

kp/kv are left at MuJoCo's defaults on purpose: the operative gains are
mode-dependent and arrive over ros2_control's stiffness/damping command
interfaces, so MujocoSystem overwrites gainprm/biasprm every tick.

No ctrlrange/ctrllimited is emitted, and that is load-bearing: an RL policy is
allowed to command a position setpoint beyond the mechanical joint limit to
hold a saturating torque, so clamping the setpoint would silently cap the
achievable torque. forcerange still bounds the output.

2. Name collision geoms <body>_collision_<i>

MuJoCo leaves URDF-imported geoms anonymous, but the training env selects them
BY NAME -- mjlab's friction DR matches r"^(left|right)_foot_collision_\d+$" --
and an unnamed geom can never match, so that randomization was silently doing
nothing. Only fills blanks; an explicit name in the MJCF still wins.

3. New variant: lite_biped_debug

Identical to lite_biped except for a deliberately simplified foot collider:
one 70x130x20 mm box per foot, replacing 11 spheres. Verified flush with the
sole (gap 0.0 mm with the origin rpy applied) and covering the full 130 mm
mesh length. Note the debug foot is much smaller than lite_biped's (130 mm vs
213 mm long), so its support polygon is roughly a third of the area -- a
policy will not transfer between the two variants unchanged.

Link and joint names match lite_biped exactly (the three new CAD parts merge
into the existing left_foot/right_foot links), so ros2_control.json, the MJCF
body list and the training-side tracked-body list all still resolve. physics
.json is byte-identical, keeping timestep 0.005 and the solver settings matched
to mjlab's training config.

Regenerated every variant's MJCF. 1531 tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012QKJQ9hze1hMWXJpdPcjp3
@T-K-233
T-K-233 merged commit 322235c into main Jul 27, 2026
2 checks passed
@T-K-233
T-K-233 deleted the feat/position-actuators-and-biped-debug branch July 27, 2026 00:36
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