# Step 06 — Ray vs AABB (two sweeps become one hit) This is where step 04 ("collision = the AND of two 1D tests") and step 05 ("the entry/exit time of one sweep") finally fuse. A **moving point vs a static box**. ## Concept A point at `p` moves by `v` over the frame. A static box has a left/right edge (its x-interval) and a top/bottom edge (its y-interval). The point is inside the **box** only while it's inside the x-interval **and** the y-interval *at the same time*. So run `sweepInterval` twice: ``` spanX = sweepInterval(p.x, v.x, box.x, box.x + box.w) // the x-edges spanY = sweepInterval(p.y, v.y, box.y, box.y + box.h) // the y-edges ``` Each gives you a time-window `[entry, exit]` during which the point is inside *that one axis's* strip. You're inside the box during the **overlap of the two windows**: ``` entry = max(spanX.entry, spanY.entry) // inside the box once you're inside the LAST axis exit = min(spanX.exit, spanY.exit) // out of the box once you leave the FIRST axis ``` Read those two lines until they feel obvious — they're the whole algorithm: - You're only truly *inside the box* once you've entered **both** strips, so the real entry is the **later** of the two entries → `max`. - You **leave** the box the instant you exit **either** strip → the **earlier** exit → `min`. ### When is there NO hit? 1. **A span is `null`** — on some axis the point isn't moving and is already outside that strip. It can never be inside the box. Return `null` immediately. 2. **`entry > exit`** — the two windows never overlap. The point is inside one strip, then the other, but never both at once. That's the classic "flies past the corner" miss. 3. **`entry >= 1` or `exit <= 0`** — the windows overlap, but not *during this frame* (it's entirely in the future, or entirely in the past). Not our problem this frame. ### The normal (which wall did we hit?) When you do collide, you also want to know **which face** you hit, so the response later can push you back the right way. That's the `normal` — a unit vector pointing out of the surface you struck. The trick: **the axis you entered *last* is the axis you actually hit.** Compare the two entry times — whichever is larger is the blocking axis: - if `spanX.entry > spanY.entry` → you hit a **vertical** wall (left/right face). The normal is horizontal, pointing back against your x-motion: `normal = { x: v.x > 0 ? -1 : 1, y: 0 }`. - otherwise → you hit a **horizontal** wall (top/bottom). The normal is vertical: `normal = { x: 0, y: v.y > 0 ? -1 : 1 }`. (Moving right and hitting something → the surface pushes you left → normal `-1`. That sign rule is all there is to it.) ## Task Implement `rayVsAABB(p, v, box)` in `ray.ts`. Return `{ time, normal }` for the entry, or `null` for any of the three no-hit cases. `sweepInterval` is provided in `given.ts` — **reuse it**, don't re-derive it. ```sh bun test workshop/steps/06-ray-vs-aabb ```