Task #22: Slap/ragdoll physics on Timmy
## What was done
- agents.js: spring physics (stiffness=7, damping=0.8, clamp ±0.44 rad) on
timmy.group.rotation.x/z via slapOffset/slapVelocity integrated per-frame with
proper dt (capped at 50ms for tab-background safety)
- agents.js: applySlap(hitPoint) — computes XZ impulse direction from hit point
relative to TIMMY_POS, adds angular velocity, triggers pip startle + crystal flash
- agents.js: _playBoing() — lazy AudioContext, sine oscillator 260→90 Hz with
exponential gain decay (0.38s)
- agents.js: Pip startle — 3s decay timer, random scatter direction offset, 4x
spin speed while startled, boosted pip light intensity
- agents.js: Crystal ball hit flash — hitFlashTimer=0.5s, intensity spikes to 10
and fades; normal crystalLight/cbMat logic when not flashing
- agents.js: getTimmyGroup() export for raycaster target
- interaction.js: registerSlapTarget(group, applyFn) — stores targets
- interaction.js: _onPointerDown capture-phase listener — raycasts against
timmyGroup recursively, calls applySlap on hit, suppresses OrbitControls drag
for 220ms via stopImmediatePropagation + controls.enabled toggle
- main.js: imports getTimmyGroup, applySlap, registerSlapTarget; wires
registerSlapTarget(getTimmyGroup(), applySlap) after initInteraction
## Verification
- Vite build: clean, 14 modules, 0 errors
- /tower HTTP 200
- Testkit: 27/27 PASS