"""Robot-agnostic WBC environment template.
Per-robot wiring lives in ``wbc_mjlab.robots.<id>.configs`` (registered via task configs).
"""
from dataclasses import replace
from mjlab.envs import ManagerBasedRlEnvCfg
from mjlab.envs.mdp import dr
from mjlab.envs.mdp.actions import JointPositionActionCfg
from mjlab.managers.action_manager import ActionTermCfg
from wbc_mjlab.env.mdp.actions import ReferenceJointPositionActionCfg
from mjlab.managers.command_manager import CommandTermCfg
from mjlab.managers.event_manager import EventTermCfg
from mjlab.managers.observation_manager import ObservationGroupCfg, ObservationTermCfg
from mjlab.managers.reward_manager import RewardTermCfg
from mjlab.managers.scene_entity_config import SceneEntityCfg
from mjlab.managers.termination_manager import TerminationTermCfg
from mjlab.scene import SceneCfg
from mjlab.sim import MujocoCfg, SimulationCfg
from mjlab.terrains import TerrainEntityCfg
from mjlab.utils.noise import UniformNoiseCfg as Unoise
from mjlab.viewer import ViewerConfig
from wbc_mjlab.env.mdp.assistive_wrench import AssistiveWrenchEvent
from wbc_mjlab.env.mdp.commands import MotionCommandCfg
from wbc_mjlab.env.mdp.sampling import AdaptiveSimilarityTermCfg, RsiCfg
import wbc_mjlab.env.mdp as mdp
VELOCITY_RANGE = {
"x": (-0.5, 0.5),
"y": (-0.5, 0.5),
"z": (-0.2, 0.2),
"roll": (-0.52, 0.52),
"pitch": (-0.52, 0.52),
"yaw": (-0.78, 0.78),
}
_MOTION_COMMAND = "motion"
# --- Motion command / RSI base template (task configs override ``motion_cmd.rsi``). ---
MOTION_POSE_RANGE = {
"x": (-0.05, 0.05),
"y": (-0.05, 0.05),
"z": (-0.01, 0.01),
"roll": (-0.1, 0.1),
"pitch": (-0.1, 0.1),
"yaw": (-0.2, 0.2),
}
MOTION_JOINT_POSITION_RANGE = (-0.1, 0.1)
MOTION_RESAMPLING_TIME_RANGE = (1.0e9, 1.0e9)
BASE_RSI_SIMILARITY_TERMS: tuple[AdaptiveSimilarityTermCfg, ...] = (
AdaptiveSimilarityTermCfg(term="joint_pos", weight=1.0),
)
BASE_RSI_CFG = RsiCfg(
sampling_mode="adaptive",
strategy="similarity_ema",
similarity_terms=BASE_RSI_SIMILARITY_TERMS,
bin_width_s=4.0,
uniform_ratio=0.15,
alpha=0.005,
temperature_base=1.0,
similarity_norm_by_remaining_clip=False,
min_bin_span_ratio=0.0,
persist_failure_levels=False,
failure_levels_filename="rsi_bin_stats.npz",
)
ASSISTIVE_WRENCH_ENABLED = True
ASSISTIVE_BETA_MAX = 0.6
ASSISTIVE_ETA = 0.8
[docs]
def make_base_wbc_env_cfg(
use_reference_residual_action: bool = True,
) -> ManagerBasedRlEnvCfg:
"""Robot-agnostic WBC env template with all manager term slots pre-populated.
Robot builders attach the entity and motion-command body names; presets
(``apply_wbc``, ``apply_zest``, …) finish weights and RSI. Prefer
:func:`wbc_mjlab.robots.env.make_wbc_env_cfg` for a registered robot.
Args:
use_reference_residual_action: If True (default), use
:class:`~wbc_mjlab.env.mdp.actions.ReferenceJointPositionActionCfg`;
otherwise mjlab absolute joint-position actions.
"""
motion = {"command_name": _MOTION_COMMAND}
# Non-SE actor: reference command + proprio only. SE measurements live in
# ``apply_se_actor`` (``presets/se_actor.py``).
# Task configs may drop terms per policy layout (e.g. ``ref_joint_vel`` in wbc/zest).
# BeyondMimic keeps the base template unchanged. SE swaps height/gravity → anchor pose error.
actor_terms = {
"ref_base_height": ObservationTermCfg(
func=mdp.ref_base_height,
params=motion,
noise=Unoise(n_min=-0.01, n_max=0.01),
),
"ref_base_lin_vel_b": ObservationTermCfg(
func=mdp.ref_base_lin_vel_b,
params=motion,
noise=Unoise(n_min=(-0.1, -0.1, -0.05), n_max=(0.1, 0.1, 0.05)),
),
"ref_base_ang_vel_b": ObservationTermCfg(
func=mdp.ref_base_ang_vel_b,
params=motion,
noise=Unoise(n_min=(-0.2, -0.2, -0.3), n_max=(0.2, 0.2, 0.3)),
),
"ref_gravity_b": ObservationTermCfg(
func=mdp.ref_gravity_b,
params=motion,
noise=Unoise(n_min=-0.02, n_max=0.02),
),
"ref_joint_pos": ObservationTermCfg(
func=mdp.ref_joint_pos,
params=motion,
noise=Unoise(n_min=-0.05, n_max=0.05),
),
"ref_joint_vel": ObservationTermCfg(
func=mdp.ref_joint_vel,
params=motion,
noise=Unoise(n_min=-0.25, n_max=0.25),
),
"base_ang_vel": ObservationTermCfg(
func=mdp.builtin_sensor,
params={"sensor_name": ""},
noise=Unoise(n_min=-0.2, n_max=0.2),
),
"projected_gravity": ObservationTermCfg(
func=mdp.projected_gravity,
noise=Unoise(n_min=-0.05, n_max=0.05),
),
"joint_pos": ObservationTermCfg(
func=mdp.joint_pos_rel,
noise=Unoise(n_min=-0.01, n_max=0.01),
),
"joint_vel": ObservationTermCfg(
func=mdp.joint_vel_rel,
noise=Unoise(n_min=-1.5, n_max=1.5),
),
"actions": ObservationTermCfg(func=mdp.last_action),
}
# Critic: actor (no noise) + privileged keybody / contact features.
critic_terms = {
**{
k: ObservationTermCfg(func=v.func, params=dict(v.params) if v.params else {})
for k, v in actor_terms.items()
},
"motion_anchor_pos_b": ObservationTermCfg(
func=mdp.motion_anchor_pos_b, params={"command_name": "motion"}
),
"motion_anchor_ori_b": ObservationTermCfg(
func=mdp.motion_anchor_ori_b, params={"command_name": "motion"}
),
"motion_anchor_pos_error_w": ObservationTermCfg(
func=mdp.motion_anchor_pos_error_w, params={"command_name": "motion"}
),
"motion_anchor_ori_error": ObservationTermCfg(
func=mdp.motion_anchor_ori_error, params={"command_name": "motion"}
),
"ref_body_pos": ObservationTermCfg(
func=mdp.ref_body_pos_b, params={"command_name": "motion"}
),
"ref_body_ori": ObservationTermCfg(
func=mdp.ref_body_ori_b, params={"command_name": "motion"}
),
"body_pos": ObservationTermCfg(
func=mdp.robot_body_pos_b, params={"command_name": "motion"}
),
"body_ori": ObservationTermCfg(
func=mdp.robot_body_ori_b, params={"command_name": "motion"}
),
"ref_body_lin_vel": ObservationTermCfg(
func=mdp.ref_body_lin_vel, params={"command_name": "motion"}
),
"ref_body_ang_vel": ObservationTermCfg(
func=mdp.ref_body_ang_vel, params={"command_name": "motion"}
),
"motion_body_lin_vel": ObservationTermCfg(
func=mdp.motion_body_lin_vel, params={"command_name": "motion"}
),
"motion_body_ang_vel": ObservationTermCfg(
func=mdp.motion_body_ang_vel, params={"command_name": "motion"}
),
"ref_joint_vel": ObservationTermCfg(
func=mdp.ref_joint_vel, params={"command_name": "motion"}
),
"ref_base_lin_acc": ObservationTermCfg(
func=mdp.ref_base_lin_acc_b, params={"command_name": "motion"}
),
"ref_base_ang_acc": ObservationTermCfg(
func=mdp.ref_base_ang_acc_b, params={"command_name": "motion"}
),
"base_lin_vel": ObservationTermCfg(
func=mdp.base_lin_vel,
params={"asset_cfg": SceneEntityCfg("robot")},
),
"assistive_force": ObservationTermCfg(
func=mdp.assistive_wrench_force,
params={"command_name": "motion"},
),
"assistive_torque": ObservationTermCfg(
func=mdp.assistive_wrench_torque,
params={"command_name": "motion"},
),
"assistive_gain": ObservationTermCfg(
func=mdp.assistive_wrench_gain,
params={"command_name": "motion"},
),
"motion_segment_phase": ObservationTermCfg(
func=mdp.motion_segment_phase,
params={"command_name": _MOTION_COMMAND},
),
"motion_tracking_step_rewards": ObservationTermCfg(
func=mdp.motion_tracking_step_rewards,
params={"command_name": _MOTION_COMMAND},
),
}
observations = {
"actor": ObservationGroupCfg(
terms=actor_terms,
concatenate_terms=True,
enable_corruption=True,
),
"critic": ObservationGroupCfg(
terms=critic_terms,
concatenate_terms=True,
enable_corruption=False,
),
}
if use_reference_residual_action:
joint_pos_action = ReferenceJointPositionActionCfg(
entity_name="robot",
actuator_names=(".*",),
scale=0.25,
command_name="motion",
)
else:
joint_pos_action = JointPositionActionCfg(
entity_name="robot",
actuator_names=(".*",),
scale=0.25,
use_default_offset=True,
)
actions: dict[str, ActionTermCfg] = {"joint_pos": joint_pos_action}
commands: dict[str, CommandTermCfg] = {
"motion": MotionCommandCfg(
entity_name="robot",
resampling_time_range=MOTION_RESAMPLING_TIME_RANGE,
debug_vis=True,
pose_range=MOTION_POSE_RANGE,
velocity_range=VELOCITY_RANGE,
joint_position_range=MOTION_JOINT_POSITION_RANGE,
rsi=replace(BASE_RSI_CFG),
assistive_wrench_enabled=ASSISTIVE_WRENCH_ENABLED,
assistive_beta_max=ASSISTIVE_BETA_MAX,
assistive_eta=ASSISTIVE_ETA,
motion_file="",
anchor_body_name="",
body_names=(),
)
}
events: dict[str, EventTermCfg] = {
"assistive_wrench": EventTermCfg(
func=AssistiveWrenchEvent,
mode="step",
params={
"command_name": "motion",
"asset_cfg": SceneEntityCfg("robot", body_names=()),
"body_name": "",
"kvp": 1.0,
"kvd": 10.0,
"kwp": 200.0,
"kwd": 10.0,
"enabled": True,
},
),
"push_robot": EventTermCfg(
func=mdp.push_by_setting_velocity,
mode="interval",
interval_range_s=(1.0, 3.0),
params={"velocity_range": VELOCITY_RANGE},
),
"base_com": EventTermCfg(
mode="startup",
func=dr.body_com_offset,
params={
"asset_cfg": SceneEntityCfg("robot", body_names=()),
"operation": "add",
"ranges": {
0: (-0.025, 0.025),
1: (-0.05, 0.05),
2: (-0.05, 0.05),
},
},
),
"pull_robot": EventTermCfg(
func=mdp.apply_external_force_torque,
mode="interval",
interval_range_s=(1.0, 4.0),
params={
"asset_cfg": SceneEntityCfg("robot", body_names=()),
"force_range": (-0.0, 0.0),
"torque_range": (-0.0, 0.0),
},
),
"encoder_bias": EventTermCfg(
mode="startup",
func=dr.encoder_bias,
params={"asset_cfg": SceneEntityCfg("robot"), "bias_range": (-0.01, 0.01)},
),
"foot_friction": EventTermCfg(
mode="startup",
func=dr.geom_friction,
params={
"asset_cfg": SceneEntityCfg("robot", geom_names=()),
"operation": "abs",
"ranges": (0.3, 1.5),
"shared_random": True,
},
),
}
# Tracking rewards (std/weights aligned with mjlab ``tracking_env_cfg``).
rewards: dict[str, RewardTermCfg] = {
"motion_global_root_pos": RewardTermCfg(
func=mdp.motion_global_anchor_position_error_exp,
weight=0.5,
params={"command_name": "motion", "std": 0.5},
),
"motion_global_root_ori": RewardTermCfg(
func=mdp.motion_global_anchor_orientation_error_exp,
weight=1.0,
params={"command_name": "motion", "std": 0.4},
),
"motion_root_lin_vel_b": RewardTermCfg(
func=mdp.motion_anchor_linear_velocity_body_error_exp,
weight=1.0,
params={"command_name": "motion", "std": 1.0},
),
"motion_root_ang_vel_b": RewardTermCfg(
func=mdp.motion_anchor_angular_velocity_body_error_exp,
weight=1.0,
params={"command_name": "motion", "std": 1.5},
),
"motion_body_pos": RewardTermCfg(
func=mdp.motion_relative_body_position_error_exp,
weight=1.5,
params={"command_name": "motion", "std": 0.15},
),
"motion_body_ori": RewardTermCfg(
func=mdp.motion_relative_body_orientation_error_exp,
weight=1.0,
params={"command_name": "motion", "std": 0.4},
),
"motion_body_lin_vel": RewardTermCfg(
func=mdp.motion_global_body_linear_velocity_error_exp,
weight=1.0,
params={"command_name": "motion", "std": 1.0},
),
"motion_body_ang_vel": RewardTermCfg(
func=mdp.motion_global_body_angular_velocity_error_exp,
weight=1.0,
params={"command_name": "motion", "std": 3.14},
),
"motion_joint_pos": RewardTermCfg(
func=mdp.motion_joint_position_error_exp,
weight=1.0,
params={"command_name": "motion", "std": 0.5},
),
"motion_joint_vel": RewardTermCfg(
func=mdp.motion_joint_velocity_error_exp,
weight=0.5,
params={"command_name": "motion", "std": 2.0},
),
"action_rate_l1": RewardTermCfg(func=mdp.action_rate_l1, weight=-0.08),
"joint_acc": RewardTermCfg(
func=mdp.joint_acc_l1,
weight=-2.0e-6,
params={"asset_cfg": SceneEntityCfg("robot", joint_names=(".*",))},
),
# "neg_regen_power": RewardTermCfg(
# func=mdp.negative_mechanical_power_l2,
# weight=0.0,
# params={
# "asset_cfg": SceneEntityCfg("robot"),
# "power_deadband": 0.0,
# "penalty_scale": 1.0,
# },
# ),
"joint_limit": RewardTermCfg(
func=mdp.joint_pos_limits,
weight=-10.0,
params={"asset_cfg": SceneEntityCfg("robot", joint_names=(".*",))},
),
"survival": RewardTermCfg(func=mdp.is_alive, weight=1.0),
"foot_slip": RewardTermCfg(
func=mdp.feet_slip,
weight=-0.0,
params={
"sensor_name": "feet_ground_contact",
"asset_cfg": SceneEntityCfg("robot", site_names=()),
},
),
"actuator_torque_soft_limit": RewardTermCfg(
func=mdp.actuator_torque_soft_limit,
weight=0.0,
params={"soft_ratio": 0.95, "asset_cfg": SceneEntityCfg("robot")},
),
"angular_momentum": RewardTermCfg(
func=mdp.angular_momentum_penalty,
weight=0.0,
params={"sensor_name": "robot/root_angmom", "axes": "xy"},
),
"anti_shake": RewardTermCfg(
func=mdp.anti_shake_ang_vel_l2,
weight=0.0,
params={
"command_name": "motion",
"threshold": 5.0,
"body_names": (),
},
),
# "self_collisions": RewardTermCfg(
# func=mdp.self_collision_cost,
# weight=-10.0,
# params={"sensor_name": "self_collision", "force_threshold": 10.0},
# ),
}
# Common tracking terminations; ``ee_body_pos`` is full 3D on end effectors (wired in base).
terminations: dict[str, TerminationTermCfg] = {
"time_out": TerminationTermCfg(func=mdp.time_out, time_out=True),
"anchor_pos": TerminationTermCfg(
func=mdp.bad_anchor_pos_z_only,
params={"command_name": "motion", "threshold": 0.25},
),
"anchor_ori": TerminationTermCfg(
func=mdp.bad_anchor_ori,
params={
"asset_cfg": SceneEntityCfg("robot"),
"command_name": "motion",
"threshold": 0.8,
},
),
"ee_body_pos": TerminationTermCfg(
func=mdp.bad_motion_body_pos_z_only,
params={
"command_name": "motion",
"threshold": 0.25,
"body_names": (),
},
),
}
return ManagerBasedRlEnvCfg(
scene=SceneCfg(terrain=TerrainEntityCfg(terrain_type="plane"), num_envs=8192),
observations=observations,
actions=actions,
commands=commands,
events=events,
rewards=rewards,
terminations=terminations,
viewer=ViewerConfig(
origin_type=ViewerConfig.OriginType.ASSET_BODY,
entity_name="robot",
body_name="",
distance=2.8,
fovy=55.0,
elevation=-5.0,
azimuth=120.0,
),
sim=SimulationCfg(
nconmax=64,
njmax=250,
mujoco=MujocoCfg(timestep=0.005, iterations=10, ls_iterations=20),
),
decimation=4,
episode_length_s=10.0,
)