← All entries

Entry 40 of 40

The stack

What the game is made of: one language for the world, one for the screen, a contract generated between them, and a practice town that is the same world in your browser.

TechWorld

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.

Two cards. Left, Go, the world: a street full of zombies, with the city loop, the rules, the zombies, the city generator, loot and names. Right, TypeScript, the screen: the same street in the game with its panels, built with React, PixiJS, Vite and Tailwind. Between them, what you can see and your meters go to the screen and your taps come back, over one contract. Below, Astro for this site, WebAssembly for the practice town and Expo for the phone app

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.

One frame of the game pulled apart into layers. At the bottom the city drawn by PixiJS, then the survivors and zombies, then a gunshot ring, fog and night, and on top the React panels: meters, panels and the log

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 browser windows. Left, a real city at night, wired to a box below it: on a server, the world shared with everyone in the city. Right, the practice town by day, with a box inside the window: the same world, compiled to run in your browser

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.