Sim-to-Real Troubleshooting

A cookbook of symptoms → likely causes → fixes. When a deployment goes sideways, start here.

High-frequency joint oscillation / “buzz”

Likely cause

Check

Fix

State estimator velocity bias

Log base_lin_vel vs ground truth (motion capture)

Tune the state estimator

PD gains too high vs trained

Compare driver Kp/Kd against owner YAML

Match training values; or retrain with realistic Kp/Kd DR

Action latency too low in training

Sweep torque_delay_ms in DR

Retrain with measured-latency × 1.5

Velocity noise too low

Compare encoder σ in sim vs hardware

Increase joint_vel_noise_std in DR

Drift while standing / walking

Likely cause

Check

Fix

IMU bias not calibrated

Robot static, check gyro_bias

Run 30-second calibration before policy start

Foot contact misclassified

Check contact event timestamps

Hysteresis on contact force threshold

Policy succeeds in sim, falls on hardware immediately

Almost always one of:

  1. Joint order swapped. Inspect policy.onnx input width and the joint order in your motor driver against the training scene XML (src/unilab/assets/robots/<robot>/scene_flat.xml).

  2. Action scale unit mismatch. Policy outputs unscaled values; the driver expects rad, but you fed it normalized [-1, 1]. Apply the env.actions.joint_pos.scale / default-angle convention from the training owner YAML before sending targets to the driver, reproducing the owner’s resolved per-actuator scale exactly.

  3. Observation layout mismatch. Compare what your hardware loop assembles against the owner’s env.observations.actor.terms — term order first, then per-term history ordering.

Policy succeeds in MuJoCo but fails in Motrix (or vice versa)

That’s a sim-to-sim problem, not sim-to-real. See Aligning Contact and Friction Between Backends and Reward Parity Across Backends.

What to capture before opening a bug report

When the lab WhatsApp blows up at 11 pm, save the following so the investigation tomorrow takes 30 minutes instead of 4 hours:

  • Full hardware trace (obs / action / wall_clock for the entire run).

  • Sim-side YAML used to train: runs/<run>/config.yaml.

  • policy.onnx and the exact task owner YAML path it was trained from.

  • One sim rollout video using the same seed: eval --seed <same> --render-mode record.

  • A git diff between the run’s commit and main if any.

See also