docs(01-foundation): create phase plan
This commit is contained in:
329
.planning/phases/01-foundation/01-02-PLAN.md
Normal file
329
.planning/phases/01-foundation/01-02-PLAN.md
Normal file
@@ -0,0 +1,329 @@
|
||||
---
|
||||
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"
|
||||
---
|
||||
|
||||
<objective>
|
||||
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.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@/home/dabit/.claude/get-shit-done/workflows/execute-plan.md
|
||||
@/home/dabit/.claude/get-shit-done/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<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
|
||||
</context>
|
||||
|
||||
<interfaces>
|
||||
<!-- Key contracts from Plan 01 that this plan extends -->
|
||||
|
||||
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
|
||||
```
|
||||
</interfaces>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Ball physics — movement, wall bounce, serve, and GameConfig</name>
|
||||
<files>index.html</files>
|
||||
<action>
|
||||
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).
|
||||
</action>
|
||||
<verify>
|
||||
<automated>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.</automated>
|
||||
</verify>
|
||||
<done>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.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Paddle-ball collision refinement and speed acceleration verification</name>
|
||||
<files>index.html</files>
|
||||
<action>
|
||||
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.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>
|
||||
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
|
||||
</automated>
|
||||
</verify>
|
||||
<done>
|
||||
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)
|
||||
</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
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.
|
||||
</verification>
|
||||
|
||||
<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>
|
||||
|
||||
<output>
|
||||
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
|
||||
</output>
|
||||
Reference in New Issue
Block a user