Alejandro Ashes
AI Alchemy ~13 min

The Architecture Was the Easy Part: A Build Log of Cosmic Grimdark

I shipped a browser roguelite set in my own novels, and the part I expected to be hard, the engine, took a week. The part that became the actual product was the gates: a literary judge and a mechanical one, both of which can fail the build and turn the lights red before anyone plays.

The Architecture Was the Easy Part: A Build Log of Cosmic Grimdark

Two earlier posts mapped the theory. The game-master piece made one argument I still stand behind: frontier tokens are an authoring cost and never a per-player runtime cost, so the model is spent in the workshop and never sits in the loop. The toolchain piece made another: pick the form first, because a novel is raw material for four different games and each points at a different stack. Both were written before there was anything to play. This post is written after. The game is live, you can play it in a browser at alejandroashes.com/game, and what I want to record here is the gap between what I expected to be hard and what actually was.

I expected the engine to be the hard part, and it came together in a week. A 54-card combat loop with a combo chain, a force cycle, two attrition tracks, and a doom clock is a known shape, and the model and I had a deterministic version of it running fast. Draw a hand, chain the cards along the cosmology, fire a Boon when you hit harmony, watch the Eldritch meter climb toward the thing at the end. That part behaved. That part is the part everyone thinks is the project.

The project turned out to be the gates. Two of them. One reads words and one plays the game, and either one can fail a build and turn CI red before a single human looks at the screen. Everything I learned worth writing down is about those two adversaries, so the rest of this is mostly about them.

The Lens Had to Gate the Game, Not Just the Prose

I built an evaluation instrument last year for my own fiction, the Grimdark Lens: named failure modes, paired anchors, a binary error-code pass that two readers can run and agree on. The narration apologizes for the viewpoint character. The medieval mercenary says “trauma.” The wound stops mattering by the next scene. I built it to grade AI-drafted prose, and I assumed the game lived somewhere else, a separate domain with separate problems.

That assumption broke the first time I read my own card flavor in a slot.

Here is the thing nobody warns you about. The model writes the game’s words. Foe names, card flavor, the one-line beat that fires when an encounter resolves, the description on a relic you pick up off the floor. All of it is generated text in the voice of my world, and all of it fails the exact same ways my prose fails. A foe arrived named “The Sorrow That Remembers.” That is a Lens failure with a name on it, the narration reaching for a feeling instead of earning it. A relic described itself as a “tapestry of suffering,” which is a banned word in my house style for a reason, and the model produced it anyway because the model does not read my style guide unless something makes it. An encounter beat had a cultist “processing his grief,” therapeutic vocabulary intrusion, the same error code that catches it in a chapter.

So the Lens had to gate the game. The same instrument, pointed at a new corpus. npm run game:test runs the engine assertions and the Lens together, and the Lens reads every foe name, every card flavor string, every encounter line in the baked content and fails the build on a register violation, with the offending line quoted, exactly as it does for a manuscript.

What surprised me is that short card text exposes voice failures faster than a novel does. A bad line buried on page 240 of a manuscript gets read once. A bad line on a card the player draws gets reread every single run, twenty times an hour, until it stops sounding like writing and starts sounding like a tell. The compression is the test. You cannot hide a weak verb in nine words the way you can hide it in nine hundred. The game became, accidentally, the most demanding place my voice gets evaluated, because the player rereads the smallest units the most often and the Lens reads them every push.

The Standout Lesson: A Greedy Bot Is the Lens for Mechanics

The Lens protects the words. Nothing in it has any opinion about whether the game is any good. A foe can have a flawless on-canon name and a brutally on-voice death line and be mathematically broken, a damage check the player auto-loses or a combo that trivializes the act. Generated content needs a literary judge. It also needs a mechanical one, and that is the piece I am proudest of, because it is the piece I did not see coming.

