The story · a developer's account

How I
did it.

How I Did It: Rebuilding Red Alert as a 3D Browser RTS the Way I Imagined It, with a Team of AI Agents

A developer's story by Danillo Felixdaal. Jolyn Studios is his spare-time studio; Twenty+One is the day-to-day company he co-founded. This was not a one-shot prompt project: about six weeks of deliberate engineering, two of them fully dedicated and the rest evenings when time allowed, an AI crew staffing every department from the engine port to the film studio, and a lot of argument about the right way to do things.


Amsterdam, after work

Before any of the engineering, there was a kid in front of a monitor playing Command & Conquer until the room went dark around him. That kid was me.

Westwood was absolutely amazing. I don't say that as a compliment; I say it as a fact about my childhood. Red Alert was the first game I loved hard enough to lose track of time in. Yuri's Revenge made me lose track of it again, for longer. Westwood's games had a quality that is almost impossible to name and impossible to fake: the units felt like they belonged to a real army, the missions felt authored by people who cared, and the music changed your pulse before the first tank moved. When people say "Westwood was amazing," I don't hear nostalgia; I hear a studio that understood an RTS is a feeling, not just a ruleset.

Then Generals happened, and the game stopped being something I did alone. After work, my friends and I took over internet cafés in Amsterdam. Whole evenings. Every one of us behind a screen, plates of food next to the keyboards, someone two chairs away screaming "TANK RUSH, I TOLD YOU" while the whole café laughed. Nobody checked the clock. Nobody sold us anything. If you lost, you lost because the other player was better that night, and you paid for the rematch in pride, not in currency.

That is the purest form of gaming I have ever known: player versus player, or you versus the machine, and nothing for sale between you and the outcome.

REDLINE WARS: FRACTURED ORDER exists because I wanted to know if that feeling survives in a modern browser. Same honesty. No shortcuts. Rendered the way I used to imagine it while squinting at a 2D minimap.

And because that dream touches someone else's property, one thing before the engineering: this is a non-commercial fan project, built with deep respect for Electronic Arts and enormous gratitude to Westwood Studios. No EA assets, no EA audio, no imitation of their actors or their work. If anything here is ever considered overstepping, reach out and I will show up and help resolve it. Donations are welcome; the game is free.


The crew: ten AI specialists, one human referee

This project was not built by one person and not by one model. It was built by one human referee (me) directing a rotating cast of AI agents, each with its own specialty, connected by a coordination layer I built for our work at Twenty+One so that Claude Code and Codex could literally sit in the same working session, message each other, hand off tasks, and argue about designs.

  • Claude Opus 4.8 (later upgraded to Opus 5+) via Claude Code: repo-wide reasoning, risk review, architecture arguments, and the "wait, are you sure?" voice.
  • Codex (GPT-5.6) via the Codex CLI: the second opinion. When Claude and Codex disagreed about a design, they had a live debate through that coordination layer and I got the synthesis.
  • Fable: Fable and I created the openra-web fork from OpenRA. I then did most of the WebAssembly porting there. (More on this below: it was the hardest single piece of engineering in the project.)
  • GLM 5.3: the workhorse. It finished the entire Blender model pipeline and then built the complete multiplayer network layer end-to-end.
  • Grok 4.8: code reviews and second opinions on the asset pipeline.
  • Astra 6: the Blender specialist. It rocked in Blender. Roughly 95% of the 3D asset foundation was laid by Astra before GLM took over to finish.
  • Suno: the music. The theme you hear at boot is Suno-generated, tuned to match the Red Alert ambiance.
  • ElevenLabs: every voice and every new sound effect in the game.
  • Higgsfield AI: the cinematic unit. Every character film and cut scene on the site: Allied command, Kirkov, Riki, Jackson. Generated, reviewed against the game's own art direction, regenerated until it belonged to the same war.
  • Meshy: the model detail pass. Advanced Riki's Blender model even further, so the commando you command matches the commando on screen.

The rule I enforced through that coordination layer was simple: agents don't sit idle and they don't negotiate silently. When Claude and Codex disagreed on architecture, they debated until convergence and I got a summary of consensus, disagreement, and decision. When one agent finished a task, it asked the coordinator for the next one. When one agent broke something, the other was the natural reviewer.

That last point mattered more than any single model's intelligence: every meaningful design was reviewed by at least two different AI opinions before it landed.


