1.8 KiB
Step 08 — Resolve (move to the wall, not through it)
Detection is done. Now for response — actually reacting to the hit. This first half is almost embarrassingly small, but it introduces the idea the whole loop (step 10) is built on: the frame isn't all-or-nothing.
Concept
sweptAABB hands you a Hit with a time between 0 and 1 — the fraction of
the frame at which you'd collide. So instead of moving the full displacement v
(which would bury you inside the wall), you move only the part of it that
happens before impact:
no hit → newPos = pos + v (nothing in the way: take the whole move)
hit at t → newPos = pos + v * t (stop exactly at the contact point)
That's it — you already have add and scale; this is them, gated on the hit.
The leftover that matters
Here's the seed for step 10: if you hit at t = 0.3, you only used 30% of
this frame. The other 70% is still owed to the player — they should keep
moving for the rest of the frame, just not into the wall. That leftover time
is exactly why sliding (step 09) and the loop (step 10) exist. A collision
doesn't end the frame; it interrupts it.
Real-engine footnote: production code usually moves to
t - EPSILON(a hair short of contact) so floating-point error can't leave the box a sliver inside the wall, where the next frame's sweep would start already-overlapping. Yournagedoes this withMath.max(0, time - EPSILON). We keep the kata exact so the numbers stay clean — just know that tiny backoff is there for a real reason.
Task
Implement resolve(pos, v, hit) in resolve.ts: return the full move when
hit is null, otherwise the position at contact. vec/add/scale and the
Hit type are in given.ts.
bun test workshop/steps/08-resolve