Breakout

Canvas 2D · 400×600 field · 8×4 bricks · 320 max score · 3 lives
Field // 01Standby60 fps
drag the field to steer

Collision matrix

AABB · single resolution per substep

FaceTestResponseGuard
Wallx−r < 0 · x+r > 400 · y−r < 0 dx = −dx  /  dy = |dy|hard reposition to contact
Paddleoverlap & dy > 0 & prevBottom ≤ ypad θ = offset · 60° → dx, dytop / side / underside resolved separately
Brickcell lookup 8×4, nearest penetration flip dy (top/bottom) · flip dx (side)one brick per substep, +10 pts
Voidy − r > 600 lives −− · ball respawns on paddlegame over at zero lives

Engineering notes

vanilla js · zero libraries

  1. 01Fixed timestepAccumulator locks logic to 60 Hz so a 120 Hz phone doesn't run the ball twice as fast. Render runs free, logic never drifts.
  2. 02Two substepsEach tick splits the 3–4 px displacement in half, so a 3 px ball can't tunnel a 20 px brick at grazing angles.
  3. 03Previous-frame side testCollision side is decided from where the ball was, not just the overlap — that's what stops the ball reversing into a wall and getting stuck.
  4. 04Magnitude renormalisedAfter every bounce the velocity vector is rescaled to |v| ∈ [3.44, 4.00]. Angles change, speed never degenerates.
  5. 05Paddle englishImpact offset maps to ±60°; the minimum vertical component keeps rallies from flattening into an infinite horizontal loop.
  6. 06Crisp at any DPRBacking store scales with devicePixelRatio while the logical field stays exactly 400×600 — touch coordinates are inverse-mapped through the CSS rect.