# Roadmap Groomed 2026-09-08. Replaces the phase-0/phase-2 backlog, all of which shipped. The board (`board.md`) is the ledger of findings; this is the plan. **The product is `sots-engine` — our code.** RE findings are inputs to it, and the measure of progress is how much of the game runs in our engine, verified against the original. Every number here is measured; the source is named. ## 1. The engine today (`sots-engine` main `0592104`, 31,980 LOC, ctest 45/45, clean-room, MIT) | module | LOC | what it is | verification | |---|---:|---|---| | `mars/parse`, `mars/text` | 1,774 | brace-block + flat-KV + CSV readers (the `Mars::Script` pull tokenizer, first-wins keys) | oracle 1,531/1,531 + 64/64 | | `mars/vfs` | 788 | `.gob` ZIP + native-override VFS (the `./Mods` mechanism) | 10,268 files CRC-clean | | `mars/stream` | 5,407 | save reader/writer, 386-class wire schema, `SchemaProbe`, `CoverageArchive` | **100% named coverage, byte-identical round-trip, 11 saves** | | `mars/rng` | 244 | MT19937 + all seven entry points (`NextFloat/NextInt/Chance/NextUInt/FloatRange/IntRangeBell/GaussianRange`) | live: every draw of a turn attributed | | `game/data`, `game/design`, `game/config` | 3,835 | weapons/sections/tech/strings catalogs, design rules, hull class, tuning | 229,042 values 0 diffs; 127/127 designs | | `game/effects`, `game/events` | 1,531 | tech effects, `EventStorage` (turn-bucketed, dedup) | live: research slice 35 calls / 0 div | | `game/sim` | 3,382 | economy (`ComputeBudget`, output term), research + unlock cascade, colony, movement | live: budget 4,437 / output 24,357 / research 35 / movement 45 calls — all 0 div on declared regions | | `game/nav`, `game/combat` | 1,163 | route classifier + failure bits; retreat **planner** | offline vs 58 waypoints / 46 plans, 0 failures; retreat never instrumented | | `app` (`sots_turn`) | 1,754 | **the standalone**: load save → walk all three drivers' phase order → write save; divergence + completion metric | 11/11 saves run; ref pair **209 → 204** leaves, closed 5 / regressed 0 | | `shim` | 769 + hooks | proxy `binkw32.dll` + MinHook trace/compare/replace harness — **scaffolding, to be displaced** | it is the instrument, not the product | What the engine can do end to end today: read every shipped asset and every save byte-for-byte, run the economy/research/movement/output pieces to the original's numbers, and step a turn with **14 of 44** driver phases and **2 of 37** tail phases modelled (8 committed). It cannot yet reproduce a turn. ## 2. The milestone, restated > `sots_turn` loads a save, runs one strategic turn, and writes an autosave that byte-matches > the original's. Today showed the milestone as first stated is **underdetermined on the reference pair**: `ModCount` = 2 + one per command applied from every player's block, and on `turn1-state.sav` the human block is empty yet 10 non-driver bumps occur — **the AI's orders are generated during the turn, after the autosave**. `Summary.Checksum` is fed by `ModCount`. So, two rungs: - **Rung A — byte-match modulo the command stream.** Every leaf except `ModCount`, `Summary.Checksum`, and what the AI's own orders touch. **No AI needed.** Near target. - **Rung B — full byte-match.** Needs AI order generation in our engine (§4.1), or recorded AI command blocks fed as input. Rung B is where the engine becomes the game. ## 3. Engine work to Rung A (ranked by leaves closed per lane) | # | engine deliverable | module | RE input (done unless noted) | closes | |---|---|---|---|---| | 1 | **income chain**: `TradePointsToMoney` (`incomeModifier/14000`) + AI difficulty ×1.1 | `game/sim` | lane N/Y; 25-record `BnkEl` oracle, 6/25 match now; **multiplier not on the wire — one Ghidra read** | P01/P02/P03/P05/P06/T31, ~82 of 204 leaves, 2 RNG words | | 2 | **wire `ShipCensus` + alliance mask into `BuildTurnRecord`**, commit T36 | `app` | lanes D2/A2, 480+560 fields 0 mismatch | +24 turnstats leaves, clean | | 3 | **encounter-detection draws** in the generator model | `game/sim` + `app` | lane I bound; **lane H measuring now** | last 2 RNG words | | 4 | **`nve` visibility record** | `game/sim` | one mechanism ×8, unread | 32 leaves | | 5 | **post events into the save's turn bucket** (P11 + tail event phases) | `app` + `game/events` | model exists; wiring only | events subtree | | 6 | **`Player.Status` restore** (S31) | `app` | needs the writer between tail 31 and autosave — **watchpoint** | S31 regression | | 7 | **tail phases** — bankruptcy decision half, turn results outbox swap, per-player sync | `game/sim` + `app` | lane K map; 34/37 stubs | tail subtree | | 8 | Rung B: **AI order generation** | new `game/ai` | §4.1 | `ModCount`, Checksum | Rules that hold for every item: formula-held-inputs-missing is **evaluated and reported, not written**; `closed` and `regressed` reported separately, never netted; `verified` in the phase table means compared against the live game and the build fails if it drifts silently. ## 4. Future engine modules (breadth, ranked by what it unblocks) 1. **`game/ai`** — `AIPlayer`, fuzzy `IAIRule`, `StrategyAIAgent`. Essentially unread; **proven to gate Rung B**. Known: `AISystem::Write` emits nothing; the 3 `AIAgent` save blocks are cache state (typed, 10 element layouts unexercised). 2. **`game/combat`** beyond the planner — resolver (7,641 B, orchestration only) and retreat are mapped; the six pipeline callees and the tanker fold are unread; **nothing here has run under an instrument**. Combat *simulation* (`GameCombatSim`) stays parked. 3. **trade + spy managers** — all 11 virtual targets named; four can draw RNG and none has fired (no trade routes / spy program in the corpus). 4. **`net/`** — the lockstep protocol. Direct IP works in the original without GameSpy; our engine inherits determinism to the word, so a lockstep peer is mostly serialization we already own. 5. **UI** and **renderer** — not on any path; DXVK carries rendering for the original. ## 5. Workloads to manufacture (an unexercised path is a hypothesis) - **orders save → End Turn → autosave** — the Rung-A reference (human commands in-file). - **trade routes + spy program** — the four tail draw sites; `spies2`/`SysMem`/`mts` elements. - **a cruiser** (2 census leaves), **a DN platform** (the third). - **a Liir fleet** — waypoint type 2 is the Liir drive, not a node line; **a Hiver game** — types 4/5. - **a battle with `res->+4` clear and a partial retreat** — resolver + retreat instrumented; tests the `NMnx` prediction and the predicted zero-ship-fleet bug in the original. - `ResErrRoll` true entering `ProcessResearch` — save exists; needs one turn funding > ½ a tech. - expense sliders / debtor / aid — likely dead in 1.8; debtor ≈ 35 turns. Low priority. ## 6. Multiplayer revival (lane G2) - **Tier 0 (hours):** `/concurrent /join 127.0.0.1:3369` — two clients on one VM, no server. Predictions written; queued behind lane H for VM140. - **Tier 1:** availability responder, byte-specified; probably a no-op (fails open on DNS failure). - **Tier 3 (config, not code):** self-hosted OpenSpy/UniSpy with the `swordots`/`Z5gR9Z`/1381 row; fix queryport 3369 (not 6500) and add the 16 custom QR2 keys (ids 50–65). - No CD-key code exists; 333networks ruled out (v0 only). ## 7. Method and infrastructure - `guides/method-rules.md` (18 rules) is the contract for every lane brief. Rule 18 — *measure first, the lab exists* — is why the RNG ledger closed in one VM session after three static lanes. - Reachability claims older than lane V2 are **lower bounds**; re-check "no caller" findings with `tools/vtable_map.py`. Never trust Ghidra's function end (five lanes bitten). - Regenerate the determinism-oracle autosaves on VM140 (lane N overwrote them; lane H doing it). - Board rows superseded by later rows are marked `SUPERSEDED` at integration — keep doing it. - Displace the shim: each hook that reaches `replace` with 0 divergences is code the original no longer runs. Track that count; it is the honest "how much of the game is ours" number. ## Parked - Battle-load bug (thread contention) — resume only with a reason. - Renderer — after the sim byte-matches.