--- phase: 01-foundation plan: 02 type: execute wave: 2 depends_on: [01-01] files_modified: [index.html] autonomous: true requirements: [CORE-01, CORE-02, CORE-03, CORE-04] must_haves: truths: - "Ball moves continuously across the canvas — it never stops" - "Ball bounces off the top wall and bottom wall predictably (angle of incidence = angle of reflection)" - "Ball deflects off Player 1 paddle when they collide" - "Ball deflects at different angles depending on which zone of the paddle it hits (top edge ~60 degrees, center ~5 degrees)" - "Ball speed increases after each paddle hit and resets when it passes the left or right edge" - "Ball serves automatically in a random direction on start and after each reset" artifacts: - path: "index.html" provides: "Complete Physics.update() with ball movement, wall bounce, paddle collision, zone deflection, speed increment" contains: "ball.vx *= -1, zone, angles, ball.speed" - path: "index.html" provides: "Ball serve function that initializes ball velocity" contains: "serveBall" - path: "index.html" provides: "Speed increment constant tunable via GameConfig" contains: "GameConfig" key_links: - from: "Physics.update()" to: "ball position" via: "ball.x += ball.vx * deltaTime; ball.y += ball.vy * deltaTime" pattern: "ball\\.x.*deltaTime" - from: "wall bounce logic" to: "ball.vy" via: "Math.abs(ball.vy) inversion on top/bottom boundary crossing" pattern: "Math\\.abs.*vy" - from: "zone deflection" to: "ball.vx / ball.vy" via: "relativeHitPos -> hitZone -> angle -> cos/sin decomposition" pattern: "relativeHitPos|hitZone|getPaddleAngle" - from: "speed increment" to: "ball.speed" via: "ball.speed += GameConfig.speedIncrement on each paddle hit" pattern: "speedIncrement" --- Complete the physics simulation: ball moves continuously, bounces off walls and paddles, deflects at zone-based angles, and accelerates with each rally. After this plan, the full Phase 1 experience is playable — Player 1 can hit a moving ball with angle control and watch speed build up over a rally. Purpose: Completes Phase 1. All 6 success criteria become verifiable. Output: Fully updated index.html with working ball physics replacing the Plan 01 skeleton. @/home/dabit/.claude/get-shit-done/workflows/execute-plan.md @/home/dabit/.claude/get-shit-done/templates/summary.md @.planning/ROADMAP.md @.planning/phases/01-foundation/01-CONTEXT.md @.planning/phases/01-foundation/01-RESEARCH.md @.planning/phases/01-foundation/01-01-SUMMARY.md GameState (from Plan 01): ```javascript GameState.ball = { x, y, // current position (logical pixels) radius: 8, vx, vy, // velocity (logical pixels per second) speed, // current scalar speed (px/s) color: '#fff' }; GameState.paddle1 = { x, y, // current position width: 12, height: 80, speed: 400, // movement speed (px/s) color: '#fff' }; ``` Physics object (skeleton from Plan 01 — replace update() and init() fully): ```javascript Physics.init(width, height) // called once at startup Physics.onResize(width, height) // called on window resize Physics.update(deltaTime) // called every frame — REPLACE THIS ``` Renderer helpers available: ```javascript Renderer.drawRect(x, y, w, h, color) Renderer.drawCircle(x, y, radius, color) Renderer.logicalWidth Renderer.logicalHeight ``` Task 1: Ball physics — movement, wall bounce, serve, and GameConfig index.html Replace the Physics.init() and Physics.update() skeleton from Plan 01 with the full implementation. Also add a GameConfig object and serveBall() function. **Add GameConfig object** (place before GameState in the script): ```javascript const GameConfig = { initialBallSpeed: 220, // px/s — starting speed per serve speedIncrement: 18, // px/s added per paddle hit paddleSpeed: 400 // px/s — Player 1 paddle movement }; ``` **Update Physics.init()** — initialize ball with a served velocity: ```javascript init(width, height) { this.width = width; this.height = height; // Position paddle1 on left side, vertically centered GameState.paddle1.x = 30; GameState.paddle1.y = height / 2 - GameState.paddle1.height / 2; GameState.paddle1.speed = GameConfig.paddleSpeed; // Serve ball from center this.serveBall(); }, ``` **Add serveBall() method to Physics:** ```javascript serveBall() { const ball = GameState.ball; ball.x = this.width / 2; ball.y = this.height / 2; ball.speed = GameConfig.initialBallSpeed; // Random vertical angle between -45 and +45 degrees, always heading right const angle = (Math.random() * 90 - 45) * Math.PI / 180; // Randomly serve left or right const dir = Math.random() < 0.5 ? 1 : -1; ball.vx = Math.cos(angle) * ball.speed * dir; ball.vy = Math.sin(angle) * ball.speed; }, ``` **Replace Physics.update()** with full ball physics: ```javascript update(deltaTime) { const ball = GameState.ball; const paddle = GameState.paddle1; // --- Move paddle1 --- const dir = Input.getVerticalInput(); paddle.y += dir * paddle.speed * deltaTime; paddle.y = Math.max(0, Math.min(this.height - paddle.height, paddle.y)); // --- Move ball --- ball.x += ball.vx * deltaTime; ball.y += ball.vy * deltaTime; // --- Wall bounce (top and bottom) --- if (ball.y - ball.radius < 0) { ball.y = ball.radius; ball.vy = Math.abs(ball.vy); } if (ball.y + ball.radius > this.height) { ball.y = this.height - ball.radius; ball.vy = -Math.abs(ball.vy); } // --- Ball out of bounds (left or right) → reset --- if (ball.x + ball.radius < 0 || ball.x - ball.radius > this.width) { this.serveBall(); return; } // --- Paddle collision (only check when ball moving left toward paddle1) --- if (ball.vx < 0) { this._checkPaddleCollision(ball, paddle); } }, ``` **Add _checkPaddleCollision() method to Physics:** ```javascript _checkPaddleCollision(ball, paddle) { // AABB: check ball center against paddle bounds (with ball radius buffer) const inX = ball.x - ball.radius < paddle.x + paddle.width && ball.x + ball.radius > paddle.x; const inY = ball.y + ball.radius > paddle.y && ball.y - ball.radius < paddle.y + paddle.height; if (!inX || !inY) return; // Zone-based deflection: divide paddle into 5 zones const relativeHitPos = (ball.y - paddle.y) / paddle.height; const hitZone = Math.max(0, Math.min(4, Math.floor(relativeHitPos * 5))); // Map zone to angle (degrees) — positive = downward const anglesDeg = [ -60, // Zone 0: top edge — steeply upward -30, // Zone 1: upper — angled upward 5, // Zone 2: center — nearly flat (slight downward to avoid infinite horizontal) 30, // Zone 3: lower — angled downward 60 // Zone 4: bottom edge — steeply downward ]; const angleRad = anglesDeg[hitZone] * Math.PI / 180; // Increment speed ball.speed += GameConfig.speedIncrement; // Decompose into velocity components, always going right after hitting paddle1 ball.vx = Math.cos(angleRad) * ball.speed; ball.vy = Math.sin(angleRad) * ball.speed; // Push ball out of paddle to prevent double-collision next frame ball.x = paddle.x + paddle.width + ball.radius + 1; }, ``` After implementing, also update the GameLoop.main() render block to draw the ball using `GameState.ball` (it already does this from Plan 01 — confirm it still works with the new state). Open index.html in browser — ball should immediately start moving from center. Observe: (1) ball bounces off top wall, (2) ball bounces off bottom wall, (3) when ball exits left or right edge it reappears at center with a new serve direction. Console.log not required but no errors should appear. Ball moves continuously at ~220 px/s initial speed, bounces off top and bottom walls, and serves from center on load and after going out of bounds left/right. Browser console shows zero errors. Task 2: Paddle-ball collision refinement and speed acceleration verification index.html With ball physics running, verify and refine collision behavior: **1. Test and tune zone deflection angles:** Hit the paddle at the top edge — ball should deflect upward steeply (~60 degrees from horizontal). Hit the paddle at center — ball should deflect nearly flat (~5 degrees). Hit the paddle at the bottom edge — ball should deflect downward steeply (~60 degrees). If angles feel wrong, adjust the `anglesDeg` array values within these constraints from CONTEXT.md: - Top/bottom edge: ~60 degrees - Upper/lower zone: ~30 degrees - Center: ~5 degrees (not exactly 0 — avoids infinite horizontal loop) **2. Add speed display for verification (debug only):** In GameLoop.main(), after Renderer.clear() and before drawing objects, add: ```javascript // Debug: show ball speed (remove after Phase 1 verification) Renderer.ctx.fillStyle = 'rgba(255,255,255,0.4)'; Renderer.ctx.font = '14px monospace'; Renderer.ctx.fillText('speed: ' + Math.round(GameState.ball.speed), 10, 20); ``` This lets the tester confirm speed increases by reading the on-canvas number during play. **3. Verify speed resets on serve:** After ball exits bounds, `serveBall()` sets `ball.speed = GameConfig.initialBallSpeed`. Confirm the debug display resets to 220 (or the configured initial speed) after each out-of-bounds. **4. Confirm no tunneling at high speed:** After ~10 paddle hits the ball will be moving at ~220 + (10 * 18) = 400 px/s. At 60 FPS, that's ~6.7 px/frame. Paddle height is 80px — well within safe range (max ~4800 px/s before tunneling risk at 60 FPS). Document in code comment: ```javascript // Speed is uncapped per CONTEXT.md. Tunneling risk begins when ball travels // more than paddle.height px/frame. At 60fps, that's paddle.height * 60 px/s. // With height=80: tunneling risk above ~4800 px/s. Current rallies max ~800px/s. // If Phase 5 introduces higher speeds, add substep physics here. ``` Place this comment inside `_checkPaddleCollision()`. **5. Remove debug speed display before final commit** — or keep it if the plan verifier needs to confirm speed increase. Keep it for Phase 1 verification; it costs nothing. Open index.html in browser and perform these manual checks: 1. Ball bounces continuously without stopping — CORE-01 2. Move paddle into ball path — ball deflects off paddle — CORE-02 3. Hit ball at top of paddle — steep upward angle. Hit at center — flat. Hit at bottom — steep downward — CORE-03 4. Rally 10+ times — speed display number increases each hit, resets to 220 on out-of-bounds — CORE-04 5. Press W — paddle moves up immediately. Press S — paddle moves down immediately — CORE-07 6. On HiDPI display — ball edges are crisp — VFX-05 All Phase 1 success criteria verified: - Canvas renders sharply on Retina/HiDPI (VFX-05) - Ball moves continuously and bounces off top/bottom walls (CORE-01) - Player 1 paddle deflects ball (CORE-02) - Ball angle changes per paddle hit zone (CORE-03) - Ball speed increases each paddle hit, resets on serve (CORE-04) - W/S keys move paddle responsively (CORE-07) Open index.html in browser and verify all 5 Phase 1 success criteria: 1. Canvas renders sharply on HiDPI — no blurry edges on ball or paddle 2. Ball moves continuously from first serve — bounces off top and bottom walls without slowing 3. Player 1 paddle moves up on W, down on S — immediate response, no lag 4. Hit ball at top/center/bottom of paddle — observe three distinctly different deflection angles 5. Rally 10 times — on-canvas speed display shows increasing value; on serve, resets to initial speed Also verify: window resize does not break ball trajectory or throw console errors. - Ball moves and bounces continuously (CORE-01) - Paddle deflects ball correctly (CORE-02) - Zone-based angle deflection produces 5 visually distinct trajectories (CORE-03) - Speed increments per hit, resets per serve — confirmed via on-canvas debug display (CORE-04) - All requirements CORE-01, CORE-02, CORE-03, CORE-04 met - index.html runs as a single static file with no build step After completion, create `.planning/phases/01-foundation/01-02-SUMMARY.md` documenting: - Physics implementation details (speed values, angle map, tunneling comment) - GameConfig constants and their rationale - How Phase 2 should extend Physics (add second paddle, scoring — without touching zone deflection logic) - Any tuning done to angles or speed increment during verification