Train a humanoid to follow a reference motion clip. The shipping example is
Unitree G1 whole-body tracking on flat terrain, registered as
Genesis-Tracking-Flat-G1-v0.
mocap / AMASS ──► retargeted CSV ──► baked NPZ ──► MotionCommand ──► RL training
(GMR, external) (fk_to_npz) (loader) (rsl_rl)
Three discrete steps, described below.
GenesisLab does not ship a retargeter. Motion retargeting — mapping a source skeleton (SMPL / AMASS / LAFAN1 / raw mocap) onto the target robot's URDF — is handled by an external tool.
Recommended: general-motion-retargeting (GMR) — produces per-frame root pose + joint angles aligned with the target robot's DoF layout.
The expected handoff format is a CSV with one row per frame and columns:
base_pos_x, base_pos_y, base_pos_z, # 3
base_quat_x, base_quat_y, base_quat_z, base_quat_w, # 4 (xyzw)
joint_pos_0, joint_pos_1, ..., joint_pos_{N-1} # N
The base quaternion is xyzw (SciPy / IsaacLab convention); fk_to_npz.py
converts to wxyz internally. For G1 the joint order must match
G1_JOINT_NAMES in
source/.../tracking/scripts/fk_to_npz.py (29 DoFs, left-leg →
right-leg → waist → left-arm → right-arm). Any other order produces garbage.
A reference clip is bundled for smoke testing:
data/datasets/dance1_subject1.csv (6574 frames @ 30 Hz).
Run the retargeted joints through Genesis forward-kinematics, interpolate to
the training control frequency, and bake every link's world pose + velocity
into a single .npz file:
python source/genesis_tasks/genesis_tasks/imitation/tracking/scripts/fk_to_npz.py \
--input-dir ./data/datasets \
--output-dir ./data/datasets \
--input-fps 30 \
--output-fps 50Every CSV in --input-dir is converted; add --window to watch the replay.
The script writes <name>.npz with these keys (T = number of output frames):
| Key | Shape | Notes |
|---|---|---|
fps |
scalar | Output FPS (50 by default) |
joint_pos |
(T, 29) |
Joint angles, robot DoF order |
joint_vel |
(T, 29) |
Finite-difference joint velocities |
body_pos_w |
(T, 30, 3) |
World-frame positions of all 30 links |
body_quat_w |
(T, 30, 4) |
Link orientations, wxyz |
body_rot_w |
(T, 3) |
Base Euler XYZ (convenience only) |
body_lin_vel_w |
(T, 30, 3) |
World-frame linear velocities |
body_ang_vel_w |
(T, 30, 3) |
World-frame angular velocities |
The link order comes from the G1 USD asset and must not be reshuffled — the
tracking config indexes into body_pos_w by link name via the env's body
registry.
The motion file path lives on the tracking command:
# source/genesis_tasks/genesis_tasks/imitation/tracking/components.py
motion: mdp.MotionCommandCfg = mdp.MotionCommandCfg(
asset_name="robot",
motion_file="data/datasets/dance1_subject1.npz", # ← change here
anchor_body_name=MISSING, # set per robot (e.g. "torso_link" for G1)
body_names=MISSING, # list of links to track (G1 tracks 14)
...
)Robot-specific overrides live in
.../tracking/robots/g1/g1_tracking_env_cfg.py — that's where the
G1 tracking task pins anchor_body_name="torso_link" and the 14-body
tracking list (pelvis, ankles, knees, torso, shoulders, elbows, wrists).
To train on a different clip, either:
- Point
motion_fileat your new.npz, or - Subclass
G1FlatEnvCfgand overridecommands.motion.motion_filein__post_init__.
The loader class is MotionCommand in
.../tracking/mdp/commands.py; reward + observation terms live
alongside it in the same mdp/ package.
python scripts/reinforcement_learning/rsl_rl/train.py \
--env-id Genesis-Tracking-Flat-G1-v0 \
--num-envs 4096 \
--num-iters 5000Variants registered for G1:
| Env ID | Notes |
|---|---|
Genesis-Tracking-Flat-G1-v0 |
Full observations (with anchor pos + base lin vel) |
Genesis-Tracking-Flat-G1-Wo-State-Estimation-v0 |
Drops anchor pos + base lin vel from policy obs |
Genesis-Tracking-Flat-G1-Low-Freq-v0 |
Lower control rate (higher decimation) |
Play back a trained checkpoint with the standard play.py:
python scripts/reinforcement_learning/rsl_rl/play.py \
--env-id Genesis-Tracking-Flat-G1-v0 \
--window \
--num-envs 1 \
--checkpoint <PATH_TO_CHECKPOINT>The tracking env itself is robot-agnostic — it just needs an NPZ with matching joint count and the link names referenced in the robot-specific cfg. To add a new humanoid:
- Retarget your reference motion with GMR (or an equivalent) onto the new robot's URDF.
- Copy
fk_to_npz.py, swapG1_FULL_ACT_CFG+G1_JOINT_NAMESfor the new robot's asset and DoF order. - Subclass
TrackingEnvCfg, setanchor_body_nameandbody_namesto valid link names for that robot, and register a new gym ID.
Robot collapses / wrong posture at reset. Almost always a joint-order
mismatch between the CSV and G1_JOINT_NAMES. Log the first frame's
joint_pos and verify left-leg joints are in the first 6 slots.
Base orientation looks mirrored / flipped. The CSV base quaternion is
xyzw but the NPZ stores wxyz. If you bypass fk_to_npz.py and write NPZ
directly, remember to convert.
Training diverges immediately. Check body_names in the robot cfg —
every name must exist in the asset's link registry. A typo silently selects
index 0 (usually the base) and the reward signal becomes nonsense.
"Motion file not found". motion_file is resolved relative to the
working directory when the env is built, not relative to the cfg file. Run
training from the repo root, or pass an absolute path.