Flagship 03 · Sim-to-real · Isaac Lab · Unitree GO2
Closing the loop after the first deployment
Phoenix trains a locomotion policy in Isaac Lab, carries it to a physical GO2 through a parity-gated pipeline, captures what breaks on hardware, and feeds those failures back into training as a randomized replay distribution.
01 The problem
Most open-source quadruped RL stops at "trained in sim, deployed once." The deployment either works on video or it does not, and either way the failures that happen on hardware never make it back into the training distribution. The gap between a policy that scores well in simulation and one that is trustworthy on a robot is exactly where the interesting engineering lives.
02 Why existing approaches fall short
Domain randomization is applied up front, blind, and generously, in the hope of covering whatever reality does. That is expensive and it is not targeted: the sweep is not informed by any failure the robot actually had. Meanwhile the export path from PyTorch to the runtime on the robot is usually taken on faith, so a numerical discrepancy introduced at export is indistinguishable from a policy that simply did not transfer.
03 Core insight
Gate every stage, and make the hardware failure a training input rather than a postmortem. Phoenix records real failures as trajectories, replays them in simulation under a randomized physics sweep seeded from that specific failure, and fine-tunes on the resulting distribution. The policy then goes back to the robot.
04 Architecture
05 What I built
The whole loop: the Isaac Lab environment layering, the training entry points, the parity-gated export, the ROS 2 deploy stack and its safety layer, the failure detector and logging format, and the replay and fine-tune path. The evidence ledger that classifies each claim as verified, inferred, or not-validated is also mine, and it is what keeps this page honest.
06 Validation and results
07 What the hardware taught me
The slew saturation is the point of the whole project. A policy with a clean simulation record met a per-step rate limit the moment it touched real actuators, and it took a month to root-cause.
The cause was not the robot. Isaac's randomize_actuator_gains
was zeroing the explicit DCMotor actuator gains during training, so the
policy had been learning against a silently wrong actuator model the whole
time. Replacing it with a custom gain-scaling startup term, plus a
per-motor slew hinge penalty, brought the evaluation to 32 of 32 at 3.30%
slew.
A related audit finding is worth stating plainly: the parity gate compares ONNX against TorchScript exports of the same wrapper, so it verifies numeric parity but cannot catch a wrong wrapper. It did not: the exporter was searching for normalizer keys that rsl_rl 3.x never writes, so every pre-2026-05-21 export silently dropped observation normalization. A green gate was not the same as a correct export, and finding that is exactly what the ledger exists for.
08 Limitations
09 Reproducibility
The pipeline is YAML-configured with shell entry points per stage, and the parity gate is part of the deploy verification rather than an afterthought.
10 Next step
Re-export the stand-v3-h25 deliverable through the corrected exporter, re-run the parity gate, and retry the stand on hardware with the slew path instrumented. If it clears, the captured failure parquets finally feed the replay curriculum and the loop closes for the first time on real data.