Two games, one match
One match, two completely different genres playing against each other. The fantasy: a hero power-fantasy carving through hordes, vs. an evil overlord macro-managing a living dungeon.
docs/info/gdd.md §1
Heroes play a bullet-heaven: weapons fire themselves, you kite, you hoover up XP gems, you draft cards on level-up and you snowball. You win by destroying three Power Cores.
The Dungeon Master plays an RTS from a god camera. They spend Souls — a pool that regenerates, and that every dying monster feeds — to summon packs, place reward chests over hidden traps, and edit a live wave timeline. They win by holding the cores to the timer, or by killing everyone.
The Souls rename, and why it stopped being a rename
The DM resource went through four names — Threat, Essence, Rage, and finally Souls — and only the last one changed the design. Souls are not a budget; they are what keeps you manifested. Every monster that dies releases a glowing soul that flies across the battlefield into your meter, and at zero you fade away. The implementation note is the whole philosophy in nine words:
The bar grows when the soul arrives, not before.
0.9.13 — Soul Harvest
The fiction
A developer — the Architect — writes a world in a language that reads less like code than scripture: syntax that compiles into matter. She means it as a game. It works too well. She wires a combat AI straight into her nerves to survive her own creation, and the turn the whole thing pivots on is that the AI is you.
The ten difficulty tiers are not zones. They are rings of the leak spreading outward from the seed — Rotting Fields, Sunken Bog, Thornwood, Ashen Caves, Frostreach, Emberkeep, Stormspire, Voidmarsh, Obsidian Deeps, The Undoing. Climbing them does not make things harder so much as it brings you closer to the compiler.
Three factions thread every ring: the Rot (organic, undead), the Synod (her world's own systems given bodies — a process that won't stop), and the Brood — the part of the world the Architect never wrote.
The three factions
Five bosses
Ten heroes, three maps
The architecture bet
The game is a native Godot executable, and its entire interface — menus, lobby, armory, HUD, dev console — is an HTML app drawn by a transparent OS webview sitting on top of the viewport. The two halves talk over a JSON bridge. The changelog calls it a reverse-hybrid.
That shape exists for one reason: the Steam overlay cannot hook a webview. Two earlier prototypes died proving it — a Phaser build inside Tauri, then a native hybrid in Rust. The current engine keeps the web UI and gets the overlay, because the process Steam launches is a real exe.
Two pillars and nothing between them
There are exactly two pillars and no orchestrator / ‘session’ layer in between. They communicate only through the transport — that is the whole contract.
docs/netcode.md §1
A server and a client, each ticking its own physics frame, connected by a transport that has four configurations from one codebase: single-player is a loopback with zero sockets, listen-host is the same loopback plus a port, dedicated is ENet, remote is ENet or Steam. The host stops drawing its own authoritative nodes and renders through the identical client pipeline a remote player uses — so “the host sees monsters the client doesn't” becomes impossible by construction.
The cost is a double entity: on a host, every enemy is two nodes — a hidden authoritative one and a visible dead-reckoned one. It looks redundant in solo play and it is load-bearing, because it means the solo FPS test exercises the exact multiplayer pipeline. The docs instruct future contributors to decline the optimisation, by name.
Six bytes per monster
Monster positions are not world coordinates. Each is a 12-bit signed half-pixel offset from the receiving player's own hero, with the origin carried once in the snapshot header. Three bytes, ±1024 pixels, bounded so it physically cannot drift into a ghost. Velocity is never sent — clients derive it. Off-screen enemies cost zero. A moving monster on the wire costs six bytes total.
Damage numbers came off the wire entirely: the client synthesises each one from the HP delta it already receives. The tradeoff is written down rather than discovered later — two hits between snapshots merge into one number, and crit gold is gone.
Tooling, all of it in-house
The game plays itself
A headless balance simulator with an AI hero that auto-drafts level-ups, time-compressed. It drives the real server sim, so what it measures is what the game does. Seeded, so the same (seed, skill, strategy) triple reproduces exactly and you can A/B two builds on one run. There is a bot-versus-bot mode where it plays the Dungeon Master too.
Sheets in, roster out
A browser tool that ingests heavy multi-animation sprite sheets through a folder picker, repacks them, writes the manifest, the sheet and the portrait, and appends to the roster. All image work is in-browser canvas — no native dependencies. The wire follows automatically.
Collision, by hand and by trace
A Python tool that traces and edits collision boxes and writes them straight into the Godot scenes, plus the prop, core and heal-zone data. It landed in a commit that ends “(Untested) [Feel free to test good night!]”.
75 icons, keyed and cropped
Local ComfyUI generates every weapon and amplifier icon in one cel-shaded house style on a green screen — magenta for green subjects, so the key can't eat them — auto-keyed to alpha, autocropped, written to the wired filename. Bad roll? Bump the seed offset.
Settings declared once
Every setting exists in one JSON file, where the key is the wire name and its prefix is its category. Save defaults, console cvars, UI defaults and the options-menu rows all generate from it. Same pattern for weapons, maps and heroes — each read by both the C# sim and the TypeScript UI.
A linter for one CSS unit
The UI scales by CSS zoom, which Chromium does not
apply to viewport units — so a raw 100vh breaks on
every screen that isn't the dev machine, invisibly. A
zero-dependency check wired into dev, build and lint forbids them
everywhere except the one authorised file.
Where it stands
0.9.15, shipped as a playable build: 50 weapons, 25 amplifiers, 100 monsters, 10 heroes, 3 maps, 2 game modes, co-op netcode with prediction and interpolation, the full DM role with its wave-timeline editor and soul economy, ten post-processing effects, controller support, keybind remapping, Steam and Discord, an in-game What's New panel that doubles as the roadmap.
1.0 is a grayed-out entry in that same panel: the full three-core run with real win and lose conditions, deeper combat with regen and mitigation, a DM economy that unlocks heavier monsters through a locked gallery you work toward, a new arena, and a real Steam App ID — it currently runs on 480, the shared test one.
The known netcode work is written down with its costs: per-entry ID and mask overhead, projectiles still absolute on the wire every tick, undeltaed gems, and one honest admission — every stress test to date used a single client, so the four-player upload multiplier is the big unmeasured axis.