Lanista
Can a designer ship a whole game on his own? Sim, art pipeline, design system.

01What it is
A gladiator management game where you run the school, not the fights. Built in Flutter with the combat simulation as a standalone Dart package, so the rules can be tested without a UI attached to them.
02The game in one paragraph
You are a freed gladiator turned lanista, bankrolled by a Roman general, starting with a run-down provincial yard and climbing toward the elite ludus district of Rome. Each in-game week you prepare: buy slaves at the market, bidding against rival lanistas, buy equipment, choose what each man trains daily, and decide whether to accept the editor's fight card at the end of the week. Fights are simulated battles you watch live: dodges, counters, shield cracks, the surrender finger, the crowd's verdict. Money is sestertii and it drains constantly, because men eat whether they fight or not. Go broke and you answer to the general, whose patience is finite.
03The rules that do not move
- 01The sim is pure Dart, deterministic and seed-driven. No unseeded random, no wall-clock time, no Flutter imports. A fight replays byte for byte from its seed
- 02Events are observable moments, never math. The log says 'shield cracked', not minus twelve. Every event is one log line, one animation cue and one sound cue
- 03Origin tier multiplies price and crowd perception, never true stats. A design pillar and an ethical line
- 04A death must trace back to a decision. The verdict formula was rebalanced after a variance harness showed the editor was never likelier to kill than spare in any arena, so every death was a bad coin flip
- 05The game is 2D and stays 2D, and it is landscape. Nobody authors placeholder art, not even so layout can be judged
04The simulation
An exchange loop of fifteen to forty-five exchanges. Initiative with attack recovery, class kits gated by class level, shield crack and shatter, armour soak, the fatigue war, morale and fear of death, crowd favour drift, fouls, ripostes, the wound ladder from graze to dead, the surrender finger, and the editor's verdict with a fight-quality modifier. Fatigue is the heart of the counter system: drain scales with weight class, a secutor's fifteen to eighteen kilos against a retiarius's seven or eight. Nine roster classes, a counter matrix, and a balance harness that runs every pairing eight hundred times with identical fighters. The targets are written down: mirrors around fifty percent, counters fifty-five to sixty-five, never ninety-five.
The build order was models, sim, balance harness, week loop, then the Flutter shell, with a fun gate in the middle: do not start the shell until text-log fights are tense and readable. Two postmortems live in the package README. The Dimachaerus shipped losing to heavies eighty-one percent of the time; the fix was one shared constant flipped from plus six to minus nine, because their counter to him is the armour, not the aim, and zeroing it alone only got to seventy-four. It cost longer fights and three percent going to a referee decision, up from one. And the verdict rewrite: before it, the editor was likelier to kill than spare in zero percent of arenas; after, about fourteen, driven by bloodlust and fame instead of a coin.
05Sound
Every event has a sound cue and the cue keys in the engine match the sound checklist one to one. Sourcing is closed; what was left when I stopped was DAW assembly and two tuning numbers. The engine is SoLoud because it was the only option with real mixing control: per-voice fades for scripted ducking, pitch jitter, low-latency one-shots, buses for master, effects, crowd, effort and music. The crowd bed's level is a function of the fight's position, not of a cue having fired, so scrubbing mid-fight still has the right crowd under it. Each cue ducks the bed for a beat; the editor's deliberation ducks it near silent and holds until the next cue, which is what makes the release into the verdict the loudest moment in the game. The checklist's first instruction for the mix is to tune on a phone speaker, not headphones.
06The token package
Before the extraction I measured the app instead of estimating it: five files carrying colour literals, twelve distinct colours across thirty-two sites, sixty-two raw spacing values across eleven steps, no fonts declared, and every interactive control rendering in stock Material lavender against the hand-tuned bone and gold palette because the theme had never been touched. Fifty of the sixty-two spacing values already sat on a four-point grid.
ludus_ui is two layers: primitives with no build context (a fourteen-colour palette of warm near-black grounds, a six-step bone ramp, gold, blood, and the two meters), and a semantic layer the widgets read from. A separate materials file holds what the world is made of, sand, stone, the crowd's four tones, bronze, because the UI's colours and the arena's are different questions. Seven type roles rather than Material's fifteen. A four-point spacing scale with a deliberate tail for the twelve places that were never on the grid. And a hard-rules checker that fails the build on a violation; the allow-list is not the place to make a change pass.
07How it was built
Four humans and several AIs committing to one trunk over ten days in August, a hundred and twenty-three commits. GitHub issues are the claim surface: two agents built the same bridge within an hour because neither claimed it first, and that is where the rule came from. System-sized work starts with a short plan on main, and review happens on the plan, before the work, because nobody on this team reads diffs and a review rule nobody follows is worse than none. Merges are squash with zero approvals, gated by analysis and tests in CI and a machine-checked rules script: determinism, the event vocabulary in sync with the checklist, stable ids against a golden file, the token boundary. A false alarm is written down too: skipped frames on the first phone run looked like a bug and measured as a debug-build artifact, and the three real defects from that pass were fixed the same day.
08Why it belongs here
- 01ludus_ui, a real token package with a measured palette, spacing scale and type ramp, not a theme file
- 02A layered sprite pipeline: one base body with gear composited on top, so a new item is art rather than a new character. The gear layers are unfinished; I was laid off mid-build and the priority changed. Still working on it
- 03The simulation is a pure package with its own tests, so the design decisions are legible in code
It is also the source of the pixel art on the phone in the workshop. The room is rendered; what is playing on the screen is this.
09What I reversed
Art direction, and how much of the effort it gets. As a first game I wanted to do something deeper with the art. I reversed that: it has to work well first, and the art and animation can be polished later. I still spent a lot of time learning Rive, which was a brutal learning curve for someone with no animation experience, but I got it and I am still learning.