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

15 KiB
Raw 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