ludic/changes/collide-centre.md
Orkuncakilkaya 219e638316 fix(render3d): col_resolve threw a body ~1000x too far from dead centre
The degenerate case - a point exactly on a collider's axis, where there is no
direction to push it - set the normal to (1, 0), which is already a unit vector,
and then divided it by the clamped d = 0.001 along with the genuine normals. The
push came out a thousand times too big: dead centre on a 0.5 m trunk moved a body
about 800 m rather than the 0.85 m that clears it.

Off-centre the arithmetic was right, and off-centre is how anything arrives at a
trunk on foot, so nothing in play ever hit it. A teleport, a spawn, or a world
generator dropping something onto an existing collider would have.

(ex, ez) / d is a unit vector for every d > 0, because d is its own length -
there was never anything to clamp and nothing that could grow. The normal is now
built once and explicitly, and the clamp is gone.

Verified through a game, which is the only harness this package has: render3d's
own float helpers need the Gl runtime spliced, so collide.ludic cannot be
compiled standalone for a unit test. Maroon Lake's selftest12 stands a body dead
centre on a trunk and asserts the push never exceeds the two radii added together
- which is the definition of being pushed clear, and so the tightest honest bound
available. It reports 798.88 m before this change and 0.80 m after, with the
off-centre case unchanged at 0.66 m.
2026-09-12 13:55:19 +03:00

921 B

bump: patch type: fix col_resolve no longer throws a body across the map when it starts dead centre on a collider. The degenerate branch — a point exactly on a circle's axis, where there is no direction to push it — chose a unit normal (1, 0) and then divided it by the clamped d = 0.001 anyway, along with the real normals. The push came out a thousand times too large: a body standing exactly on a 0.5 m trunk was moved about 800 m instead of the 0.85 m that clears it.

Off-centre the arithmetic was correct, and off-centre is how anything arrives at a trunk while walking, so it never showed up in play — only a teleport, a spawn or a world generator placing something on an existing collider could land on the axis.

(ex, ez) / d is always a unit vector for d > 0, because d is its own length: there was never anything to clamp. The normal is now built once, explicitly, and the clamp is gone.