Commit graph

1 commit

Author SHA1 Message Date
Admin
8dadda7f4b Movers push each other apart instead of clipping through
A POST-PASS, not a sweep change. The sweep carries the 0.55 step-up,
CONTACT_SKIN and the terrain-cliff logic, and every existing contract was
written against it, so it is untouched. separate_movers runs after the whole
integration loop — which also makes the result independent of who stepped
first — and before rider pinning, which stays authoritative.

HORIZONTAL ONLY, resolving the least-penetration axis of x/z. Resolving
vertically is exactly how characters end up standing on each other's heads; an
overlapping pair is pushed apart on the ground plane and a stack unpicks
itself.

Three FIXED relaxation passes, deliberately not convergence-based: an
early-exit on "nothing moved" makes the result depend on iteration order, and
this has to be bit-reproducible. Broad phase is a uniform grid sized 2x the
widest half, with buckets as a sorted (cell_key, index) array rather than a
hash map — allocation-light AND ordered without a second sort. That replaced a
hash map of per-cell Vecs and took allocations from 617/tick to ~15.

Each shove is clamped by sweep_axis against the solid world. Without that, a
crowd pressed against a wall squeezes its outermost members straight through.

push_mass weights the split by the OTHER body's mass, so equals each give half
and a player at 4.0 shoulders through NPCs at 1.0. 0.0 — the Default — READS
AS 1.0, not as weightless: a literal zero would make every default-constructed
mover infinitely shovable and divide by zero when two met. Same discipline as
`hidden` over `visible`.

Projectiles are excluded, and that is CORRECTNESS not taste: collect_touches
reports a strike from the overlap itself, so separating projectiles would mean
a bullet could never touch anyone. Sensors, collide:false decor and attached
riders are skipped too.

  50 packed movers               0.023 ms/tick
  200 packed movers              0.123
  12 villagers + 500 static      0.107
  200 movers + 500 static        2.020
Packed crowds where everyone overlaps a neighbour — the honest worst case. The
200-among-500-statics figure is dominated by the per-shove static clamp; at the
realistic 12-50 NPCs it is 0.1-0.25 ms. The fix if 200+ becomes normal is
accumulating pushes and clamping once per mover per pass, deliberately not done
because it changes Gauss-Seidel to Jacobi and the numbers don't justify it.

THE GOLDEN HASHES DID NOT CHANGE, and that is genuine rather than lucky:
mover_scene's walkers start 1.7 apart with 0.4 halves and diverge, and its only
other mover is an attached rider, so no pair ever overlaps and the pass is
inert. Nine new tests prove separation works; the unchanged goldens prove it
does nothing where movers never meet.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-03 12:20:26 +02:00