A seventh behaviour hook, collidefunc, defaulting to akgl_actor_collide_block.
Contacts are delivered one actor at a time with the normal already pointing the
way that actor has to move, which removes the "which one is a1" ambiguity that
made the old physics `collide` slot unimplementable -- and means a game
overriding one actor's response never has to reason about the other's.
The default moves the actor out along the normal by the penetration depth and
removes the component of its motion that was going into the surface. Three
things about that are easy to write wrongly, and each has a test that names the
symptom rather than the assertion:
- **It writes `e` and `t`, never `v`.** akgl_physics_simulate recomputes velocity
as `e + t` at the top of every step, so a write to `v` is discarded before
anything reads it. Worse than useless: an actor holding a direction into a
wall would keep accumulating thrust while standing still and leave at speed
the instant the wall ended. Breaking this leaves the fall running at 300 px/s
through the floor.
- **It removes the component, not the axis.** Zeroing the whole thrust vector
costs the actor its motion *along* the surface as well, which is a character
scraping a ceiling stopping dead and a character landing on a floor losing its
run. Sliding along a wall is the difference between a wall and glue.
- **A separating contact is left alone.** An actor already moving out of a
surface that gets its velocity zeroed is an actor stuck to a wall it is
walking away from, and it reads to a player as the collision grabbing them.
`e` and `t` are treated separately rather than as their sum, so an actor
pressing into a floor loses its downward gravity without losing the sideways run
it is also doing.
A sensor is reported and never pushed -- walking through a coin is not being
blocked by it.
The default deliberately does not pre-load the environmental term with a step of
gravity. A resolver running *before* the step has to, or the step it is
correcting for re-adds gravity and commits a quarter pixel of penetration, which
is invisible and still fatal because every horizontal move then reads as
blocked. This library resolves after the move, so the case does not arise; the
sidescroller example carries that workaround because it had no choice.
Also asserted: the other six hooks survive. A seventh that displaced one of them
would be a silent regression somewhere unrelated.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>