# Lane L1 — predictions, committed before the build Written 2026-09-08 against `main` `0117495`, before any code was written and before any VM run. Instrument: `src/shim/hooks/tail_rng.cpp` (five new nested hooks), `src/shim/hooks/watchpoints.cpp` (one new arming mode). Workloads: `turn1-state.sav` (hives), `ref-turn2.sav` (oracle control and `SnLv`). The point of writing this first is that every number below can be wrong in a way that is visible in the trace. Each prediction carries its falsifier. --- ## 0. What the static reading says, re-derived independently Lane SV recovered all of this without running anything. Before predicting, its instruction reading was re-done from `dumps/sots.exe` with `objdump -M intel` at real instruction boundaries (rule 17 — disassembled past Ghidra's reported end to the `int3` padding in every case). **Everything below was confirmed byte for byte**, with two small corrections of address, not of substance. * `StrategyServer::BeginProcessTurn` **0x007d98e0 .. 0x007d9ae8** (`ret`, thiscall, no stack args). `inc [esi+0xc]` at **0x007d990a** is `Frame++`. The last thing the function does before its epilogue is the script two-step at **0x007d9ab8**: `vft[0x10](0x13, 0)` then `vft[0x60]()`. `+0x60` is exactly what lane SV's jump table gives for event 0x13. Everything else in the body is two container walks (`S+0x44` systems, `S+0x64` fleets) and an event post (`push 0x24`). * `SVSOSwarmQueen_OnTurnBegin` **0x00529930 .. 0x0052999f** (`ret`): `RegisterHives` → prune-by-`EggScio` → `TickHives` → `0x00505100`. * `SVSOSwarmQueen_RegisterHives` **0x00527630 .. 0x00527761** (`ret`). Per system in `GetGame()->+0x44..+0x48`, `[sys+0x184] == this->+0x4` and no existing hive whose `+0x4` is that system; then ``` 5276e1 mov ecx,ds:0xae0204 ; &LO 5276e7 mov edx,ds:0xae0208 ; &HI 5276ed mov eax,[eax+0xc] ; frame (eax = GetGame()) 5276f0 mov edi,[ecx] ; LO 5276f2 mov esi,[edx] ; HI 5276f4 add edi,eax ; add esi,eax 5276f8 call GetGame ; mov eax,[eax+0x16c] 527707 sub esi,edi ; bound = HI - LO 527709 lea ecx,[eax+4] ; ECX = &mt 52770f call 0x4271c0 ; RNG_NextInt <-- THE DRAW, one per new hive 527717 add eax,edi ; nextQ = frame + LO + draw 52771f call 0x523080 ; push_back ``` **Correction to `svsctob-writers.md` §4.3:** the draw call is at **0x0052770f** (return address 0x00527714), not 0x0052770c — 0x0052770c is the `mov [ebp-0x14],esi` that stores the bound. The substance is unchanged and `GetGame()` (0x00578050) returns the **`S` frame**: `[+0xc]` is `Frame` and `[+0x16c]` is the strategic generator, the same object lane Z's ledger watches. * `SVSOSwarmQueen_TickHives` **0x00527770 .. 0x005279a1** (`ret`). Four gate-failure edges (`0x005277e3`, `0x005277fe`, `0x00527848`, `0x0052784f`) all land on **`inc [esi+0x8]`** at **0x0052785a**. Gates read `[0x00ae0210]` (a frame floor), a queens-vector cap over `(this->+0x24 − this->+0x20)/12`, and `[0x00ae0228]`; the spawn arm reads `[0x00ae0220]`. **No RNG primitive is called anywhere in the body.** * `SVSOSlaversRefuel_UpdateDifficultyTier` **0x00515820**. The stack table, read off the stores: `{1,1,1} {50,2,3} {100,2,5}` at `[ebp-0x34]`, `[ebp-0x28]`, `[ebp-0x1c]`. Only the **threshold** column is ever loaded (`cmp [ecx],edx`); the other two are dead in this function. The scan is ``` 515893 xor eax,eax ; lea ecx,[ebp-0x34] 515898 cmp [ecx],edx ; jg 0x5158c2 ; first threshold > frame 51589c add eax,ebx ; add ecx,0xc ; cmp eax,3 ; jl 0x515898 5158c2 test eax,eax ; jle 0x5158a6 ; index 0 -> NO STORE 5158c6 dec eax ; cmp eax,-1 ; je ... ; (redundant with the jle) 5158cc cmp [edi+0x38],eax ; je ... ; unchanged -> NO STORE 5158d1 mov [edi+0x38],eax ; CDiff = index - 1 ``` and the class's ctor at **0x0051a820** stores `[eax+0x38] = 0xffffffff`, which is exactly the `CDiff = -1` that `turn1-state.sav` carries. **Lane SV's off-by-one reading is confirmed independently: `CDiff` can only ever hold 0 or 1.** * `SVSOSlaversRefuel_HandleEvent` **0x0051a800** is nine instructions, `ret 8`: `if (evt == 0x14) UpdateDifficultyTier(this)`. ## 1. The corpus fact this lane is built on `turn1-state.sav` (Frame 1) has `SvSctOb / EncID 10 / Hives` **count 0**. `turn2-state.sav` (Frame 2) has **two** hives, `(HiveID 336, QueenID 0, NextQ 31)` and `(400, 0, 29)`. `turn3-state.sav` has the same two with `NextQ` **32 / 30**. The two systems with `EggScio == 3` in `turn1-state.sav` are 336 and 400. So the End Turn taken from `turn1-state.sav` is **the hive-creation turn**, and the End Turn after it is **the slip turn**. Both are one click. --- ## P1 — hive creation draws, and lane Z's "residual exactly zero" is a statement about its workload On the End Turn taken from `turn1-state.sav`: 1. `SVSOSwarmQueen_RegisterHives` is **entered exactly once**, inside `BeginProcessTurn`, before `StrategyServer::ProcessTurn` runs. 2. It reports `hives_in = 0` and `hives_out = 2`. 3. Its measured word cost is **2** — one `RNG_NextInt` per new hive. (`NextInt` is a reject-on-`(y & mask) > n` loop, so 2 is the *minimum*; see the falsifier.) 4. `SVSOSwarmQueen_OnTurnBegin`'s cost is the same 2 (`TickHives` adds nothing). 5. `StrategyServer::BeginProcessTurn`'s cost is the same 2. 6. **The bracket residual is 2, not 0**: `Autosave(endTurn=1) → Autosave(endTurn=0)` total minus `ProcessTurn` minus the tail equals **2**. 7. The two hives written carry `NextQ` **31** and **29**, matching `turn2-state.sav` exactly. **What this would mean.** `tail-rng-ledger.md`'s headline — *"the residual outside the two turn drivers is exactly zero"* — is correct for turns 4, 5, 6, 18, 19, 20, 52-55 and 62-64 of two saves whose hives already existed, and **wrong as a statement about the code**. That is the same shape as "the tail draws nothing", which was true of eight turns and false of turn 64. ### Falsifiers | symptom | what it would mean | |---|---| | `RegisterHives` **not entered** | the evt-0x13 delivery does not reach `SVSOSwarmQueen` on this save. SV's `evt -> slot` map or its class table would need correcting. The entry probe separates this from "entered and drew nothing" (rule 20). | | entered, `hives_in = 0`, **0 words** | the draw is not taken — e.g. the bound `HI-LO` is 0, in which case `NextInt` may return without consuming. Lane Z's zero would stand and SV's claim would be **wrong**. | | entered, `hives_in = 2` already | the hives exist before `BeginProcessTurn`, so `turn1-state.sav` is not the pre-creation state I read it as. | | cost **> 2** | `RNG_NextInt`'s rejection loop fired. Expected: with `bound = HI-LO`, `mask = 2^ceil(log2(bound+1)) − 1`, cost per draw is `(mask+1)/(bound+1)` words on average. A cost of 3 on two hives is unremarkable and does **not** falsify the claim; it falsifies only the literal "2". | | residual **2** but `BeginProcessTurn` cost **0** | the draw is between the autosave markers but outside `BeginProcessTurn`. | | `NextQ` ≠ 31/29 | the state I loaded is not the state `turn2-state.sav` came from (different queued orders). Does not falsify the draw; does kill P3. | ## P2 — the control: the second End Turn costs 0 outside the drivers On the End Turn taken from the state P1 produced (frame 2 → 3): * `RegisterHives` is **entered**, finds both systems already referenced, and costs **0 words**. * `TickHives` is **entered**, takes a gate-failure edge on both hives, costs **0 words**, and `NextQ` reads **32 / 30** afterwards — SV's slip rule, live. * `BeginProcessTurn`'s cost is **0** and the bracket residual returns to **0**. **This is what makes P1 mean "creation draws" rather than "`BeginProcessTurn` always draws".** If P2's residual is also 2, the draw is not hive creation and P1's attribution is wrong even if its number is right. Falsifier for the slip specifically: if `NextQ` moves by anything other than +1 per hive, or if `TickHives` costs words, the `inc`-only model is wrong. ## P3 — `LO` and `HI`, which no save can show The shim prints `*(int*)0x00ae0204` and `*(int*)0x00ae0208` at `RegisterHives` entry. Lane SV could not follow these to an initialiser and correctly refused to fit them from one two-hive observation. Reading them live closes the leaf outright. Prediction: `NextQ = frame + LO + NextInt(HI − LO)` with `frame = 2` reproduces 31 and 29, i.e. `LO + r₁ = 29` and `LO + r₂ = 27`, with both `r` in `[0, HI − LO]`. Falsifier: any `(LO, HI)` for which those two are impossible. ## P4 — `CDiff`: two edges, one of them unreachable by playing forward Model, transcribed above: | frame at the tail's evt 0x14 | scan index | stored | |---|---|---| | ≤ 0 | 0 | **nothing** | | 1 … 49 | 1 | `CDiff = 0` | | 50 … 99 | 2 | `CDiff = 1` | | ≥ 100 | falls off the end | **nothing** | Predictions, in increasing cost: * **P4a (free, this run).** `UpdateDifficultyTier` is **entered once per End Turn** on `turn1-state.sav`, stores `0` on the first (frame 2, `CDiff` −1 → 0) and stores **nothing** on the second (frame 3, unchanged). The `this+0x38` region shows the store and its absence. This is rule 20's distinction applied to a *store*: "entered and stored nothing" is not "not entered". * **P4b (~48 End Turns).** The first End Turn whose frame lands in 50…99 stores `CDiff = 1`, and every later turn below 100 stores nothing. * **P4c (~98 End Turns).** At frame ≥ 100 the function is still **entered every turn** and stores **nothing**; `CDiff` stays 1 forever. Tier 2 is unreachable. * **P4d.** The `frame ≤ 0` edge is unreachable in a real game: `Frame` starts at 1 and the tail never runs before the first End Turn, which is exactly why `turn1-state.sav` carries the ctor's −1. This is recorded as a code property, not a workload gap. Falsifier for P4b/P4c: `CDiff` reading **2** at any frame falsifies the whole reading. ## P5 — `SnLv`, one arming line `nvo-tshn-visible-owner.md` §6 marks *"the sensor branch is what refreshes Spica"* as **[H]** because `SnLv` (`ServerSystem+0x24`) is not serialised and has never been measured. The watchpoint module's `tshn` mode already finds Spica by predicate at arm time; the new `snlv` mode points slot 1 at `sys+0x24` instead of the map's `_Mysize`, and additionally **logs `SnLv` for all 28 systems at arm time**, which costs no debug register at all. Prediction: Spica's `SnLv` has a **non-zero** 2-bit field for the player index its `NVO` record is keyed on (4 on `ref-turn2.sav`), i.e. `(SnLv >> 8) & 3 != 0`; and Bismol's is **zero** for player 1. That makes the sensor branch the live refresher of Spica ([H] → [V]) and settles which conjunct fails at Bismol. Falsifiers: Spica's field reads 0 → the refresh comes from somewhere else entirely and the model in §5 of that document is wrong about the mechanism even though it fits the corpus. Bismol's field reads non-zero → the failing conjunct is `HasResearched(CCC_AdvSens)` alone, which the corpus already proves true, and the "outside the sensor envelope" alternative is dead. ## P6 — the instrument is behaviour-neutral (rule 19), and I expect it might not be Five new MinHook detours are installed. Lane H proved one detour on a clean prologue boundary changed the autosave by 4 bytes. So this is a genuine prediction and not a formality: **One End Turn from `ref-turn2.sav` with the full L1 hook set live reproduces the oracle:** `(Autosave EndTurn).sav` = `bb4fd9ac89f41e3b…`, `(Autosave).sav` = `978041acd168b56e…`. If it does not, every number in P1–P4 is void and the correct report is which hook broke it, found by bisecting the five. The `hooks=off` run on the same workload is taken as well, so the hive-creation autosave has an uninstrumented twin to compare against. --- # Outcomes, appended after the runs (2026-09-08) Recorded here beside the predictions rather than only in the finding, so the pair can be read together. Full account: `sots-re/findings/control-flow/hive-creation-rng.md` and `sots-re/findings/subsystems/snlv-measured.md`. | prediction | outcome | |---|---| | **P1** hive creation draws 2 words in `BeginProcessTurn`; residual 2 | **held exactly.** `RegisterHives` entered once at depth 2, `predict_new_hives = 2`, words 170 → 172; `BeginProcessTurn` 2, `ProcessTurn` 20. The draw-site ledger independently shows a new row at return address 0x00527714, `NextInt`, 2 calls / 2 words. | | **P1** `NextQ` 31 / 29 | **held**, via a route the prediction did not spell out: `RegisterHives` writes 30 / 28 and `TickHives`' slip takes them to 31 / 29 **on the same turn**. | | **P2** the next turn costs 0 outside the drivers | **held.** `RegisterHives` entered, `predict_new_hives = 0`, 0 words; `BeginProcessTurn` 0; bracket 192 → 211 = `ProcessTurn`'s 19; residual 0. `NextQ` 32 / 30. | | **P3** `LO`/`HI` read live | **`LO = 20`, `HI = 30`** ⇒ `NextQ = frame + 20 + NextInt(10)`, inclusive. Both draws land in range (8 and 6). The three `TickHives` gate constants are 10, 5 and 3. | | **P4a** `UpdateDifficultyTier` entered every turn, stores on frame 2 only | **held.** Frame 2: `predict_path = store`, region `cdiff` −1 → 0. Frame 3: `predict_path = no_store_unchanged`, 0 → 0, **entered and stored nothing**. | | **P4b / P4c** the frame-50 and frame-100 edges | see the finding; the table scan was re-read independently and `CDiff` can only hold 0 or 1. | | **P5** Spica's `SnLv` non-zero for its `NVO` key player | **held.** `AFlags = 0x0`, `SnLv = 0x00000200`, level **2** for player 4. Bismol is not on this map, so that half is still open. | | **P5** the level is 1 | **wrong, and informatively.** The observed non-visible level is **2**; `ComputeContactLevel` has four return tails and the documented "else 1" names only one. | | **P6** the instrument is behaviour-neutral | **held.** `ref-turn2.sav` + one End Turn with all five new detours live reproduced `bb4fd9ac…` / `978041ac…` exactly, and again with the four watchpoints armed on top. | **The one thing no prediction anticipated**, and it is the most important by-product: loading `turn1-state.sav` and ending one turn produces a **different** post-turn autosave in every process, including with `hooks=off` and nothing installed. One field moves — player 3's research target — and the strategic generator does not. See the finding's §6.1.