The mechanical judge is a bot, and it runs on no model at all. About two hundred lines of deterministic code that plays the game greedily, a headless simulation that picks, every turn, the move with the best immediate value and never plans, never reads intent, never shows mercy. It runs the full Delve, thousands of times, on every push. npm run game:balance. And the rule is a band: the greedy bot’s win-rate has to land between 35 and 65 percent. Below 35, the content I just generated is unfair, a wall a real player cannot pass. Above 65, it is trivial, a loop with no teeth. Either way CI goes red and the build does not ship.

I have started thinking of this as the Mechanical Lens, the same shape of instrument as the literary one, aimed at a different failure surface. The Grimdark Lens is a non-negotiable adversary that auto-fails generated prose on a register violation. The greedy bot is a non-negotiable adversary that auto-fails generated mechanics on a balance violation. Both are deterministic. Both run on every push. Both quote the failure. Neither has taste, and that is the whole point, because taste does not scale and a binary band does.

The reason a bot works as a balance judge is the same reason binary error codes work as a prose judge. You are not asking a hard, subjective question. You are asking a narrow one with a clear answer. “Is this fun” is unanswerable by a machine. “Does a player who always takes the greedy line win between 35 and 65 percent of the time” is a number, and a number can fail a build. The greedy line matters specifically because it is the dumb line, the one a new player stumbles into. If the dumbest reasonable strategy wins 80 percent of runs, there is no game, there is a slot machine that always pays. If it wins 12 percent, the game is punishing players for not yet knowing the combo system. The band is where a stupid strategy loses more than it wins but a smart one can climb, and that is the definition of a roguelite that respects the player.

There is a second half to the test that took me a while to get right. The band alone is not enough, because a bot can hit 50 percent on a game that is pure noise, where skill does nothing and the coin decides everything. So the balance run also plays a few control strategies, a bot that plays randomly, a bot that ignores the combo chain entirely, and it asserts that those control bots do clearly worse than the greedy one. That is the proof that the chain matters, that sequencing along the cosmology is a real skill and not decoration. Greedy beats random, smart beats greedy, and the gap between them is the room where mastery lives. If random ever catches up to greedy, the game has stopped being a game and the build fails for that too.

This is the lesson I would hand any author generating mechanics with a model. The model will happily produce a foe, a relic, a whole act, and it will all read beautifully and be silently unplayable, because nothing in a language model has any idea whether the numbers compose. Generated mechanics need a mechanical judge. Build the cheapest possible adversary that plays your game and fails your build, and put it in CI before you put in a single boss.

The Source of Truth, Under Many Hands

The Lens and the bot are useless if the thing they are reading is not the thing that ships. This is where multi-agent authoring gets dangerous, and where the bake pipeline earns its place.

