Co-op arcade. One URL. Zero origin.
Browser-2D-Shooter mit Waves, Bossen, Campaign und globalem Leaderboard — komplett auf Cloudflare-Edge. Kein Download. Kein Account. Kein Ad-Roll.
Why this exists.
Browser-Arcade-Shooter sind heute entweder werbefinanzierte Portal-Spiele (Poki, Crazy Games) mit 2-Sekunden-Ad-Rolls vor jedem Run — oder native Mobile-Apps, die einen Store-Umweg und Account-Registrierung voraussetzen.
DefOrbit liefert ein vollwertiges 2D-Arcade-Erlebnis — Waves, Bosse, Co-Op, Campaign-Progression, globales Leaderboard — direkt aus einer einzigen URL. Kein Download, keine Ads, kein Account-Zwang für den Solo-Run. Zielgruppe: Gelegenheitsspieler, die auf Desktop oder Mobile „in den Pausenmodus" wollen.
The box it had to fit in.
index.html + ESM aus CDN. Kein Webpack, kein Vite.How it runs.
Four deliberate choices.
Durable Object pro Room, statt Redis + Socket-Server.
GameRoom-DO-Instanz (Room-Code als stable ID). WebSocket direkt am DO.wrangler deploy. DO-Hibernation hält Idle-Rooms bei ~0 Kosten. State (Spielerliste, Seed, Started-Flag, Disconnect-Timer) lebt im DO-Memory — kein externer KV. Auto-Expire nach 15 min idle.Lockstep mit Seeded-RNG, statt authoritativem Server.
SeededRNG.js) parallel.score / enemies / earthHp) und 30-s-Reconnect-Window.Static Frontend + Worker/D1, statt SPA + Backend.
index.html als Root von CF Pages. Worker hängt nur unter /api/* an derselben Domain.wrangler deploy + git push, alles ist live.Google-OAuth inline verifizieren, statt OAuth-Library.
auth.js verifiziert Google-JWTs manuell via crypto.subtle.verify + JWKS-Cache (1 h TTL).Things that were not obvious.
Reconnect-Logik mit Tick-Delay
_inputDelay = 3 Ticks — kein Client darf seinen eigenen Input instant anwenden. Er schickt ihn, beide Seiten applyen frühestens in 3 Ticks.Das glättet die Simulation und erlaubt dem DO, bei Disconnect den 30-s-Reconnect-Timer zu halten, ohne den Partner sofort rauszuwerfen. Erst wenn der Timer abläuft, triggert
partner-left.Anti-Cheat mit Mode-Scope
SHA-256(score:kills:time:...:mode + SERVER_SALT), Timestamp-Window 5 min.Für Campaign-Purchases:
WHERE currency >= cost-Guard im UPDATE gegen Double-Click / Multi-Tab-Races. earn_nonces verhindert Replay. Outlier-Detection (p99-basiert) ist per-Mode gescoped und für Co-Op komplett übersprungen — zwei Spieler sprengen den Solo-p99 natürlich, ohne dass das geflaggt werden darf.Single Draw Call für 512 Bullets
InstancedBufferGeometry mit Line-Primitive gerendert. Per-Instance-Attribute (iOff, iRot, iOn) werden im Vertex-Shader zu rotierter Line-Segment-Position. Off-Bullets via z = -9999 aus dem Clip-Space geschoben statt echtem Culling.Ergebnis: ein Draw-Call egal wie voll die Szene ist — erlaubt UnrealBloom + 4 Shader-Passes auch auf Mobile bei 60 fps (mit reduziertem Bloom-Radius).
Scaling auf null
Der DO entscheidet per Heartbeat, wann er hibernatet — State wird serialisiert, nächste Nachricht weckt ihn auf. Kein Cron-Job nötig, um leere Rooms zu killen. Auto-Expire nach 15 min idle ist reine Sicherheitsleine, nicht Kostenmechanismus.
What's running.
What I learned.
Lockstep ist billiger als man denkt.
Deterministische Simulation wirkt im ersten Moment nach hohem Implementierungs-Aufwand. In der Praxis war der Seeded-RNG + Checksum-Vergleich weniger Code als ein authoritativer Server inklusive Interpolation gewesen wäre — und die Free-Tier-Kompatibilität war nebenbei ein Bonus. Für asymmetrische Gameplay-Modi wäre der Trade-off anders.
Draw-Call-Budget zuerst, Shader danach.
Der Instinkt bei Bullet-Hell ist, an Shader-Kosten zu sparen. Falsche Front. 512 Bullets × 512 Draw-Calls killt Mobile sofort; ein Draw-Call + teure Fragment-Shader läuft flüssig. Nächstes Game plane ich die Instanced-Pipeline als allererstes Architektur-Element, nicht als spätere Optimierung.