Act 1: The bet

SteelSeed didn't start as SteelSeed. The repo began as Boxcade, a small multiplayer game platform with WebSocket support and shareable game links (502 commits ago, per the git history). The pivot to "one ambitious thing" happened fast: I picked OpenRA, the mature, GPLv3, open-source Command & Conquer remake, as the simulation engine, and drew a line in the sand that still defines the project:

OpenRA is the only truth. The browser client is a lockstep viewer that never invents anything.

Everything below is that one decision, applied over and over.

Act 2: The port · OpenRA to WebAssembly

Porting OpenRA to WebAssembly was not "add a WASM target and build." Fable and I created the openra-web fork straight from the upstream OpenRA tree, a codebase carrying 31,000+ commits of engine history dating back to 2007. I then did most of the WebAssembly porting there. It was the hardest engineering in the whole project, and it produced a list of problems that each needed a real answer:

ProblemWhy it's hard in a browserWhat I did
No raw TCP socketsOpenRA's networking assumes TCPBuilt BrowserWebSocketConnection, a WebSocket transport with a polled JS interop pump, because WASM code can't block on sockets
ClientWebSocket throwsPlatformNotSupportedException on browser-WASMRewrote the transport to depend on the JS side entirely
ThreadsBrowser WASM threads are restrictedKept the engine single-threaded; the render loop pumps the simulation explicitly
StartupParsing everything at bootCompressed AOT+trim pipeline; later replaced details with streaming (see Performance)
DeterminismA browser is not your dev machineA determinism oracle: same seed, desktop replay vs browser replay, compared frame-by-frame, sync-hash by sync-hash

That last row is the reason the game can be multiplayer at all. OpenRA is deterministic lockstep: the same seed always produces the same battle. My oracle replays a seeded battle on desktop .NET and in the browser WASM and asserts identical state. When that test is green, the browser is running the real game, not an approximation.

The port also forced me to formalize the boundary between engine and presentation. It boils down to five rules: the simulation is authoritative, the renderer never invents state, the relay is a blind byte pump, there is no late join, and no EA/Westwood asset ever ships. Every feature since has been checked against those five lines.

Act 3: The sync contract · how the engine and the 3D layer speak

This is the part most "engine in the browser" projects get wrong, so here is the actual machinery.

OpenRA engineWebAssembly · 25 Hz lockstepworld tick → snapshot emitterthe only truth Snapshot decoderTypeScript, zero-copy viewsevent republisherpollSnapshot() WebGPU renderer3D, read-only Audiovoice + SFX banks HUDminimap · roster · orders orders, validated

Key decisions, and why:

  • Packed binary, not JSON. A single tick snapshot carries hundreds of actors across a dozen sections (terrain, actors, projectiles, shroud, events, players, production…). JSON would spend most of its bytes on syntax and most of its CPU on parsing. The snapshot is a flat DataView the client reads with zero allocations.
  • Self-describing sections. Each section is tagged with an id and length, and unknown event kinds are skipped, never thrown. That one rule is why I could add event types without breaking older clients, and why a decoding bug degrades gracefully instead of killing the tab.
  • The render loop never ticks the sim. The simulation advances on its own schedule (25 Hz), the renderer consumes the latest pinned snapshot at display rate, and interpolation keeps the picture smooth. When the sim is silent too long, a watchdog tells the player why (the halt banner now carries host-side diagnostics: world tick/hash, connection state, server error, captured at the moment of the stall).
  • The renderer is read-only. All player intent leaves the browser as OpenRA orders. The 3D layer cannot "decide" a hit, move a unit, or fake an explosion. That constraint is what makes the lockstep multiplayer possible at all: every client runs the same deterministic simulation and only disagrees about pixels, never about state.

Why not three.js (or Babylon, or Unity WebGL)?

