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

215 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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
```