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 |
|
|
true |
|
|
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.mdGameState (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
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.
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:
- Ball bounces continuously without stopping — CORE-01
- Move paddle into ball path — ball deflects off paddle — CORE-02
- Hit ball at top of paddle — steep upward angle. Hit at center — flat. Hit at bottom — steep downward — CORE-03
- Rally 10+ times — speed display number increases each hit, resets to 220 on out-of-bounds — CORE-04
- Press W — paddle moves up immediately. Press S — paddle moves down immediately — CORE-07
- 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)
- Canvas renders sharply on HiDPI — no blurry edges on ball or paddle
- Ball moves continuously from first serve — bounces off top and bottom walls without slowing
- Player 1 paddle moves up on W, down on S — immediate response, no lag
- Hit ball at top/center/bottom of paddle — observe three distinctly different deflection angles
- 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>