Skip to content

Falling chain (mujoco.org)

Basic contacts

Four interlocked thin chain links drop onto a catch bar and hang from it.

0.00 s
The steel preset (the default), recordedon an NVIDIA RTX PRO 6000 Blackwell.

Run it

Terminal window
cd examples
# the default preset (steel), then the others
uv run shelf play mjorg_falling_chain
# change solver settings live
uv run shelf tune mjorg_falling_chain

Source: examples/shelf/01_basic_contacts/mjorg_falling_chain/ · ported from google-deepmind/mujoco model/speculative/chain.xml (MuJoCo 4.0 tour on mujoco.org)

Presets

PresetSolverStatusChecked

Solvers with no row are untested on this example. The full settings are inpresets.toml.

Code

examples/shelf/01_basic_contacts/mjorg_falling_chain/example.py
# SPDX-FileCopyrightText: Copyright (c) 2026 Skild AI and the Steel contributors
# SPDX-License-Identifier: Apache-2.0
###########################################################################
# Example mujoco.org falling chain
#
# Four interlocked chain links of 6 mm capsules drop onto a thin catch bar at a coarse
# time step and hang there without passing through each other.
#
# Replicates the "Speculative Contacts: Falling Chain" scene of the MuJoCo 4.0 tour on
# mujoco.org from its MJCF (scene.xml, flattened from google-deepmind/mujoco
# model/speculative/chain.xml). See README.md for the gaps.
#
# Command: python -m shelf run mjorg_falling_chain
#
###########################################################################
from pathlib import Path
import numpy as np
import newton
import newton.examples
from common.steel_scenes import SceneExample
class Example(SceneExample):
file = __file__
# MuJoCo's free camera for the scene: azimuth 135, elevation -14 around the statistic centre
camera = {"eye": (1.48, -1.48, 1.77), "target": (0.0, 0.0, 1.25)}
def build(self):
builder = newton.ModelBuilder()
self.preset.apply_model(builder)
builder.add_mjcf(str(Path(__file__).with_name("scene.xml")))
return builder, {}
def check(self):
"""The chain dropped onto the bar and hangs from it, each link stretched below the one above."""
self.solver.check_constraint_capacity() # dropped contact rows invalidate the run
q = self.state_0.body_q.numpy()
assert 1.6 < q[0, 2] < 1.76, f"link1 is not hanging from the catch bar (z={q[0, 2]:.3f})"
# links start 18 cm apart; hanging interlocked they are about 31 cm apart, and more once one slips out
gaps = np.linalg.norm(np.diff(q[:, :3], axis=0), axis=1)
assert (gaps > 0.25).all() and (gaps < 0.34).all(), f"links not hanging interlocked: gaps {np.round(gaps, 3)}"
if __name__ == "__main__":
parser = Example.create_parser()
viewer, args = newton.examples.init(parser)
newton.examples.run(Example(viewer, args), args)

README

Steel replica of “Speculative Contacts: Falling Chain” from the MuJoCo 4.0 tour on mujoco.org. Four interlocked chain links built from 6 mm capsules drop 8 cm onto a catch bar at a coarse time step. With speculative contacts the links stay interlocked and the chain hangs from the bar.

  • Source: google-deepmind/mujoco model/speculative/chain.xml, Apache-2.0 (text in LICENSE). scene.xml is that file compiled with MuJoCo 3.17 and re-emitted flat, without the 4.0 speculative flag (Newton’s collision pipeline enables speculative contacts instead, from the preset’s collision table).
  • Check: after 10 s link 1 hangs on the bar and each link hangs 25-34 cm below the one above (they start 18 cm apart). Without speculative contacts links 3 and 4 fall through, as they do in MuJoCo 3.17.

Known gaps:

  • Newton sizes the speculative gap from each body’s velocity at the start of the step, capped by speculative_contact_gap_max. MuJoCo 4.0’s speculative contacts are its own scheme.
  • The step is 6.7 ms (3 substeps at 50 fps); the tour uses 6 ms.
  • Rendering uses the shelf viewer, not MuJoCo’s Filament renderer, image-based light or skybox.