--- phase: 02-core-gameplay plan: 02 subsystem: ui tags: [canvas, game-loop, state-machine, scoring, pong, vanilla-js] # Dependency graph requires: - phase: 02-core-gameplay/02-01 provides: paddle2, AI module, predictive ray-cast, 3 difficulty presets, Input cleanup provides: - Full game state machine (modeSelect → diffSelect → playing → scored → gameover) - Scoring detection (ball exits left/right edge → score1/score2 increment) - 1-second post-score pause with auto-serve - Match-end condition (first to WIN_SCORE=7 wins) - Mode selection UI (1=Solo vs AI, 2=2-Player) - Difficulty selection UI (1=Easy, 2=Medium, 3=Hard) - Winner overlay with "Press R to play again" - R-key restart (scores reset, back to modeSelect) - Center divider dashed line rendering - Score numbers rendered on canvas (top-left / top-right) affects: [03-menus-audio, 04-effects-powerups, 05-polish-release] # Tech tracking tech-stack: added: [] patterns: - State machine pattern via gameState string field driving render/update branches - GameLoop owns scoring detection; Physics.update() no longer owns out-of-bounds reset - pauseTime field on GameLoop tracks scored-state start for 1s serve delay key-files: created: [] modified: - index.html key-decisions: - "GameLoop owns scoring/serve, not Physics: removed auto-reset from Physics.update() so GameLoop can increment scores before calling serveBall()" - "modeSelect state renders full-screen prompt and returns early — no physics/paddles rendered until game starts" - "pauseTime stored on GameLoop object (not GameState) — transient timing data that doesn't need to survive restart" - "Ball hidden during 'scored' pause state (gameState !== 'scored' guard on drawCircle) — clean visual signal to players" - "2-Player mode skips diffSelect entirely — straight to playing after pressing 2" - "Winner identity set on GameState.winner as 'player1' | 'ai' | 'player2' — AI win labeled 'AI', not 'Player 2'" patterns-established: - "State machine pattern: gameState string drives render/update branches in GameLoop.main()" - "GameLoop owns game-level events (scoring, match end, serve); Physics owns physics-level events (collision, deflection)" requirements-completed: [CORE-05, CORE-06] # Metrics duration: ~5min (continuation after human-verify checkpoint) completed: 2026-03-10 --- # Phase 2 Plan 02: Core Gameplay State Machine Summary **Full game loop with state machine, mode/difficulty selection UI, scoring detection, 1s re-serve pause, match-end at 7 points, and R-key restart — all in a single index.html** ## Performance - **Duration:** ~5 min - **Started:** 2026-03-10T20:10:00Z - **Completed:** 2026-03-10T20:15:00Z - **Tasks:** 2 (1 auto + 1 checkpoint) - **Files modified:** 1 ## Accomplishments - Game state machine fully wired: modeSelect → diffSelect → playing → scored → gameover → modeSelect - Scoring detection moved to GameLoop (after Physics.update()), Physics auto-reset removed — scores now correctly increment - Mode/difficulty selection screens rendered on canvas with clear key prompts - Winner overlay with player name and restart prompt; R-key cleanly resets all state - Phase 1 debug speed text removed; center divider dashed line added ## Task Commits Each task was committed atomically: 1. **Task 1: State machine — mode/difficulty selection, scoring, match end, and restart** - `77071a5` (feat) 2. **Task 2: Checkpoint: Verify Phase 2 complete gameplay** - approved by user (no code commit) ## Files Created/Modified - `index.html` - GameLoop.main() rewritten with full state machine; Input._handleKeyDown extended with mode/difficulty/restart keys; Physics.update() out-of-bounds auto-reset removed; pauseTime field added to GameLoop ## Decisions Made - GameLoop owns scoring detection and serve calls — Physics.update() no longer has out-of-bounds reset. This separation is critical: if Physics auto-resets, GameLoop never sees the scoring condition. - Ball hidden during `scored` pause via guard on drawCircle — cleaner player feedback than showing stationary ball. - 2-Player mode bypasses difficulty selection entirely (no AI to configure). - pauseTime stored on GameLoop (not GameState) — it's transient per-point timing that doesn't need to survive an R-key restart. ## Deviations from Plan None - plan executed exactly as written. ## Issues Encountered None. ## User Setup Required None - no external service configuration required. ## Next Phase Readiness - Phase 2 complete: full playable Pong with mode selection, AI opponent, scoring, match end, and restart - Phase 3 (menus, audio, pause) can proceed — GameState fields (gameState, mode, difficulty, score1, score2, winner) are stable contracts - No blockers --- *Phase: 02-core-gameplay* *Completed: 2026-03-10*