akgl_collision_test takes two proxies and fills in a contact: a unit normal, a
depth, and a point. Three paths, cheapest first.
The proxies' bounds reject most pairs in four comparisons and no arithmetic,
which is free because the bounds were computed when the proxy was synced.
A box against a box is answered in closed form. That is not only cheaper than
the general solver, it is *exact*, and the difference shows up where it matters
most: an actor resting on a floor wants a normal of precisely (0, -1, 0), and an
iterative solver converges to something like (0.0001, -0.99999, 0), which
accumulates into a slow sideways creep. A tile game is almost entirely boxes, so
this is the path that runs.
Everything else goes to ccdMPRPenetration. MPR rather than GJK+EPA because MPR
allocates nothing at all, converges in fewer iterations, and its one weakness --
a coarser contact *point* -- is on a field the blocking resolver never reads.
EPA stays compiled and available behind the arena for a caller who one day wants
an accurate manifold. The header says the point is approximate so nobody builds
a damage falloff on it.
The normal points out of the second shape and toward the first, so a caller
moving `a` along it by `depth` separates the pair. libccd hands back the
opposite convention; converting once here saves every resolver from remembering
the sign, and getting it backwards would compile, would pass any "do these
collide" test, and would drag actors into walls. There is a test that asserts
the direction, and inverting the conversion turns it red.
AKGL_COLLISION_TEST_PLANAR flattens the normal into the xy plane. The extrusion
the setters apply already makes z the most expensive axis, so this is a net for
a caller who set a depth by hand -- and the failure it catches is silent: the
narrowphase reports a contact, the resolver pushes the actor into the screen,
and the actor does not move on screen while remaining inside the floor. It also
rescues the degenerate case, two shapes at exactly the same centre, where there
is no planar direction at all and a zero-length normal would be a wall that
stops nothing. Level authors stack things constantly.
Three things the tests found rather than assumed:
- The guard has two copies, one per path, and the box one is where it earns its
place. Removing both is caught; removing only the iterative one is not,
because the iterative solver is seeded from the line between the two centres
and so converges to a planar answer on its own for a planar offset. That is
measured and written down in the test rather than papered over with a fixture
contrived to force it.
- A concentric pair has no preferred sign on the degenerate axis, so the test
asserts the magnitude of the normal rather than its direction. The first
version asserted -1 and failed against an equally correct +1.
- The box fast path and the iterative solver are two implementations of one
answer, so they are driven over the same arrangements and required to agree on
the decision and the axis. A shortcut nothing cross-checks is a shortcut
waiting to diverge.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>