Phase 3 (playtesting/tuning): empirical multi-exchange verification confirms Breakout's constants are sane; subjective feel-tuning deferred to the user's own hands-on play
Context
Phase 3 of the approved Breakout plan calls for tuning constants (initial_ball_speed, paddle.speed/paddle_half_width, brick grid size) 'manually via the dev server' -- an inherently hands-on, subjective activity ('does this feel right') an agent without eyes on a real rendered screen or hands on a real mouse/keyboard cannot perform the way a human playtester would. Directly mirrors decision 0025, Tiers' own equivalent phase (RM-020), which established this project's standing response to exactly this situation. tools/browser_check_breakout.ts (decision 0053) already confirmed one real brick hit deterministically, but that alone doesn't confirm the game holds up over a longer, multi-exchange session -- the actual shape of real play.
Options considered
Decision
Ran a one-off Playwright probe (not committed -- ad-hoc, scratchpad-only, same convention as decision 0025's probe, distinct from the repo's permanent tools/browser_check_breakout.ts) against the real dev server: held a simple reactive paddle sweep (alternating ArrowLeft/ArrowRight every 2s) for up to 20 real seconds, sampling #glemy-score/.glemy-brick[hidden]/#glemy-status every 500ms. Observed two real, distinct brick hits (score 0 -> 10 -> 20, at t~=653ms and t~=2722ms) followed by a clean transition to "Game over!" at t~=4789ms once the paddle sweep failed to intercept the ball -- confirming the wall-bounce/paddle-bounce/brick-hit loop genuinely sustains multiple real exchanges, not just the single hit already covered by browser_check_breakout.ts, and that the Lost transition (missing the paddle) fires correctly and doesn't leave the game in a stuck/frozen state. Cross-checked the current constants analytically too: initial_ball_speed's vy component (60) over the ~41-46 world-unit gap from spawn (bounds.min.y + paddle_margin_from_bottom + paddle_height + ball_radius + 1 = 16) to the nearest brick row (bottom row at world-y [57, 62]) predicts a ~0.68-0.77s first hit -- the real probe's observed 653ms falls almost exactly inside that window, a genuine confirmation the physics composition (decision 0052) behaves as designed under real browser timing, not just in gleam test's synthetic dt. paddle.speed (80 world-units/s) against the paddle's own full clamped travel range (80 world-units, half_width 10 in a 100-wide field) predicts a ~1.0s full-width sweep -- comfortably faster than a human's likely reaction time, but the probe's own naive 2s-per-direction AI oscillator was slower than that sweep time, plausibly explaining why it eventually missed; not itself evidence any constant needs changing, since a real human player reacts to the ball's actual position rather than sweeping blindly. No constant was changed as a result -- everything checked out internally consistent and functioning correctly across a real multi-exchange session.
Verification
Real headless-Chromium session against the real dev server and breakout.html: 9 real DOM-state samples over ~4.8s of real wall-clock time showing two real, correctly-scored brick destructions and one real, correctly-triggered game-over, zero page errors throughout. gleam test both targets unchanged (263 Erlang / 288 JavaScript, no source modified by this phase) and both tools/browser_check.ts and tools/browser_check_breakout.ts still passing.
Consequences
docs/development-plan.jsonl's RM-033 resolution is updated to record this verification and reference this decision. Actual feel-tuning of initial_ball_speed, paddle.speed/paddle_half_width, and the brick grid's row/column/spacing constants remains open, explicitly left for the user (or a future session with the user's real hands-on feedback) rather than guessed at here -- consistent with decision 0025's own precedent, this is now the one legitimately open, by-design item on Breakout's roadmap; glemy-website catalog integration (the other half of the plan's Phase 3) is a separate, concretely-scoped, non-subjective remaining step.