Skip to content

Release 1.4.0: the kit: water, wheels, pieces and soft bodies - #9

Merged
nolanbaxter merged 8 commits into
mainfrom
develop
Nov 10, 2026
Merged

nolanbaxter merged 8 commits into
mainfrom
develop

Conversation

@nolanbaxter

Copy link
Copy Markdown
Member

The kit, in one release: boxes of water that float what is in them as Archimedes says, a wheel joint for a car's suspension, fracture() to cut a box or hull into pieces that fill it exactly, and soft bodies (cloth, flags and closed shapes that keep their volume) colliding with every rigid body both ways. Also faster joints with stops and faster resting on terrain, and a capsule leaning on what it rests on no longer buzzes. Saves are now format 19. See the changelog's 1.4.0 section.

🤖 Generated with Claude Code

nolanbaxter and others added 8 commits November 6, 2026 15:46
A driven joint with a stop lost all its kept work: ball joints and hinges
with stops worked out rows, couplings, inverses and errors 16 times a
step for 9 poses. 100 driven ragdolls with stops took 33 ms a step, 56%
of it in the one-at-a-time pass, against 19 ms with no stops.

Every ball joint and hinge in a tree now keeps its work, and so does a
rotation joint with a cone, stops' rows and how far off they are with it.
A stop reached on a kept pass needs the anchors, a hinge's frames and the
pinned rows, which weren't read: they are fetched then, as the lazy pass
already did, since PIN is scratch of whichever joint went last.

That pass 18.5 -> 11.9 ms. Pinned, both load orders: driven ragdolls with
stops 13.5-14.7% faster, stops alone 1-5%, and about 8% with workers.
Same bits as 1.3.4 on 120 runs of drives pressed into stops, on workers
too; HASH_STOPS stored from 1.3.4 holds it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Finding contacts is 36-47% of a step on hulls, terrain and meshes. While
a pile settles on terrain, 87% of the time spent on the ground went to
pairs that had barely moved since their manifolds were found, and only
convex pairs kept theirs: a mesh pair tested every triangle under it
again each step.

A touching mesh or heightfield pair now keeps every manifold while
2 drift <= tolerance from the pose it was found at, REUSE's touching
rule, its normals in the mesh's frame. Not for jointed bodies or parts.
The rebuild, the kept ends and the drift are shared with convex pairs.

Pinned, both load orders, 12 s: terrain 5.6-8.7% faster, level mesh
14-15%; box and hull piles even. Results change where such a body
rests. 640 heaps: one body through the ground before and after; ridge
drops 575 of 576 on the solid hull's side before and after; bodies
awake at 30 s the same. Saves are format 16.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Capsules on bumpy ground stayed awake long after a heap landed. Most
were rolling: their energy fell by exactly what 1% rolling resistance
takes. A few buzzed in place, gaining energy for good: a capsule lying
across a peak of the mesh, its kept contact with the slope beyond
rebuilt 3.5 mm deep where it was 10 mm clear.

holdEnds() put a round end square onto the capsule's axis. A contact
whose normal leans along the capsule slid along it by r (n . axis), and
rebuilt, its depth was out by r (n . axis)^2. The end now goes to the
core along the normal, the way collide() and touch() brought it up.
Kept mesh manifolds showed it; convex pairs have had it since
2026-10-29, where a contact wasn't at a capsule's rounded end.

Both capsules now sleep within 1.5 s, and no body in 80 heaps gains
energy. Heaps and ridge drops as before. The stored hash changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Milestone 22, the first of part five, the kit. A static or kinematic box
given fluid: true is water: a sensor that holds up every dynamic body in
it by the weight of the water it displaces, at the centre of the part
under the surface, and slows it by the water's damping by how much of it
is under. density is the water's, flow its current.

How much of a body is under is worked out for each shape: boxes and
hulls cut exactly (faces fanned from a point on the plane, so the cap
adds nothing), balls by the cap formula, capsules summed in discs across
the axis, to within 5e-7 of the volume. The push acts on every substep
after gravity, over the sensor pairs the narrowphase already lists.

