Skip to content

Hello Steel

01 · Getting started

The smallest complete Steel program. Build a model with ModelBuilder, construct SolverSteel and a CollisionPipeline, then alternate collide and step. Every body added with add_body gets a free joint, because Steel simulates joint coordinates and only moves bodies that are connected through joints. Tree joints are exact in these coordinates, and default drives are folded into the mass matrix; contacts, limits and other constraints become rows solved with projected Gauss-Seidel (PGS).

0.00 s

What to look at

  • The six shapes fall and collide in the first second.
  • The boxes and one sphere come to rest; the other sphere, the capsule and the cylinder keep rolling.

Key parameter

SolverSteel(model)

All options have defaults: 12 PGS iterations, Baumgarte factor 0.2, patch friction, joint limits off. The script steps at 240 Hz, eight steps per 30 Hz frame.

Try changing

  • Change DT to 1/60 and SUBSTEPS to 2 and watch the contacts get softer.
  • Pass iterations=4 to SolverSteel.
  • Add more bodies in a loop and time the step.

Concept

Reduced coordinates and projected Gauss-Seidel

What it is

Steel stores each articulation as joint coordinates, so tree joints cannot drift apart. Every step builds and factors the joint-space mass matrix, predicts velocities without constraints, then solves contacts, limits and other constraints as rows with projected Gauss-Seidel: each row in turn computes the impulse that fixes its own velocity error, clamps it to its bounds and applies it at once.

λᵢ ← clamp(λᵢ − (Jᵢv + bᵢ) / dᵢ)

In this scene

Six free bodies, each a one-joint articulation, fall and collide; each contact becomes a normal row plus friction rows.

Considerations

Cost
Grows with rows times iterations; the 8-box stack takes 1.0 ms per step at 12 iterations in one world and 1.65 ms in 1024 worlds.
Accuracy
PGS stops after a fixed number of sweeps, so redundant or stiff contact sets keep a small residual such as stack sag.
Use it when
Start from the defaults and change one option at a time, as these tutorials do.

The script

cd tutorials && python 01_hello_steel.py prints each variant's final metric; --record out.json also writes the replay. Every tutorial imports replay.py from the same folder.

tutorials/01_hello_steel.py
# SPDX-FileCopyrightText: Copyright (c) 2026 Skild AI and the Steel contributors
# SPDX-License-Identifier: Apache-2.0
"""Hello Steel: drop a few shapes on the ground and step SolverSteel."""
import newton
import numpy as np
import warp as wp
from newton.solvers import SolverSteel
from replay import Recorder
DT = 1.0 / 240.0
SUBSTEPS = 8 # 8 x 240 Hz steps per 30 Hz frame
def build():
builder = newton.ModelBuilder()
builder.add_ground_plane()
shapes = ["box", "sphere", "capsule", "cylinder", "box", "sphere"]
for i, kind in enumerate(shapes):
body = builder.add_body(
xform=wp.transform(
(0.15 * (i % 2), 0.0, 0.3 + 0.25 * i), wp.quat_rpy(0.3 * i, 0.2, 0.1)
)
)
if kind == "box":
builder.add_shape_box(body, hx=0.08, hy=0.06, hz=0.05)
elif kind == "sphere":
builder.add_shape_sphere(body, radius=0.07)
elif kind == "capsule":
builder.add_shape_capsule(body, radius=0.04, half_height=0.1)
else:
builder.add_shape_cylinder(body, radius=0.06, half_height=0.06)
return builder.finalize()
def simulate(rec, label, **options):
model = build()
solver = SolverSteel(model, **options)
pipeline = newton.CollisionPipeline(model)
contacts = pipeline.contacts()
state_0, state_1, control = model.state(), model.state(), model.control()
run = rec.variant(label, model)
for _ in range(rec.frames(4.0)):
speed = np.linalg.norm(state_0.body_qd.numpy()[:, :3], axis=1).max()
run.frame(state_0, max_speed=speed)
for _ in range(SUBSTEPS):
pipeline.collide(state_0, contacts)
solver.step(state_0, state_1, control, contacts, DT)
state_0, state_1 = state_1, state_0
run.check(solver)
if __name__ == "__main__":
rec = Recorder()
simulate(rec, "SolverSteel(model), defaults")
rec.save()

Recorded with SolverSteel on Newton 1.9.0.dev0, Warp 1.19.0, NVIDIA RTX PRO 6000 Blackwell Server Edition. Replays read the state back every frame, so they are not a speed benchmark. Script · page text