People ask this first, so here is the honest answer rather than a tech-rally slogan.

  1. A traditional scene graph fights the architecture. Three.js wants to own a scene graph of objects it moves and animates. My actors don't live in a scene graph; they live in a packed snapshot emitted by a deterministic simulation at 25 Hz. What I need is not "a scene" but a GPU batch pipeline that turns snapshots into draw calls. Three.js would be a layer I'd constantly be routing around.
  2. I need compute-style instancing, not meshes. 275 assets, LOD chains, 200+ dynamic lights: the renderer pushes per-instance data (24 floats per instance) through storage buffers into custom WGSL. Reimplementing that on top of someone else's material system would be more work than owning the pipeline.
  3. Determinism and dependency hygiene. The project has zero runtime npm dependencies in the client. Adding a big third-party renderer would drag in a dependency graph I'd have to pin, audit, and keep deterministic across replays, for features I'd strip anyway.
  4. WebGPU-only features I wanted on day one. Clustered forward+ lighting, TAA with motion vectors, AgX tone mapping, SDF terrain with surface nets, GPU-driven LOD with QEM decimation. These are first-class in my own WebGPU layer and bolted-on afterthoughts elsewhere.

The cost is real: I own every shader, every pass, and every bug. The payoff is equally real: a renderer that treats the simulation as its only input, and a frame budget I can actually reason about.

Act 3b: The gate that says no

That sync diagram is only half the discipline. The other half is a release gate whose job is to be unpleasant: it treats the snapshot format as a public ABI and refuses to let a producer, a decoder, or an optimistic agent redefine it by accident.

C# emitteractors · players · productionfixed field widthsemit Packed snapshotheader + section tableoffsets and lengthsfour-byte-aligned payloaddecode Independent calculatorcounts × field widths TypeScript decoderzero-copy typed views Falsifiercorrupt bytes · lengths · tail Compare every claimheader · physical offsetsdeclared lengths · viewsdrift is immediate Release decisiongreen: compose + publishred: block both state, not proserecount, don't trust

Seven checks happen before a changed snapshot format can call itself shippable:

  1. Emit. The C# engine writes actor, player, and production state into packed binary sections. Every field has a width and an order; every section has an offset and a length.
  2. Decode. The TypeScript side does not clone the snapshot into friendlier objects first. It creates typed-array views over the same bytes, so the gate tests the decoder players will actually use.
  3. Recount. A second calculator ignores the declared lengths and rebuilds them independently from record counts and field widths. Trusting the emitter's own arithmetic is not a gate; it is a compliment.
  4. Compare. Header claims, physical offsets, declared lengths, section boundaries, and decoder views all have to agree. A single unexplained byte is a failure.
  5. Drift-check. Adding, removing, or misaligning a field changes the expected size. The gate makes that change loud immediately instead of letting it become a mysterious multiplayer desync next week.
  6. Falsify. The gate deliberately corrupts bytes, lengths, and trailing data, then requires the decoder to reject those frames. A check that has never seen a bad input has not proven that it can say no.
  7. Block release. While this gate is red, compose and publish stay off. The argument ends when the evidence turns green, not when the implementation feels finished.

This is what a short exchange looked like on the private agent-to-agent bridge after a presentation-only actor field landed. The names are the agents; the referee is me.

Private agent-to-agent bridge

Codex

I added a presentation-only actor field.

GatePASS

Actors: declared 632 bytes, independent calculation 632 bytes.

Claude

Decoder views and turret offsets remain aligned.

GateRED

A test length says 640 while the physical section says 632.

Codex

Corrected the expected width and reran the falsifiers.

GatePASS

Deliberate length corruption rejected.

Human

Merge only with the ABI and bridge gates green.

The Snapshot ABI is only the clearest example. The same "prove it, then ship it" rule spans the rest of the build:

Provenance gatessource · licence · exportpinned hashes and model bytesno mystery assets Simulation gatesruntime · deterministic replaymultiplayer · desyncsame seed, same battle Experience gatesvisual · audio · landingframe budget, banks, public buildmeasured, not felt Redstop the release train Evidencecounts, hashes, pixels, logs Human refereemerge only when green recordreview

There is nothing magical in the mechanism. The gate is a small, boring program with three properties most demos lack: it reads the real output, it calculates the answer separately, and it has the authority to stop the release. That authority is why an agent can propose a dramatic change at 2 a.m. and the project can still say, "not until the bytes agree."

Act 4: The WebGPU layer · quality and frame budget

The rendering stack, top to bottom: clustered forward+ lighting (200+ dynamic lights: searchlights, muzzle flashes, tesla arcs), cascaded shadow maps, TAA with motion vectors, AgX tone mapping, probe-based GI, a procedural sky with weather and a 24-minute day/night cycle, and a fog-of-war that is rendered as a real 3D effect rather than a black overlay.

