cabinet 04 · 400×600 · aabb · 60fps
CAB-04 canvas · vanilla js

BREAKOUT

32 cyan blocks. one yellow ball. three lives. Explicit AABB collision, closest-hit resolution, fixed 60 Hz physics step — no engines, no libraries.

60fps
READYstate
ONaudio · M
screen 400×600pal #00ffff
mouse · arrows · finger drag

tap the screen to serve — the paddle follows your finger

build notes

How the physics actually works

Every collision below is written by hand. The rule that keeps the board honest: one resolution per frame — the closest penetration wins, the rest wait for the next tick.

  1. 01

    Field & grid

    Eight columns, four rows, laid out top-aligned with no gutter — brick i lives exactly at x = col*50, y = row*20.

    400×600 · 8×4 · 50×20
  2. 02

    Ball launch

    Seeded from dx=2, dy=-2.8, normalised, then jittered ±11° so no two serves are identical — never straight up.

    |v| = 3.44 → 4.00 px/f
  3. 03

    Paddle angle

    Hit position across the 60px face maps to a ±60° exit angle. Centre bounces up, edges fling it wide — and the vertical floor stops horizontal lock.

    rel ∈ [-1,1] → sin/cos
  4. 04

    AABB vs circle

    Closest-point test against the brick rect, then four penetration depths. The smallest decides the axis: top/bottom flips dy, left/right flips dx.

    closest hit wins · 1/frame
  5. 05

    No sticking

    On resolution the ball is repositioned flush outside the brick, so it can never re-enter the same cell — and velocity is re-normalised to constant magnitude every step.

    clamp + renormalise
  6. 06

    Lives, score, endgame

    +10 a brick, 320 max. Three drops ends it. Paddle is clamped to 0–340 so the side walls are always sealed — no sneaky corner escapes.

    3 lives · win @ 32
  7. 07

    Touch

    Finger x is remapped through getBoundingClientRect() into game space, smoothed toward the paddle, with touch-action:none so the page never scrolls mid-rally.

    ontouchstart / move / end
  8. 08

    Frame rate

    Accumulator loop with a fixed 16.66 ms step (capped at 5 catch-up steps), so a 120 Hz phone and a 60 Hz monitor play identically.

    requestAnimationFrame