sots-re/findings/control-flow/per-history-residue.md

57 KiB
Raw Permalink Blame History

The per-history residue — the same save, two routes to turn 39, both reproducible

  • Type: control-flow / determinism (two instrumented runs; one build, no static reading)
  • Owner / date: lane BS · 2026-09-09 · guest VM144 (sots-re-win10-144, 192.168.10.144)
  • Spec: findings/resolutions/2026-09-09-fleet-id-order-residue.md §8 (VM144 paragraph) and §9
  • Tests: §9.2's ranked candidates for the per-history input; §9.3's committed predictions (a)–(d)
  • Depends on: findings/control-flow/seed-exposure-forward-test.md (lane BQ — the residue, the click path, the Done-pixel test); findings/control-flow/pinned-seed-rich-turn.md (lane BP — the per-process residue, which this is not)
  • Rules exercised: 2 (predict first), 15, 19, 20, 21, 24, 25, 26 and its corollary (c), 28

1. Predictions, committed before the build was deployed and before the game was launched (rule 2)

Everything in §1–§3 was written and committed before binkw32.dll was copied onto VM144 and before any hash was read. It is not edited afterwards, only annotated with verdicts in §5.

1.1 What is at stake

Lane BQ measured something the campaign had no model for: ar-oracle-A-pre.sav runs turn 39 one way when the state is reached by continuation and another way when it is reached by a load, and both routes are reproducible.

route processes (Autosave EndTurn) (Autosave)
continuation — load ar-turn37-816raiders.sav, End Turn, Auto Resolve Peacefully, End Turn 3 (lane AR) — 15b99255… (F38) then 7a8b3d5e… (F39)
load — load ar-oracle-A-pre.sav, End Turn 2 (lane BQ) 33e30092… 4c356f59…

23 leaves apart, 17 of them one AI fleet: Flt[1570 "Zeta Fleet V"] (PID 32). Both routes' turn-39 input has it parked at Alpha Centauri (LocID 432, HFPlan False, FtFlg 12, both ships at Range 9.0) — and so does turn 37's save, so it had already sat still for a turn. The continuation issued it a move order on turn 39; both loads issued nothing.

So the pre-turn save does not fully determine the next turn's AI fleet-task decisions. §9.2 ranks the candidates and this lane's job is to say which survives:

  1. AI agent state the save does not carry — AISystem::Write emits nothing; the three AIAgent save blocks are cache. A task created on turn 38 exists on turn 39 only in the process that ran turn 38. Same class as the 0x0069dbb0 fuse and the client id counter. Leading.
  2. Client generator stream position — rejected by the resolver: both routes are seed-insensitive on this turn (AR's three continuation processes had three seed sets and agreed; BQ's two loads had two and agreed).
  3. Server-side state not in the save — nothing in the record points here. Last.

1.2 P0 — the neutrality gate, and it is checkable at k = 1 here

Rule 19 asks for the measurement with the instrument removed. Unusually, both routes already have hooks=off controls that agree with themselves, so rule 26 is satisfied on both sides and an instrumented run agreeing with one is not a 1/k coincidence — k is 1 on each route.

P0 — committed. With shim.cfg.bsrun installed:

  • continuation: the (Autosave).sav after End Turn #1 is 15b99255e1f03dab… and the (Autosave).sav after End Turn #2 is 7a8b3d5eb3a60eba…;
  • load: the (Autosave EndTurn).sav is 33e3009254f5acd1… and the (Autosave).sav is 4c356f5916f0e6e4….

If either route misses either hash, that route is VOID. The instrument is not neutral on it and nothing — not one probe count, not one airng row — may be read from it. This is stated as a stop condition, not as a caveat.

1.3 The measurements — §9.3's (a)–(d), committed as mine, plus one of my own

P1 (= §9.3 a) — the block. In the continuation's turn-39 aiorders batch, the block for pid=32 carries a list-8 element whose first word is 1570 and at least one list-14 element whose first word is 1570. In the load's turn-39 block for pid=32, neither is present.

Basis: in lane BP's dump the first word of a list-8, list-10 and list-14 element is the fleet id (list 8 idx0 ints=[ 2002 … ], list 14 idx0 ints=[ 1970 0 … ]), and the save-side difference BQ measured is exactly a FPlan with one waypoint (Wpt 320) appearing only in the continuation.

P2 (= §9.3 b) — the task. The two routes' aiprobe counts for the turn-39 window differ in at least one row, and the difference is on a task Execute entry (probe idx 2–9) — either a task that fires in the continuation and not in the load, or the same task firing with a different AcquireFleetsForTask (13) / IsClaimedByAnotherTask (14) count. Probe 11 (AssignFleetsAndIssueOrders) is entered in both routes, and probe 0 (RunTaskList) is non-zero in both — without which every other row is meaningless (rule 20 is built into the report).

P3 (= §9.3 c) — the draws. In every OnResumePlaying bracket on both routes: foreign_words=0, foreign_calls=0, residual=0, and no airngcensus row whose rng is anything but that client's +0x134. The two routes' per-client word counts differ by at most the cost of the task P2 names. RNG is not the mechanism, and this is the measurement that says so rather than the inference.

P4 (= §9.3 d) — the pre-turn states. The continuation's own frame-38 autosave and the load run's (Autosave EndTurn).sav differ in exactly one leaf, /CD[1]/NPrvVa (+5.00), under --floats bits --mask resave. Restated: the two routes enter turn 39 from the same state, so whatever differs is not in the file. (BQ measured this without an instrument; if it moves under mine, the instrument moved it and P0 will have said so first.)

P5 — mine, the exposure fact the standing instruction asks for. airng emits one bracket row per StrategyClient::OnResumePlaying entry carrying agent=, so it reports which clients actually run an AI turn. I predict three clients run on both routes — pid 32, 496 and 512 — and that players 528/544/560/576 produce no bracket row with a non-null agent, matching lane BP's count of three clients from seven non-human players on the same lineage. NumOwn == 0 gates nothing: 496 and 512 have NumOwn == 0 and are predicted to run.

How each can be wrong, and the symptom (rule 2):

way it could be wrong symptom what it would mean
the residue is not in the AI's emission at all P1 fails in the null direction: both routes' pid-32 blocks are element-for-element identical the divergence is downstream of BuildTurnCommands — in the server's application of the block, or in ProcessFleetMovement. This relocates the question and is the more valuable outcome, because it would exonerate the whole AI-agent-state candidate in one run
the block differs but no probe does P1 holds, P2 fails the task set is the same and the gathering differs — the fleet set AcquireFleetsForTask returns. That is BR's stage-2 territory (AssignFleetsAndIssueOrders 0x006c16c0 and its 992-byte hub), and the two lanes converge on one function from two directions
a draw is involved after all P3 fails: a non-zero foreign_words, or a census row on 0x00af6e58 the resolver's rejection of candidate 2 was wrong; the return address names the consumer and the follow-up is a global pin
the routes do not enter turn 39 from the same state P4 fails on a leaf other than NPrvVa there is a save-visible difference and the whole "not in the file" framing is wrong. This is the one failure that would overturn BQ's headline rather than extend it
the instrument perturbs P0 fails the run is void; report the miss and stop
more or fewer than three clients run P5 fails the client count is a property of the state, not of the lineage, and rule 26 (c)'s "read the predicate against who actually runs" needs a per-save count

