Every Last City
An original turn-based strategy game in the tradition of Empire — you begin with one city on a fogged map, cities produce units, units explore and fight and take more cities, and nobody can talk their way out of it.
It is not a reproduction of any existing game. Mechanics and ideas are not anybody's property; specific expression is, and none is taken. Every value in this specification was designed for a stated reason rather than inherited, and where the genre has a known weakness the design is obliged to attack it rather than reproduce it.
Specification
Fifteen documents in docs/spec/, 2,793 numbered requirements. Start with the overview, then the turn model — it is the spine everything else hangs from.
| Doc | Prefix | Reqs | Scope |
|---|---|---|---|
| 00-overview.md | OV | 12 | Pillars, document map, cross-document discipline, 47-term glossary |
| 01-game-rules.md | GR | 199 | Map, terrain, movement, fog, the Intelligence Picture, Regions and Landmarks, setup |
| 02-units-and-industry.md | US | 237 | Unit schema, the Industry ladder, the roster, production |
| 03-architecture.md | AR | 123 | Deterministic core, fixed-point arithmetic, event sourcing, saves, testing |
| 04-ui-ux.md | UX | 209 | Orders authoring, Cascade playback, the Dispatch, accessibility, mobile |
| 05-multiplayer.md | MP | 159 | Correspondence, real-time, hot seat, lobbies, fog enforcement, self-hosting |
| 06-ai.md | AI | 219 | The Staff — four layers, six Ranks, honest fairness, the self-play farm |
| 07-modding-content.md | MOD | 139 | Content formats, WASM plugin ABI, editors, workshop |
| 08-services-platform.md | SVC | 126 | Guest-first accounts, sync, notifications, privacy, operations |
| 09-roadmap.md | — | — | Phased delivery plan and risk register (non-normative) |
| 10-turn-model.md | TM | 279 | Orders, the Cascade, the Reckoning — Initiative, Stance, reactions, orders-as-data |
| 11-combat.md | CB | 288 | The Clash, strength and disorder, retreat and rout, emergent encirclement |
| 12-economy.md | EC | 241 | Works and Manpower, Integration, the six visible brakes on growth |
| 13-command.md | CM | 221 | Postures, Sanctions, the order stack, Reflexes, the Dispatch |
| 14-victory.md | VC | 341 | Tracks, the Vigil, Standing and Tenure, the endgame and the comeback path |
Design record
docs/design/ holds the reasoning behind the specification — twelve papers covering the binding direction, the turn-model decision, six subsystem proposals, and four angles on the AI including a red team whose only job was predicting how the AI will fail.
These carry what usually evaporates: what was rejected, and why. 00-direction.md and
01-decision-turn-model.md are binding; the rest are the arguments that produced the specifications.
Art production
docs/art/ specifies the sprite sheets in enough detail to hand to an image generator: art direction and the tint-mask format, the 26 unit sprites with per-unit generation prompts, terrain and the city, and the overlay glyph set.
It is written for the production method it will actually face — one sprite per image rather than a sheet, a style plate fed back as an image reference for consistency, and honest yield figures (4-8 takes per sprite, 10-20 minutes of cleanup each). Text is never generated; small glyphs are vector-authored.
Code
Phase 0 of the roadmap — the determinism foundation, built before any rules code because it cannot
be retrofitted. Phase 1 has started on top of it: the turn machine (Orders / Cascade / Reckoning,
initiative, stance, reactions, the Reckoning's clocks and hashes) now lives in packages/core.
corepack pnpm install
corepack pnpm run verify
That runs typecheck, the determinism lint gate, the test suite, and the specification cross-reference checker. Other useful scripts:
| Script | What it does |
|---|---|
pnpm test:browser |
Runs the determinism suite inside a real browser engine (Chromium by default; set ELC_BROWSER for firefox or webkit) |
pnpm golden-hash |
Prints the reference log hash |
pnpm check-spec |
Proves every requirement citation in the specification resolves |
pnpm build-site |
Renders the documents to static HTML in site/ |
| Package | What it is |
|---|---|
packages/core/src/numeric |
Int and Fixed (signed Q16.16) exact arithmetic. Overflow throws; never wraps, saturates, or yields NaN. |
packages/core/src/rng |
Seeded PCG32 with SplitMix64-derived named streams. Hi/lo 32-bit math, no BigInt on the draw path. |
packages/core/src/hash |
Strict canonical encoder and 64-bit hashing — how two states are proven identical. |
packages/core/src/harness |
Replay-and-compare over a toy fixture. Not the game; the proof that the machinery works. |
packages/eslint-plugin-determinism |
Lint rules banning float arithmetic, ambient clocks and entropy, locale-sensitive comparison, and unordered iteration. |
scripts/check-spec-refs.mjs |
Proves every requirement citation in the specification resolves to something real. |
packages/core/test/reference-hash.ts |
The committed reference hash every supported platform must reproduce — the cross-platform gate. |
The CI job in .github/workflows/determinism.yml runs the suite under Node on Linux, macOS and
Windows and inside Chromium, Firefox and WebKit — the browser is the primary delivery target, so
a determinism guarantee proved only on server runtimes would have a hole exactly where the players
are. Agreement is asserted inside the suite against a committed constant rather than by diffing CI
artifacts: comparing a freshly computed hash to itself proves only that an engine is self-consistent,
which every non-deterministic engine also manages.
Contributing
The server is the only shared state — there is no other remote, and nothing local is visible to anyone else. Clone and push there:
git clone everylastcity@195.26.247.75:git/everylastcity.git
A push to main is the only way to publish anything. The hook checks the tree out to the served
directory, removes files deleted in the commit, restarts the site service, and warns if the rendered
site is older than the documents. There is no second step, and there is no other mechanism — do not
copy files to the server by scp, rsync, or any script that packs a working tree. A publish that
is not a commit is a state nobody can reproduce, review, or roll back, and the deploy hook will
delete it on the next push without asking.
The loop
git pull --rebase # someone else may have pushed
# ... work ...
corepack pnpm run verify # typecheck, determinism lint, tests, spec references
corepack pnpm run build-site # only if you changed anything under docs/
git add -A && git commit
git push # this deploys
Commit and push early, and often. Uncommitted work is invisible to the team — which is the whole
reason this project has a server — and it is the one state the tooling cannot protect. Work that
lives only in a working tree for a day is a day of work one git clean away from gone.
If pnpm verify fails, fix it before pushing. main is what deploys, so a red main is a broken
published site, and there is no staging.
Where to work
Your own machine is the normal case: clone, work, push. Everything the project needs runs on Windows, macOS and Linux, and CI proves the determinism suite agrees across all three plus three browser engines.
If you work on the server, make your own clone rather than sharing one:
git clone ~/git/everylastcity.git ~/dev/<yourname>
Everyone SSHes in as the same everylastcity user, so ~/dev/everylastcity is a single working
tree with no locking. Two people editing it at once will overwrite each other silently.
Never work in ~/apps/everylastcity. That directory is the deploy artifact, not a workspace:
every push runs git checkout -f and git clean -fd there, so anything uncommitted in it is
destroyed without warning and without an error message. The deploy hook re-stamps a
DEPLOYED_COPY_DO_NOT_EDIT.md there saying so on every deploy.
This is not a hypothetical. The Phase 1 turn machine — 5,200 lines — sat untracked in that
directory and was one push from deletion. A scripts/publish.mjs used to pack the working tree,
uncommitted files included, and extract it over ~/apps; the README told people to run it. Two
publishing mechanisms owned one directory and destroyed each other's output. The script is gone and
git push is the only path.
Domains
| Domain | Serves | Port |
|---|---|---|
docs.everylastcity.usvtg.net |
this documentation site | 49000 |
everylastcity.usvtg.net |
reserved for the game; points at the docs until the game has a server | 49000 |
The apex belongs to the game: it is what someone typing the project's name should get. Until a game server exists it points at the docs too, so the name is never dead and — more importantly — never released.
Repointing it later is one command, and it rewrites the mapping in place:
vhostctl publish everylastcity.usvtg.net <game-port>
Never use vhostctl unpublish on either domain. It does not just detach the
name, it releases it for any other account on this shared host to claim. There
is no case in normal operation that needs it: publish repoints.
Ports must stay inside the assigned range 49000–49199, and every app must
bind 127.0.0.1 only — 0.0.0.0 would expose it to every other account on the
box. The shared Caddy reverse proxy is what makes a bound port public, and it is
driven entirely by vhostctl; the Caddyfile is not ours to edit.
Working on the server
The server is a full development environment. Node 22.23.2 and pnpm 11.25.0 are installed under
~/.local/opt/node, exported from both ~/.profile and ~/.bashrc, so any shell you log into has
them. The distribution's own Node 18 is below this repo's engines floor and cannot run the site
build.
If you see v18.19.1, you are in a shell that reads no startup file — which is what
ssh host 'command' gives you, since that shell is neither a login shell nor interactive. Use
ssh host 'bash -lc "command"' for scripted access. Logging in normally needs nothing.
cd ~/dev/everylastcity
git pull
pnpm install
pnpm verify
Develop in ~/dev/everylastcity, never in ~/apps/everylastcity. The second is the deploy
artifact: every push runs git checkout -f and git clean -fd there, so uncommitted work in that
directory is destroyed without warning. The deploy hook re-stamps a DEPLOYED_COPY_DO_NOT_EDIT.md
there on every deploy saying exactly that.
The deploy path itself deliberately runs no build, even though the toolchain now exists. site/ is
committed so a deploy cannot fail for a toolchain reason and leave the site half-updated. Run
pnpm build-site before committing a docs change instead.
The determinism suite passes on the server and produces the same reference hash as a Windows
machine — 6dac791367eb690d — which is the cross-platform agreement the whole engine rests on,
observed rather than asserted.
Run pnpm build-site before committing if you changed anything under docs/. The rendered site is
committed deliberately (see .gitignore) so the server needs no toolchain; review the markdown in
docs/ and ignore site/ diffs as generated noise.
Status
Phase 0 is complete except the rights decision, which is an owner's call rather than an engineer's. The fixed-point representation is chosen and implemented, the determinism harness and lint gate exist and are verified to actually fire, cross-platform CI covers three operating systems and three browser engines, and the specification carries no claim to reproduce another game. The design is complete on paper across seven interlocking systems.
Nothing has been played. Every number — combat odds, Integration rates, Industry costs, Initiative bands — is a first estimate by a designer who has never seen the system in motion. That is the project's real risk, and playtesting is a roadmap phase rather than an assumption.
Historical context only: Empire Deluxe Combined Edition (Walter Bright's Empire → Empire Deluxe 1993 → EDIE/EDEE 2002 → EDCE) is the ancestor this game learns from. No asset, value, name, or table is taken from it.