Files
gijs_pong/.planning/research/STACK.md
Dabit 28f86d781c docs: complete project research synthesis
Synthesized 4 parallel research efforts into comprehensive SUMMARY.md:
- STACK.md: Vanilla Canvas 2D, fixed timestep, Web Audio, no dependencies
- FEATURES.md: MVP features + v1.x enhancements + defer list
- ARCHITECTURE.md: Game loop + state machine + entity patterns, 10-phase build order
- PITFALLS.md: 8 critical pitfalls with prevention strategies

Key recommendations:
- Use fixed timestep accumulator (60 Hz physics, variable rendering)
- Implement 10 phases from game loop foundation to cross-browser testing
- Address critical pitfalls early (tunneling, timing, DPI, autoplay, AI, power-ups, lag, memory)
- MVP ships with core Pong + AI + menus + basic polish
- v1.x adds particles, trails, power-ups, arenas

All research backed by official sources (MDN, web.dev, Chrome docs) and established patterns.
Confidence: HIGH. Ready for requirements definition.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 14:18:11 +01:00

17 KiB

Stack Research: HTML5 Canvas Arcade Game (Super Pong Next Gen)

Domain: Vanilla HTML5 Canvas arcade game Researched: 2026-03-10 Confidence: HIGH

Core Technologies

Technology Version Purpose Why Recommended
HTML5 Canvas 2D Context Built-in Game rendering and graphics Native to all modern browsers, perfect for 2D arcade games. Canvas 2D is the standard choice for sprite/particle-based games like Pong with excellent CPU performance and full control over pixel rendering. WebGL adds complexity (requires GLSL shaders and 3D math) without benefit for 2D arcade games.
Vanilla JavaScript (ES6+) Latest Game logic, input handling, game loop No framework overhead. Direct Canvas API control. ES6+ provides async/await, arrow functions, and classes for clean game code. This is the standard for shipping static HTML5 games.
requestAnimationFrame (RAF) API Built-in Main game loop timing Standard for browser games since 2011. RAF automatically syncs with monitor refresh rate (60 Hz or 120 Hz depending on display), pauses in background tabs (saves battery), and eliminates timing issues with setTimeout/setInterval. All modern browsers support it natively.
Fixed Timestep Accumulator Pattern Custom implementation Consistent game physics and gameplay Decouples game simulation (fixed 60 Hz physics) from rendering (variable frame rate). Ensures Pong ball physics, paddle collision, and scoring behave identically across devices with different monitor refresh rates. Prevents "spiral of death" on low-end hardware. Accumulator pattern is the gold standard since Gaffer on Games established it in 2006.
Web Audio API Built-in Sound effects and music Native support across all modern browsers. Handles unlimited simultaneous sounds (no 32-sound limit like old tags). Precise timing control needed for paddle hits, scoring, and power-up sounds. Avoids HTML tag bloat and pre-loading issues.

Supporting Libraries

Library Version Purpose When to Use
GSAP (TweenLite) v3 Easing, tweening, animations Optional for smooth UI animations (menu transitions, score popups, power-up indicators). Only needed if you want sophisticated easing curves beyond linear interpolation. ~20 KB minified. Can be omitted if easing is written manually.
None (write particle effects from scratch) Particle effects (screen shake, paddle hits, power-up bursts) Vanilla JavaScript particle system is only ~200 lines of code. Creates a simple emitter system with velocity, gravity, alpha fade. Gives full control over visual style and performance. Pre-built particle libraries add 10-50 KB for minimal benefit in a 2D arcade game.
None (write collision detection from scratch) Collision detection (paddle-ball, ball-wall) For Pong, only AABB (Axis-Aligned Bounding Box) and circle-rectangle collision needed. Simple math functions (~50 lines total). Physics libraries like Cannon.js are overkill and add 100+ KB.

Development Tools

