- Collision test
- Circle vs AABB via closest-point distance. Penetration is resolved on the shallower axis, which yields the true bounce side: top/bottom flips dy, left/right flips dx.
- One hit per frame
- All 32 boxes are scanned, the deepest overlap wins, and that brick id is stamped with the current frame so it can never re-trigger. The ball is pushed clear of the grid — no brick sticking.
- Constant speed
- Velocity is re-normalised to 3.44 px/frame after every bounce, so paddle angle changes direction but never pace. A vertical-speed floor stops flat stalls.
- Paddle angle
- Contact offset from centre (−1…1) maps to a ±60° exit angle. Dead centre ≈ vertical, edges throw hard flats. Side faces are solid, so the ball can't slip past.
- Sub-stepping
- Motion splits into up to 4 sub-steps per frame with a collision pass in each, so r3 at speed can't tunnel a 20px brick or the 10px paddle lip.
- Refresh independence
- Position integrates by dt/16.67, so the trajectory is identical on 60, 90 and 120 Hz panels, and a backgrounded tab can't teleport the ball.
- Fail states
- Ball leaving the bottom costs one of 3 balls, re-seats the ball on the paddle, and never resets score. 32 bricks cleared = You Won. Both end screens carry a restart control.
- Mobile
- Touch-drag with pointer capture and eased follow, touch-action:none, dpr backing store capped at 3×, particle pool capped, screen letterboxed from 320 px up.