← All projects

Evocatio

In development

You're a conqueror god outbidding a city's incumbent deity. You govern an occupied city, confidently, and wrongly.

SimulationStrategyRustSystems

At a glance

  • Outcome: You are a conqueror god outbidding a city’s incumbent deity, governing an occupied city through a season or several. The comedy is being confident and wrong.
  • Status: In development and still not playable, because there are no player commands yet. What exists is a simulation you can look at: it founds a city from a drawn god, runs its year, and opens a window on the result.
  • Role: Solo. The design, the contract it’s built against, and the method for working on it.
  • Stack & libraries: A Rust workspace of eleven crates against roughly 35,000 lines of design documents, with a Nix shell pinning the toolchain and a custom lint tool enforcing the crate boundaries. The previous implementation was four times the code.
  • Validation: A determinism gate at every day boundary, four lints running as a required test, a content validator that stood before any content did, and a document checker that verifies what the design says about itself.
  • Limitations: No player commands and no campaign. Nothing in the city can die, so a city under strain can only starve indefinitely, never shrink. The paper prototype that would test whether a person can tell the diagnoses apart is still unowned, and its one run printed the answer on the kit.

A city is an argument

The design’s own sentence for it: a city is an argument, the god is the thesis, and the city is the evidence, and neither is stated outright.

So what the player actually does is infer. Watch a system behave, guess what it’s expressing, test the guess with an order, get corrected. That makes legibility a hard requirement and predictability a defect. A city whose behavior you can derive from a formula has stopped being a puzzle and become arithmetic.

Every rule below comes from that one. A surprise is only worth having if you can afterward trace exactly why.

What a god is

The bet the whole design rests on is that a city is generated from its god instead of authored around one. That moves the authoring problem rather than removing it. The generator becomes the thing being authored, and how expressive it is decides whether two cities are two arguments or one argument told twice.

So the answer is to author one level up. What gets written isn’t a god but an aspect. The Ploughman, the Protectress, the Wellmother. Each leans a certain way, cares about certain things, demands something particular of a rite, and permits some ways of being reached and not others. A god is then a draw: a few aspects, one of them dominant, a fixed budget spent according to how they lean, and a reach taken from the overlap of what they all allow. Sixteen aspects are written.

Authoring whole gods would make variety grow with author-hours, so your fourth campaign meets a god you’ve already argued with. Authoring aspects makes it combinatorial. The cost is stated in the same place instead of discovered later: no god is composed, so its name and omens are assembled from parts, and an authored god would have had a voice that an assembled one has to work for.

In the generator that order is load-bearing, not rhetorical. The god is drawn first. The land is then forced to include the ground that the god’s rites actually need. Only then is the city founded on both. A city can never be given ground its god has no use for, because the ground is drawn after the god and against it.

The founding rules that would otherwise be enforced by drawing again until one passes are satisfied by construction instead. The generator refuses to retry until valid, on the grounds that retrying hides the failure rate, and the failure rate is the number you most want while tuning a generator.

The rebuild

The previous implementation reached four times this much code across four crates whose boundaries followed binary targets instead of concerns. That left every ownership rule as prose in a markdown file instead of a compile error, and its final forty commits turned out to be one architectural problem wearing forty disguises. A derived quantity had no single owner, so quantities kept getting defined twice and quietly disagreeing.

In August 2026 I started over at an orphan commit. Nothing carried forward as code. The old tree is reference only, so systems get re-read and rewritten instead of ported.

The rule the rebuild exists to enforce is that every quantity has exactly one owner, and what enforces it is the dependency direction. One crate holds mutable facts and the record of what happened. Another holds the pure functions that read them and computes nothing back into the world. Because the first can’t depend on the second, a derived value literally can’t be stored on the world; the type isn’t in scope. A store’s capacity is a function of the ground it stands on, not a second field that can drift away from it.

The rule continues past the simulation. A presentation crate forwards finished readings to the window and does no arithmetic of its own, and a lint refuses any frontend file that imports the compute layer, so the interface can’t reach the raw state it would need to invent a competing number. The lint’s own comment names its limit: it can’t express “do not compute”, only “do not import the types you’d compute from”, which is the enforceable half.

Design as the deliverable

The tree holds the argument in six numbered volumes, an enforced simulation contract, derived indexes, and a written method for how the design gets worked on. A register of numbered rows is the design’s own inbox, holding every question the document raised and answering it in the section that owns it. The rows outgrew their original home, so they now live in the volume that owns them, and a checker rebuilds the register from those volumes. A row lost or duplicated in a move fails the build.

This is the part I’d point at. Running the city to the end of its own year and reading the last day is now a numbered move in that method, and it earned that place twice before it was written down.

The hand-authored demonstration city claimed in its own comment to hold its year, and ended the year with the fields producing no grain at all. The cause wasn’t a bug. One section owned the price of resting ground and another owned what a rested day returns, and nothing owned which tile a household works today, so every tile was worked every day forever and both rules were unreachable. The method calls that the largest finding the project has made about itself.

The second was a generated city that passed every founding check and then starved at the counter, with nearly all of its final day’s refusals being households out of walking range.

The signature is consistent. A claim in a comment or an invariant, a test checking something nearby but different, and exhaustion arriving as a slope instead of a cliff, so every short run passes.

What’s built

  • Six simulation crates with the ownership direction enforced by the compiler, and lints that walk them plus presentation and generation.
  • A determinism gate comparing the whole world at every day boundary instead of at the end, so a run that diverges and later reconverges still fails. The other half of the contract, that a refused command changes nothing, is written down and not built, because there’s no command to refuse yet.
  • A measurement harness writing profiles to a tracked directory.
  • A content validator that stood before any content did, and caught what it was built to catch on the very first city authored against it: the counter’s own staff couldn’t legally queue at the counter they served.
  • A document checker that verifies marker counts, register contiguity, citation resolution, and that a counted noun reads the same everywhere. It runs in under a second, before every commit.

What a long run can’t show yet

The most useful thing a twenty-year run has told me is about the model, not the city. A founded city doesn’t collapse and doesn’t cycle. It floors, settling within a few years and holding there with every household short of fuel.

The trajectory is stable because nothing can die. Mortality isn’t built, neither are inflows, and building condition isn’t state anywhere in the simulation. Until one loop closes from want back to population, shrinking isn’t something the model can express. Only starving indefinitely.

The six-hundred-year horizon the design wants is, for now, a claim about a city that cannot change size.