Tool Purpose Notes
Live Server / Python -m http.server Local development No build step required. Run python3 -m http.server or use VS Code Live Server extension. Canvas and Web Audio require HTTP (not file://) due to CORS and security.
Browser DevTools (Chrome/Firefox) Debugging and profiling Built-in. Use Performance tab to measure frame rate (aim for 60 fps consistent). Use Network tab to verify static file loads instantly. Use Console for logging game state.
No build tools (webpack, vite, esbuild) Single static file delivery Avoid build complexity. Ship as index.html with inline <script> or separate .js file. This is the greenfield constraint and strength of this approach.

Architecture Patterns

// Fixed timestep with variable rendering
let lastTime = 0;
let accumulator = 0;
const FIXED_TIMESTEP = 1 / 60; // 60 Hz physics

function gameLoop(currentTime) {
  const deltaTime = (currentTime - lastTime) / 1000; // Convert to seconds
  lastTime = currentTime;

  // Cap deltaTime to prevent spiral of death (max 3 frames worth)
  const clampedDelta = Math.min(deltaTime, 3 * FIXED_TIMESTEP);

  accumulator += clampedDelta;

  // Fixed timestep updates
  while (accumulator >= FIXED_TIMESTEP) {
    update(FIXED_TIMESTEP); // Physics, collisions, AI
    accumulator -= FIXED_TIMESTEP;
  }

  // Variable rate rendering
  render(); // Draw to canvas

  requestAnimationFrame(gameLoop);
}

requestAnimationFrame(gameLoop);

Why this pattern:

  • Physics runs at fixed 60 Hz → identical gameplay on 60 Hz and 144 Hz monitors
  • Rendering runs as fast as hardware allows → smooth visuals
  • Accumulator prevents frame skipping and ensures no physics updates are missed
  • Capped deltaTime prevents "spiral of death" when a frame takes too long

Particle System Pattern

class Particle {
  constructor(x, y, vx, vy) {
    this.x = x; this.y = y;
    this.vx = vx; this.vy = vy;
    this.life = 1.0; // Alpha
    this.decay = 0.02; // Life per frame
  }

  update(dt) {
    this.x += this.vx * dt;
    this.y += this.vy * dt;
    this.life -= this.decay * dt;
  }

  draw(ctx) {
    ctx.globalAlpha = this.life;
    ctx.fillStyle = 'rgba(255, 200, 0, 1)';
    ctx.fillRect(this.x - 2, this.y - 2, 4, 4);
    ctx.globalAlpha = 1.0;
  }
}

class ParticleEmitter {
  constructor() {
    this.particles = [];
  }

  emit(x, y, count = 10) {
    for (let i = 0; i < count; i++) {
      const angle = (Math.PI * 2 * i) / count;
      const speed = 100 + Math.random() * 50;
      this.particles.push(
        new Particle(x, y, Math.cos(angle) * speed, Math.sin(angle) * speed)
      );
    }
  }

  update(dt) {
    this.particles = this.particles.filter(p => {
      p.update(dt);
      return p.life > 0;
    });
  }

  draw(ctx) {
    this.particles.forEach(p => p.draw(ctx));
  }
}

Why custom: No external library needed, ~100 lines total, full control over visuals and performance, can emit thousands of particles without hiccup.

Collision Detection (AABB for Paddles/Ball)

// Axis-Aligned Bounding Box collision
function checkAABBCollision(rect1, rect2) {
  return rect1.x < rect2.x + rect2.width &&
         rect1.x + rect1.width > rect2.x &&
         rect1.y < rect2.y + rect2.height &&
         rect1.y + rect1.height > rect2.y;
}

// Circle-Rectangle collision (ball hitting paddle edge)
function checkCircleRectCollision(circle, rect) {
  const closestX = Math.max(rect.x, Math.min(circle.x, rect.x + rect.width));
  const closestY = Math.max(rect.y, Math.min(circle.y, rect.y + rect.height));

  const distX = circle.x - closestX;
  const distY = circle.y - closestY;

  return (distX * distX + distY * distY) < (circle.radius * circle.radius);
}

Why custom: These are 10-15 lines of simple math. Physics libraries like Cannon.js add 100+ KB for zero benefit in Pong.

Alternatives Considered

Recommended Alternative When to Use Alternative
Vanilla JS Three.js or Babylon.js (WebGL) Only if game needs 3D rotation, lighting, or 10,000+ on-screen objects. Arcade Pong is 2D, simple geometry → Canvas 2D is faster and easier.
Canvas 2D WebGL 2.0 Only if game needs advanced shaders, real-time shadow casting, or HDR rendering. For "juicy" 2D arcade effects (particles, glow, screen shake) Canvas 2D is sufficient.
Fixed timestep accumulator Variable timestep (frame-by-frame) Only for turn-based games where gameplay doesn't depend on consistent physics timing. Pong requires consistent ball physics across all refresh rates → fixed timestep is mandatory.
Web Audio API HTML tag Only for narration/long-form audio where streaming is needed. Web Audio is superior for game SFX: no pre-loading, unlimited simultaneous sounds, precise timing, no browser quirks.
Custom particle system Pixi.js (2D rendering library) Only if game needs 100,000+ animated sprites with sophisticated batching. Pong has <50 particles at once → custom system is overkill. Pixi adds 150+ KB.
GSAP (optional) Manual easing functions Only if UI animations are sophisticated. For power-up indicators or simple score popups, hand-written easing (ease-in, ease-out) is 5 lines of code.

What NOT to Use

Avoid Why Use Instead
Phaser, Babylon.js, Cocos2d, Unity WebGL Overkill for 2D arcade game. Adds 500 KB-2 MB bundle size, build complexity, learning curve, and abstraction layers that make low-level control harder. Vanilla Canvas 2D + requestAnimationFrame. This is greenfield—ship single HTML file.
Webpack, Vite, esbuild, rollup Build tool complexity defeats purpose of "single static file." Game runs fine without transpilation (ES6+ is supported in all modern browsers since 2015). Ship as plain HTML + vanilla JS. Keep file structure flat: index.html, styles.css (optional), game.js.
Three.js / WebGL Adds 3D complexity (GLSL shaders, matrix math, GPU programming) for no visual benefit. Pong is 2D rectangles + particles. Canvas 2D renders these faster on CPU. Stay with Canvas 2D. If you ever need 3D, migrate then (unlikely).
Cannon.js, Rapier (Physics engines) Pong physics is trivial: ball bounces off walls and paddles with simple reflection math. Physics engine overhead (100+ KB, collision manifolds, impulse resolution) is wasted. Implement ball reflection logic in 20 lines: ballVx *= -1 for wall hits, angle-based bounce for paddle hits.
npm dependencies for utilities Every npm package adds build friction and bundle size. Helper functions (color conversion, easing, vector math) are simpler to write inline than to manage as dependencies. Write custom utilities inline. A 2D vector helper is 30 lines. An easing function is 5 lines.
Persistent leaderboards / Backend Out of scope (no server in requirements). Adds deployment complexity and API security concerns. Store scores in localStorage for local play session only. No remote persistence needed for v1.

Stack Patterns by Variant

If you want polished particle effects and screen shake:

  • Use Web Audio API for impact sounds timed with physics (paddle hit → sfx + particle burst)
  • Implement custom ParticleEmitter class (~100 lines)
  • Add screen shake via simple canvas translate: ctx.translate(Math.random() - 0.5, Math.random() - 0.5)
  • This is the path for "visually juicy" requirements in PROJECT.md

If you want to minimize code and polish:

  • Strip to: Canvas 2D, RAF, Web Audio API (built-in), no particles, basic shapes only
  • Trade visual juice for ~500 lines of code
  • Not recommended given PROJECT.md emphasis on "makes someone say whoa"

If you want to add difficulty scaling (AI opponent):

  • Implement AI as simple state machine: track ball, move paddle toward it, add reaction delay
  • Use fixed timestep to ensure AI behaves consistently across devices
  • AI decision logic lives in update loop, no separate AI library needed

Version Compatibility

Package Version Notes
Canvas 2D API All modern browsers (ES5+) No version to pin. Available everywhere.
requestAnimationFrame All modern browsers (ES5+) No version. Standard since 2011. IE 10+ supported.
Web Audio API All modern browsers (2012+) No version. Safari, Chrome, Firefox, Edge all current versions support it fully.
GSAP TweenLite (if used) v3+ v3 is lightweight (~40 KB minified). Can be loaded from CDN or skipped entirely. No peer dependencies.
JavaScript language ES6+ Modern syntax. All deployment targets support ES6+ (Chrome 51+, Firefox 54+, Safari 10+, Edge 15+). No transpilation needed.

Installation (No Build Required)

<!-- Minimal setup: index.html -->
<!DOCTYPE html>
<html>
<head>
  <title>Super Pong Next Gen</title>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <style>
    body { margin: 0; overflow: hidden; background: #111; }
    canvas { display: block; }
  </style>
</head>
<body>
  <canvas id="gameCanvas" width="800" height="600"></canvas>

  <!-- Optional: GSAP TweenLite for UI animations -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>

  <!-- Game code -->
  <script src="game.js"></script>
</body>
</html>

To run locally (no build step):

# Option 1: Python (built-in)
python3 -m http.server 8000

# Option 2: Node.js (if installed)
npx http-server

# Option 3: VS Code Live Server extension (GUI click)

Then open http://localhost:8000 in browser.

No npm, no build tools, no transpilation needed. The constraint is a feature—maximizes portability and minimizes deployment surface area.

Critical Decisions

Canvas 2D, Not WebGL

Decision: Use Canvas 2D API exclusively.

Reasoning:

  • Pong is pure 2D (rectangles, circles, particles)
  • Canvas 2D has zero startup cost, WebGL requires GLSL shader compilation
  • Canvas 2D draws millions of pixels per frame efficiently on CPU
  • WebGL overkill: no 3D transforms, no complex lighting, no real-time shadow maps
  • Canvas 2D is familiar to JavaScript devs (no GPU programming knowledge needed)
  • Performance: Canvas 2D renders 2D arcade games faster than WebGL on mid-range hardware

Outcome: Single canvas element, 2D context. All graphics drawn with fillRect, fillCircle, drawImage (if sprites).

Fixed Timestep Over Variable Timestep

Decision: Implement fixed 60 Hz physics timestep with variable rendering refresh rate.

Reasoning:

  • Ensures ball physics are identical on 60 Hz monitors and 144 Hz monitors
  • Prevents "bounce randomness" that breaks game feel
  • Accumulator pattern handles frame skipping without losing physics updates
  • Is the standard for physics-based games since 2006 (Gaffer on Games)
  • Prevents "spiral of death" on low-end hardware (capped deltaTime)

Outcome: Game loop uses accumulator. Physics updates in fixed increments, rendering as fast as hardware allows.

Web Audio API, Not HTML

Decision: Use Web Audio API for all sound.

Reasoning:

  • No pre-loading required (unlike tags which need preload attributes and suffer quirks)
  • Unlimited simultaneous sounds (no 32-sound limit)
  • Precise timing control needed for game feel (paddle hit → instant sfx feedback)
  • Single API handles both sound effects and dynamic music
  • Works offline and in Service Workers

Outcome: Create AudioContext, use Web Audio nodes for synthesis or sample playback.

No External Game Engine or Physics Library

Decision: Custom implementation of game loop, collision detection, and particle effects.

Reasoning:

  • Phaser/Babylon.js add 500 KB-2 MB for features Pong doesn't use
  • Collision detection is trivial (AABB + circle-rect = 20 lines)
  • Particle system is 100 lines of simple code
  • Keeping code hand-written gives full control, understanding, and minimum bundle
  • Greenfield constraint: single static file = no build toolchain

Outcome: ~1000-1500 lines of game code total. No dependencies. Portable everywhere.

Deployment Constraints

  • Single file delivery: HTML file can inline CSS and JavaScript or load them as separate files
  • No server required: Can be hosted on GitHub Pages, Netlify, or any static host
  • No build step: Can edit game.js, reload browser, see changes instantly
  • Browser compatibility: Target modern browsers (Chrome 90+, Firefox 88+, Safari 14+, Edge 90+)

Sources


Stack research for: Super Pong Next Gen (HTML5 Canvas arcade game) Researched: 2026-03-10 Confidence: HIGH