A null result is a real result. If both routes produce identical AI fleet-task probe sequences and identical blocks, I will report that as the finding, not hunt for a difference elsewhere.

1.4 What this lane will not settle (rule 15)

  • One process per route. Each route's reproducibility is already established by two or three hooks=off processes (AR, BQ); this lane adds the instrument, not a third control. So it can say what the AI emitted on each route; it cannot add evidence about whether a route is reproducible.
  • No static reading, no build of game/ai. If the answer is "the fleet set differs", naming the container is BR's stage 2, not this lane's.
  • The 0x0069dbb0 fuse cannot fire in a load→one-turn run by construction, so the load arm says nothing about it. The continuation arm runs two turns from a load, which is still inside the 3–40-turn fuse window, so neither arm bears on it in either direction.
  • The research probes (16–19) are not installed. BQ measured candidatesTried=1 and an agreeing pick on this lineage; four fewer detours is four fewer ways to fail P0. So this lane says nothing about the research half.
  • Turn 38's encounter is auto-resolved peacefully on the continuation arm, and that is only safe here because the other side has nothing to fight with (lane BQ: Auto Resolve Peacefully means "I commit no ships", not "no combat occurs"; on a later turn a planet fired and killed a different number of raiders in each process). AR's turn-38 encounter reproduced across three processes, which is the only reason this procedure is admissible at all. It does not generalise.
  • aiorders.deep=off, so list 8's route vector and list 10's counted vector are behind pointers and are absent from the dump. The fleet id is in the element window; the waypoint list is not.

2. Exposure facts, read off the inputs before the runs (rule 26 (c), with the route)

Read with verify/state-checksum/state_checksum.py's leaf walk over the typed tree. The standing instruction: record which players have an AI client, and record the route as part of the procedure. The client list is a run-time fact, so it is predicted in P5 and reported in §4; what the saves carry is below.

2.1 ar-turn37-816raiders.sav — Frame 37, the continuation arm's input

PID name NPC ResTNm NumOwn
16 re (human) no '' 3
32 The Eternal Empire no BIO_EnvTail 18
496 Spengler yes DRV_PlsFiss 0
512 Spengler yes XNC_TrnsMorr2 0
528 / 544 / 560 / 576 Alien Menace / Peacekeeper Enforcer / Von Neumann / Independent Colony yes '' 0

Flt[1570 "Zeta Fleet V"] (PID 32) on turn 37: LocID 432, HFPlan False, FtFlg 12, NShips 2, both ships Range 9.0. Identical to its turn-38 state — so the AI left it parked on turn 38 in the continuation too, and the order it issued on turn 39 is not a continuation of an order in flight.

2.2 ar-oracle-A-pre.sav — Frame 38, the load arm's input and the continuation arm's midpoint

Per-player table as lane BQ published it (§2.1 there): the only player that can research holds a target, NumDes 47 constant, NumOwn 18 constant, two completions a turn into existing Freighters fleets — no new fleet is formed on this turn, which is why BP's per-process visit order has nothing to vary and why this turn shows no per-process residue.

Flt[1570 "Zeta Fleet V"]: LocID 432, HFPlan False, FtFlg 12, NShips 2, ships 5840 and 6144 both at Range 9.0. This is the leaf set the two routes disagree about.

Predicate reading, both routes: no research pick (target held), no design turn, no fleet-assignment batch, no AI fleet at an enemy colony. Rule 26 (c) screens this turn as not exposed — and it is exposed anyway, through a term the predicate does not have. That is the point of the lane.


3. Procedure and configuration

3.1 The two runs

route procedure
run C continuation SavedGames reset to exactly ar-turn37-816raiders.sav; fresh process; Load → Single Player → row 1 → OK; End Turn; the turn-38 encounter resolved with Auto Resolve Peacefully then Done; End Turn
run L load SavedGames reset to exactly ar-oracle-A-pre.sav; fresh process; Load → Single Player → row 1 → OK; End Turn

Both saves are copied in from verify/results/saves/, which moves the Load-dialog rows; with exactly one file the dialog is one row and it is screenshotted and re-derived anyway (lane BQ's tip). The Done pixel at (233,673) is sampled to detect the encounter query rather than assumed (≈(183,18,0) query up, near-black on the map). One click per helper call with a screenshot between (lane M, confirmed by BQ on a second guest); ≥ 9 s after schtasks /Run before believing a shot (lane AP's nine-minute gotcha).

3.2 Configuration — shim.cfg.bsrun, one file, both runs

