The June entry on how the world runs said what the loop is. It never said what it is written in, so this entry names the tools.
Two languages, one contract
The world is Go. The city loop, the rules, the zombies, the city generator, the loot tables, the name filter, and the program that starts the cities. I picked Go because a loop that wakes up twice a second, does a bounded amount of work and goes back to sleep is exactly what Go is comfortable with. One process, one goroutine that owns the world, a channel of taps in front of it. No framework, no database in the loop.
The screen is TypeScript. The two halves talk over one WebSocket per player, in JSON, and what they say is one contract: every message, every item, every building type, every rule. The contract is written once, in Go, and generated into TypeScript types. Nobody edits the generated file. If the generated file is out of date, or the world changes a field the screen uses, the build fails and nothing ships.

Bulky things don’t go as JSON. The tiles your eyes reached, or the grid of building ids under your viewport, travel as packed bytes and are unpacked on your side. A few hundred cells is a handful of bytes that way, and a few kilobytes the other way, each time it is sent.
The screen
The game is a web page, built with Vite, and it is two things stacked. Underneath is a canvas drawn by PixiJS, on the graphics chip. That is the map: tiles, then interiors, then survivors and zombies, then sound rings and hits, then fog and night, then your waypoint, each its own layer. A map of a few thousand tiles with fog and a few dozen animated sprites is a drawing job, and a web page is bad at drawing jobs.
On top of the canvas is React: the meters, the tap menu, the log, the bag, the guide, the minimap, character creation, the lobby. A web page is very good at those. It’s styled with Tailwind, and the one rule I hold hardest is the one from the phone entry: the map is never covered, so every panel is the same bottom sheet over a live map.

A few smaller choices. Every interface icon is one drawn set, no emoji, because emoji look different on every phone. Colours come from one shared file that this site uses too, so the game and the site are the same dark. The page installs as an app from the browser, so it has its own icon and no address bar, and when a new build lands mid-game it waits and asks instead of reloading under you. This site is Astro, plain static pages.
The phone app is React Native, built with Expo. It exists so the city has somewhere to send a notification while you are away. The parts a phone does better are native: the intro, the sign-in, the list of cities with your survivor in each, and the notification settings. The game itself is not rebuilt. When you enter a city, the app opens the same game page in a web view, loaded from the server instead of packed into the app, so a change to the contract never waits for an App Store release. Even the survivor standing in the native lobby is drawn by the web game, in a small web view of its own.
The practice town is the same world
The practice town from August needed the real rules with no server. Two rulebooks would drift. So the practice town is the Go world, compiled to WebAssembly, running in a background thread in your browser. The screen sends a tap and gets a message back, the same bytes it would get from a socket, and it can’t tell the difference.

Two rulebooks was the thing I refused, and the practice town is the proof it could be refused. What I don’t know is what that costs on a cheap phone. The world compiled to WebAssembly is not small, and a background thread on a four-year-old Android is not a server. It runs fine on the phones I own. I own good phones.