Skip to content

Commit a6cacd0

Browse files
committed
fix(mjlab): go1 Newton can finally stand (PD actuators + init pose + action order)
Three stacked bugs kept go1 collapsed on the Newton path (so 'go1 walks' was never real): - go1.xml has NO <actuator> elements (mjlab adds them at runtime). The Newton RobotCfg path loaded the raw XML -> mj nu=0 -> zero position-control torque -> free-fall. Now patch_mjcf_with_pd_actuators(go1.xml, GO1_KP) on the Newton path too (nu=12), matching the mujoco scene path. - init height 0.42 -> 0.278 (mjlab INIT_STATE) + hip 0.0 -> ±0.1 (R/L). - action target was emitted in cfg joint order but Newton applies tensor actions in get_joint_names(sort=True) order -> PD targets scrambled to wrong joints. Reorder cfg->sorted in _apply_action. Verified: go1 holds z~0.30 stable under zero action (was collapsing to 0.05); 12/12 mujoco + 12/12 Newton smoke still passes (no regression). Depends on the MetaSim bare-joint-name alias fix. Retraining in progress to verify walking.
1 parent 9b943a3 commit a6cacd0

2 files changed

Lines changed: 47 additions & 10 deletions

File tree

roboverse_pack/robots/mjlab_go1_cfg.py

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,22 @@
2626
"RL_calf_joint",
2727
)
2828

29-
# Default stance (matches velocity_go1_v2._GO1_DEFAULT_POSE_NP)
30-
_GO1_DEFAULTS = {
31-
name: val
32-
for name, val in zip(
33-
_GO1_JOINTS,
34-
[0.0, 0.9, -1.8] * 4,
35-
)
36-
}
29+
# Default stance — MUST match mjlab INIT_STATE (go1_constants.py): thigh=0.9,
30+
# calf=-1.8, and asymmetric hips R_hip=+0.1 / L_hip=-0.1. The earlier symmetric
31+
# hip=0.0 here (Newton path never got the r7 ±0.1 fix) gave an unstable stance
32+
# that, together with the wrong 0.42 init height, made go1 collapse under PD on
33+
# Newton (couldn't even stand). FR/RR are the "R" legs, FL/RL the "L" legs.
34+
def _go1_default(name: str) -> float:
35+
if "thigh" in name:
36+
return 0.9
37+
if "calf" in name:
38+
return -1.8
39+
if "hip" in name:
40+
return 0.1 if name[1] == "R" else -0.1
41+
return 0.0
42+
43+
44+
_GO1_DEFAULTS = {name: _go1_default(name) for name in _GO1_JOINTS}
3745

3846

3947
@configclass
@@ -72,5 +80,5 @@ class MjlabGo1Cfg(RobotCfg):
7280
default_joint_positions: dict[str, float] = _GO1_DEFAULTS
7381
default_joint_velocities: dict[str, float] = {n: 0.0 for n in _GO1_JOINTS}
7482
control_type: dict[str, Literal["position", "effort"]] = {n: "position" for n in _GO1_JOINTS}
75-
default_pos: tuple[float, float, float] = (0.0, 0.0, 0.42)
83+
default_pos: tuple[float, float, float] = (0.0, 0.0, 0.278) # mjlab INIT_STATE height (was 0.42 -> 14cm drop -> collapse)
7684
default_rot: tuple[float, float, float, float] = (1.0, 0.0, 0.0, 0.0)

roboverse_pack/tasks/mjlab/velocity_go1_v2.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from __future__ import annotations
2727

2828
import math
29+
import os
2930

3031
import mujoco
3132
import numpy as np
@@ -491,6 +492,23 @@ def __init__(self, scenario: ScenarioCfg | None = None, device: str | torch.devi
491492
sim = getattr(scenario, "simulator", None) if scenario else None
492493
if scenario is not None and sim == "mujoco":
493494
scenario.robots = []
495+
elif scenario is not None and sim == "newton":
496+
# CRITICAL: go1.xml has NO <actuator> elements (mjlab adds them at
497+
# runtime). Without actuators (mj nu=0) the Newton SolverMuJoCo
498+
# applies ZERO position-control torque -> go1 free-falls. The mujoco
499+
# scene path patches PD actuators in; the Newton RobotCfg path must
500+
# do the same. Point the go1 robot at an actuator-patched MJCF so
501+
# nu=12 and the PD (kp=GO1_KP) actually holds the stance.
502+
for r in scenario.robots:
503+
mjcf = getattr(r, "mjcf_path", None)
504+
if getattr(r, "name", None) == "go1" and mjcf:
505+
src = mjcf if os.path.isabs(mjcf) else os.path.abspath(mjcf)
506+
try:
507+
r.mjcf_path = patch_mjcf_with_pd_actuators(src, GO1_KP)
508+
except Exception as e: # noqa: BLE001
509+
import warnings
510+
511+
warnings.warn(f"go1 Newton actuator patch failed ({e}); go1 will not stand", RuntimeWarning)
494512
self.num_actions = 12
495513
# Cache joint qpos/qvel indices for fast write in _apply_action
496514
# (lazy — set on first call, since handler isn't constructed yet)
@@ -643,7 +661,18 @@ def _apply_action(self, processed_action: torch.Tensor) -> None:
643661
"""
644662
target = self.action_manager.process(processed_action)
645663
if not hasattr(self.handler, "physics"):
646-
self.handler.set_dof_targets(target)
664+
# The action_manager produces targets in cfg (_GO1_JOINTS) order, but
665+
# the Newton handler's tensor set_dof_targets applies them in
666+
# get_joint_names(sort=True) (alphabetical) order. Without reordering,
667+
# PD targets land on the WRONG joints -> go1 collapses. Build the
668+
# cfg->sorted permutation once and reorder.
669+
if getattr(self, "_cfg_to_sorted", None) is None:
670+
sorted_bare = [n.split("/")[-1] for n in self.handler.get_joint_names("go1", sort=True)]
671+
cfg_order = list(_GO1_JOINTS.joint_names)
672+
self._cfg_to_sorted = torch.tensor(
673+
[cfg_order.index(b) for b in sorted_bare], device=target.device, dtype=torch.long
674+
)
675+
self.handler.set_dof_targets(target[..., self._cfg_to_sorted])
647676
return
648677
target_np = target.detach().cpu().numpy().reshape(-1)
649678
self.handler.physics.data.ctrl[:12] = target_np

0 commit comments

Comments
 (0)