# Step 12 — The overlap trap (when the sweep has no answer) You found this one yourself, in the capstone: start a box **inside** another collider, press a key, and the position turns into `NaN`. That's not a typo in your code from steps 01–10 — the kernel is *correct* and still does this. It's a **blind spot in the whole approach**, and every real engine has to patch it. > This is a genuine hole in the finished kernel, shared by the capstone's > `game.js`. It is a *third* thing, separate from the two bugs you're hunting in > `engine/system/physics.ts` — no spoilers here. ## Why the sweep can't see it Everything since step 05 answers one question: *"when, during this frame, will I **enter** the box?"* The whole ladder quietly assumes the answer lies in the future — that you start the frame **outside**. Start inside, and "when will I enter?" has no sane answer. The math doesn't refuse — it cheerfully reports that you entered *in the past*. Remember step 05: what sign does `entry` have when `p` is already between `min` and `max`? Every function above `sweepInterval` trusts that number without checking it. ## The autopsy (do this before reading on) The `NaN` isn't born where you see it — it's the end of a chain, and each link is one line you wrote. Reproduce it in a scratch file and trace it: ```ts import { moveAndSlide, sweptAABB } from "./given.ts"; const box = { x: 10, y: 10, w: 2, h: 2 }; const wall = { x: 0, y: 0, w: 32, h: 32 }; console.log(sweptAABB(box, { x: 5, y: 0 }, wall)); console.log(moveAndSlide(box, { x: 5, y: 0 }, [wall])); ``` Work through these **in order, predicting each answer before checking** (add `console.log`s inside your step-10 loop — it's your code, instrument it): 1. What `time` does `sweptAABB` report? Now flip the velocity so the box moves *away* from the wall — why do you *still* get a hit? (This is why you can't even walk out of a wall you're stuck in.) 2. Follow that `time` into the `else` branch of `moveAndSlide`. Three lines use it. Which line is saved by the `Math.max(0, …)`? What happens to `vel` when you `slide` against that normal? And what does `timeLeft = timeLeft * (1 - time)` do when `time` is negative — shrink, or *grow*? 3. Next iteration: `vel` is now `(0, 0)` but the loop keeps going. What does `sweepInterval` return for `v = 0` while inside the interval (look at the first branch — you wrote it in step 05)? So what is `entry` now, and what does `timeLeft` become after multiplying by `(1 - entry)`? 4. Last link. In JavaScript, what is `0 * Infinity`? That's `scale(vel, timeLeft)` on iteration three. And once one `NaN` exists, every comparison against it is `false` — so which branch of the loop does the poisoned move fall into, and what does `pos = add(pos, move)` do then? Four links: **overlap → a hit in the past → dead velocity + growing time debt → `0 × ∞`**. When you can retell that chain from memory, you own it. ## The fix: measure the overlap, push out The sweep is *continuous* detection — it prevents overlap but can't recover from it. So real engines pair it with a *discrete* partner: if you're already inside, don't ask "when do I enter?" — ask **"how deep am I, and what's the shortest way out?"**, then teleport that far and *only then* sweep. That shortest-way-out is the **penetration vector** (the famous *minimum translation vector*). For two overlapping AABBs there are exactly four escapes — push `a` left, right, up, or down until the boxes just separate: ``` outLeft = (a.x + a.w) - b.x // slide a left this far -> separated outRight = (b.x + b.w) - a.x // slide a right this far -> separated outUp = (a.y + a.h) - b.y outDown = (b.y + b.h) - a.y ``` Two things to convince yourself of (don't skip — the tests check both): - The boxes strictly overlap **iff all four distances are positive**. (What is `outLeft` when `a` sits fully to the right of `b`? When they merely touch?) - The answer is the **smallest** of the four, as a vector, with the sign that moves `a` *away*. Smallest, because depenetration is a teleport the player can see — one pixel of pop beats being flung across the room. Note this handles `a` fully *swallowed* by `b` too, where "the overlap of the intervals" would lie to you — one of the tests is exactly that case. ## Task Two functions in `overlap.ts`: 1. `penetrationVector(a, b)` — the smallest single-axis push that separates the boxes, or `null` if they don't strictly overlap. 2. `safeMoveAndSlide(box, v, walls)` — check every wall; if the box is inside one, apply the push **plus an `EPSILON` of slack in the push direction** (same idea as the backoff in the loop: land *flush* on the wall and next frame's sweep starts half-trapped again). Then run the given `moveAndSlide` from the safe position. ```sh bun test workshop/steps/12-overlap ``` ## Make the capstone unbreakable (optional) Port both functions into `11-capstone/game.js`, swap the `moveAndSlide` call for `safeMoveAndSlide`, and set the player's spawn inside the pillar. It should pop out and play on like nothing happened. Then the question you actually care about: does your *real* engine survive the same experiment?