From 10ddf09cb4510e692219f09c77e0a07a2b23e252 Mon Sep 17 00:00:00 2001 From: alex Date: Wed, 9 Sep 2026 02:02:10 -0400 Subject: [PATCH] lane BP merged: C-exact false on a rich turn, residue is one fleet-id transposition; rule 26(c) qualified --- campaign/DASHBOARD.md | 24 ++++++++++++------------ campaign/board.md | 11 +++++++---- guides/method-rules.md | 6 +++++- 3 files changed, 24 insertions(+), 17 deletions(-) diff --git a/campaign/DASHBOARD.md b/campaign/DASHBOARD.md index 8985220..6bc5d5d 100644 --- a/campaign/DASHBOARD.md +++ b/campaign/DASHBOARD.md @@ -1,16 +1,16 @@ # SotS RE campaign — coverage dashboard -Generated 2026-09-09 05:36 UTC · `sots-re` @ f14e845,2026-09-09 · `sots-engine` @ 989c692,2026-09-08 (240 commits) · regenerate with `tools/dashboard.py` +Generated 2026-09-09 06:02 UTC · `sots-re` @ 371f789,2026-09-09 · `sots-engine` @ 1654785,2026-09-09 (241 commits) · regenerate with `tools/dashboard.py` > **North star:** A functional reimplementation of the engine — behavior-equivalent, NOT byte-for-byte ## 1. Map coverage (campaign/board.md) -422 targets · mapped-or-better **375/422** `[█████████░] 89%` · verified **333/422** `[████████░░] 79%` +425 targets · mapped-or-better **378/425** `[█████████░] 89%` · verified **336/425** `[████████░░] 79%` | Status | Count | % | |---|---:|---:| -| verified | 333 | 79% | +| verified | 336 | 79% | | mapped | 42 | 10% | | in-progress | 4 | 1% | | backlog | 41 | 10% | @@ -19,12 +19,12 @@ Generated 2026-09-09 05:36 UTC · `sots-re` @ f14e845,2026-09-09 · `sots-engine | Type | verified | mapped | in-progress | backlog | blocked | total | |---|---:|---:|---:|---:|---:|---:| | objects | 46 | 6 | 0 | 3 | 1 | 56 | -| control-flow | 32 | 2 | 0 | 0 | 0 | 34 | +| control-flow | 33 | 2 | 0 | 0 | 0 | 35 | | subsystems | 4 | 8 | 0 | 2 | 1 | 15 | -| engine | 30 | 0 | 0 | 0 | 0 | 30 | +| engine | 31 | 0 | 0 | 0 | 0 | 31 | | verify | 104 | 15 | 3 | 35 | 0 | 157 | | phase2 | 13 | 3 | 1 | 0 | 0 | 17 | -| meta | 85 | 6 | 0 | 1 | 0 | 92 | +| meta | 86 | 6 | 0 | 1 | 0 | 93 | | other | 19 | 2 | 0 | 0 | 0 | 21 | ## 2. Binary understanding @@ -38,7 +38,7 @@ Generated 2026-09-09 05:36 UTC · `sots-re` @ f14e845,2026-09-09 · `sots-engine - Catalogs: **1,595/1,595** files parsed (91 block kinds in `schema_stats.json`), dangling cross-refs **0** (`crosslink.json`) - Oracle `mars-parse`: **1,531/1,531** files agree `[██████████] 100%` - Oracle `mars-text`: **64/64** files agree `[██████████] 100%` -- Saves: **38/38** real saves strict-clean — strict exit 0, 0 errors, 0 warnings +- Saves: **41/41** real saves strict-clean — strict exit 0, 0 errors, 0 warnings - Design rules: **127/127** stock designs pass `[██████████] 100%` - Value domains: **490/724** typed fields have been seen to vary `[███████░░░] 68%` — the other **234** have only ever held one value across the corpus, so their typing is untested (`value-domain-census.md`) @@ -66,7 +66,7 @@ Generated 2026-09-09 05:36 UTC · `sots-re` @ f14e845,2026-09-09 · `sots-engine | `shim/trace` | 2,258 | 9 | 273 | direct (WIN32) | shim-trace.md | | **total** | **58,647** | **122** | **4257** | | | -Board `engine:` rows: verified **30**, mapped 0, in flight 0 (of 30) — verified & merged `[██████████] 100%` +Board `engine:` rows: verified **31**, mapped 0, in flight 0 (of 31) — verified & merged `[██████████] 100%` ## 5. Phase 2 milestones @@ -112,7 +112,7 @@ Detail: `verify/results/standalone/report.txt`. ## 7. Verification ledger -- ✅ Saves strict: 38/38 (strict exit 0, 0 errors, 0 warnings) +- ✅ Saves strict: 41/41 (strict exit 0, 0 errors, 0 warnings) - ✅ Design rules: 127/127 - ✅ oracle mars-parse 1531/1531 · ✅ oracle mars-text 64/64 - ✅ Compare harness present (`verify/harness/compare/`) @@ -133,11 +133,11 @@ Most recent open: ## 9. Delta since previous dashboard -- verified targets: 330 → 333 (+3) · mapped-or-better: 372 → 375 (+3) +- verified targets: 333 → 336 (+3) · mapped-or-better: 375 → 378 (+3) - engine LOC: 58,647 → 58,647 (+0) · test files: 122 → 122 (+0) · checks: 4,257 → 4,257 (+0) -- addresses verified: 1,152 → 1,160 (+8) · recovered layouts: 384 → 384 (+0) · open questions: 26 → 26 (+0) +- addresses verified: 1,160 → 1,160 (+0) · recovered layouts: 384 → 384 (+0) · open questions: 26 → 26 (+0) - standalone leaves closed: 45 → 45 (+0) · leaves still diverging: 63 → 63 (+0) --- warnings: board.md: unknown types subsystems; mars-rng.md: no oracle total row parsed; mars-stream.md: no oracle total row parsed; mars-vfs.md: no oracle total row parsed - + diff --git a/campaign/board.md b/campaign/board.md index f8cb571..3ee08b3 100644 --- a/campaign/board.md +++ b/campaign/board.md @@ -59,7 +59,7 @@ Status flow: `backlog → in-progress → mapped → verified` (or `blocked`). | P2-B3 ProcessResearch (behavioral, RNG) | phase2 | mapped | high | 85% | 2026-09-08 | LIVE, PARTIAL PASS: 15 calls compared, 13 zero-divergence; RNG post-state matched 14/15 incl. every roll (validates MT19937 + draw mapping + odds together). 2 divergences are the declared SetResearched boundary. ORACLE FAILS by exactly one item across 40,300: an unposted EVENT_RESEARCH_OVERBUDGET - compare was blind because the event list was never a declared region. fpu_cw=0x127f => 53-bit double, x87 question SETTLED. No Zuul in the save: double roll still disassembly-only . RECAPTURED WITH GUARDS 2026-09-08 (lane R): the oracle gap is now a COMPARE DIVERGENCE - `side.events.after.v.next_id orig=4 ours=3` on call 0, its only divergent field, with node[144] progress 2879->5768 and flag 1->2 both reproduced and the single RNG draw identical. 15 calls over 5 turns: 3 diverged, **RNG 15/15** (better than the original 14/15 - no tech-effect draw in this session). Guards on the two completion calls map SetResearched: ConMod[0..2]/OutMod/PopMod, ResTNm, TechTree+0x20 order counter, and the undeclared otch vector | . **LANE V 2026-09-08 (build `eventlive-dd38117-20260908T0916Z`): the events divergence is CLOSED on the reference turn** - 3 calls, 3 compared, **0 diverged, exit 0**. Over 5 turns 15 calls / **2** diverged (was 3), both short by exactly 1 `next_id` = the unmodelled `EVENT_TECHS_UNLOCKED`. **CORRECTION to lane R's 'RNG 15/15': that was WORKLOAD LUCK, not a property.** On lane V's call 9 the completed tech had `research_roll_pending` set, so the original drew one word `ours` did not (`left` 374 vs 375, `next_index` 250 vs 249) - the `OnTechResearched` callback draw, a DECLARED out-of-scope boundary (P-events-wiring §3 says so in as many words). Honest statement: the `rng` region reproduces bit-for-bit on every call that does not complete a roll-triggering tech, and is short by exactly one draw on every call that does. Also live: `sizeof(ObservedTech)` = **44**, measured twice (`observed_techs.bytes` 440->484 and 484->528). Zuul double roll now CLOSED on a species-5 save lane V made - see its own row | RNG signatures (Ghidra) | meta | verified | high | 100% | 2026-09-08 | Seed/Twist/NextFloat/NextInt verified; draw = y/(2^32-1); NextInt [0,n] inclusive; lazy twist; left@+0x9c4. RUNTIME CONFIRMED: fpu_cw=0x127f (53-bit double, round-nearest) - our next_float model is right, float_from_pc24 is an unused contingency | | engine: game/effects | engine | verified | high | 100% | 2026-09-08 | merged: TechId enum (196 slots @10000+i), 44 ids with typed strategic effects, species flag bits, ApplyTechEffect; 254 checks | -| VM140 exclusivity (lab rule) | meta | verified | high | 100% | 2026-09-08 | one agent at a time. Holder: **VM140=FREE, oracle bytes verified intact), VM141=FREE (L2 released, 192.168.10.143, hooks=off), VM144=FREE (L3 released; workload saves left on disk, so Load-dialog row positions have moved on that guest), VM145=FREE, save set restored), VM146=FREE (CB released, stopped, restored)** (five parallel investigations 2026-09-08). VM140 remains the reference/oracle guest. Was: FREE (W3 released 2026-09-08, oracle bytes verified; **AND THE LAB IS NOW FIVE GUESTS** - VM141/144/145/146 are ZFS clones for probes and workloads, VM140 remains the exclusive reference; W2 released 2026-09-08, VM restored + oracle bytes verified; H released 2026-09-08, oracle reproduced + VM restored; N released 2026-09-08; Z released 2026-09-08; O-workloads released 2026-09-08 08:30 local, see the LANE O note at the end of this row; U-unlock released 2026-09-08 06:55 local; V-eventlive before it; F-fpucw before it, M-movefleet before that). QUEUE: empty. VM left at the MAIN MENU, `hooks=trace`, build `recap-7584bad-20260908T0615Z` restored from `C:\SOTS\shimdist-recap` (that dist also carries `shim.cfg.recap{trace,b3,b1,misc}`). Lane M also left `C:\SOTS\shimdist-mf` + `C:\SOTS\ui\mf{deploy,release}.ps1` in place - harmless, and a working template for the next lane. Windows Update DISABLED/paused on the VM. Non-holders build /srv/re-lab/build/sots-engine-, stage dist-, deploy C:\SOTS\shimdist-. GOTCHA (lane R): after `schtasks /Run /TN SOTS` the main menu can take >60 s - SCREENSHOT AND VERIFY before clicking, or the click path lands in Credits. GOTCHA (lane M): drive the load dialog ONE rui.ps1 CALL PER CLICK with a screenshot between - a single chained cmd.txt loses sync and silently ends up somewhere else. And the Load Game dialog does NOT pre-select Single Player on a fresh launch: the documented path really is Load Game (512,536) -> Single Player (512,290) -> OK (551,523) -> row -> OK (682,624) -> Launch (511,663). ref-turn2 row is at (400,436) GOTCHA (lane F, confirms lane R): the >60 s startup is REAL and cost a whole wasted run — do not sleep-and-click, **verify the main menu from a screenshot** (`verify/fpu-cw/` run scripts poll a screenshot until the Load Game / Exit buttons are bright red; 3 probes ≈ 25 s was typical). TIP (lane F): reset `SavedGames\` to a fixed file set before every run — the Load dialog row positions depend on how many files are listed, so a constant set means the click path never has to be re-derived (with the 4-file set ref-turn2 sits at (400,348), not (400,436)). PowerShell over SSH mangles quoting badly: send snippets base64 as `powershell -EncodedCommand`, or use `-ExecutionPolicy Bypass -File`. Lane F left `C:\SOTS\shimdist-fpu` + `C:\SOTS\ui\f{deploy,grab,fpu}.ps1` + `C:\SOTS\ui\preF\` (the pre-lane-F SavedGames snapshot, restored) in place. VM RESTORED: recap build `recap-7584bad-20260908T0615Z`, `hooks=trace`, SavedGames back to the 7-file pre-F set, main menu verified by screenshot. GOTCHA (lane V, third confirmation): the >60 s startup is REAL - the Kerberos intro was still on screen at t+30 s and the main menu appeared at ~t+70 s. GOTCHA (lane V): `type ` via the click helper did **NOT** register in the game's text fields this session (neither the Create Game name nor the Save Game file name took SendKeys); the workaround is to accept the default name and **rename the `.sav` on disk** - the Load dialog lists files by FILENAME. SAVEDGAMES IS NOW **8 FILES**: the pre-existing 7 plus `zuul-turn5.sav` (a species-5 save, see the Zuul row). Row positions are UNCHANGED for existing lanes because `zuul-turn5` sorts last: **ref-turn2 is still (400,436)**, zuul-turn5 is the new row 8 at (400,465), rows 29 px apart from y=262 - verified by screenshot `verify/results/shim/eventlive/load-dialog-8files.png`. The three autosaves now belong to the Zuul game. Lane V left `C:\SOTS\shimdist-v` + `C:\SOTS\ui\v{deploy,release,click}.ps1`. VM RESTORED: recap build `recap-7584bad-20260908T0615Z`, `hooks=trace`, main menu verified by screenshot. LANE U (2026-09-08): left `C:\SOTS\shimdist-u` + `C:\SOTS\ui\u{deploy,release}.ps1` in place. SavedGames is STILL 8 FILES (the three autosaves now belong to the Zuul game at turn 15) - **ref-turn2 is still (400,436)**, zuul-turn5 still (400,465). A QEMU-level screenshot (feed `screendump /tmp/vm140.ppm` to `qm monitor 140` on spicy, then scp the .ppm) is more reliable than the click helper's `shot` action and does not need the game window. FOURTH confirmation of the >60 s startup: the Kerberos intro was still on screen at t+45 s, the main menu at ~t+85 s. VM RESTORED: recap build `recap-7584bad-20260908T0615Z`, `hooks=trace`, main menu verified by screenshot. | **LANE O 2026-09-08.** **VM140 is FREE.** Holder was O-workloads; released 2026-09-08 after ~9 turns of Zuul play (turn 15 -> 23) and a fresh 3-turn Human game. **CORRECTION to lane V's `type` gotcha: the click helper's `type` DOES reach the game's text fields - you must CLICK THE FIELD FIRST.** `click 480 234` (the Save Game File Name box) then `type ` worked every time this session, and `click 500 363` + `type` worked for the Create Game name box; lane V's failure was almost certainly an unfocused field. Saves can therefore be named properly and no longer need renaming on disk (the file written is gameName + typedName). Coordinates confirmed this session at 1024x768: in-game menu = round button (1000,714) -> `Save` (937,679) / `Quit to Main Menu` (937,698) -> confirm OK (537,377); Save dialog `Save` (772,233), `Game Successfully Saved` OK (612,383), `Done` (772,542); map panel `Research` (55,124) `Design` (142,124) `Build` (221,124), `Manage Fleets` (60,464) `Move` (155,464) `Special` (229,464), `End Turn` (100,714); Build screen `Add To Queue` (125,714); **Esc leaves the Build and Research screens** - the bottom-left button on the Research screen is `Cancel research`, NOT back, and clicking it pops `Confirm Cancel`. Move mode: send `move X Y` FIRST to read the target's name / Range / ETA off the map, THEN `click`, because that readout is what tells you whether the route is a node route (2-5 turns) or a sublight crawl (37-86 turns). **Chained clicks in one `cmd.txt` DO work for repeated clicks on the SAME button** (`Add To Queue`) but drop about half at `sleep 250` and about 1 in 6 at `sleep 500` - count the result, never assume. End Turn takes 30-45 s under `hooks=trace` with ~25 ships. A mid-turn AI diplomacy popup can steal a scripted click sequence - screenshot after any batch that seems to have done nothing. **VM RESTORED and verified**: quit to the main menu (screenshot), `SavedGames` back to the ORIGINAL 8-file set with the three autosaves restored BYTE-IDENTICAL (sha256 prefixes 0630daaa / e0bf3485 / 1f0dc162), so `ref-turn2` is still (400,436) and `zuul-turn5` still (400,465); lane O's staging dir `C:\SOTS\o-stage` removed; `shim.cfg` still `hooks=trace`, build `recap-7584bad-20260908T0615Z` untouched. Lane O's 7 saves live in the REPO ONLY - push one back with `scp verify/results/saves/.sav re@192.168.10.139:C:/SOTS/SavedGames/` when a compare needs it, and remember that adding a file shifts the Load dialog rows (`MyGame*` sorts BEFORE `MyGameverify1*` **VM141 = FREE (lane AD released 2026-09-08; guest restored, SavedGames back to AC's 13-file set byte-identical PLUS `ad-turn27-two-raiders.sav`, so Load-dialog rows have MOVED AGAIN - screenshot the dialog).** Lane AD left `C:\SOTS\shimdist-ad` + `C:\SOTS\ui\ad-*.ps1`; `click_helper.ps1` unmodified. **GOTCHA: startup on this guest is ~4.5 min through THREE intro movies** (fg + `key {ESC}` x3 via the click helper, then ~90 s), and loading a turn-27 save is a further 3-4 min - budget **~9 min per run** before the first End Turn. **GOTCHA: the first End Turn click after leaving Move mode is swallowed exiting the mode**; the turn starts on the second click. **VM144 = FREE (lane AS released 2026-09-09**; restored to L3's build + `shim.cfg` + the three pre-AS autosaves byte-identical, main menu verified by screenshot). **SavedGames is now 15 FILES** - AS added `MyGameas1predeploy` (400,347) and `MyGameas1spydep` (400,376), so **ROW POSITIONS HAVE MOVED AGAIN**; screenshot the dialog. AS left `C:\SOTS\shimdist-as` + `ui\as{go,hash}.ps1` + `ui\preAS-*`; `click_helper.ps1` untouched. **VM141 = FREE (lane AR released 2026-09-09**; restored, `shim.cfg.hoff`, this lane's `binkw32.dll`, SavedGames = AD's set PLUS `ar-turn37-816raiders.sav`; with autosaves deleted the list is 12 rows from y=260 at 29 px pitch, `ar-turn37-816raiders` is **row 3 at (400,318)**). Left `C:\SOTS\shimdist-ar` + `ui\ar-*.ps1`; `click_helper.ps1` unmodified. **GOTCHA, CORRECTS THE STARTUP FIGURE: `fg` + `key {ESC}` x3 spaced ~2.5 s then ~90 s puts the main menu up in about TWO minutes, not 4.5** - the 4.5 figure is what you get by waiting the movies out. Loading a turn-37 save is a further 3-4 min and that part is NOT compressible. **GOTCHA: `rdrag x y x y` (a zero-length right-drag) IS A RIGHT-CLICK** - the fleet panel's `Add To New Fleet`/`Split Ships` menu is right-click-only and the helper has no right-click verb. **GOTCHA: a trade-sector node is a legal move target EVEN WHEN ITS CUBE IS NOT DRAWN** - in Move mode the hover readout names it; fit `screen = (A*wx + B*wz + C, D*wx + E*wz + F)` from two known points in the save and you land on any node first try. **GOTCHA: the encounter query BLOCKS End Turn** - sample the `Done` pixel at (233,673): ~(183,18,0) when a query is up, ~(8,8,8) on the map; `Auto Resolve Peacefully` (667,641), `Done` (233,673). **VM144 = FREE (lane AP released 2026-09-09**; restored to L3's build + L3's `shim.cfg` + the three pre-AS autosaves BYTE-IDENTICAL, main menu screenshot-verified). SavedGames **STILL 15 FILES** and AS's row positions **STILL HOLD** - `MyGameas1predeploy` (400,347), `MyGameas1spydep` (400,376), OK (682,624), verified twice. AP added `C:\SOTS\shimdist-ap`, `ui\ap{go,hash}.ps1`, `ui\ap\` - **an END-TURN DRIVER WORTH REUSING**: `ap_loop.ps1` + the `SOTSAP` task clicks End Turn, **polls the autosave mtime**, snapshots `(Autosave).sav` per turn and stops on the first stall with a screenshot - **11 turns unattended in 11 minutes**. **GOTCHA THAT COST NINE MINUTES: the click task takes ~6 s end to end, so a screenshot taken 4 s after `schtasks /Run` shows the state BEFORE the click** - four such shots read exactly like an 87 px coordinate offset and **nearly became a false gotcha in this very row**. WAIT >= 9 s. **GOTCHA: the first click after `fg` is swallowed by focus activation.** **GOTCHA: the main-menu pixel test must sample the BUTTON (470,530) = (184,0,0), not the white label at (511,536).** TIP: `wheel` zoom separates stars that share a hit test at default zoom (Kepler vs Midway, which cost lane AS a run) - no affine fit needed. An End Turn on this state costs **6 SECONDS** under `probes=8`. | +| VM140 exclusivity (lab rule) | meta | verified | high | 100% | 2026-09-08 | one agent at a time. Holder: **VM140=FREE, oracle bytes verified intact), VM141=FREE (L2 released, 192.168.10.143, hooks=off), VM144=FREE (L3 released; workload saves left on disk, so Load-dialog row positions have moved on that guest), VM145=FREE, save set restored), VM146=FREE (CB released, stopped, restored)** (five parallel investigations 2026-09-08). VM140 remains the reference/oracle guest. Was: FREE (W3 released 2026-09-08, oracle bytes verified; **AND THE LAB IS NOW FIVE GUESTS** - VM141/144/145/146 are ZFS clones for probes and workloads, VM140 remains the exclusive reference; W2 released 2026-09-08, VM restored + oracle bytes verified; H released 2026-09-08, oracle reproduced + VM restored; N released 2026-09-08; Z released 2026-09-08; O-workloads released 2026-09-08 08:30 local, see the LANE O note at the end of this row; U-unlock released 2026-09-08 06:55 local; V-eventlive before it; F-fpucw before it, M-movefleet before that). QUEUE: empty. VM left at the MAIN MENU, `hooks=trace`, build `recap-7584bad-20260908T0615Z` restored from `C:\SOTS\shimdist-recap` (that dist also carries `shim.cfg.recap{trace,b3,b1,misc}`). Lane M also left `C:\SOTS\shimdist-mf` + `C:\SOTS\ui\mf{deploy,release}.ps1` in place - harmless, and a working template for the next lane. Windows Update DISABLED/paused on the VM. Non-holders build /srv/re-lab/build/sots-engine-, stage dist-, deploy C:\SOTS\shimdist-. GOTCHA (lane R): after `schtasks /Run /TN SOTS` the main menu can take >60 s - SCREENSHOT AND VERIFY before clicking, or the click path lands in Credits. GOTCHA (lane M): drive the load dialog ONE rui.ps1 CALL PER CLICK with a screenshot between - a single chained cmd.txt loses sync and silently ends up somewhere else. And the Load Game dialog does NOT pre-select Single Player on a fresh launch: the documented path really is Load Game (512,536) -> Single Player (512,290) -> OK (551,523) -> row -> OK (682,624) -> Launch (511,663). ref-turn2 row is at (400,436) GOTCHA (lane F, confirms lane R): the >60 s startup is REAL and cost a whole wasted run — do not sleep-and-click, **verify the main menu from a screenshot** (`verify/fpu-cw/` run scripts poll a screenshot until the Load Game / Exit buttons are bright red; 3 probes ≈ 25 s was typical). TIP (lane F): reset `SavedGames\` to a fixed file set before every run — the Load dialog row positions depend on how many files are listed, so a constant set means the click path never has to be re-derived (with the 4-file set ref-turn2 sits at (400,348), not (400,436)). PowerShell over SSH mangles quoting badly: send snippets base64 as `powershell -EncodedCommand`, or use `-ExecutionPolicy Bypass -File`. Lane F left `C:\SOTS\shimdist-fpu` + `C:\SOTS\ui\f{deploy,grab,fpu}.ps1` + `C:\SOTS\ui\preF\` (the pre-lane-F SavedGames snapshot, restored) in place. VM RESTORED: recap build `recap-7584bad-20260908T0615Z`, `hooks=trace`, SavedGames back to the 7-file pre-F set, main menu verified by screenshot. GOTCHA (lane V, third confirmation): the >60 s startup is REAL - the Kerberos intro was still on screen at t+30 s and the main menu appeared at ~t+70 s. GOTCHA (lane V): `type ` via the click helper did **NOT** register in the game's text fields this session (neither the Create Game name nor the Save Game file name took SendKeys); the workaround is to accept the default name and **rename the `.sav` on disk** - the Load dialog lists files by FILENAME. SAVEDGAMES IS NOW **8 FILES**: the pre-existing 7 plus `zuul-turn5.sav` (a species-5 save, see the Zuul row). Row positions are UNCHANGED for existing lanes because `zuul-turn5` sorts last: **ref-turn2 is still (400,436)**, zuul-turn5 is the new row 8 at (400,465), rows 29 px apart from y=262 - verified by screenshot `verify/results/shim/eventlive/load-dialog-8files.png`. The three autosaves now belong to the Zuul game. Lane V left `C:\SOTS\shimdist-v` + `C:\SOTS\ui\v{deploy,release,click}.ps1`. VM RESTORED: recap build `recap-7584bad-20260908T0615Z`, `hooks=trace`, main menu verified by screenshot. LANE U (2026-09-08): left `C:\SOTS\shimdist-u` + `C:\SOTS\ui\u{deploy,release}.ps1` in place. SavedGames is STILL 8 FILES (the three autosaves now belong to the Zuul game at turn 15) - **ref-turn2 is still (400,436)**, zuul-turn5 still (400,465). A QEMU-level screenshot (feed `screendump /tmp/vm140.ppm` to `qm monitor 140` on spicy, then scp the .ppm) is more reliable than the click helper's `shot` action and does not need the game window. FOURTH confirmation of the >60 s startup: the Kerberos intro was still on screen at t+45 s, the main menu at ~t+85 s. VM RESTORED: recap build `recap-7584bad-20260908T0615Z`, `hooks=trace`, main menu verified by screenshot. | **LANE O 2026-09-08.** **VM140 is FREE.** Holder was O-workloads; released 2026-09-08 after ~9 turns of Zuul play (turn 15 -> 23) and a fresh 3-turn Human game. **CORRECTION to lane V's `type` gotcha: the click helper's `type` DOES reach the game's text fields - you must CLICK THE FIELD FIRST.** `click 480 234` (the Save Game File Name box) then `type ` worked every time this session, and `click 500 363` + `type` worked for the Create Game name box; lane V's failure was almost certainly an unfocused field. Saves can therefore be named properly and no longer need renaming on disk (the file written is gameName + typedName). Coordinates confirmed this session at 1024x768: in-game menu = round button (1000,714) -> `Save` (937,679) / `Quit to Main Menu` (937,698) -> confirm OK (537,377); Save dialog `Save` (772,233), `Game Successfully Saved` OK (612,383), `Done` (772,542); map panel `Research` (55,124) `Design` (142,124) `Build` (221,124), `Manage Fleets` (60,464) `Move` (155,464) `Special` (229,464), `End Turn` (100,714); Build screen `Add To Queue` (125,714); **Esc leaves the Build and Research screens** - the bottom-left button on the Research screen is `Cancel research`, NOT back, and clicking it pops `Confirm Cancel`. Move mode: send `move X Y` FIRST to read the target's name / Range / ETA off the map, THEN `click`, because that readout is what tells you whether the route is a node route (2-5 turns) or a sublight crawl (37-86 turns). **Chained clicks in one `cmd.txt` DO work for repeated clicks on the SAME button** (`Add To Queue`) but drop about half at `sleep 250` and about 1 in 6 at `sleep 500` - count the result, never assume. End Turn takes 30-45 s under `hooks=trace` with ~25 ships. A mid-turn AI diplomacy popup can steal a scripted click sequence - screenshot after any batch that seems to have done nothing. **VM RESTORED and verified**: quit to the main menu (screenshot), `SavedGames` back to the ORIGINAL 8-file set with the three autosaves restored BYTE-IDENTICAL (sha256 prefixes 0630daaa / e0bf3485 / 1f0dc162), so `ref-turn2` is still (400,436) and `zuul-turn5` still (400,465); lane O's staging dir `C:\SOTS\o-stage` removed; `shim.cfg` still `hooks=trace`, build `recap-7584bad-20260908T0615Z` untouched. Lane O's 7 saves live in the REPO ONLY - push one back with `scp verify/results/saves/.sav re@192.168.10.139:C:/SOTS/SavedGames/` when a compare needs it, and remember that adding a file shifts the Load dialog rows (`MyGame*` sorts BEFORE `MyGameverify1*` **VM141 = FREE (lane AD released 2026-09-08; guest restored, SavedGames back to AC's 13-file set byte-identical PLUS `ad-turn27-two-raiders.sav`, so Load-dialog rows have MOVED AGAIN - screenshot the dialog).** Lane AD left `C:\SOTS\shimdist-ad` + `C:\SOTS\ui\ad-*.ps1`; `click_helper.ps1` unmodified. **GOTCHA: startup on this guest is ~4.5 min through THREE intro movies** (fg + `key {ESC}` x3 via the click helper, then ~90 s), and loading a turn-27 save is a further 3-4 min - budget **~9 min per run** before the first End Turn. **GOTCHA: the first End Turn click after leaving Move mode is swallowed exiting the mode**; the turn starts on the second click. **VM144 = FREE (lane AS released 2026-09-09**; restored to L3's build + `shim.cfg` + the three pre-AS autosaves byte-identical, main menu verified by screenshot). **SavedGames is now 15 FILES** - AS added `MyGameas1predeploy` (400,347) and `MyGameas1spydep` (400,376), so **ROW POSITIONS HAVE MOVED AGAIN**; screenshot the dialog. AS left `C:\SOTS\shimdist-as` + `ui\as{go,hash}.ps1` + `ui\preAS-*`; `click_helper.ps1` untouched. **VM141 = FREE (lane AR released 2026-09-09**; restored, `shim.cfg.hoff`, this lane's `binkw32.dll`, SavedGames = AD's set PLUS `ar-turn37-816raiders.sav`; with autosaves deleted the list is 12 rows from y=260 at 29 px pitch, `ar-turn37-816raiders` is **row 3 at (400,318)**). Left `C:\SOTS\shimdist-ar` + `ui\ar-*.ps1`; `click_helper.ps1` unmodified. **GOTCHA, CORRECTS THE STARTUP FIGURE: `fg` + `key {ESC}` x3 spaced ~2.5 s then ~90 s puts the main menu up in about TWO minutes, not 4.5** - the 4.5 figure is what you get by waiting the movies out. Loading a turn-37 save is a further 3-4 min and that part is NOT compressible. **GOTCHA: `rdrag x y x y` (a zero-length right-drag) IS A RIGHT-CLICK** - the fleet panel's `Add To New Fleet`/`Split Ships` menu is right-click-only and the helper has no right-click verb. **GOTCHA: a trade-sector node is a legal move target EVEN WHEN ITS CUBE IS NOT DRAWN** - in Move mode the hover readout names it; fit `screen = (A*wx + B*wz + C, D*wx + E*wz + F)` from two known points in the save and you land on any node first try. **GOTCHA: the encounter query BLOCKS End Turn** - sample the `Done` pixel at (233,673): ~(183,18,0) when a query is up, ~(8,8,8) on the map; `Auto Resolve Peacefully` (667,641), `Done` (233,673). **VM144 = FREE (lane AP released 2026-09-09**; restored to L3's build + L3's `shim.cfg` + the three pre-AS autosaves BYTE-IDENTICAL, main menu screenshot-verified). SavedGames **STILL 15 FILES** and AS's row positions **STILL HOLD** - `MyGameas1predeploy` (400,347), `MyGameas1spydep` (400,376), OK (682,624), verified twice. AP added `C:\SOTS\shimdist-ap`, `ui\ap{go,hash}.ps1`, `ui\ap\` - **an END-TURN DRIVER WORTH REUSING**: `ap_loop.ps1` + the `SOTSAP` task clicks End Turn, **polls the autosave mtime**, snapshots `(Autosave).sav` per turn and stops on the first stall with a screenshot - **11 turns unattended in 11 minutes**. **GOTCHA THAT COST NINE MINUTES: the click task takes ~6 s end to end, so a screenshot taken 4 s after `schtasks /Run` shows the state BEFORE the click** - four such shots read exactly like an 87 px coordinate offset and **nearly became a false gotcha in this very row**. WAIT >= 9 s. **GOTCHA: the first click after `fg` is swallowed by focus activation.** **GOTCHA: the main-menu pixel test must sample the BUTTON (470,530) = (184,0,0), not the white label at (511,536).** TIP: `wheel` zoom separates stars that share a hit test at default zoom (Kepler vs Midway, which cost lane AS a run) - no affine fit needed. An End Turn on this state costs **6 SECONDS** under `probes=8`. **VM146 = FREE (lane BP released 2026-09-09 restored** and hash-verified: pre-BP autosaves byte-identical, `shim.cfg` `hooks=off` and `binkw32.dll` restored to the pre-BP files, game process **not running, exactly as found**). `ad-turn27-two-raiders.sav` deliberately left on disk, so **Load-dialog rows have MOVED**: autosaves deleted gives 7 rows from y=260 at 29 px pitch, `ad-turn27-two-raiders` **row 1 at (400,260)**. Left `C:\SOTS\shimdist-bp` + `ui\bp{deploy,shot,hash}.ps1` + `ui\preBP\`. **GOTCHA THAT COST FOUR WASTED CLICKS: the lab wall's `/shot/.png` serves a CACHED frame and can be a whole poll cycle (~5 s) BEHIND your last action.** Clicking a Load row then shooting 5 s later shows it un-selected; a second click shows it selected - which reads exactly like a swallowed click and is not. **Wait ~12-16 s, or take two shots and use the second.** Loading the turn-27 save is **3.5-4 min** and is NOT compressible; End Turn on it ~35 s with the light hook set. | | Zuul double-roll (behavioural) | verify | verified | high | 100% | 2026-09-08 | **CLOSED by lane V 2026-09-08.** No species-5 save existed, so lane V MADE one on VM140: a custom game with only Zuul left in the Available Species pool (both players Zuul), 4 End Turns under `shim.cfg.recapb3`. **8 calls, 8 compared, 0 diverged, exit 0.** All four researching calls have `species=5` and the generator advances by TWO, not one: `left` 540->538 / 522->520 / 504->502 / 485->483, `next_index` +2 each, `mt` hash unchanged; `ours` reproduced every post-state bit-for-bit. Save `verify/results/saves/zuul-turn5-species5.sav` (48559ab5b719b332, 59,131 B, turn 5, homeworld Gallandro) and on the VM as `C:\SOTS\SavedGames\zuul-turn5.sav`. NOT yet exercised by it: a Zuul tech COMPLETION (node 144 was at ~4,700 of 5,000 after 5 turns - one more End Turn gets there), so `SetResearched` and the Zuul boarding-pod grant are still untouched. Report `findings/subsystems/eventlive-verification.md` §5 **EXTENDED by lane U 2026-09-08: the double roll and the COMPLETION PATH together.** `zuul-turn5` + 10 more End Turns (turn 5 -> 15) under the same config: **20 calls, 20 compared, 0 diverged, exit 0**, with TWO Zuul completions. Call 2 (turn 7, alloc {144,1376}) advances the generator by two (`left` 449->447, `next_index` 175->177) AND runs the unlock cascade in the same call - node 144 -> state 4 / order 21 / turn_researched 7, nodes 132 and 136 -> state 2 with cost_rp 10000 / 16000 and turn_available 7, `next_id` 10->12, `observed_techs` 396->440. Note the ZUUL tree unlocks only 132 and 136 from tech 144 where the HUMAN tree also unlocks 142, so this is an independent instance of the cascade, not a repeat. Trace `verify/traces/unlock-b3-zuul.jsonl.gz` | | budget tail coverage (expenses/aid/debt) | verify | backlog | — | 0% | 2026-09-08 | 8 ComputeBudget slots were always 0 in ref-turn2 (no sliders, no aid, no debt, no handicap). Need a save with expense sliders, a debtor and a research-aid treaty to exercise ExpenseTotal + the aid/bonus tail . CONFIRMED AND WORSE 2026-09-08 (lane R, 4284 calls): **13 of 22 slots are 0 on every call** - tradeIncome, shipCarriedPop, secondaryManager, bonusIncome, systemIncomeNeg, debtInterest, construction, expenses, researchMoneyGiven, savingsGiven, tra, researchPointsGiven, trp | **LANE O 2026-09-08.** **PARTIALLY unblocked, and three of the remaining slots are now shown to be UNREACHABLE through the 1.8 UI.** Delivered: `[11] construction` and a large `[8] maintenance` are live in `zuul-turn17-orders2.sav` (build queue of 20 DEs) and `zuul-turn23-fleet23.sav` (27 DEs in service, `Maint` 14,500 vs 3,000/1,000 in every earlier save); the empire research slider is off default (`ResRate` 0.97 vs 0.25) in all five Zuul saves. NOT delivered, with reasons. **(a) `[12] expenses`**: `Nexp` is `[]` for EVERY player in ALL 11 saves across turns 1-23, human and AI alike, and no screen in the 1.8 UI (map panel, Build, Research, Ranking, Intel Analysis, Empire Population Manager, in-game menu) exposes a per-category expense slider - the only economic sliders are the empire Savings/Research split and the per-system `SRs/SRt/SRsc/SRtf/SRi/SRoh` rates, which land in `Sys`, not `Nexp`. Working hypothesis: slot 12 is dead in 1.8. **(b) `[10] debtInterest` / `[7] systemIncomeNeg`**: a debtor cannot be manufactured quickly - a ship's savings cost is deducted at QUEUE time so the queue cannot be over-committed, and `Maint` counts only ships IN SERVICE, which arrive at construction-budget/ship-cost per turn (~4.5 DE/turn here). With income floored at 78,570 (Planetary Budget pushed fully to Construction; measured 124,055 -> 78,570 on the panel) that is ~157 destroyers, i.e. ~35 more End Turns. Reachable, just not cheap. **(c) `[13]/[14]/[19]` aid**: a treaty offer DOES occur (the AI offered a non-aggression pact on turn 3 of the Human game and it was accepted - `human-turn3-noderoute.sav`), but the accepted pact leaves the player's `aid` count 0, and SotS 1.8 has no player-initiated treaty UI - proposals are AI-driven events. Research aid needs a longer game and luck, not a click path | hook GetDifficultyMods | meta | backlog | — | 0% | 2026-09-08 | B1 derived the two difficulty rows from trace values (AI maintenance divisor 3, research x1.5) instead of snapshotting them; hook it properly so they stop being constants | @@ -237,7 +237,7 @@ Status flow: `backlog → in-progress → mapped → verified` (or `blocked`). | standalone data root | engine | verified | high | 100% | 2026-09-08 | The standalone grew `--data DIR` / $SOTS_DATA_DIR. NO GAME DATA EMBEDDED; absence costs exactly the six census counters and SAYS SO. Note for integrators: with a data root set, `game_data_realdata` fails - and it FAILS IDENTICALLY ON UNMODIFIED MAIN, because the extracted sots.gob is a different data version than the counts that test pins. Not a regression; worth pinning the data version in that test | | THE STRATEGIC AI IS A StrategyClient, not part of ProcessTurn | control-flow | verified | high | 90% | 2026-09-08 | Lane AI1, instruction-verified. One extra Game::StrategyClient (0x708 B) is built PER AI PLAYER at SNMRunAI (net msg 0x3d) by StrategyApp::RunAI 0x008706f0, carrying a Game::StrategyAIAgent at StrategyClient+0x12c, and it receives the same SE* events the human client does. THE PER-TURN TRIGGER IS SEResumePlaying, client event 0x26, raised by StrategyServer::ResumePlaying 0x007ddc90 to every player with Status==0; it lands in StrategyClient::OnResumePlaying 0x00777480 which does `if (this->+0x12c) agent->vt[1](0x26, ev)` - and THAT CALL RUNS THE AI'S WHOLE TURN SYNCHRONOUSLY ON THE MAIN THREAD. Order: ProcessTurn -> OnAllCombatDone_Tail -> "(Autosave EndTurn)" [the save is written HERE] -> SNMResumePlaying -> each AI client's ~30 phases -> cl_EndTurn -> human End Turn -> SNMUpdate -> ApplyTurnCommands (ModCount++ per command) -> ProcessTurn. EXACTLY THE SHAPE LANE A2 MEASURED. CORRECTION to turn-spine.md 2.3: RaiseAIPrepareTurn/SEAIPrepareTurn (type 9) is CONSTRUCTION, not a turn hook - two callers, RunAI and CreateGame, both one-shot | | how an AI decision becomes a command | control-flow | verified | high | 95% | 2026-09-08 | THERE IS NO SEPARATE AI ORDER CHANNEL - the join to everything we already own. StrategyClient+0x160 is an ACCUMULATING TurnCommands; +0x4d8 is the send buffer. ~21 client order methods in 0x00762ca0..0x00763f60 each do `lea ecx,[this+0x160]` and call a TurnCommands::Add* helper. At End Turn, StrategyClient::BuildTurnCommands 0x00783780 does Clear(+0x4d8); +0x4d8 = +0x160; playerId; SetResearchRate - WHICH INCIDENTALLY EXPLAINS why every save's block has the research-rate gate set and the other five clear. SendEndTurn copies +0x4d8 into SNMEndTurn. **11 of the 21 order methods are called directly from the AI module; 7 of those are also UI-reachable.** So ModCount is downstream of StrategyClient+0x160, and RUNG B REDUCES TO FILLING THAT OBJECT THE WAY THE AI DOES | -| AI determinism: every client seeded with ZERO | verify | mapped | high | 85% | 2026-09-08 | All AI randomness uses a PER-CLIENT Mars::RNG at StrategyClient+0x134 (operator_new(0x9cc) + RNG_Seed(ctorArg) in the client ctor); all six direct NextInt sites in the AI band reach it, as do the two facade helpers cl_Chance and cl_RandRange. **ZERO AI CALLS TO THE STRATEGIC GENERATOR** - which closes lane Z's zero residual FROM THE OTHER SIDE, and neither lane has to weaken anything. The seed comes from NextUInt() on a static g_GlobalRNG at 0x00af6e58 that is **NEVER SEEDED** (its only static init writes the IStreamable vftable; none of the six RNG_Seed call sites targets it), and AN ALL-ZERO MT STATE IS A FIXED POINT OF THE TWIST - so every AI client is seeded with 0. GOOD: constant and reproducible. BAD: **the AI's generator state is in NO SAVE** - the StrategyAIAgent::Streamable block's 35 tags carry none. UNVERIFIED LIVE: P1/P2 routed to the VM holder | +| AI determinism: every client seeded with ZERO | verify | mapped | high | 85% | 2026-09-08 | All AI randomness uses a PER-CLIENT Mars::RNG at StrategyClient+0x134 (operator_new(0x9cc) + RNG_Seed(ctorArg) in the client ctor); all six direct NextInt sites in the AI band reach it, as do the two facade helpers cl_Chance and cl_RandRange. **ZERO AI CALLS TO THE STRATEGIC GENERATOR** - which closes lane Z's zero residual FROM THE OTHER SIDE, and neither lane has to weaken anything. The seed comes from NextUInt() on a static g_GlobalRNG at 0x00af6e58 that is **NEVER SEEDED** (its only static init writes the IStreamable vftable; none of the six RNG_Seed call sites targets it), and AN ALL-ZERO MT STATE IS A FIXED POINT OF THE TWIST - so every AI client is seeded with 0. GOOD: constant and reproducible. BAD: **the AI's generator state is in NO SAVE** - the StrategyAIAgent::Streamable block's 35 tags carry none. UNVERIFIED LIVE: P1/P2 routed to the VM holder **SCOPE, MEASURED 2026-09-09 (lane BP)**: on the rich turn `ad-turn27` **pinning does NOT collapse two processes to one.** It collapses **94 leaves to 35**, and the 35 are a single **fleet-id transposition** with every decision leaf identical. **Pinning explains the AI's CHOICES on a rich turn; it does not explain the ORDER in which two new fleets are matched to their ids.** | | P2: the test that decides whether Rung B is stated correctly | verify | in-progress | — | 0% | 2026-09-08 | ROUTED TO LANE H (holds VM140). Load one save twice IN TWO FRESH PROCESSES, End Turn in each, diff the autosaves. Predicted byte-identical incl. ModCount. IF IT HOLDS, Rung B stays as written (implement the AI, the byte-match is reachable from a save alone). **IF IT FAILS, Rung B is UNREACHABLE FROM A SAVE ALONE** and must be restated as "reproduces a recorded command stream", making --turn-commands injection the primary path instead of writing an AI. A roadmap-level answer for ten minutes of VM time, and it must be TWO SEPARATE PROCESS LAUNCHES to catch per-process seeding - distinct from the oracle restoration, which re-runs the same path | | CORRECTION: fuzzy::IAIRule is the TACTICAL AI | subsystems | verified | high | 100% | 2026-09-08 | A framing error the roadmap and several briefs carried: Game::fuzzy::IAIRule is the tactical/COMBAT AI - its only four implementors are TacAISquadRule_*. The STRATEGIC AI is 34 IAITask goals + 29 IAIRelationScale/Shift diplomacy terms + AIRulesDB (mostly SHIP DESIGN, and AIRulesDB::SelectRules_T is the ship-design selector, NOT the task selection loop). Only the strategic half is on the Rung-B path | | AI module boundary: lane AI1 read ~3% | meta | verified | high | 100% | 2026-09-08 | The module is **1,629 functions / ~360 KB** (dense core 0x680000-0x6d1000) and lane AI1 read the spine, maybe 3%, and says so. NOT DONE: the 34 IAITask bodies - NOT ONE - and IAITask's 8 pure virtuals are unnamed; THE TASK SELECTION LOOP IS UNFOUND; 19 of the 30 AI Process-Turn phases are unclassified addresses; the order-method -> list mapping (mechanical, one pass); and g_CurrentClientIndex 0x00ae4808 is uninstrumented - who sets it and in what ORDER AI clients are stepped matters, BECAUSE ModCount IS A COUNT. ONE HOP IS INFERRED NOT READ: StrategyAIContext::Broadcast 0x006b3840 delivering to the queued callback that reaches OnAIPacket - the identification is safe (one caller, registered by one function, jump table indexed by exactly the codes that function emits) but a next lane should finish it. ALSO UNSETTLED: AIProcessMinTime + Game::AIProcessingDialog hint at a think-time throttle; no deferral seen in OnAIPacket but no per-frame driver was looked for, and if an AI turn CAN span frames the clean "all AI orders in before the human's End Turn" ordering needs revisiting | @@ -344,7 +344,7 @@ Status flow: `backlog → in-progress → mapped → verified` (or `blocked`). | LAB HAZARDS from L2 | meta | verified | high | 100% | 2026-09-08 | (1) **`pgrep -f "tap141i0"` matches QEMU's OWN command line** (`ifname=tap141i0`), so stopping a tcpdump SIGTERMed the VM. Use `pgrep -x tcpdump`. The guest came back clean (fsutil not dirty, same IP). (2) Driving the starmap needs **REAL MOUSE MOTION** - SetCursorPos alone does not move the game's cursor - plus wheel zoom; L2's driver is in verify/harness/l2-ui/. (3) From lane L4: **`move X Y` then `click X Y`** - a bare click is delivered at the PREVIOUS cursor position about half the time, which reads as "the click did nothing" then "the next click did the previous thing" | | FOURTH cross-lane commit sweep - now with plain git commit | meta | verified | high | 100% | 2026-09-08 | Lane L1's board commit swallowed all 22 of lane L2's staged files. Content intact and verified in HEAD; L2 correctly did NOT rewrite history with other lanes live. **This is the fourth instance of the hazard addresses.d/README documents, and it now happens with plain `git commit` too, not just addresses.json** - because a lane stages by path but another lane's commit picks up whatever is in the index. The index is shared per-repo; staging by path does not protect against someone else's commit. Needs a real fix, not another rule | | STRUCTURAL FIX for the commit-sweep hazard (rule 25) | meta | verified | high | 100% | 2026-09-08 | Four instances in one session, the last with plain `git commit` and NO `-A` anywhere - **every lane had staged by path exactly as instructed and it still happened**, because `git add ` puts a file in THE REPO'S ONE INDEX and the next commit takes all of it. Staging by path was never the protection. **THE FIX IS A FORM OF THE COMMAND**: `git commit -m "msg" -- path/one path/two` commits only those paths and leaves the rest of the index untouched - verified in a scratch repo (two files staged, pathspec commit took one, the other stayed staged). Lanes now commit with a pathspec, always. Deeper fix if it recurs: a per-lane CLONE of sots-re, mirroring the per-lane worktree of sots-engine - which is precisely why sots-engine has never had this problem | -| RESOLUTION: the byte-match SURVIVES, in a stronger form | meta | verified | high | 100% | 2026-09-08 | First re-resolver run, `findings/resolutions/2026-09-08-ai-seed-per-process.md`. **The AI is not non-deterministic - it is MT19937 from ONE 32-bit word per client, drawn once at construction from a per-process global, and we own that generator bit for bit.** So game/ai is a deterministic function of (save, per-client seed). Rung C splits: **C-exact** - capture the three seeds and our block matches element for element and the autosave byte-matches ON EVERY RUN (the user's framing made literal), precondition being a per-client DRAW LEDGER so we consume the stream in the original's order and count INCLUDING draws whose result never reaches the save; **C-set** - the fallback for references whose seeds were never logged, like turn2-state.sav, which **no process including the original can reproduce**. Instrument exonerated: three unrelated 32-bit words per process are successive generator outputs, not clock reads, so AI1's call-site reading STANDS and only its "never seeded" reading falls **The shape recurs and is now a PREDICATE (resolver 2026-09-09)**: pick turns (`ResTNm == ''` at turn start: `turn1-state`, `as-turn15`) and heavy build turns (`ad-turn27`) **EXPOSE** the seed; quiet-consumer turns (`ar-turn37 -> 39`) do not, and agree byte-for-byte across three processes. **SCOPE of the C-exact proof: it was demonstrated on `turn1-state` (early game) only** - the rich-turn test is `ad-turn27` with `aiseed=pin`, queued on VM146. A control that agrees means "no exposed decision was reached", never "the AI is deterministic here". | +| RESOLUTION: the byte-match SURVIVES, in a stronger form | meta | verified | high | 100% | 2026-09-08 | First re-resolver run, `findings/resolutions/2026-09-08-ai-seed-per-process.md`. **The AI is not non-deterministic - it is MT19937 from ONE 32-bit word per client, drawn once at construction from a per-process global, and we own that generator bit for bit.** So game/ai is a deterministic function of (save, per-client seed). Rung C splits: **C-exact** - capture the three seeds and our block matches element for element and the autosave byte-matches ON EVERY RUN (the user's framing made literal), precondition being a per-client DRAW LEDGER so we consume the stream in the original's order and count INCLUDING draws whose result never reaches the save; **C-set** - the fallback for references whose seeds were never logged, like turn2-state.sav, which **no process including the original can reproduce**. Instrument exonerated: three unrelated 32-bit words per process are successive generator outputs, not clock reads, so AI1's call-site reading STANDS and only its "never seeded" reading falls **The shape recurs and is now a PREDICATE (resolver 2026-09-09)**: pick turns (`ResTNm == ''` at turn start: `turn1-state`, `as-turn15`) and heavy build turns (`ad-turn27`) **EXPOSE** the seed; quiet-consumer turns (`ar-turn37 -> 39`) do not, and agree byte-for-byte across three processes. **SCOPE of the C-exact proof: it was demonstrated on `turn1-state` (early game) only** - the rich-turn test is `ad-turn27` with `aiseed=pin`, queued on VM146. A control that agrees means "no exposed decision was reached", never "the AI is deterministic here". **C-EXACT FALSIFIED ON A RICH TURN 2026-09-09 (lane BP)**: with all three AI client seeds pinned, two fresh processes on `ad-turn27` still differed. **Restated: given (save, per-client seeds) the AI's command block is determined UP TO the assignment of newly allocated fleet ids to newly formed fleets** - exactly three words of the 81-element block, and the id counter itself is deterministic. **Rung C is DAMAGED, NOT LOST**: the assignment is canonicalisable the way C-set canonicalises a pick. | | the tie-set claim: rescued as a STATEMENT, open as a RESULT | verify | mapped | high | 80% | 2026-09-08 | And **not on "equal cost"** - the resolver's sharpest correction. The 2000-RP tier-1s are not the picks; the picks are tier-2s at 13k-30k. S(D) is **the support of a random draw**, per save, not a set of equal-score candidates. `BIO_GnMod` (2 of 6 observations) proves a SECOND ARM whose producer is unidentified - likeliest producer A or B gated by a draw, never entering the walk - so **"we can name all k" is TO PROVE**. The four-part statement is in the resolution 3.1: derive S before any sweep; canonical pick = lowest tech id; containment; **and a pinned-seed sweep produces nothing outside S** - part (iv) is what makes it non-tautological | | THE ONE PROBE: pinned seeds THROUGH the resolver | verify | backlog | — | 0% | 2026-09-08 | The seed SOURCE is not the probe. The resolver probe is only an experiment **if the seed is pinned** - otherwise two runs with two unknown seeds cannot separate "draws from the stream" from "reads something else per-process". Implementation named: L1's `l1seed` Seed detour (return address 0x0078304b, three constants) + L4's instrument + a `left` region over client+0x134 at TryResearchCandidate 0x006c8580; ref-turn2 once as the pin's OWN control; turn1-state twice; then sweep by quit-to-menu/reload (the pin table advances per load, **no relaunch needed**), logging which producer returned non-null for player 512. Predictions committed in the resolution 4. If the four leaves still differ, extend the pin to the unidentified fourth seeded object | | NEW OPEN RISK: g_GlobalRNG has four other consumers | verify | backlog | — | 0% | 2026-09-08 | Including **RunCombatRound+0x60f** - and **no battle turn has ever been compared across two processes**. Add "twice, two processes" to the battle workload in backlog 5. Lane L2 verified combat lockstep between two clients in ONE game; this is the different question of whether a battle is reproducible across separate runs | @@ -412,7 +412,7 @@ Status flow: `backlog → in-progress → mapped → verified` (or `blocked`). | CORRECTION: `CreateRaidEncounter` success is not 0-or-1 words | control-flow | verified | high | 100% | 2026-09-09 | `tail-rng-ledger.md` 11.1's **"a success costs 0 or 1 further word"** is right about the **corpus** and wrong about the **code**. A success with a **non-empty** candidate list reaches **five** further draw sites through `0x00892640` (`0x0088f1eb`, `0x0088f356`, `0x0083b460`, `0x00848fd9`, `0x00820c1b`), each a `NextInt` with its own rejection loop. Lane AG 5.1. The firing-indexed table could not hold these because no corpus state has ever had a non-empty list | | `Mars::RNG::Chance` modelled, with the zero-word cases and the NaN word | engine | verified | high | 100% | 2026-09-09 | Acting on lane AG's warning. Body read in full at `0x008e6dd0` (94 bytes, via the ReVa HTTP fallback - the MCP link was down): **`p <= 0` returns false and `p >= 1` returns true, each WITHOUT a draw**; in between exactly one `NextFloat` compared with a **strict `<`**. Now `MT19937::chance()` in the engine with tests pinning all four cases, the accumulating 0.2f-per-turn pattern (4 words then nothing for ever), and the strict comparison. **The NaN case is reproduced, not smoothed**: both early-outs are pairs of ordered comparisons that a NaN makes false, so a NaN probability falls through, **draws a word**, and returns false - hiding that would hide a real divergence. Engine `989c692`; `docs/mars-rng.md` updated. Rationale: a model without the early-outs is one word out of step from the fifth turn of every counter-mission onward, permanently, and one word of drift is the whole determinism claim | | The trade-raid roll is PER FLEET: 2 raiders on one sector = 2 entries, 2 words; B returns without drawing | control-flow | verified | high | 100% | 2026-09-08 | Lane AD, VM141, `findings/control-flow/raid-gate-multiplicity.md`. Two single-cruiser fleets parked on sector 6 (TradeID 832) from ac-turn22-raider.sav: `TradeManager::Slot13RngCalleeA` entered **2x** per End Turn on BOTH measured turns against lane AC's 1, `OnAllCombatDone_Tail` costs **2** words against AC's 1, and the return-address ledger prices the site exactly - 0x00820e18 calls=2 words=2, bracket residual 0. H-sector (one roll per sector) is DEAD; the cost is `sum over qualifying fleets`. `vslot13` is still entered ONCE per turn, so the multiplicity is the caller's loop over StrategyServer+0x64, exactly as AC read it. Instrument `probes=8` (the byte-neutral set - NOT 11), shim `ad-0c6d0a3-20260909T0159Z`, cfg shim.cfg.hp8 unmodified, own build dir /srv/re-lab/build/sots-engine-ad rm -rf'd first. CONSTANTS READ FROM THE IMAGE (rule 23, all WIDENED FLOATS): ODDS_CR 0x009e20d8 = (double)0.2f, ODDS_DE 0x009e5060 = (double)0.05f, PER_SHIP 0x009e31c0 = (double)0.01f, and a per-ship scale 0x009f8d48 = 0.33f that REDUCES the odds when ship+0x18 & 0x100000. One-cruiser fleet odds = 0.21000000275671482; six rolls across AC+AD gave two successes (expectation 1.26) - consistent, not validated. `Slot13RngCalleeB` entered 1x on turn 28 and cost **0 words** by return-address attribution - and lane AG's reading that RNG_NextInt has NO zero-bound early-out turns that into a PROOF that B returned before its target pick, i.e. the candidate list was empty (G_B4), reproduced with a fleet AC never had. trdmgr holds 3 routes, trfr 18/14/14, sourced in sectors 816 and 768 - NONE in 832 | -| NO ORACLE PAIR for the turn-27 raider state - the control varies, but ONLY in the AI's decisions, and /Sim/RNG is bit-identical across three processes | meta | verified | high | 100% | 2026-09-08 | Lane AD (rule 26). Two `hooks=off` runs of one End Turn from ad-turn27-two-raiders.sav in two fresh processes gave DIFFERENT post-turn autosaves (a634510e / 676b1778); the pre-turn autosave is byte-identical in all three processes (16f63adb), so the load path is deterministic and the divergence is inside the turn. So NO rung-A calibration pair came out of this state and none is proposed. BUT the state-checksum localises the variation completely: 94 leaves, ALL of them Player[32 "The Eternal Empire"] designs/ShipRecs/odes/build queues/fleet ids and positions, plus ModCount, NumFlts, the three id lists and Summary.Checksum. `/Sim/RNG` = 1e458a997f51bbdf and `/Sim/trdmgr` = 89643da6fabb386b in ALL THREE post-turn saves (two hooks=off and one probes=8), and NO trade-sector, trdmgr or player-0 fleet leaf appears in either diff. Two consequences: (a) every number lane AD reports is read off a sub-tree the control reproduces bit-for-bit; (b) RULE 19, on this state: the probes=8 + draw_sites + boundary-ledger instrument is behaviour-neutral on every leaf OUTSIDE the AI-decision sub-tree, including the serialised strategic generator. The named next step is a RUNG B pair - `--turn-commands` replay runs no AI, so a captured command stream should make this state reproducible where Rung A cannot **EXPLAINED IN PART 2026-09-09 (resolver)**: the variation is the AI's **build / fleet-assignment / colony** layer and the **id-allocation ORDER** downstream of it - **NOT** the design content (the two bodies are byte-identical apart from `DesID`) and **NOT** a research pick (`ResTNm` held). **Consumer unidentified**; the pin probe on this state (VM146) decides whether it is the seed at all. | +| NO ORACLE PAIR for the turn-27 raider state - the control varies, but ONLY in the AI's decisions, and /Sim/RNG is bit-identical across three processes | meta | verified | high | 100% | 2026-09-08 | Lane AD (rule 26). Two `hooks=off` runs of one End Turn from ad-turn27-two-raiders.sav in two fresh processes gave DIFFERENT post-turn autosaves (a634510e / 676b1778); the pre-turn autosave is byte-identical in all three processes (16f63adb), so the load path is deterministic and the divergence is inside the turn. So NO rung-A calibration pair came out of this state and none is proposed. BUT the state-checksum localises the variation completely: 94 leaves, ALL of them Player[32 "The Eternal Empire"] designs/ShipRecs/odes/build queues/fleet ids and positions, plus ModCount, NumFlts, the three id lists and Summary.Checksum. `/Sim/RNG` = 1e458a997f51bbdf and `/Sim/trdmgr` = 89643da6fabb386b in ALL THREE post-turn saves (two hooks=off and one probes=8), and NO trade-sector, trdmgr or player-0 fleet leaf appears in either diff. Two consequences: (a) every number lane AD reports is read off a sub-tree the control reproduces bit-for-bit; (b) RULE 19, on this state: the probes=8 + draw_sites + boundary-ledger instrument is behaviour-neutral on every leaf OUTSIDE the AI-decision sub-tree, including the serialised strategic generator. The named next step is a RUNG B pair - `--turn-commands` replay runs no AI, so a captured command stream should make this state reproducible where Rung A cannot **EXPLAINED IN PART 2026-09-09 (resolver)**: the variation is the AI's **build / fleet-assignment / colony** layer and the **id-allocation ORDER** downstream of it - **NOT** the design content (the two bodies are byte-identical apart from `DesID`) and **NOT** a research pick (`ResTNm` held). **Consumer unidentified**; the pin probe on this state (VM146) decides whether it is the seed at all. **PARTLY EXPLAINED 2026-09-09 (lane BP, VM146)**: with the three AI client seeds **pinned**, **59 of AD's 94 leaves go away** - `ModCount`, `NumFlts`, `DesignIDs[]`, `FleetIDs[]`, both build queues, every design and ship record and all four turnstats rows become identical. The **35 that remain** are fleets 1970 and 1986 **exchanging contents**. So AD's attribution to the per-process AI seed is **right for the substance** of the variation and **incomplete**: a second, non-seed per-process input orders the fleet-id assignment. | | Sector 816 is UNREACHABLE from lane AC's empire - the AG probe needs a 27 ly hull, and one is already buildable | control-flow | verified | high | 100% | 2026-09-08 | Lane AD, measured from ad-turn27-two-raiders.sav geometry + the Build screen. Node distances from the human's only two colonies (Epsilon Eridani idx 0, Downbelow idx 12) against a 9.0 ly cruiser range: sector 816 = 15.66 / 12.12, sector 768 = 11.53 / 10.14, sector 752 = 12.85 / 8.43, sector 800 = 9.37 / 11.77, sector 784 = 9.36 / 3.49, sector 832 = 4.99 / 4.56. The only two sectors IN range (832, 784) source no route; the only two that source routes (816 via trfr 18, 768 via trfr 14 x2) are out of range. With Trade View on, only sector 6's cube renders at any zoom, so no other sector node is even a clickable move target. THE FIX IS ALREADY UNLOCKED: the stock `Extended Range` DESTROYER has Range 27.0, construction cost 2,252, node speed 4.0 - ~1-2 turns to build, ~4 turns to sector 816's node, so AG's probe is ~6 turns of play and needs no research. TWO WARNINGS: a destroyer takes the nDE odds branch, 0.05f + 0.01f = 0.06 (0.12 if the AI Freighters fleet in 816 satisfies the doubling), i.e. a 1-in-8 success per fleet per turn - send several fleets or measure many turns; and sector 816's tsct = 2 is the AI's bit, not player 0's, so no FtFlg & 0x800 is required there, exactly as on 832 **REACHED 2026-09-09 (lane AR) exactly as costed**: five stock Extended Range destroyers (Range 27.0, 2,252 each) built at Epsilon Eridani over turns 28-31, split into four fleets, ETA 4 turns, parked on sector 5's node turn 35. **AD's geometry table and its build costing are exact.** Two things AD's recipe did not say: a new ship **JOINS an existing fleet** and a fleet's Range is the **MINIMUM over its ships**, so the 9-ly cruiser must be split off before the node is a legal target; and the sector node is clickable even though its cube is not drawn. | | the route set MOVES between turns - AG's 816 prediction needs restating for AD's state | control-flow | verified | med | 100% | 2026-09-09 | Not a contradiction between the lanes, but it would read like one. Lane AG read **four** routes out of `ac-turn22-raider.sav` with `trfr` 18/16/14/20 (sectors 816/816/768/752); lane AD read **three** out of `ad-turn27-two-raiders.sav` with `trfr` 18/14/14 **[AD'S READ WAS WRONG - lane AR re-read the same save and found FIVE, `trfr` 18/16/14/20/14. So sector 816 had TWO candidate routes at turn 27, not one, and my inference below that it had one was built on AD's bad number. The route set does still move (four at turn 22, five at turn 27), so the standing instruction to re-read `trfr` on the state you build survives - but the arithmetic I used to justify it did not.]**. Different saves, five turns apart - **the AI's route set is not static.** Consequence for the sector-816 probe: on AC's turn-22 route set 816 had **two** candidates (`NextInt(bound=1)`, mask 1, no rejection possible); on AD's turn-27 set it has **one** (`NextInt(bound=0)`, mask 0, accepted on the first draw). **Both still cost exactly one word**, so AG's committed prediction of 1 word on a failing turn and 3 on a succeeding one survives - but whoever runs the probe must re-read `trfr` on the state they actually build rather than inheriting either number | | THE SPY DETECTION ROLL FIRES - 1 word, gated on an asteroid belt; `spies2` and `deat` closed | verify | verified | high | 100% | 2026-09-09 | **Lane AS, VM144, build `as-c172c99-20260909T0205Z`, `probes=8` (never 11).** `Mars::RNG::Chance` at **0x00887c8a**, in the BODY of `ServerSpyManager::vslot13` (tail phase 23 call 9), fired **1 call / 1 word / no_draw_calls=0 / strategic=true** at `ret_rva 0x00887c8f`; `OnAllCombatDone_Tail` went **363 -> 364**, against 0 on every turn any lane has ever measured. Turn total 19, per-site sum 19, **residual 0**. THE ENTRY PROBE ON `SpyManager::Slot13RngCallee 0x008408e0` READ **0 ON THE SAME TURN** - that zero was never a statement about the subtree, only about which function the call sits in (rule 28 practice 4, demonstrated rather than argued). **CORRECTION: the roll is NOT inline** (the resolution and AC §2.2 both say so) - it is a plain `E8` to the `Chance` entry point in the caller. **THE PREDICATE**, decoded to save fields: `spy.deat != 0 && spy.sdet == -1 && sys(deat).ARes2 > 0 && (sys(deat).TerrFl & 1)`; **corpus count 0 of 22, failed conjunct `deat != 0`**. `ServerSystem_BeltUsableFlags 0x00743f80` (16 bytes) returns `TerrFl` with bit 0 cleared when `ARes2 <= 0`; `ARes2>0` and `TerrFl&1` agree on all 616 corpus system records AND on the new map, which also has `TerrFl = 2` and `3`, so bit 0 is the belt specifically. **POLARITY: belt gone => NO DRAW AND THE SPY IS DETECTED ANYWAY** (`je` past the roll to `sdet := turn`) - a zero here is two different results. Cost: `sdo` starts at 0, `SpyCraft_AccumulateDetectionOdds 0x0081f570` ACCUMULATES `0.7*(0.01 + [cA]0.01 + [cB]0.02 + 0.001*cC) * species(1.0/0.75/0.5)` and CLAMPS AT 1.0, so `Chance` costs 1 word until sdo reaches 1 and 0 thereafter. Measured `sdo` 0 -> 0.0084 -> 0.0168 (= 0.7*0.012 exactly, a value no run had produced). **`spies2` IS CLOSED**: `ServerSystem::AddSpy 0x007514c0` sets `spy.deat = system handle` and pushes `spy.sid` into `ServerSystem+0x1cc` in the same two instructions - `spies2` is the per-system DEPLOYED-spy id vector; L3's "not the spy list" was half right, AC's P4 was right. Measured: `count=1 element=1` at Kepler, 0 in the other 27. **NEW DRAW SITE NOBODY HAS**: `0x0078c97f` NextFloat in the SHIPACTION_DEPLOYSPY handler `0x0078c930` (`cbh = NextFloat()*2pi`), strategic generator, in the ProcessTurn bracket - decoded, NOT measured, and its predicate is on the COMMAND STREAM not the save, a column the gate-indexed audit lacks. Oracle: input byte-identical in 3 processes; A vs B differ in **22 leaves, all inside one AI player's research pick** (the known per-process client seed, resolution 2026-09-08) - so this is NOT a calibration pair and must not enter `determinism-hashes.txt`. `probes=8` run came out byte-identical to control A. Saves: `as-turn15-spydeployed.sav`, `as-turn14-predeploy.sav`; findings `findings/subsystems/spy-detection-roll.md` **EXTENDED 2026-09-09 by lane AP**: the roll fired on seven consecutive turns and **SUCCEEDED on Frame 22**; `sdo` is exactly `0.0084*(f-14)` as float32 over **NINE** points and **FREEZES the moment `sdet` stamps**. **AS's probability table is one turn late - corrected to 43%/70%/87%** at +10/+15/+20. AS's `0x0078c97f` is now **MEASURED** (1 word, `ProcessTurn` bracket) and `cbh` reproduced **bit-identically across processes and builds**. | @@ -427,3 +427,6 @@ Status flow: `backlog → in-progress → mapped → verified` (or `blocked`). | THE SPY ATTRIBUTION ROLL FIRES - and P's "three draws" are TWO SPECIES BRANCHES, only one reachable per game | verify | verified | high | 100% | 2026-09-09 | **Lane AP, VM144, build `ap-989c692-20260909T0425Z`, `probes=8` (never 11), own build dir /srv/re-lab/build/sots-engine-ap rm -rf'd first.** Continues lane AS from `as-turn15-spydeployed.sav`: **11 End Turns, 6 SECONDS EACH** (the brief budgeted an evening; the cost is the launch and the load, not the turns). **DETECTION AT FRAME 22, `P` RAN AT FRAME 25.** `Mars::RNG::Chance` at **0x00840a3c** fired **1 call / 1 word / no_draw_calls=0 / strategic=true** at `ret_rva 0x00840a41` - a site that had never fired. **CORRECTION TO EVERY INVENTORY (row 207, lane AG 6, lane AS 6, and this lane's own brief): 0x00840929 / 0x008409c7 / 0x00840a3c ARE NOT THREE SITES BEHIND ONE GATE.** `SpyManager::Slot13RngCallee 0x008408e0` opens `cmp [spyOwner+0x5c], 6; jne` - `Species == 6` (MORRIGI) takes `Chance(0.75f)` at 0x00840929 and then `NextInt` at 0x008409c7 to blame a RANDOM THIRD EMPIRE (a false flag; the true owner is never named); every other species takes `Chance(0.25f)` at 0x00840a3c and on success returns the TRUE owner. **No call can reach more than one.** Species table decoded from the save's own ISsp list and cross-checked on lane V's Zuul save: **0 Human, 1 Hiver, 2 Tarkas, 3 Liir, 4 _NPC, 5 Zuul, 6 Morrigi** - which also NAMES lane AS's two unexplained multipliers in `AccumulateDetectionOdds` (0.75f = ZUUL, 0.5f = MORRIGI). Constants read as the four bytes (rule 23): 0x009e5ac0 = 0.25f, 0x009e5ac4 = 0.75f (the same word AS read as the Zuul multiplier). **0x008409c7 needs BOTH the 0.75f roll to succeed AND a third non-eliminated empire** that is neither the spy's owner nor the target's. **SECOND SITE CLOSED: 0x0088dc43** (the counter-mission roll, lane AG 3.2, corpus 0 of 22, never fired) fires 1 word/turn from **Frame 19**; the bootstrap turn (Frame == tdep+3) sets spyon/cm:=1/cmo:=0.2f and DRAWS NOTHING. `cm`, `cmo`, `spyon`, `ncp` - four of the value-domain census's 234 single-valued fields - are all OFF ZERO with a measured trajectory. **TWO CORRECTIONS TO AG 3.2:** the `CnTrd` the stage machine consults at cm 1->2/3 is the TARGET OWNER's not the spy owner's (my player has CnTrd=False and it still took 1->2, because Kepler's owner has CnTrd=True), and `ncp` is a SCALAR COUNTER of completed stages (0,0,1,1,2,2,3 in lock-step with cm), not a container. **THIRD SITE CLOSED: 0x0078c97f**, the deploy `NextFloat` in the SHIPACTION_DEPLOYSPY handler that is in NO inventory - 1 call / 1 word / strategic, in the **ProcessTurn** bracket, and **spy.cbh came out 5.033599376678467 BIT-IDENTICAL to lane AS's** from a different process, build and session. **Its predicate is on the COMMAND STREAM, and this is the first measured entry in a column the gate-indexed audit does not have.** THE POLARITY CASE, MEASURED: on Frames 23 and 24 (sdet+1, sdet+2) `vslot13` is ENTERED (probe = 1) and costs **ZERO** - a third arm of the same function, and `sdo` FREEZES at 0.0672 in the save, proving branch D stopped independently of the ledger. On Frame 25 the entry probe on `SpyManager::Slot13RngCallee 0x008408e0` reads **1** - ITS FIRST NON-ZERO EVER, the complement of lane AS's demonstration that its zero said nothing about the subtree. The spy is DESTROYED at the end of phase 23 on sdet+3 by manager vtable slot 10 `0x00838480` -> `ServerSystem::RemoveSpy`, so vslot14 costs 0 that turn: nspy 1->0, spies2 emptied. `sdo` measured on NINE points, exactly `0.0084*(f-14)` as float32 (`3c89a027 3cce703a 3d09a027 3d2c0831 3d4e703b 3d70d845 3d89a027`), which also corrects AS's detection-probability table by one turn: **43%/70%/87%** at +10/+15/+20, not 38/65/85. Residual **0 on all 12 measured turns**, overflow 0. **ONE PREDICTION WRONG AND NAMED: PB4** said the tail costs 2 words/turn from Frame 19; it costs 2,4,6,9,8,9,8,8 because the TRADE-RAID roll 0x00820e1d entered the tail at Frame 20 and grew 2,4,7,7,8,7,8 as the AI's navy went 157->251 ships - an independent live re-confirmation of lane AD's per-fleet model on a different map and an AI-owned fleet. The spy halves were predicted exactly; quoting a BRACKET total requires modelling every subsystem in it. Saves `ap-turn22-spydetected.sav`, `ap-turn25-spydestroyed.sav`, `ap-deploy-turn15.sav`; findings `findings/subsystems/spy-program-draws.md` | | CONTROL ON as-turn15: THREE OUTCOMES IN SIX PROCESSES, and exposure is SATURATED on this map | verify | verified | high | 100% | 2026-09-09 | Lane AP, VM144. Two fresh `hooks=off` processes (OA, OB) on `MyGameas1spydep.sav` + 1 End Turn, added to lane AS's A/B/M and this lane's `probes=8` run W. **Pre-turn `(Autosave EndTurn).sav` is byte-identical across four processes, two lanes and TWO DIFFERENT BUILDS** (`98e45d3745b91450...`). Post-turn: **three distinct outcomes in six processes** - `262f8bda` x4 (AS's A and M, AP's W and OB), `e34775a7` x1 (AS's B), `e296394f` x1 (AP's OA). **OA != OB, so this is NOT a calibration pair and must not enter `determinism-hashes.txt`** - stated in advance as PD3. OA vs OB diverge in **15 leaves, ALL of them Player[32]'s research pick plus /Summary/Checksum and one turnstats `tch` cell**; three distinct picks were sampled (Overthrusting completed / DRV_NodFoc in progress / DRV_McroFus over budget). **`probes=8` IS BYTE-NEUTRAL, PROVEN PROPERLY THIS TIME: W is IDENTICAL to OB over 44,438 leaves** (`state_checksum --floats bits --mask none`), while the two un-instrumented controls differ from each other - so the instrument agrees exactly with a control in a leaf class it provably does not touch. Sub-tree digests: `Sim/spymgr`, `Sim/systems`, `Sim/fleets`, `Sim/trdmgr` all IDENTICAL across the pair; only `Sim/players` and the derived `Summary` move. **RULE 26(c) EXPOSURE FIELDS RECORDED NEXT TO THE HASHES** (coordinator's request): the control input carries `Player[32].ResTNm == ''` - a pick turn - and it varied in exactly the leaves the predicate names. **AND THE PREDICATE IS SATURATED ON THIS MAP: all 13 pre-turn states from Frame 14 to Frame 26 carry at least one exposure signature** (9 of 13 a research pick, every single one a batch of ship completions, four a design, four a colonisation; Player[32]'s navy goes 146 -> 251 ships in twelve turns). **There is NO quiet turn anywhere in this lineage**, so no rung-A pair is obtainable from `as-turn15-spydeployed.sav` by waiting - the opposite end of the axis from lane AR's turns 38-39, and a lane that wants a pair on this map must pin the client seeds. Counter-nuance: exposure is necessary for variation, not sufficient per pair - W, A and M (three processes, two configurations, two builds) landed on the SAME bytes on this exposed turn, so a single agreeing pair on an exposed turn proves nothing. Saves `ap-oa-turn16.sav`, `ap-ob-turn16.sav` | | predict at the SITE, not at the bracket | meta | verified | med | 100% | 2026-09-09 | Lane AP's PB4, the one prediction it got wrong, and the lesson generalises. AP forecast every spy-half draw **exactly** and still missed its headline number, because it had predicted the whole `OnAllCombatDone_Tail` total and **an unrelated subsystem entered the bracket**: the trade-raid roll `0x00820e1d` arrived at Frame 20 and grew **2 -> 8 words** as the AI's navy went **157 -> 251 ships**. A bracket total is a sum over every subsystem inside it, so quoting one commits you to modelling all of them. **Predict and report per site, by return address; let the bracket total be the arithmetic check, not the claim.** The consolation: the surprise was itself a result - an independent live re-confirmation of lane AD's per-fleet raid model, on a different map and an AI-owned fleet. Now a corollary to rule 23 | +| PINNED SEEDS DO NOT COLLAPSE A RICH TURN - C-exact is false on ad-turn27, and the residue is ONE FLEET-ID TRANSPOSITION | control-flow | verified | high | 100% | 2026-09-09 | Lane BP, VM146, probe 2 of the 2026-09-09 resolution (`findings/control-flow/pinned-seed-rich-turn.md`). Build `bp-989c692-20260909T0505Z`, fresh CT111 tree (rule 24), lane worktree (rule 21). THREE fresh processes on `ad-turn27-two-raiders.sav` (1c8baa27...), one End Turn each, lane CB's CONSTRUCTOR-ARGUMENT overwrite (`aiseed=pin`, NOT `airng.pin_seed`), every template hook off BY NAME (27 reported `mode=off`), zero unknown keys, zero COVERAGE lines, `aiseed call=1/2/3 pinned=1` in both pinned runs. **THE BRIEF WAS WRONG ABOUT THE PLAYER COUNT: the save has seven non-human players and the engine builds THREE AI clients** - RunAI fires exactly 3x, net ids 32/496/512; 528 Alien Menace, 544 Peacekeeper Enforcer, 560 Von Neumann and 576 Independent Colony get no client, no call and no seed. Nine distinct natural seeds across three processes, none shared (L1's per-process seeding, reproduced a 4th time on a turn-27 state). RESULT: **run L (unpinned) and run A (pinned to L's observed seeds) are BYTE-IDENTICAL** (`e913ff41...`, state_checksum IDENTICAL, 61147 leaves, coverage PROVED) - a pinned process reproduced an unpinned run of a rich build turn. **Run B, same pinned seeds, fresh process, DIFFERS: `724528ff...`, 35 leaves.** So P1 IS FALSIFIED and READING 3 IS ALIVE: a per-process input that is NOT the seed reaches a rich turn. **BUT THE PIN DID ALMOST ALL OF THE WORK.** AD's UNPINNED pair on the same save differed in 94 leaves across designs, ShipRecs, Sav/Maint/PvSav/FNGNum, both AI build queues, ModCount (1496 vs 1500), NumFlts, DesignIDs[], FleetIDs[] and four turnstats rows. BP's PINNED pair differs in 35, and ALL 35 ARE ONE TRANSPOSITION: fleets **1970 "Sigma Fleet VI"** and **1986 "Tau Fleet VI"** exchange their entire contents (Pos, PrvPos, FtOrig, FPlan, LocID 384<->0, HFPlan, FtTrans, FtFlg 12<->13, Ship[6976]<->Ship[5264]), plus Sys[384]/Flt[1] 1970->1986, /Sim/ShipIDs[] list-reordered and /Summary/Checksum. **ModCount, NumFlts, DesignIDs[], FleetIDs[], every build queue, every design, every ship record and /Sim/RNG are IDENTICAL.** The AI command block says the same thing from the input side: 9 blocks / 81 elements, same (pid,list,idx) sequence, same per-list counts, and - masked by the noise set the byte-identical L/A pair PROVES (list 1 w1,5,8,11; list 3 w4-11; list 5 w8-11; list 7 w2-11; list 8 w1-11; list 10 w2-11; list 12 w5-11; list 14 w2-11; list 23 w2-11) - **EXACTLY THREE WORDS DIFFER**: list 8 idx1 word0 fleetId 1986/1970, list 10 idx0 word0 systemId 384/80, list 10 idx1 word0 systemId 80/384. List 10's fleet ids are in the SAME order in both (1970, 1986, 2002, 7072), so the id counter is deterministic and what varies is WHICH NEWLY FORMED SHIP GROUP EACH ID LANDS ON. Design elements identical including the name bytes ("Egg Thief Mk 4", "Bravestar Mk 3"). **C-EXACT RESTATED: given (save, per-client seeds) the AI's command block is determined UP TO the assignment of newly allocated fleet ids to newly formed fleets; one residual per-process input decides that assignment and it is not the seed.** Rung C is damaged, not lost - the assignment is canonicalisable the way L4's C-set canonicalises a pick. INFERENCE, NOT MEASUREMENT: the residue has the shape of a container walked in pointer order under ASLR - the reading the 09-08 resolution withdrew for the research pick and never re-tested on a build turn. RULE 26 (c): the A/B control does NOT agree with itself, so it certifies nothing; the L/A pair does, and certifies only (save, procedure, hashes) reached by two of three processes - a 1/k result, NOT an oracle pair. Exposure facts on the same line: Player[32] ResTNm=BIO_TerBac HELD, NumDes 46, NumOwn 15, 13 ships completing; 496/512 hold targets with NumOwn=0/NumDes=0; the four NPC factions have ResTNm=='' AND NumOwn==0 AND NO AI CLIENT. NEXT: one more pinned process (run C) sizes the residual outcome set; probe 3's per-client draw ledger says whether the fleet-creation sites draw at all. Artefacts: `verify/results/saves/bp-{pinA,pinB}-turn28.sav`, `bp-turn28-pre.sav`, `verify/results/shim/bp/` | +| rule 26(c) qualified on its first forward use: the signature is NECESSARY, not sufficient | meta | verified | high | 100% | 2026-09-09 | Lane BP, applying the corollary published hours earlier. **Four players on `ad-turn27` carry `ResTNm == ''` and contributed NONE of AD's 94 leaves** - they have `NumOwn == 0`, and more fundamentally **the engine constructs only THREE AI clients for the save's SEVEN non-human players** (`RunAI` fires exactly 3x, net ids 32/496/512; 528 Alien Menace, 544 Peacekeeper Enforcer, 560 Von Neumann and 576 Independent Colony get no client, no call and no seed). So an exposure signature must be read **against who actually runs**, not off the field alone. Corollary (c) amended in place. This is the good case of a rule being used the day it was written and coming back sharper | +| the cbpin trap fixed, and made unrepeatable | engine | verified | med | 100% | 2026-09-09 | Lane BP reported two defects in `src/shim/shim.cfg.cbpin` rather than working around them, and noted its own first draft **reproduced one of them** - the definition of a trap. (1) The header comment **wrapped onto a live setting**, leaving a stray `aiseed.values=...` with a trailing backtick AHEAD of the real one at the bottom, so the file contradicted its own "filled in per run" note. (2) **Six registered hooks went unnamed**, and under `hooks=trace` an unnamed hook defaults back **ON** - so a determinism probe ran six more detours than it claimed, which is exactly what rule 19 says can move the bytes. Both fixed; `cbpin` now names all 27. New `tools/check_shim_configs.py` enforces the two syntax defects across every template and exhaustiveness **only where a template opts in with `# exhaustive`**. **My first draft of that check was wrong and I threw it away**: it enforced exhaustiveness globally and "failed" 40 configs, because `hooks=trace` legitimately means "trace all but these" - the check was wrong, not the configs. Engine `1654785` | diff --git a/guides/method-rules.md b/guides/method-rules.md index aacd7a7..85930c9 100644 --- a/guides/method-rules.md +++ b/guides/method-rules.md @@ -388,7 +388,11 @@ mechanism. > existing fleets. The per-process seed was present in all three. It reaches the save only through a > decision whose outcome set is not a singleton given the state, and whether such a decision is > reached is a **predicate on the pre-turn save** — rule 28's shape: `ResTNm == ''` names a pick -> turn; `NumDes` moving names a design turn; a batch of completions names a fleet-assignment turn; +> turn **for a player that has an AI client and something to decide with** (lane BP, 2026-09-09: +> four players on `ad-turn27` carry `ResTNm == ''` and contributed nothing, because they have +> `NumOwn == 0` and the engine builds **three** AI clients for seven non-human players — the +> signature is *necessary, not sufficient*, and a save-field predicate still has to be read +> against who actually runs); `NumDes` moving names a design turn; a batch of completions names a fleet-assignment turn; > one consumer (`0x0069dbb0`) has a 3–40-turn fuse and cannot show inside two turns of a load. > So: record the pair as (save, procedure, hashes) and write the exposure facts next to the hashes; > never extend a certified pair by a turn without its own two-process agreement (`ar-oracle-A-post`