Skip to main content
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)
ParamDescription
robot"go1" or "g1".
xml_pathOverride the MuJoCo model XML.
disturbancePerturbation level (0.01.0), with optional disturbance_seed.
sceneA Scene to load (see below).
Methods
MethodDescription
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):
MethodSignature
add_boxadd_box(position, size, *, fixed=True, rgba=None)
add_slopeadd_slope(position, size, angle_deg, *, axis=(0,1,0), fixed=True, rgba=None)
add_sphereadd_sphere(position, radius, *, fixed=True, rgba=None)
snakesnake(*, 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_slopeSame, 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:
KeyMeaning
pos, yaw, roll, pitch, body_heightBase pose.
foot_contactsPer-foot ground contact (0/1).
terrain_aheadDownward raycasts: max_step_up, ground heights ahead.
obstacles_aheadForward 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)
python demo_scene.py