Akshay Parkhi's Weblog

Teaching a Humanoid Robot to Wave: Custom Motions with GEAR-SONIC

24th February 2026

GEAR-SONIC can track arbitrary motions, not only its built-in locomotion styles. You do this in three stages:

  1. Define joint angles in a CSV.
  2. Preview them with direct replay in MuJoCo.
  3. Deploy them through the SONIC neural network.

Here’s how the three-stage pipeline works.

Stage 1: Create the Motion

The script create_motion.py lets you define joint angles for each frame at 50 Hz. The Unitree G1 has 29 joints in IsaacLab ordering:

Legs (0-11) → Waist (12-14) → Left Arm (15-21) → Right Arm (22-28)

For example, you build a wave gesture frame by frame with a smooth envelope that ramps 0→1, holds, then ramps 1→0:

for each frame:
    t = frame / 50Hz
    env = smooth_envelope(t)          # ramps up 0→1, holds, ramps down 1→0

    poses[R_SH_P]  = env * -0.8       # shoulder pitch (arm slightly forward)
    poses[R_SH_R]  = env * -1.2       # shoulder roll (arm out to side)
    poses[R_ELB]   = env * 1.5        # elbow bent 90° (forearm up)
    poses[R_WR_Y]  = env * sin(2.5Hz) # wrist oscillates side-to-side

This outputs six CSV files:

wave/
├── joint_pos.csv      (250 × 29) — joint angles per frame
├── joint_vel.csv      (250 × 29) — finite-difference velocities
├── body_pos.csv       (250 × 42) — 14 body positions (x,y,z)
├── body_quat.csv      (250 × 56) — 14 body orientations (w,x,y,z)
├── body_lin_vel.csv   (250 × 42) — body linear velocities
├── body_ang_vel.csv   (250 × 42) — body angular velocities
└── metadata.txt       — body part indexes, frame count

250 frames at 50 Hz gives a 5-second motion clip. The script precomputes every quantity the SONIC encoder needs and writes it to disk:

  • joint positions
  • velocities
  • body orientations
  • angular velocities

Stage 2: Visualize the Motion

Before involving any neural network, you verify the motion with direct replay:

python visualize_motion.py --motion_dir wave/

This script does four things:

  1. Reads joint_pos.csv.
  2. Reorders the joints from IsaacLab ordering to MuJoCo ordering.
  3. Sets qpos directly.
  4. Renders.

It uses no policy and no controller. It only applies raw joint angles frame by frame.

The reordering is necessary because IsaacLab and MuJoCo number joints differently:

isaaclab_to_mujoco = [0,3,6,9,13,17, 1,4,7,10,14,18, 2,5,8,11,15,19, ...]

What you see here is the ground truth: exactly the motion you defined. If it looks wrong at this stage, fix the CSV before you continue.

Stage 3: Deploy Through SONIC

This is the full pipeline with the neural network. It runs in two terminals:

# Terminal 1: MuJoCo physics simulation
python gear_sonic/scripts/run_sim_loop.py

# Terminal 2: SONIC C++ inference with your motion data
bash deploy.sh --motion-data wave/ sim

Here’s what happens inside the SONIC C++ binary every 20ms (50 Hz):

┌──────────────────────────────────────────────────────┐
│  1. Read CSV files (joint_pos, body_quat, etc.)      │
│                                                      │
│  2. Every 20ms (50Hz):                               │
│     ┌──────────────────────────────────────────┐     │
│     │ Planner assembles reference:             │     │
│     │  - next motion reference frames from CSV │     │
│     │  - current robot joint positions         │     │
│     │  - current robot joint velocities        │     │
│     │  - body angular velocity                 │     │
│     │  - last actions                          │     │
│     └──────────────────┬───────────────────────┘     │
│                        ↓                             │
│     ┌──────────────────────────────────────────┐     │
│     │ Encoder (TensorRT GPU):                  │     │
│     │  290D motion joints + velocities         │     │
│     │  → 64D latent token                      │     │
│     └──────────────────┬───────────────────────┘     │
│                        ↓                             │
│     ┌──────────────────────────────────────────┐     │
│     │ Decoder (TensorRT GPU):                  │     │
│     │  latent + robot state → 29 joint targets │     │
│     └──────────────────┬───────────────────────┘     │
│                        ↓                             │
│     Send motor commands via DDS                      │
└──────────────────────────────────────────────────────┘
                         ↓
┌──────────────────────────────────────────────────────┐
│  MuJoCo (Terminal 1):                                │
│  Receive motor commands (kp, kd, q_target)           │
│  torque = kp*(q_target - q) + kd*(dq_target - dq)   │
│  Step physics → render                               │
└──────────────────────────────────────────────────────┘

Direct Replay vs Neural Network

This is the critical distinction: SONIC does not directly replay your CSV. It uses your motion as a hint to a trained neural network. The encoder compresses the motion reference into a 64-dimensional latent token. The decoder combines that latent with the robot’s actual physical state to produce joint targets that are physically feasible. These targets respect balance, joint limits, and contact dynamics.

The policy decides how closely to follow your motion. It was trained primarily on locomotion data, so:

  • Leg tracking: accurate. The policy saw thousands of walking/running examples in training.
  • Arm gestures: may be attenuated. The policy is conservative about upper-body motions it did not see during training.

This is a feature, not a bug. The neural network keeps the robot balanced even if your motion CSV would make it fall with direct replay.

Summary

StageWhat It DoesCommand
CreateDefine joint angles at 50 Hz, output CSVspython create_motion.py
VerifyDirect replay in MuJoCo — no neural netpython visualize_motion.py --motion_dir wave/
DeployThrough SONIC neural networkbash deploy.sh --motion-data wave/ sim

Create defines the intent. Verify shows that the intent looks right. Deploy runs it through a physics-aware neural network that keeps the robot on its feet.

Links

More recent articles

This is Teaching a Humanoid Robot to Wave: Custom Motions with GEAR-SONIC by Akshay Parkhi, posted on 24th February 2026.

Next: Smartphone Photos to Synthetic Training Data: A 3D Reconstruction Pipeline

Previous: VLA → WBC → MuJoCo: Two Ways to Wire Up NVIDIA's GR00T Humanoid Stack