diff --git a/findings/control-flow/seed-exposure-forward-test.md b/findings/control-flow/seed-exposure-forward-test.md new file mode 100644 index 0000000..0facc89 --- /dev/null +++ b/findings/control-flow/seed-exposure-forward-test.md @@ -0,0 +1,223 @@ +# The seed-exposure predicate's first forward test — probe 1 (parts 1b and 1) + +- **Type:** control-flow / determinism (two `hooks=off` measurements from saves already on disk; no + build, no shim config change, no instrument) +- **Owner / date:** lane **BQ** · 2026-09-09 · guest **VM145** (`sots-re-win10-145`, 192.168.10.145) +- **Spec:** `findings/resolutions/2026-09-09-seed-exposure-is-a-predicate.md` §6, probe 1 +- **Tests:** `guides/method-rules.md` rule 26 corollary (c) — the predicate written from lane AR's + falsified P7, so far supported only by three states read *after* the fact +- **Depends on:** `findings/control-flow/raid-target-pick-verdict.md` §6 (lane AR's pair) + +--- + +## 1. Predictions, committed before either run (rule 2) + +*Everything in §1 and §2 was written and committed before the game was launched on VM145 and before +any hash was read. It is not edited after the fact, only annotated with verdicts in §5.* + +### 1.1 What is actually at stake + +The resolver ruled that the per-process AI client seed (row 326) is present in **every** process on +**every** turn, and reaches the save **only** through a decision whose outcome set is larger than one +given the state — and that whether such a decision is reached is a **predicate on the pre-turn save**. +That ruling currently rests on three states read *after* their controls were run (lane AD's turn 28, +lane AS's turn 16, lane AR's turns 38–39). It has never made a forward prediction. + +This lane makes two, on states nobody has run, and both can fail. + +### 1.2 Part 1b — the precondition + +**Procedure.** `ar-oracle-A-pre.sav` (Frame 38, sha256 `15b99255e1f03dab3e35ab8c1ac64f221cb5aa9321f113c7a6e3d1263c3f34ca`), +one fresh process, `hooks=off`, **one End Turn**, no other input. + +> **P1b — committed.** The resulting `(Autosave).sav` (Frame 39) hashes to +> `7a8b3d5eb3a60ebac9f40646d3e4b15768a24a0af047a439ce090c7cf38e8b38`. + +Why it matters. Lane AR's `15b99255…` → `7a8b3d5e…` step has only ever been reached **by +continuation** — the second of two End Turns inside one process that started from +`ar-turn37-816raiders.sav`. A calibration pair for the standalone must be a **load → turn** +procedure. If P1b holds, the single-turn pair is certified in that form and is the first pair +anywhere that exercises the trade-raid roll (four raid rolls, one word each, no encounter). If it +fails, the pair remains valid **only** as the two-turn procedure from turn 37, and the resolution +names the first suspect: the **resave canonicalisation** (`determinism-oracle.md`, round-trip +section). + +> **P1b-aux — committed, and it costs nothing.** The same run also writes +> `(Autosave EndTurn).sav`, which is the state **as End Turn is pressed** — i.e. a resave of the +> file that was loaded. I predict it hashes to `15b99255…`, the input's own hash. **This is a direct +> test of the resave-canonicalisation suspect**, taken in the same run whichever way P1b goes: +> +> - P1b holds **and** P1b-aux holds ⇒ load→resave is byte-exact here and the pair is clean. +> - P1b fails **and** P1b-aux fails ⇒ the divergence is present *before the turn runs*; it is the +> round trip, not the turn, and the resave canonicalisation is confirmed as the cause. +> - P1b fails **and** P1b-aux holds ⇒ the round trip is exact and the **turn** diverged from its +> continuation form. That is the interesting failure and it is not one the resolution anticipated; +> it would mean a load boundary changes turn 39's outcome, and it is a resolver case, not a lane +> result. +> - P1b holds **and** P1b-aux fails ⇒ the resave is not byte-exact but is behaviourally equivalent; +> the pair is certified and `determinism-oracle.md`'s round-trip section needs a note. + +### 1.3 Part 1 — the exposure test + +**Procedure.** `ar-oracle-A-post.sav` (Frame 39, sha256 `7a8b3d5e…`), **two fresh processes**, +`hooks=off`, **one End Turn each**, any encounter query resolved with **`Auto Resolve Peacefully` in +both**. + +> **P1 — committed.** The two `(Autosave).sav` files **differ**, and the difference is confined to +> `Player[32]`'s `TechTree/*`, `ResTNm`, `otch`, `Events`, `turnstats/…/tch` and `Summary/Checksum`, +> while `/Sim/RNG`, `/Sim/trdmgr`, **every** `Player[16]` leaf and **every** player-0 fleet are +> identical. + +The reasoning, and it is the predicate speaking: `ar-oracle-A-post.sav` carries `ResTNm == ''` for +Player[32] (§2). By the AS precedent (`as-turn15 → 16`, 22 leaves, entered with `ResTNm == ''`) and +the row-326 precedent on `turn1-state`, that is a research-pick turn. So the predicate says, **in +advance**, that lane AR's pair must not be extended by a turn — and this run is what makes that a +measurement rather than an inference. + +**How P1 can be wrong, and the symptom of each way** (rule 2): + +| way it could be wrong | symptom | +|---|---| +| a producer supplied the target, so the pick had an outcome set of size 1 (as for player 32 on `turn1-state`) | the two files **agree** byte-for-byte; the predicate survives but needs a **producer gate**, and lane L4's `airesearch=on` dump names which producer returned non-null | +| something else on turn 40 is exposed as well as the pick | the files differ, but **outside** the named leaf set — e.g. in fleets, `trdmgr`, or `Player[16]` | +| the pick is exposed but its outcome-set is 1 for a different reason (only one affordable node) | files agree; same follow-up as the producer case, and `otch`/`TechTree` at turn 40 says which node was taken | +| VM145 is not byte-faithful to the lineage | **P1b fails** as well, and part 1b is the guard against reading part 1 off a guest that cannot reproduce anything | + +**A negative here is a result, not a null.** If the two files agree, the honest statement is *"no +exposed decision was reached on turn 40 either, and the pick is therefore producer-supplied on this +state"* — never *"the AI is deterministic here"* (rule 26 (c)). + +### 1.4 A false-positive class in the predicate, named before the run + +Read off the two saves (§2), and stated here because it is a *pre-run* correction to the predicate +as the resolution words it: + +**`ResTNm == '' at turn start` is true of four players on `ar-oracle-A-pre.sav` — the turn whose +control agreed byte-for-byte.** Players 528 (`Alien Menace`), 544 (`Peacekeeper Enforcer`), 560 +(`Von Neumann`) and 576 (`Independent Colony`) all carry `ResTNm == ''` on turn 38 **and** turn 39, +and player 16 (the human) does too. They have `NumOwn == 0`: no colonies, no research income, no +pick to make. So the raw predicate has a known false-positive class already sitting inside the very +state that falsified AR's P7. + +The sharp form the next lane should use is therefore: + +> `ResTNm == ''` **at turn start, for a non-eliminated AI player with `NumOwn > 0`**, names a +> research-pick turn. + +On `ar-oracle-A-post.sav` exactly one player satisfies that: **Player[32]**, `NumOwn 18`. That is +the whole basis of P1, and it is why P1 names Player[32]'s leaves and no one else's. + +*(Note for honesty: players 496 and 512 — the two `Spengler` NPCs — hold non-empty `ResTNm` and also +have `NumOwn == 0`. They are not evidence either way; they are listed in §2 for completeness.)* + +### 1.5 What this lane will not settle (rule 15) + +- **Two processes, not three**, for part 1 — rule 26's bar, matching AR's. +- **One state.** Neither part says anything about `ad-turn27`, which is probe 2's job on VM146. +- **The producer gate is not run here** unless part 1 comes out as agreement and time allows; if it + does not, this document names it as the next step rather than guessing at it. +- **`0x0069dbb0`'s 3–40-turn fuse cannot fire** in a one-turn-from-load run, by construction — so + neither part can say anything about it, in either direction. +- **No instrument.** `hooks=off` throughout, so this lane measures behaviour and never attributes it + to a draw site. The consumer identification is probe 3's job. + +--- + +## 2. The exposure facts, read off the saves before the runs (rule 26 (c)) + +`uv run python3 verify/save-reader/save_reader.py --json`, three saves on the AD/AR lineage. +`NumDes` = the `designs` list length, `NumOwn` = the `owners` list length (both are the on-wire count +prefixes; verified against the `--dump` tree at `NumOwn`/`NumDes`). "ships +" is the per-owner +`ShipID` set delta from the previous save in the lineage. + +### 2.1 `ar-oracle-A-pre.sav` — Frame 38, `ModCount 2108`, the input to part 1b + +| PID | PlyrIdx | name | species | NPC | `ResTNm` | `NumDes` | `NumOwn` | ships | fleets | +|---|---|---|---|---|---|---|---|---|---| +| 16 | 0 | `re` (human) | 0 | no | `''` | 6 | 3 | 5 | 5 | +| **32** | **1** | **The Eternal Empire** | 2 | no | **`BIO_EnvTail`** | **47** | **18** | 243 | 59 | +| 496 | 2 | Spengler | 0 | yes | `DRV_PlsFiss` | 0 | 0 | 0 | 0 | +| 512 | 3 | Spengler | 2 | yes | `XNC_TrnsMorr2` | 0 | 0 | 0 | 0 | +| 528 | 4 | Alien Menace | 4 | yes | `''` | 19 | 0 | 0 | 0 | +| 544 | 5 | Peacekeeper Enforcer | 4 | yes | `''` | 1 | 0 | 0 | 0 | +| 560 | 6 | Von Neumann | 4 | yes | `''` | 6 | 0 | 0 | 0 | +| 576 | 7 | Independent Colony | 4 | yes | `''` | 1 | 0 | 0 | 0 | + +**Predicate reading for turn 38→39:** the only player that can research holds a target +(`BIO_EnvTail`), `NumDes` is constant at 47, `NumOwn` constant at 18. **No exposed decision +predicted** — which is exactly what AR measured (three processes agreed). Part 1b is therefore a +*re-run of an agreeing turn from a load*, and its only open question is the load boundary itself. + +### 2.2 `ar-oracle-A-post.sav` — Frame 39, `ModCount 2147`, the input to part 1 + +| PID | PlyrIdx | name | species | NPC | `ResTNm` | `NumDes` | `NumOwn` | ships | fleets | +|---|---|---|---|---|---|---|---|---|---| +| 16 | 0 | `re` (human) | 0 | no | `''` | 6 | 3 | 5 | 5 | +| **32** | **1** | **The Eternal Empire** | 2 | no | **`''`** ← **pick signature** | **47** | **18** | 245 | 59 | +| 496 | 2 | Spengler | 0 | yes | `DRV_PlsFiss` | 0 | 0 | 0 | 0 | +| 512 | 3 | Spengler | 2 | yes | `XNC_TrnsMorr2` | 0 | 0 | 0 | 0 | +| 528 | 4 | Alien Menace | 4 | yes | `''` | 19 | 0 | 0 | 0 | +| 544 | 5 | Peacekeeper Enforcer | 4 | yes | `''` | 1 | 0 | 0 | 0 | +| 560 | 6 | Von Neumann | 4 | yes | `''` | 6 | 0 | 0 | 0 | +| 576 | 7 | Independent Colony | 4 | yes | `''` | 1 | 0 | 0 | 0 | + +**Predicate reading for turn 39→40: EXPOSED.** Player[32] enters with `ResTNm == ''` and `NumOwn 18`. +`otch` moved 46 → 47 across turn 39 (`BIO_EnvTail` completed), which is the same shape AS recorded. + +### 2.3 Ship completions on the lineage, for the fleet-assignment half of the predicate + +| transition | `ModCount` | PID 16 ships | PID 32 ships | reading | +|---|---|---|---|---| +| turn 37 → 38 | 2057 → 2108 | **−7408** (`Zeta Fleet`'s destroyer, lost in the encounter) | **+8784, +8800** | 2 completions, into existing fleets — not a batch | +| turn 38 → 39 | 2108 → 2147 | none | **+8816, +8832** | 2 completions, into existing fleets — not a batch | + +Two ships a turn into existing `Freighters` fleets is the *quiet* fleet consumer, not the +thirteen-into-five-new-fleets batch that AD's turn 28 carried. `designIds` is constant at 95 and +`fleetIds` at 64 across both transitions, corroborating "no design turn, no fleet creation". + +**So: on the input to part 1b the predicate says NOT exposed; on the input to part 1 it says +EXPOSED, via the research pick, for exactly one player.** + +--- + +## 3. Procedure and configuration + +`hooks=off` throughout, no build (rule 24 is satisfied trivially — nothing is compiled). + +| item | value | +|---|---| +| guest | VM145 `sots-re-win10-145`, 192.168.10.145 | +| shim build | `C:\SOTS\shimdist-l4\binkw32.dll`, `BUILD_ID l4r-20260908T2208Z` | +| config | `C:\SOTS\shimdist-l4\shim.cfg.l4off`, **unmodified** — `hooks=off`, installs nothing | +| why this build | lane L4 proved *on this guest, with this exact DLL and this exact config* that VM145 reproduces the campaign's published autosave oracle byte-identically (`ai-order-capture.md` §3: `bb4fd9ac89f41e3b` / `978041acd168b56e`). It is the only hooks=off pair on VM145 with a published byte-fidelity result behind it | +| `SavedGames` | reset to **exactly one file** — the run's input save — before every launch, so the Load dialog has one row and its position is re-derived from a screenshot anyway | +| main menu | verified from a `qm monitor` screendump (`tools/vmshot.py --one 145`), never by sleeping | +| encounter query | detected by sampling the `Done` pixel at (233, 673): ≈(183,18,0) query up, ≈(8,8,8) map. `Auto Resolve Peacefully` (667,641), `Done` (233,673) | + +**VM145 as found**, recorded so it can be put back (§8): + +``` +binkw32.dll 15,527,327 B sha256 903527f4a698eea9fbe25f3a6236657c7d8348f994eef394696f187e5e554b97 + (= shimdist-recap, BUILD_ID recap-7584bad-20260908T0615Z) +shim.cfg sha256 0ae410cc72fe155837d711c78543e14b1f5743310cd023298eb1e5bbd5457e71 (hooks=trace, lane R recapture) +SavedGames 9 files, saved to C:\SOTS\ui\preBQ\ +game not running +``` + +--- + +## 4. Results + +*(added after the runs; nothing above this line is edited)* + +--- + +## 5. Verdicts on the committed predictions + +*(added after the runs)* + +--- + +## Proposed board rows + +*(added after the runs)*