diff --git a/findings/control-flow/per-history-residue.md b/findings/control-flow/per-history-residue.md new file mode 100644 index 0000000..a61a0fb --- /dev/null +++ b/findings/control-flow/per-history-residue.md @@ -0,0 +1,215 @@ +# 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 +```