Skip to content

Physics and actions

Every parameter here obeys the three numeric domains in Simulation overview, and every write is all-or-nothing.

Physics

Action Key params
step n_steps=1 (MuJoCo: max 100 000/call; Isaac and Newton have no ceiling). Non-negative whole number; 0 is an accepted no-op
send_action n_substeps=1 - positive whole number, no per-call ceiling (see Actions)
set_gravity / set_timestep gravity=[x,y,z] or a scalar z-component; timestep
get_contacts / get_contact_forces get_contacts lists every geom pair inside the detection range (margin + gap) and marks each active; only the pairs inside margin reach the solver, so a pair between the two thresholds is a proximity report carrying no force. Contact predicates count only active pairs; get_contact_forces gives the load a touching pair carries
apply_force body_name, force, torque, point - latched on that body and re-applied every step until the next apply_force for it, so several bodies can hold wrenches at once (force=[0,0,0] stops one, reset() stops all)
get_jacobian body_name or site_name or geom_name. Columns are DOFs of the whole compiled model, not the robot's joints: a free or ball joint owns several, and a two-robot scene reports one width spanning both. Pair dq with the json block's dof_joint_names, not with robot_joint_names
get_mass_matrix - the reported diagonal is DOF-indexed on the same terms, named by dof_joint_names
inverse_dynamics - compensation torques to hold the current qpos/qvel
forward_kinematics body_name (optional) - refresh every body's pose from the current qpos, then filter to one body when named
save_state / load_state name - snapshot/restore full physics. A checkpoint is valid only for the model it was taken against: any mutation that swaps the compiled model invalidates it, and load_state then errors instead of writing a state vector whose indices moved
set_joint_positions positions (dict or ordered list), robot_name, hold - write qpos + run FK (teleport, bypassing actuators). Kinematic only: a joint held by a position servo is pulled back to that servo's setpoint by the next step, and the success text names those joints. hold=True moves the matching setpoints with the pose; a torque- or velocity-driven joint, and a joint a tendon couples to one ctrl (every stock gripper, the stretch3 arm), is left alone because its ctrl is not a pose
set_joint_velocities velocities (dict or ordered list), robot_name - write qvel directly
get_energy / get_sensor_data sensor_name (optional)

A body name for forward_kinematics, get_body_state, get_jacobian, apply_force or set_body_properties may be bare (gripper) or namespaced (arm0/gripper): add_robot namespaces every compiled body and the bare form is retried under each robot's namespace. When that retry resolved the name, the answer names the entity it landed on (resolved 'base' to 'alice/base') and, in a scene where several robots carry the name, the qualified spelling that reads each other one. The physics writes refuse such a name outright rather than writing the first robot attached.

Joint names come from get_robot_state, or from robot_joint_names - Python-only, not in the tool schema's action enum. A dict key the model cannot resolve is refused rather than skipped. Some assets name joints by servo id or CAD term (the SO-101's are 1..6, the SO-100's Rotation..Jaw); for those the registry entry carries joint_labels - the shoulder_pan .. gripper the same arm's driver and datasets use - so get_robot_state prints 1 (shoulder_pan) and the writers accept the label as a key, bare or <robot>/<label>, in any case.

A joint value is in that joint's own MuJoCo unit: radians for a hinge, metres for a slide, never degrees. It matters most when mirroring a real arm onto its sim twin, because the driver reports the other unit (drivers/feetech reads an SO-arm in degrees), so a value outside the joint's range is refused naming the unit - and names the conversion when converting would land inside it: shoulder_pan=-96.2 outside [-1.92, 1.92] rad (radians, not degrees: -96.2 deg = -1.679 rad).

Actions

send_action(action, robot_name=None, n_substeps=1) writes actuator/joint targets and advances physics. action accepts either form:

Form Binding
{joint_or_actuator_name: value} mapping applied by name; unresolved keys are reported in an unresolved_keys JSON block so a caller can self-correct (no silent drop). Use a mapping to target a subset of actuators
ordered numeric vector (list / tuple / 1-D numpy array) bound positionally to robot_action_keys(robot_name) in declaration order - the convention replay_episode uses too - so a policy's raw chunk drives the arm without being zipped into a dict. The length must match the actuator count exactly; a mismatch, or a non-numeric / scalar / string action, returns a structured error naming the count and order

The vector binds to robot_action_keys, not robot_joint_names: those are the keys send_action resolves and the order the LeRobotDataset recorder writes the action column in. The two coincide unless a robot has passive/mimic joints, a tendon gripper, or a floating base on the Newton backend - whose 6-DoF free joint is a joint but not a commandable scalar, so it is absent from the action keys (its pose is read as base_pos / base_quat / base_lin_vel / base_ang_vel) and send_action refuses it as a command key. Do not assume equal widths.

n_substeps is how many physics steps the written targets are held for: a positive whole number, coerced from a NumPy or integral-float count (np.int64(3), a 3.0 from a config), refused with nothing written when fractional, zero, negative, non-finite, boolean or non-numeric. The floor is 1 rather than step's 0 because of that write - to advance without commanding, use step(n).

See also