Found on the way: the push first read a body's turn from the step's
start, and lagged its rocking enough to feed it (an undamped plank went
from 0.89 to 5.5 rad/s in 30 s); it reads the turn now. Sensors never
woke what they let go of, and water must: water added, moved, raised by
a tide or removed wakes what it covers. New water damps at 3, measured:
at 0 nothing takes a body's bob, and it bobs for good.

A light cube floats corner up and a square timber corner down, as real
ones do: 30.2 mm worked out by hand, 29.9 simulated. The stored hash
changes, the test world having water in it now. Saves are format 17.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Milestone 23. A joint of type 'wheel' holds a wheel to a chassis as a
car's suspension does: Box2D's wheel joint, in 3D. The wheel slides
along axis on a spring (frequency, dampingRatio, between min and max)
and spins about axle, a new option (its default the world axis most
square to axis, as Nolan chose), driven and braked by its motor.
Steering is turning axle with setJoint, which keeps what the wheel
holds, so a car steered every step doesn't sag.

The suspension's line is the chassis's: a slider's line turns with a,
but a wheel spins. It is kept in b's frame of the joint, as a cone's
swing is, and a new row, onLine(), measures the gap along it with b's
lever reaching a's anchor. Wheels are solved one joint at a time, never
in a tree: driving, steering and braking, a car's wheels stayed within
0.08 mm of their tracks and 0.03 degrees of their axles.

A car stands level and sleeps, drives at its spin times its radius,
brakes to a stop, turns, and crosses a flat heightfield and a mesh at
20 m/s with no wheel leaving the ground, moving exactly as on one flat
box. A wheel under a fixed frame sags g / (2 pi f)^2 and stops at min.
Cylinders are milestone 26. The stored hash changes, the test world
having a cart in it now. Saves are format 18.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Milestone 24. fracture(shape, { count, seed }), exported beside readGlb,
cuts a box or a hull into count convex pieces that fill it exactly, face
to face: Voronoi cells, the part of the shape nearer each of count
points scattered through it than to any other. Each piece comes back as
{ vertices, position }, its corners about its own centre of mass, ready
for addBody as a hull. When to break is the game's.

The points come from Park and Miller's generator, kept a quarter of
their even spacing apart so no piece is a sliver (compared as sixth
powers: Math.cbrt isn't the same on every machine). Each cell is the
shape cut by the planes halfway to every other point, nearest first. A
corner where a cut crosses an edge is worked out from the edge's kept
end, so the two faces beside it find the same corner to the bit, and
the cap is ordered by Bearing's own atan2. So a seed gives the same
pieces on every machine: a stored hash holds it, and CI runs it on Bun.

The pieces' volumes sum to the shape's within 1e-12, and of 16,000
points each is in exactly one piece. A slab broken where it lay, into 6
or 12, rests within 2 mm and sleeps. Into 30, one piece at the edge on
a cut of 44 degrees slides off, friction 0.5 holding 27, as a real one
would. 20 pieces of a rock take about 5 ms. Found on the way: the
default box was a 2 m cube, not 1 m.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Milestone 25, the last of the kit. world.addSoftBody, setSoftBody,
softBodyOf and softBody(id), and the SoftBody handle with positions: an
open mesh is cloth, a closed one with pressure keeps its volume. Nolan
chose density in kg/m3 as bodies', air drag with a wind option, and the
body options with their own meanings.

XPBD, one pass a substep, before the rigid solve, two ways with every
rigid shape. Stiffness from the sheet's own weight and size, tethers so
hung cloth keeps its length at any mesh spacing, two contact planes a
corner, XPBD's velocity pass, and a material damping of 1/s, measured on
six drapes. Found on the way: constraints solved one at a time spun a
ball at rest about the vertical (now taken back each substep), and a
volume pushed back into the floor kept balls wobbling.

Known limits documented: corners collide, not triangles; no
self-collision; a heavy closed body may not sleep; a body much heavier
than the cloth sinks further than its stiffness says. FORMAT 19; the
mixed scene has a flag, a ball and a draped crate, so HASH changed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@nolanbaxter
nolanbaxter merged commit a1dd576 into main Nov 10, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant