Machine spec
read-outLogical canvas space, DPR-scaled backing buffer, CSS-fitted from 320px up to desktop.
Brick grid at x = col·50, y = row·20, flush to the field edges — 32 blocks, full-width rows.
Per brick destroyed. Hard ceiling of 320 points, cleared in one perfect life.
Ball speed magnitude in px/frame. Ramps as the wall thins, renormalised on every bounce.
Max paddle deflection. Contact offset across the 60px paddle sets the exit angle — no vertical ping-pong.
Collision & edge cases
Circle vs AABB, single resolution. Every frame is divided into two sub-steps; each sub-step sweeps the ball's bounding box across the brick grid, keeps only the closest overlapping cell, resolves it, then stops scanning. Two bricks can never be consumed in one sub-step.
Side detection. Overlap on both axes is measured against the inflated rect. The smaller penetration defines the reflected axis — dy for top/bottom, dx for left/right — and the ball is separated out of the brick so it cannot re-trigger next frame. A second validation rejects a flip that would leave the ball still overlapping, unsticking corner hits.
Paddle faces. The paddle is a full solid box: top face deflects by contact offset, side faces bounce dx so the ball never tunnels through, and the paddle's own x is clamped to 0…340 so it can never leave the field.
Frame safety. Fixed 60Hz accumulator, max 5 catch-up steps, delta clamped after tab switches. Wall bounce preserves the velocity magnitude — |v| only changes at the paddle, never by accident.