Sim wraps MuJoCo physics for a robot, and Scene builds the world around it.
Together they’re the engine under every other feature.
Sim
Sim is the simulator handle for a robot. Build it, then run commands.
import cadenza_lab as cadenza
sim = cadenza.Sim(robot="go1", disturbance=0.3)
sim.run(["walk_forward", "turn_left", "sit"])
state = sim.get_state() # dict: pose, velocity, contacts, …
Sim(robot="go1", xml_path=None, disturbance=None,
disturbance_seed=None, scene=None)
| Param | Description |
|---|
robot | "go1" or "g1". |
xml_path | Override the MuJoCo model XML. |
disturbance | Perturbation level (0.0–1.0), with optional disturbance_seed. |
scene | A Scene to load (see below). |
Methods
| Method | Description |
|---|
run(commands, ...) | Execute a command string or list. Retries up to max_retries (default 3). |
run_reactive(memory_fn, vla_fn, goal_fn, ...) | Closed-loop control: at each step, goal_fn/vla_fn decide the next action from memory + observation. |
get_state() | Return the current world state as a dict. |
Scene
Scene builds a static world by adding primitives. Builder methods return the
scene, so they chain. Pass it to Sim or GymAdapter.
scene = (
cadenza.Scene()
.add_box(position=[-1.3, 0.2, 0.08], size=[0.18, 0.18, 0.08])
.add_slope(position=[-2.0, 0.0, 0.0], size=[1.0, 1.0, 0.1], angle_deg=15)
.add_sphere(position=[-2.8, 0.0, 0.1], radius=0.12, rgba=[1, 0.2, 0.2, 1])
)
sim = cadenza.Sim(robot="go1", scene=scene)
Builder methods (all fixed=True, optional rgba):
| Method | Signature |
|---|
add_box | add_box(position, size, *, fixed=True, rgba=None) |
add_slope | add_slope(position, size, angle_deg, *, axis=(0,1,0), fixed=True, rgba=None) |
add_sphere | add_sphere(position, radius, *, fixed=True, rgba=None) |
snake | snake(*, start_x, step_x, count, snake_y=0.4, z=None, box_size=(0.07,0.07,0.07), rgba=...). A row of staggered boxes. |
snake_on_slope | Same, laid over a slope. |
clear() | Remove all added geometry. |
compile() | Finalize the scene (called automatically when loaded). |
Cadenza’s forward direction is −X. Positions and sizes are in MuJoCo world
units (meters). size is half-extents for boxes/slopes.
fixed vs. dynamic objects
fixed=True (default) welds the object to the world. fixed=False wraps it in a
body with a free joint, so it falls under gravity and the robot can shove it.
scene.add_box(position=[-1.0, 0, 0.1], size=[0.1, 0.1, 0.1], fixed=False) # kickable
State & sensing
Sim.get_state() returns a dict the closed-loop helpers and modalities read from:
| Key | Meaning |
|---|
pos, yaw, roll, pitch, body_height | Base pose. |
foot_contacts | Per-foot ground contact (0/1). |
terrain_ahead | Downward raycasts: max_step_up, ground heights ahead. |
obstacles_ahead | Forward raycasts: nearest hit left/center/right (min_m, side). |
Demo: build a world and read it back
"""demo_scene.py: compile a scene, read terrain/obstacle sensing (headless)."""
import cadenza_lab as cadenza
scene = (
cadenza.Scene()
.add_box(position=[-1.3, 0.2, 0.08], size=[0.18, 0.18, 0.08])
.add_slope(position=[-2.4, 0.0, 0.0], size=[0.8, 0.6, 0.05], angle_deg=12)
.add_sphere(position=[-2.9, -0.3, 0.12], radius=0.12, rgba=[1, 0.2, 0.2, 1])
)
sim = cadenza.Sim(robot="go1", scene=scene, disturbance=0.2, disturbance_seed=0)
state = sim.get_state()
print("body height:", round(state["body_height"], 3))
print("obstacle ahead (min m):", state["obstacles_ahead"]["min_m"])
print("max step-up ahead:", round(state["terrain_ahead"]["max_step_up"], 3))
# To watch it instead of inspecting it (opens a viewer window):
# cadenza.view(robot="go1", scene=scene)