The One Architecture Decision That Made My Roguelike Possible
A pure deterministic seeded simulation with no node dependencies turned a roguelike into something testable, with daily challenges and shareable links and no backend.
There is one decision in World 11 that everything else hangs off. If I had gotten it wrong, half the features that make the game feel finished would have been impossible, or at least so painful that I would have skipped them.
The decision was to make the match and gauntlet simulation a pure, seeded function with no dependency on the Godot scene tree. No nodes, no signals, no global state. You hand it a draft and a seed, it hands you back a full tournament result. That is the whole contract.
If you want the broader tour of what World 11 is, I wrote that up in the overview post. This one is about the spine.
What "Pure" Actually Means Here
In World 11 the file that runs the show is scripts/sim.gd. It does not reach out to any node. It does not read the clock, it does not touch a singleton, it does not pull anything from the running scene. Everything it needs arrives as an argument. Your eleven players, your formation, your captain, and a single integer seed.
From that seed it produces every random number it will ever use through a seeded generator. The same seed walks the same path every single time. Six qualifiers, four group matches, then the knockouts, all the way to the final or to the run that ends early. Same inputs and same seed, identical result, on any machine, forever.
That is the property that makes everything downstream cheap. The simulation is a function. Input goes in one end, a tournament comes out the other, and nothing in the middle leaks.
I keep two other files just as clean for the same reason. ratings.gd is pure rating math, no nodes, no side effects, just numbers in and numbers out. content_db.gd loads the squad data and buckets opponents into strength tiers, and once it has loaded, the buckets are just data the sim reads. The sim never asks a node a question. It only reads what it was handed.
Why That Makes The Whole Thing Testable
The first thing purity buys you is that you can test the game without a player.
A normal game loop is hard to test because it is tangled up with the engine. You have to spin up a scene, drive input, wait for frames, and read pixels back out. A pure function does not need any of that. I can construct a draft in code, hand it to sim.gd with a fixed seed, and assert on the result. No window, no rendering, no waiting.
That is what lets me run the game headless. I can put a thousand drafted teams through the full gauntlet in the time it takes a coffee to go cold, and read back exactly how each run ended. Most of my confidence in the balance comes from runs no human ever watched, because the simulation does not care whether anyone is looking. It just needs the inputs and the seed.
The same property catches regressions. If I change the rating math or the goal model and a known seed suddenly produces a different champion, I know immediately, because determinism means a diff in output is always a real change, never noise.
The Daily Challenge Is Just Today's Date
A daily challenge usually implies a server. Something has to decide what today's puzzle is, store it, and hand the same one to everybody. That is infrastructure, and infrastructure is the thing a solo developer most wants to avoid.
In World 11 the daily challenge is one line of thinking. The seed equals today's date. That is it. Every player who opens the game on the same day derives the same seed, which means the same nations spin, the same squads appear, the same draft pool, and the same opponents down the gauntlet. There is no service deciding anything. The date is the source of truth, and the date is already on the device.
I did not build a daily challenge feature so much as I noticed I already had one. Because the simulation is deterministic, "everyone gets the same run today" is free. The seed is a date, the date is shared by the calendar, and the rest falls out of the function.
Shareable Links With No Backend
The same trick gives you challenge links.
When you finish a run you can share it. The link carries the seed. Someone opens it on their own machine and gets the exact same draft pool and the exact same tournament road you faced. They are not watching a recording of your run, and there is no saved game being fetched from anywhere. Their copy of the game replays the identical sequence locally, because the same seed through the same pure function always lands in the same place.
There is no server in that loop. No upload, no database row, no match between a stored result and a request. The link is the seed, the seed is the input, and the deterministic sim does the rest on whatever device opens it. A friend can take the run you bragged about and try to beat your result on the very same draw.
If the simulation pulled even one value from somewhere it was not handed, a system clock, a random source the engine seeded for me, a node that happened to exist on my machine but not theirs, all of this would quietly break. The shared link would diverge, the daily challenge would not match across players, and the headless tests would stop being trustworthy. The whole structure rests on purity. Take it away and everything above it falls.
The Lesson I Keep Relearning
The temptation as a solo developer is to wire things together fast, because wiring feels like progress. Let the match code reach into the scene, grab the player nodes, read whatever it needs in the moment. It works the first day and it costs you every day after.
Drawing a hard line, that the simulation receives everything and reaches for nothing, felt slower at the start. It paid for itself the first time I ran the headless balance pass, then again when the daily challenge turned out to already exist, then again when challenge links worked on the first try with no server to stand up. One boundary, drawn once, and a whole category of features stopped being work and started being consequences.
If you build a roguelike, or anything with a simulation at its core, push the sim into a pure seeded function early, while it is still small enough to move. You are not just making it testable. You are making a long list of features cheap before you have even thought of them.
World 11 is free to play in your browser, and there is a feedback button right on the page if you want to tell me where the balance feels off. Play it at games.kevingabeci.com/world-11, spin a draft, and share the link with someone who thinks they can do better.
Like this? You'll like what I'm building too.
Two ways to support and get more of this work.
HEARTH
A privacy-first Life OS for your desktop. Journal, tasks, and notes that stay on your machine. Coming soon, direct download from this site.
Read moreMY TOOLKITS
Receipts-first toolkits for shipping after hours, building Claude agents, publishing on Amazon, and more. The exact methods I used, not theory.
Browse on WhopRelated Articles
How I Built A Game About Real Players Without A Licensing Nightmare
Facts are not copyrightable. Here is the practical line a solo developer can walk to ship a game using real names, positions, and years, with freely licensed faces.
I Treat Claude Code Like a Small Dev Team
Agentic AI coding gives real leverage only when you decompose work, isolate it, anchor it with durable context, and verify the output. Here is how I direct it.
Published In The Database Is Not Live On The Internet
I built a small content registry that scans every blog and verifies each post is actually reachable and in the sitemap, not just marked published in a row somewhere.