Core concept

Every simulator is a set of modelling decisions. MuJoCo’s are unusually explicit, which makes it possible to say precisely where it is faithful, where it approximates, and where it is silent. Knowing this before you build on it saves you from the most expensive kind of research error: a result that is real in the simulator and false in the world.

Sort every phenomenon you care about into one of three bins.

Modelled. MuJoCo has a mechanism designed for it, documented, with parameters you can identify. Articulated rigid bodies, joint limits, gravity, actuators with gains and dynamics, tendons, equality constraints, contact with friction.

Approximated. MuJoCo produces the qualitative behaviour through a model that is known to differ from the physics in specific, documented ways. Contact (soft, not rigid), friction (regularized, so objects creep), deformable bodies (flex elements), fluid drag (two stateless phenomenological models).

Not modelled. No mechanism exists; if your result depends on it, MuJoCo cannot tell you anything. Fluid flow fields, heat, fracture and cutting, granular media, optics of real cameras and tactile skins, electromagnetic effects in motors beyond the actuator models MuJoCo provides.

What MuJoCo models

[!established] Generalized coordinates MuJoCo simulates multi-joint dynamics in generalized (joint) coordinates: the state is the joint positions and velocities, not the 6-D pose of every body. Joint constraints therefore hold exactly by construction; a hinge cannot come apart. Other constraints (contacts, joint limits, equality constraints, tendon limits) are handled by the constraint solver and are soft (next section).

[!established] Actuation Actuators map a control signal through a transmission (joint, tendon, site, slider-crank, body) with a gain, a bias and optional activation dynamics, which covers torque motors, position and velocity servos, muscles and adhesion. Recent releases added a dcmotor actuator with back-EMF (3.7, redesigned in 3.12), an orientation servo on SO(3) (3.11) and a pid actuator with integral action and anti-windup (3.12). Actuator and sensor delays exist since 3.5.0 through history buffers (delay, nsample).

What MuJoCo approximates

Contact is soft

In the real world two rigid objects cannot overlap. In MuJoCo they do, slightly: contact forces come from a convex optimization whose constraints are soft, so a resting object sits a fraction of a millimetre inside what it rests on (0.37 mm for the balls in Lesson 0.1) and an impact sinks tens of millimetres for an instant. This is a deliberate design choice, not a bug: soft constraints make the forces unique and smooth functions of the state, which is what makes MuJoCo’s inverse dynamics well defined and its derivatives usable for optimization and control. Level 9.2 derives the model.

Friction creeps

The same softness reaches friction. An ideal Coulomb contact holds an object perfectly still on a slope until the slope exceeds the friction angle $\arctan\mu$. MuJoCo’s regularized friction holds it almost still: it creeps. The documentation calls this slow slippage and says it is expected behaviour. Here is how much, measured:

INPUT: `incline.xml`, a block on a ramp, sliding friction 0.4 on both surfaces
PROCESS: tilt the ramp to 15 degrees (the Coulomb slip angle is 21.8 degrees), simulate 10 s with four solver settings
OUTPUT: distance slid down the slope for each setting