The rule is plain. The source of truth is typed data, the src/data/*.ts files: cards, encounters, relics, the meta layer, the palette. A build step compiles all of it, plus the art, into one generated artifact the engine reads, content.js. You edit the typed source and you bake. You never hand-edit the generated file, and the generated file carries a “GENERATED, DO NOT EDIT” banner across the top so nobody, human or agent, mistakes it for source.

I learned why this matters the way everyone learns it, by breaking it once. When several agents are authoring in parallel, foes from one, relics from another, a balance pass from a third, the generated artifact is the obvious place to make a quick fix, because it is the file the game actually loads. Every quick fix there is a change that the next bake silently erases, and a change the Lens and the bot never saw because they read from the source. The banner is doing real work. It stands between you and a class of bug where the game that ships is not the game that passed its gates. One direction of edit, source to artifact, never the other. The compile is the contract.

Grounding the World in Eight Books

A card game set in my novels has an advantage a generic roguelite does not, which is that the content can be true. The foes and the relics come from somewhere. They are mined from the eight source novels, roughly 580,000 words of manuscript stored as chaptered JSON, by parallel agents whose only job is to find candidates and bring back the quote that justifies each one.

The discipline that makes this work is the citation. An agent does not get to propose a foe because it sounds cool. It proposes a foe and attaches the passage from the book where that thing appears, the chapter, the line. The Void Wraith that hits your Stress track instead of your health is grounded in the violet flame’s documented hunger for despair. Emperor Ravain, the vampire lord sitting in the King of Spades slot, is grounded in the line where he appears. The quote is the leash. It keeps the generated layer tethered to the canon graph instead of drifting into plausible-sounding invention, which is the failure mode every author fears when a model touches their world.

Then the candidates get checked adversarially, by a different pass that tries to break them, that asks whether the citation actually supports the claim or whether the agent reached. Mining and checking are separate jobs done by separate agents on purpose, the same separation the calibration work taught me, because the pass that wants to find content and the pass that wants to reject it should never be the same pass. What survives both is on-canon by evidence, not by vibe.

Art as a Gated Pipeline

The art runs the same way: generate freely, gate hard. Every card and foe has bespoke art now, well over a hundred files, and none of it reaches a slot without clearing a check.

The portraits come out of Midjourney, because the painterly dark-fantasy look I want lives behind a style profile I trained, and a locked profile string on every prompt is what keeps a vampire lord and a void wraith and a crystal relic looking like they belong in the same game instead of three different games. Consistency is the entire battle. Fidelity is cheap now and consistency is the thing that reads as a real product, so the profile lock does the work a hand on the tiller used to do.

Generation is the loose half. The gate is a Visual Lens, a check that reads each image for the things that can go wrong mechanically: the wrong aspect ratio, baked-in text the card layout will fight, and most of all a hue outside the force’s band, because a Flame card that came back cold or a Void portrait that drifted warm breaks the read of the whole board. The same shape again. A loose creative front end, a strict deterministic gate, a clear reason code when something fails so I know whether to regenerate or to adjust. The art pipeline is the literary pipeline and the balance pipeline wearing a different coat. Make it freely, judge it narrowly, fail it loudly.

No Backend, and the Save That Stands In for an Account

One more constraint shaped more than I expected. There is no server. The game is static files on the same site that serves the fiction, which is the only way the economics from the game-master post hold, a play loop that costs the same for one player as for a million because it never calls anything. That means progression has nowhere to live but the browser. The Guildhall, the Gold and Souls and levels you carry between Delves, all of it is localStorage, and localStorage is a promise written in sand. A cleared cache wipes it. A new device starts over.

An account would fix that and an account needs a backend, which is the thing I refuse to run. So the stand-in is a Vault: a panel that lets a player download a backup file or copy a save code, plain text, no account, nothing uploaded, and restore from either on any device. Behind it sits a quiet requestPersist() call that asks the browser to mark the storage durable so a routine cache sweep is less likely to take it. It is humbler than an account and it keeps the no-backend promise intact, a save the player owns as a file instead of a row in a database I would have to maintain. The durability is the player’s, held in their hand, which is the right place for it in a thing built to run for free forever.

The Gates Are the Product

So here is the build log compressed to one line. The architecture was the easy part. A deterministic card engine is a known quantity and a week of honest work, and if that were the whole job, the job would be small and so would the thing.

The product is the verification. A literary adversary that reads every generated word and fails the build on a voice it does not recognize. A mechanical adversary, a greedy bot with no taste and a hard band, that plays the game thousands of times a push and fails the build when a stupid strategy wins too much or too little. A compile step that makes sure the game that ships is the game that passed. A citation that keeps the world true and a hue gate that keeps it consistent. None of it is the part that looks like the game in a screenshot. All of it is the part that lets a model author a world and a working novelist still put his name on it.

If you are building something like this, spend your worry where I should have spent mine from the start. The engine will come together faster than you fear. Build the judges first. Generated content does not need a better generator. It needs an adversary that can tell it no, in a way that turns the lights red, before anyone ever plays.

✦  Ballads of Eldritch Metal
—
Alejandro Ashes
0:00 0:00