"""Tests specific to motion tracking tasks.""" import pytest from mjlab.asset_zoo.robots import G1_ACTION_SCALE from mjlab.envs.mdp.actions import JointPositionActionCfg from mjlab.tasks.registry import list_tasks, load_env_cfg from mjlab.tasks.tracking.mdp import MotionCommandCfg @pytest.fixture(scope="module") def tracking_task_ids() -> list[str]: """Get all tracking task IDs.""" return [t for t in list_tasks() if "Tracking" in t] @pytest.fixture(scope="module") def g1_tracking_task_ids(tracking_task_ids: list[str]) -> list[str]: """Get all G1 tracking task IDs.""" return [t for t in tracking_task_ids if "G1" in t] def test_tracking_tasks_have_motion_command(tracking_task_ids: list[str]) -> None: """All tracking tasks should have a 'motion' command of type MotionCommandCfg.""" for task_id in tracking_task_ids: cfg = load_env_cfg(task_id) assert "motion" in cfg.commands, f"Task {task_id} missing 'motion' command" motion_cmd = cfg.commands["motion"] assert isinstance(motion_cmd, MotionCommandCfg), ( f"Task {task_id} motion command is not MotionCommandCfg" ) def test_tracking_tasks_have_self_collision_sensor( tracking_task_ids: list[str], ) -> None: """All tracking tasks should have a self_collision sensor.""" for task_id in tracking_task_ids: cfg = load_env_cfg(task_id) assert cfg.scene.sensors is not None, f"Task {task_id} has no sensors" sensor_names = {s.name for s in cfg.scene.sensors} assert "self_collision" in sensor_names, ( f"Task {task_id} missing self_collision sensor" ) def test_tracking_no_state_estimation_observations() -> None: """No-state-estimation tasks remove observations that depend on state estimation.""" task_id = "Mjlab-Tracking-Flat-Unitree-G1-No-State-Estimation" # Test both training and play modes for play_mode in [False, True]: cfg = load_env_cfg(task_id, play=play_mode) mode_str = "play mode" if play_mode else "training mode" assert "actor" in cfg.observations, ( f"Task {task_id} ({mode_str}) missing policy observations" ) actor_terms = cfg.observations["actor"].terms assert "motion_anchor_pos_b" not in actor_terms, ( f"Task {task_id} ({mode_str}) has motion_anchor_pos_b in policy, " "expected it to be removed for no-state-estimation variant" ) assert "base_lin_vel" not in actor_terms, ( f"Task {task_id} ({mode_str}) has base_lin_vel in policy, " "expected it to be removed for no-state-estimation variant" ) def test_tracking_play_disables_rsi_randomization() -> None: """Tracking play tasks should disable RSI randomization.""" tracking_tasks = [ "Mjlab-Tracking-Flat-Unitree-G1", "Mjlab-Tracking-Flat-Unitree-G1-No-State-Estimation", ] for task_id in tracking_tasks: cfg = load_env_cfg(task_id, play=True) motion_cmd = cfg.commands["motion"] assert isinstance(motion_cmd, MotionCommandCfg), ( f"Task {task_id} (play mode) motion command is not MotionCommandCfg" ) assert motion_cmd.pose_range == {}, ( f"Task {task_id} (play mode) has non-empty pose_range={motion_cmd.pose_range}, " "expected empty dict for disabled RSI" ) assert motion_cmd.velocity_range == {}, ( f"Task {task_id} (play mode) has non-empty velocity_range={motion_cmd.velocity_range}, " "expected empty dict for disabled RSI" ) def test_tracking_play_uses_start_sampling_mode() -> None: """Tracking play tasks should use sampling_mode='start'.""" tracking_tasks = [ "Mjlab-Tracking-Flat-Unitree-G1", "Mjlab-Tracking-Flat-Unitree-G1-No-State-Estimation", ] for task_id in tracking_tasks: cfg = load_env_cfg(task_id, play=True) motion_cmd = cfg.commands["motion"] assert isinstance(motion_cmd, MotionCommandCfg), ( f"Task {task_id} (play mode) motion command is not MotionCommandCfg" ) assert motion_cmd.sampling_mode == "start", ( f"Task {task_id} (play mode) sampling_mode={motion_cmd.sampling_mode}, expected 'start'" ) def test_g1_tracking_has_correct_action_scale(g1_tracking_task_ids: list[str]) -> None: """G1 tracking tasks should use G1_ACTION_SCALE.""" for task_id in g1_tracking_task_ids: cfg = load_env_cfg(task_id) assert "joint_pos" in cfg.actions, f"Task {task_id} missing 'joint_pos' action" joint_pos_action = cfg.actions["joint_pos"] assert isinstance(joint_pos_action, JointPositionActionCfg), ( f"Task {task_id} joint_pos action is not JointPositionActionCfg" ) assert joint_pos_action.scale == G1_ACTION_SCALE, ( f"Task {task_id} action scale mismatch, expected G1_ACTION_SCALE" )