# Rung B — predictions, written before the replay module exists Lane RB, 2026-09-08. Committed **before** `src/game/ai/command_capture.*`, `src/game/ai/apply_order.*` or `src/app/command_replay.*` were written, and before any `--turn-commands` run. Rule 2. Baseline taken on a **fresh** `build-host` in this worktree (rule 24), `main` at `4f25f1e`, ctest 54/54, header regenerated from `~/sots-re/ghidra` (1,217 entries, no diff): ``` turn2-state.sav -> turn3-state.sav : baseline 108, after our turn 63, closed 45, regressed 0 our /Sim/ModCount = 14 oracle = 24 input = 12 ``` The whole lane is aimed at that one arithmetic gap: our turn contributes the **two driver bumps** and nothing else, because a command stream is the only other writer and a save does not carry the AI's. --- ## The inputs this lane replays Lane L4's live dumps at `StrategySim::ApplyTurnCommandBatch`, already in the RE repo: `verify/results/shim/aiorders/l4-turn{1to2,2to3}-aiorders.txt`. Each holds two batches; the **second** (`seq=2`, `n=8`) is the End-Turn submission. The first (`seq=1`, `n=1`) is the load-time batch and is deliberately **excluded** — our standalone loads from disk and never runs it, and the save's on-disk `ModCount` is the value before it. --- ## P1 — `ModCount` lands on exactly 24 on the canonical pair Replaying the `turn2to3` block set against `turn2-state.sav` makes `/Sim/ModCount` read **24**, closing that leaf. The decomposition, item by item, from AI4's cost table: | term | count | |---|---:| | turn drivers (`S00`, `T00`) — already ours | 2 | | research-rate gate, blocks 0–3 (pids 16, 32, 496, 512) | 4 | | list 5 system rates ×1 | 1 | | list 3 build ×1 | 1 | | list 10 ×1 | 1 | | list 14 fleet task ×2 | 2 | | list 8 fleet move ×1 | 1 | | list 23 population ×1 — **free half** | 0 | | **total** | **24** | **Falsified if** the run reports anything but 24, and each near miss names its own defect: * **25** — list 23 charged; the 17..27 free half is wrong and AI4 §1 must be reopened. * **23** — the AI's fleet order counted as the interface's single list-14 element; AI2's P1 and the `(fleetId, mode)` dedup key are wrong. * **28** — the four uninitialised monster slots' garbage rate payloads read as *set* gates. This is the parser failing, not the model: those slots carry `pid == 0` with the gate bit clear and a garbage float behind it. * **22** — the human's block (pid 16, every list empty) charged 0 instead of 1. The research-rate gate is set unconditionally by the send-buffer build; a block that ordered nothing still costs one. This is the boundary case rule 23 asks for and it carries four of the ten. ## P2 — the apply order is this permutation, and it is neither list order nor offset order The batch is a flat run of 27 per-list loops with three per-player gate loops spliced in: ``` lists 6 11 20 19 17 18 5 23 24 gate loop A { group5(+0x3c, free), target(+0x14, bump), rate(+0x0c, bump) } lists 1 4 3 21 2 22 9 10 12 13 14 15 16 7 8 25 27 26 gate loop B { boost(+0x20, bump) } gate loop C { group4(+0x2c, bump) } ``` Each list step is `for each player block: for each element: apply` — so **every player's list-6 elements are applied before any player's list-11 element**. Order is per list, not per player. **How I will verify it, three ways, and what each cannot show.** 1. **Address monotonicity of the six inlined bump sites.** `ApplyTurnCommandBatch` inlines six appliers and each writes `ModCount` in place: `0x0088fe0a` (gate A target), `0x008902fe` (list 12), `0x008903b9` (list 13), `0x0089046c` (list 14), `0x008905c8` (list 7), `0x008907bc` (gate B boost). Their positions in the permutation above are 10th, 18th, 19th, 20th, 23rd and 28th. Adding the three gate-loop heads (A `0x0088fdb0`, B `0x008907b1`, C `0x0089080a`) gives a nine-point chain that must be strictly increasing in both address and position. **This is a check on 9 of 30 positions and no more** — the other 21 lists' handlers are out-of-line and this lane has no record of their call-site addresses inside the batch, so their relative order rests on AI4's direct read of the `add edi, imm` sequence and is inherited, not re-derived. *Falsified if* any of those nine is out of order. 2. **The permutation is a bijection of 1..27**, asserted at compile time. Catches a transcription slip, catches nothing about the order being right. 3. **Neither sorted.** Asserted: the list sequence is not ascending, and the member-offset sequence implied by it is not ascending either. A naive implementation that loops `for (list = 1..27)` or walks the block's members in memory order cannot pass this — which is the only reason the test earns its place, since on every workload the corpus holds the *outcome* is order-independent (no two commands in either capture touch the same object through a modelled handler). **Stated plainly: apply order is unfalsifiable on this workload.** Both captures put every non-empty list on one player and every command on one system, so any permutation produces the same save and the same count. The order is implemented because a later workload will need it, and it is tested against the instruction stream rather than against an outcome. ## P3 — a replay closes `ModCount` and nothing else, and regresses nothing Predicted canonical-pair result: **closed 46 (45 + `/Sim/ModCount`), regressed 0, remaining 62.** Of the ten commands in the `turn2to3` block set, only three have a handler this lane can write, and all three are **no-ops on this workload**: * the research-rate gate — `ServerPlayer.ResRate` already reads 0.25/0.8 in `turn2-state`; * list 5 system rates — Ke'Dolarra's rates already read `{0, 1.0, 0, 0, 0, 0, 0}`; * the research-target gate — not set on any block this turn. The other seven need subsystems we do not have (P4). So the replay's entire contribution to the byte-match distance is the counter. **Falsified if:** any further leaf closes — which would mean one of the seven is more modellable than I claimed, or that a "no-op" write is not one; or any leaf regresses — which would mean a modelled handler writes the wrong field, and is the failure this prediction exists to catch. ## P4 — the no-op control Running `--turn-commands` with the three modelled handlers enabled must change **zero** leaves relative to the same run with them suppressed. That is the control on "these are no-ops here": a rate written to the wrong player, or rates written into the wrong system's frame, shows up as a regression against a run that wrote nothing, even though both agree with the oracle by luck. Rule 1 — the outcome agreeing is not evidence the write went to the right place. ## P5 — the secondary pair, where the stream actually does something `turn1-state.sav` carries `ModCount 0`; the oracle `turn2-state.sav` carries 12. The `turn1to2` capture holds 4 rate gates, **3 research-target gates** (ids 144, 90, 288 on players 32, 496, 512), one list-1 design, one list-3 build, one list-5 rates and one list-23 population. **Prediction:** `ModCount` 0 → **12** (2 + 4 + 3 + 1 + 1 + 1 + 0), and — if and only if the capture carries the resolved tech *names* — the replay closes `ResRate` on players 32, 496 and 512 and `ResTNm` on 32 (`IND_Waldo`) and 496 (`DRV_PlsFiss`). **And it must NOT close `ResTNm` on player 512.** The capture records target id **288**, which that run resolved to `XNC_TrnsMorr2`; `turn2-state.sav` holds `BIO_GnMod`. The recording is from a *different process* than the oracle, and 512's pick is the one leaf lanes L4 and L5 both showed is not reproducible. So the recorded stream and the reference save genuinely disagree there, and a replay that closed it would mean I had fitted something. *This is the sharpest prediction in the set*, because it is the one where the replay's output is determined by the recording and the recording is known to be from a divergent run. If 512 closes, something is copying the oracle rather than the capture. *Falsified if:* `ModCount` ≠ 12; or `ResRate` fails to close on all three; or 512's `ResTNm` closes; or 32/496 fail to close while the capture does carry names. ## P6 — the wire carries an id, the save carries a name, and we cannot bridge them The research-target gate's payload is an `int techId` (`block+0x10`). `ResTNm` on the wire is a string. Process Turn phase 18 resolves the chosen object's `std::string` at `+0x4` and passes a `char*` to `cl_SetResearchTarget`, so the resolution happens **client-side, off the command**, and the applier receives an id whose mapping to a name this campaign has not read. `144`, `90` and `288` are not `index * 16` and are not indices into anything we hold. **Prediction:** the target-gate handler cannot be modelled from the id alone, and the capture format must therefore be able to carry the *observed* name alongside the id. When it does, the leaf is closed **by recorded payload, not by model**, and the report must say so in a separate column — otherwise a future reader will mistake a transcription for a reimplementation. *Falsified if:* someone finds the id→name map, at which point this becomes a modelled handler and the column empties. --- ## What this lane is not predicting * **Nothing about `Summary.Checksum`.** Its inputs are unread; it moves whenever anything else does. * **Nothing about the RNG frame.** No command in either capture draws. * **Nothing about the load-time batch.** `seq=1` is excluded by construction (above), and whether the original charges `ModCount` for it is untested — a save loaded and immediately re-saved would settle it in one run and this lane does not do it. * **Nothing about ordering effects between two commands on the same object.** No workload has one.