The collide slot has existed unused since the backend was written. It is wired
in now, and its signature changes from (self, a1, a2) to (self, actor, dt): the
old shape had nowhere to put a normal, a depth, or a piece of map geometry, and
no answer to which of the two actors it was supposed to be resolving.
**Resolution runs after `move`.** That is the decision everything else follows
from. A resolver called from movementlogicfunc runs before gravity, drag and the
move, so it has to predict where the actor will end up -- which means
re-implementing the integrator's arithmetic in the game, and being wrong
whenever the integrator changes. examples/sidescroller/collision.c carries forty
lines of exactly that, and says so.
Only `move` is subdivided. Gravity, drag, the thrust integration and the speed
ellipse all still run once against the whole dt, and sum(subdt) is dt, so an
actor with nothing to collide with takes one sub-step of exactly dt -- `dt/1.0f`
is dt exactly in IEEE-754 -- and follows the arithmetic path it always did. That
is what lets the integrator stay untouched and every recorded physics number
stay valid, and tests/physics.c and tests/physics_sim.c pass unchanged with no
world attached.
Collision is opt-in. A backend with no collision world costs one comparison per
actor per step and does nothing else.
Also here:
- `self->gravity` was never null-checked and is dereferenced unconditionally, so
a hand-built backend with only `move` filled in crashed rather than reporting.
- Children are re-snapped in a post-pass, gated on collision being on. The
in-loop snap uses whatever position the parent had when the child's slot came
up in pool order, which is already sometimes a frame stale; that was harmless
while a parent only moved by v*dt and is not once a parent can be pushed out
of a wall mid-step.
- tests/physics.c's deliberate tripwire has been visited. It asserted that
arcade collide always raised AKERR_API, with a comment saying it existed so
that implementing collision would have to come back here. What replaces it
asserts the opt-in contract instead.
- physics_sim.c's hard-coded "%d of 7 simulations" is derived now. A literal
goes stale the first time somebody adds a simulation, which is this commit.
Three new whole-motion simulations, and the third took two attempts to make
honest:
- Landing and resting: 0.0000 px of drift over 120 steps after settling.
- Walking after landing: 107 px in a second, which is the symptom a player sees
when a resting actor is caught on the ground it is standing on.
- A fast fall not tunnelling. The first version used gravity, so the step size
grew every frame and the actor happened to land *inside* the floor rather than
stepping over it -- it passed with sub-stepping disabled and proved nothing.
It uses a constant 1200 px/s now, so every step is exactly 60 pixels against a
32-pixel window in which an overlap exists, and the samples miss it cleanly.
With sub-stepping the actor stops at y=288; without it, y=1300.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>