Skip to content
Work

Browser game · phone and desktop

Live swarmgeddon.adelsonaguasvivas.workers.dev

SWARMGEDDON

A twin-stick survival shooter: swarms of alien insects across three worlds, bosses, and a seeded daily challenge.

SWARMGEDDON on a phone: a STAMPEDE alert, then a stream of alien insects pours in while the pilot fires.
The live game with touch controls, two and a half minutes into a run.
Role
Solo: design, code, release
Timeline
Jun 2026 to now
Stack
TypeScript, PixiJS, WebGL, Web Audio, Capacitor, Cloudflare Workers

Live at swarmgeddon.adelsonaguasvivas.workers.dev. Public code on GitHub. Status checked Oct 15, 2026.

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

Fig. 1 One frame: fixed steps, then one render
  1. 1 · Every frame requestAnimationFrame The frame time is clamped, so a stall cannot queue seconds of steps, and added to the accumulator.
  2. 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. 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

  1. 01 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.

    Where in the code: scripts/check-sim-purity.mjs (opens in a new tab)

  2. 02 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.

    Where in the code: src/core/time.ts (opens in a new tab)

  3. 03 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.

    Where in the code: scripts/playtest/matrix.mjs (opens in a new tab)

What’s next

Ship the iPhone version from the same codebase.

The SWARMGEDDON level-up draft: three perk cards to choose from, with reroll, banish and skip below.

Try it

Related by idea