```python file=examples/l0_3_slow_slip.py “"”Lesson 0.3: measure how far a block creeps on a slope that should hold it.

INPUT incline.xml (block on a mocap ramp, sliding friction 0.4 on both geoms) PROCESS tilt the ramp to 15 degrees (below the Coulomb slip angle atan(0.4) = 21.8 degrees), place the block on it, simulate 10 s under four solver settings OUTPUT the distance the block slid along the slope for each setting

Ideal Coulomb friction predicts zero motion. Any non-zero distance is a property of MuJoCo’s regularized (soft) friction model, not of the physics being modelled.

Run: python examples/l0_3_slow_slip.py “””

import math

import mujoco import numpy as np

from mjcourse import model_path

ANGLE = math.radians(15.0) SETTINGS = { “pyramidal cone (default)”: dict(cone=mujoco.mjtCone.mjCONE_PYRAMIDAL, impratio=1.0, noslip=0), “elliptic cone, impratio 1”: dict(cone=mujoco.mjtCone.mjCONE_ELLIPTIC, impratio=1.0, noslip=0), “elliptic cone, impratio 10”: dict(cone=mujoco.mjtCone.mjCONE_ELLIPTIC, impratio=10.0, noslip=0), “elliptic, impratio 10, noslip 3”: dict(cone=mujoco.mjtCone.mjCONE_ELLIPTIC, impratio=10.0, noslip=3), }

def tilt(model: mujoco.MjModel, data: mujoco.MjData, angle: float) -> np.ndarray: “"”Tilt the ramp about world y and put the block at rest on its surface.

Returns the unit vector pointing down the slope.
"""
quat = np.array([math.cos(angle / 2), 0.0, math.sin(angle / 2), 0.0])  # rotation about +y
data.mocap_quat[0] = quat
rot = np.zeros(9)
mujoco.mju_quat2Mat(rot, quat)
rot = rot.reshape(3, 3)
normal, along = rot[:, 2], rot[:, 0]
# ramp half-thickness 0.01 m, block half-size 0.03 m (see incline.xml)
centre = data.mocap_pos[0] + normal * (0.01 + 0.03)
data.qpos[0:3] = centre
data.qpos[3:7] = quat
data.qvel[:] = 0
mujoco.mj_forward(model, data)
return -along if along[2] > 0 else along

def slide_distance(cone: int, impratio: float, noslip: int, seconds: float = 10.0) -> float: model = mujoco.MjModel.from_xml_path(str(model_path(“incline”))) model.opt.cone, model.opt.impratio, model.opt.noslip_iterations = cone, impratio, noslip data = mujoco.MjData(model) downhill = tilt(model, data, ANGLE) start = data.qpos[0:3].copy() for _ in range(round(seconds / model.opt.timestep)): mujoco.mj_step(model, data) return float(np.dot(data.qpos[0:3] - start, downhill))

if name == “main”: print(f”slope 15.0 deg, friction 0.4, Coulomb slip angle {math.degrees(math.atan(0.4)):.1f} deg”) for name, kw in SETTINGS.items(): d = slide_distance(**kw) print(f”{name:<34s} slid {1e3 * d:9.4f} mm in 10 s”)

Output with MuJoCo 3.14.0:

| Solver setting | Slid in 10 s |
|---|---:|
| pyramidal cone (MuJoCo's default) | 11.75 mm |
| elliptic cone, `impratio` 1 | 6.68 mm |
| elliptic cone, `impratio` 10 | 0.74 mm |
| elliptic cone, `impratio` 10, `noslip_iterations` 3 | 0.076 mm |

The ranking is the one the documentation predicts: elliptic cones with a larger `impratio` make friction "harder" relative to the normal direction, and the NoSlip post-processing step suppresses most of what remains, at extra cost and with documented side effects (it makes inverse dynamics ill-defined). None of them reaches zero.

> [!research] When creep matters
> A centimetre per ten seconds is irrelevant for a block that sits for one second. It is not irrelevant for a long-horizon task where an object rests in a gripper while the arm moves, for a policy evaluated on whether an object "stays put", or for any learned model that might discover creep and exploit it. Choose cone, `impratio` and NoSlip deliberately for manipulation, and report the choice.

### Deformables, cables and cloth

Since MuJoCo 3.0, deformable objects are simulated with **flex** elements: collections of bodies connected by massless stretchable capsules (1-D), triangles (2-D) or tetrahedra (3-D), plus the `flexcomp` element to generate them. Earlier releases emulated soft bodies with `composite` objects made of many small rigid bodies, which still exist. Both are approximations of continuum mechanics with a modest number of degrees of freedom; release 3.14.0 also added an experimental `ipc` contact mode for penetration-free flex contact. If your research question is about the mechanics of the deformable itself (stress, fine wrinkling, tearing), use a dedicated finite-element or cloth solver and treat MuJoCo as the robot side.

### Fluids

The documentation is direct: "Proper simulation of fluid dynamics is beyond the scope of MuJoCo." What it offers instead are two *stateless* phenomenological models of the force a body feels moving through a medium (an inertia-box model and an ellipsoid model with tunable coefficients), enabled by setting `density` and `viscosity` in `<option>`. They are good enough for flying and swimming behaviour of the kind control research needs. They have no flow field: no wakes, no pouring, no liquid in a cup.

## What MuJoCo does not model

> [!established] Sensor noise is not simulated
> Every MuJoCo sensor has a `noise` attribute, and it is easy to assume it adds noise. It does not. The documentation says the attribute "does not affect the simulation; it serves as a convenient location for storing standard deviation information for later use." `mjData.sensordata` is noise-free unless your code adds noise. Level 17 adds it, deliberately and reproducibly.

**Camera realism.** MuJoCo renders with OpenGL (the classic renderer) or with a Filament-based physically based renderer. Neither models a real camera's noise, rolling shutter, motion blur, auto-exposure or lens defects. Depth images are perfect depth, without the holes, flying pixels and multipath errors of real RGB-D sensors. Vision policies trained only on MuJoCo images face a visual gap even when the physics is right.

**Tactile realism.** The `touch` sensor sums normal contact force in a region; the `tactile` sensor reports penetration depth over a grid (raw depth since release 3.9). Real tactile skins have elastomer mechanics, optics or capacitive physics, hysteresis and crosstalk that none of this represents.

**Hardware specifics.** Gear backlash, cable stretch, motor heating and torque derating, encoder quantization, bus latency jitter and controller firmware are absent unless you model them, and MuJoCo gives you the pieces for some of them (backlash with two joints and a limit, latency with actuator delays, quantization in your own code).

**Contact geometry beyond the model.** MuJoCo collides the shapes you give it. A real object's contact surface has compliance, micro-geometry and material variation; a mesh's convex hull is used for collision unless you decompose it. Many "the grasp works in simulation" results are really "the grasp works on the convex hull".

## Comparing simulators without ranking them

Simulators differ in what they assume, and the right one depends on the question. The table compares modelling choices, which can be checked, rather than overall quality, which cannot.

| Choice | MuJoCo | Alternatives you will meet |
|---|---|---|
| Coordinates | generalized (joint) coordinates for articulated systems | many engines also use reduced coordinates for articulations; some represent every body with 6 DOF and enforce joints as constraints |
| Contact | soft constraints, convex optimization, unique forces | complementarity (rigid, LCP-style) formulations; penalty springs; hydroelastic pressure fields (Drake) |
| Friction | pyramidal or elliptic cone, optional torsional and rolling | similar cones; many engines omit torsional and rolling friction |
| Massive parallelism | CPU threads, `mujoco.rollout`; GPU through MJX (JAX) and MuJoCo Warp | GPU-native engines built for thousands of environments |
| Rendering | OpenGL or Filament | engines coupled to ray-traced or photoreal renderers |

> [!unverified] Claims about other simulators
> The right-hand column describes families of approaches in general terms and names one well-documented example. This course does not benchmark other simulators and makes no claim about which is faster or more accurate on your task. If you need such a comparison, run it on your task with your metrics.

## Debugging

**"The object slowly slides out of the gripper."** Before tuning the controller, check whether the grasp is inside the friction cone at all (squeeze force times friction coefficient against the load), then whether slow slippage explains the rate: switch to an elliptic cone, raise `impratio`, try a few NoSlip iterations, and see whether the drift rate falls. If it does, the controller was never the problem.

**"My policy works in simulation with sensor noise but fails on the robot."** Check whether the noise was ever applied. Setting a sensor's `noise` attribute changes nothing in `sensordata`.

**"Water pours through the cup."** There is no water. MuJoCo has no liquid simulation; particles made of tiny spheres are granular, not fluid, and behave accordingly.

## Exercise

Pick one paper whose main experiment uses MuJoCo. For each claim in its abstract, put it into one of the three bins above according to what the claim depends on: modelled, approximated or not modelled. For every claim in the second or third bin, write one sentence on what would have to be true about the approximation for the claim to transfer to hardware.

## Challenge

Extend the slow-slip experiment into a curve: the creep rate (mm/s, measured from 2 s to 10 s to skip the initial transient) as a function of slope angle from 5 to 20 degrees, for the default pyramidal cone and for an elliptic cone with `impratio` 10. Plot both on a log scale. Then answer: is the creep a constant velocity, and does its rate grow smoothly or suddenly as the slope approaches the friction angle?

## Research connection

The honest default for a MuJoCo result is "this is true of the model". Turning it into a claim about robots needs an argument that the phenomena the result depends on are in the first bin, or that the approximation error in the second bin is small relative to the effect. Levels 17 to 19 give you the tools for that argument (randomization, identification, transfer experiments), and Level 21 shows how to write it into an evaluation protocol.

```quiz
{"id": "0.3-check", "title": "Knowledge check", "questions": [
  {"kind": "mcq", "q": "A model sets <code>&lt;jointpos joint=\"elbow\" noise=\"0.01\"/&gt;</code>. What is in <code>data.sensordata</code> for that sensor?",
   "options": ["The joint angle plus Gaussian noise with standard deviation 0.01 rad", "The exact joint angle; the noise attribute is only stored", "The joint angle rounded to 0.01 rad", "A noise sample only"],
   "answer": 1,
   "explain": "<p>The documentation is explicit that <code>noise</code> does not affect the simulation. Your code must add noise if you want it.</p>"},
  {"kind": "mcq", "q": "In the slow-slip experiment, which change reduced creep the most on its own (before adding NoSlip)?",
   "options": ["Switching from a pyramidal to an elliptic cone", "Raising impratio from 1 to 10 with an elliptic cone", "Raising the friction coefficient", "Lowering the timestep"],
   "answer": 1,
   "explain": "<p>Elliptic cones alone took creep from 11.75 mm to 6.68 mm; raising <code>impratio</code> to 10 took it to 0.74 mm. The friction coefficient and timestep were not varied in the experiment, so the table says nothing about them; the challenge asks you to find out.</p>"},
  {"kind": "open", "q": "A colleague proposes training a policy for pouring water from a jug in MuJoCo using thousands of small spheres as the liquid. What would the trained policy have learned about, and what experiment would expose the gap?",
   "reference": "<p>It would learn to pour a granular material with MuJoCo's soft contact and friction between spheres, not a liquid: no viscosity-driven flow, no surface tension, no free-surface dynamics. A direct test is to compare the outflow rate versus tilt angle curve in simulation with a measured curve for water from the same jug; granular outflow has a threshold angle and a nearly constant rate, which water does not show. A cheaper first test is to change the sphere friction coefficient and observe whether the learned behaviour changes, which it should not if the policy had learned about water.</p>"}
]}

Next

Level 1 opens MuJoCo up: Lesson 1.1 separates what is fixed in a simulation (mjModel) from what evolves (mjData), with a live inspector that highlights which arrays each call writes.