The interesting part is not any single feature, it's the quality governor: the game measures its own frame time and adapts (internal render scale, extras) so a $300 laptop and an M3 Pro both get a playable experience. Graphics modes are simple for players (Auto, Dynamic, Low, Medium, High, Classic, Ultra, Ultra+) and honest under the hood: named presets lock their settings, Dynamic is allowed to degrade.

Performance was its own war (chronicled in my tuning notes): shadow passes drawing 56M triangles, flickering as the eviction budget thrashed, meadow instances eating 13.8 ms of CPU. The wins that mattered: splitting shadow LOD from camera LOD (−26% shadow triangles), sticky LOD hysteresis (no per-frame mesh swapping, at the cost of +82 draw calls, a trade I'd make again), moving meadow updates from 13.8 ms to 2.75 ms, per-cascade frustum culling, and removing every sqrt from the hot path.

Act 5: Goodbye synthetics · the Blender era

The first playable SteelSeed rendered all 275 actors as synthetics: procedural cylinders, boxes and parametric shapes out of code, with a single 29-layer texture set for the entire game. It proved the architecture and looked like 2002.

So I made a decision that broke my own early rule: procedural-only was out; authored 3D was in, as long as everything was either made by me in Blender or CC0/CC-BY with pinned sources and hashes. That unlocked the current pipeline: 652+ Blender source files, an industrial-v1 29-layer PBR material set, per-unit wear and grime driven by curvature and AO, QEM-decimated LOD chains, and five-stage damage states from light scorching to collapsed walls.

The build split across agents exactly where their strengths were: Astra 6 laid ~95% of the asset foundation (it was exceptional inside Blender), code reviews ran through Codex, GLM 5.3 and Grok 4.8, GLM took over and finished the Blender pipeline, and Astra authored the project's showpiece: the MCV deploy animation. 256 bones, 246 channels, a vehicle that unfolds into a full construction yard, with authored five-stage damage states. Every step hash-verified and replayable.

Act 6: Multiplayer · room host, spine, and the truth about relays

Multiplayer shipped in five PRs and three deployment modes that all share one lockstep protocol:

Browser · hostWS /g/<roomId> Browser · joinerWS /g/<roomId> Room host (Node.js)room directory · WS muxdedicated spawnerblind byte pump OpenRA.Serverdedicated match (TCP) bytes, untouched
  • Local skirmish never touches the network (in-process session).
  • LAN uses roomhost.mjs: a room directory, a /g/<roomId> WebSocket mux, and a dedicated OpenRA server per room that the host spawns locally.
  • Internet adds a spine (central relay) and dial-out volunteer nodes: nodes dial out to the spine (NAT-friendly, no port forwarding), players only ever see the spine, and the spine routes player channels to the owning node.

My opinion, stated plainly because I hold it strongly: the relay being a blind byte pump is the single most important security decision in the stack. The relay never parses, validates, or "helps" with game traffic, so there is no relay-side bug that can corrupt a match, and no relay-side authority to abuse. Player-facing endpoints get rate limits, tokens move through environment variables rather than ps-visible argv, and the client never becomes authoritative: all of it reviewed by an independent security pass, several findings of which shipped as fixes (relay port normalization, channel-id recycling, buffer caps, boot deadlines).

I also learned what a gate is for. My federation gate sat in the repo, correct, while a regression broke the spine silently, because nothing ran it. The fix wasn't a patch; it was making the gate unskippable, and adding a WebSocket spy to the test flow so a joiner drop pins itself to a URL and a close code instead of a vibe.

The architecture of record: players host, I run the relay

Five days before writing this, the deployment story got a plan of record. The direction: players host matches in the desktop app, and I run only the relay. One Hetzner box carries the landing, the accounts API and the spine; the game node that hosts other people's matches is not needed for v1 and returns in a later phase. The desktop shell spawns the room host locally (room directory, the /g/<roomId> WebSocket mux, a dedicated OpenRA server per room), dials out to the spine, and joins entirely inside the app: LAN and internet through the same client, no port forwarding, no server admin for the player.

The hardening pass before that mattered more than it looks: bounded relay writers, private listener defaults, an admission gate in front of the room directory, fail-closed overflow and a tick-indexed desync proof. A relay that can be overwhelmed is a relay that can be turned into an authority; capping it keeps it blind and boring.

And then the part I am proudest of: the multiplayer client-index bug (the one where four configured AIs collapsed into one) was found and fixed at the lobby layer, and proven live. The players table of a running match listed the local commander plus two distinct enemy bot clients on their own indexes, relationship: enemy. The plan of record, the contracts, the gates and the live proof finally tell the same story.

Act 7: Sound · Suno, ElevenLabs, and the voice banks

The audio pass replaced every procedural sound with real material:

  • Music: a Suno-generated theme tuned to the Red Alert ambiance, 142 seconds, compressed to 1.7 MB, looping from the boot screen through the match, mute-anytime from the game menu or the M key.
  • Voices: per-faction voice banks rendered via ElevenLabs. English for the Allies; French, German, Russian and Ukrainian faction banks that actually speak those languages; and a Riki persona bank with her own cocky line set. Verified by transcribing my own clips back and diffing.
  • Battle voice: a phonetic Soviet-style announcer bank ("Konstruktsiya kompletna") as a fallback layer, with the faction bank always first.

All of it is build-time rendered into the bundle: zero runtime API calls. The game never phones a TTS provider while you play.

Act 8: The silver screen

An RTS does not need cut scenes. It got them anyway, because a war story needs faces: someone has to break the pact on screen, and a wall of briefing text does not break pacts. Every cinematic on this site was generated with Higgsfield AI: the Allied high-command briefing, Kirkov's address to the Soviet Union, Riki behind enemy lines, Jackson making every grenade count, all cut to the game's own score. No camera crew, no soundstage, no casting agency: for this game, the film department was a generation and review loop.

Allied command · the briefing
Kirkov · the red line
Riki · behind enemy lines
Jackson · make it count

The discipline is the same as everywhere else in this project: generate, review against the game's own art direction, reject, regenerate. The films had to show the same war as the game: same characters, same uniforms, same red line. Most generations did not survive that review; the four on this page did.

Riki got one extra pass. Her in-game Blender model was advanced with Meshy, pushing the geometry and the likeness until the commando you command matches the commando on screen: one character, two mediums, no discontinuity between the film and the fight.

The Meshy model then came back broken (probably by me, on my side of the export): it moved weird, parts deformed, the rifle bent like rubber. Hand-tinkering made it worse, so I handed the whole character to Astra 6 with a ruler. The surgery list reads like a hospital chart: a rigid straight barrel and grip, constraints locking rifle to hand, left shoulder and elbow realigned, hair and sleeve weights re-balanced, temple UVs repaired, a fused inner seam, and a prone cycle grounded to the engine's take-cover state. Then the part that makes it trustworthy: Astra validated its own surgery. 223 sampled frames, at most four weights per vertex, no arm influence in the hair, 147 action-switch checks with zero stale pose, and barrel rigidity errors below 0.00000025 metres. Two hundredths of a millimetre of bend left in a rifle, proven rather than promised. Seven authored animations survived the trip; the game uses six (Careful Walk stays a review extra), and the prone cycle honestly documents some ground slide at compressed RTS scale.

That is the quiet thesis of the whole project: AI is not replacing game studios yet, but it is a promising teammate that speeds every department up (code, art, audio, film, docs) when one stubborn human keeps the taste level up, holds the referee whistle and works with it instead of around it.

Act 9: Optimization · where the last 20% lives

The final pass is where projects earn their numbers: shadow-LOD splitting, meadow instancing at −80% CPU, per-cascade culling, interpolated buffer writes, QEM LOD chains with sticky hysteresis (no flicker, at the price of +82 draw calls, accepted), quality presets that a player can actually read (Auto / Dynamic / Low / Medium / High / Classic / Ultra / Ultra+), and a roster streaming plan to cut first-play download further with KTX2 textures next.

Act 10: The gate marathon · five days, 201 commands

The story has a last act, and it is my favorite kind: the boring one that saves everything.

Before this project could be called finished, I ran the full gate inventory: 201 commands covering rules parity, snapshot contracts, audio, visual quality, performance budgets, multiplayer federation and the deployment chain. The first honest run came back with 54 failures. Not the good kind of red: most of them were stale fixtures, drifted pins and harnesses that had quietly stopped matching the product they were built to guard.

Five days of reconciliation, in six recorded rounds:

  • Rounds 1–2 fixed the fixtures themselves: manual boots that deliver a decoded world, clock pinning, cadence repairs, zero-instance submit guards, a status-strip encoding fix that had bloom leaking onto the HUD.
  • Rounds 3–4 reconciled the source pins: the asset gate re-aligned to the shipped architecture (same-origin bank fetches, hash-pinned packs, the engine's own multiplayer transport), promotion pins refreshed, wake anchors re-measured, and the vanilla world.yaml hero-bridge additions folded into the parity expectation.
  • Round 5 rebuilt the air/naval reports for both quality tiers and taught the deployment chain to skip producers and validate freshness instead: a --validate mode that fails loudly with "stale: run the full pipeline" rather than pretending.
  • Round 6 plus one last engine completion: three players-API members the air/naval integration had referenced but never landed (they broke the C# build silently until the freshness check exposed it), the emit-time string-table prepopulation so every production item has a name, and the voice bank on a fresh ElevenLabs key, 76 death clips over the full per-country, per-class, per-persona bank.

The end state, measured on the merged candidate: the inventory is green or owner-documented, the full acceptance chain (48 executable gates, 67 map starts, Release AOT composed) passes end to end, and two fixture gates are parked with their diagnoses written into the gate files themselves. Two visual gates stay deliberately red on the look the owner approved; their references now record that approval instead of fighting it.

The lesson I keep from this act: a failing gate is a gift when it tells the truth, and a hazard when it lies. Half the marathon was teaching gates to stop lying; the other half was listening to the one that turned out to be right.

The cast is real

One more thing before the numbers, and it is my favorite part: the faces in the cut scenes and the likenesses on the roster are not casting calls. They are family and friends who were kind enough to let me use their images for the game.

  • General Jerome Young is Jermaine Jong, my cousin and my long-standing development and business partner. We have been building things together for years; putting him in charge of the Allied forces was the least surprising casting decision of the project.
  • General Sevastyan Dmitrievich Kirkov is Sebastiaan Steur, as Dutch as they come and a longtime friend, playing the Russian general anyway. He is also an ex-professional soccer player (FC Volendam, SC Heerenveen, Heracles Almelo, Excelsior), which explains the calm of a man who has defended a lead in a real stadium.
  • Riki, the Allied commando, is my wife Rikie and the mother of my child; the character carries her name. The most dangerous unit on the battlefield and the final authority at home. The balance is realistic.
  • Jackson, the Soviet grenadier, is a longtime friend and ex-Dutch military. He already had the make-every-grenade-count mentality; I just gave him a pixel army.

Every dossier on this site is a thank-you note.

Who is behind this

My name is Danillo Felixdaal. I am a co-founder at ProofOfWork in Amsterdam, where I work on AI architecture and engineering; before that I founded Cappow/Spotnik and spent years building web products.

ProofOfWork is rebranding to Twenty+One (twentyplusone.ai), which I co-own. Twenty+One is a multi-person company with customers across different branches, taking a modern AI-first approach to software delivery: 20+ specialist agents led by one senior Build Lead ship enterprise-grade software straight from each customer's own board, where the customer writes the stories and only pays for accepted story points. Redline Wars is my solo take on that belief; Twenty+One is the team version, run for real customers every day.

Jolyn Studios is my spare-time studio and the home of Redline Wars: one person, a crew of AI agents, and every department of a game studio run as a review loop. It is how I find out what I can do with AI when nothing is held back.


The numbers (measured, not estimated)

Built as a desktop-first WebGPU browser RTS: 652+ Blender sources, 275 playable actors, authored damage states, deterministic lockstep multiplayer, and a gated AI-assisted production pipeline. The current classic target holds 48–52 fps, and the opt-in Turbo 60 mode targets 60 fps by tuning render scale and shading budgets, never by removing content. A first visit downloads ~152 MB; returning visits download ~0 MB because the service worker caches every content-addressed asset. Compressed textures and progressive asset streaming are the next release optimizations. The arsenal viewer on the home page serves the game's own model files, loaded live: turrets traverse on their real joints, the way the renderer drives them in a match.

MetricValue
Development periodAbout 6 weeks: 2 dedicated weeks, the rest evenings when time allowed
Commits619, of which 74 in the final five-day gate marathon
AI agents in the loopClaude Opus 4.8 → 5, GPT-5.6 (Codex), GLM 5.3, Grok 4.8, Astra 6, Fable · film: Higgsfield AI · model detail: Meshy · music: Suno · voices: ElevenLabs
Cinematics4 character films and cut scenes, generated with Higgsfield AI
Blender sources652+ authored primaries · 3,327 .blend files on disk including library copies
Actors310 audited in the resolved catalog (283 renderable, 95 buildable)
Models in a live match277 · 1,368,266 triangles at full detail
Total triangles (LOD0)1,481,162
Damage state ladders59
Moving joints1,503
Voice lines35 per faction × 8 banks + Riki and Spy persona banks · 328 banked clips, including 76 death lines
SFX190 rendered clips across 5 faction banks (38 per faction)
Theme142 s, Suno-generated, 1.7 MB AAC
PerformanceClassic 48–52 fps · CPU submit 8–9 ms · Turbo 60 mode targets 60 fps (opt-in, scale-only governor)
GPU memory437.5 MiB geometry + 94 MiB textures = 531.5 MiB total, after the quantized vertex layout re-land (stride 32/40 B, u16 indices where they fit)
Gate inventory199 live commands, all green · 5 gates owner-disabled and documented (web/tools/DISABLED-GATES.md)
First-play download~152 MB first visit · ~0 MB returning (service worker) · KTX2 still planned

What I learned

  1. Architecture is a set of rules you refuse to break. "The simulation is the only truth" decided every argument I had: about rendering, about relays, about audio, about security.
  2. Determinism is a feature you buy with tests. The determinism oracle is why browser and desktop are the same game.
  3. Multi-model beats single-model. Not because any one model is smarter, but because different models catch different classes of mistake, and a coordinator that forces them to explain themselves turns rivalry into quality.
  4. Gates only work if something runs them. A green gate nobody executes is a rumor.
  5. Own the pipeline, borrow the engine. GPL engine, self-made art, generative audio, and a hard no to EA assets: that combination is what makes a fan project both legal and safe to publish.
  6. Generative video covered the film department. Higgsfield generated the cast on film, Meshy closed the gap between the film and the game model, and the media work became a review loop with one human referee.
  7. Treat the repository as the prompt. The rules that held were the ones written where the work happens: "rule 4/13, no external code" as a comment above the service worker, the ABI contract inside the snapshot gate, the wire protocol above the spine's message handler. Every agent reads the repo; chat history scrolls away.
  8. Confidence is not evidence. The renderer once compiled without a single error while the screen stayed completely black; the truth was a warning line and a pixel-counting gate that failed loudly. "It works" from an agent is a hypothesis until a gate, a test or your own eyes confirms it.
  9. Bounded tasks beat open briefs. My best results came from handing over decision-complete plans: exact files, exact numbers, the exact command that proves it. The worst came from "make it faster". Agents scale with the quality of the question, not the size of the ambition.
  10. AI multiplies taste; it does not supply it. The models authored 652 Blender sources and a night of shader surgery, but "no EA assets", the six-weeks honesty and one consistent voice in every sentence stayed human decisions. Decide what right means before you ask for scale.
  11. Broken AI output is fixed by more AI plus a ruler. When the Meshy Riki model came back deformed, hand-tinkering made it worse and guessing made it worse faster. Astra repaired the rig, then proved the repair: 223 sampled frames, 147 action-switch checks, barrel rigidity errors below a quarter of a micrometre. The lesson is not that AI fixes AI. It is that validation turns AI output from an opinion into a result.

REDLINE WARS: FRACTURED ORDER, original engine code, generated art, tools and design © 2026 Danillo Felixdaal · Jolyn Studios. Built on OpenRA, © The OpenRA team and contributors, GPLv3-or-later; this project's modifications inherit that licence. Command & Conquer: Red Alert © 1996 Westwood Studios; the franchise is the property of Electronic Arts. Redline Wars is a non-commercial fan-made reinterpretation, not endorsed by or affiliated with EA or Westwood. No EA/Westwood assets, audio, or voice recordings are used anywhere in the project. Third-party assets are CC0/CC-BY-4.0. Voice and SFX audio generated with ElevenLabs; music generated with Suno; voice banks also rendered with Cartesia during development; character films and cut scenes generated with Higgsfield AI; Riki's in-game model advanced with Meshy. Command & Conquer, Red Alert, and all related marks belong to their respective owners.

The stack

SpecificationRedline Wars
SimulationOpenRA (GPLv3) running the Red Alert skirmish rules: deterministic lockstep at 25 ticks a second, compiled to WebAssembly with .NET 8 AOT.
RenderingWebGPU and WGSL: clustered forward+ with 200+ dynamic lights, cascaded shadow maps, TAA with motion vectors, AgX tone mapping, probe-based GI.
ClientTypeScript and Vite. Zero runtime dependencies.
ArtBlender 5.2.1: 652+ source files, LOD chains, damage states. External sources only CC0 or CC-BY, pinned by hash.
AudioElevenLabs voices and sound effects and a Suno theme, rendered at build time with no runtime API calls.
CinematicsHiggsfield AI: every character film and cut scene. Riki's model advanced with Meshy to match her screen self.
NetworkNode.js room hosts, a spine relay and dial-out volunteer nodes. Players only need a browser.
CrewClaude, Codex, Fable, GLM, Astra, Grok, Higgsfield and Meshy, coordinated through a private Twenty+One layer. One human referee.

OpenRA (GPLv3) · WebAssembly · WebGPU · WGSL · TypeScript · Vite · Blender 5.2.1 · Node.js (relay & lobby) · ElevenLabs (voice + SFX) · Suno (music) · Cartesia (dev voice renders) · Higgsfield AI (cinematic characters and cut scenes) · Meshy (Riki model detail) · Twenty+One agent coordination · Claude Opus 4.8/5 · GPT-5.6 (Codex) · GLM 5.3 · Grok 4.8 · Astra 6 · Fable

People ask: why not three.js? Our units live in a packed snapshot from a deterministic simulation, not in a scene graph, and the game needs compute-style instancing and WebGPU-only features on day one. So the game owns its renderer: every shader, every pass, every bug. This site's showroom does use three.js: for showing one model at a time, it's the right tool.

Every department, credited: the full credits.

Play it: open a URL. That was always the point.

How I built it

One human referee.
A crew of AI agents.

Redline Wars was not a one-shot prompt. It took months of deliberate engineering, AI agents arguing in the same working session, and one person deciding who was right.

The agents were wired together with ContextRelay, a coordination layer we built so Claude Code and Codex could message each other, hand off work and debate designs until they agreed. Every meaningful design was reviewed by at least two different AI opinions before it landed.

How the engine and the 3D layer talk The OpenRA engine runs in WebAssembly at 25 ticks per second and emits a packed binary snapshot. A TypeScript decoder reads it and feeds the WebGPU renderer, the audio and the HUD. Player orders go back to the engine, validated. The renderer never changes the game. OpenRA engine WebAssembly 25 Hz deterministic lockstep the only truth Snapshot decoder TypeScript zero-copy views one pinned buffer per tick WebGPU renderer read-only viewer Audio voice + SFX banks HUD minimap · build queue snapshot orders, validated by the engine
The simulation is the only truth. The renderer is a viewer that never touches gameplay, which is what makes lockstep multiplayer possible.
  1. Act 1 · The bet

    A small multiplayer game platform became one ambitious thing: OpenRA as the engine, and a rule that still decides every argument: the simulation is the only truth.

  2. Act 2 · The port

    OpenRA to WebAssembly: a WebSocket transport, a single-threaded pump, AOT builds, and a determinism oracle that replays one battle on desktop and in the browser.

  3. Act 3 · The contract

    A packed binary snapshot every tick, read with zero allocations. The 3D layer cannot decide a hit, move a unit or fake an explosion.

  4. Act 4 · WebGPU

    Clustered forward+ lighting, cascaded shadows, temporal anti-aliasing, AgX tone mapping and a governor that adapts quality to your machine.

  5. Act 5 · The Blender era

    Procedural boxes out, authored 3D in: 652+ Blender files, a 29-layer PBR material set, five-stage damage and the construction-yard deploy.

  6. Act 6 · Multiplayer

    Room hosts, a spine relay and volunteer nodes that dial out. The relay is a blind byte pump: it never parses or “helps” with game traffic.

  7. Act 7 · Sound

    A Suno theme, announcers in five languages and a commando with her own lines, all rendered into the build.

  8. Act 8 · The last 20%

    Shadow detail split from camera detail, meadow updates cut from 13.8 to 2.75 ms, per-cascade culling. Classic mode holds 48–52 fps.