An interactive MuJoCo workspace for building and previewing a small quadruped robot. The robot and its scene are authored as Jinja2 XML templates, then rendered in a reactive Marimo notebook.
- uv for Python and dependency management
- Python 3.14 or newer (uv installs a compatible Python automatically when needed)
- A graphical environment with OpenGL support for MuJoCo rendering
Clone the repository and enter it:
git clone <repository-url>
cd catbot_mujocoCreate the project environment and install the locked dependencies:
uv syncuv sync creates .venv/ and installs Marimo, MuJoCo, Jinja2, Pillow, and the development tooling declared in pyproject.toml.
Start the interactive notebook from the repository root:
uv run notebookThe notebook command launches uv run marimo edit notebook/main.py --watch. Marimo prints a local URL; open it in a browser to view the generated MuJoCo XML and rendered robot. Keep the command running while you work.
Editing either file in assets/ automatically refreshes the dependent notebook cells:
# Robot body, joints, geometry, and appearance
assets/robot.xml.j2
# MuJoCo scene wrapper, lighting, and robot insertion point
assets/world.xml.j2main.py is the root-level launcher used by the notebook command. Running uv run python main.py starts the same watched Marimo editor.
For a persistent browser video preview, open the dedicated livestream notebook:
uv run marimo edit notebook/livestream.py --watchIt starts a localhost-only backend, streams the simulation as fragmented H.264 MP4, and embeds the video in a Marimo iframe. The notebook provides Start / Stop and Reset controls. This mode requires a system ffmpeg installation with libx264 support.
For training log, you can use tensor board:
uv run tensorboard --logdir runs.
├── assets/
│ ├── robot.xml.j2 # Jinja template for the Catbot quadruped
│ └── world.xml.j2 # Jinja template for the enclosing MuJoCo scene
├── notebook/
│ ├── main.py # Static reactive scene preview
│ └── livestream.py # Local MP4 stream controls and iframe preview
├── livestream_backend.py # Local MuJoCo-to-MP4 streaming utility
├── main.py # `uv run notebook` launcher
├── pyproject.toml # Project metadata and Python dependencies
├── uv.lock # Reproducible dependency lockfile
└── README.md
notebook/main.pyloads the templates fromassets/with Jinja2.- It renders
robot.xml.j2into a<body>definition. - It passes that body as
worldbodywhile renderingworld.xml.j2into a complete<mujoco>document. - MuJoCo parses the resulting XML, and the static notebook renders a camera view as an image.
robot.xml.j2 defines a free-floating torso, four ball-jointed hips, hinge knees, capsule limbs, and spherical feet. world.xml.j2 supplies the top light and the <worldbody> where the robot is placed.
catbot_env.py exposes the rendered model as CatbotEnv, a standard Gymnasium environment. It has 16 normalized position-action slots for checkpoint compatibility: hip x/y and knee targets are active, while every hip-z target is fixed at zero because that movement is unavailable on the real robot. Its 66-value observation contains base pose and velocity, joint pose and velocity, the current (forward, lateral, yaw) velocity command, and the prior action.
The initial task is velocity tracking. The reward combines velocity tracking, uprightness, a small alive bonus, and penalties for control magnitude and abrupt changes. Episodes terminate for a fall, inverted torso, or non-finite physics state, and truncate after 1,000 control steps by default. Reset randomizes the torso mass, ground friction, initial state, and command; pass domain_randomization=False for deterministic physics parameters.
Run the deterministic smoke tests and a random-action rollout:
uv run python -m unittest tests/test_catbot_env.pyInstall dependencies and train a first policy:
uv sync
uv run train --timesteps 1000000The final model is written to runs/catbot_ppo.zip; periodic checkpoints are written to runs/checkpoints/ every 25,000 timesteps. The runs/ directory is intentionally Git-ignored but remains visible in Finder and the terminal. The training environment is headless; use CatbotEnv(render_mode="rgb_array") for evaluation frames or render_mode="human" from a desktop session for an interactive MuJoCo viewer.
Change the checkpoint interval, or disable intermediate saves with --checkpoint-freq 0:
uv run train --checkpoint-freq 50000Open the native MuJoCo viewer and play one episode from the default checkpoint:
uv run rolloutTo render a selected checkpoint, pass its path as the only argument:
uv run rollout runs/checkpoints/catbot_ppo_<run-id>_25000_steps.zipThe viewer runs deterministic PPO actions with fixed physics. It holds the final pose after a fall or the 1,000-step limit so it can be inspected; close the viewer to return to the terminal.
Open a native viewer with direct control over each hip and knee actuator:
uv run ragdollIn the MuJoCo viewer, open the Actuator/Control panel and drag the actuator sliders. The default script leaves data.ctrl under the viewer's control, so slider changes are applied directly to the simulation.
The manual viewer uses half-strength position servos with the existing joint damping for softer landings. Startup and reset targets match the initial joint pose to avoid kicking the knees during the drop. Tune compliance with uv run ragdoll --stiffness-scale 0.3 (softer) or --stiffness-scale 1 (original stiffness). This setting applies only to the manual viewer; training and policy rollout physics are unchanged. Manual targets do not actively balance the robot, so extreme poses can still tip it over.
For terminal-based control instead, use:
uv run ragdoll --terminalThe terminal then accepts commands while the viewer is open. Select a leg and axis, then nudge or set its position target:
leg fl
axis knee
set -1.8
status
Use axis x or axis y to test the available hip actuators. Hip-z is locked at zero in both the policy model and viewer. + and - move the selected target by 0.1; targets are clipped to their actuator ranges. reset restores the reference pose, and quit closes the command loop. The viewer's actuator/joint panels remain available for inspecting the resulting pose.
To continue a finished or interrupted run, pass its checkpoint and the number of additional timesteps to collect. The default output path overwrites that checkpoint only after the extra training is complete.
uv run train --resume runs/catbot_ppo.zip --timesteps 1000000PPO defaults to CPU because MuJoCo physics and this small MLP policy are CPU-heavy. On Apple Silicon, opt into Metal Performance Shaders after confirming availability with uv run python -c 'import torch; print(torch.backends.mps.is_available())':
uv run train --device mpsRun static checks with Ruff:
uv run ruff check .After changing dependencies, update the lockfile and sync the environment:
uv lock
uv syncCommit uv.lock with dependency changes so other contributors get the same resolved environment.
- MuJoCo cannot create a renderer: Run from a desktop session or configure an appropriate off-screen OpenGL backend for your platform. The notebook needs a renderer even though it displays the result in the browser.
- Template edits do not refresh: Start Marimo from the repository root using the command above. The notebook watches
assets/robot.xml.j2andassets/world.xml.j2relative to that directory. - Python version error: Let uv manage Python (
uv python install 3.14) and rerunuv sync.