luvcraft/physics.lisp
--------------------------------------------------------------------- Parameters. Box3D's constants, in cells and seconds; each one is a special so it can be turned while a pile is settling.
How far past its radius a body looks for terrain and boxes, in cells. Wide enough that a fast body's contacts exist a step before it lands.
--------------------------------------------------------------------- Handles. A body handle is a fixnum: the id-table index shifted up sixteen bits over a generation. A destroyed body's slot is reused with the next generation, so a stale handle answers BODY-ALIVE-P with NIL rather than naming whatever took its place. #N7Q4XS
The id table: three parallel fixnum columns plus a free list. SET says which body set holds the body and LOCAL where in it; GENERATION is the handle's, incremented on every reuse of the slot.
Claim an id slot for a body now at local in set; return its handle.
Give index's slot back; the next body to take it gets a new generation.
--------------------------------------------------------------------- Body sets. One generated columnar layout serves both the awake and the sleeping set; a sleeping body simply keeps zero velocity in columns nobody iterates. The X Y Z here are absolute; DX DY DZ accumulate the substeps' motion within one step and are folded into X Y Z at its end, so that a contact's separation is a small delta added to a base value and a static side needs no state (#S5K3WM). Orientation is only for the eye: a sphere's contact geometry does not turn with it.
The body's handle, so a moved row can patch its id-table entry.
Angular velocity, radians per second, world axes.
Orientation as a unit quaternion (x y z w), for the renderer.
A solid sphere: 1 / (2/5 m r^2).
Linear damping, per second: air drag, or something thicker.
What the body looks like and is: an index into the client's palette of body kinds. The physics never reads it.
See PHYSICS-BODY-... below.
Seconds left to live; negative means immortal.
How long the body has been slow, toward sleep.
Define (name BUFFER INDEX): move BUFFER's last row into INDEX and shrink.
Return the row index that was moved into INDEX, or NIL when INDEX was the last row and nothing had to move. The caller must then patch whatever pointed at the moved row: this is the forwarding-address discipline of #W2M9FJ, kept explicit at every call. Every lane is moved through its precise array type, so no float is boxed on the way.
Define (name SOURCE INDEX DESTINATION): push SOURCE's row INDEX onto
DESTINATION and return its new index there.
buffer's lane array with its precise specialized type, for the loops
that borrow one lane at a time rather than a whole row schema.
--------------------------------------------------------------------- Kinematic boxes: the axis-aligned boxes of things that are not spheres and are moved by their own minds -- the player, an animal. The client posts them afresh every step; a body that meets one is pushed as if by terrain moving at the box's velocity, and the box feels nothing. OWNER is the client's tag, handed back in hit events.
--------------------------------------------------------------------- Contacts. One row per pair within speculative distance, persistent across steps so its impulses can warm-start the next solve. KEY names the pair; KIND says what the other side is; LAST-STEP is when the pair was last within reach, so pruning is one comparison per row.
Handles, never local indices: rows move, handles do not.
For a box contact, the box's owner, for hit events.
The accumulated impulses this pair carries from step to step.
The step's manifold: one point. The normal points from A toward the other side; P is the contact point on the other side, in the world; KV is the other side's velocity when it is a kinematic box.
The per-step constraint buffer: the contacts that will be solved, written in colour order (#G3W7KD) with everything the solver iterates over precomputed, so that a substep touches only these lanes and the awake body columns. A is always a dynamic body; B is a dynamic body, or the static dummy row past the end of the awake set. Anchors are from the body centres to the contact point. KV is the velocity of a kinematic other side (zero for terrain and bodies).
The persistent contact row whose impulses this constraint carries.
Separation at the start of the step, already less the linear slop.
The approach speed before solving, which restitution answers to.
The normal impulses of every substep summed: restitution and hit events only act on contacts that actually pushed.
The softness this contact solves with (#R7F2QH); a static contact is stiffer than one between bodies. BIAS-RATE already carries MASS-SCALE.
---------------------------------------------------------------------
Events: what the step found out, for the client to poll (#F8H3PW).
A begin or end names two handles (the second is PHYSICS-NO-BODY for
terrain); a hit carries the contact point and approach speed; the rest
name one body. OWNER is a box contact's owner.
--------------------------------------------------------------------- The world.
Contact key -> contact row. Patched on every swap-remove.
Where each colour's constraints begin in CONSTRAINTS; one more entry than colours, the overflow colour last.
The colouring scratch: one bit-vector of awake bodies per colour, and the colour each contact was given this step.
The kernel family this world solves with; see PHYSICS-SIMD.LISP.
What the last step cost, so it can be read off the world.
Contact rows made and dropped over the world's life: how much the pairs churn, which is what the hash and the buffer pay for.
Every sphere the block world is simulating, and how they touch.
--------------------------------------------------------------------- Creating, finding, and destroying bodies.
Add a sphere to world and return its handle.
Whether handle still names a body in world.
Return the set constant and local row of handle's body, unchecked.
Return handle's body centre as three single-floats, or NIL when dead.
Return handle's body velocity as three single-floats, or NIL when dead.
Set handle's velocity from a list of three reals, waking the body.
Move handle's row from from-set to to-set, patching every forwarding
address the move disturbs.
The row that filled the hole belongs to another body: tell it.
Bring handle's body into the awake set if it was asleep; return T if so.
Move handle's body out of the awake set; return T if it was awake. #QKS4GZ
A sleeper is still: its velocity is not merely unread.
Remove handle's body from world; its contacts go at the next prune.
Remove every body and contact from world.
Run body with handle bound to each body's handle in the named SETS.
body must not create or destroy bodies.
--------------------------------------------------------------------- Kinematic boxes: posted by the client before each step.
Tell world about a moving box that bodies must not enter this step.
---------------------------------------------------------------------
The terrain probe: the voxel lattice asked one cell at a time, with the
last chunk's storage kept borrowed, so a body's neighbourhood -- almost
always inside one chunk -- costs one hash lookup, not twenty-seven.
Missing terrain is a wall, as it is for the walking bodies (see
world-terrain-solid-p): a body near ground that has not streamed in
leans on the boundary rather than falling through the world.
How many chunks the probe keeps borrowed at once. A body's neighbourhood straddles at most eight chunks.
The way the last question landed in, checked first.
The next way to evict.
Per way: the chunk coordinate held, its borrowed indices (or NIL for a chunk that is not resident), which palette entries are solid, and how long the palette was when that was computed.
Point probe at world and forget whatever chunks it was holding.
Recompute the palette solidity bits of the chunk in way.
Borrow chunk chunk-x,Y,Z into a way of probe and return the way.
The way holding chunk chunk-x,Y,Z, borrowing it if no way does.
Whether the cell at world X,Y,Z is solid, or a boundary standing in.
A palette can grow under us between visits.
Absent: solid below the world's height, air above it.
--------------------------------------------------------------------- The hash grid: every body, awake or asleep, dropped into a cell of a uniform grid keyed by its centre, as a linked list threaded through a NEXT column. Rebuilt from scratch every step; a query walks the twenty-seven cells around a body. Entries are handles.
Drop every body of world into the grid, sized to the largest body.
Enough cells that the lists stay short, as a power of two.
A cell holds the largest body with room to spare, so a query needs only the neighbouring cells.
Run body with handle bound to each body in the grid cells around X,Y,Z.
--------------------------------------------------------------------- Finding contacts. The key packs the pair; the hash gives the row.
Pack a contact key: kind in the low byte, other above it, index-a on top.
Return the row of the contact key, making it if the pair is new.
The row's LAST-STEP is stamped with the current step.
The key survived its pair: a body index came back to life. Keep the row, forget what it carried.
Drop every contact whose pair was not within reach this step, ending those that were touching.
--------------------------------------------------------------------- Contact generation. Each generator writes one manifold into a contact row: normal from A toward the other side, the point on the other side, the other side's velocity, and the separation.
Find every awake body's neighbours in the grid and give each near pair a contact. A moving awake body wakes a sleeping neighbour first; a slow one leans on it as on terrain. Woken bodies join the awake set at its end and are visited by this same pass.
A wake appends to the awake set and may reallocate its lanes, so every lane is fetched afresh per body rather than bound once.
Near enough to wake? Check before relocating.
Each awake pair once: from the lower row.
A is the awake body of the pair, and when both are awake, the lower row.
Run body when the sphere at cx,CY,CZ is within REACH of the box, binding
its contact normal, point, and separation. ACCEPT-P is evaluated with
OUTSIDE-X OUTSIDE-Y OUTSIDE-Z bound to -1, 0, or 1 for the sphere centre's
position against each axis of the box, and may refuse the contact.
Outside the box: the closest point is on a face, edge, or corner.
The normal points from the sphere toward the box, as every
contact normal points from A toward its other side.
The centre is inside the box: push out through the nearest
face. ACCEPT-P then sees the face's direction.
Into the box: against the face's outward direction.
Give every awake body a contact with each exposed terrain face it is near.
The voxel grid is the static tree (#D9W4CH). A cell's face, edge, or corner is only offered when no solid cell lies in the direction the sphere's centre overshoots it, which is what keeps a ball rolling across a floor of cells from catching on the seams between them: the neighbouring cell's own face is always the nearer, truer contact, and it is the only one made. #MYWH16
The neighbour rule needs the centre within one cell of the cells it looks at.
Give every awake body a contact with each kinematic box it is near.
A moving box wakes the sleepers it reaches, before the awake pass so that they are in it.
--------------------------------------------------------------------- Softness (#R7F2QH): three numbers from a frequency, a damping ratio, and the substep.
--------------------------------------------------------------------- Colouring and preparation. Every live contact is given a colour such that no two contacts in one colour share an awake body, then the constraint buffer is filled in colour order with all the solver needs.
Colour the live contacts and fill the constraint buffer in colour order. Return the number of colours in use, the overflow colour counted. #SSMPYW
Pass one: colour.
Static contacts colour from the top down: solved last.
Prefix sums: STARTS[c] is where colour C begins.
Pass two: fill, with a moving cursor per colour.
Anchors: from each centre to the contact point. A's runs along the normal; B's against it.
A tangent frame from the normal.
The normal is near vertical: cross with X.
Cross with Y.
Approach speed now, for restitution later.
Warm start from what the pair carried.
How many colours actually got contacts.
--------------------------------------------------------------------- Kernels. Each phase of a substep is a generic function on the world's kernel family; the methods here are the scalar reference kernels, one contact at a time over the borrowed columns. PHYSICS-SIMD.LISP adds the four-wide families, which run the same arithmetic in the same order on four contacts of one colour at once and hand the tail back here. A kernel is given a half-open range of constraint rows and must touch no other; that is what the colouring guarantees is safe.
The kernel family make-physics-world picks by default; PHYSICS-SIMD.LISP
sets it to a native family when one is available.
Apply gravity and damping to the awake bodies over H.
Advance the awake bodies' deltas and orientations by H.
Apply the carried impulses of every constraint, colour by colour.
starts gives each colour's first row, with one more entry than colours.
One Gauss-Seidel iteration over every colour in turn: normal impulses
with soft or speculative bias when use-bias-p, and without bias plus
friction and rolling resistance when not. elapsed is how far into the step
the substep is, for a kinematic other side's motion.
Bounce the constraints that hit hard enough, colour by colour.
Run body with start and END bound to each non-empty colour's row range.
The columns every contact kernel borrows, bound by name.
The orientation integration the wide position kernel shares with the scalar one: per body, a quaternion step and a normalization.
Angular: rA x P and rB x P, plus the rolling impulse.
Current separation from the accumulated deltas.
Relative velocity at the contact point. A's anchor is along the normal, so its spin adds nothing along it, but the general form is kept so the wide kernel can mirror it.
Rolling resistance: an angular impulse against the relative spin, bounded by the normal impulse.
Friction: one tangent impulse pair, bounded by the cone.
Only a real hit bounces: fast enough, and it pushed.
--------------------------------------------------------------------- Storing impulses back, with the touching state machine and hit events.
A hit: it pushed, and it arrived fast, and someone wants to know.
--------------------------------------------------------------------- Finalizing a step: fold the deltas into positions, age the mortal, and put the still to sleep.
The sleepers age too, or a mortal body could sleep for ever.
Relocations after the loop: they move rows.
Wake every sleeping body within radius of X,Y,Z: the ground moved.
--------------------------------------------------------------------- The step.
Zero the row past the awake set's end: the static side of every contact.
Advance world by dt seconds (its step by default) and return it.
Events from the step are then readable until the next step begins. #IDVK7G
Contacts.
The Soft Step loop.
--------------------------------------------------------------------- Reading the events and the world.
Run body for each event of WORLD's last step.
The last step's events as a list of plists, for inspection.
A hash of every body's position and velocity, in set and row order, so two runs of the same code can be compared. Same-image reproducibility is the claim (#S6T2MV); this is how it is policed.
Check every forwarding address, the way B3VALIDATESOLVERSETS does: each live id names a row whose handle names it back, and each contact row is where its key says. Signal on the first inconsistency; return T.
Gravitational acceleration, cells per second squared, along Y.
Substeps per step: the Soft Step loop runs this many times.
Contact stiffness as a frequency; zero would be rigid.
Contact softness damping ratio.
The most a soft contact will push overlapping bodies apart, cells/s.
How much overlap a contact tolerates before it pushes, in cells.
How far apart two things may be and still have a contact between them.
Approach speed below which nothing bounces, cells/s.
Below this speed a body accumulates sleep time, cells/s.
How long a body must be slow before it goes to sleep.
An awake body faster than this wakes the sleeping bodies it reaches.
Approach speed above which a contact reports a hit event, cells/s.
How many disjoint constraint colours to try before the overflow colour.
How far past its radius a body looks for terrain and boxes, in cells. Wide enough that a fast body's contacts exist a step before it lands.
(handle)(handle)(index generation)Subtraction or unary negation.
An id-table slot naming no body.
The local index that means 'no body': the static side of a contact.
Multiplication and scalar scaling.
(table minimum)A simple Org table without formulas.
The maximum of compatible quantities.
(table set local)Test whether one compatible scalar is at least another.
(table index)(table handle)Logical conjunction of tests and raw truth values.
Test whether one compatible scalar is less than another.
Test whether two compatible scalars are equal.
(name-and-options &body lane-descriptions)Define a concrete synchronized structure-of-arrays buffer. Each lane is (NAME INITIAL-ELEMENT :TYPE TYPE [:CLEAR-ON-REMOVE T]). Optional :QUANTITIES on NAME-AND-OPTIONS groups named physical lanes into fixed quantity projections. The generated MAKE-, -PUSH, -POP, and -RESET functions operate on raw specialized…
Set on bodies that meet other bodies; a spray droplet does not.
Set on bodies whose hard contacts should be reported as hit events.
(name buffer-type)Define (NAME BUFFER INDEX): move BUFFER's last row into INDEX and shrink. Return the row index that was moved into INDEX, or NIL when INDEX was the last row and nothing had to move. The caller must then patch whatever pointed at the moved row: this is the forwarding-address discipline of #W2M9FJ, kept explicit at…
One top-level defining form of a source file.
(name)Return the inspectable physical row layout named by NAME.
(declaration)Return DECLARATION's backend or Common Lisp representation type, or NIL.
Test whether one compatible scalar is at most another.
(name buffer-type)Define (NAME SOURCE INDEX DESTINATION): push SOURCE's row INDEX onto DESTINATION and return its new index there.
(buffer-type buffer lane)BUFFER's LANE array with its precise specialized type, for the loops that borrow one lane at a time rather than a whole row schema.
Logical disjunction of tests and raw truth values.
(definition lane-name)Every sphere the block world is simulating, and how they touch.
(&optional (count "12"))Print COUNT fresh figure IDs that no page uses (default 12); never six hex digits, which the reader takes for a colour.
Addition over compatible quantities.
Division of two represented quantities.
((call tool-call) stream)(&key terrain (kernels :fastest))(world x y z &key (radius 0.25) (mass 1.0) (vx 0.0) (vy 0.0) (vz 0.0)
(restitution 0.3) (friction 0.5) (rolling-resistance 0.01)
(damping 0.05) (kind 0)
(collides-with-bodies-p t) (hit-report-p nil)
(never-sleep-p nil) (lifetime nil))(world handle)(world handle)(world set)(world handle)(world handle)(world handle)(world handle)(world handle from-set to-set)Move HANDLE's row from FROM-SET to TO-SET, patching every forwarding address the move disturbs.
(world handle)(world handle)(world)((handle world &key (sets '(:awake :sleeping)))
&body body)Run BODY with HANDLE bound to each body's handle in the named SETS. BODY must not create or destroy bodies.
(world)(world min-x min-y min-z max-x max-y max-z
&key (vx 0.0) (vy 0.0) (vz 0.0) owner)(world x y z)How many chunks the probe keeps borrowed at once. A body's neighbourhood straddles at most eight chunks.
(probe world)(probe way)(scheme)(block)(probe chunk-x chunk-y chunk-z)The non-negative remainder of integer division.
(world x y z)((domain palette indices) chunk &body body)(probe chunk-x chunk-y chunk-z)(probe x y z)(grid x y z)(world)((bindings buffer buffer-type) &body body)Borrow BUFFER-TYPE's active extent, row declaration, and raw lane arrays. BINDINGS is (LENGTH ROW-DECLARATION (ARRAY LANE-NAME) ...). The buffer is evaluated once, and every array receives its precise specialized array type. This is the checked aggregate boundary for closed scalar or SIMD kernels; the kernel…
((handle grid x y z) &body body)(kind index-a other)(x y z)(world key kind handle-a handle-b owner)Return the row of the contact KEY, making it if the pair is new. The row's LAST-STEP is stamped with the current step.
Compare compatible quantities and produce dimensionless values.
(world)Drop every contact whose pair was not within reach this step, ending those that were touching.
(world row nx ny nz px py pz kvx kvy kvz separation)(world)Find every awake body's neighbours in the grid and give each near pair a contact. A moving awake body wakes a sleeping neighbour first; a slow one leans on it as on terrain. Woken bodies join the awake set at its end and are visited by this same pass.
Test whether one compatible scalar is greater than another.
Logical negation of one test or raw truth value.
The componentwise square root of a raw value.
The minimum of compatible quantities.
((nx ny nz px py pz separation)
(cx cy cz radius min-x min-y min-z max-x max-y max-z reach
&key (accept-p t))
&body body)Run BODY when the sphere at CX,CY,CZ is within REACH of the box, binding its contact normal, point, and separation. ACCEPT-P is evaluated with OUTSIDE-X OUTSIDE-Y OUTSIDE-Z bound to -1, 0, or 1 for the sphere centre's position against each axis of the box, and may refuse the contact.
Convert one scalar float or unsigned value to a 32-bit float.
(world)Give every awake body a contact with each exposed terrain face it is near. The voxel grid is the static tree (#D9W4CH). A cell's face, edge, or corner is only offered when no solid cell lies in the direction the sphere's centre overshoots it, which is what keeps a ball rolling across a floor of cells from catching…
(hertz zeta h)(world awake-count contact-count)(buffer count)(world h)Colour the live contacts and fill the constraint buffer in colour order. Return the number of colours in use, the overflow colour counted. #SSMPYW
Headings, paragraphs, figures and their IDs, mentions, marks.
The componentwise absolute value of a raw value.
The kernel family MAKE-PHYSICS-WORLD picks by default; PHYSICS-SIMD.LISP sets it to a native family when one is available.
(kernels awake h)Apply gravity and damping to the awake bodies over H.
(kernels awake h)Advance the awake bodies' deltas and orientations by H.
(kernels constraints awake starts)Apply the carried impulses of every constraint, colour by colour. STARTS gives each colour's first row, with one more entry than colours.
(kernels constraints awake starts inv-h use-bias-p elapsed push-max)One Gauss-Seidel iteration over every colour in turn: normal impulses with soft or speculative bias when USE-BIAS-P, and without bias plus friction and rolling resistance when not. ELAPSED is how far into the step the substep is, for a kinematic other side's motion.
(kernels constraints awake starts threshold)Bounce the constraints that hit hard enough, colour by colour.
((start end starts) &body body)((constraints awake) &body body)(constraints awake start end)(constraints awake start end inv-h use-bias-p elapsed push-max)(constraints awake start end threshold)(world)(world dt)(world x y z radius)(world &optional dt)Advance WORLD by DT seconds (its step by default) and return it. Events from the step are then readable until the next step begins. #IDVK7G
((kind handle-a handle-b owner x y z speed world) &body body)(world)(world)A hash of every body's position and velocity, in set and row order, so two runs of the same code can be compared. Same-image reproducibility is the claim (#S6T2MV); this is how it is policed.
Interpolate compatible quantities by a scalar amount.
(world)Check every forwarding address, the way B3VALIDATESOLVERSETS does: each live id names a row whose handle names it back, and each contact row is where its key says. Signal on the first inconsistency; return T.
Every public handle (include/box3d/id.h) is a small value struct: Three decisions are packed in here. The index is one-based so that a zero-initialized id is already the null id — C's cheapest initialization convention is recruited as API safety. The world index means any id is globally resolvable without a world…
The organizing structure of a world is the solver set (src/solver_set.h): set 0 holds static bodies, set 1 disabled ones, set 2 is the awake set, and every sleeping island gets a set of its own, numbered 3 and up. A set owns dense arrays of body sims, joint sims, and contact data. Only the awake set carries…
The id indexes a sparse array of small organizational structs that never move. b3Body (src/body.h) holds user data, intrusive linked lists of contacts and joints, sleep bookkeeping, mass properties — and, crucially, a forwarding address: setIndex (which solver set the body currently lives in) and localIndex (its…
The advertised "Soft Step" solver is, structurally, the TGS-soft substepping scheme of Box2D v3 (comments cite Macklin's small-steps paper and Catto's soft-constraint talks). One step at timestep dt with n substeps of h = dt/n runs (b3SolverTask, src/solver.c): Softness is a three-number recipe (b3MakeSoft): a bias…
Constraints are assigned to one of 24 colors (src/constraint_graph.c) such that no two constraints in a color share a body. Assignment is greedy bitset probing over body ids; what doesn't fit lands in a designated overflow color, solved single-threaded. Two refinements carry design weight: – Constraints touching a…
There are no user callbacks during a step, with three narrow documented exceptions (pre-solve, custom filter, material mixers). Everything else is a buffer the user polls afterwards: body move events (one per awake body, with a fell-asleep flag patched retroactively by CCD and sleep), contact begin and end, hit…
A body slower than the sleep speed for half a second -- speed measured as Box3D does, the larger of its velocity and half its position correction rate (#P8N4TC) -- is copied to the sleeping buffer and swap-removed from the awake one, with both moved rows' handles patched (#S5K3WM). Its velocity is zeroed on the way;…
Box3D queries static geometry through an AABB tree because its static world is a soup of arbitrary shapes. A voxel world is better than a tree for this purpose: the grid is the acceleration structure, and a swept AABB enumerates candidate cells directly. The broad phase against terrain degenerates to arithmetic, and…
A sphere near the lattice does not query a tree (#D9W4CH): it enumerates the cells within its reach through a probe that keeps up to eight chunks' storage borrowed for the step, so a neighbourhood that straddles a chunk edge costs a lookup only the first time. For each solid cell the closest point of the cell's box…
Each step every live contact is given a colour by greedy bitset probing over awake rows (#G3W7KD): a body–body contact takes the lowest colour in which neither body is marked, from twenty colours; a contact with terrain or a box takes the highest free colour, marking only its body; what fits nowhere goes to the…
One step-physics-world runs, in this order and with these owners: – Reset. The event buffer empties and the terrain probe forgets its chunks: nothing borrowed survives a step. – Grid. Every body, awake or asleep, is dropped into a uniform hash grid keyed by its centre, cell size a little over the largest diameter. –…
Box3D's deepest structural rhyme with the block-world design: sleeping is relocation out of the iterated arrays (#S5K3WM), and chunk eviction is relocation out of the resident world (#W8D2MT). Both say that "inactive" should mean absent from the hot path, not flagged within it. A luv simulation adopting the domain…
Rigid spheres in the block world: bodies, contacts, and the soft-step solver that moves them.
#G7Q3XR is the design this grew from and the Box3D field notes (#W2M9FJ, #S5K3WM, #R7F2QH, #G3W7KD) are the landmark it steers by. The shape of the thing is the seam #K2F6WD asks for: CLOS objects and generic functions own the world, its parameters, and its events; the bodies and contacts themselves are rows in generated columnar buffers, and every per-substep loop is a closed loop over borrowed specialized arrays.
What is here, in the order the step runs it:
table plus a generation, #N7Q4XS) and lives as one row of whichever SET currently holds it: the awake set the step iterates, or the sleeping set it does not. Sleep is relocation, not a flag; the id table is the forwarding address every relocation patches (#B5N8JT).
directly which exposed faces it is near, through a probe that keeps one chunk's storage borrowed between neighbouring questions. Moving things that are not spheres -- the player, an animal -- enter as KINEMATIC BOXES the client posts each step.
awake body which is moving wakes a sleeping one it reaches; one which is merely resting on it leans on it as if it were terrain.
hash each step, pruned when their pair separates. What persists is the accumulated impulse -- the warm start -- and the touching state the begin/end events derive from.
share a dynamic body (#G3W7KD), and packed in colour order into a constraint buffer. That disjointness is what lets four contacts of a colour be solved in one f32.4 lane group (PHYSICS-SIMD.LISP) with no gather or scatter ever colliding. Static contacts take the highest colours so they are solved last in every iteration.
integrate velocities, warm start, solve with bias, integrate positions, relax without bias; then restitution once, then the impulses are stored back on their contacts.
began or ended, a body hit something hard, expired, slept, or woke.
Everything numeric here is single-float, and every column is a SIMPLE-ARRAY SINGLE-FLOAT (*) or of fixnums, so that the scalar reference kernels and the four-wide kernels read the same storage. Reproducibility is of the same-image, fixed-code kind #S6T2MV settles on: the step's order is deterministic and a state hash can police it, and no more.