DEV Community

Cover image for From Buggy Prototype to Polished Arcade: How I Upgraded Three Browser Games
Kehinde Owolabi
Kehinde Owolabi

Posted on

From Buggy Prototype to Polished Arcade: How I Upgraded Three Browser Games

From Buggy Prototype to Polished Arcade: How I Upgraded Three Browser Games

Over the past few days I've been deep in the trenches refactoring and rebuilding three browser games built on a lightweight 2D/3D canvas engine. What started as "fix a few bugs" turned into a full-blown feature overhaul across a platformer, a racer, and an FPS.

Here's what actually changed, why, and what I learned.


The Starting Point

Three games, all built on the same homegrown engine:

  • Neon Caverns: Seasons — a platformer with 20 levels and a coin economy
  • Death Boost: Seasons — a pseudo-3D arcade racer
  • Riftfire (formerly Raycast FPS) — a raycast shooter

The initial request was simple: "there are bugs." But as I dug in, the real issues were architectural — not just a few typos.


Neon Caverns: Seasons

The Bugs

Two silent killers were ruining the game:

1. Tile mutations across restarts. The engine's tilemap constructor mutated a shared array every time a level loaded. After one restart, every tile lookup broke. Players saw invisible walls.

2. The pause button drifted off screen. It was added as a world-space entity, so it scrolled away with the camera instead of staying pinned to the HUD.

The Overhaul

  • 4 seasons × 12 levels = 48 unique stages, each procedurally generated but deterministic per run.
  • Ramping difficulty curve — enemy HP, speed, accuracy, and map density all scale with a single normalized value from 0 to 1.
  • Full procedural audio — every sound effect and music track is synthesized live via the Web Audio API. No external files, no downloads.
  • Persistent coins and upgrades — health, speed, jump, magnet range, shield tiers, all saved to localStorage.
  • Star ratings per level (1–3 stars based on health, time, and coin collection).
  • Season unlock chaining — clear all 12 levels of a season to open the next one.

Death Boost: Seasons

The Bugs

This game had a list:

  • Invisible hazards were killing players
  • Cars were colliding from behind the player
  • Traffic recycled to a moving target, causing pile-ups
  • Menu clicks silently failed when audio init threw
  • Sprites rendered ~1000× too small to be visible

The Overhaul

  • 4× wider road — from 2000 to 3400 units wide, 5 lanes instead of 3.
  • 60% fewer obstacles, 50% less traffic, longer spacing between hazards.
  • 12 tracks total with lap counts from 1 to 3.
  • Bug fixes: signed z-window for hazard collisions, cooldowns on repeated hits, sprite scale corrected to match world geometry, audio failures no longer swallow input.
  • Always-visible pause button — now pinned to screen coordinates instead of world space.
  • Per-level difficulty display — cars show their speed, health, and boost stats as animated bars.

Riftfire

The Overhaul

What was originally a 5-map raycast FPS became:

  • 12 seasons × 30 levels = 360 stages, all procedurally generated with seeded randomness.
  • Difficulty ramp — enemy HP, damage, accuracy, speed, and fire rate all scale from easy (level 1) to brutal (level 360).
  • Procedural audio — every gunshot has its own synthesized waveform. Rifle, pistol, shotgun, sniper, minigun — all distinct, all generated at runtime.
  • Adaptive music — four different musical scales that switch based on the current level's difficulty.
  • Weapons shop, ranks, and coin economy — buy new weapons, earn XP, climb 10 ranks.
  • Mobile touch support — analog joystick for movement, drag-to-aim for firing.

The Common Threads

Across all three games, the same principles showed up again and again:

1. Separate World Space from Screen Space

The pause button bug and the sprite scale bug both came from mixing world coordinates with screen coordinates. Anything that should stay fixed on the HUD must be drawn after the camera transform is popped.

2. Never Trust Global Mutable State

The tilemap bug was caused by an engine-level array.unshift() that silently mutated data shared between level loads. Whenever possible, rebuild state fresh at scene boundaries.

3. Procedural Content Is Cheap and Endless

All three games now generate their content at runtime. This means:

  • No asset loading delays
  • No missing-file errors
  • No copyright issues
  • No download size

Web Audio can synthesize a gunshot, a jump sound, or a full musical theme in a few lines of code.

4. Input Handling Must Be Defensive

The click-swallowing bug in Death Boost happened because initAudio() threw an exception and aborted the event listener before pendingClick was ever set. Always set state before calling anything that might throw.

5. Difficulty Should Be a Single Scalar

Rather than tweaking 20 numbers per level, each game derives all difficulty factors from one normalized value:

difficulty = (globalLevel / totalLevels) ^ 0.85
Enter fullscreen mode Exit fullscreen mode

That one number then scales enemy HP, count, speed, accuracy, and map density. It's clean, tunable, and easy to reason about.


What I'd Do Differently

If I started over:

  1. Version control from day one. This was all done in single monolithic files.
  2. Write tests for input flow. The click bug was caught by a human, not a test.
  3. Separate rendering from state. Several bugs came from render functions that also mutated game state.
  4. Build a small dev console — a way to jump to any level, tweak difficulty, and inspect state.

Final Numbers

Game Levels Audio Notable Feature
Neon Caverns: Seasons 48 100% procedural Season unlock chains
Death Boost: Seasons 12 tracks 100% procedural Multi-lap enduros
Riftfire 360 100% procedural Adaptive music scales

All three ship as single HTML files, no external assets, and run in any modern browser.


Closing Thoughts

The best thing about browser games is how fast the iteration loop can be. Change a number, refresh the page, feel the difference. The worst thing is how quickly small architectural mistakes compound into invisible bugs.

The three games above went from "playable but broken" to "actually fun" by fixing a handful of fundamental issues and then building outward. Every new feature is now cheap to add because the foundation is solid.

If you're building a browser game and something feels off, it's almost never "just one bug." It's usually a small design decision that ignored a boundary between two systems. Find the boundary. Fix the boundary. The rest follows.

Top comments (0)