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>
32 KiB
Domain Pitfalls: HTML5 Canvas Arcade Games
Domain: HTML5 Canvas 2D arcade games (Pong-like) Researched: 2026-03-10 Confidence: HIGH (verified with official docs, multiple sources, and established patterns)
Critical Pitfalls
Pitfall 1: Tunneling — Ball Passing Through Paddles at High Speed
What goes wrong: The ball moves so fast between frames that it jumps from one side of a paddle to the other without ever registering a collision. Players see the ball suddenly teleport through their paddle, breaking game feel and causing frustration. This is especially common when ball velocity increases via power-ups or as AI difficulty scales.
Why it happens: Discrete collision detection only checks positions at the start and end of each frame. If the ball travels 50+ pixels per frame and the paddle is only 20 pixels wide, the ball can be "before paddle" at frame N-1 and "after paddle" at frame N, with no intersection detected in between.
How to avoid:
- Continuous Collision Detection (CCD): Sweep a collision shape along the ball's motion path and detect if it intersects the paddle at any point during travel, not just endpoints.
- Smaller time steps: Subdivide each frame's physics into multiple micro-steps (e.g., 0.016 seconds into 4 × 0.004 second steps). Ball travels less distance per step, reducing tunnel size.
- Swept collision volumes: Create a rectangle representing all space the ball occupied during the frame and test against paddle AABB (axis-aligned bounding box).
- Ray casting: Cast a ray from the ball's previous position to current position and check intersection with paddle geometry.
- Clamp ball velocity: Set a maximum ball speed that you know will be reliably detected with your paddle width and frame time.
Warning signs:
- Ball occasionally disappears through paddles during fast rallies
- High-difficulty AI generates faster ball speeds, increasing "cheap" losses
- Power-up that increases ball speed makes paddling feel unreliable
- Player reports: "I moved the paddle right into it and it went through"
Phase to address: Phase 2 (Core Physics) — Implement collision detection architecture from day one. Tunneling is a show-stopper that's expensive to retrofit. Must be proven working before power-ups or AI velocity scaling is introduced.
Pitfall 2: Game Loop Timing — Frame Rate Dependency and Spiral of Death
What goes wrong: If you multiply physics by frame time (delta time) but don't cap it, one slow frame causes physics to jump a large distance. That large jump takes longer to simulate, making the next frame slower, requiring even larger physics jumps. The game enters a "spiral of death" — each frame gets slower, simulations get larger, until the browser freezes.
Alternatively, if you ignore delta time and tie movement directly to frame count, the game plays at different speeds on 60fps monitors vs. 120fps monitors, or on slow vs. fast machines. Players on high-refresh displays move twice as fast, breaking competitive play and AI balance.
Why it happens: Developers either:
- Use variable delta time without capping it (causes spiral of death)
- Ignore delta time entirely (assumes 60fps everywhere, unrealistic)
- Use setInterval instead of requestAnimationFrame (poor browser synchronization, timing jitter)
How to avoid:
- Use fixed delta time game loop: Accumulate elapsed time and step physics in fixed increments (e.g., always step by 0.016s = 1/60 frame). Decouple physics from frame rate.
- Cap delta time: If a frame takes unexpectedly long, clamp delta time to a maximum (e.g., 0.032s max) to prevent spiral of death. Better to "skip" physics than freeze.
- Use requestAnimationFrame: Schedule updates with
window.requestAnimationFrame(), notsetInterval(). RAF syncs with browser refresh, reducing jitter and input lag. - Multiply velocities by delta time:
position += velocity * deltaTime— ensures same distance traveled regardless of frame rate. - Test on variable frame rates: Force slow frames and verify the game doesn't enter spiral of death. Test on 60hz, 120hz, and intentionally laggy machines.
Warning signs:
- Game runs smoothly at 60fps but fast/jerky at 120fps+
- One stutter causes cascading slowdowns until browser becomes unresponsive
- AI or ball speed is inconsistent between test runs
- Player loses while ball moves visibly faster than usual
- Performance profiler shows frame time growing each frame (spiral signature)
Phase to address: Phase 1 (Game Loop) — This is the foundation. Every other system depends on correct timing. Implement and test fixed-step physics before any game logic.
Pitfall 3: Canvas DPI Scaling — Blurry Graphics on Retina Displays
What goes wrong: Your game looks sharp on a standard 96 DPI display but appears blurry, pixelated, or soft on Retina/HiDPI screens (2x pixel ratio on modern MacBooks, iPhones, newer Windows displays). Canvas is rendered at 96 DPI by default; HiDPI screens scale it up, causing visual degradation. Players notice immediately and the game feels cheap, even if gameplay is solid.
Why it happens:
HTML5 Canvas has a default DPI of 72-96. When viewed on a 2x density Retina screen, the browser upscales the canvas image (like stretching a PNG), causing blurriness. The CSS size and the internal resolution are decoupled. Most developers set canvas width/height in CSS but don't account for devicePixelRatio.
How to avoid:
- Query devicePixelRatio: Read
window.devicePixelRatio(typically 1 on standard, 2 on Retina, can be 1.5 or 3+ on unusual displays). - Scale canvas internally: Set canvas width/height to
(targetWidth * dpr, targetHeight * dpr). Set CSS width/height to original size. - Scale 2D context: Call
ctx.scale(dpr, dpr)after creating the context. Now all drawing commands account for the pixel ratio. - Example:
const dpr = window.devicePixelRatio || 1; canvas.width = 800 * dpr; canvas.height = 600 * dpr; canvas.style.width = '800px'; canvas.style.height = '600px'; ctx.scale(dpr, dpr); - Handle zoom changes:
devicePixelRatiocan change if the user zooms the page (fractional values like 2.223). Re-apply scaling on window resize and orientation change. - Test on real Retina hardware or browser DevTools device emulation (Chrome: toggle device mode, select "iPhone 12" or "MacBook Pro").
Warning signs:
- Game looks sharp on your desktop, but blurry on MacBook or mobile
- Players report "pixelated graphics" or "soft rendering"
- Text, particle trails, and thin lines appear fuzzy while gameplay works fine
- Obvious visual gap compared to native mobile apps
Phase to address: Phase 1 (Rendering Setup) — Implement DPI scaling before drawing anything. It's a one-time setup with massive visual payoff. Cheap to fix early, embarrassing to fix after launch.
Pitfall 4: WebAudio Autoplay Policy — Audio Context Won't Initialize
What goes wrong: You initialize the Web Audio API on page load to prepare sound effects and music. On Chrome, Firefox, and Safari, the audio context is suspended and audio never plays. The game launches silently, and sound only works after the player clicks somewhere. If you try to play audio before user interaction, nothing happens. In some browsers, audio context creation itself fails.
Modern browsers enforce strict autoplay policies: audio must be preceded by explicit user interaction (click, tap, key press). This was added to prevent auto-playing ads and surprise sounds.
Why it happens:
Developers assume they can initialize audio immediately. They don't know about the autoplay policy. On first load, there's no user gesture, so AudioContext is created but immediately suspended. Attempts to play sounds fail silently.
How to avoid:
- Create AudioContext on first user interaction: Wait for the player's first click/tap, then initialize Web Audio. This also saves resources on pages the player never actually plays.
- Call context.resume() after user gesture:
document.addEventListener('click', () => { if (audioContext.state === 'suspended') { audioContext.resume(); } }); - Provide audio enable/disable toggle: Let players disable sound in settings without breaking the game. Store preference in localStorage.
- Test on Chrome, Firefox, Safari: Autoplay policies differ subtly. Test cross-browser on real devices.
- Graceful fallback: If audio doesn't play, the game still works. Don't break core functionality over sound.
Warning signs:
- Game launches silently on first load, audio works after any click
- Audio context state is "suspended" when logged to console
- Web Audio graphs show no output or oscillators don't produce sound
- "DOMException: NotAllowedError" when creating audio context
- Mobile browsers (iOS Safari) never play audio unless explicitly enabled
Phase to address: Phase 4 (Audio & Juiciness) — Audio is non-critical to core gameplay, but getting it right prevents the silent-game impression. Should be addressed before polish pass, not MVP.
Pitfall 5: AI Difficulty Balance — Unbeatable or Brain-Dead
What goes wrong: AI opponent is either trivially easy (always misses, moves slowly) making single-player boring, or perfectly inhuman (hits every ball perfectly) making the game unwinnable. There's no middle ground. Players either crush the AI immediately or face an impossible wall, killing replayability.
Why it happens: Developers often take a shortcut: set AI paddle Y-axis equal to ball Y-axis (perfect tracking). Or they hardcode a fixed reaction speed. No difficulty levels, no margin of error. The AI is deterministic and extreme.
How to avoid:
- Add reaction delay: AI should have a perceptual delay before reacting (e.g., 0.2 seconds). Humans have reaction time; so should the AI.
- Introduce error margin: AI aims for ball position ± random offset (e.g., ± 10 pixels). Perfect aim is impossible; gives human players a chance.
- Implement multiple difficulty levels:
- Easy: Large reaction delay (0.3s+), large error margin (±20 pixels), slower paddle speed
- Medium: Small delay (0.1s), small error margin (±5 pixels), normal paddle speed
- Hard: Minimal delay (0.05s), minimal error margin (±2 pixels), slightly faster paddle speed
- Add adaptive learning (optional): Track player skill and adjust AI in real-time. But only after proving static difficulties work.
- Give AI imperfect information: AI shouldn't predict ball trajectory perfectly. Let it track the ball's visible path and occasionally misjudge angle or spin.
- Test at each difficulty: Play 10 rounds at each level. Easy should feel winnable. Hard should feel challenging but fair. Medium should feel competitive.
Warning signs:
- Single-player against AI feels pointless (too easy or too hard)
- No difficulty setting, players have no progression path
- AI hits every ball or misses every ball (no middle ground)
- Player reports: "I can beat easy mode with my eyes closed" or "Hard is impossible"
- Difficulty spike between Easy and Hard is too large (missing Medium equivalent)
Phase to address: Phase 3 (AI) — AI difficulty must be tuned before launch. Broken difficulty is worse than no AI. Must include multiple levels and feel fair.
Pitfall 6: Power-Up Balance — Breaks Gameplay or Feels Pointless
What goes wrong: Power-ups either trivialize the game (ball slows down, paddle doubles in size, player wins instantly) or are too weak to matter (1% speed increase that nobody notices). Some power-ups are clearly better than others, so only one is ever useful. Or power-ups spawn so frequently that the game becomes random chaos instead of skill-based.
Why it happens: Power-ups are added late as a "juiciness" feature without playtesting. Developers don't balance spawn rates, durations, or effects relative to the base game. Each power-up is tuned in isolation, not against the full suite.
How to avoid:
- Define power-up tiers: Categorize by impact (weak, medium, strong). Each tier should spawn at different rates.
- Weak: +5% ball speed (noticeable but not game-changing)
- Medium: Paddle size +30% or ball speed -20% (moderately useful)
- Strong: Triple ball or freeze opponent paddle (rare, high-impact)
- Tune spawn rates: Weak power-ups spawn every 15-20 seconds. Medium every 30-40 seconds. Strong every 60+ seconds. Adjust based on playtesting.
- Set sensible durations: Most power-ups should last 5-10 seconds (enough to feel useful, not permanent). Longer durations make balancing harder.
- Avoid overpowered combinations: A large paddle + slow ball is almost unbeatable. Test all combinations.
- Make power-ups visible: Players should know a power-up is active and when it expires (visual indicator, timer bar, color change). Mystery mechanics feel unfair.
- Playtest extensively: 20+ rounds with varied power-up timing. Does it change strategy or just randomize outcomes? If the latter, recalibrate.
Warning signs:
- One power-up is clearly better; players camp it or ignore others
- Power-ups spawn too often, removing skill from the game
- A single power-up is almost unbeatable (ball slow + large paddle)
- Players don't understand what a power-up does
- Power-up effect is so subtle it's not noticeable (wasted UI real estate)
Phase to address: Phase 4 (Power-Ups & Arenas) — Power-ups should be added only after base gameplay (physics, AI, collision) is solid. Balancing requires playtesting against a working AI. Can't tune power-ups if the foundation is broken.
Pitfall 7: Input Lag — Paddle Feels Unresponsive
What goes wrong: Player presses up arrow or W key to move the paddle, but there's a visible delay before the paddle responds. Feels sluggish, uncontrollable. Timing-sensitive games (like Pong) become unplayable because the delay breaks prediction. Players blame the game for feeling bad, even if physics is correct.
Why it happens:
- Using
setInterval()instead ofrequestAnimationFrame()(not synced to refresh) - Keyboard state not polled during game update (only during event handlers)
- Excessive DOM manipulation between input and rendering
- Browser input lag (8-13 frames / 130+ ms on some browsers)
How to avoid:
- Use requestAnimationFrame: Schedule updates with
window.requestAnimationFrame(), notsetInterval(). RAF syncs with monitor refresh. - Maintain a key state map: Track which keys are pressed in a boolean object. Update on keydown/keyup, then read state during game update.
const keys = {}; document.addEventListener('keydown', (e) => keys[e.key] = true); document.addEventListener('keyup', (e) => keys[e.key] = false); // In game loop: if (keys['ArrowUp']) paddleY -= paddleSpeed * deltaTime; - Update paddle position during game update, not event handler: Never move the paddle directly in the keydown event. That causes de-sync between input and rendering.
- Minimize DOM changes: Avoid
document.getElementById()inside the game loop. Pre-cache DOM references. - Test input latency: Use a camera at 240fps+ or the built-in browser input latency tester to measure delay. Target <50ms total (including display lag).
Warning signs:
- Paddle movement feels sluggish or delayed
- Paddle doesn't move immediately after key press
- Player can't hit fast balls reliably (timing window too tight)
- Player reports: "The paddle won't respond fast enough"
- Frame-by-frame video shows visible delay between input and paddle movement
Phase to address: Phase 2 (Game Loop) — Input responsiveness is fundamental. If the player can't control the paddle smoothly, nothing else matters. Must be perfect before AI or power-ups.
Pitfall 8: Memory Leaks from Event Listeners
What goes wrong: After playing multiple rounds, the game consumes more and more memory. Opening/closing menus, starting/ending matches, or switching game modes accumulates stale event listeners. Eventually, the browser tab becomes slow or crashes. The leak is silent — no error messages, just creeping slowness.
Why it happens: Event listeners for keyboard input, window resize, or state changes aren't removed when transitioning between game states. Each menu open or game over adds more listeners. The old listeners are never garbage collected because they still reference the old state objects.
How to avoid:
- Always remove event listeners on state cleanup:
// When transitioning state document.removeEventListener('keydown', handleKeyDown); document.removeEventListener('keyup', handleKeyUp); window.removeEventListener('resize', handleResize); - Use named functions for removeEventListener: Anonymous functions can't be removed. Use a reference:
const handleKeyDown = (e) => { /* ... */ }; document.addEventListener('keydown', handleKeyDown); // Later: document.removeEventListener('keydown', handleKeyDown); - Use the
onceoption for one-time listeners:addEventListener('click', handler, { once: true })auto-removes after firing. - Centralize listener management: Keep a registry of all active listeners. When changing game state, remove all listeners for the old state.
- Test for leaks: Use Chrome DevTools Memory tab to take heap snapshots. Play multiple rounds, switch states, check memory growth. Should be stable, not growing.
Warning signs:
- Memory usage grows 10-50MB per game round
- After playing for 10+ rounds, noticeable slowdown
- Chrome DevTools Memory shows detached DOM nodes or listeners piling up
- Game works fine for 2 rounds, then stutters on round 3
- Closing and reopening settings menu causes a lag spike
Phase to address: Phase 3 (State Management) — Implement cleanup as part of state transitions. Can't be patched later without refactoring. Establish the pattern early.
Technical Debt Patterns
| Shortcut | Immediate Benefit | Long-term Cost | When Acceptable |
|---|---|---|---|
| Skip collision detection architecture, hardcode paddle bounds checks | "Simple" code, faster initial development | Ball tunneling, impossible to add moving obstacles or complex arenas | Never. This breaks the game. |
| Use variable delta time without capping | "Works" on most machines | Spiral of death on slow frames, unpredictable gameplay | Never. Use fixed timestep. |
| Render full canvas every frame without optimization | Quick to code | 30fps instead of 60fps, power-ups cause visible stutters | Only if particle count is <50. Profile first. |
| Don't implement power-up balance, ship with minimal tuning | Launch faster | Power-ups break or trivialize gameplay, kills replayability | Only in MVP. Must be tuned before v1.0. |
| Ignore DPI scaling, assume standard 96 DPI | Saves setup code | Blurry on Retina displays, players think game is poorly made | Never. 5 minutes of code, massive visual impact. |
| Don't remove event listeners on state transitions | Fewer lines of code | Memory creeps, lag accumulates, browser crashes on long sessions | Never. Cleanup is non-negotiable. |
| Create AudioContext on page load (ignore autoplay policy) | Audio "ready" immediately | Silent game on first load, player thinks sound is broken | Never. Respect autoplay policy. |
Integration Gotchas
| Integration | Common Mistake | Correct Approach |
|---|---|---|
| Canvas rendering + requestAnimationFrame | Use setInterval() for game loop instead of RAF. Causes jitter, visual tearing, and poor input feel. |
Always use window.requestAnimationFrame(). Syncs with monitor refresh, provides timestamp for delta time. |
| Keyboard input + Game state | Listen for keydown/keyup globally without checking game state. Menu and game both respond to same keys, inputs conflict. | Maintain key state map. Only read state in appropriate game phase (not in menu). Clear state on transitions. |
| Power-ups + Physics | Power-up changes ball speed instantly mid-flight. Can cause tunneling if new speed exceeds collision detection limits. | Clamp power-up effects to safe ranges. Test all combinations against max expected velocity. |
| Audio + Game state | Play sounds in keydown event handler. Sounds overlap, button mashing causes audio feedback explosion. | Queue audio events and play during game update, once per frame max. |
| Canvas size + Window resize | Set canvas CSS size but not internal resolution. On window resize, canvas stretches or shrinks, breaking DPI scaling. | Listen to window.resize, recalculate canvas width/height accounting for devicePixelRatio, re-apply scale. |
Performance Traps
| Trap | Symptoms | Prevention | When It Breaks |
|---|---|---|---|
| Excessive canvas clears | Frame rate drops from 60 to 40 fps as particle count grows. clearRect() becomes the bottleneck. | Use dirty rectangles: only clear regions where objects moved, not full canvas. Or use offscreen canvases for static backgrounds. | 500+ particles on screen, or lots of large objects moving fast. |
| Unoptimized collision detection | 60 fps with 2 paddles and 1 ball. 30 fps when adding 3 obstacles. O(n²) collision checks are too slow. | Use broad-phase filtering (grid-based spatial partitioning). Only test nearby objects, not all objects. | 10+ collidable objects. Switch to spatial hashing before launch. |
| Physics simulation with large delta time | One stutter triggers spiral of death: frame takes 50ms, next frame must simulate 50ms of physics, takes 100ms, etc. | Cap delta time to a maximum (e.g., 0.032s). Overflow into multiple micro-steps instead of one large step. | Any slow frame during fixed-timestep simulation. Must cap. |
| Global event listeners not cleaned up | Memory grows 20-30MB per game session. After 10 sessions, browser becomes laggy. | Remove all listeners in state cleanup. Test with DevTools Memory profiler. | Long gaming sessions (>30 min). Problems appear silently. |
| Rendering tiny particles with full alpha blending | Smooth 60 fps with particles at alpha=0.8. Huge lag when alpha=0.1 (transparent), GPU works harder on blending. | Batch particle renders, use WebGL for >1000 particles, or limit transparency to alpha >= 0.3. | Lots of semi-transparent particles. Plasma/explosion effects common culprit. |
UX Pitfalls
| Pitfall | User Impact | Better Approach |
|---|---|---|
| No difficulty setting | New players get crushed by hard AI or bored by easy AI. No progression path. | Offer Easy, Medium, Hard. Let players choose. Easy should be winnable for non-gamers. |
| Power-ups with no clear visual feedback | Player collects power-up but has no idea what happened. Effect is subtle or non-obvious. "Did something change?" | Use visual indicators: paddle glow when large, trail color change, timer bar, on-screen text ("SLOW MODE"). |
| Input lag makes game feel broken | Player presses key, paddle responds late. Feels uncontrollable. Player blames the game, not their skill. | Ensure <50ms total latency. Test on target hardware. If possible, implement input prediction (show paddle moving before update if expected). |
| Silent game on first load | Player thinks speakers are broken or audio is missing. | Initialize Web Audio on first click, provide audio toggle in settings, test audio on launch. |
| Winning feels random due to power-ups | Player feels skill doesn't matter, only luck. Devalues high-score runs. | Balance power-ups so they enhance skill expression, not replace it. Rare, high-impact power-ups are better than constant minor ones. |
| No visual feedback on collisions | Ball bounces off paddle but feels unresponsive. Paddle hit doesn't feel good. | Add particle burst, paddle glow flash, slight screen shake, or audio tick on hit. "Juice" makes hits feel impactful. |
"Looks Done But Isn't" Checklist
- Collision detection: Often missing CCD or tunneling fixes — verify ball doesn't pass through paddles at max expected speed
- Game loop: Often using
setInterval()instead of RAF — verifyrequestAnimationFrame()is used, delta time is capped, physics is deterministic - Canvas scaling: Often ignores DPI — verify game looks sharp on Retina displays by testing on real hardware or DevTools emulation
- Audio: Often doesn't handle autoplay policy — verify audio plays on first click, not silently on load, and context.resume() is called
- AI difficulty: Often unbeatable or trivial — verify Easy, Medium, Hard all feel fair after 10 test rounds at each level
- Power-up balance: Often untested with full suite — verify no single power-up is overpowered, spawn rates feel right, visual feedback is clear
- Input responsiveness: Often laggy due to poor event handling — verify <50ms input latency, test on target hardware, use RAF
- Memory leaks: Often accumulate silently — verify memory is stable after 10 game sessions, use DevTools Memory profiler
- Multiple game states: Often reuse listeners across states — verify all listeners are removed on state transitions, no detached DOM nodes linger
Recovery Strategies
| Pitfall | Recovery Cost | Recovery Steps |
|---|---|---|
| Tunneling detected after launch | HIGH | Implement CCD or smaller timesteps. Requires refactoring collision system. 2-3 days of work. |
| Spiral of death due to uncapped delta time | HIGH | Cap delta time and implement fixed timestep. Physics must be re-verified. 1-2 days. |
| Game blurry on Retina (discovered late) | LOW | Add devicePixelRatio scaling. 1 hour. But impacts visual first impression. |
| Audio silent on first load | MEDIUM | Add user gesture detection and context.resume(). Requires event listener refactor. 4-6 hours. |
| AI is unbeatable, players quit | MEDIUM | Implement difficulty levels and tune each. Requires playtesting loop. 2-3 days. |
| Power-ups break gameplay | HIGH | Re-balance spawn rates, durations, effects. Requires extensive playtesting. 2-3 days. |
| Input lag makes game unplayable | MEDIUM | Switch to RAF, implement key state map. May require game loop rewrite. 1-2 days. |
| Memory creeping after long sessions | MEDIUM | Audit all event listeners, implement cleanup. Use DevTools Memory to verify. 4-6 hours. |
Phase-Specific Warnings
| Phase Topic | Likely Pitfall | Mitigation |
|---|---|---|
| Phase 1: Game Loop | Frame rate dependency, delta time not capped, requestAnimationFrame not used | Use fixed timestep with capped delta time from day one. Verify on 60hz and 120hz monitors. |
| Phase 1: Rendering | Canvas DPI not scaled, blurry on Retina | Implement devicePixelRatio scaling. Test on real Retina hardware or browser emulation. |
| Phase 2: Physics & Collision | Tunneling at high speeds, ball passing through paddles | Implement CCD or smaller timesteps. Test with ball speeds up to expected max (with power-up scaling). |
| Phase 2: Input | Input lag, unresponsive paddle controls | Maintain key state map, poll during game update, use RAF. Test input latency <50ms. |
| Phase 3: AI | AI difficulty too easy or too hard, no middle ground | Implement Easy/Medium/Hard with reaction delays and error margins. Playtest each difficulty level. |
| Phase 3: State Management | Memory leaks from event listeners, detached DOM nodes | Implement cleanup on state transitions. Test with DevTools Memory profiler. |
| Phase 4: Audio | Audio context suspended, silent on first load | Implement context.resume() on first user gesture. Test autoplay policy on Chrome, Firefox, Safari. |
| Phase 4: Power-Ups | Overpowered or useless effects, breaks gameplay | Implement difficulty tiers, tune spawn rates, playtest all combinations. Don't ship untested power-ups. |
| Phase 4: Polish & Juiciness | Particle effects cause lag, screen shake is jerky | Profile particle rendering, use offscreen canvases or reduce count if needed. Use RAF for smooth animations. |
Sources
Official Documentation & Standards
- Web APIs: Canvas API — MDN
- Optimizing canvas — MDN Web Docs
- Window: requestAnimationFrame() method — MDN
- Web Audio, Autoplay Policy and Games — Chrome for Developers
- Autoplay policy in Chrome — Chrome for Developers
- Window: devicePixelRatio property — MDN Web APIs
- Autoplay guide for media and Web Audio APIs — MDN Media
Game Development & Physics
- Video Game Physics Tutorial - Part II: Collision Detection for Solid Objects — Toptal
- Continuous Collision Detection (Background Information) — DigitalRune Documentation
- Collision detection — Game development — MDN
- Pong AI Test — Joshua Masters
- Pong Extra: Making an Enemy Paddle — Retro Game Deconstruction Zone
- Thinking about pong AI — Lucas Coelho
Performance & Optimization
- Creating a variable delta time javascript game loop — Stephen Dodd Tech
- Creating a fixed delta time javascript game loop — Stephen Dodd Tech
- A Detailed Explanation of JavaScript Game Loops and Timing — Isaac Sukin
- Performant Game Loops in JavaScript — Aleksandr Hovhannisyan
- Optimising HTML5 Canvas games — Nicola Hibbert
- Top Mistakes to Avoid When Developing Games with HTML5 — Superpowers HTML5
- HTML5 Canvas Performance and Optimization Tips — GitHub Gist
- Improving HTML5 Canvas performance — web.dev
- High DPI Canvas — web.dev
- Ensuring our Canvas Visuals Look Good on Retina/High-DPI Screens — Kirupa
Input & Interactivity
- Handling user input in HTML5 Canvas-based games — IBM Developer
- The effect of web browser "Input Lag" in HTML5 games — VSync Tester
- Optimize input delay — web.dev
Memory & State Management
- How to Avoid Memory Leaks in JavaScript Event Listeners — DEV Community
- 4 Types of Memory Leaks in JavaScript and How to Get Rid Of Them — Auth0
AI & Game Balance
- Dynamic game difficulty balancing — Wikipedia
- Artificial Difficulty in Games — Indiecator
- Dynamic Difficulty Adjustment (DDA) in Computer Games: A Review — Zohaib et al. 2018
Pitfalls research for: HTML5 Canvas arcade game (Pong-like) Researched: 2026-03-10