Files
gijs_pong/.planning/research/ARCHITECTURE.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

791 lines
29 KiB
Markdown

# Architecture Research: HTML5 Canvas Arcade Games
**Domain:** HTML5 Canvas 2D arcade games (Pong-like)
**Researched:** 2026-03-10
**Confidence:** HIGH
## Standard Architecture
### System Overview
```
┌────────────────────────────────────────────────────────────────┐
│ Presentation Layer │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Canvas View │ │ UI Layer │ │ Audio Out │ │
│ │ (rendering) │ │ (DOM menus) │ │ (Web Audio) │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
├─────────┴─────────────────┴─────────────────┴──────────────────┤
│ Game Loop Manager │
│ ┌────────────────────────────────────────────────────┐ │
│ │ requestAnimationFrame → Update → Render → Loop │ │
│ └────────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────────┤
│ State Machine │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Menu │→ │ Game │→ │ Pause │→ │GameOver │ │
│ │ State │ │ State │ │ State │ │ State │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
├─────────────────────────────────────────────────────────────────┤
│ Game Logic Layer │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Entity Mgr │ │ Physics/ │ │ Collision │ │
│ │ (ball, │ │ Movement │ │ Detection │ │
│ │ paddles) │ │ Update │ │ │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Particle │ │ Power-up │ │
│ │ System │ │ Logic │ │
│ └──────────────┘ └──────────────┘ │
├─────────────────────────────────────────────────────────────────┤
│ Input Layer │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Keyboard │ │ Input State │ │ Command │ │
│ │ Events │ │ Manager │ │ Queue │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└────────────────────────────────────────────────────────────────┘
```
### Component Responsibilities
| Component | Responsibility | Typical Implementation |
|-----------|----------------|------------------------|
| **Game Loop Manager** | Orchestrates frame timing, calls update/render in sequence, manages requestAnimationFrame | Single `gameLoop()` function that runs at ~60fps |
| **State Machine** | Transitions between menu, playing, paused, game over states; isolates logic per scene | Object with `onEnter()`, `update()`, `render()`, `onExit()` methods per state |
| **Input Manager** | Captures keyboard events, debounces, maintains current key state | Listens to keydown/keyup, maintains a `keysPressed` object |
| **Entity Manager** | Owns and updates all game objects (paddles, ball, power-ups) | Array of entities with position, velocity, update/render methods |
| **Physics/Movement** | Updates entity positions based on velocity, applies gravity if needed | `velocity.x`, `velocity.y` updated per frame, position += velocity |
| **Collision Detection** | Detects overlaps between entities, triggers responses | AABB (Axis-Aligned Bounding Box) for rectangles; circle distance for round objects |
| **Particle System** | Manages short-lived visual effects (impacts, explosions, trails) | Maintains particle array, updates lifetime/position, removes expired particles |
| **Power-up System** | Spawns power-ups, detects collection, applies effects | Object with spawn logic, duration tracking, effect callbacks |
| **Audio Manager** | Plays sound effects and music with pooling to avoid cutoff | Array of audio elements for each sound, round-robin playback |
| **Renderer** | Draws all entities to canvas | Canvas 2D context calls in specific order (background, entities, UI) |
## Recommended Project Structure
For a vanilla JavaScript HTML5 Canvas game (no build tools), structure as a single HTML file or minimal multi-file approach:
```
index.html # Single entry point (if single-file)
├── <canvas>
└── <script src="game.js">
OR multi-file (if modular):
game/
├── index.html # Canvas element + script loading
├── game.js # Main game loop and orchestration
├── states/
│ ├── MenuState.js # Title screen, mode selection
│ ├── GameState.js # Active gameplay
│ ├── PauseState.js # Pause overlay
│ └── GameOverState.js # End screen
├── entities/
│ ├── Ball.js # Ball entity (position, velocity, radius)
│ ├── Paddle.js # Paddle entity (position, score, AI logic)
│ ├── PowerUp.js # Power-up entity
│ └── EntityManager.js # Holds and updates all entities
├── systems/
│ ├── InputManager.js # Keyboard input handling
│ ├── PhysicsSystem.js # Movement and velocity updates
│ ├── CollisionSystem.js # Collision detection and response
│ ├── ParticleSystem.js # Particle management
│ └── AudioManager.js # Sound effects and music
└── utils/
├── constants.js # Game settings (canvas size, speeds)
└── helpers.js # Utility functions
```
### Structure Rationale
- **Single file vs. modular:** For a small game like Pong, a single HTML file works well for shareability. Use modular structure if the game grows (multiple levels, complex mechanics).
- **States folder:** Each game scene (menu, gameplay, pause) is isolated, making it easy to change scene logic without affecting others.
- **Entities folder:** Separates "what things exist" from "how the game works," making entity behavior updates straightforward.
- **Systems folder:** Core game mechanics (input, physics, collision, audio) are independent modules that don't need to know about each other.
- **Utils folder:** Shared constants and helpers keep the code DRY without creating unnecessary abstractions.
## Architectural Patterns
### Pattern 1: Game Loop (requestAnimationFrame + Update/Render Separation)
**What:** The core loop calls `update()` to change game state, then `render()` to draw the result. Uses `requestAnimationFrame` to stay in sync with the browser's refresh cycle.
**When to use:** Every HTML5 Canvas game needs this. It's the heartbeat of the application.
**Trade-offs:**
- **Pro:** Prevents visual glitches (draw happens after all logic updates)
- **Pro:** Matches browser refresh rate (~60fps) automatically
- **Pro:** Better battery life on mobile than raw setInterval
- **Con:** Slightly more complex than a naive loop
- **Con:** Frame rate is tied to display refresh (usually not an issue)
**Example:**
```javascript
function gameLoop(timestamp) {
// Calculate delta time for frame-rate independent movement
const deltaTime = (timestamp - lastTimestamp) / 1000;
lastTimestamp = timestamp;
// Update all game logic
currentState.update(deltaTime);
// Clear and redraw
ctx.clearRect(0, 0, canvas.width, canvas.height);
currentState.render(ctx);
// Continue loop
requestAnimationFrame(gameLoop);
}
requestAnimationFrame(gameLoop);
```
### Pattern 2: State Machine (Menu → Playing → Paused → GameOver)
**What:** The game has distinct scenes (states). Only one is active at a time. Each state has `onEnter()`, `update()`, `render()`, and `onExit()` hooks.
**When to use:** Any game with multiple screens (menu, gameplay, pause, game over).
**Trade-offs:**
- **Pro:** Clear separation of concerns — menu logic doesn't leak into gameplay
- **Pro:** Easy to add new states without rewriting existing ones
- **Pro:** Makes pause/resume trivial (just swap states)
- **Con:** Requires discipline to avoid state-to-state coupling
- **Con:** Small games might feel over-engineered with this pattern
**Example:**
```javascript
class GameState {
onEnter() {
// Initialize game (reset ball, paddles, score)
}
update(deltaTime) {
// Update paddles, ball, check collisions, update score
inputManager.poll();
entityManager.updateAll(deltaTime);
collisionSystem.check(entityManager.entities);
}
render(ctx) {
// Draw paddles, ball, particles, score
ctx.fillStyle = '#000';
ctx.fillRect(0, 0, canvas.width, canvas.height);
entityManager.renderAll(ctx);
particleSystem.render(ctx);
}
onExit() {
// Cleanup if needed
}
}
class StateMachine {
currentState = new MenuState();
update(deltaTime) {
this.currentState.update(deltaTime);
// Check for state transitions
if (someCondition) {
this.currentState.onExit();
this.currentState = new GameState();
this.currentState.onEnter();
}
}
render(ctx) {
this.currentState.render(ctx);
}
}
```
### Pattern 3: Entity System (Ball, Paddles, Power-ups as Objects)
**What:** All dynamic game objects (ball, paddles, power-ups) inherit from a base `Entity` class or implement a common interface. Entities have position, velocity, and `update()`/`render()` methods.
**When to use:** When you have multiple types of objects that behave similarly (moveable, renderable).
**Trade-offs:**
- **Pro:** Adding new entity types (new power-up, new obstacle) is just another class
- **Pro:** All entities update/render the same way in the loop
- **Pro:** Easy to track all game objects in one collection
- **Con:** Overkill for very simple games with only 3 objects
- **Con:** Inheritance hierarchies can get messy if not carefully designed
**Example:**
```javascript
class Entity {
constructor(x, y, width, height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.velocity = { x: 0, y: 0 };
this.active = true;
}
update(deltaTime) {
this.x += this.velocity.x * deltaTime;
this.y += this.velocity.y * deltaTime;
}
render(ctx) {
// Override in subclass
}
}
class Ball extends Entity {
constructor(x, y, radius) {
super(x, y, radius * 2, radius * 2);
this.radius = radius;
this.velocity = { x: 300, y: 300 }; // pixels per second
}
render(ctx) {
ctx.fillStyle = '#fff';
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
ctx.fill();
}
}
class Paddle extends Entity {
constructor(x, y, width, height) {
super(x, y, width, height);
this.score = 0;
}
render(ctx) {
ctx.fillStyle = '#fff';
ctx.fillRect(this.x, this.y, this.width, this.height);
}
}
const entities = [];
entities.push(new Ball(canvasWidth / 2, canvasHeight / 2, 5));
entities.push(new Paddle(10, canvasHeight / 2 - 40, 10, 80));
entities.push(new Paddle(canvasWidth - 20, canvasHeight / 2 - 40, 10, 80));
// In game loop:
entities.forEach(e => e.update(deltaTime));
entities.forEach(e => e.render(ctx));
```
### Pattern 4: Collision Detection (AABB Bounding Box for Rectangles)
**What:** Check if two axis-aligned rectangles overlap using four boundary conditions. For the ball (circle), check distance between centers against sum of radii.
**When to use:** Every frame to detect ball-paddle hits, ball-boundaries, paddle-obstacles.
**Trade-offs:**
- **Pro:** Fast and simple for rectangular objects
- **Pro:** Good enough for arcade games (player won't notice slight imprecision)
- **Con:** Not pixel-perfect — ball can clip through corners
- **Con:** Doesn't handle rotating objects well
**Example:**
```javascript
// Rectangle-Rectangle collision (paddle-to-paddle boundaries)
function checkAABB(rectA, rectB) {
return (
rectA.x < rectB.x + rectB.width &&
rectA.x + rectA.width > rectB.x &&
rectA.y < rectB.y + rectB.height &&
rectA.y + rectA.height > rectB.y
);
}
// Circle-Rectangle collision (ball-to-paddle)
function checkCircleRect(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 distanceX = circle.x - closestX;
const distanceY = circle.y - closestY;
return (distanceX * distanceX + distanceY * distanceY) < (circle.radius * circle.radius);
}
// In collision system:
collisionSystem.check = function(entities) {
const ball = entities.find(e => e instanceof Ball);
const paddles = entities.filter(e => e instanceof Paddle);
paddles.forEach(paddle => {
if (checkCircleRect(ball, paddle)) {
ball.velocity.x = -ball.velocity.x; // Bounce
ball.x = ball.velocity.x > 0 ? paddle.x + paddle.width : paddle.x - ball.radius;
}
});
};
```
### Pattern 5: Particle System (Short-Lived Visual Effects)
**What:** Maintains an array of particles (small objects with position, velocity, lifetime). Each frame, particles move, fade out, and are removed when dead.
**When to use:** Impact effects, trail effects, explosions — anything visual that lasts a few frames.
**Trade-offs:**
- **Pro:** Creates visual juice and feedback
- **Pro:** Performant (particles are simple, deleted quickly)
- **Con:** Particles can accumulate if not carefully pruned
- **Con:** Requires tuning (count, lifetime, velocity) for good feel
**Example:**
```javascript
class Particle {
constructor(x, y, vx, vy, lifetime) {
this.x = x;
this.y = y;
this.vx = vx;
this.vy = vy;
this.lifetime = lifetime;
this.maxLifetime = lifetime;
}
update(deltaTime) {
this.x += this.vx * deltaTime;
this.y += this.vy * deltaTime;
this.lifetime -= deltaTime;
}
render(ctx) {
const alpha = this.lifetime / this.maxLifetime;
ctx.globalAlpha = alpha;
ctx.fillStyle = '#fff';
ctx.fillRect(this.x, this.y, 2, 2);
ctx.globalAlpha = 1;
}
isAlive() {
return this.lifetime > 0;
}
}
class ParticleSystem {
particles = [];
burst(x, y, count = 10) {
for (let i = 0; i < count; i++) {
const angle = (Math.PI * 2 * i) / count;
const vx = Math.cos(angle) * 200;
const vy = Math.sin(angle) * 200;
this.particles.push(new Particle(x, y, vx, vy, 0.5));
}
}
update(deltaTime) {
this.particles.forEach(p => p.update(deltaTime));
this.particles = this.particles.filter(p => p.isAlive());
}
render(ctx) {
this.particles.forEach(p => p.render(ctx));
}
}
// In game loop, on ball-paddle collision:
particleSystem.burst(ball.x, ball.y, 8);
```
### Pattern 6: Audio Manager with Sound Pooling
**What:** Maintain a pool of Audio objects for each sound effect. Cycle through them to avoid cutting off sounds when playing rapid-fire effects.
**When to use:** When you need to play the same sound multiple times in quick succession (paddle hits, power-up pickups).
**Trade-offs:**
- **Pro:** Sounds don't cut each other off
- **Pro:** No lag from creating new Audio objects
- **Con:** Requires pre-loading and managing multiple copies of each sound
- **Con:** Web Audio API is more complex but more powerful
**Example:**
```javascript
class AudioManager {
sounds = {};
registerSound(name, filename, poolSize = 3) {
this.sounds[name] = [];
for (let i = 0; i < poolSize; i++) {
const audio = new Audio(filename);
audio.poolName = name;
audio.ready = false;
audio.addEventListener('canplaythrough', () => {
audio.ready = true;
});
this.sounds[name].push(audio);
}
}
play(name, volume = 1.0) {
const pool = this.sounds[name];
if (!pool) {
console.warn(`Sound "${name}" not registered`);
return;
}
// Find next available sound in pool
for (let audio of pool) {
if (audio.paused) {
audio.currentTime = 0;
audio.volume = volume;
audio.play().catch(() => {
// Browser autoplay policy blocks it — ok to ignore
});
return;
}
}
}
}
// Usage:
const audioManager = new AudioManager();
audioManager.registerSound('paddle-hit', 'sounds/paddle.wav', 4);
audioManager.registerSound('score', 'sounds/score.wav', 1);
// In collision system:
audioManager.play('paddle-hit', 0.7);
```
## Data Flow
### Game Loop Flow
```
Frame Start
Input Manager (poll keyboard)
Current State.update(deltaTime)
├→ Entity Manager.update() [move ball, paddles]
├→ Collision System.check() [detect hits, trigger events]
├→ Particle System.update() [age and remove particles]
└→ Audio Manager [enqueue sounds from events]
Canvas.clearRect() [blank the canvas]
Current State.render(ctx)
├→ Background [draw arena]
├→ Entity Manager.render() [draw paddles, ball]
├→ Particle System.render() [draw particle effects]
└→ UI Layer [draw score, instructions]
Request next frame
```
### State Transition Flow
```
Current State (e.g., MenuState)
Check for exit conditions
Call currentState.onExit()
Create new state (e.g., GameState)
Call newState.onEnter()
Update state reference
Next frame uses new state
```
### Collision Response Flow
```
Collision Detected (e.g., Ball ↔ Paddle)
Update velocity [bounce ball]
Trigger event [emit "paddle-hit"]
Particle System.burst() [visual feedback]
Audio Manager.play() [sound feedback]
Power-up System [check if power-up triggered]
```
## Build Order (Recommended Implementation Sequence)
The following order minimizes integration complexity and ensures each phase is testable:
### Phase 1: Game Loop + Single State
1. Create canvas, get context
2. Implement `requestAnimationFrame` loop
3. Implement first state (GameState with basic ball + one paddle)
4. Test: Ball moves, paddle can be manually updated
**Deliverable:** Ball bounces off canvas edges, stationary paddle on screen
### Phase 2: Input + Movement
1. Implement InputManager (keyboard event listeners)
2. Wire input to paddle 1 movement
3. Test: Paddle responds to keys
**Deliverable:** Player can move left paddle up/down
### Phase 3: Collision + Physics
1. Implement Collision System
2. Ball bounces off paddles and walls
3. Ball-paddle collision response (velocity reversal)
**Deliverable:** Ball bounces realistically off paddles and boundaries
### Phase 4: Second Paddle + Score
1. Add paddle 2 (AI or second player)
2. Implement score tracking (ball off-screen = point)
3. Reset ball position on score
**Deliverable:** Game tracks score, one paddle controlled, one paddle AI or manual
### Phase 5: State Machine + Menu
1. Implement StateMachine and multiple states (Menu, Game, GameOver)
2. Add MenuState (start game, select mode)
3. Add GameOverState (show winner, restart)
4. Wire transitions
**Deliverable:** Full menu flow — start, play, game over, restart
### Phase 6: Audio
1. Implement AudioManager with sound pooling
2. Add sound effects for paddle hits, scoring, menu clicks
3. Add background music to GameState
**Deliverable:** Game has audio feedback
### Phase 7: Particle System + Polish
1. Implement ParticleSystem
2. Emit particles on collisions, scoring
3. Glow effects, trails (via particles)
**Deliverable:** Visually polished — particles on impact, trails on ball
### Phase 8: Power-ups
1. Implement PowerUpSystem
2. Power-up types: speed boost, paddle enlargement, ball split
3. Spawn logic, collision detection, effect application
**Deliverable:** Power-ups spawn randomly, apply effects
### Phase 9: Multiple Arenas
1. Add arena config (layout, obstacles, hazards)
2. Arena selection in menu
3. Level progression on wins
**Deliverable:** Multiple distinct arena layouts playable
### Phase 10: Polish + Refinement
1. Tune difficulty curves (AI, ball speeds)
2. Screen shake on impacts
3. Pause state and pause key
4. Settings menu (volume, difficulty)
**Deliverable:** Game feels complete and juicy
## Scaling Considerations
For an HTML5 Canvas Pong game, "scaling" is less about handling millions of users and more about complexity growth.
| Scale | Architecture Adjustments |
|-------|--------------------------|
| **Current (2-4 entities)** | Single state, simple collision checks, particle count < 100. No optimization needed. |
| **Medium (10+ entities, complex arenas)** | Spatial partitioning for collision detection (divide canvas into grid cells, only check nearby entities). Consider renderer caching (re-use canvas for static backgrounds). |
| **Large (many power-ups, obstacles, hazards)** | Separate rendering layer for UI (use DOM instead of canvas for menus/HUD). Consider offscreen canvas rendering for frequently-drawn elements. Profile particle system (may need pooling limits). |
**In practice:** For a Pong game, the "medium" complexity level is the realistic ceiling. Even with 50 particles and dozens of power-ups, a single-threaded JS game will run fine at 60fps.
## Anti-Patterns
### Anti-Pattern 1: All Game Logic in the Render Function
**What people do:**
```javascript
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Move entities
ball.x += ball.vx;
ball.y += ball.vy;
// Check collisions
if (ball.x < 0) ball.vx = -ball.vx;
// Update score
if (ball.x < 0) score++;
// Draw
ctx.fillRect(ball.x, ball.y, 10, 10);
}
```
**Why it's wrong:**
- Mixing logic and rendering makes the frame rate dependent on rendering performance
- Hard to test (can't verify logic without drawing)
- Debugging is confusing (is the bug in movement or rendering?)
- Impossible to pause (pause the render, logic stops too)
**Do this instead:** Separate `update()` and `render()` phases.
```javascript
function gameLoop() {
update(deltaTime); // All logic changes state
render(); // Render displays state
requestAnimationFrame(gameLoop);
}
```
### Anti-Pattern 2: No Input Debouncing / Polling Every Event
**What people do:**
```javascript
document.addEventListener('keydown', (e) => {
if (e.key === 'w') paddle.y -= 10; // Move immediately on keydown
if (e.key === 's') paddle.y += 10;
});
```
**Why it's wrong:**
- Movement tied to event firing (non-deterministic)
- Key repeat lag can cause jerky movement
- Can't handle two keys pressed simultaneously
- Frame rate dependency (holding key moves differently on 60fps vs 120fps)
**Do this instead:** Track key state in `update()`.
```javascript
const keysPressed = {};
document.addEventListener('keydown', (e) => {
keysPressed[e.key] = true;
});
document.addEventListener('keyup', (e) => {
keysPressed[e.key] = false;
});
function update(deltaTime) {
if (keysPressed['w']) paddle.y -= paddle.speed * deltaTime;
if (keysPressed['s']) paddle.y += paddle.speed * deltaTime;
}
```
### Anti-Pattern 3: Collision Response Couples Ball and Paddle
**What people do:**
```javascript
class Ball {
checkCollision(paddle) {
if (this.overlaps(paddle)) {
paddle.score++; // Ball knows about paddle state
this.velocity.x = -this.velocity.x;
this.y = this.y < paddle.y ? paddle.y - this.radius : paddle.y + paddle.height;
}
}
}
```
**Why it's wrong:**
- Ball is tightly coupled to Paddle (hard to add obstacles, other bounceable objects)
- Score logic is buried in physics code
- Hard to test ball movement independently
**Do this instead:** Separate collision detection from response.
```javascript
function handleCollision(ball, paddle) {
ball.velocity.x = -ball.velocity.x;
ball.x = ball.velocity.x > 0 ? paddle.x + paddle.width : paddle.x - ball.radius;
// Event-driven responses
emitEvent('collision', { object: 'ball-paddle', paddle });
}
// Listeners respond to event:
on('collision', (event) => {
if (event.object === 'ball-paddle') {
audioManager.play('paddle-hit');
particleSystem.burst(ball.x, ball.y);
}
});
```
### Anti-Pattern 4: Drawing Every Frame Without Clearing
**What people do:**
```javascript
function draw() {
// ctx.clearRect(...) is commented out to create "trails"
ctx.fillStyle = '#fff';
ctx.fillRect(ball.x, ball.y, 10, 10);
}
```
**Why it's wrong:**
- Performance degrades over time (entire canvas accumulates garbage)
- Can't pause or reset without flickering
- Artifact buildup makes the scene look muddy
**Do this instead:** Clear intentionally, use particles for trails.
```javascript
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw arena
ctx.fillStyle = '#222';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Draw entities
entities.forEach(e => e.render(ctx));
// Particles create visual trails naturally
particleSystem.render(ctx);
}
```
## Integration Points
### Internal Boundaries
| Boundary | Communication | Notes |
|----------|---------------|-------|
| **Collision System ↔ Event System** | Events (e.g., "ball-paddle-hit") | Decouples physics from reactions (audio, particles, score) |
| **State ↔ State Machine** | State transitions, hooks | Each state is independent; only state machine knows how to switch |
| **Input Manager ↔ Current State** | Polled each frame (keysPressed object) | State reads keys; input manager doesn't know about game logic |
| **Entity Manager ↔ Collision System** | Entity list passed to collision check | Collision system reads entity data; doesn't modify entities directly |
| **Audio Manager ↔ Collision/Scoring** | Direct play() calls from event handlers | Audio is triggered by events, not tightly coupled |
| **Particle System ↔ Collision Events** | Direct burst() calls from event handlers | Particles are visual feedback, triggered by events |
### External Services (if any)
| Service | Integration Pattern | Notes |
|---------|---------------------|-------|
| **Sound Files** | Loaded via `new Audio(filename)` at init | Pre-load and cache; handle browser autoplay policy |
| **Image Assets** | `new Image()` for sprites if used | Load before game state starts |
## Sources
- [The Complete Guide to Building HTML5 Games with Canvas and SVG - SitePoint](https://www.sitepoint.com/the-complete-guide-to-building-html5-games-with-canvas-and-svg/)
- [Best JavaScript and HTML5 game engines (updated for 2025) - LogRocket Blog](https://blog.logrocket.com/best-javascript-html5-game-engines-2025/)
- [Game Loop in HTML5 Canvas - Bryan Lew](https://blewjy.github.io/gamedev/html5/canvas/gameloop/javascript/2020/02/21/game-dev-html5-canvas-game-loop.html)
- [Creating a State Stack Engine for your game with JavaScript - idiallo.com](https://idiallo.com/blog/javascript-game-state-stack-engine)
- [Game State Management Patterns - Jake Gordon](https://jakesgordon.com/writing/javascript-game-foundations-state-management/)
- [Create a Proper Game Loop - Spicy Yoghurt](https://spicyyoghurt.com/tutorials/html5-javascript-game-development/create-a-proper-game-loop-with-requestanimationframe)
- [Collision Detection - MDN](https://developer.mozilla.org/en-US/docs/Games/Tutorials/2D_Breakout_game_pure_JavaScript/Collision_detection)
- [Collision Detection and Physics - Spicy Yoghurt](https://spicyyoghurt.com/tutorials/html5-javascript-game-development/collision-detection-physics)
- [GitHub: html5-pong Vanilla JavaScript Implementation](https://github.com/SMenigat/html5-pong)
- [Dev.to: Create Ping Pong Game Using JavaScript](https://dev.to/cwrcode/create-ping-pong-game-using-javascript-source-code-2d43)
---
*Architecture research for: HTML5 Canvas arcade games (Pong-like)*
*Researched: 2026-03-10*