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>
29 KiB
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:
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:
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:
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:
// 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:
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:
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
- Create canvas, get context
- Implement
requestAnimationFrameloop - Implement first state (GameState with basic ball + one paddle)
- Test: Ball moves, paddle can be manually updated
Deliverable: Ball bounces off canvas edges, stationary paddle on screen
Phase 2: Input + Movement
- Implement InputManager (keyboard event listeners)
- Wire input to paddle 1 movement
- Test: Paddle responds to keys
Deliverable: Player can move left paddle up/down
Phase 3: Collision + Physics
- Implement Collision System
- Ball bounces off paddles and walls
- Ball-paddle collision response (velocity reversal)
Deliverable: Ball bounces realistically off paddles and boundaries
Phase 4: Second Paddle + Score
- Add paddle 2 (AI or second player)
- Implement score tracking (ball off-screen = point)
- Reset ball position on score
Deliverable: Game tracks score, one paddle controlled, one paddle AI or manual
Phase 5: State Machine + Menu
- Implement StateMachine and multiple states (Menu, Game, GameOver)
- Add MenuState (start game, select mode)
- Add GameOverState (show winner, restart)
- Wire transitions
Deliverable: Full menu flow — start, play, game over, restart
Phase 6: Audio
- Implement AudioManager with sound pooling
- Add sound effects for paddle hits, scoring, menu clicks
- Add background music to GameState
Deliverable: Game has audio feedback
Phase 7: Particle System + Polish
- Implement ParticleSystem
- Emit particles on collisions, scoring
- Glow effects, trails (via particles)
Deliverable: Visually polished — particles on impact, trails on ball
Phase 8: Power-ups
- Implement PowerUpSystem
- Power-up types: speed boost, paddle enlargement, ball split
- Spawn logic, collision detection, effect application
Deliverable: Power-ups spawn randomly, apply effects
Phase 9: Multiple Arenas
- Add arena config (layout, obstacles, hazards)
- Arena selection in menu
- Level progression on wins
Deliverable: Multiple distinct arena layouts playable
Phase 10: Polish + Refinement
- Tune difficulty curves (AI, ball speeds)
- Screen shake on impacts
- Pause state and pause key
- 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:
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.
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:
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().
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:
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.
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:
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.
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
- Best JavaScript and HTML5 game engines (updated for 2025) - LogRocket Blog
- Game Loop in HTML5 Canvas - Bryan Lew
- Creating a State Stack Engine for your game with JavaScript - idiallo.com
- Game State Management Patterns - Jake Gordon
- Create a Proper Game Loop - Spicy Yoghurt
- Collision Detection - MDN
- Collision Detection and Physics - Spicy Yoghurt
- GitHub: html5-pong Vanilla JavaScript Implementation
- Dev.to: Create Ping Pong Game Using JavaScript
Architecture research for: HTML5 Canvas arcade games (Pong-like) Researched: 2026-03-10