The headline is one number: our turn writes ModCount 14 where the oracle writes 24, and the missing ten are the turn's command stream. P1 decomposes the ten and names what each near miss would mean; P5 predicts the secondary pair closes five leaves and must NOT close the sixth, because the recording is from a run that diverged there.
9.6 KiB
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 == 0with 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.
- Address monotonicity of the six inlined bump sites.
ApplyTurnCommandBatchinlines six appliers and each writesModCountin 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 (A0x0088fdb0, B0x008907b1, C0x0089080a) 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 theadd edi, immsequence and is inherited, not re-derived. Falsified if any of those nine is out of order. - The permutation is a bijection of 1..27, asserted at compile time. Catches a transcription slip, catches nothing about the order being right.
- 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.ResRatealready reads 0.25/0.8 inturn2-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=1is excluded by construction (above), and whether the original chargesModCountfor 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.