item value
guest VM144, held by lane BS
build C:\SOTS\shimdist-bs\binkw32.dll, built fresh in /srv/re-lab/build/sots-engine-bs (rm -rf'd first, rule 24) from worktree wip/bs (rule 21)
config shim.cfg.bsrun — hooks=trace with all 27 registered hooks named off (the # exhaustive marker; tools/check_shim_configs.py passes)
instrument aiorders=on aiorders.words=32 aiorders.deep=off; aiprobes=16 (probes 0–15: the control+pass recorder, every task Execute entry the table carries, the three emission exits, the hub, the claim test, BuildTurnCommands); airng=on airng.pin_seed=off
off probes=off, watch=off, airesearch=off, aidesign=off, aiseed=off, no pin of any kind

aiseed=off means the RunAI net-id list is not logged — the brief's config says every template hook off by name, and aiseed=log would have been one more detour on RunAI than the brief asked for, on a run whose whole value is a k = 1 neutrality check. The exposure fact is taken instead from airng's per-bracket rows, which carry pid and agent for every OnResumePlaying entry — the clients that actually run, measured at the bracket rather than at the seed. §4 reports it as such.

3.3 VM144 as found, recorded so it can be put back

binkw32.dll  22,972,439 B  sha256 479b8614d2417603b64e66482e429ecd3cdfe4033a2c99969ca91553642e517d
             (= shimdist-l3, BUILD_ID l3cont2-0117495-20260908T2124Z)
shim.cfg     sha256 692dabb6f00343bfcc2f86d62ade827c295acc4630a18a1f2ddcebd53640978d
SavedGames   15 files (hashes in §8)
game         RUNNING at the main menu (pid 8408), screenshot-verified

4. Results

(added after the runs; nothing above this line is edited)

4.0 What was run, and both routes passed their own hash check

Two fresh processes on VM144, C:\SOTS\shimdist-bs\binkw32.dll (BUILD_ID bs-1654785-20260909T0629Z), shim.cfg.bsrun unmodified in both, SavedGames reset to exactly one file before each launch. The Load dialog was screenshotted every time: with one file the row sits at (400, 261) and it sat there in both runs. The main menu, the Load dialog, the lobby's Launch button, the loaded map, the turn-38 encounter query and the selected dove were each verified from a screendump before the next click. No run was driven by sleeping.

Instrument armed and verified from shim.log, not assumed (rule 1), identically in both runs:

config: 45 lines, 0 "ignoring unknown key"
27 of 27 registered template hooks   mode=off (not installed)
7 draw sites                          create=MH_OK enable=MH_OK
aiorders batch hook                   create=MH_OK enable=MH_OK
aicfg words=32 deep=0 research=0 seed=off pins=0 detours=17
aiprobe 0..15                         create=MH_OK enable=MH_OK   (16 of 20; 16..19 not installed by config)
airng bracket OnResumePlaying         create=MH_OK enable=MH_OK   pin_seed=off
0 COVERAGE lines in either log
run route input End Turns encounter (Autosave EndTurn).sav (Autosave).sav
C continuation ar-turn37-816raiders (F37) 2 turn 38, Deep Space, 1 human DE vs 1 AI CR, peaceful after #1: 107,097 21447568…
after #2: 107,478 15b99255…
after #1: 107,485 0fe8f374…
after #2: 107,667 7a8b3d5e…
L load ar-oracle-A-pre (F38) 1 none 107,477 33e30092… 107,732 4c356f59…

P0 holds on both routes, byte for byte. 15b99255… / 7a8b3d5e… on the continuation and 33e30092… / 4c356f59… on the load, identical to lane AR's three hooks=off processes and lane BQ's two. The instrument is neutral on both routes at k = 1, and everything below is read from runs that passed their own check.

One correction to my own P0, and it is a mis-specification of a snapshot point, not a miss. §1.2 said "the (Autosave).sav after End Turn #1 is 15b99255…". It is not: that file is 0fe8f374… (107,485 B), and 15b99255… is the (Autosave EndTurn).sav of End Turn #2. So ar-oracle-A-pre.sav is the frame-38 state as End Turn #2 is pressed, not the frame-38 autosave — which is exactly why it carries Player.Status == 0 while a post-turn autosave carries 4, and why BQ's load run reproduces it (modulo NPrvVa) at its (Autosave EndTurn) snapshot. The check that belongs at the End-Turn-#1 point is this, and it is the strongest available:

state_checksum --floats bits --mask resave  ar-oracle-A-pre.sav  C-f38.sav
  [masked: Checksumx1, Statusx8]   IDENTICAL

IDENTICAL, and with no /CD[1]/NPrvVa residue — because no load happened between. The continuation reached exactly ar-oracle-A-pre.sav's state before running turn 39.

4.1 The block: every save-side divergence has a block-side counterpart

aiorders list sizes for pid=32 (the only player that emits anything; 16, 496, 512 and the four NPC blocks are nonEmpty=0 in every batch of both runs):

batch list 3 list 5 list 8 list 10 list 12 list 14 list 23
C, turn 38 2 18 3 4 11 6 18
C, turn 39 2 18 1 0 12 0 18
L, turn 39 2 18 0 0 11 0 18

The continuation's turn-39 block differs from the load's in exactly two elements:

C  aielem blk=1 pid=32 list=8  idx=0  ints=[ 1570 ... ]      <- Zeta Fleet V, absent from L
C  aielem blk=1 pid=32 list=12 idx=8  ints=[ 914 ... ]       <- Nu Fleet III, absent from L
   (list 12's other eleven elements are the same fleet ids in the same order in both runs:
    274 290 306 322 626 642 786 882 [914] 930 1746 2338)

And the save-side divergence — reproduced under the instrument, 23 leaves, --mask none — is exactly those two fleets and nothing else:

DIVERGED: 23 leaf difference(s)          (C-post vs L-post)
  15  Flt[1570 "Zeta Fleet V"]   FPlan only-in-C, LocID 0->432, HFPlan True->False, FtFlg 13->12,
                                 Pos/FtOrig/PrvPos x3 each, both ships Range 7.0->9.0
   3  Sys[432 "Alpha Centauri"]  NumFlts 0->1, Flt only-in-L, FFlags 0->2
   2  Flt[914 "Nu Fleet III"]    HLay False->True, Lay only-in-L
   1  /Sim/ModCount 2147 -> 2145
   1  /Summary/Checksum
   1  /CD[1]/NPrvVa 501.13 -> 506.13

So the divergence is entirely in what the AI emitted. Nothing diverged in the server's application of the block, and candidate 3 ("server-side state not in the save") has no work left to do on this turn. This is the conjunct the null-result branch would have relocated the question to, and it is closed in the other direction.

4.2 The task: a colonise task exists on the continuation's turn 39 and does not exist after a load

aiprobe counts for the turn-39 window (turn=, the per-batch counter; probe 0 is non-zero in both, so no row is unmeasured — rule 20). Run C's turn-38 window is shown for context.

idx probe C t38 C t39 L t39 Δ
0 StrategyAIAgent::RunTaskList [control+pass] 6 6 6 0
1 StrategyClient::OrderList16 0 0 0 0
2 AITRaid::Execute 0 0 0 0
3 AITColonize::Execute [+Goal] 2 2 0 +2
4 AITEscortGateInvade::Execute 0 0 0 0
5 AITInvade::Execute [+Goal] 2 2 2 0
6 AITNodeBore::Execute 0 0 0 0
7 AITBuildPoliceShips::Execute 2 2 2 0
8 AITBuildDeepScanShips::Execute 4 4 4 0
9 AITAdvanceIdleShips::Execute 6 6 6 0
10 StrategyAIAgent::RequestBuildForTask [lists 3,1] 4 4 4 0
11 StrategyAIAgent::AssignFleetsAndIssueOrders [14,8,10] 6 2 0 +2
12 StrategyAIAgent::IssueRouteForFleets [list 14] 7 2 0 +2
13 StrategyAIAgent::AcquireFleetsForTask 15 12 12 0
14 StrategyAIAgent::IsClaimedByAnotherTask [entry] 3055 2197 2024 +173
15 StrategyClient::BuildTurnCommands [control] 5 6 5 +1

RunTaskList is 6 in both — three AI clients × two passes, the same sweep. AcquireFleetsForTask is 12 in both. Six of the eight task Execute entries are identical. The whole difference is AITColonize::Execute, entered once per pass in the continuation and never in the load, and the two emission exits it reaches.

The event stream places it exactly (aievent, ordered; the ring overflowed at 4096 hits in both runs and says so, so the counters above are the evidence and the ordering below is corroboration):

C, turn 39, agent=0x3306dbd8 (pid 32):
  run=7 pass=0  probe=0  RunTaskList
  run=7 pass=0  probe=3  AITColonize::Execute        <- FIRST task in the list
  ...
  run=7 pass=0  probe=12 IssueRouteForFleets
  run=7 pass=0  probe=11 AssignFleetsAndIssueOrders
  run=7 pass=0  probe=5  AITInvade::Execute
  run=8 pass=1  probe=0  RunTaskList
  run=8 pass=1  probe=3  AITColonize::Execute

L, turn 39, agent=0x33202398 (pid 32):
  run=1 pass=0  probe=0  RunTaskList
  run=1 pass=0  probe=13 AcquireFleetsForTask        <- no colonise task to run
  ...

And in run C's turn-38 window AITColonize::Execute is likewise the first task on both passes. So the task was live on turn 38, survived into turn 39 in the same process, and after a load of turn 38's own autosave it is simply not there.

4.3 The draws: no foreign draw in any bracket, on either route

airng, every StrategyClient::OnResumePlaying bracket in both runs — 9 in run C (two turns), 5 in run L (one turn):

foreign_words=0  foreign_calls=0  residual=0   in 14 of 14 brackets

Turn-39 brackets, per client:

route pid 32 pid 496 pid 512 pid 16
C (seq 6–9) left 595→591, 4 words, 4 calls, 2 sites 0 words 0 words agent=0x00000000, 0 words
L (seq 2–5) left 615→610, 5 words, 5 calls, 4 sites 0 words 0 words agent=0x00000000, 0 words

Sites, turn 39, pid 32:

C:  0x002ad878 NextFloat calls=2 words=2      L:  0x002ad878 NextFloat calls=2 words=2
    0x00179915 NextInt   calls=2 words=2          0x00179915 NextInt   calls=2 words=2
                                                  0x004e6e09 NextFloat calls=1 words=0
                                                  0x00178d15 Chance    calls=1 words=1
                                                  (facade cl_Chance, ret_rva 0x0029086f)

Two things worth stating plainly.

  • The per-process global generator 0x00af6e58 took zero words inside any AI bracket in either run. Its lifetime count is in the census: run L held it at 3 words for the whole process; run C went 3 → 87 words between the turn-38 and turn-39 brackets — 84 words drawn on the global during turn 38's processing, entirely outside any AI client's turn. That is the combat turn, and it is a free corroboration of the 09-08 warning about RunCombatRound; it is not the AI.
  • The one extra word is in the load, not the continuation, and it is a consequence. The load's pid-32 bracket spends one more word than the continuation's, at a Chance whose return address 0x0029086f falls inside AITBuildPoliceShips::Execute (rva 0x00290380). Both routes enter that task twice; the roll fires only in the load. So the draw difference sits downstream of the task difference, which is the opposite of the direction P3's wording implied.

Note also, and against the stream-position candidate rather than for it: the two routes do enter turn 39 at different positions in pid 32's own generator (continuation idx=29, load idx=9, and different seeds besides). That is a real difference, and it is not the mechanism — see §5.2.

4.4 The pre-turn states: identical but for the load-boundary counter

state_checksum --floats bits --mask resave   C-endturn.sav   L-endturn.sav
  [masked: Checksumx1, Statusx8]
  DIVERGED: 1 leaf difference(s)
    /CD[1]/NPrvVa: 496.13360595703125 -> 501.13360595703125

One leaf. BQ's correction to determinism-oracle.md is re-measured here under an instrument and holds exactly: --mask resave does not cover /CD[1]/NPrvVa, and it advances +5.00 across a load. The two routes run turn 39 from the same state.

4.5 The exposure fact the standing instruction asks for

aiseed was off, so the RunAI net-id list is not logged. What is logged is better for the purpose and is a measurement rather than a save-field reading: airng emits one row per StrategyClient::OnResumePlaying entry with pid and agent, and agent is non-null only on an AI client.

route brackets AI clients (non-null agent) no agent
C 9 (3 turns' worth of client sweeps) 32, 496, 512 — 0x3306dbd8, 0x3306d4c8, 0x3306cdb8, stable across both turns pid 16, agent=0x00000000, every time
L 5 32, 496, 512 — 0x33202398, 0x33202720, 0x332031b8 pid 16, agent=0x00000000

Players 528 / 544 / 560 / 576 — the four NPC factions — produce no bracket at all. Seven non-human players, three AI clients, on this lineage as on ad-turn27. And NumOwn == 0 gates nothing: 496 and 512 have NumOwn == 0, run a client, and spend 0 words.

4.6 Two by-products, recorded because they bear on other lanes

(a) The image base does not move on VM144 either, and it is a third distinct guest value. exe base=0x006b0000 in both processes (pids 11992 and 11468), against 0x00f40000 ×5 on VM145 and 0x005e0000 ×3 on VM146. Per boot, not per process, on a third guest. Any "pointer order" reading still means heap order (resolution §2.2).

(b) A turn that formed two client-minted fleets did not vary — four processes. Run C's turn 38 is a fleet-assignment turn: AssignFleetsAndIssueOrders ×6, list 10 with four {systemId, fleetId} elements (384,2466) (384,2178) (336,2482) (384,2338). Decoding (id = (counter << 4) | node, Player[32] = node 2): all four are client-minted, and 2466 (counter 154) and 2482 (counter 155) do not exist in ar-turn37-816raiders.sav — they are new fleets minted this turn, in that order. 2178 and 2338 are pre-existing. The output of that turn is 15b99255…, reproduced by lane AR's three hooks=off processes and by this instrumented run — four processes, byte for byte.

So the fleet-group visit order was exercised on this turn, over two new fleets, and its outcome set was 1. That does not contradict lane BP — different turn, different groups, and BP's own outcome set of 2 came from two observations — but it does bound the claim: a turn that mints new fleet ids is not thereby a turn whose visit order varies. One datum, one turn, handed to lane BR.

4.7 Lane BR merged while this was being written — one confirmation and one scope correction

Lane BR's ai-visit-order-probe.md landed on main at 02:52 local, after this lane's predictions were committed (15c4471, 02:31) and while its runs were in flight, so neither lane knew the other's answer. Its headline and mine are the same measurement taken from opposite sides — BR held the route fixed and varied the process; this lane held the process shape fixed and varied the route — and they agree.

Confirmation, on a different save, a different lineage and a third guest. BR: the per-process global generator at 0x00af6e58 was "touched three times in the entire process, all three at one return address 0x00784f9e inside StrategyNetworkClient::OnMessage, sixteen bytes before the RunAI call — one seed per AI client". Run L's census, independently, on ar-oracle-A-pre:

airngcensus rng=0x00da6e58 ret_rva=0x00384f9e va=0x00784f9e entry=NextUInt calls=3 words=3

Three words, one site, the same Ghidra address, for the whole process. Two lanes, two saves, two guests, same number and same return address.

And a scope correction to BR's row title, measured here. BR's turn had no encounter. Run C's turn 38 does, and on it the global generator does not stop at three:

run turn global generator lifetime at the last bracket
L 39 only, no encounter 3 words — 0x00784f9e NextUInt ×3
C 38 (encounter) + 39 87 words — 0x00784f9e NextUInt ×3, 0x008622aa NextInt ×2, 0x006f3517 NextInt ×2, 0x006fb6a1 Chance ×80 = 80 words (with the inner 0x008e6e09 NextFloat ×80 at 0 words)

The 84 extra words appear between the turn-38 client sweep and the turn-39 client sweep — an interval that contains the deep-space encounter and its peaceful auto-resolve. This lane did not bracket that interval, so "the encounter spent them" is the obvious reading and not a measurement; what is measured is that they are on the global generator, at three named return addresses, and that none of them is inside any AI client's bracket (foreign_words=0, 14 of 14).

So "the global generator only mints seeds" is true on a turn with no encounter and false on a turn with one. That is rule 28's shape exactly: a zero on a gated path is a statement about the states measured until the gate is a predicate. BR's H2 verdict is untouched — the question there was whether a foreign draw fires inside an AI bracket, and it does not, on either lane's workload. Only the row's summary sentence needs the scope.


5. Verdicts on the committed predictions

# prediction verdict
P0 both routes reproduce their published hashes under the instrument CONFIRMED, byte for byte, on both routes. The instrument is neutral at k = 1 on each. My wording named the wrong snapshot point for one of the four hashes (§4.0); the continuation's End-Turn-#1 autosave is ar-oracle-A-pre.sav IDENTICAL under --mask resave, with no NPrvVa residue
P1 the continuation's turn-39 pid-32 block carries a list-8 element for fleet 1570 and the load's does not CONFIRMED. aielem list=8 idx=0 ints=[ 1570 … ] in C; list 8 is entirely absent from L's block
P1 …and its list-14 pair FALSIFIED. The continuation's turn-39 block has no list 14 at all (list 14 is emitted on turn 38, six elements, for the fleets formed there). IssueRouteForFleets was entered twice on turn 39 and emitted nothing
P1, unpredicted — The block also differs by one list-12 element, fleet 914, which is the counterpart of the other save-side divergence (Flt[914 "Nu Fleet III"] HLay/Lay). Every save-side leaf that differs has a block-side cause; I predicted only half of the block difference
P2 a task Execute probe fires in one route and not the other CONFIRMED, and sharply. AITColonize::Execute — 2 in the continuation, 0 in the load. Six of the eight Execute entries are identical, RunTaskList is 6 in both, AcquireFleetsForTask is 12 in both
P2 …and probe 11 (AssignFleetsAndIssueOrders) is entered in both FALSIFIED. It is entered twice in the continuation and zero times in the load, and so is IssueRouteForFleets. The fleet-gathering hub is not reached at all on the load route
P3 foreign_words=0, foreign_calls=0, residual=0, no census row on a foreign generator inside any bracket, both routes CONFIRMED, 14 of 14 brackets. The global generator 0x00af6e58 took zero words inside any AI bracket in either run
P3 the two routes' per-client word counts differ by at most the cost of that task CONFIRMED numerically, wrong in direction. 4 (C) vs 5 (L) for pid 32; 0 vs 0 for 496 and 512. The extra word is in the load, a Chance inside AITBuildPoliceShips::Execute — downstream of the task difference, not the cost of the missing task
P4 the two routes' pre-turn states differ only in /CD[1]/NPrvVa CONFIRMED exactly. One leaf, +5.00
P5 three AI clients — 32, 496, 512 — on both routes; the four NPC factions none; NumOwn == 0 no gate CONFIRMED on both routes, measured at the bracket rather than read off the save

Which conjunct I satisfied and which I did not. The conjunction I set out to test was "the block differs, the task set differs, and no draw is involved." All three hold. The two clauses that failed are both over-specifications in the direction of "the load does less than I thought": I expected the load to run the same machinery and reach a different answer, and instead it does not run the machinery at all.

5.1 The verdict: candidate 1 survives, and it is now measured rather than inferred

The per-history residue is AI agent state that the save does not carry. Specifically: a Colonize task that is alive in the agent's task list on turn 39 in a process that ran turn 38, and is absent from a process that loaded turn 38's autosave. It is the first task in the list on both of the continuation's turns; it is the task that reaches AssignFleetsAndIssueOrders/IssueRouteForFleets and issues Zeta Fleet V's move; and after a load it does not exist, so the two emission exits are never entered and list 8 is never written.

The chain is closed end to end and every link is a measurement on this run:

turn 38 runs in-process  ->  a Colonize task is in the agent's task list on turn 39
                             (probe 3: 2 hits in C, 0 in L; RunTaskList 6 in both)
   -> it reaches the pass-1 emission exits
                             (probes 11 and 12: 2 and 2 in C, 0 and 0 in L)
   -> the block gains a list-8 element for fleet 1570 and a list-12 element for fleet 914
                             (aielem, both absent from L)
   -> the save gains Zeta Fleet V's FPlan/LocID/FtFlg/Pos/Range and loses Nu Fleet III's Lay
                             (23 leaves, all of them those two fleets plus Checksum/ModCount/NPrvVa)

Ranking the resolution's §9.2 candidates after the run:

candidate verdict
1 AI agent state the save does not carry SURVIVES, and is the only one that explains a task that is not there. It is the same class as the 0x0069dbb0 fuse and the client id counter, and the record's "AISystem::Write emits nothing" is no longer needed as an inference to reach it
2 client generator stream position does not explain it. The routes really do differ in stream position (§4.3), so this run does not exclude it on that ground — what excludes it is that (i) both routes are seed-insensitive (AR: three processes, three seed sets, agree; BQ: two loads, two seed sets, agree), and (ii) the difference is a task that is absent, at RunTaskList pass 0 with an identical sweep count, not a decision resolved differently. A draw changes an outcome; it does not add an element to a task list
3 server-side state not in the save positively disfavoured now. Every diverging save leaf has a block-side counterpart, so nothing diverged in the application of the block

5.2 What would falsify this verdict

Stated so the next lane can kill it cheaply, in decreasing order of directness:

  1. Dump the agent's task list at RunTaskList entry on both routes (one hook, the vector at the agent, {count, per-element vtable}) and find the two lists identical. Then AITColonize is present in both and something inside it returns early on the load route — which would move the answer from "the task is not there" to "the task's own state is not there", a different claim with the same parent. This is the probe I would run next and it is one lane-hour. Prediction under the verdict: the continuation's pid-32 task list holds a colonise task at turn-39 entry and the load's does not, and the counts differ by exactly one.
  2. A load that produces the colonise task. If any load of ar-oracle-A-pre.sav ever emits list 8 for 1570, the residue is not deterministic-per-route and the whole two-residue framing is wrong. BQ has two hooks=off loads agreeing and this lane has a third instrumented one; a fourth is cheap.
  3. A continuation that does not produce it — e.g. run turn 37 → 38 → save manually → quit → reload → turn 39. If that reproduces the continuation's answer, the missing input is in the save after all and this lane read a load-boundary artefact.
  4. Find Colonize task state in the save. The three AIAgent save blocks are recorded as cache (backlog §4.1); if one of them turns out to carry a task list, the input is in the file and the engine has to honour it.

5.3 What this means for the rungs, in one sentence each

  • Rung C / C-exact. The scope line the resolver wrote stands and is now measured: every byte-match claim is a claim about a load→turn procedure. An engine that runs one turn from a save can never reproduce a continuation's turn on this lineage, because the original's AI carried a task across the turn boundary that the file does not hold. This is not a defect to fix — it is the definition of the comparison.
  • Rung B / replay. Untouched and strengthened again from a third direction: the block is where the divergence lives, and replay applies the block off the wire.
  • game/ai. If our AI keeps its task list in the engine's own state across turns (it must, to be an AI at all), then our engine has the same property — a continuation and a load will differ — and that is faithful, not a bug. What must not happen is for our task list to be reconstructed differently from a save than the original reconstructs it, and this lane says nothing about that.

6. Coverage, and what was not done (rule 15)

  • One instrumented process per route. Reproducibility of each route is lane AR's (three processes) and lane BQ's (two); this lane adds the instrument and a fourth/third process respectively, both landing on the published hashes. It is not an independent test of reproducibility.
  • The task is named by its Execute entry, not by reading its object. AITColonize::Execute is the probe table's name for the address 0x0028b400; this lane did not read the task object, its goal, or which system it was colonising. The Colonize reading is the probe table's, inherited from lanes AI2/AI3, not re-derived here.
  • Why the colonise task exists on turn 39 in the continuation is not measured. It was already running on turn 38 (probe 3 = 2 there too). Whether turn 38 created it, or it predates turn 37, is untested — ar-turn37-816raiders.sav reached by its own load would answer it and was not run.
  • aiorders.deep=off, so list 8's route vector and list 10's counted vector were not followed. The list-8 element's first word (the fleet id) is in the window; its waypoint payload is not, and the claim "Wpt 320" is lane BQ's from the save, not this lane's from the block.
  • List 12 is named by its position, not decoded. Its elements are fleet ids; the continuation's carries 914 and the load's does not; the save-side counterpart is Flt[914]'s HLay/Lay. The causal direction is not established — "in list 12 ⇒ lay cleared" and "absent from list 12 ⇒ lay set by something else" both fit one observation (rule 20). One line of watchpoint would settle it.
  • The aievent ring overflowed (4096 of 5359 hits in C, of a larger number in L) and says so in the file. Every ordering statement in §4.2 is from the recorded prefix; every count is from the complete counters.
  • 0x0069dbb0's fuse cannot have fired in either arm and nothing here bears on it.
  • The turn-38 encounter was auto-resolved peacefully and reproduced, which is a datum for AR's procedure and not a general licence: lane BQ measured a peaceful auto-resolve against a colony varying across three processes. This one reproduced because both sides committed nothing that fought.
  • No claim about game/ai's own behaviour. Nothing was built or compared against the engine.

7. Artefacts

what where
this document findings/control-flow/per-history-residue.md
shim config, both runs, unmodified verify/results/shim/bs/shim.cfg.bsrun (source: sots-engine src/shim/shim.cfg.bsrun, branch wip/bs)
run C — block, draws, log verify/results/shim/bs/C-{aiorders.txt,airng.txt,shim.log}
run L — block, draws, log verify/results/shim/bs/L-{aiorders.txt,airng.txt,shim.log}
run C saves verify/results/saves/bs/bs-C-endturn.sav (15b99255…), bs-C-post.sav (7a8b3d5e…), bs-C-f38-autosave.sav (0fe8f374…, the End-Turn-#1 autosave, new)
run L saves verify/results/saves/bs/bs-L-endturn.sav (33e30092…), bs-L-post.sav (4c356f59…)
inputs (unchanged) verify/results/saves/ar-turn37-816raiders.sav, verify/results/saves/ar-oracle-A-pre.sav
build CT111 /srv/re-lab/build/sots-engine-bs (rm -rf'd first), staged /srv/re-lab/shim/dist-bs, BUILD_ID bs-1654785-20260909T0629Z, exports 66/66 identical

verify/results/saves/bs/ is a subdirectory, so it is invisible to the corpus scan and the coverage ratchet is not touched (rule 27), exactly as lane BQ left bq/.


8. VM144 as left

Hold released. Guest restored and verified byte for byte against the as-found snapshot (§3.3):

SavedGames   the same 15 files, every sha256 identical to as-found
             (row positions UNCHANGED: MyGameas1predeploy (400,347), MyGameas1spydep (400,376), OK (682,624))
binkw32.dll  22,972,439 B  479b8614d2417603…   (= shimdist-l3, l3cont2-0117495-20260908T2124Z)
shim.cfg     692dabb6f00343bf…
game         RUNNING at the main menu, screenshot-verified, as found

Left in place, all additive: C:\SOTS\shimdist-bs, C:\SOTS\ui\preBS\ (the as-found SavedGames + shim.cfg.asfound + binkw32.dll.asfound), C:\SOTS\ui\bs\ (the two inputs and this lane's outputs), C:\SOTS\ui\bs{go,hash}.ps1. click_helper.ps1 is unmodified. ap_loop.ps1 and the SOTSAP task are untouched.

Six things for the next holder of VM144.

  • The Load path has a step the row-62 notes do not mention: a LOBBY. After the file-list OK the game does not go to the map — it shows the player-setup lobby ("Human", the two player cards) with Launch at (511,663) and Cancel Game at (511,692). Loading is OK → ~2 min → lobby → Launch → ~2 min → map, so the "3–4 min load" is two waits with a click in the middle, and a lane that sleeps 4 minutes and screenshots will find the lobby and think the load hung.
  • The game window is at screen (-8,-39)–(1032,749), client 1024×749 — the bottom 19 px of the game's 768-tall client is off-screen. SetCursorPos coordinates are screen coordinates and the published ones are all still correct (End Turn (100,714), Done (233,673), Auto Resolve Peacefully (667,641), OK (682,624)). But the click helper's shot captures the window, whose origin is (-8,-39), so a coordinate read off a helper screenshot must have (8,39) subtracted before it is clicked. A qm monitor screendump (tools/vmshot.py --one 144) is the screen and needs no correction — prefer it.
  • With SavedGames reset to exactly one file the row is at (400,261) — one pixel off lane BQ's (400,262) on VM145 and inside the same row. BQ's tip is confirmed on a second guest: reset to one file and there is nothing to re-derive.
  • This state's End Turn is FAST — the autosave appeared within 5 s of the click on both turns, with 16 aiprobes + the airng bracket + the aiorders dump installed. Budget the load, not the turn.
  • Startup is ~2 min (fg + key {ESC} ×3 spaced ~2.5 s, then ~90 s), confirming AR's and BQ's correction on a third guest.
  • ar-oracle-A-pre.sav is a pre-turn snapshot (Player.Status == 0), not a post-turn autosave. In a continuation it is the (Autosave EndTurn).sav of the second End Turn. Do not expect the (Autosave).sav after the first End Turn to equal it — that file is 0fe8f374… and differs in Status ×4 + Checksum only.

Proposed board rows

New rows:

| THE PER-HISTORY RESIDUE IS A TASK THAT THE SAVE DOES NOT CARRY - a Colonize task alive on turn 39 by continuation and ABSENT after a load, measured end to end | control-flow | verified | high | 100% | 2026-09-09 | **Lane BS**, VM144, `findings/control-flow/per-history-residue.md`. Predictions committed before the build was deployed (commit 15c4471). Two instrumented runs of the AR lineage to turn 39, `aiorders=on` + 16 aiprobes + the `airng` OnResumePlaying bracket, no pin, all 27 template hooks off by name. **INSTRUMENT NEUTRALITY CHECKED AT k = 1 ON BOTH ROUTES AND PASSED BYTE FOR BYTE** - continuation `15b99255...`/`7a8b3d5e...` (lane AR's three hooks=off processes), load `33e30092...`/`4c356f59...` (lane BQ's two). **THE ANSWER: `AITColonize::Execute` is entered TWICE (once per pass) in the continuation and ZERO times after a load**, while `RunTaskList` is 6 in both (3 AI clients x 2 passes), `AcquireFleetsForTask` is 12 in both and six of the eight task Execute entries are identical. It is the FIRST task in pid 32's list on both of the continuation's turns, and it is the task that reaches the pass-1 emission exits: `AssignFleetsAndIssueOrders` 2 vs **0** and `IssueRouteForFleets` 2 vs **0** - the fleet-gathering hub is NOT REACHED AT ALL on the load route. Consequence in the block: the continuation's turn-39 pid-32 block carries `list=8 idx=0 ints=[ 1570 ... ]` (Zeta Fleet V) and one extra `list=12` element `[ 914 ... ]` (Nu Fleet III), and the load's block carries NEITHER - list 8 is entirely absent. **EVERY ONE OF THE 23 DIVERGING SAVE LEAVES HAS A BLOCK-SIDE COUNTERPART** (15 on Flt[1570], 3 on Sys[432], 2 on Flt[914], plus ModCount/Checksum/NPrvVa), so nothing diverged in the SERVER's application of the block and candidate 3 (server-side state) is disfavoured. **RNG IS EXCLUDED BY MEASUREMENT: `foreign_words=0 foreign_calls=0 residual=0` in 14 of 14 brackets across both runs, and the per-process global generator 0x00af6e58 took ZERO words inside any AI bracket** (it moved 3 -> 87 words across the continuation's turn 38, entirely OUTSIDE the AI brackets - that is the combat turn). Per-client words turn 39: pid 32 = 4 (continuation) vs 5 (load), 496 and 512 = 0 on both; the ONE extra word is in the LOAD, a Chance at ret_rva 0x0029086f inside AITBuildPoliceShips::Execute - downstream of the task difference, not its cost. Pre-turn states differ in EXACTLY ONE LEAF, `/CD[1]/NPrvVa` +5.00 (BQ's correction re-measured under an instrument). VERDICT: **resolution 09-09 §9.2 candidate 1 SURVIVES and is now measured rather than inferred**; candidate 2 (stream position) does not explain a task that is not there (and the routes DO differ in stream position, so this run does not exclude it on that ground - the seed-insensitivity of both routes does); candidate 3 is disfavoured. FALSIFIER, one lane-hour: hook the task vector at `RunTaskList` entry and log `{count, per-element vtable}` for pid 32 on both routes - under this verdict the continuation's list holds a colonise task at turn-39 entry and the load's does not, and the counts differ by exactly one. **SCOPE CONFIRMED: every byte-match claim in every rung is a claim about a load->turn procedure, and that is the definition of the comparison, not a defect to fix** |
| TWO PREDICTIONS FAILED, BOTH BECAUSE THE LOAD ROUTE DOES LESS THAN ANYONE EXPECTED - and one block difference nobody predicted | meta | verified | high | 100% | 2026-09-09 | Lane BS, from the same runs. (1) **P1's list-14 clause FALSIFIED**: the continuation's turn-39 block has NO list 14 AT ALL - list 14 is emitted on turn 38 (six elements, for the two fleets minted there), and on turn 39 `IssueRouteForFleets` is entered twice and emits nothing. So "a list-8 element and its list-14 pair" was wrong about the shape of a move order. (2) **P2's "probe 11 is entered in BOTH routes" FALSIFIED**: `AssignFleetsAndIssueOrders` is entered 2x in the continuation and **0x** in the load. The brief and I both expected the load to run the same machinery to a different answer; it does not run the machinery. (3) **UNPREDICTED AND IT STRENGTHENS THE RESULT**: the block ALSO differs by one `list=12` element, fleet **914**, which is the counterpart of the OTHER save-side divergence (`Flt[914 "Nu Fleet III"]` HLay False->True, Lay only-in-load). I predicted half the block difference and the other half was there waiting - which is what turns "the AI emitted a move order" into "**every** diverging leaf is in the block". List 12's elements are fleet ids (274 290 306 322 626 642 786 882 [914] 930 1746 2338); its meaning is NOT decoded and the causal direction is NOT established (rule 20) - one watchpoint would settle it |
| A TURN THAT MINTED TWO NEW FLEET IDS DID NOT VARY - four processes, byte-identical, on a turn whose visit order was exercised | control-flow | verified | med | 100% | 2026-09-09 | Lane BS, VM144, a by-product of the continuation run. Turn 38 of the AR lineage IS a fleet-assignment turn: `AssignFleetsAndIssueOrders` entered **6x**, list 10 carries four `{systemId, fleetId}` elements `(384,2466) (384,2178) (336,2482) (384,2338)`, and decoding by `id = (counter << 4) | node` (Player[32] = node 2) all four are CLIENT-MINTED - with **2466 (counter 154) and 2482 (counter 155) absent from `ar-turn37-816raiders.sav`**, i.e. two fleets minted on this turn, in that order. That turn's output is `15b99255...`, produced by **lane AR's three `hooks=off` processes AND this instrumented run - four processes, byte for byte**. So the fleet-group visit order was exercised over two new fleets and its **outcome set was 1**. This does NOT contradict lane BP (different turn, different groups, and BP's outcome set of 2 rests on two observations) but it BOUNDS the claim: **a turn that mints new fleet ids is not thereby a turn whose visit order varies.** One turn, one datum, handed to lane BR for stage 2 - if the container is address-keyed, this turn is a case where two same-size allocations landed in the same relative order in four processes, which the LFH randomisation model has to accommodate |

Edits to existing rows:

  • Row 62 — VM144 = FREE (lane BS released 2026-09-09; guest RESTORED and verified byte-for-byte to as-found: SavedGames = the same 15 files with identical sha256 so AS's row positions STILL HOLD - MyGameas1predeploy (400,347), MyGameas1spydep (400,376), OK (682,624); binkw32.dll = shimdist-l3 22,972,439 B 479b8614..., shim.cfg = 692dabb6..., game RUNNING at the main menu screenshot-verified, as found). Lane BS left C:\SOTS\shimdist-bs + C:\SOTS\ui\preBS\ + C:\SOTS\ui\bs\ + ui\bs{go,hash}.ps1; click_helper.ps1 and ap_loop.ps1 unmodified. GOTCHA THAT THE ROW-62 CLICK PATH DOES NOT MENTION: THE LOAD PATH HAS A LOBBY. After the file-list OK the game does NOT go to the map - it shows the player-setup lobby ("Human", the two player cards) with Launch at (511,663) and Cancel Game at (511,692). The sequence is OK -> ~2 min -> LOBBY -> Launch -> ~2 min -> map, so the documented "3-4 min load" is two waits with a click in the middle; a lane that sleeps four minutes and screenshots will find the lobby and read it as a hang. GOTCHA: the game window sits at screen (-8,-39)-(1032,749) with a 1024x749 client, so the click helper's shot (which captures the WINDOW) is offset by (8,39) from screen coordinates - subtract it before clicking anything read off a helper screenshot. tools/vmshot.py --one 144 captures the SCREEN and needs no correction; prefer it. All published screen coordinates are still correct (End Turn (100,714), Done (233,673), Auto Resolve Peacefully (667,641), Load OK (682,624)). CONFIRMS BQ ON A SECOND GUEST: reset SavedGames to exactly ONE file and the Load row is at (400,261) - one pixel off BQ's (400,262) and inside the same row, nothing to re-derive. CONFIRMS AR/BQ ON A THIRD GUEST: fg + key {ESC} x3 spaced ~2.5 s then ~90 s puts the main menu up in about TWO minutes. TIP: End Turn on this state is FAST - the autosave appeared within 5 s of the click with 16 aiprobes + the airng bracket + the aiorders dump installed. Budget the LOAD, not the turn. IMAGE BASE: exe base=0x006b0000 in BOTH processes on VM144 (vs 0x00f40000 x5 on VM145 and 0x005e0000 x3 on VM146) - per boot, not per process, on a third guest.
  • Row 424 (AR's oracle pair) — append: MECHANISM FOUND 2026-09-09 (lane BS, VM144): the reason this pair does not survive a load is that a **Colonize task** alive in Player[32]'s AI agent on turn 39 by continuation is ABSENT after a load - AITColonize::Execute2 vs 0,AssignFleetsAndIssueOrders2 vs 0,IssueRouteForFleets2 vs 0, whileRunTaskList is 6 in both. Both routes reproduced their published hashes UNDER INSTRUMENT, so the pair is now certified in both forms with the instrument as well as without it.
  • Row 426 (the 09-09 resolution's row) — append: LANE BS RAN §8's VM144 PARAGRAPH 2026-09-09. §9.2 candidate 1 (AI agent state the save does not carry) SURVIVES and is measured, not inferred: the missing input is a TASK. Candidate 3 (server-side state) is disfavoured - every diverging save leaf has a block-side counterpart. Candidate 2 (stream position) is not what explains it, though the two routes DO differ in stream position, so this run does not exclude it on that ground; the seed-insensitivity of both routes does. §9.3's (a) and (d) CONFIRMED, (b) CONFIRMED and sharper than written, (c) CONFIRMED with the word difference in the OPPOSITE route from the one implied. Two clauses FALSIFIED, both over-specifications: there is no list-14 pair on turn 39, and probe 11 is NOT entered on the load route at all.
  • Row 360 / the pinned-seed rows — append: BOUNDED 2026-09-09 (lane BS): a turn that mints new client fleet ids is not thereby a turn whose visit order varies. AR turn 38 minted TWO (2466 counter 154, 2482 counter 155, both absent from ar-turn37) through six entries to AssignFleetsAndIssueOrders, and four processes (AR's three hooks=off plus BS's instrumented one) produced 15b99255... byte for byte - outcome set 1 on that turn.
  • determinism-hashes.txt — the AR load-form pair (resolution §9.3) enters with the route on the line, and BS adds a second certified procedure for the same lineage: ar-turn37-816raiders.sav -> (load, End Turn, Auto Resolve Peacefully, End Turn) -> (Autosave EndTurn) 15b99255... / (Autosave) 7a8b3d5e..., four processes, three of them un-instrumented. Both lines must carry the /CD[1]/NPrvVa mask note, and the load-form line must say that its (Autosave EndTurn) is a RESAVE of ar-oracle-A-pre.sav differing on that one leaf.
  • Lane BR's new row (RNG excluded by measurement, the global generator only mints seeds) — append a scope clause: SCOPED 2026-09-09 (lane BS, VM144, independently and on a different lineage): the three-words-one-site measurement REPRODUCES EXACTLY on ar-oracle-A-pre - rng=0x00af6e58 ret_rva=0x00384f9e va=0x00784f9e NextUInt calls=3 words=3for a whole process. **But "only mints seeds" holds only on a turn with NO ENCOUNTER.** On the AR continuation's turn 38, which has a deep-space encounter auto-resolved peacefully, the same generator ends the process at **87 words**: the 3 seed words plus0x006fb6a1Chance x80 = 80 words and two NextInt pairs at0x008622aa/0x006f3517. The 84 extra words fall between the turn-38 and turn-39 client sweeps - an interval containing the encounter - and were NOT bracketed, so their attribution to the encounter is a reading, not a measurement. **H2's verdict is untouched** (the question is whether a foreign draw fires INSIDE an AI bracket, and foreign_words=0 in 14 of 14 brackets across BS's two runs as well); only the summary sentence needs the scope. Rule 28's shape.
  • Rule 26 (c) term (5) — append one clause to the load-history term: and the difference can be that the load route does not run the machinery at all: on the AR lineage the continuation enters AssignFleetsAndIssueOrders twice on turn 39 and the load zero times, because the TASK that would have called it does not exist after a load (lane BS).