Domain randomization
Randomize by writing the model arrays in place, then notify the solver with the flag for what changed. Arrays are per element, so a per-world randomization writes that world’s bodies, shapes and DOFs. notify_model_changed(flags, world_mask=...) refreshes cached data for every world; the mask only limits which worlds lose their warm-start impulses.
| Property | Model array | Notify with | When Steel reads it | Tutorial |
|---|---|---|---|---|
| Mass, inertia | body_, body_ and inverses |
BODY_ |
Notify by contract. With the default update_ the mass matrix is rebuilt from these arrays every step, so it happens to work without the notification; with a longer interval the old values stay until the next rebuild. |
dr-mass, dr-inertia |
| Center of mass | body_ |
BODY_ |
Cached (body-to-COM transforms): ignored without the notification. | dr-com |
| Friction | shape_ |
SHAPE_ |
Copied on notify (scaled by contact_, combined by friction_); ignored without it. |
dr-friction |
| Floor friction per world | a per-world floor shape’s shape_ |
SHAPE_ |
A ground plane in the global world is one shape with one coefficient for every world. Give each world its own floor shape; with the default average combine the other shape’s coefficient dilutes the spread. | dr-terrain-friction |
| Restitution | shape_ |
SHAPE_ |
Copied on notify; ignored without it. Positive restitution at construction excludes the sparse mass factors; if the solver already uses sparse factors, introducing positive restitution raises and needs a new solver. That check reads the host, so notify outside capture. | dr-restitution |
| Spin and rolling friction | shape_, _ |
SHAPE_ |
On notify, for free-body contacts with steel: 4 or 6 (integration branch); a solver built without condim above 3 ignores them. |
|
| Drive gains | joint_, joint_ |
JOINT_ |
Implicit gains are folded into the mass matrix; notify by contract so it is rebuilt with them (with interval 1 the rebuild happens every step anyway). | dr-drive-gains |
| Effort limits | joint_ |
JOINT_ (optional) |
Read every step. | dr-effort-limits |
| Joint damping, spring stiffness | joint_, joint_ |
JOINT_ |
Copied on notify; ignored without it. | dr-joint-damping |
| Armature | joint_ |
JOINT_ |
Cached effective armature: ignored without the notification. | dr-armature |
| Spring rest position | joint_ |
JOINT_ |
Copied on notify. | |
| Joint friction | joint_, steel: |
JOINT_ |
Rows exist only with enable_ at construction; values are read every step. The notification wakes sleeping islands and validates values. The load term exists only if some coefficient was positive at construction. |
dr-joint-friction |
| Velocity limits | joint_ |
none needed | Rows exist only with enable_ at construction; values are read every step. |
dr-velocity-limits |
| Position limits | joint_, joint_ |
none needed | Read every step; enforced only with enable_. |
|
| Gravity | model. |
MODEL_ with reuse_ |
Per world; read every step. Without the notification a change lags one step under reuse_. |
dr-gravity |
| Pushes | state. (velocity kick) or state. (force) |
none (state) | A free joint’s joint_ is the COM velocity and angular velocity. Write body_ on the input state of every step the push lasts. Writes to state. are ignored: Steel recomputes it from joint_. |
dr-pushes |
| Adhesion | steel: |
none needed | Read every step (integration branch); a solver built with no positive value ignores it. | |
| Kinematic flag | body_ |
BODY_ |
Read on the host: notify outside graph capture. A kinematic free body that the solver left out of the response at construction cannot become dynamic later. | |
| Collision geometry and offsets | shape_, shape_, margin, gap |
SHAPE_ |
Covered by the notification (which also discards friction anchors of the changed bodies), but it changes contact generation and has no tutorial yet. Shape variants use SHAPE_ at reset. |
not covered |
Several of these also have runnable tutorials, including dr-per-world-mixed, which randomizes base mass, drive stiffness, foot friction and gravity per world with one combined notification and a world_mask partial reset.
Pitfalls
Section titled “Pitfalls”- A missing notification is silent. Center of mass, friction, restitution, joint damping and armature changes are ignored without their notification. Mass, inertia and drive gains appear to work without one at
update_mass_matrix_interval=1; notify anyway. Log the value the solver uses, not only the array you wrote. - Feature switches are fixed at construction.
enable_joint_friction,enable_joint_velocity_limits,enable_joint_limits, combine modes,drive_modeand row capacities cannot be randomized; randomize the values those features read. - Capture. Joint, DOF, inertial and shape notifications run on the device and can be captured into the reset graph; they read the arrays they were captured with. Body-flag notifications, sleeping and some validity checks read the host and must run outside capture.
- Construction-time gates. Joint-friction load coefficients, adhesion and torsion radii must be positive somewhere, and
steel:condimmust be 4 or 6 somewhere, when the solver is built for their rows to exist. Restitution decides the mass-factor layout at construction. To randomize these later, build the solver with the feature already on. - Independent per-shape materials split friction patches. A region needs matching friction coefficients, so randomizing each shape separately makes more, smaller regions and more friction rows. Re-measure capacities with randomization on.
- One factor at a time. Validate each randomized property alone against the baseline before combining them; interactions (restitution in particular) are task-dependent.
- Reset order. Write the arrays, notify, then reset the state of the selected worlds; the notification and reset discard warm-start impulses and friction anchors that no longer apply.
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.