One collision per frame. Every surviving brick is tested with circle-vs-AABB, then only the deepest overlap is resolved and remembered as lastHit so the ball can never re-trigger the same brick twice.
Side detection. Overlap on X vs overlap on Y decides the axis: horizontal penetration flips dx, vertical flips dy. The ball is then pushed flush against the brick face, so velocity magnitude survives the bounce intact.
Paddle English. Contact offset -1…1 maps to a ±65° exit angle plus a slice of paddle velocity — edge hits skid wide, centre hits pop almost straight up.
Speed envelope. Magnitude is renormalised after every bounce, ramping 3.40 → 4.00 px/frame as the field clears. A vertical floor on dy prevents infinite horizontal stalls.
Fixed timestep. Physics runs in 16.67 ms steps inside requestAnimationFrame, so 120 Hz phones and 60 Hz desktops play at identical speed.