canvas build · v1.0 · vanilla js

BREAKOUT

Thirty-two bricks, one paddle, three lives. Fixed-timestep loop, analytic AABB resolution, side-aware reflections — rendered entirely to a 400×600 canvas at 60 FPS.

field 400×600 grid 8×4 bricks 32 max score 320 deps none
crt · 400×600 · 60hz
mode: serveinsert coin

engineering notesHow the ball decides
which wall it just met.

01

Axis-separated motion, then AABB overlap

The ball is advanced on X, resolved, then advanced on Y and resolved. Because it moves only ~1.9 px per sub-step against a 3 px radius, tunnelling through a 20 px brick is impossible — even on a 120 Hz phone.

02

Smallest penetration wins the side call

For each candidate brick we compute horizontal and vertical overlap. ox < oy means the ball entered from a left/right face → flip dx; otherwise it landed on a top/bottom face → flip dy. The ball is then pushed out by exactly that penetration depth, so it can never end a frame inside the brick.

03

One collision per step, plus a 4-step cooldown

Only the deepest overlapping brick is processed each step; every other brick touched in the same frame is deferred. Destroyed bricks carry a cooldown counter so a grazed corner can't register twice.

04

Paddle angle is a mapping, not a bounce

rel = (ball.x − paddleCentre) / 30 drives a ±58° launch vector, and a fraction of paddle velocity is added as sidespin. The resulting vector is renormalised to the constant speed, so |v| never drifts across a long rally.

05

Angle guard & grid lookup

After every reflection the velocity is clamped to a minimum vertical and horizontal component — no infinite horizontal ping-pong, no dead-vertical stall. Brick queries index a 2D grid by cell rather than iterating all 32 bricks.