Browser game · phone and desktop
SWARMGEDDON
A twin-stick survival shooter: swarms of alien insects across three worlds, bosses, and a seeded daily challenge.
- Role
- Timeline
- Stack
- Links
By the numbers
60 Hz
fixed simulation step with an accumulator; rendering interpolates between steps
7
seeded random streams: spawn, script, boss, loot, draft, combat and effects
30
seeds per world for each bot type in the final balance matrix
14
weapons, plus 31 perks and 10 fusions, defined as data
- 1 · Every frame request
Animation The frame time is clamped, so a stall cannot queue seconds of steps, and added to the accumulator.Frame -
2 · Simulation, 60 Hz
Fixed steps While the accumulator holds 1/60 s: run one step, subtract 1/60 s. Game state changes only here.7 seeded streams Each one is mulberry32, seeded with hash32(run seed, its salt).- spawn
- script
- boss
- loot
- draft
- combat
- fx
Purity gate On every build, src/systems, src/game and src/content may not call:- Math.random
- Date.now
- performance.now
- window.
- document.
- localStorage
- 3 · Once per frame Render Draws between the last two steps with alpha = accumulator / step, so 30 fps and 120 Hz play the same game.
The idea
SWARMGEDDON is a twin-stick survival shooter that runs in the browser on phones and desktops. You fight swarms of alien insects across three worlds, with bosses and a seeded daily challenge. I wanted dense fights to stay responsive on a phone without a general-purpose game engine.
What I built
The game loop advances the simulation in fixed 60 Hz steps with an accumulator and renders once per frame, interpolating between steps. Collisions use a hand-written spatial hash instead of a physics engine. All run randomness comes from seven mulberry32 streams, each seeded from the run seed, so the same seed and the same inputs replay the same run.
Weapons, perks, enemies, pilots and worlds are data on top of the same engine: 14 weapons with 9 evolutions, 31 perks, 10 fusions, 3 pilots and 3 worlds. Every sound is synthesized with the Web Audio API, so the repo has no audio files. Capacitor wraps the same build for iOS and Android, and saves write through to native storage.
The hard parts
-
Same seed, same run
Replays and a fair daily challenge need a simulation that never reads the clock or an unseeded random number. Every build runs a purity gate over the simulation folders that rejects Math.random, Date.now, performance.now, window, document, localStorage and audio calls.
-
The same game at any frame rate
A phone at 30 fps and a monitor at 120 Hz have to play the same game. The simulation always steps at 60 Hz, and the renderer draws between two steps using an interpolation value, so motion stays smooth without changing the rules.
-
Tuning with bots
Headless bots play the real game in a browser with turret, roam, crude and smart policies, plus dash and reaction-time variants. A matrix runner records metrics per world and seed, and I tuned boss health, fight length and swarm density from those results.
What’s next
Ship the iPhone version from the same codebase.
Try it
Related by idea
Seeded randomness, so any run can be replayed
Invicto, Flawless, The Veil · El Velo, Conuco, Coherente, This site
Rules in a module with no UI imports
Difficulty checked by simulation before release
Invicto, Flawless, The Veil · El Velo, Coherente
Saves that survive reloads, updates and storage loss
Conuco, Coherente, El Corre Corre, Invicto