Card house
Many contacts
Four levels of playing cards held up by friction alone.
steel preset (the default), recordedon an NVIDIA RTX PRO 6000 Blackwell.Run it
cd examples# the default preset (steel), then the othersuv run shelf play card_house# change solver settings liveuv run shelf tune card_houseSource: examples/shelf/02_many_contacts/card_house/ · ported from steel-webgpu tools/scenes.py card_house() @ ad03e49
Presets
| Preset | Solver | Status | Checked |
|---|---|---|---|
steeldefault | steel | works | 2026-11-10, newton f6fc82a8b, NVIDIA RTX PRO 6000 Blackwell Server Edition |
Solvers with no row are untested on this example. The full settings are inpresets.toml.
Code
# SPDX-FileCopyrightText: Copyright (c) 2026 Skild AI and the Steel contributors# SPDX-License-Identifier: Apache-2.0# Direct source: steel-webgpu; see presets.toml and THIRD_PARTY.md.# Modified for Steel Shelf: preset-driven execution and shared runner integration.
############################################################################ Example Card house## Card house: four levels of leaning cards and flat separators held up by friction alone.## Ported from steel-webgpu's tools/scenes.py (card_house); the scene builds# as it does there and the shelf's preset drives the solver.## Command: python -m shelf run card_house############################################################################
import math
import numpy as npimport warp as wp
import newtonimport newton.examplesfrom common.steel_scenes import BLUE, GOLD, GREY, RED, TEAL, SceneExample, cfg, tf
class Example(SceneExample): file = __file__ camera = {'eye': (3.5, -6.0, 3.6), 'target': (0.0, 0.0, 1.8)}
def build(self): """Card house: four levels of leaning cards and flat separators held up by friction alone.""" b = newton.ModelBuilder() b.add_ground_plane(cfg=cfg(mu=0.9)) w, t, depth, theta = 1.2, 0.05, 1.0, 1.22 foot, h = w * math.cos(theta), w * math.sin(theta) pitch = 2 * foot + 0.15 card = cfg(density=200, mu=0.9) levels = 4 for level in range(levels): n = levels - level z0 = level * (h + t + t * math.cos(theta)) x0 = -(n - 1) * pitch / 2 for i in range(n): cx = x0 + i * pitch for side in (-1, 1): # Faces (not mid-planes) meet at the apex, and the lower corners rest on the floor. x = cx + side * (foot / 2 + t / (2 * math.sin(theta))) body = b.add_body(xform=tf((x, 0, z0 + h / 2 + t / 2 * math.cos(theta)), wp.quat_from_axis_angle(wp.vec3(0, 1, 0), side * theta))) b.add_shape_box(body, hx=w / 2, hy=depth / 2, hz=t / 2, cfg=card, color=[BLUE, TEAL, GOLD, RED][(level + i) % 4]) if level < levels - 1: for i in range(n - 1): body = b.add_body(xform=tf((x0 + (i + 0.5) * pitch, 0, z0 + h + t * math.cos(theta) + t / 2))) # 0.99 pitch, not steel-webgpu's 1.05: at 1.05 neighbouring separators overlap 49 mm end to end and # start the scene by pushing each other apart; at 0.99 they leave a 1 cm gap over each apex and # still rest on both cards' outer corners (5 cm from the apex) b.add_shape_box(body, hx=pitch * 0.99 / 2, hy=depth / 2, hz=t / 2, cfg=card, color=GREY) return b, {}, "Card house: four levels of leaning cards and flat separators stand on friction alone."
def check(self): """The house stands: every card and separator stays within 25 mm of where it was built (it settles ~11 mm).""" moved = np.linalg.norm(self.state_0.body_q.numpy()[:, :3] - self.model.body_q.numpy()[:, :3], axis=1) assert moved.max() < 0.025, f"body {moved.argmax()} moved {1e3 * moved.max():.0f} mm: the house is coming down"
if __name__ == "__main__": parser = Example.create_parser() viewer, args = newton.examples.init(parser) newton.examples.run(Example(viewer, args), args)example = "card_house"source = "steel-webgpu tools/scenes.py card_house() @ ad03e49"default = "steel"
[check]# Arguments `python -m shelf check` passes in test mode: 300 frames, 5 s, the house settled; the example's check() asserts the outcome.args = ["--num-frames", "300"]
[gallery]short = "Four levels of playing cards held up by friction alone."title = "Card house"frame = 120
[xpbd]status = "untested"
[vbd]status = "untested"
[mujoco]status = "untested"
[featherstone]status = "untested"
[semi_implicit]status = "untested"
[feather_pgs]status = "untested"
[steel]status = "works"# 4 substeps x 24 iterations with sleeping: settles 10.7 mm, as a 16 x 12 run without sleeping (11.3 mm), at 2.1 ms.# 24 iterations so the house also stands awake (26 mm with sleeping off); at 4 x 12 to 4 x 20 it stands only# because its islands fall asleep before a slow collapse, which a wake-up would restart. 4 x 6 falls even asleep.# propagation_constraint_max 2048: the default 544 rows overflowed (604 asked), dropped contacts and the house fell.sim = { fps = 60, substeps = 4 }solver = { solve_mode = "matrix_free", iterations = 24, articulated_contact_response = "propagation_colored", enable_joint_limits = true, enable_sleeping = true, propagation_constraint_max = 2048 }checked = "2026-11-10, newton f6fc82a8b, NVIDIA RTX PRO 6000 Blackwell Server Edition"
[kamino]status = "untested"
[implicit_mpm]status = "untested"
[style3d]status = "untested"Steel docs and tutorials are licensed under Apache-2.0. Third-party notices. "Skild", "Skild AI" and the Skild logo are trademarks of Skild AI and are not licensed under Apache-2.0.