09 — Implementation Roadmap
This document sequences the work described by the specification set into buildable phases with explicit exit criteria. It is the only document in the set that is not a specification of the product: it makes no normative claims about behaviour, carries no requirement IDs of its own, and where it conflicts with any other specification, that document wins. Its purpose is to answer "what gets built first, and how do we know a phase is done?"
Status: Draft v0.2 · Owner: unassigned · Depends on: docs/design/00-direction.md, docs/design/01-decision-turn-model.md, and the specification set — 00-overview.md, 01-game-rules.md, 02-units-and-industry.md, 03-architecture.md, 04-ui-ux.md, 05-multiplayer.md, 06-ai.md, 07-modding-content.md, 08-services-platform.md, 10-turn-model.md, 11-combat.md, 12-economy.md, 13-command.md, 14-victory.md
What changed in v0.2. The v0.1 plan was sequenced around reproducing another game: a golden-master corpus as the first deliverable, reference data as a Phase 0 gate, four inherited unit sets spread across three phases, and a definition of "done" that ended with a veteran being unable to name a rule that behaved differently. That premise is withdrawn (docs/design/00-direction.md). The plan is now sequenced around a different and harder problem: the design is original, seven rules documents deep, and has never been played. Verification against an oracle is replaced by verification against the specification plus playtesting, which now gets a phase of its own. The AI is promoted from a late add-on to the product pillar the owner asked for. Phase numbers after 2 have shifted; §2.9 maps the old numbers to the new ones.
1. Sequencing principles
- Determinism is a Phase 0 gate, and it is closed. The fixed-point representation, the determinism harness, the lint rules and the cross-platform CI job all existed before the first line of rules code. This is done (§2.1), and it is listed first because it is the only principle that could not have been applied later.
- Get to a played game as early as the design allows. There is no external oracle any more. Every constant in the rules documents is a first guess, argued in prose and never tested against a person. The plan is therefore optimised for time to first played game, not for time to feature-complete: Phase 1 builds the smallest rules surface that is genuinely a game, which the design conveniently already identifies — Industry tier 1, described in 02-units-and-industry.md §8.1 as "the complete simple game."
- The turn machine is built first and never stubbed. Orders, the Cascade and the Reckoning (10-turn-model.md TM-020) are the spine every other system keys off. Initiative order, activation semantics and "resolve against the world as it is at that instant" cannot be approximated in Phase 1 and corrected in Phase 2, because every rule in every other document is written against them.
- The order vocabulary is rules code, not a convenience layer. 13-command.md is core: its Postures and Sanctions reach the simulation through the order log (CM-2040) and changing one is a versioned rules change that breaks stored replays (CM-2060, CM-2070). It ships in Phase 1 with the turn machine. Deferring it is the single most expensive mistake available in this plan; see the first row of §4.
- The core is never bypassed. No phase adds a shortcut where UI, netcode, or AI mutates state directly. If a phase needs behaviour the core lacks, the core gets it.
- Every phase ships something playable. Each phase ends with a build a person can play, even if that build is a CLI in Phase 1.
- Content is data from day one. The shipped roster is authored in the shipping JSON format (02-units-and-industry.md US-700, US-1340) and loaded through the same content path a custom set will use, in Phase 1, not hardcoded and migrated later.
- Verification is the safety net, not review. The implementation is largely AI-authored. With no original to check against, the anchors are the requirement IDs themselves: every requirement maps to at least one named test, plus property tests (03-architecture.md AR-950), the cross-platform replay job (AR-960), and the worked test vectors the rules documents already nominate (14-victory.md VC-3310 is the model — a document naming its own reproducible computations).
- Instrument before tuning. Each rules document nominates the metrics that would show it was wrong (10-turn-model.md TM-1440, 02-units-and-industry.md §14, 14-victory.md VC-3300). Those emitters are built with the systems they measure, not retrofitted when balance becomes urgent.
- The AI is a pillar, not a phase-three afterthought. The owner has named advanced AI opponents as the headline advancement. Most play of a game of this shape is one person against AI seats, so a client with no credible opponent is not a playable build in the way that matters. A baseline opponent therefore lands in Phase 2, and the AI proper gets a full phase of its own ahead of multiplayer.
- One client, six platforms. The browser build is the primary delivery target; the desktop and mobile apps are the same web client packaged, not a second implementation.
2. Phase plan
2.1 Phase 0 — Foundations · complete
Done, and verifiable in the repository.
- The fixed-point representation is chosen: signed Q16.16, expressed as a branded
Fixedtype alongside a brandedInt, inpackages/core/src/numeric. Overflow throws; it never wraps, saturates, or yieldsNaN. A barenumbercannot reach rules math without a visible conversion. This satisfies the "decide once, before anything else" half of 03-architecture.md AR-080. - The determinism harness exists and is green.
packages/core/src/rng(seeded PCG32 with SplitMix64-derived named streams, hi/lo 32-bit arithmetic, no BigInt on the draw path),packages/core/src/hash(strict canonical encoder plus 64-bit hashing), andpackages/core/src/harness(replay-and-compare over a toy fixture). - The lint gate exists.
packages/eslint-plugin-determinismimplements the four bans of 03-architecture.md AR-080/AR-090/AR-100/AR-110: float arithmetic in rules paths, ambient clocks and entropy, locale-sensitive comparison, and unordered iteration. - Cross-platform CI exists and blocks merges.
.github/workflows/determinism.ymlruns typecheck, lint and tests on Linux, macOS and Windows, then a separate job asserts every platform printed the same reference hash. The comparison, not the per-platform green, is the gate. - Repo and toolchain. pnpm workspace, TypeScript
strict: true, the package boundaries 03-architecture.md AR-010 and AR-020 depend on.
Done since this section was written.
The rights decision.Done. Taken offline by the owner on 2026-09-03; the Status below records it. Much smaller under clean-room than it was, and the owner's call rather than an engineer's: under docs/design/00-direction.md the question is no longer "license or replace" — nothing is being reproduced, and mechanics are not owned — leaving three concrete items: confirming by audit that no name, number, table, or text of another game's expression survives anywhere in the specification set or the content files; trademark clearance for the title; and settling what the project publishes about its relationship to its ancestor, if anything.The browser leg of the determinism matrix.Done. The matrix now covers Node on Linux, macOS and Windows and three browser engines — Chromium, Firefox and WebKit — satisfying 03-architecture.md AR-070 and AR-960. The determinism, arithmetic, PRNG and hashing suites run inside each engine viavitest.browser.config.ts(pnpm test:browser).The mechanism changed as well as the coverage, and the change is the more important half. Cross-platform agreement is now asserted inside the test suite against a committed constant (
packages/core/test/reference-hash.ts), not inferred by diffing uploaded CI artifacts. Comparing a freshly computed hash to itself proves only that an engine is self-consistent, which every non-deterministic engine also manages; comparing it to a recorded value proves this machine agrees with every machine that ever ran it. A divergence now turns that platform's own suite red and names the engine, instead of surfacing later as a mismatch between files.The specification consistency sweep.Done since this section was written. Every document has had the pass: 00-overview.md is rebuilt, 06-ai.md is rewritten from the AI design record, and the fidelity language is gone from 03-architecture.md, 04-ui-ux.md, 05-multiplayer.md and 07-modding-content.md. The two fixtures this bullet named are fixed at the root: AR-930 now reads "golden masters, recorded from this engine" rather than recorded from another game, and AR-760's reference game is defined against our own map rules (01-game-rules.md GR-190) rather than against unit sets that no longer exist.scripts/check-spec-refs.mjsruns in CI and proves every requirement citation across the set resolves.
Void, and named so a reader of v0.1 can see they were dropped rather than forgotten.
- Acquire and archive a reference installation for golden-master extraction. There is nothing to extract from and nothing to be faithful to.
- Resolve the
⚠ VERIFYbacklog — the drain formula, the combat-mod-to-hit function, the stat values the manual did not give. Void as posed. Each was a question about what another game did. The work that replaced it — designing those systems on their own terms — is largely complete: seven rules documents carrying 1,790 requirements (GR 199, US 225, TM 279, CB 284, EC 241, CM 221, VC 341), plus the design record indocs/design/. What was a research task is now a playtesting task, which is why it has a phase (§2.4).
Exit: rights path chosen; the browser leg green in the determinism matrix; no document in
docs/spec/ claiming to reproduce, match, or be faithful to another game, and no (EDCE p. N)
citation anywhere in the set.
Status: complete (2026-09-03). The rights decision — the one item left open, and the owner's
rather than an engineer's — was taken offline by the owner on 2026-09-03. The browser leg is green
across Chromium, Firefox and WebKit; the documentation sweep is complete and verified mechanically —
pnpm check-spec passes across the set, and the only occurrences of the forbidden citation pattern
anywhere in docs/spec/ are in this document, quoting the rule that forbids it. The completed items
are not re-gated.
2.2 Phase 1 — The turn machine, the core, and the first played game
Build @everylastcity/core up to the smallest rules surface that is a game, and get a person to
play it. The order below is a build order, not a list.
- The turn machine. Orders / Cascade / Reckoning as an explicit authoritative phase value (TM-090); the Initiative score and the total activation order it produces, with creation sequence as the tiebreak; the purity guarantee (TM-040) and the no-input rule (TM-050); reactions; entry and exit of units mid-Cascade; the unachievable-order classification (TM-1430).
- The order vocabulary, per §1.4: the order stack, the six Postures (13-command.md CM-330), the four Sanctions — Engagement, Leash, Risk, Seize (CM-570) — Reflexes, and the interruption model. Formations, Postings, Consignments and Requisitions may follow in Phase 2; the conditional vocabulary itself may not.
- The world. Map, coordinate system, terrain, movement and legality, occupancy and stacking, zones of control, cities and the capture procedure, line of sight and fog, the Intelligence Picture, and setup (01-game-rules.md). Two to eight players (GR-030); five entity kinds and no sixth (GR-010).
- Combat. The Clash, persistent strength and disorder, bombardment, reactive fire, retreat and
rout, encirclement, and the pre-commitment odds readout — which is the same
winPermillefunction the command layer and the AI use (11-combat.md CB-2250), so there is never a second predictor to keep in sync. - The economy. Works and Manpower, Integration, Unrest, the Cadre, Reach, War Damage, and the Ledger phase (12-economy.md), built with the Visibility Test (EC-020) as an acceptance condition rather than an aspiration, and with EC-010's prohibition enforced by review of every new formula.
- Units and Industry. The full attribute schema and the closed eighteen-flag vocabulary (02-units-and-industry.md §2–§3), the Industry ladder, the production queue and the Yard, and capture degradation — but only the tier 1 roster authored (§8.1–§8.2). The schema is complete in Phase 1 even though the roster is not, because the schema is what the later tiers are data against. US-060's ban on branching by unit id is enforced by CI lint from the first roster commit.
- Victory. The Track abstraction, Standing and Tenure, the Seal, the Vigil, and a small Track subset — enough that a Phase 1 game ends, with total conquest present as one option among several and not as the default (14-victory.md VC-020).
@everylastcity/clion Node: create, play hot-seat, save, load, replay, verify a game headlessly.- Verification. Requirement-anchored fixtures (every requirement ID mapping to at least one named test), the worked test vectors the rules documents nominate, property tests (03-architecture.md AR-950), and the Phase 0 determinism corpus grown out to AR-960's full matrix.
- Instrumentation, from the first playable build. The stance-fallback rate (TM-1440) above all; the alarm metrics of 02-units-and-industry.md §14; the acceptance metrics of 14-victory.md VC-3300 wherever the Track subset supports them.
- Performance re-baseline against 03-architecture.md AR-760/AR-770, early enough that the answer can still change the state layout. Those budgets were written for AOT-compiled native code and have not been re-derived for a JIT with a garbage collector. The AR-760 fixture also needs restating: as written it specifies unit sets and rule options that no longer exist.
Milestone P0 — first game played by a human, in the CLI, hot seat, tier 1. This is the earliest point at which anything in the design can be falsified, and it sits deliberately inside Phase 1 rather than at its end. From P0 onward, playtesting is a continuous track (§2.4), not a later stage.
Exit: a full tier-1 game is playable end-to-end in the CLI and has been played to a decided finish by people who did not write it; identical replay hashes across the AR-960 platform matrix, browser included; every requirement ID maps to a named test and the gaps are listed rather than absorbed; the stance-fallback rate is emitted per game; the measured V8 numbers are recorded against the (restated) AR-760 budgets with any shortfall named.
Ruling, 2026-09-06: "by people who did not write it" moves to Phase 2. P0 keeps the part the CLI can actually prove.
The clause was written when "playable end-to-end in the CLI" was assumed to describe something a person could sit down to. Built, it does not. The hot seat asks the player to type JSON order objects — a March Posture is some 180 characters of nested braces — naming units by id and destinations as flat tile indices, against a character-grid map. That form can establish that the rules run, that a game reaches a decided finish, and that the quantities move as the documents say. It cannot establish whether the turns are interesting, whether the decisions read as decisions, or whether committing before seeing is thrilling or merely frustrating, because everything it would measure is swamped by the cost of the interface.
Phase 2's exit (§2.3) already asks the real question in the only form that can answer it: a person who has never seen the design plays a tier-1 game to a finish without reading anything and can say what they were deciding. Asking a weaker version of that question here spends an outside reader's first session on the interface rather than on the game, and a first session is not repeatable — US-2220's two failures and VC-3300's M13 are exactly the things a spent first session can no longer be used to find.
P0's exit therefore reads: a full tier-1 game is playable end-to-end in the CLI and has been played to a decided finish, with the container kept and every finding recorded as a defect or a design question rather than absorbed — the authors may play it. The rest of the clause is unchanged, and the independent reading is Phase 2's to gate, where it already is.
What this gives up, named rather than absorbed. Until Phase 2's exit, nobody outside the two authors will have exercised these rules. The exposure is a shared authorial assumption that no author can see, and no amount of machine coverage finds it: the 33-game determinism corpus and the requirement ratchet both prove the engine agrees with itself, never that the game is worth playing. Two things bound it. First, this milestone is not the last gate before players — Phase 2 cannot close on the same evidence, and its exit clause is the one that must not be softened. Second, author play is not nothing when it is recorded: the first CLI game played through on 2026-09-06 produced two crashes, a Reflex loop that makes the natural movement order useless against its own standing order, terrain a quarter of the map wide that no land unit may enter, and a capture-then-attrition-then-revolt arc that returns a hard-won city to neutral for no net gain. None of those needed a stranger. What needs a stranger is the question of whether any of it is enjoyable, and that question is now asked once, in Phase 2, on a screen.
Consequence for §2.4: Phase 3's "human playtest, closed then widening" is unchanged and remains the tuning instrument. This ruling moves the first independent session from P0 to Phase 2; it does not move Phase 3.
Status: in progress (2026-09-04). First increment landed: the turn machine in
@everylastcity/core — the explicit phase value, createGame and applyOrder as the whole
mutation surface (AR-030), the Initiative score and the frozen activation order it sorts, Stance
and the committed-Stance determination, the Cascade with its tile-entry and reaction procedures
and skip tests, the Reckoning's fixed step order and clocks, and the state and turn hashes —
with 10-turn-model.md's §20 worked example held as a normative test vector. The command, world,
combat, economy, and victory increments land in place through the seams this increment defines
(the CascadeRules and ReckoningRules interfaces); the public API does not change when they do.
Second increment landed (2026-09-04): the world's static surface — the map and coordinate
system (GR-100–GR-240), the eleven base terrains and terrain-set validation (GR-260–GR-370),
installations and river crossings (GR-400–GR-500), movement cost and legality with zones of
control (GR-550–GR-700), occupancy and stacking (GR-710–GR-810), the closed eighteen-flag
capability vocabulary (US-470), the deterministic pathfinder (AR-750), and the city slices —
the capture procedure (GR-920–GR-990) and the Ports (GR-890–GR-910), wired into the Cascade
at the moment of entry (TM-1510). Setup/map generation lands in the following
slice; createGame carries world: null until the map-generation slice does.
Third increment landed (2026-09-04): vision, line of sight, and fog — the observer set
and the effective vision radii (GR-1000, GR-860), the sight ray with its candidate tiles,
strict block test and symmetry (GR-1010–GR-1030), the City's block/view adjustment
(GR-870), the air exception (GR-1070) and the layer rules (GR-1080, GR-1090), the three
tile states and the Terrain Record (GR-1120–GR-1170) with the no-leakage discipline
(GR-1180), and the production knowledge view (CM-130): the virtual world the true-world
step model runs against (CM-140). The per-player records ride GameState.fog (hashed)
and refresh at every activation (GR-1100) through the afterActivation seam.
Fourth increment landed (2026-09-04): the Intelligence Picture — the Contact Register
(GR-1200–GR-1250), the fidelity ladder (GR-1260–GR-1290), decay (GR-1300–GR-1320,
GR-1680 via the afterReckoning seam), position uncertainty (GR-1330–GR-1360), and
the predictor interface (GR-1370, GR-1380). The registers ride GameState.contacts
(hashed); the setup slice wires the per-activation refresh and the once-per-turn decay.
Fifth increment landed (2026-09-04): perceptible events — the loud half of the Intelligence Picture (GR-1390–GR-1450): a combat, a bombardment, or a spent expendable creates a Trace at the named tile for every player who hears it (the observer's own effective radius plus 4 or 6, line of sight ignored; the expendable, every player map-wide), evaluated at the instant it resolves in the Cascade (GR-1450). The event is a set-not-raise refresh of the register: a higher-fidelity Contact at the same tile is untouched, at another tile it is downgraded to the Trace there, and the event reveals nothing (GR-1410) — a City's recorded owner is kept (GR-1290). The City ownership announcement (GR-1430) updates only the recorded owner for explored fogs, and the owner's own loss (GR-1440) raises the UnitLost and CityTaken Attention Events with the tile. Pure functions over state that already exists; the setup slice wires the call sites.
Performance re-baseline (2026-09-05): the budgets are missed by three orders of magnitude, and the
cause is a single requirement not implemented. Run with pnpm bench; the fixture is the
simulation-only half of AR-760 on the surface that exists, since the normative one needs the roster,
Industry tiers and PlayerView, none of which are built.
This finding was acted on and is closed — see "Re-baseline resolved (2026-09-07)" below. The measurements and the present-tense diagnosis in the rest of this subsection are the 2026-09-05 record of what was found, kept because the reasoning is what located the seam. They are no longer a description of the engine.
| Units | Turn | Per activation | vs AR-760 (100 µs) |
Fog share | Fog per activation | vs AR-740 (40 µs) |
|---|---|---|---|---|---|---|
| 25 | 0.09 s | 3,488 µs | 35× | 77 % | 2,675 µs | 67× |
| 100 | 0.61 s | 6,108 µs | 61× | 70 % | 4,278 µs | 107× |
| 250 | 5.08 s | 20,314 µs | 203× | 93 % | 18,796 µs | 470× |
| 500 | 23.58 s | 47,155 µs | 472× | 95 % | 44,872 µs | 1,122× |
The cost is quadratic in unit count and the probe stops at 500 — AR-760's fixture calls for
10,000. Extrapolating the observed curve, one turn at that size is measured in hours.
Isolating it takes one line: stub the afterActivation seam and 70–95 % of the turn disappears.
turn/world-rules.ts answers that seam by recomputing, for every player, a from-scratch observation
set and a full fog and Contact-register refresh — after every activation. AR-740 requires the
opposite in terms: "fog updates MUST be incremental: a unit move recomputes visibility only in
affected radii (dirty-region tracking per chunk), so the cost is a function of the sight footprint,
not of map size or player count."
The individual operation is not slow; it is being called in the wrong place. AR-740 budgets a
from-scratch recompute of one player's whole fog at ≤ 50 ms, and the measured cost is about 6 ms per
player — comfortably inside that. The defect is that the ≤ 50 ms operation runs where the ≤ 40 µs one
belongs, once per player per activation. Nothing about the state layout needs to change to fix it,
which is the good news in these numbers: AR-740's dirty-region tracking is an addition to the world
module rather than a re-shaping of GameState, so the roadmap's fear — that a late re-baseline would
force a state-layout change — does not materialise.
Recorded rather than fixed: world-rules.ts was in another session's working tree when this was
measured. The roadmap asks for this re-baseline "early enough that the answer can still change the
state layout", and running it now is what makes that answer cheap — every increment landing on top of
a fog refresh this expensive is one more caller to re-time later.
Re-baseline resolved (2026-09-07): the fog is incremental, the cost is no longer quadratic, and the
overrun is now a roughly constant factor. AR-740's dirty-region tracking landed in f55f1a2 and
9febc86 — the observation set, the fog and the Contact register are maintained as a function of what
changed, with the tracker kept out of the canonical hash (AR-620) so determinism is untouched. The
prediction above held exactly: it was an addition to the world module, and GameState's layout did
not move.
Same-host comparison, the P0 host, against the 2026-09-06 re-run recorded in the status paragraph below:
| Units | Before (2026-09-06) | After (2026-09-07) | Change |
|---|---|---|---|
| 25 | 14,800 µs | 9,558 µs | 1.5× |
| 100 | 35,100 µs | 11,489 µs | 3.1× |
| 250 | 82,200 µs | 9,524 µs | 8.6× |
| 500 | did not complete | 17,335 µs | — |
| 1000 | did not complete | 40,746 µs | — |
Independently re-run on the Windows development host, which is a different machine and so is not comparable to the figures above in absolute terms — it is here because the shape is the result, and the shape reproduces:
| Units | Map | Per activation | vs AR-760 (100 µs) |
Fog share | Fog per activation | vs AR-740 (40 µs) |
|---|---|---|---|---|---|---|
| 25 | Small | 2,302 µs | 23× | 58 % | 1,338 µs | 33× |
| 100 | Small | 2,347 µs | 23× | 27 % | 623 µs | 16× |
| 250 | Standard | 1,971 µs | 20× | 26 % | 503 µs | 13× |
| 500 | Standard | 3,149 µs | 31× | 30 % | 949 µs | 24× |
| 1000 | Large | 7,221 µs | 72× | 34 % | 2,484 µs | 62× |
Three things changed, and only the third is a number:
- The curve. Forty times the units now costs about three times the work; before, 250 units cost six times what 100 did. The probe reaches 1,000 units on both hosts, where it previously could not finish 500.
- Where the time goes. The fog's share of a turn fell from 77–95 % to 26–58 %. It is no longer the dominant term, which means the next profiling pass will find a different bottleneck — this subsection's diagnosis has been spent.
- The overrun. Still 20–72× over
AR-760's 100 µs, andAR-760's fixture calls for 10,000 units, which none of these runs reaches. That is the honest remaining gap. But "a constant factor over budget" and "quadratic, one turn measured in hours" are different engineering problems, and the project is now in the first one. The earlier extrapolation to hours no longer holds.
Still open, named rather than absorbed: the normative AR-760 fixture needs the roster, Industry
tiers and PlayerView, so every figure here remains the simulation-only half; and no run yet reaches
the fixture's 10,000 units. The next re-baseline should be run once PlayerView exists, against the
normative fixture, and should not reuse this subsection's diagnosis.
Cascade profile (2026-09-07): the fog is one of two halves, and the other half is per-activation
Cascade machinery. The prediction above — that the next profiling pass "will find a different
bottleneck" — held. Method: on the bench's fixture at 500 units / Standard (P0 host, warm steady
state), the afterActivation seam and the Reckoning's heavy steps are stubbed in a 2×2, and the
differences isolate each phase. Stubbed runs diverge in trajectory (no fog, so the knowledge views
are empty; no Reckoning, so nothing produces or attrites), so the split is an approximation — the
ordering, though, is stable round to round:
| Phase (500u, warm, median of 4 rounds) | µs/activation | Share |
|---|---|---|
| Full turn | 17,800 | 100 % |
| Cascade + Reckoning, fog stubbed | 9,900 | 56 % |
| Cascade + fog, Reckoning stubbed | 18,400 | ≈100 % |
| Pure Cascade | 9,300 | 52 % |
- Fog + contacts: ~8–9 ms (44–51 %).
AR-740's fix held: the fog is one of two terms of the turn, not the turn. - Pure Cascade machinery: ~9.3 ms (52 %). The
--cpu-profSELF breakdown names the terms: GC ≈ 21 % (per-pass allocation),detectOpportunityinturn/interruption.ts≈ 8 % — an interruption scan after every activation — and the knowledge-view rebuild (buildVirtualWorld/worldKnowledge/synthesizedEnemy) ≈ 5–6 %: the virtual world is rebuilt after every activation even when nothing changed for that player. The same pathologyAR-740had: per-activation work that belongs per-change. - Reckoning: ≤ 0.6 ms (≤ 3 %). Essentially free in this fixture, which has no cities —
economy, production and repair iterate over nothing. This is a floor, not a measurement: a real
board's Reckoning costs, and the
TM-1970displacement ruling lands in that step.
The overrun at 500 units is now ~178× AR-760's 100 µs, warm and simulation-only — a constant
factor with named targets: GC pressure in the hot paths, the per-activation interruption scan, and
the per-activation knowledge view. The normative 10,000-unit fixture still cannot be built
(AR-500's PlayerView, the roster and Industry are absent), so this profile closes the
simulation-only question and defers the rest to the re-baseline the "Still open" note above asks
for.
Exit instrumentation (2026-09-04). The exit clause above asks that every requirement ID map to
a named test and that the gaps be listed rather than absorbed. pnpm check-req-coverage now
measures it: 273 of 2,793 (9.8%) — 01-game-rules.md 56%, 10-turn-model.md 33%,
13-command.md 24%, and 0% for the documents whose increments have not started. It is a ratchet,
not a threshold: it fails when a requirement that was covered stops being covered, and lists
the rest. A gate demanding 100% at this stage is one nobody passes and everybody disables, and the
number is only a floor on traceability — a cited requirement can still be tested badly, which is
what the risk register's "spend scarce human review on spec-to-test correspondence" is for.
Where the testing actually stops (2026-09-05). pnpm check-req-coverage answers "how much of
the roadmap has been reached", which at this stage is mostly a progress figure: Victory reads 0 %
because Victory does not exist. pnpm coverage-gaps asks the narrower question this clause is really
about — within each section of each document, is the testing partial?
| Sections | Requirements | |
|---|---|---|
| Fully covered | 17 | 206 |
| Partially covered | 72 | 546 untested |
| No coverage — unbuilt, expected | 133 | 1,538 |
The middle row is the risk surface, and it is the one neither number showed before. A section nobody has started reads as unbuilt and is honest about it. A section tested in part reads as done and is not: somebody built the area, tested some of it, and moved on, and the untested remainder is code whose neighbours are covered — which is exactly where §4's register says the dominant failure mode hides, "a plausible-looking rule that is subtly wrong" in work that "more code is produced than anyone will read closely".
The register is a tool rather than a committed list, so it cannot go stale; --ids <PREFIX> names
the requirements in any partial section. The first two entries it was read against were §9 The
Cascade, the most-exercised code in the engine, at 7/23, and §8 Initiative at 41 %.
Spot-checking the Cascade's gaps found TM-1280 among them — "nothing that happened after
activation i MAY influence activation i" — a causality property the whole determinism guarantee
rests on, which no test named. §9 now reads 23/23, §10 Order evaluation 17/17, §11 Reactions 23/23,
§4 The Orders phase 15/15, §6 Stance 18/18, §13 The Reckoning 12/12, §8 Initiative 26/27 and
§19 Contracts 6/12 and §16 Determinism hazards 8/12. With §1, §2, §3, §7 and §12 closed as well,
every requirement in the turn model is now either tested or carries a written reason why not.
What reading the register against those sections actually produced. §9 found no defect: the Cascade held all fourteen of its untested requirements. §8 found three, §10 one open call, §11 a requirement with nothing behind it, §4 another plus a side channel, §6 a normative table written down twice, §13 a revolt nobody is told about, §19 a contract that is broken rather than untested, §16 a replay the repo could not yet perform, and §12 a clock that credited a unit with a turn it had not spent — none of which a passing test suite would ever have surfaced, because in each case the code was self-consistent and merely did not do what the document says.
TM-1050was not implemented. Tier 1 is the default for every game shape, and the doc comment onSetupConfig.initiativeTiersaid so, but the field was required and nothing defaulted it. Resolved now throughinitiativeTierOf, in one place, so that an omitted tier and an explicit1are the same game — a default resolved after canonicalisation would give one game two hashes (GR-1460).initiativeReadoutwas a turn stale, in the one phase where it is read. The readout answers "what will this unit's initiative be?", and it took the Stance term from the storedcommittedStance, whichTM-710does not recompute until the freeze. A player switching a unit from Hold to March saw a number 14 points from the one they were about to get, for the whole of the Orders phase — on the panelTM-1080requires. It now reads the live derivation; at the freeze and inside the Cascade the two agree.TM-1110's rationale overstated its own example, and has been corrected. It read "a class at the top of the Assault band reads Steady in Advance, drops to Slow in Hold". The top of that band is 37 and 37 − 6 = 31, which is Steady; all three readings hold together only for bases 30 to 35. The normative text was consistent and the engine implements it correctly, so only the rationale moved — it now names the window — andinitiative-contract.test.tspins the window's upper edge, so moving either the commitment bands or the disclosure bands fails a test rather than quietly falsifying a paragraph.
TM-1010 is the one §8 requirement still uncovered, and deliberately so: its substance is the
condition under which the In-command term applies — Posture Attach, within the Formation's
cohesion radius of its Command Reference Point — which is 13-command.md §7's derivation, and
Formations are not built. inCommand is written false at creation and nothing ever writes it again.
The turn model's own half of it, that the flag contributes +4 at Tier 3 and 0 below, is covered.
§10 produced one open call and one confirmation of a ruling already made.
TM-1390case 4 has two readings, and the code takes the narrower one. Case 4 says that when the order's target is gone "the movement component MUST execute; the attack or capture component MUST NOT. The unit halts on contact with whatever is actually there, or completes the movement if nothing is." For an Attack Task whose named tile has been vacated,command-policy.tsproduces no action at all, citing CM-260's reading that the Task waits for its end condition. The two can be reconciled either way: the movement component of an Attack Task is an approach derived from the target, so with no target there is arguably nothing to execute; against that,TM-1430's whole argument is that a unit-turn must not be spent on a technicality, and "your target moved, so you stood still" is the shape of exactly that. Recommendation: the spec's reading is the better one — advance to the named tile, halt on contact with whatever is really there, do not attack — but it is a behaviour change in an increment another session owns, so it is recorded rather than applied. The tests pin only what both readings agree on: the attack does not execute.
§11 found one requirement with no implementation behind it at all.
TM-1740's second half was not built. It requires that "the reach of a visible enemy unit MUST be shown to a player who has observed that unit's class", andVisibleEnemycarried no reach field — so the knowledge view could not have shown it, and no client could have. Reach is what decides whether a route passes a picket safely, which makes it one of the few numbers a player actually plans around, and its absence is the kind of gap that never surfaces as a bug: the panel simply has nothing to draw. Now admitted with the class at fidelity 2, on the same gate asatkand the other class constants, and read from the unit rather than stored per Contact — reach is a class constant, so the remembered value and the live one are the same number and storing it would put a constant into hashed state for nothing.CB-2310's fidelity table is not contradicted by this: it lists the constants combat prediction may read, which is a different question from what the panel may show.
§4 found one requirement with nothing behind it and one side channel nobody had looked for.
TM-380was not implemented. A player MUST be able to see the relative activation order of their own units before committing — it is what makes Stance a decision with a visible consequence rather than a feel — and no query answered it.relativeActivationOrder(state, player)now does, reading the live derivation the wayinitiativeReadoutdoes, so the answer moves as the player edits and the freeze then delivers what they were shown. The prohibition in the same requirement is satisfied by the shape of the answer rather than by a filter: it returns the asking player's own unit ids and nothing else, so there is no opponent in the result to leak and no initiative value either. It is a query and hashes nothing.nextOrderSeqdiscloses an opponent's order count.TM-450forbids showing a player any indication of another player's orders or order count, and the core is clean on the first: the turn under assembly never enters the canonical state, so twelve of an opponent's order records leave no trace. But every player's orders draw from one shared sequence counter, and that counter is in the canonical state — so a client handed it could subtract its own orders and read the opponent's count. The core is not wrong to hold the counter; the sequence is the authoritative log's identity, and moving it would change the reference hash for no gain. The obligation lands on §14: the per-player view MUST masknextOrderSeq. Recorded here rather than fixed, and pinned as an executable expectation inorders-phase.test.ts, so that building the view without masking it fails a test instead of shipping a side channel.
TM-460 is worth a note in the other direction, because it is the one place this pass found the code
ahead of the reading. Hotseat and concurrent play must produce identical hashes, which looks false at
first sight: the order log keeps arrival order, and the two schedules interleave differently.
turnRecordToCanonical regroups the orders by player and renumbers within each player's own stream
before anything is hashed, so the interleaving — an artefact of who typed when — is erased by
construction. The test asserts the two arrival sequences genuinely differ before asserting the hashes
match, because without that guard it would be comparing a run against itself.
§6 found the normative Stance table written down twice. reactionCapacityFor in stance.ts
carried the reaction-capacity column of TM-640, was exported — and was never called. The Cascade
kept its own private copy of the same switch. The two agreed, so nothing was broken today; what was
broken was the arrangement, because a table with two homes has two places to change and only one of
them is reachable from the public surface. That is the argument TM-700 already makes about the
entrenchment counter in as many words — "MUST NOT define a second counter, a second name, a second
range, or a second increment or reset condition" — and it applies to the capacity column for the same
reason. The private copy is gone and the freeze reads the exported table. The hash is unchanged,
because the two produced identical values.
The rest of §6 held, including the part worth holding. The split between the committed Stance, bought during Orders at a stated price, and the effective Stance, which a Reflex may force mid-Cascade, exists to close two specific holes that §6's rationale names: a March column that takes cover must not acquire an overwatch shot it never paid for, and a Hold unit driven off its tile by a Withdraw must not lose the reaction it already did. Both are now tested as behaviour rather than inferred from the fields — a mutation that recomputes capacity from the effective Stance after each activation fails both, and nothing else in the suite notices it.
§13 held everything this document owns, and found a defect in the seam where it meets another.
Steps 5, 6 and 9 move nothing, solicit nothing, log nothing, and run in a fixed explicit order. What
the section could not check about itself is what its seams do — and one of them does two things
TM-1970 forbids by name.
A city that changes hands by revolt is never announced.
CityOwnershipChangedis emitted in exactly two places, both in the Cascade: the capture at tile entry (turn/cascade.ts) and the capture after a Clash (turn/clash.ts).turn/world-rules.tsdrivesGR-1430's per-player end-of-turn announcement solely from that stream, and that announcement is what updates each player's recorded owner. The Ledger's revolt (economy/ledger.ts,EC-1130clause 3) changes the owner during the Reckoning and emits nothing — it sets aownerChangedSinceLastLedgerflag the fog path does not read. The consequence is player-visible: after a revolt every player's map still shows the former owner until somebody re-observes the tile. This half is a defect rather than a question, and the fix is small: the revolt must emit the event. The catalogue needs two additions for it — arevoltmember on the event'scause, and anactivationIndexthat admits "no activation", since a Reckoning-time change has none. Both belong toAR-245's single enumeration.The same revolt path moves units, which
TM-1970forbids.displaceOwnerUnitscallsmoveUnitduring the Reckoning's economy step, and destroys units where no destination exists.TM-1970is categorical — "No step of the Reckoning MAY move a unit, initiate combat, or change tile ownership" — and carries its own remedy: "A Reckoning step that needs one MUST instead set state the next Cascade acts on." This half is a genuine two-document conflict rather than a bug, and needs an owner ruling. Recommendation: split it. The ownership change is bookkeeping a player reads in the turn summary, and admitting it costsTM-1970little once the announcement above exists; the displacement is precisely what the rationale is about — units standing somewhere else with no animation to explain it — so defer that half, marking the units during the Reckoning and moving them at the next Cascade's start.
Both sit in the economy increment another session owns and had just landed when this was found, so
they are recorded rather than applied. AGENTS.md carries the pointer.
Resolution (2026-09-07, implemented, 7627623). The owner ruled as
recommended: the city-owner change stays in the Reckoning — it is the
bookkeeping the announcement half makes player-visible — and the
displacement is deferred. UnitState carries two hashed marker fields,
displacementPending and displacementTo (null = destroy at
consumption); the revolt sets them during the Reckoning and the next
Cascade consumes them at its start, before any activation, under the
CM-380 displacement cause, so the displaced marker and the
ReturnToPost Reflex behave exactly as for any external effect. A unit
destroyed in the same Reckoning voids its mark, and a game that ends in
the marking Reckoning never consumes it. The two hashed fields move every
state hash, so the re-emission was full: all 33 corpus games (the three
revolt games' trajectories additionally diverge from their revolt turn;
every hazard event timing the corpus README documents was re-verified
unmoved), both artifacts re-emitted with byte-identical entry bytes and
logs (no city changed hands in either), the two CLI goldens, and the
container pins in cli/test/binary.test.ts.
§19 is the section where "not testable yet" is the answer for half of it, and saying which half is the useful output. A contract on another document is checkable only where the other side exists. Six of its eleven open requirements have a built counterparty and are now covered — what 01-game-rules.md owes the Cascade, what 11-combat.md owes initiative and the entrenchment counter, and the three that draw the boundary with 13-command.md and 14-victory.md. The other six are not covered, for reasons that differ and matter:
| Counterparty | Why it is not covered |
|---|---|
| 02-units-and-industry.md | No class table exists in the core: no commitment bands, no per-class Stance restrictions, nothing to check the contract against. The two ranges this document polices on its behalf are covered where it enforces them. |
| 12-economy.md | The contract is broken, not untested — the revolt moves units and changes tile ownership inside the Reckoning, which the contract forbids in as many words. Citing it would book a known violation as satisfied. |
| 03-architecture.md | Four of its five clauses are provided; incremental visibility with a proof of equivalence is not, which is the same gap the performance re-baseline found. |
| 05-multiplayer.md, 06-ai.md | No implementation at all. |
| 04-ui-ux.md | No implementation. Every datum it must present is queryable from the core today and each query is tested where this document requires it — but supplying a number is not presenting it. |
The distinction in the second row is the one worth keeping. A register that cannot tell "nobody has tested this" from "this is known to be false" will read the same in both cases, and the second is the one that needs a person.
The citation-in-passing failure has now happened three times, and it is time to state it as a rule
rather than a caution. Each time it was a docstring or a comment explaining why something was not
covered, and each time naming the identifier booked it as covered — CM-960 in the §8 suite, the
In-command requirement in the same file, and three more here, including the very requirement whose
paragraph said it was out of scope. The scanner reads every identifier a test file contains and
cannot tell an explanation from a claim, and no amount of care at review time catches it, because the
prose reads as scrupulous either way. The rule: a requirement identifier appears in a test file
only where an assertion reaches it. Everything else — the reasons, the exclusions, the open calls —
is named by document and section here, where no scanner is watching.
§16 needed something the repo did not have: a replay that starts from the record. The existing replay test says so about itself — it drives two games through the same calls and compares them, which proves the engine is deterministic but cannot prove the record is sufficient, because both runs had the caller feeding them. The two claims are different, and only the second is what a save file, a desync investigation or a spectator stream rests on.
A replay driver now exists in the test suite: it starts from the setup, the seed and the recorded turn records, re-runs the game through the public entry point, and compares. It found nothing — the log is sufficient today, which is the answer one wants and could not previously assert. It is worth saying what that test would have caught: a mutation adding a module-level counter to the Cascade, or dropping a single order kind from the record, diverges the hash immediately, and neither is visible to a reproduction test.
The driver lives in the tests because the production entry point is the save-format increment's, and that increment owes one more thing than it did before:
TM-2460has no implementation and, more to the point, nowhere to live. A replay whose recordedRulesVersiondiffers from the build's must be replayed under the recorded version where available and reported as incompatible where it is not. Nothing compares the two, because there is no entry point at which to compare them — the version is carried, hashed and recoverable, and then never read. Today the consequence is mild, since a mismatch shows up as a divergent hash at the end; the failure it invites is the one the requirement names, a log replayed approximately and believed. Recommendation: the version check is the first thing the replay entry point does, and it belongs in the same increment that creates it.
The three other uncovered requirements are 05-multiplayer.md's activation-order hash exchange,
06-ai.md's prohibition on invoking AI code during a replay — there is no AI to prohibit — and the
obligation to bump RulesVersion when a rule changes, which is a process the reference-hash gate
enforces at the moment of the change rather than a behaviour a test can observe.
The turn model is finished, in the sense that matters: every requirement in it is either tested or has a written reason why not. §1, §2, §3, §7 and §12 join the seven sections already complete, and what remains across the whole document is fifteen requirements in four groups, each recorded above — the host-side deadline and reserve configuration (§5), the In-command condition that waits on Formations (§8), the four in §16 that belong to multiplayer, to AI, to process, or to a replay entry point that does not exist, and the six in §19 whose counterparties are unbuilt or, in one case, whose contract is broken rather than untested.
§2's clauses are prohibitions on how the engine may reach an answer, not on the answer, and the only way to test one is to build the same situation two ways and show it does not move. Four variations carry that section: the seed (a Cascade that consulted the PRNG outside combat would answer differently under a different one), the order the host listed the starting units in, the route by which a committed state was reached, and what the unit behind the acting one has been told to do — which is the clause §2's own rationale calls the easy one to get wrong.
§12 found a defect in a clock. reckonUnitClocks guards age against being credited to a unit
created in the same Reckoning — that is what TM-1940 is for — but did not guard the on-tile count.
A unit placed by step 4 has not changed tile, so step 5 immediately credited it with a full turn on
ground it had occupied for none. The consequence runs through three requirements: 13-command.md's
CM-390 derives Entrench from turnsOnTile >= 1, so a unit built last night opened its first
Orders phase already deriving the deepest defensive Stance; it therefore took −12 initiative rather
than Hold's −6, on top of the −8 it already owes to Forming Up, making a new unit slower than
TM-960's curve intends; and at the following Reckoning it would accrue a dig-in level under
TM-700 without its owner ever having chosen to dig, which is precisely the tempo price that
requirement's rationale says entrenching must pay. Fixed by extending the existing guard. The
reference hash did not move, so no fixture depended on it.
One observation recorded rather than changed. Two hosts describing the same game with the starting
units listed in a different order produce identical games — the units come out with the same ids,
sequence numbers and tiles, because TM-850 sorts the enumeration — but different state hashes,
because the setup block is canonicalised verbatim, listing order included. In practice both peers
read the same scenario file and no divergence arises; the hazard is a host that re-serialises through
an unordered structure, which would desync at the first comparison and name a bug that is not there.
Normalising the block would move the reference hash and is therefore a RulesVersion decision
(TM-2450) rather than a fix to make in passing.
ReturnToPoststill fires on voluntary movement, which theCM-380ruling narrows to three displacement causes and which has not landed (it needs a persisteddisplacedmarker, and that is hashed state). It is visible now in the shape of the §10 fixtures: a unit with a Hold Posture and a MoveTo Task gets a Reflex pushed above the Task on its second activation and is dragged back, so every test of the Task level stands a Screen beneath it instead. That workaround is a reasonable marker of the defect's real cost — it makes "standing Posture plus a one-off Task", which is the ordinary way to use the stack, behave surprisingly.
Two cautions the register's first real use taught, both about reading it rather than about the
code. A test that cites a requirement is not necessarily a test that exercises it: the first
TM-1280 test compared two empty event arrays, and would have booked the requirement as covered
while asserting nothing at all. A test written against a property no other test reaches is worth one
deliberate check that its observable actually moves when that property breaks — the §9 suite was
accepted only after each of its assertions was shown to fail against a deliberately broken engine.
And the headline number can rise while the work improves: covering the first requirement in an
untouched section reclassifies that whole section out of "unbuilt, expected" and into the risk
surface, so the §9 work moved 14 requirements out of the middle row and brought 6 in behind them.
The corollary is a discipline — cite a requirement in a test only where the test reaches it, because
a citation in passing books its entire section as started.
The roster increment moved the middle row up, 536 → 546, by the mechanism the cautions above describe: its tests are the first to cite anything in four sections of 02-units-and-industry.md, so those sections left the bottom row for the middle one and brought their untested remainder with them.
The 546 is an upper bound on work, not a count of defects. Some of those requirements are contracts on other documents, presentation obligations or performance bounds that a unit test cannot express, and the honest response to those is to say so at the requirement rather than to write a test that pretends. The number's value is that it points at sections, and the sections are real.
A caution for anyone reading the increments above as progress toward the exit. The world
increments are landing as tested modules, but most of them are not yet wired:
defaultCascadeRules still answers tileEntryEffects with cost 1, throws from chebyshev and
settleCityCapture, and no-ops resolveAttack. Only tests supply real implementations. So no unit
moves over real terrain through the Cascade yet, no game can end, and Milestone P0 — a human
playing a tier-1 game in the CLI — is unreachable until the setup slice wires the seams and the
combat, economy, units and victory increments arrive. The seams are the integration surface and
they are the part nothing currently covers.
The units increment, first slice (2026-09-05): the schema, the validator, and the tier-1 roster.
packages/core/src/units carries the complete unit-definition schema of 02-units-and-industry.md
§2–§3 — every field, every closed enumeration — and the set validator of §4, as a pure function of the
document that reports every failure in one pass (US-800). The shipped set is a JSON document in
the format of US-700, loaded and validated at module import so that a broken roster fails the first
import rather than the first game, and it holds tier 1 exactly as §8.1 and §8.2 print it: the five
manoeuvre units and the three auxiliaries, the latter priced by 12-economy.md as the ruling in §8.2
requires. setupUnitFrom turns a definition into a starting unit, so a game can now be built from
the roster rather than from a test fixture, and the first such game activates its Scout, Field Gun
and Infantry in exactly the commitment order US-290 argues for. The build times of §12.1 come out
of the data to the turn.
The schema is complete; the roster is not, and the roadmap says so above. Tiers 2 to 4 are authored
against the same schema, and until they are the coverage checks of US-110 and US-480 report the
gap as warnings — ten classes and ten flags no shipped unit carries — which a test pins exactly, so
that the moment the roster is complete the warnings become the CI failures those requirements ask
for.
Pointing the validator at the roster found the specification disagreeing with itself twice, and
both are ruled in docs/design/02-spec-rulings.md: US-390 required every bombarding class to
engage land, and three shipped warships bombard while engaging sea alone; and zoc and
mayOccupyCity are required by the schema, read by 01-game-rules.md and 13-command.md, and assigned
by no table in §8. Three further inconsistencies are recorded here rather than ruled, because each
belongs to a document another increment owns:
EC-1620names two flags that do not exist. It says a Depot "MUST enter play as a unit with … the flagsnonCombat,noZoc", and neither is in the closed vocabulary ofUS-470, which a set naming them would be rejected for. The roster expresses the same facts through the schema —atk 0, an emptyengages,zoc false— and the economy's sentence should say that instead.US-790cites the wrong requirement. It requires the "tier power ceiling of US-1310", andUS-1310is the placement rule for a completed unit; the ceiling is Law 2 of §12.2. The check is not implemented until the citation is repaired and the ceiling's arithmetic named.- The engine has one class string where the schema has two. A unit's
classKeyis now the definitionid(scout,field_gun), the stable key saves reference (US-080); its taxonomyclass(recon,artillery) is a separate field. One reader compares the key against a class value — the recon fidelity bonus ofGR-1270inworld/contacts.ts— and will not fire for a roster-built Scout until it reads the definition.US-410wants instances to carrydefId, which is hashed state and so a RulesVersion question; the smaller fix is a lookup from the key to the definition where the class is needed. Likewise the Ledger's defaultcadreClassKeyis'cadre'and the roster's Cadre isadmin_cadre(the art bundle's key); the seam is configurable and must be pointed at the roster id when production games are built from it.
Two things the roster does not yet carry because no document supplies them: reactionReach, which
§8 has no column for, so every unit takes the TM-1580 default of 1; and the widened clause 9 of the
domination test is implemented but has nothing to exercise it until a cross-role pair appears on the
same domain and tier, which tier 1 does not contain.
The CLI increment, and the first played game (2026-09-06): Milestone P0's exit, on the engine's side.
@everylastcity/cli landed the P0 verb set of AR-810 — new (host-minted GameId, a deterministic
starting force with complete Hold/Defend postures), play (hot seat on stdio with the full-state
display), apply, save, replay --verify, hash — over one container as the save (AR-550): a ZIP
holding manifest.json, initial.bin (the setup and the seed; loading re-materializes through the
sanctioned path, AR-200) and orders.log (AR-260 envelopes, gapless seq, chain re-derived on
verify). The load path is AR-030's third entry point, loadFrom, in packages/core/src/turn/load.ts.
Two core fixes landed with it, each named by the first played game. GR-280: the roster bridge
fed the raw move attribute into movementAllowance; tier 1 now runs at move × 4 (scout 20,
infantry 8, militia 4) — the missing multiplier was the near-immobility. GR-330, the minimum
move, was specified but unimplemented: a unit that has spent no MP in the current activation may
enter one adjacent legal tile whose entry cost exceeds its entire allowance, spending all remaining
MP. A third defect was found and fixed by re-playing the game with the ×4 in place: the March
policy's waypoint loop re-tested the minimum move with the original spentBefore after its own
first step had already paid most of the allowance, so the policy consumed a tile the engine could
not pay for and the March cache (CM-1470) desynced by one step — the unit then spent the rest of
the game consuming stale cache the engine rejected as unenterable, frozen for 199 turns. All three
carry named tests, and the ratchet rose 763 → 764 with GR-330.
The caveat above is retired: the setup slice wired the seams, and the combat, economy, units and
victory increments (the Seal — V1 at 0397688) had landed before this one. The first tier-1 game
was played end-to-end in the CLI to a decided finish — elc new (seed 00000000cafe0042, two
players, Small, four units each) into elc play on hot-seat stdin: the 200-turn horizon, winner P1,
final stateHash 01def33c8fb09492 — re-hashed on 2026-09-06 as the canonical form and
the victory seams moved (VC-3320, the EC-1450 claimant, the VC-1470 mark transfer;
2fb6914c6217c32e was that era's value), re-hashed again that day as the March
follow-on the hashed posture carries went from the bare string the old validator
stored to the well-formed order object the fixed one requires (59429c0c7441cc4a
was that era's value), and re-hashed a third time that day as setupToCanonical
began sorting the starting force in the canonical TM-850 clause-3 order (the
AR-090(e) shuffle-harness fix: the container's setup was enumerated in the
placement's walk order, so its state hashes moved) — same seed, orders and
trajectory, record for record, at every re-hash —; the container
(artifacts/reference-game.elc, re-emitted 2026-09-07 so its manifest
carries the current value) replays at 601 records with the chain
re-derived, elc replay --verify fully green on a fresh clone, and the
stance-fallback rate is emitted per game (0.9950). The remaining exit items stand as recorded: the AR-960(a) corpus has
passed the P0 stage floor (the first-milestone 25-game minimum is met with
33 — the 25-game idle grid plus the eight TM-2360 hazard vehicles that
landed 2026-09-06, one whole-turn game per named hazard), and identical
replay hashes across the AR-960 matrix are verified locally (the browser
leg 221/221 on chromium, firefox and webkit — the 187 determinism
primitives plus the full 33-game corpus, replayed against the same
expected-hashes.json the node lane uses; the webkit leg on this host
without root, its dependencies extracted to user space because noble ships
no WPE packages — and the golden hash unchanged through both core
fixes), with the macOS/Windows leg on CI; the V8 numbers are recorded
against the restated AR-760 with the shortfall named — re-run on the P0 host on 2026-09-06, 14.8 /
35.1 / 82.2 ms per activation at 25 / 100 / 250 units with fog at 73–93 % of the cost, the same
single named cause; and the played-to-a-finish clause is now read per the 2026-09-06 ruling above — the authors play it, the container is kept, the findings are recorded, and the independent session is Phase 2's to gate. The hot seat it is played in is pnpm elc new … then pnpm elc play … from the repository root, with corepack enable first on a machine where pnpm is not yet a command; docs/PLAYTEST.md is the procedure, and it draws the map rather than listing coordinates. The last two victory-surface defects (the EC-1450 claimant seam and the VC-1470 double mark) were closed on 2026-09-06 alongside the canonical-form gap (VC-3320); one P0 risk (no P0 path makes a player not-live — the elimination machinery is Phase 2) remains logged in SESSION.md's Possible issues.
2.3 Phase 2 — The client, the full rules surface, and an opponent
Make it a game people want to look at, finish the rules, and give the solo player someone to play.
- Web client, browser first. Phaser 3 (WebGL) draws the tile map with LOD; everything else is DOM (React + TypeScript). The same bundle packaged with Tauri 2 is what ships to Windows, macOS, Linux, iOS and Android; packaging is a build step inside this phase, not a phase of its own.
- The Cascade playback is this phase's signature feature and must be treated as one. Under this turn model the player commits before they see, and the playback is where that commitment pays off — watching a plan execute is the reward the model is built around (docs/design/01-decision-turn-model.md, "What this costs, honestly"). A progress bar here is a product failure, not a polish item. Per the same document's first amendment, solo and two-player games must be able to run the Cascade instantly, with optional step-through.
- The Orders phase UI, which is the other half of §1.4's bet: Postures and Sanctions stated in plain language on the unit panel (CM-570), refusals explained in numbers rather than asserted (CM-2000), and the Dispatch surfacing the few units that genuinely need a person (13-command.md §11, ranked within 16 ms for 200 events, CM-2130).
- The rest of the command layer: Formations and Groups, Postings, Consignments and Requisitions, absent-player handling (CM-2010).
- Industry tiers 2–4 — the remaining roster. Note that this is not a second content-authoring project of the kind v0.1 planned across three phases: there is one shipped unit set (US-1340), and the Industry ladder is what phases its contents in front of the player (US §5.4).
- The full Track catalogue, Charters and Sealed Orders, Warrants and Tallies, the Ebb, the endgame instruments, and the victory setup screen with its legibility rules (VC-030).
- A baseline AI seat. Not the Staff — the Constraint Floor: garrisons met, no city left empty, no idle units, no stranded formations, production never halted, Cadre and Depot arithmetic performed. This is arithmetic against specifications that already exist in closed integer form; it is the highest-confidence part of the whole AI design, and it is where opponents in this genre visibly fail. An AI perfect here reads as strong before it has made one interesting decision, and it is the prerequisite for the farm in Phase 3.
- Accessibility baseline (screen reader, UI scaling, colourblind-safe palettes) built on real DOM semantics rather than engine-drawn controls — built in, not retrofitted.
- Onboarding. The tutorial teaches tier 1 and then gets out of the way; the Industry ladder carries progressive disclosure from there, so there is no second complexity system to teach (US §5.4). The encyclopedia is generated from the loaded unit set rather than authored (US-2110).
Exit: a person who has never seen the design plays a tier-1 game to a finish without reading anything and can say what they were deciding; a full four-tier game runs to a decided end against Constraint Floor opponents; accessibility audit passes; 60 fps in the browser on the reference hardware (03-architecture.md AR-700) at the performance fixture's map size, with the Tauri desktop and mobile packages of the same build inside AR-700's device allowances; the Cascade playback of a 150-unit turn is watchable and skippable.
Status of the exit clause, 2026-09-24, one clause at a time, because they are six different kinds of evidence and none stands in for another.
- "A full four-tier game runs to a decided end against Constraint Floor opponents" — met. Standard
map, four Floor seats, reference seed, 200 turns (
node scripts/full-game.mjs --players 4 --size Standard --turns 200): every seat's Industry reached level 4, and the game ended decided — the Horizon Track at turn 200, seat 0 first on 15 cities. Two qualifications, both recorded on SESSION.md's standing list rather than softened here: the decision came from the Horizon, a time limit, not from conquest, which no measured Floor game has reached; and a two-seat Duel on the same seed does not end in 120 turns, because captures in contested ground revolt. The clause asks for a decided end and this is one; it does not ask that the Floor win by force, and the standing list says it cannot yet. - The turn closes this depends on are affordable now: a Standard/4 close at turn 59 took 24 s until 2026-09-24 and now takes under a second, and a Vast/8 close about one second (the standing list's Vast item has the two fixes). A player was waiting on them; the clause's run was too.
- "The Cascade playback of a 150-unit turn is watchable and skippable" — the engineering half is met,
on a development workstation rather than the reference hardware. Measured 2026-09-24 in the running
client (
?map=Standard&players=4&units=40: 160 units in the turn, 40 of them the seat's): the playback is on screen 0.4 s after Close the turn; it plays its 24 grouped steps at about one a second; over twelve seconds of it the frame interval's median was 6.9 ms and its 99th percentile 7.1 ms, with no frame over 33 ms — the display's full refresh throughout; it runs to its end; and Skip takes it from step 21 to 24 in 51 ms. Two things this is not: "watchable" is finally a person's judgement, and the frame figures are from a 144 Hz workstation display, not AR-700's reference machine. The playback waits paused on its first step until the player presses Play, which is a design choice worth the first-time player's test. - Preparing for that person, 2026-09-25: the authors walked the learning game the way a newcomer
would, in the browser at 1280 × 800 and 375 × 812, and fixed what it found before anyone else met it:
- the learning game opening on its own lint warning;
- one right-click both moving a unit and opening a menu;
- a report headed one turn late;
- Carrier offered at Industry 1;
- Formations and Postings leaking through the Field profile;
- dialogs scrolling sideways on a phone. SESSION.md lists each. This is the authors' walk, not the person's test the clause asks for.
- Not yet evidenced, and not claimed: the first-time player's tier-1 finish (a person, not a script);
60 fps at the performance fixture's size on the reference hardware; and the Tauri packages, which do not
exist yet. The accessibility audit's automated half passes in
pnpm verify(check-a11y, and the rendered axe pass); its manual screen-reader matrix is UX-1320.6's and has not been run.
2.4 Phase 3 — Playtest and the tuning pass
This phase is new in v0.2, and it exists because the premise changed. When the plan was to reproduce another game, balance was inherited: the numbers had been played by other people for decades and the job was to match them. Now every number is ours and none has been played. Seven interlocking systems — the Cascade, the order vocabulary, Clash and rout, Integration and Cadre, the Industry ladder, Tracks and the Vigil — have never run together in the same game. That is not a risk to be mitigated at the end; it is the largest single unknown in the project and it gets time on the schedule.
The rules documents already asked for this in their own voices. 02-units-and-industry.md US-2230 requires the design's known hard edges to be "revisited after the first full playtest." US-2220 names two failures no metric will catch. TM-1440 makes the stance-fallback rate the fastest diagnostic in the design and puts a band on it. VC-3300 defines thirteen acceptance metrics with targets and makes a metric outside its target a release blocker. Every rules document ends with open questions that say, in effect, settle this with a build. This phase is where those obligations are paid.
- Telemetry as a deliverable. Every metric the rules documents nominate is emitted per game and aggregated. VC-3280 already requires every victory quantity to be exposed to the self-play harness as unformatted integers; the same discipline is applied across combat, economy and command.
- The AI-vs-AI farm, brought forward from v0.1's Phase 3. It is the only instrument that produces thousands of games, and its second product is balance, not AI strength. It needs the Phase 2 Constraint Floor opponent and nothing more to start earning its cost. VC-3290's CI check — fail the build when a preset's measured median length misses its advertised estimate by more than 25 percent — runs here.
- Human playtest, closed then widening. The farm cannot answer US-2220's two questions, or VC-3300's M13 ("show a mid-game Watch; ask who is winning"), or whether committing orders before seeing them is thrilling or merely frustrating. Those need people, in sessions someone watches.
- A tuning window with an explicit budget. Constants are expected to move; schemas, requirement IDs and the flag vocabulary are not. Each rules document should mark which of its numbers are tuning knobs before the window opens, so that a tuning pass does not quietly become a redesign. Every change bumps its logic version (CM-2050, VC-3220) and the reason is recorded.
- The open questions this phase is chartered to close with a build, rather than with another argument: Chebyshev versus octile movement cost (01-game-rules.md); per-city versus per-order Yard (02-units-and-industry.md); the three-band initiative disclosure and its boundaries (10-turn-model.md); Vigil defaults and Ebb start (14-victory.md); the caution coefficient and frontage behaviour on the AI side.
Exit: the VC-3300 metrics computed and inside their targets, or explicitly and publicly revised; the stance-fallback rate under TM-1440's 8 percent band across a corpus of real games; the alarm table of 02-units-and-industry.md §14 clear; every open question listed above closed by a build with the evidence recorded; a written list of every constant changed during the window and why. A metric that cannot be brought inside its band exits this phase as a named design defect, not as a silently widened target.
2.5 Phase 4 — The AI proper
The headline feature, and the largest engineering ambition in the project after the turn model.
- The plugin host — the browser's and Node's own WASM engines, so there is no embedded runtime to ship or port per platform — plus the player-view contract, budgets, and determinism handling. The AI is a client and its orders enter the authoritative order log like any player's, which means a replay replays the logged orders and never re-runs the AI: changing the AI invalidates no saved game. That asymmetry is worth stating loudly to whoever schedules this work — command-layer changes are expensive and rare (CM-2060), AI changes are cheap and continuous, and the AI can therefore improve every release for the life of the product.
- The layered agent: perception and fog-honest belief, strategy, and the operational search, over the Constraint Floor already shipped in Phase 2.
- The evaluation function and the forecast. The economy and victory are exactly forecastable in
closed integer form (12-economy.md, 14-victory.md), and combat is exactly predictable through the
same
winPermillethe player sees (CB-2250). Strength here comes from evaluation and forecasting, not from tree depth — a full-Cascade rollout is expensive enough that thousands of simulated futures are simply not available, and any plan premised on them should be rejected on sight. - The plan-ranking falsification test, built before the search layer is optimised. Sample turns from farm games, take the top plans by the AI's own ranking, play each forward in the real engine, and correlate predicted rank against true outcome. If the correlation is weak the search layer is decoration and the documented fallback — greedy assignment plus local search over a good candidate set, on top of a perfect Constraint Floor — is taken. This matters more than any optimisation: the fallback is still a competent opponent, which is what makes the ambitious version safe to attempt.
- Difficulty and character. A ladder driven by search and knowledge rather than by hidden bonuses, with handicaps available separately and disclosed. The floor is absolute: zero Constraint Floor violations at every tier, because an easy opponent that leaves cities empty is not easy, it is broken.
- Self-play tuning, at whichever level the measurement supports — offline weight tuning first, and anything beyond it justified by a measured gain rather than by ambition.
Exit: the mid-tier AI beats a competent human less than half the time but more than never on a standard map; every tier beats the tier below it at a clear margin over a large round-robin; zero Constraint Floor violations at any tier, as a build gate rather than a metric with a band; the farm runs a nightly batch with zero desyncs and no illegal orders; and the human-survey row nobody can automate — did the opponent do anything that surprised you? — comes back yes more often than not.
2.6 Phase 5 — Multiplayer
Connect people. Async is the primary mode and this turn model is unusually kind to it: one submission per player per turn, whenever each player happens to sit down, rather than a network round-trip per unit (docs/design/01-decision-turn-model.md, "What decided it").
@everylastcity/serveron Fastify + PostgreSQL: accounts (guest-first), lobbies, async games, notifications. It imports the same@everylastcity/corethe client does, so authoritative resolution and client-side preview are one implementation.- Correspondence play without a live room: the order log is persisted and a session is materialised only while someone is connected. Deadlines, stalls, absence and substitution are already specified by the turn model and the command layer (CM-2010); this phase implements them over a network rather than inventing a second set.
- Real-time online and LAN. Colyseus supplies rooms, matchmaking, presence and reconnection only; orders go up and events come down over the order log (03-architecture.md AR-250), never as engine state snapshots. Hot seat is already present from Phase 1.
- Reconnection, host migration, mode switching, spectating.
- Self-host Docker image (Node + PostgreSQL) with parity to the official instance.
Scheduling boundary (owner ruling, 2026-09-19). Phase 5 does not include the signed-file mode of 05-multiplayer.md §10. Its MP-750–MP-880 contract is retained intact in Phase 7. Bundle-specific work must not be selected before this phase's exit, except where shared container, replay, signing, encryption or verification machinery independently satisfies a current-phase requirement. This does not move server-hosted correspondence: §9 remains a Phase 5 launch candidate.
Exit: an async game survives a week of real play across three time zones and two platforms; a self-hosted instance runs the same test suite green; fog enforcement holds against a client modified to ask for hidden state; an eight-player Cascade resolves and plays back inside its budget.
2.7 Phase 6 — Openness
Hand the game to its community. The design already made this cheap: a custom unit set is a single JSON document using the closed flag vocabulary, with no scripting of any kind (US-2050, US-2060), and its content hash is in the canonical game hash so a version skew desyncs loudly rather than silently (US-2090, US-2100).
- Unit set editor; map and scenario editor with live generator preview and playtest-from-editor.
- Asset packs; localisation pipeline.
- World-builder plugin ABI alongside the AI ABI.
- Workshop: publishing, versioning, dependency resolution, compatibility hashes, moderation.
Exit: a third-party author, given only the public docs, ships a custom unit set and a map generator that other players install in-game and use in a multiplayer match without desync.
2.8 Phase 7 — Long tail
- Turn-bundle export/import — signed file play, the email and fileshare heir (05-multiplayer.md MP-750–MP-880), including its client/CLI import and export flow, order packets, friendly-trust labelling, stale/tamper detection, bundle-only mode switching, and the optional relay drop-box. The container, signature and encryption groundwork built for other purposes is reused rather than removed.
- Statistics, history, leaderboards.
- Platform polish: store presence, cloud save conflict UX, telemetry dashboards.
- The second tuning pass, on population-scale data rather than farm data. Phase 3 tunes against thousands of AI games and dozens of human ones; this is the first point at which the metrics of VC-3300 can be computed over a real player population, and some of them will read differently.
Exit: the VC-3300 metrics recomputed on live data with the deltas from Phase 3 explained; no Track, Charter or unit outside its balance band on population data; and a four-seat signed-file game completes a full turn through one opening bundle and three order packets, rejecting a stale packet and a tampered hand-off by name.
2.9 Old phase numbers
For readers of v0.1 and for any document that cites a phase by number: old Phase 3 (AI) is now Phase 4, old Phase 4 (multiplayer) is Phase 5, old Phase 5 (modding) is Phase 6, and old Phase 6 (the Enhanced set and long tail) is Phase 7 with its unit-set content removed — there is one shipped set, and the Industry ladder rather than a fourth content pack carries the complexity curve. Phase 3 is new.
3. Dependency structure
Phase 0 fixed-point ✓ · determinism CI ✓ · lint gate ✓ · browser leg ✓ · doc sweep ✓ · rights decision ✓
│
Phase 1 turn machine + order vocabulary + core rules (tier 1) + CLI ──── Milestone P0
│
Phase 2 Client · full rules surface · Constraint Floor opponent
│
Phase 3 Playtest & tuning ◄──── the farm needs Phase 2's opponent
│ (continuous from P0; a dedicated window here)
├──────────────┐
Phase 4 AI Phase 5 Multiplayer
└──────┬───────┘
│
Phase 6 Modding & workshop
│
Phase 7 Signed file play · stats · polish · second tuning pass
Phases 4 and 5 can run concurrently once Phase 3's tuning has settled the constants both are built against; neither depends on the other, and the ordering shown reflects the owner's stated priority rather than a technical dependency. Phase 3 is drawn as a phase because it needs dedicated time, but it begins at Milestone P0 and never stops.
4. Risk register
| Risk | Impact | Mitigation |
|---|---|---|
| The conditional-order vocabulary ships thin, and the turn model collapses into blind WEGO. Named in docs/design/01-decision-turn-model.md as the single biggest execution risk in the design, and it is a scheduling risk as much as a design one: the vocabulary is the easiest thing in the plan to defer, because it looks like a UX convenience and is actually the load-bearing element | The central design decision was wrong. Committing orders before seeing them becomes a gamble rather than a skill, players correctly report the game feels like guessing, and the fix is a rework of the spine rather than an addition to it | Ship the six Postures (CM-330) and four Sanctions (CM-570) in Phase 1 alongside the turn machine, not in the client phase (§1.4). Instrument the stance-fallback rate (TM-1440) from the first playable build and treat its 8 percent band as a gate, not a dashboard — it is a direct proxy for "is the player's intent surviving contact with the turn," it responds immediately to changes in the vocabulary, and if it is unhealthy nothing else about the model matters. Note the asymmetry that makes early investment cheap and late investment expensive: command-layer behaviour is core and versioned, so every later change to a Posture algorithm or a Sanction test breaks stored replays (CM-2060, CM-2070) |
| AI ambition exceeds what a small team can build. The owner has named advanced AI as the headline feature, which is exactly the condition under which an over-promised AI ships weak and the failure is invisible to the team until players find it in week one | The pillar the product is sold on is the thing players complain about; and because the failure is a plausible-looking opponent rather than a crashing one, it survives internal review | Build in confidence order, not ambition order: the Constraint Floor in Phase 2, because it is arithmetic against closed integer specifications and is where opponents in this genre visibly fail; the search layer only after its plan-ranking falsification test passes (§2.5), with a named fallback — greedy plus local search over a good candidate set — that is still a competent opponent. Keep the AI a client rather than core, so its orders enter the order log and improving it never invalidates a save. Make "zero Constraint Floor violations at every difficulty tier" a build gate. State the honest ceiling in public — beat most players, lose to experts — rather than letting marketing set it |
| Seven interlocking new systems, 1,790 requirements, never playtested together. Nothing in the design has been played. Every constant is a first guess, and the systems couple: Industry feeds the economy, the economy prices combat losses, combat feeds Tracks, Tracks set the tempo the command layer is sized for | The failure is not a bug. It is a game that is coherent on paper and unpleasant to play, discovered late, when the constants are baked into fixtures, saves and player expectations | Phase 3, with dedicated time and exit criteria (§2.4). Milestone P0 as early as the design allows, on the smallest rules surface that is a game. Instrument before tuning (§1.9) so the first balance conversation has numbers in it. Freeze schemas and requirement IDs, not constants, and mark the knobs in advance. Treat each rules document's "what could go wrong" section as a work item rather than as reflection |
| The design's own open questions are load-bearing and unsettled. Each rules document ends with questions its author could not close by argument — the movement metric, the Yard's granularity, how much initiative to disclose, Vigil defaults. Several change how the game feels, not merely how it computes | Answering them by argument produces a defensible wrong answer; answering them late produces a rework across documents that already cite each other | They are chartered to Phase 3 and closed with a build (§2.4). Where a question can be settled by a cheap experiment, the experiment is the deliverable, not the memo |
| Committing before seeing loses players who came for the interactive feel. An honest cost the turn model decision names rather than hides | Bounce on first session; reviews describing the game as "you don't get to play, you submit forms" | Instant Cascade with optional step-through in solo and two-player games, and no Orders deadline unless one is asked for, per the decision document's first amendment. Treat playback quality as a Phase 2 feature (§2.3), because the playback is the compensation. Measure first-session abandonment during Phase 3, and treat a bad number as a presentation problem to solve rather than a model to relitigate |
The determinism rules are a discipline, and disciplines decay. TypeScript has one number type; nothing in the language stops a plain * on two plain numbers from entering rules math a year in |
The violation is invisible at review — one correct-looking expression — and surfaces later as a rare desync, or a replay that diverges at turn 300 with no obvious cause | Already mechanised rather than discouraged: the branded Fixed/Int types, the lint bans, and the cross-platform hash-agreement job, all in place from Phase 0. What remains is upkeep — nightly re-replay so a violation surfaces within days rather than inside a player's save, and an audit of every eslint-disable in the core at each release, because the real leak is suppression rather than absence |
| V8 performance on large-map turn resolution against budgets written for AOT-compiled native code | The AR-760 budgets may be missed at the upper map tiers, and the miss lands hardest on the target with the least headroom — the browser tab, which is the primary one | Re-baseline in Phase 1 while state layout can still change; typed arrays and structure-of-arrays state, no allocation on hot paths, monomorphic call sites, simulation in a worker. Note that the turn model helps here: the Cascade is computed once per turn rather than interactively, and playback is decoupled from computation. If a budget still cannot be met, the fallbacks in order are: relax the budget and publish the new number; cap the top map tier on the weakest targets; and only last, a WASM kernel for one isolated hot path — acceptable only behind a bit-identical differential test, since a second implementation of anything is itself a determinism risk. The AR-760 fixture must first be restated in terms of the current design |
| The implementation is largely AI-authored | The dominant failure mode is not a crash but a plausible-looking rule that is subtly wrong: code that reads correctly, survives a casual review, and computes the wrong number in one branch. Volume makes it worse — more code is produced than anyone will read closely. The withdrawal of the fidelity premise makes this worse, not better: there is no longer an external oracle that would have caught a wrong constant | Anchor verification to the requirement IDs, which are now the only authority: every requirement maps to at least one named test, plus the worked test vectors the documents nominate (VC-3310 is the pattern), property tests (AR-950), determinism CI (AR-960), coverage gate and fuzzing (AR-990). Spend scarce human review on spec-to-test correspondence — does this test assert what the requirement actually says — because that is precisely what a reviewer skimming plausible code will not catch. Prefer loud failure to defensive defaults; a silent fallback hides the mistake until it reaches a save file |
| Scope: fifteen documents describe more than a small team can build | Nothing ships | Phases 1–3 plus hot seat and a Constraint Floor opponent is a legitimate product; everything after is additive. The ordering above is chosen so that the cut line falls in a defensible place at the end of every phase |
| Rights and naming unresolved at ship time | Cannot publish, though development is unaffected | Much smaller under clean-room than under the withdrawn premise (§2.1). Resolved: the owner took the decision offline on 2026-09-03 and Phase 0 is complete (§2.1) |
Retired with the fidelity premise, named here so a reader of v0.1 can see they were dropped deliberately rather than forgotten: the drain and combat-mod formulas may not be recoverable from the original — there is no original and both systems are specified in 11-combat.md and 12-economy.md as original design; reference data may be unavailable — no reference data is needed or wanted; and the Enhanced set's special cases resist data-driven modelling — there is no Enhanced set, and the closed eighteen-flag vocabulary of 02-units-and-industry.md §3 with its ban on branching by unit id (US-060) is the structural answer to that whole class of problem.
Retired earlier by the stack change, and still retired: Godot C# on iOS cannot host WASM plugins — the plugin host is now the platform's own WASM engine, present in every browser and in Node; and browser delivery is unreachable — the browser is the primary build rather than a second client that would have had to be written twice.
5. What "done" means for the project
The v0.1 answer was that a veteran could play the game they remembered and not point to a rule that behaved differently. That is no longer a goal; it was never the owner's goal, and it is a ceiling rather than a target. The replacement is harder to measure and worth more:
- A player who has never heard of this game's ancestor plays it for the loop's own sake — you start small, cities produce units, units take cities — and can say what they were deciding on the turn they just took.
- A six-player correspondence game runs to a decided end without anyone spending an hour hunting a last holdout, because the victory system dissolved the mop-up rather than patching it.
- A player who is losing has a path back and can see it, and therefore stays in the game.
- The AI is the reason people play alone, not the thing they tolerate until a human is free.
- A hundred turns in, commanding two hundred units, the player is still making decisions rather than performing arithmetic.
- And a modder ships a unit set on the first afternoon they try, because it was always just data.
Open questions
- Team size and shape is unstated. The phases are sequenced by dependency, not staffed. A one-person effort would likely cut Phases 6–7 and ship 1–5; a two-person effort probably has to choose between Phase 4's AI and Phase 5's multiplayer rather than doing both. This plan does not make that choice and cannot.
Where does the AI specification live?Settled:06-ai.md. It has been rewritten from the four AI design documents and carries 219 AI-prefixed requirements specifying the Staff — the layered architecture, the fairness constraints, the six Ranks, the personality roster and the self-play pipeline. It keeps its original number, so every existing citation of it still resolves. Phase 4 has a normative document to point at.- Who owns the telemetry contract? Phase 3 depends on metrics nominated across five documents (TM-1440, 02-units-and-industry.md §14, 11-combat.md, 12-economy.md, VC-3300) and no document currently owns the emission, aggregation and retention of them. 08-services-platform.md is the natural home. Until someone owns it, Phase 3's exit criteria have no implementation.
- Is a public beta warranted, and when? Without an external oracle, outside play is worth more than it was — but a beta of an untuned game teaches the wrong lesson and an audience can only be burned once. The current instinct is a closed playtest during Phase 3 and a public one only after its exit criteria are met, but that is a judgement about audience, not engineering.
- Does the AI ship before, alongside, or after multiplayer? §2.9 puts AI first on the owner's stated priority. The counter-argument is that async multiplayer is unusually cheap under this turn model and would put real humans in front of the design sooner, which is exactly what a never-playtested design most needs.
- How much tuning budget is spent before first outside play, and who decides a metric is "close enough"? VC-3300 makes an out-of-band metric a release blocker; someone has to be allowed to revise a target publicly rather than quietly, and the plan does not say who.
- Does the farm need to exist before Phase 3, and who pays for the compute? The farm is the only instrument that produces thousands of games, it is what VC-3290's CI check and most of VC-3300 assume, and it has an ongoing cost nobody has budgeted.
- Do the AR-760 and AR-770 budgets survive V8 at the upper map tiers? Related: does the answer differ between the browser build and the Tauri builds, and is a tier cap that applies only in-browser acceptable given that the browser is the primary target? The fixture itself must be restated before the question can be answered, since as written it specifies content that no longer exists.
- Where do the human review gates sit, given that most code is AI-authored? Reviewing every diff neither scales nor catches the failure that matters; reviewing spec-to-test correspondence might. §1.8 asserts the principle and staffs nothing.
- Do the Tauri packages ship alongside the browser build at the end of Phase 2, or one phase later once store presence is being handled anyway?
Closed since v0.1: which fixed-point representation — signed Q16.16, one global scale, chosen and implemented (§2.1), and largely moot besides, since most of the rules surface specifies integer-only arithmetic (US-030 and the economy and turn-model documents throughout). Whether the browser client is a second implementation — it is not; one web client, packaged by Tauri 2. Whether iOS can host WASM plugins — the platform's own WASM engine hosts them. Whether Phase 0 VERIFY resolution needs cooperation from a rights holder — void with the premise; there is nothing to verify against and nothing to ask for.