FTH 2 · From simulator to ground|INEMA.CLUB-PRO

Lesson 6 · 18 min

Sim2sim as a gate before the robot

Before any robot, the policy has to pass in a second simulator and through the same message path the robot uses. It's the official unitree_rl_lab flow: train in Isaac Lab, validate in unitree_mujoco, and only then the G1. In this lesson you build that gate in three stages, with real DDS, and you see the error that most often brings policies down on this crossing.

Start the lesson
Two monitors side by side with the same simulated humanoid robot walking and a network switch between them.

In this lesson you will

  1. 1The gate's three stages and the criterion for each
  2. 2unitree_mujoco + unitree_sdk2_python + CycloneDDS: rt/lowcmd and rt/lowstate
  3. 3Error number 1: joint order (joint_ids_map)
  4. 4A warning that comes from the simulator, not the policy

Goal: run the policy through a three-stage gate: a different robot model and the manufacturer's DDS message path.

01The three-stage gate

Click each block. Fail one stage, and you don't move up.

Click each block in the diagram to see what it does.

A 2026 study with the G1 on construction-site actions evaluated each action in two physics engines before the robot, and only those that passed went to the physical G1 (arXiv 2608.01600, according to robotica-real2sim, checked on 10/14/2026).

02What the crossing tends to break

Real findings from building this gate (10/19/2026). Click each one.

1. Joint order

Isaac Lab orders the joints one way; the robot's SDK, another. The deploy.yaml carries the map (joint_ids_map), and each list comes in its own order: kp and kd in the robot's order, default pose and actions in Isaac Lab's order.

Getting this wrong brings the robot down in 1 s, in both simulators (common error below).

03Set up the manufacturer's path (once)

CycloneDDS 0.10.2, unitree_sdk2_python and unitree_mujoco, pinned to the tested commits. You need cmake and a C compiler if pip does not find a wheel.

Terminal · DDS + SDK + Unitree simulator
F=${FTH:-$HOME/fth}; cd $F && . .venv/bin/activate
pip install -q cyclonedds==0.10.2 2>/dev/null || {
  [ -d cyclonedds ] || git clone -q --filter=blob:none -b releases/0.10.x https://github.com/eclipse-cyclonedds/cyclonedds
  git -C cyclonedds checkout -q 5041f35
  cmake -S cyclonedds -B cyclonedds/build -DCMAKE_INSTALL_PREFIX=$F/cyclonedds/install -DBUILD_EXAMPLES=OFF > /dev/null
  cmake --build cyclonedds/build --target install -j 4 > /dev/null 2>&1
  export CYCLONEDDS_HOME=$F/cyclonedds/install && pip install -q cyclonedds==0.10.2; }
git -C cyclonedds log -1 --format="cyclonedds (C) %H (%ad)" --date=short 2>/dev/null | sed -E 's/([0-9a-f]{7})[0-9a-f]{33}/\1/'
[ -d unitree_sdk2_python ] || git clone -q https://github.com/unitreerobotics/unitree_sdk2_python
git -C unitree_sdk2_python checkout -q 814556d && pip install -q -e unitree_sdk2_python pygame
[ -d unitree_mujoco ] || git clone -q https://github.com/unitreerobotics/unitree_mujoco
git -C unitree_mujoco checkout -q 1eb6642 && git -C unitree_mujoco log -1 --format='unitree_mujoco %h (%ad)' --date=short
python3 -c "import importlib.metadata as m, unitree_sdk2py; print('cyclonedds', m.version('cyclonedds'), '· unitree_sdk2py ok')"
Example from the repository
cyclonedds (C) 5041f35 (2026-03-22)
unitree_mujoco 1eb6642 (2026-09-07)
cyclonedds 0.10.2 · unitree_sdk2py ok

Three lines. The first time on this machine (aarch64), building CycloneDDS took 1 to 2 minutes.

04Run the gate

Without --dds, steps 1 and 2 (deterministic, ~1 min). With --dds, step 3 in real time. --passo changes the unitree_mujoco step.

Terminal · steps 1 and 2 (unitree_mujoco 5 ms step)
F=${FTH:-$HOME/fth}; cd $F/sim2real && . $F/.venv/bin/activate
python3 kit/portao_sim2sim.py; echo "saída: $?"
Terminal · all three steps, with DDS
F=${FTH:-$HOME/fth}; cd $F/sim2real && . $F/.venv/bin/activate
python3 kit/portao_sim2sim.py --passo 0.001 --dds 2>/dev/null; echo "saída: $?"
Terminal · the common mistake: joint order
F=${FTH:-$HOME/fth}; cd $F/sim2real && . $F/.venv/bin/activate
python3 kit/portao_sim2sim.py --ordem sdk; echo "saída: $?"
Example from the repository
política: unitree_rl_lab velocity v0 · ordem das juntas: isaac · 0.5 m/s por 10 s
1 Menagerie (MuJoCo, PD no MJCF)     passou · andou 4.7 m
2 unitree_mujoco (passo 5 ms)        passou · andou 4.8 m
                                     aviso: braços no limite de torque em 99% do tempo
PORTÃO: passou (2 de 2 etapas)
saída: 0

It passed, with the arms warning at the 5 ms step.

05Practice

Nothing goes to the robot without passing here.

E1 · Manufacturer's path ready

10 min

Run the DDS, SDK and unitree_mujoco setup.

Done when: the block ends with cyclonedds 0.10.2 · unitree_sdk2py ok.

E2 · Gate with DDS

3 min

Run the three steps with a 1 ms step.

Done when: python3 kit/portao_sim2sim.py --passo 0.001 --dds 2>/dev/null; echo "saída: $?" → PORTÃO: passou (3 de 3 etapas) and saída: 0.

E3 · The gate catches the error

3 min

Run with the wrong joint order and note at which step and how long until it fails.

Done when: python3 kit/portao_sim2sim.py --ordem sdk; echo "saída: $?" → PORTÃO: reprovou — caiu em t = 1.0 s and saída: 1.

06Lesson quiz

The explanation appears after your answer.

Question 1 of 4

Why does step 3 run the robot in a separate process and talk over DDS?skill

07In three sentences

GateMenagerie, unitree_mujoco, and real-time DDS: three stages, same criteria.
Mistake #1A wrong joint order makes the robot fall in 1 s; check the joint_ids_map.
WarningSeparate what belongs to the simulator (step, integrator) from what belongs to the policy.

Going further

Previous lessonNext lesson