# The standalone, and the measured distance to the byte-match Lane S2, 2026-09-08. Engine branch `wip/standalone`; full documentation in `sots-engine/docs/S-standalone.md`. This note records what the lane measured, what it declined to implement, and the two numbers the campaign should track from here. --- ## 0. The headline `sots_turn` loads a real save through the engine's own reader, walks the **published phase order of all three turn drivers**, runs what we hold, prints what we do not, and writes a save through the engine's own writer. ``` turn1-state.sav -> turn2-state.sav (a real End Turn) baseline (a standalone that does nothing) 209 leaves diverge after one standalone turn 204 leaves diverge closed 5, regressed 0 turn2-state.sav -> turn3-state.sav baseline 108 -> 103, closed 5, regressed 0 ``` Phases: **14 of 44** turn-driver phases modelled, **7** committing anything; **2 of 37** of the post-combat tail. Generator words modelled per turn: **0** of the 18–20 consumed. Leaves are `verify/state-checksum/state_checksum.py`'s named leaves; every one of the six saves involved reported `coverage: PROVED` on the same run, so the diff cannot be hiding anything. Regenerate with `tools/standalone_report.py`; outputs land in `verify/results/standalone/` and the dashboard's new section 6 reads `status.json`. ## 1. What the scaffold is for Everything the milestone needs already existed in pieces — a save reader with 100 % named coverage, a verified budget roll-up, a verified research slice, a mechanism-verified movement model, and byte-for-byte maps of both turn drivers. What did not exist was **a place to put them and a number that says how far they get**. That is the whole of this lane. The phase catalog (`src/app/phase_catalog.cpp`) is the roadmap: all 32 + 12 + 37 phases, each carrying a status and a note. Running `sots_turn --phases` prints the turn, and every unimplemented phase prints itself. There is no way for a phase to be silently absent, and `app_catalog` fails the build if a table develops a gap or if anything claims to be `verified` (which in that table means "compared against the live game" — lane S2 held no VM). ## 2. The finding that justifies the design: a committed phase can make things worse The first version of this lane implemented `StrategyServer::ProcessTurn` phase 31's player-status restore — `Status = 1` — and committed it. The comparison tool immediately reported **two regressed leaves** on the `turn2 → turn3` pair: two `Player.Status` words that **agreed** with the oracle before the turn and disagreed after it. The phase writes 1. The post-turn file carries 4. A load resets it to 0. So a writer between phase 31 and the autosave is unaccounted, and the input save happened to already carry the right answer. This is the same shape as the campaign's oldest lesson in a new place: running more code is not the same as knowing more. The standalone therefore separates **modelled** from **committed**, and a phase whose inputs are not modelled is evaluated, reported and *not written* unless `--commit-blocked` is passed. `regressed` is reported next to `closed` in every run, never netted off. Five phases are blocked behind one unresolved formula (below); `S31` and `T31` are blocked behind their own; `P11` is blocked behind the event-text table. ## 3. Two things this lane declined to implement **The `TShn` / `ltis` counters.** 18 leaves of the remaining 204 are `TShn` and `ltis` moving `1 -> 2` on 8–10 systems, on both turn pairs. They look exactly like per-turn counters and a `+1` would close 18 leaves in ten lines of code. Nothing in the campaign names their writer, so "+1 per turn across one observed turn" is a hypothesis, not a reading, and rule 6 says label it as one. They are the **cheapest measured target on the board** and they are named here so the next lane can close them properly rather than plausibly. **The RNG state.** The generator advances during a real turn; the standalone leaves the blob byte-identical by default. An advanced-but-incomplete state is wrong in a different way from an untouched one, and the untouched one at least reports the truth. `--commit-rng` is there for the day lane Z's ledger closes. ## 4. One hypothesis under test, and it survived a changed workload `ProcessColonyTurn` takes `stable` as an input; in the original it is a callee's verdict. The standalone stands in `owned && !abandoned && !destroyed`, labelled a hypothesis in the code and printed as one in the run log. It drives `ntdev`, which is a named leaf, so it is falsifiable. On `turn1 → turn2` it judged 3 of 28 systems stable and closed exactly the 3 `ntdev` leaves the oracle moved. On `turn2 → turn3` — a different turn, a different set of orders — it closed the same 3 again. **Six agreements, zero disagreements, across two workloads.** Not proof; recorded as such. ## 5. What is now measurably in the way Ordered by what must be solved, not by size. | # | blocker | cost in leaves on the reference pair | who can close it | |---|---|---:|---| | 1 | **the RNG ledger** — 18–20 words/turn, none modelled, and the generator is saved state | 1 leaf, and it makes a byte-match *arithmetically impossible* | lane Z (in flight); nothing in `src/app` | | 2 | **the population → base-output term** — one unresolved formula that blocks `P01 P02 P05 P06 T31` | ~10 directly (`Sav`, `BnkPr`, `BnkEl` on 4 players), and it gates 5 of the 44 phases | a formula lane against the live game | | 3 | **the post-combat tail**, 37 phases, none implemented, and the driver the autosave is written from | 24 (`turnstats`) + the bankruptcy limits + observed designs + player reports | its own milestone | | 4 | **the per-player system-visibility record** (`nve`) | 32 — one mechanism, eight repetitions | spine phase 24 or tail phase 21 | | 5 | **the event pipeline** — buckets, ids and localised text | ~20 across the players | needs the string table | | 6 | **`Summary.Checksum`** — algorithm unknown | 1, and it is the last leaf to fall | — | | 7 | **the `Player.Status` writer** — phase writes 1, file carries 4 | 4 | small, self-contained | | 8 | **`ModCount`** — advances 12–44 times a turn from writers across both drivers; we model 2 | 1 | falls out of implementing the other phases | ## 6. Gates run, separately ``` tools/clean_room_check.sh -> clean-room check: OK (with src/app + tests/app staged) ctest --preset host -> 100% tests passed out of 38 (was 36; +app_catalog, +app_turn) app_turn with SOTS_SAVES_DIR set -> 11 saves driven, 0 failures ``` `src/shim/` was not touched, so no CT111 cross-build was required. ## 7. What the corpus cannot answer The two pairs above are the only true End-Turn transitions we hold. The other nine saves are single states: `app_turn` drives a turn over each of them and asserts the file survives, but there is no oracle to diff against. A third and fourth consecutive-turn pair — especially on a Zuul game, where the research and node-travel paths differ — would make every number in §0 sturdier for the cost of two End Turns on VM140.