A MuJoCo humanoid locomotion stack centered on a Convex Model Predictive Controller (MPC). The control pipeline combines Single Rigid Body (SRB) modeling, contact-wrench optimization, heuristic swing-foot planning with additional touchdown rules, and explicit early/late contact handling to generate actuator torques for walking, standing balance, and in-place turning.
The same controller can be retargeted across robots through robot-specific MuJoCo models, kinematic/contact mappings, and YAML tuning, while the shared locomotion pipeline remains the same.
- SRB-based Model Predictive Control
- Contact-wrench optimization for feasible stance forces and moments
- Heuristic swing-foot planning with Raibert-style placement and touchdown heuristics
- Early/late contact handling for touchdown, liftoff, and recovery
- Shared controller pipeline across robots, with viewer and headless execution modes
Forward Clip velocity: 0.6 m/s
|
Lateral Clip velocity: 0.3 m/s
|
In-place Turn Clip yaw rate: 1.3 rad/s
|
Roll Base orientation tracking |
Pitch Base orientation tracking |
Height Base height tracking |
Combined locomotion demo
|
|
Web dashboard: telemetry plots, command compass, and MuJoCo WASM viewer. See Web Dashboard for setup and implementation details.
Tip
Start with Quick start if you want the build path. Jump to Debugging Probes when you want logs, replay plots, and contact checks.
- Install the requirements below.
- Set up MuJoCo and
vcpkg. - Build from the repository root.
- Launch the main binary with a robot selector and viewer flag.
cmake --preset dev
cmake --build --preset dev -j
./build/apps/main m y- CMake 3.16 or newer
- A C++17 compiler
vcpkg- MuJoCo installed separately
Recommended local layout:
~/.local/vcpkg~/.local/mujoco
Example macOS setup:
mkdir -p ~/.local
cd ~/.local
git clone https://github.com/microsoft/vcpkg.git
cd vcpkg
./bootstrap-vcpkg.sh
echo 'export VCPKG_ROOT="$HOME/.local/vcpkg"' >> ~/.zshrc
echo 'export PATH="$VCPKG_ROOT:$PATH"' >> ~/.zshrc
source ~/.zshrcInstall MuJoCo into ~/.local/mujoco:
cd /path/to/workdir
git clone https://github.com/google-deepmind/mujoco.git
cd mujoco
cmake -S . -B build -DCMAKE_INSTALL_PREFIX="$HOME/.local/mujoco"
cmake --build build -j
cmake --install buildIf you need a specific MuJoCo version, check out the tag you want before configuring, for example git checkout 3.7.0.
Then configure and build:
cmake --preset dev
cmake --build --preset dev -jIf MuJoCo lives somewhere else, point mujoco_DIR in CMakePresets.json at that installation's lib/cmake/mujoco directory before configuring.
flowchart LR
subgraph Config[Configuration]
RobotYAML[Robot YAML\nconfig/<robot>/my_controller.yaml]
SimYAML[Simulation YAML\nconfig/simulation.yaml]
Spec[RobotMujocoSpec\nrobot-specific mapping]
end
subgraph Runtime[Runtime]
Main[apps/main]
Sim[SimulationRunner]
Est[StateEstimator]
Ctrl[My_Controller]
Filter[Command filter\nand gait scheduling]
Swing[SwingFootPlanner]
Ref[ReferenceTrajectory]
MPC[Convex MPC]
Out[LegController / ArmController]
MJ[MuJoCo actuators]
end
RobotYAML --> Main
SimYAML --> Sim
Spec --> Sim
Main --> Sim --> Est --> Ctrl
Ctrl --> Filter
Ctrl --> Keys[Keyboard input]
Keys --> Ctrl
Filter --> Swing --> MPC
Filter --> Ref --> MPC
Est --> MPC
MPC --> Out --> MJ
Important
The concrete values below describe the current MIT Humanoid checked-in tuning. Timing and MPC cadence are YAML-driven, so other robots should be treated as robot-specific tuning targets rather than validated defaults.
| Layer | Rate | Notes |
|---|---|---|
| Physics integration | 0.002 s / 500 Hz |
MuJoCo step from config/simulation.yaml |
| Controller tick | 500 Hz | SimulationRunner -> RobotRunner -> MyController::runController() runs every simulation step |
| Contact manager | 500 Hz | Updates contact overrides and recovery state on each tick |
| Swing-foot planner | 500 Hz | Advances swing trajectories on each tick |
| MPC solve | 50 Hz | MIT default: rebuilds the QP every iterations_between_solve = 10 physics steps |
| MPC horizon sample | 20 ms / 50 Hz |
MIT default: timing.horizon = 0.5 s, timing.horizon_steps = 25 |
| Reference trajectory | 50 Hz | Rebuilt on each scheduled MPC solve |
MIT Humanoid gait timing:
| Cycle | Swing | Stance | Horizon | Horizon steps | dt_mpc |
|---|---|---|---|---|---|
0.5 s |
0.17 s |
0.33 s |
0.5 s |
25 |
20 ms |
Unitree G1 and H1 configs are kept as integration starting points. Their timing, weights, and contact parameters still need robot-specific fine tuning before they should be treated as validated defaults.
| Path | Purpose |
|---|---|
apps/ |
Entry points and robot selection |
common/ |
Shared data structures, estimator, math utilities, and keyboard input |
robot/ |
Controller orchestration and torque aggregation |
sim/ |
MuJoCo runner, robot bindings, and cheater-state reader |
My_Controller/ |
Gait scheduler, touchdown planning, reference generation, MPC, and swing-foot tracking |
config/ |
Robot-specific and simulation YAML configuration |
docs/ |
Technical notes about frames, reference trajectories, gait scheduling, swing planning, and controller conventions |
test/ |
Manual experiments and standing debug tools |
Key technical notes in docs/:
- Web dashboard: browser telemetry, command compass, draggable charts, and MuJoCo WASM viewer setup.
- MPC frame convention: state, wrench, and frame conventions used by the MPC.
- Reference trajectory: desired body-state rollout and command integration.
- Swing-foot touchdown planning: foot placement, touchdown rules, and braking offsets.
- Gait scheduling + contact management: gait phase logic, contact overlays, and MPC constraint inputs.
| Robot | Status | Notes |
|---|---|---|
| MIT Humanoid | Primary validation target | Best documented path and default example configuration; the MIT MJCF and URDF are not distributed in this public repository |
| Unitree G1 | Supported | Uses robot-specific config in config/unitree_robots/g1/ |
| Unitree H1 | Supported | Uses robot-specific config in config/unitree_robots/h1/ |
The main executable takes two arguments:
./build/apps/main <robot> <viewer>For a single launcher that can start the controller and an optional Python dashboard, use:
./scripts/launch_convexmpc.sh <robot> <viewer>When dashboard/app.py is present, the launcher starts it automatically on the first free
dashboard port at or above 8001, then opens the browser unless
CONVEXMPC_DASHBOARD_OPEN_BROWSER=0 is set. If you need a specific port, set
CONVEXMPC_DASHBOARD_PORT.
To run the controller headless with the browser dashboard and embedded MuJoCo WASM viewer:
./scripts/launch_convexmpc.sh --web-viewer m nSee Web dashboard for the Python/Node requirements, standalone viewer mode, shared-memory streams, and implementation details.
Robot selection:
m: MIT Humanoidg: Unitree G1h: Unitree H1
Viewer selection:
y: open the MuJoCo viewern: run headless
Warning
requested_locomotion_mode is read at startup. Edit config/<robot>/my_controller.yaml
and restart the app to switch between modes.
Set requested_locomotion_mode in config/<robot>/my_controller.yaml:
walkingstandinginteractive- start in standing and toggle walking / standing from the keyboard
Example:
requested_locomotion_mode: interactivew / s: forward / backward velocitya / d: left / right velocityq / e: yaw rate left / rightup / down: body height up / downi / k: pitch forward / backwardj / l: roll left / rightt: toggle walking / standing whenrequested_locomotion_mode: interactivespace: clear the command
up / down: up / down velocityi / k: pitch forward / backwardj / l: roll left / rightt: toggle walking / standing whenrequested_locomotion_mode: interactivespace: clear the command
- Keyboard commands are filtered before they reach the controller.
- Walking mode treats planar
x_dot,y_dot, andpsi_dotas velocity commands. The MPC reference is rebuilt from the current estimated state on each solve, while height/roll/pitch remain pose offsets. - Standing mode still ignores
w / s,a / d, andq / e. interactivemode starts in standing, andtswitches between walking and standing.- Walking -> standing first zeroes the command while keeping the walking gait active, then waits until the body has been slow for a few ticks before it starts counting touchdown events or a timeout.
- The active walking limits for
x_dot,y_dot, andpsi_dotcome fromuser_command_filterin YAML.
The main robot configuration lives in config/<robot>/my_controller.yaml.
Key fields:
requested_locomotion_mode: startup mode,walkingorstandingtiming: cycle, swing, stance, horizon length, and horizon stepsmodel: MuJoCo model path and foot end-effector sourcempc: contact model, weights, and solve rateswing: touchdown planner gains, nominal offsets, and braking offsetuser_command_filter: command smoothing and max command limitslocomotion_transition: settle speed / hold ticks, braking timeout, and touchdown count used when braking from walking to standingstartup: initial settle timinglogging: standing MPC debug trigger times
Note
The config files are intentionally small and robot-specific. The shared controller code reads them at startup and keeps the runtime behavior in sync with the YAML snapshot that is also written into each MPC debug log.
The simulation-wide settings live in config/simulation.yaml.
The post-processing wrapper reads one MPC debug JSON and generates four diagnostics: one single-solve SRB replay plus three probes for contact consistency, wrench projection, and receding-horizon behavior.
Capture a log while the app is running:
- Press
Shift+Lonce the robot is in the desired state. - Or set
logging.standing_mpc_debug_trigger_timesinconfig/<robot>/my_controller.yamlto queue standing-mode logs at specific simulation times. - Logs are written under
logs/debug/standing_mpc/orlogs/debug/walking_mpc/.
Warning
Shift+L queues a log for the next scheduled MPC solve, so the JSON is captured after
the controller reaches that solve boundary.
Run the wrapper to generate the full set:
./scripts/run_mpc_debug.sh --standing -n 80
./scripts/run_mpc_debug.sh --walking -n 80
./scripts/run_mpc_debug.sh --all-latest -n 80
./scripts/run_mpc_debug.sh -l logs/debug/walking_mpc/walking_mpc_debug_20260501_140727.json -n 80
./scripts/run_mpc_debug.sh --walking -n 120 --x-dot-final 0.7Each pass writes a report, a CSV where relevant, and the corresponding plot
artifacts under logs/debug/{standing_mpc,walking_mpc}/....
This is a single-solve replay, not a fresh MPC solve. The script reads the logged
x0, A_qp, B_qp, and wrench horizon from the JSON, reconstructs the predicted
state horizon from that MPC solution, and plots the reconstruction against the
stored prediction. Use it to verify that the captured solve is internally
self-consistent.
This probe restores the logged MuJoCo state (full_qpos, full_qvel), applies the
recorded full_tau_command, runs mj_forward, and measures the realized contact
wrench about the same reference point used in the log. The plot compares the QP
desired wrench with the wrench MuJoCo actually realizes.
This analysis forms the leg-jacobian map A and projects the QP wrench through its
pseudoinverse, w_rec = A^+ (A w_qp). The result is the row-space projection of the
desired wrench, so in the common two-5-DoF-leg case it shows which of the 6-D wrench
components fall outside the row space of the available leg Jacobians. If the log does
not contain wrench_to_tau_jacobian, the script rebuilds it from MuJoCo foot Jacobians.
This is the closed-loop replay path. At every rollout step it rebuilds the reference
trajectory, gait constraints, walking foot targets, SRB formulation, and QP, then
advances from the first state of the new solve. The walking foot targets are seeded
from the logged solve and then recomputed as gait phase advances; pass
--fixed-foot-points only for the older fixed-anchor replay. Use --x-dot-final
or --x-dot-rate to sweep the raw forward command during the rollout. The output plots are states.png, wrench.png, and
metrics.png, which show state tracking, first-wrench evolution, and stability
metrics over time.
Tip
If you only need the latest log, ./scripts/run_mpc_debug.sh --all-latest -n 80
runs the full set in one shot.
The controller is intentionally structured to support new humanoid robots with a small integration surface.
To add a robot, prepare:
- A robot YAML under
config/<robot>/with the robot XML path, end-effector source, and tuning. - A
RobotMujocoSpecimplementation undersim/src/models/that maps bodies, foot links, contact sites, joints, and arms. - Robot assets in
models/for the MuJoCo XML, URDF, meshes, and any related files. - Any robot-specific controller tuning in
config/<robot>/my_controller.yaml. - If needed, runtime updates to register the new
RobotTypeand its config path inTypes.h,robot/src/RobotConfig.cpp, andsim/src/models/RobotMujocoSpec.cpp.
The existing examples are:
sim/src/models/MitHumanoidSpec.cppsim/src/models/UnitreeG1Spec.cppsim/src/models/UnitreeH1Spec.cpp
- StateEstimator is still cheater-state based.
- Headless runs continue until interrupted.
- The MIT Humanoid MJCF and URDF are intentionally not shared in this public repository.
- The Unitree G1 and H1 parameters in
config/unitree_robots/{g1,h1}/my_controller.yamlare present, but they are not fully tuned or validated yet; treat them as starting points rather than final gains.











