Skip to content

Commit a0ad877

Browse files
authored
fix(humanoid): apply standing-env zeroing in resample_commands (#777)
The standing-command zeroing wrote `cfg.value[env_ids][final_env_ids, :] = 0.0`, but `cfg.value[env_ids]` is advanced indexing (a copy), so the write was discarded — the rel_standing_envs fraction never had its velocity command zeroed. Three locomotion configs set rel_standing_envs=0.02, so they were silently not getting standing envs (the default 0 masked it). Index by absolute env id: `cfg.value[env_ids[final_env_ids], :] = 0.0`. This matches the known-good mjlab reference (tasks/mjlab/mdp/commands.py:104-112, `stand_ids = env_ids[standing_mask]; self._command[stand_ids] = 0.0`). Adds a backend-free regression test driving the real resample_commands with rel_standing_envs=1.0 (all resampled envs must end zero). Red on pre-fix.
1 parent 548ed49 commit a0ad877

2 files changed

Lines changed: 60 additions & 1 deletion

File tree

roboverse_pack/callback_funcs/humanoid/step_funcs.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,10 @@ def resample_commands(env: EnvTypes, env_states: TensorState = None):
4747
# low_cmd_mask = torch.norm(cfg.value[env_ids, :2], dim=1) < 0.1
4848
random_mask = sample_uniform(0, 1, (len(env_ids),), device=env.device) <= cfg.rel_standing_envs
4949
final_env_ids = random_mask.nonzero(as_tuple=False).flatten()
50-
cfg.value[env_ids][final_env_ids, :] = 0.0
50+
# final_env_ids index INTO env_ids; cfg.value[env_ids] is advanced indexing
51+
# (a copy), so the chained write was a silent no-op — the standing-command
52+
# envs never had their velocity command zeroed. Index by absolute env id.
53+
cfg.value[env_ids[final_env_ids], :] = 0.0
5154

5255
if cfg.heading_command:
5356
env_states = env.get_states(mode="tensor") if env_states is None else env_states
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
"""Regression: resample_commands actually zeroes the standing-env velocity commands.
2+
3+
``resample_commands`` picks a ``rel_standing_envs`` fraction of the just-resampled
4+
envs and zeroes their velocity command so the robot learns to stand still. The
5+
zeroing was written as ``cfg.value[env_ids][final_env_ids, :] = 0.0`` — but
6+
``cfg.value[env_ids]`` is advanced indexing (a copy), so the write was discarded
7+
and the standing command was never applied. With ``rel_standing_envs = 1.0`` every
8+
resampled env must end up with a zero command; this is backend-free (a stub env +
9+
commands cfg, no simulator).
10+
"""
11+
12+
from __future__ import annotations
13+
14+
from types import SimpleNamespace
15+
16+
import pytest
17+
import torch
18+
19+
20+
@pytest.mark.general
21+
def test_standing_envs_command_is_zeroed():
22+
import roboverse_pack.callback_funcs.humanoid.step_funcs as sf
23+
24+
# Velocity ranges kept away from 0 so the pre-fix (un-zeroed) command is
25+
# detectably non-zero.
26+
ranges = SimpleNamespace(
27+
lin_vel_x=(0.5, 1.0),
28+
lin_vel_y=(0.5, 1.0),
29+
ang_vel_yaw=(0.5, 1.0),
30+
heading=(0.5, 1.0),
31+
)
32+
cfg = SimpleNamespace(
33+
value=None,
34+
num_commands=3,
35+
ranges=ranges,
36+
heading_command=False,
37+
rel_standing_envs=1.0, # every resampled env becomes a standing env
38+
resampling_time=0.02,
39+
)
40+
env = SimpleNamespace(
41+
commands_manager=cfg,
42+
num_envs=8,
43+
device="cpu",
44+
step_dt=0.02, # resampling_time / step_dt == 1 -> all envs resampled this step
45+
_episode_steps=torch.zeros(8, dtype=torch.long),
46+
)
47+
48+
sf.resample_commands(env)
49+
50+
# All 8 envs were resampled and are standing -> every command must be zero.
51+
assert cfg.value is not None
52+
assert cfg.value.shape == (8, 3)
53+
assert torch.count_nonzero(cfg.value) == 0, (
54+
"standing-env velocity commands were not zeroed — the advanced-index write "
55+
"is a no-op; index by absolute env id instead."
56+
)

0 commit comments

Comments
 (0)