Files
gijs_pong/.planning/phases/01-foundation/01-02-PLAN.md
2026-03-10 14:42:41 +01:00

13 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
phase plan type wave depends_on files_modified autonomous requirements must_haves
01-foundation 02 execute 2
01-01
index.html
true
CORE-01
CORE-02
CORE-03
CORE-04
truths artifacts key_links
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
path provides contains
index.html Complete Physics.update() with ball movement, wall bounce, paddle collision, zone deflection, speed increment ball.vx *= -1, zone, angles, ball.speed
path provides contains
index.html Ball serve function that initializes ball velocity serveBall
path provides contains
index.html Speed increment constant tunable via GameConfig GameConfig
from to via pattern
Physics.update() ball position ball.x += ball.vx * deltaTime; ball.y += ball.vy * deltaTime ball.x.*deltaTime
from to via pattern
wall bounce logic ball.vy Math.abs(ball.vy) inversion on top/bottom boundary crossing Math.abs.*vy
from to via pattern
zone deflection ball.vx / ball.vy relativeHitPos -> hitZone -> angle -> cos/sin decomposition relativeHitPos|hitZone|getPaddleAngle
from to via pattern
speed increment ball.speed ball.speed += GameConfig.speedIncrement on each paddle hit 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.

<execution_context> @/home/dabit/.claude/get-shit-done/workflows/execute-plan.md @/home/dabit/.claude/get-shit-done/templates/summary.md </execution_context>

@.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):

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):

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:

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):

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:

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:

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:

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:

_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:

// 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:

// 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.

<success_criteria>

  • 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 </success_criteria>
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