70 KiB
The spy program's remaining draws — P's branch split, the counter-mission roll, and the deploy's own NextFloat
- Type: subsystem (static decode + live measurement)
- Owner / date: lane AP · 2026-09-09 · guest VM144 (
sots-re-win10-144,re@192.168.10.144) - Instrument: lane Z's
draw_sitesreturn-address ledger atprobes=8(the byte-neutral set; neverprobes=11— lane H bisected 11 as not byte-neutral) - Continues: lane AS's
findings/subsystems/spy-detection-roll.md, whose §6 named this workload from its own failed conjunct; lane AG'sfindings/control-flow/gate-indexed-rng-audit.md§3.2 - Inputs:
verify/results/saves/as-turn15-spydeployed.sav(target 1),verify/results/saves/as-turn14-predeploy.sav(target 2)
(Sections 0–3 were written and committed before the build and before any measurement, in their own commit. Sections 4 onward are the measurement. Rule 2.)
0. Headline of the predictions
The brief that created this lane asks for "
P's three draws". There are three sites but they are two mutually exclusive branches, and the branch selector is the spy owner's species. Fromas-turn15-spydeployed.savexactly one of the three can ever fire —0x00840a3c— because the spy's owner is player 0 (re),Species = 0(Human).0x00840929and0x008409c7are gated onspyOwner->Species == 6, i.e. Morrigi, and are unreachable from this save, from every save in the 22-save corpus, and from any workload that does not start a Morrigi empire.Predicted, before the run:
0x00840a3cfires once, for one word, on the End Turn that producesFrame == sdet + 3; and two sites nobody expected fire on the way — the spy counter-mission roll0x0088dc43(lane AG §3.2, corpus 0 of 22, never fired) starting at Frame 19, and the deploy's ownNextFloat0x0078c97fon a separate one-turn run.
1. P decoded — SpyManager::Slot13RngCallee 0x008408e0, complete
Program sots / "Sword of the Stars.exe", ImageBase 0x00400000, all addresses VAs. Body
disassembled from its start to the next function start (rule 17): 0x008408e0 .. 0x00840a5a,
followed by int3 padding. The ReVa MCP server is down (CONNECTION_CLOSED) for this lane too, so
this is instruction stream only.
1.1 How P is reached, and that there is no further gate
The chain in ServerSpyManager::vslot13 0x008877b0 from lane AS's §1, extended past the point AS
stopped at. Between the last gate and the call to P there is no branch at all — the whole span
0x00887897 .. 0x00887af2 is two EVENT_SPY_DESTROYED_OWNER-shaped string builds, a push_back,
and an event post:
0088781d je 0x887e92 G1 spy.deat (+0x10) == 0 -> next spy
00887856 je 0x887c5b G2 spy.sdet (+0x40) == -1 -> BRANCH D (the detection roll)
00887867 jl 0x887e92 G3 (server.Frame - sdet) < 3 -> next spy
0088787a je 0x887c4f G4 ServerSystem_GetOwner(sysA) == NULL -> sdet := -1, next spy
00887891 je 0x887c4f G5 ServerSystem_GetOwner(sysA) == ownerO -> sdet := -1, next spy
008878a1 call 0x59f1a0 push_back spy.sid into a LOCAL vector<int> at [ebp-0xdc]
008879dd call 0x8862b0 PostEvent "EVENT_SPY_DESTROYED_OWNER"
00887ae3 call 0x7437e0 eax = ServerSystem_GetOwner(sysA)
00887aee push eax ; arg2 = the TARGET system's owner
00887aef push edx ; arg1 = ownerO, the SPY's owner ([ebp-0xc0])
00887af0 mov ecx,edi ; this = the spy manager
00887af2 call 0x8408e0 P <-- the false-flag draw
00887af7 ...
00887b07 cmp eax,ebx
00887b09 je 0x887b5b ; P returned NULL -> the "unknown empire" event string
so once sdet is stamped and three turns have passed, P is called unconditionally (given a
target system that is owned and not owned by the spy's own empire). P's arguments are two
Game::ServerPlayer*, not ids — P compares them by pointer against entries of the player vector.
1.2 The species branch — and why "three draws" is one draw
00840909 mov esi,[ebp+8] ; esi = arg1 = the SPY'S OWNER (a ServerPlayer*)
0084090c cmp dword ptr [esi+0x5c], 0x6 ; +0x5c = ServerPlayer::Species
00840910 jne 0x840a29 ; NOT species 6 -> BRANCH B
; ---- BRANCH A, species == 6 only --------------------------------------------------------------
00840919 fld dword ptr ds:0x9e5ac4 ; 0.75f (bytes 00 00 40 3f)
0084091f mov ecx,[eax+0x16c] ; THE STRATEGIC GENERATOR (the object, not +4)
00840929 call 0x8e6dd0 A1 ; Mars::RNG::Chance(strategic, 0.75f) ret 0x0084092e
00840930 je 0x840a45 ; roll failed -> return NULL
; build a candidate list over StrategyServer +0x54..+0x58 (the player vector):
; skip p if p->+0xfb != 0, p->+0xf8 != 0, p == arg1, or p == arg2
008409ac je 0x840a07 ; list EMPTY -> free it, return NULL (no NextInt)
008409ae eax = count - 1
008409b0 mov ecx,[...+0x16c]; add ecx,4 ; the +4 SUB-OBJECT (the NextInt convention)
008409c7 call 0x4271c0 A2 ; Mars::RNG::NextInt(&(count-1)) ret 0x008409cc
008409cf esi = list[eax] ; the blamed empire
return esi
; ---- BRANCH B, every other species ------------------------------------------------------------
00840a2c fld dword ptr ds:0x9e5ac0 ; 0.25f (bytes 00 00 80 3e)
00840a32 mov ecx,[edx+0x16c] ; the strategic generator
00840a3c call 0x8e6dd0 B1 ; Mars::RNG::Chance(strategic, 0.25f) ret 0x00840a41
00840a43 jne 0x8409f2 ; roll SUCCEEDED -> return esi == arg1 (the true owner)
00840a45 xor eax,eax ; else return NULL
Both float constants were read as the four bytes in the image (rule 23): 0x009e5ac0 is
00 00 80 3e = 0.25f and 0x009e5ac4 is 00 00 40 3f = 0.75f. 0x009e5ac4 is the same word
lane AS read as the species-5 multiplier in SpyCraft_AccumulateDetectionOdds — one constant, two
uses.
So P is the attribution roll for a destroyed spy, and it has two completely different shapes:
| spy owner | draw sites reached | semantics |
|---|---|---|
Species == 6 |
0x00840929 (Chance 0.75f), then 0x008409c7 (NextInt) only if the roll succeeds and the candidate list is non-empty |
75 % of the time a random third empire is blamed — a false flag. The true owner is never returned. |
| every other species | 0x00840a3c (Chance 0.25f) |
25 % of the time the true owner is returned; otherwise nobody is blamed. |
1.3 The species table, decoded from the corpus
Game::ServerPlayer +0x5c is the field objects/layouts.md names Species (Write
0x008563e0). The sim/species node lists seven ISsp names in index order, identical in every
save read:
| 0 | 1 | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|---|
| Human | Hiver | Tarkas | Liir | _NPC | Zuul | Morrigi |
Cross-checked against lane V's zuul-turn5-species5.sav, whose three real players all carry
Species = 5 — and lane V's hook independently reported species=5 on the Zuul double-roll. The
four NPC empires (Alien Menace, Peacekeeper Enforcer, Von Neumann, Independent Colony) carry
Species = 4 in every save.
That also names lane AS's two unexplained multipliers: SpyCraft_AccumulateDetectionOdds
multiplies the per-turn detection odds by 0.75f for Zuul and 0.5f for Morrigi, and by
1.0f for everyone else. Morrigi are the stealth species on both sites — half the detection odds,
and a false flag when they are caught.
1.4 What happens to the spy on that turn
The local vector<int> filled at 0x008878a1 is drained after the spy loop:
00887eb0..00887ee8 for each collected sid, back to front:
[[manager]+0x28](sid) ; vtable slot 10 = 0x00838480
and 0x00838480 looks the SpyCraft up by sid, calls ServerSystem::RemoveSpy 0x0074f550 on
the deat system (which clears spy.deat and erases sid from spies2), clears the tender's
atto back-pointer (spy+0xc, ship+0xa8), erases the craft from the manager's vector at +0x10,
and invokes the scalar deleting destructor. The spy is gone at the end of vslot13, which is
phase 23 — before vslot14 runs at phase 33.
1.5 The deploy's NextFloat — 0x0078c97f, re-read
SHIPACTION_DEPLOYSPY handler 0x0078c930, body to the next function start 0x0078c9e0. One gate,
then the draw, with no branch between them:
0078c93d..0078c964 build a 12-byte stack query object (vtable 0x00a09648) and ask it about the ship
0078c969 test al,al
0078c96b jne 0x78c975 ; FALSE -> return 0 with NO DRAW
0078c975 mov ecx,[esi+0x16c]; add ecx,4 ; the strategic generator's +4 sub-object
0078c97f call 0x47d830 ; Mars::RNG::NextFloat ret 0x0078c984 <-- THE DRAW
0078c984 fld qword [0x009e21b0] ; 6.2831854820251465 == 2*pi
0078c98a fldz ; LO = 0.0
angle = LO + (HI - LO) * r ; the float-range idiom, stored through a float32 temp
0078c9ac call 0x0080c860 ; (ship, angle) -> eax
0078c9bb call edx ; [[esi+0x15c]+0x1c] = ServerSpyManager::DeploySpy slot 7
Lane AS read this as cbh = NextFloat() * 2pi; that is right in value (LO is literally fldz) and
the expanded form is worth recording because it is the campaign's standard lerp(LO, HI, r) shape,
not a bare multiply. The draw is unconditional once the validator passes, so it is exactly one
strategic word per applied Deploy Spy order, in the ProcessTurn bracket
(ApplyAllTurnCommands), not the tail's.
Its predicate is on the command stream, not on save fields: "a SHIPACTION_DEPLOYSPY command in
this turn's command stream". Lane AG's gate-indexed audit has two kinds of cell — a measured firing,
or a predicate over the save — and this is neither.
1.6 A complete RNG scan of all four bodies
Every call/jmp in each body decoded to a real instruction boundary and matched against the seven
RNG entry points (NextFloat 0x0047d830, NextInt 0x004271c0, Chance 0x008e6dd0,
Twist 0x00426e00, Seed 0x0049fdf0, 0x005876c0, 0x0057da00):
| body | range | RNG sites |
|---|---|---|
ServerSpyManager::vslot13 |
0x008877b0 .. 0x00887f30 |
0x00887c8a only |
SpyManager::Slot13RngCallee (P) |
0x008408e0 .. 0x00840a5a |
0x00840929, 0x008409c7, 0x00840a3c |
SHIPACTION_DEPLOYSPY handler |
0x0078c930 .. 0x0078c9e0 |
0x0078c97f only |
ServerSpyManager::vslot14 |
0x0088db80 .. 0x0088dd2c |
0x0088dc43 only |
No inlined draw is present in any of them (rule 16's tempering immediates do not appear).
2. The corpus and the state, counted before building (rule 28 practice 5)
as-turn15-spydeployed.sav, read with verify/save-reader/save_reader.py:
| field | value | consequence |
|---|---|---|
sim/Frame |
15 | |
spy sid |
1 | |
spy sown |
16 | player 0, re |
re's Species |
0 (Human) | P takes branch B; 0x00840929 and 0x008409c7 are unreachable |
spy deat |
400 (Kepler) | G1 satisfied — lane AS's contribution |
spy sdet |
−1 | the failed conjunct; G2 sends us to branch D every turn |
spy sdo |
0.00839999970048666 | |
spy tdep |
15 | vslot14's Frame − tdep ≥ 3 first holds at Frame 18 |
spy cm, cmo, spyon, ncp |
all 0 | vslot14 G4 fails today; it bootstraps itself at Frame 18 |
spy cbh |
5.033599376678467 | = 2π × 0.8011207…, i.e. 0x0078c97f already fired once, unmeasured |
| players | 7 — re(0), Revenge Fleet(0), Spengler(0), 4 × _NPC(4) |
branch A's candidate list would be non-empty, if it were reachable |
Corpus count for P, on the predicate ∃ spy with deat != 0 && sdet != -1 && Frame − sdet ≥ 3:
0 of 22 before this lane, and 0 of 23 including as-turn15-spydeployed.sav. The failed conjunct is
sdet == -1, exactly as lane AS handed it over — and it is not a conjunct any amount of clicking can
satisfy directly. It is satisfied by waiting, at a probability the code fixes.
Corpus count for 0x0078c97f: not expressible over save fields at all (§1.5). The nearest
save-side witness is cbh != 0, which holds in 1 of 23 saves — as-turn15-spydeployed.sav,
because the site fired on the turn that produced it.
No save needs to be manufactured for either target. Lane AS built both states. This lane generates no map and plays no new game.
3. Predictions
Committed before the build, before the deploy of any binary to VM144, and before any End Turn.
3.1 The static claims
| prediction | how it is wrong | |
|---|---|---|
| PA1 | P reaches at most one draw site per call, selected by spyOwner->Species (+0x5c) == 6. From this save, only 0x00840a3c. |
a draw_sites row at ret_rva 0x0044092e or 0x004409cc on any turn of this workload falsifies it outright |
| PA2 | Species indices are 0 Human, 1 Hiver, 2 Tarkas, 3 Liir, 4 _NPC, 5 Zuul, 6 Morrigi; therefore AccumulateDetectionOdds's 0.75f is Zuul and its 0.5f is Morrigi |
a save whose sim/species list is ordered differently, or a Zuul save with Species != 5 |
| PA3 | 0x00840a3c is Chance(0.25f) — a widened float, not the decimal 0.25 read from a decompiler |
the four bytes at 0x009e5ac0 are not 00 00 80 3e |
| PA4 | the spy is destroyed at the end of vslot13 on the turn P runs: nspy 1 → 0, spies2 at Kepler 1 → 0, and no spy record survives in the autosave |
the post-P autosave still carries a spy record |
3.2 Target 1 — the wait, and the per-turn word trajectory
Let T be the Frame on which sdet is stamped. sdo grows by 0.0084 per turn and the roll on
the End Turn producing Frame f uses sdo = 0.0084 × (f − 14), because AccumulateDetectionOdds
runs before the gate. Chance costs one word for 0 < p < 1 (board row 367).
| prediction | |
|---|---|
| PB1 | sdo in the autosave after the End Turn producing Frame f is exactly 0.0084 × (f − 14) as a float32, for every f until detection: 0.0168, 0.0252, 0.0336, 0.0420, 0.0504, … |
| PB2 | OnAllCombatDone_Tail costs 1 word on Frames 16, 17, 18 (vslot13's detection roll alone) |
| PB3 | 0x0088dc43 fires for the first time in this campaign on Frame 19, one call, one word, ret_rva 0x0048dc48, strategic = true — because vslot14 bootstraps its counter-mission at Frame 18 (Frame − tdep = 3, ncp empty), setting spyon, cm := 1, cmo := 0.2f, without drawing on Frame 18 itself |
| PB4 | from Frame 19 the tail costs 2 words per turn (vslot13 1 + vslot14 1), and drops to 1 on any turn where cmo has accumulated to ≥ 1.0 or where cm has just cycled back to 0 |
| PB5 | save-side: cm moves off its corpus-constant 0 on Frame 18, spyon moves to the Kepler owner's id on Frame 18, and cmo starts stepping by 0.2f per failed counter-mission roll from Frame 19 |
| PB6 | on the detection turn T: vslot13 still costs 1 word (the roll that succeeds), sdet := T |
| PB7 | on T+1 and T+2: vslot13 costs ZERO words — sdet != -1 skips branch D, and Frame − sdet < 3 skips the destruction path. A zero at 0x00487c8f on those two turns is a positive result about a third arm and must not be read as "the roll did not fire" (rule 28 practice 6) |
| PB8 | on T+3: one row at ret_rva 0x00440a41, entry = Chance, calls = 1, words = 1, no_draw_calls = 0, strategic = true; vslot13's own 0x00487c8f row absent; vslot14 contributes 0 because the spy was destroyed at the end of phase 23 |
| PB9 | the entry probe on Game::SpyManager::Slot13RngCallee 0x008408e0 reads 1 on Frame T+3 — its first non-zero in the campaign, and the complement of lane AS's demonstration that its zero said nothing about the subtree |
| PB10 | cumulative detection probability: 11 % by +4 turns, 43 % by +10, 70 % by +15, 87 % by +20 — computed from p_k = 0.0084(k+1). (Lane AS's brief quoted 38/65/85; that is the same model evaluated one turn late, using p_k = 0.0084k. Minor, and stated so the difference is on the record before the run.) |
| PB11 | the tail bracket total over the whole run equals the per-site sum: residual 0 on every turn |
PB12 — the tail case, stated in advance. At +20 turns there is a 13 % chance sdet is still −1.
If that happens, the result reported is the sdo trajectory against PB1 plus the 0x0088dc43
series, and P stays a decoded-not-measured site. That is a real measurement of the accumulator and
it will be reported as such rather than ground out.
3.3 Target 2 — the deploy order
From as-turn14-predeploy.sav, one Deploy Spy order, one End Turn, probes=8:
| prediction | |
|---|---|
| PC1 | one draw_sites row at ret_rva 0x0038c984, entry = NextFloat, calls = 1, words = 1, no_draw_calls = 0, strategic = true |
| PC2 | it lands in the ProcessTurn bracket, not OnAllCombatDone_Tail's, because ApplyAllTurnCommands runs first |
| PC3 | the same turn's OnAllCombatDone_Tail costs exactly 1 — the first detection roll at sdo = 0.0084 |
| PC4 | the resulting spy.cbh reproduces lane AS's value exactly: 5.033599376678467, i.e. 2π × 0.80112…. The strategic generator is seeded from the save and the per-process AI client seed does not feed it, so the same input plus the same order must draw the same word. This is the sharpest single prediction in the document: it says the deploy draw is reproducible across processes, sessions and lanes |
| PC5 | spies2 at Kepler goes 0 → [1], deat 0 → 400, tdep := 15, sdo := 0 then 0.0084 — reproducing lane AS's §4.2 from a different process |
3.4 The control
| prediction | |
|---|---|
| PD1 | two fresh hooks=off processes on the same input will produce a byte-identical pre-turn (Autosave EndTurn).sav and differing post-turn autosaves |
| PD2 | the differences localise to one AI player's research pick plus Summary/Checksum and the turnstats tch cell — the per-process StrategyClient seed of 2026-09-08-ai-seed-per-process.md. Nothing of my empire, the spy, deat, sdet, sdo, cm, cmo or spies2 moves |
| PD3 | therefore this is not a calibration pair and must not enter determinism-hashes.txt — stated in advance so it cannot be a post-hoc excuse (rule 26) |
| PD4 | the leaves this lane's numbers depend on — the spymgr sub-tree and the systems sub-tree — do reproduce across the control pair, and that is the claim that will be checked by sub-tree rather than by whole-file hash |
3.5 How the whole model could be wrong
countCis not stable.sdo's per-turn increment is0.7 × (0.01 + 0.001 × countC)withcountC = 2inferred, not observed. If a fleet moves in or out of Kepler the increment changes and PB1 breaks — and that would be the most interesting failure available, because it would identifycountCfrom the delta. Symptom: ansdostep that is not 0.0084.vslot14might not bootstrap. AG'sncpcheck is on a container this lane has only seen as the integer 0. Ifncpis a count and non-empty means something else, PB3 fires late or not at all. Symptom: Frame 19 tail costs 1, not 2, andcmstays 0.- The stage machine consults
owner->+0xff(CnTrd, FTL Economics) at 1 → 2/3. This game has no FTL Economics, so the 1 → 3 arm is the one that runs; that changes which stage sequence is exercised but not any word count. - PB7's two zero turns are the polarity trap in a new place.
vslot13will be entered onT+1/T+2(the probe reads 1) and cost 0. If this lane reported that as "the roll stopped working", it would repeat exactly the mistake rule 28 exists for. - PC4 can fail benignly. If AI orders applied before mine consume strategic words,
cbhdiffers and the deploy draw is still 1 word. That falsifies reproducibility, not the site.
(End of the pre-registered section. Everything below is measurement.)
4. What was run
Shim build ap-989c692-20260909T0425Z, cross-built on CT111 in this lane's own directory
/srv/re-lab/build/sots-engine-ap after rm -rf of the whole tree, from a lane-private
git worktree at ~/sots-engine-ap with no local build* shipped (rules 21 and 24). binkw32.dll
sha256 ae084ed4774caf24ea7927fcb012d1999633f10556c7c99f0209f66067416a17. Staged to
/srv/re-lab/shim/dist-ap, deployed to C:\SOTS\shimdist-ap on VM144. This lane wrote no engine
code; shim.cfg.hp8 and shim.cfg.hoff were used unmodified.
| run | config | input | End Turns | purpose |
|---|---|---|---|---|
| W | shim.cfg.hp8 (probes=8) |
MyGameas1spydep.sav (Frame 15) |
11 (15 → 26) | target 1 |
| D | shim.cfg.hp8 |
MyGameas1predeploy.sav (Frame 14) + one Deploy Spy order |
1 | target 2 |
| OA / OB | shim.cfg.hoff |
MyGameas1spydep.sav |
1 each, two fresh processes | control (§7) |
Instrument armed and verified from shim.log, not assumed (rule 1). Both instrumented runs:
seven drawsite: detours create=MH_OK enable=MH_OK; config: probes=8 -> 8 lane-H entry probes
and all eight create=MH_OK enable=MH_OK; zero COVERAGE: lines; zero
config: ignoring unknown key lines; draw_site_overflow = 0 on every turn. Probe indices 8–11
report NOT INSTALLED, never 0. The one MH_ERROR_ALREADY_CREATED line is the fpu module
losing the race for BeginProcessTurn to the ledger; it is present verbatim in lanes AC, AD, AR and
AS's logs, so this is the same instrument those lanes ran.
probes=11 was not used anywhere in this lane. No number here is comparable to lane AC's turn
totals.
4.1 The wait cost eleven minutes, not an evening
The brief budgeted "~20 End Turns, expensive in wall-clock". On this state an End Turn under
probes=8 completes in 6 seconds; the eleven turns took 11 minutes including the load. The
expensive part is the launch (~2 min through the intro movies) and the load (~3 min). Turn 27 hit a
Von Neumann encounter dialog at Rigel and the driver stopped on it exactly as designed; by then the
question was answered and the run was ended there rather than clicked through.
The driver is worth carrying forward: a scheduled task (SOTSAP) running an in-guest loop that
clicks End Turn, then polls the autosave's mtime — never a button colour, which is lane AS's
lesson — snapshots (Autosave).sav per turn, and stops on the first stall with a screenshot. It is
left at C:\SOTS\ui\ap\ap_loop.ps1.
5. Target 1 — the wait, measured turn by turn
Detection landed on Frame 22, seven turns in. P therefore ran on Frame 25.
5.1 The per-turn ledger
Every row is draw_sites at the post-turn autosave marker, which the shim resets at the pre-turn
marker — so these are per-turn figures, not cumulative.
| Frame | 0x00887c8a spy 13 |
0x0088dc43 spy 14 |
0x00840a3c P B1 |
0x00820e1d trade raid |
tail bracket | probe vslot13 |
probe Slot13RngCallee |
|---|---|---|---|---|---|---|---|
| 16 | 1 | — | — | — | 1 | 1 | 0 |
| 17 | 1 | — | — | — | 1 | 1 | 0 |
| 18 | 1 | — | — | — | 1 | 1 | 0 |
| 19 | 1 | 1 ← first ever | — | — | 2 | 1 | 0 |
| 20 | 1 | 1 | — | 2 | 4 | 1 | 0 |
| 21 | 1 | 1 | — | 4 | 6 | 1 | 0 |
| 22 | 1 ← succeeds, sdet := 22 |
1 | — | 7 | 9 | 1 | 0 |
| 23 | — | 1 | — | 7 | 8 | 1 | 0 |
| 24 | — | 1 | — | 8 | 9 | 1 | 0 |
| 25 | — | — | 1 ← first ever | 7 | 8 | 1 | 1 ← first ever |
| 26 | — | — | — | 8 | 8 | 1 | 0 |
Every cell is calls = words, no_draw_calls = 0, strategic = true. The tail bracket equals the
sum of its rows on all eleven turns: residual 0 every turn, and draw_site_overflow = 0.
Three lines of that table are the lane:
Frame 25:
entry = Chance,ret_rva = 0x0044_0a41(VA0x00840a41, the instruction aftercall 0x8e6dd0at0x00840a3c),calls = 1,words = 1,no_draw_calls = 0,strategic = true.0x00840a3chad never fired in this campaign.Frames 23 and 24: no row at
0x0048_7c8fat all, while the entry probe onServerSpyManager::vslot13reads 1 on both. That is "entered and gated", not "did not run" — rule 28 practice 1, in a third distinct arm of the same function.Frame 25, the probe column:
Game::SpyManager::Slot13RngCallee 0x008408e0reads 1. Lane AS demonstrated that this probe's zero said nothing about the subtree. This is the other half: the same probe, on the one turn the callee is actually entered, reads one.
5.2 0x00840929 and 0x008409c7 did not fire, and could not have
Neither site appears in any of the eleven turns, including Frame 25 — the one turn P ran. PA1
held. The reason is not a state we failed to build: P's first instruction pair is
cmp [spyOwner+0x5c], 6 / jne, the spy's owner is player 16 re with Species = 0 (Human), and
the launch card confirms it in the UI. Branch A is a Morrigi-only path.
The brief that created this lane, lane AS's §6, board row 207 and lane AG's §6 all list
0x00840929/0x008409c7/0x00840a3cas three sites behind one gate. They are two mutually exclusive branches selected by species, and no workload can fire more than one of them per call. To reach the other two, a lane must play a Morrigi empire, deploy a spy, and lose it.
5.3 sdo — the accumulator, exact to the float32
Read from the eleven autosaves. Prediction PB1 was sdo(f) = 0.0084 × (f − 14) as a float32.
| Frame | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
|---|---|---|---|---|---|---|---|---|---|
sdo |
0.016800 | 0.025200 | 0.033600 | 0.042000 | 0.050400 | 0.058800 | 0.067200 | 0.067200 | 0.067200 |
| bits | 3c89a027 |
3cce703a |
3d09a027 |
3d2c0831 |
3d4e703b |
3d70d845 |
3d89a027 |
frozen | frozen |
Seven consecutive exact steps of 0.0084, then frozen the moment sdet stamps — because branch D
is the only writer of sdo and sdet != -1 skips it. The freeze is a second, save-side proof that
the branch stopped running on Frames 23 and 24, independent of the ledger's zero.
Lane AS measured two points of this line (0 → 0.0084 → 0.0168) and derived the rest. Nine points
now lie on it, and countC stayed at 2 for the whole window, so sdo's per-turn increment is a
constant on this state rather than a fitted one.
5.4 The counter-mission machine, measured for the first time
0x0088dc43 (lane AG §3.2) had corpus count 0 of 22 and had never fired. It fires here from
Frame 19, one call and one word per turn, and the save-side state machine is fully visible:
| Frame | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
|---|---|---|---|---|---|---|---|---|
spyon |
32 | 32 | 32 | 32 | 32 | 32 | 32 | — |
cm |
1 | 1 | 2 | 2 | 3 | 3 | 4 | — |
cmo |
0.2 | 0.4 | 0.2 | 0.4 | 0.2 | 0.4 | 0.2 | — |
ncp |
0 | 0 | 1 | 1 | 2 | 2 | 3 | — |
words at 0x0088dc43 |
0 | 1 | 1 | 1 | 1 | 1 | 1 | 0 |
Everything AG decoded statically is confirmed, and four things are added:
- The bootstrap turn costs nothing. Frame 18 is
Frame − tdep == 3;vslot14setsspyon, runs the stage machinecm: 0 → 1andcmo := 0.2f, and does not draw. The first draw is Frame 19. PB3 predicted exactly this and it held. cmosteps by0.2fon failure and resets to0.2fon success, as decoded. The stage advanced on Frames 20, 22 and 24 — always on the second try, atp = 0.4. Six rolls, three successes; expectation at (0.2, 0.4) alternating is 2.4. Consistent, not validated.ncpis a scalar counter of completed stages, not a container. It is a plainintitem in the stream and it increments by exactly 1 on each stage completion (0,0,1,1,2,2,3), in lock-step withcm. AG's decode reads thecm == 0branch as gated on "ncpnon-empty"; that gate was never reached here, becausecmnever returned to 0 before the spy was destroyed. It stays a rule-6 hypothesis — and a sharper one now thatncp's values are known.- The
CnTrdthe stage machine consults is the TARGET's, not the spy owner's. AG's caution to lane AS was that a spy program run without FTL Economics takes the1 → 3arm. This run took1 → 2, and in the save my player (16) hasCnTrd = Falsewhile Kepler's owner (32) hasCnTrd = True. Soowner->+0xffat that branch is the victim empire's FTL Economics. The caution as written would have mispredicted this run.
cm, cmo, spyon and ncp are four of the 234 fields the value-domain census lists as having
only ever held one value. All four are now off zero, with a measured trajectory each.
5.5 The spy is destroyed at sdet + 3, and vslot13 does it
nspy goes 1 → 0 between the Frame-24 and Frame-25 autosaves, spies2 at Kepler returns to
count = 0, and the spymgr record loses its only spy child. That is vslot13's drain loop at
0x00887eb0 calling manager vtable slot 10 0x00838480, which calls ServerSystem::RemoveSpy 0x0074f550. PA4 held.
It also settles the ordering question in §1.4 behaviourally: 0x0088dc43 costs 0 on Frame 25
although vslot14's entry probe still reads 1. Phase 23 destroys the spy before phase 33 walks the
list.
6. Target 2 — 0x0078c97f, the site in no inventory, measured
Run D: MyGameas1predeploy.sav (Frame 14) in a fresh process under probes=8; Kepler selected on
the map; Special → Deploy Spy → OK; one End Turn.
draw_sites, post-turn autosave marker, the row verbatim:
entry = NextFloat
ret_rva = 0x0038_c984 (VA 0x0078c984 -- after `call 0x47d830` at 0x0078c97f)
calls = 1
words = 1
no_draw_calls = 0
strategic = true
boundary ledger, same turn:
BeginProcessTurn 326 -> 326 0
ProcessTurn 326 -> 344 18 <-- the deploy draw is in here
OnAllCombatDone_Tail 344 -> 345 1 <-- the first detection roll, sdo = 0.0084
Autosave 345 -> 345 0
PC1, PC2 and PC3 all held exactly. The site fires once per applied SHIPACTION_DEPLOYSPY
command, for one word, on the strategic generator, inside the ProcessTurn bracket — and the same
turn's tail costs exactly 1, because ApplyAllTurnCommands runs before OnAllCombatDone_Tail and
the freshly deployed spy is immediately eligible for its first detection roll.
It appears in no other turn of this lane: eleven turns of run W carry no row at 0x0038c984.
That is the stream predicate showing its shape — the site's cost is not a function of the save at
all, it is a function of what the player ordered.
6.1 The sharpest prediction held: the deploy draw is reproducible across processes
PC4. Lane AS's as-turn15-spydeployed.sav was written by a different process, on a different
day, from a different build. This lane's Frame-15 autosave was written by run D.
lane AS spy.cbh = 5.033599376678467 (float32 0x40A11333)
lane AP spy.cbh = 5.033599376678467 identical
cbh / 2pi = 0.8011222000
The same input state plus the same order drew the same word, in two processes separated by hours and
by a rebuild. Together with deat = 400, tdep = 15, sdo = 0.0084, sdet = -1 and spies2 = [1]
at Kepler reproducing exactly (PC5), this says the strategic generator's consumption up to
ApplyAllTurnCommands is deterministic given the save — the per-process AI client seed does not
reach it. It is a much stronger statement than a whole-file hash comparison, because it isolates one
word to one site.
6.2 A correction to lane AS's reading of the same bytes
AS gives the site as cbh = NextFloat() * 2*pi. The value is right; the expression is the
campaign's standard range idiom with LO supplied by an fldz:
0078c984 fld qword [0x009e21b0] ; HI = 6.2831854820251465
0078c98a fldz ; LO = 0.0
0078c994 fsub st(1),st ; HI - LO
0078c99b fmulp st(1),st ; (HI - LO) * r
0078c99d fstp [ebp+8] ; through a FLOAT32 temp
0078c9a0 fadd [ebp+8] ; LO + that
so it is lerp(0.0, 2*pi, r) rounded to float32 mid-expression, not a double multiply. A
reimplementation that computes r * 6.2831854820251465 in double and stores at the end can differ in
the last bit.
The other half of the sequence is worth writing down because it reads wrong at first glance:
0078c998 push ecx ; reserve DeploySpy's SECOND argument slot
0078c9a9 fstp [esp] ; slot := the angle
0078c9ac call 0x0080c860 ; __thiscall getter, `mov eax,[ecx+0xa8]; ret` -- NO stack args,
; so it leaves the angle slot untouched. eax = the spy's id.
0078c9ba push eax ; the FIRST argument
0078c9bb call edx ; ServerSpyManager::DeploySpy(spyId, angle)
0x0080c860 looks like it consumes the float and does not; the compiler built the argument list out
of order and called a zero-argument getter across the half-built frame. DeploySpy takes two
arguments, and 0x008874b0 fld [ebp+0xc] / 0x008874c0 fstp [esi+0x18] is where cbh is
written. Lane AS's "spy->cbh (+0x18) := <the float argument>" is exactly right; this only names
which argument and shows why an eye following eax misses it.
7. The control — a truthful non-pair, and the exposure predicate checked against it
Two fresh hooks=off processes (OA, OB) on the same input, one End Turn each, plus this
lane's instrumented run W and lane AS's three runs from the day before. Six processes, one
input, one End Turn.
7.1 The hashes
input MyGameas1spydep.sav 854a10fa1ea602f0f7909f9bf9154942e56ea5fa1db4cce47d6d32bf53952e08
| run | lane | config | pre-turn (Autosave EndTurn).sav |
post-turn (Autosave).sav |
size |
|---|---|---|---|---|---|
| A | AS | hoff |
98e45d3745b91450… |
262f8bda97c9511d… |
77,685 |
| B | AS | hoff |
98e45d3745b91450… |
e34775a757e6fb3d… |
77,649 |
| M | AS | probes=8 |
98e45d3745b91450… |
262f8bda97c9511d… |
77,685 |
| W | AP | probes=8 |
— | 262f8bda97c9511d… |
77,685 |
| OA | AP | hoff |
98e45d3745b91450… |
e296394f578b3a56… |
77,677 |
| OB | AP | hoff |
(§7.4) | (§7.4) |
The pre-turn save is byte-identical across four processes, two lanes and two builds — including this lane's own, which is not lane AS's binary. That is the strongest statement available about the input, and it is what makes the post-turn comparison meaningful.
The post-turn saves are not. Five processes have produced three distinct outcomes:
262f8bda (three times: AS's A, AS's M, this lane's W), e34775a7 (once: AS's B), e296394f
(once: this lane's OA).
This is not a calibration pair and must not enter
determinism-hashes.txt. Said in advance as PD3, and it is the honest verdict rather than a post-hoc excuse.
7.2 What varies, localised by sub-tree
state_checksum --no-audit --floats bits --mask none, OA against W:
DIVERGED: 15 leaf difference(s)
/Summary/Checksum 985948923 -> 985948837
/Sim/turnstats/history/hist[1]/stats[15]/tch 45 -> 46
/Sim/players/Player[32 "Revenge Fleet"]/TechTree/St[60] 3 -> 2
/Sim/players/Player[32 "Revenge Fleet"]/TechTree/TResDone[60] 22469 -> 0
/Sim/players/Player[32 "Revenge Fleet"]/TechTree/Tbd[60] 2 -> 1
/Sim/players/Player[32 "Revenge Fleet"]/TechTree/St[64] 2 -> 4
/Sim/players/Player[32 "Revenge Fleet"]/TechTree/TResDone[64] 0 -> 22469
/Sim/players/Player[32 "Revenge Fleet"]/TechTree/TAcq[64] -1 -> 16
/Sim/players/Player[32 "Revenge Fleet"]/TechTree/TiAcq[64] -1 -> 45
/Sim/players/Player[32]/Events/…/EvDsc 'Research Over Budget' -> 'Research Complete'
/Sim/players/Player[32]/Events/…/EvMsg 'Research for Micro-Fusion Drives has gone overbudget.'
-> 'Tech Overthrusting has been acquired'
/Sim/players/Player[32]/Events/…/EvImg 'EVENT_RESEARCH_OVERBUDGET' -> 'EVENT_RESEARCH_COMPLETE'
/Sim/players/Player[32 "Revenge Fleet"]/ResTNm 'DRV_McroFus' -> ''
/Sim/players/Player[32 "Revenge Fleet"]/otch/.[34] only-in-B
/Sim/players/Player[32 "Revenge Fleet"]/otch/.[0] 33 -> 34
And the sub-tree digests say which halves of the save are affected, which is the form the claim should take (PD4):
| sub-tree | OA | W | |
|---|---|---|---|
Sim/spymgr |
9c3f224cf954aa54 |
9c3f224cf954aa54 |
identical |
Sim/systems |
3227b08124339fe2 |
3227b08124339fe2 |
identical |
Sim/fleets |
26a640cdfc597869 |
26a640cdfc597869 |
identical |
Sim/trdmgr |
ed99071bdc121694 |
ed99071bdc121694 |
identical |
Sim/players |
2d1c0cad92679606 |
46ddf6778ef36e0b |
differs |
Summary |
66db5e1055c9fe37 |
cb8a4c8e22480af0 |
differs (the derived checksum) |
Every leaf this lane's numbers depend on reproduces across the pair.
spymgr— the spy'sdeat,sdet,sdo,cm,cmo,ncp,cbh— is byte-identical between an instrumented run and an un-instrumented one, as is every system'sspies2. What does not reproduce is one AI empire's research pick and the two derived values downstream of it.
Three distinct picks across five processes is worth stating plainly, because it is the first
time this outcome set has been sampled more than twice: A/M/W completed Overthrusting (tech 64),
B was part-way through DRV_NodFoc, OA went over budget on DRV_McroFus. AS observed A and M
agreeing and correctly flagged it as a 1/k coincidence rather than evidence about the instrument.
With five samples the set has at least three members and the modal outcome took 3 of 5 — so the
coincidence AS refused to lean on was about a 1-in-3 event, not a 1-in-2 one, and refusing to lean
on it was right.
7.3 The seed-exposure predicate, checked (rule 26 corollary (c))
The coordinator's instruction is to record the exposure fields next to every control hash. Here they
are for the pre-turn save of the control pair, and for every turn this lane played, so the
predicate can be checked rather than asserted. Species = 4 (NPC) players are omitted; they never
decide. Net ids: player 16 is re (human), 32 is Revenge Fleet (the AI), 48 is Spengler.
| pre-turn save | Frame | Player[32] ResTNm |
NDes |
NOwn |
ships | exposure |
|---|---|---|---|---|---|---|
as-turn14-predeploy |
14 | '' |
15 | 10 | 146 | pick |
as-turn15-spydeployed (the control input) |
15 | '' |
15 | 10 | 146→148 | pick + colonise + 2 ships |
w-16 |
16 | '' |
15 | 11 | 157 | pick + 9 ships |
w-17 |
17 | '' |
16 | 12 | 161 | pick + design + colonise + 4 ships |
w-18 |
18 | '' |
20 | 12 | 169 | pick + 4 designs + 8 ships |
w-19 |
19 | '' |
20 | 12 | 181 | pick + 12 ships |
w-20 |
20 | 'WEP_AmWhd' |
20 | 12 | 185 | 4 ships |
w-21 |
21 | 'WEP_AmWhd' |
23 | 12 | 201 | 3 designs + 16 ships |
w-22 |
22 | 'WEP_AmWhd' |
27 | 12 | 218 | 4 designs + 17 ships |
w-23 |
23 | 'WEP_AmWhd' |
27 | 12 | 236 | 18 ships |
w-24 |
24 | 'WEP_AmWhd' |
27 | 14 | 235 | colonise ×2 |
w-25 |
25 | '' |
28 | 15 | 244 | pick + design + colonise + 9 ships |
w-26 |
26 | '' |
28 | 15 | 251 | pick + 7 ships |
The predicate held, and it held in its strong direction. The control input carries
Player[32].ResTNm == '' — a pick turn — and the control varied, in exactly the research-pick leaves
the predicate names and nothing else. Two independent lanes have now produced that signature on this
input.
And the state says something the predicate did not anticipate: on this map exposure is
saturated. Every one of the thirteen pre-turn states above carries at least one exposure
signature; nine of thirteen carry a research pick, and every single turn adds ships. There is no
quiet turn anywhere in this lineage to attempt an agreeing control on — so no rung-A oracle pair is
obtainable from as-turn15-spydeployed.sav at any horizon by waiting, and a lane that wants one on
this map must pin the client seeds instead. That is a fact about a 2-empire game where the AI runs a
146 → 251-ship war economy, and it is the opposite end of the same axis from lane AR's quiet
turns 38–39.
One nuance that cuts against a naive reading of the predicate. Exposure is necessary for
variation, not sufficient per process pair: W (probes=8) and AS's A and M (two different
configurations, two different builds, three different processes) landed on the same post-turn
bytes on this exposed pick turn. A single agreeing pair on an exposed turn therefore proves nothing —
which is rule 26's original point, now with a measured outcome-set size behind it.
7.4 OB, and the verdict
OB (Autosave EndTurn).sav 98e45d3745b914506fc4c409ad5a5ad0938a2003ab4c845421a99a8065149c3a 76,706
OB (Autosave).sav 262f8bda97c9511d8d2b41e6a8a0582d6f5364c2b01b1caf99e639d0e4f53ebe 77,685
- OA ≠ OB. The two
hooks=offprocesses of this lane's own control disagree, in 15 leaves, all of themPlayer[32]'s research pick plus/Summary/Checksumand theturnstatstchcell. Nothing of my empire, the spy,deat,sdet,sdo,cm,cmo,ncp,spies2, the systems, the fleets ortrdmgrmoved between them. - OB is byte-identical to W, the instrumented run:
state_checksumprints IDENTICAL over 44,438 leaves. Both arefc85c4228b9973fa159c1ed6aa851ae0.
So the honest three-part statement, which is all the evidence supports:
- Not a calibration pair. Two fresh un-instrumented processes on this input do not agree, so
nothing here may enter
determinism-hashes.txtas a rung-A pair. PD1 and PD3 held. - The instrument is neutral on everything this lane measured.
probes=8reproduced an un-instrumented run leaf for leaf, and the sub-trees carrying the spy state are identical across all three of this lane's processes. PD4 held. That is a stronger neutrality statement than lane AS could make, because AS's instrumented run agreed with only one of two controls; here it agrees with one control exactly and completely while the two controls differ from each other in a leaf class the instrument provably does not touch. - The divergence is the known one, and it was predicted. PD2 held, and it held with the pre-turn exposure fields recorded in advance (§7.3) rather than diagnosed afterwards.
A note for whoever certifies pairs. Lane AS reported A ≠ B and called the class "one AI player's research pick plus the derived checksum". Two more processes, taken independently, land in the same class and add a third member to the outcome set. That is a second lane reproducing the class while failing to reproduce the value — which is exactly what a per-process seed feeding a small discrete choice should look like, and it is now measured rather than asserted.
8. Predictions scored — including the one that was wrong
| prediction | verdict | |
|---|---|---|
| PA1 | P reaches at most one draw site per call, selected by Species == 6; only 0x00840a3c from this save |
HELD. No row at 0x0044092e or 0x004409cc on any of the twelve measured turns, including the turn P ran |
| PA2 | species table 0 Human … 4 _NPC, 5 Zuul, 6 Morrigi; 0.75f is Zuul, 0.5f is Morrigi |
HELD (corpus-wide; the launch card independently shows re = Human) |
| PA3 | 0x00840a3c is Chance on a widened 0.25f |
HELD — 0x009e5ac0 is 00 00 80 3e |
| PA4 | the spy is destroyed at the end of vslot13 on the P turn |
HELD — nspy 1 → 0, spies2 emptied, no spy record at Frame 25 |
| PB1 | sdo(f) = 0.0084 × (f − 14) exactly, until detection |
HELD — seven exact float32 steps, then frozen |
| PB2 | tail costs 1 on Frames 16, 17, 18 | HELD |
| PB3 | 0x0088dc43 first fires on Frame 19; Frame 18 bootstraps without drawing |
HELD exactly |
| PB4 | the tail costs 2 words/turn from Frame 19 | WRONG. It costs 2 on Frame 19 and then 4, 6, 9, 8, 9, 8, 8 |
| PB5 | cm, spyon move on Frame 18; cmo steps by 0.2f |
HELD |
| PB6 | on the detection turn vslot13 still costs 1 and sdet := T |
HELD — Frame 22 |
| PB7 | vslot13 costs zero on T+1, T+2 while still being entered |
HELD — Frames 23, 24: no row, probe reads 1 |
| PB8 | T+3: one row at 0x0044_0a41, 1/1/0/strategic; no 0x0048_7c8f; vslot14 contributes 0 |
HELD in all three parts |
| PB9 | the Slot13RngCallee probe reads 1 on T+3 |
HELD — its first non-zero in the campaign |
| PB10 | 11 % by +4, 43 % by +10, 70 % by +15, 87 % by +20 | not tested. Detection landed at +7, where the model says 26 %. One sample is consistent with the model and does not test it |
| PB11 | residual 0 on every turn | HELD on all eleven turns of W and on run D |
| PB12 | the +20 tail case | not reached |
| PC1 | 0x0038c984, NextFloat, 1/1/0, strategic |
HELD exactly |
| PC2 | in the ProcessTurn bracket |
HELD |
| PC3 | the same turn's tail costs exactly 1 | HELD |
| PC4 | cbh reproduces 5.033599376678467 exactly |
HELD |
| PC5 | spies2 0 → [1], deat → 400, tdep := 15, sdo := 0.0084 |
HELD |
| PD1 | the control pair's pre-turn saves agree, post-turn saves differ | HELD |
| PD2 | the divergence is one AI's research pick + checksum + tch, and nothing of mine |
HELD — 15 leaves, localised |
| PD3 | not a calibration pair; must not enter determinism-hashes.txt |
HELD, and stated in advance |
| PD4 | the sub-trees this lane's numbers depend on reproduce | HELD — spymgr, systems, fleets, trdmgr digests identical; and W is byte-identical to OB over 44,438 leaves |
8.1 PB4, the one that was wrong, and why it was wrong
I predicted the tail bracket would cost 2 words per turn from Frame 19, on the reasoning that
vslot13 contributes 1 and vslot14 contributes 1. Both halves of that were right on every single
turn. The prediction was still wrong, because the tail has a third contributor and I treated it as
a constant zero.
Frame 16 17 18 19 20 21 22 23 24 25 26
vslot13 1 1 1 1 1 1 1 0 0 0 0 <- predicted exactly
vslot14 0 0 0 1 1 1 1 1 1 0 0 <- predicted exactly
0x00820e1d 0 0 0 0 2 4 7 7 8 7 8 <- NOT PREDICTED
tail bracket 1 1 1 2 4 6 9 8 9 8 8
0x00820e1d is the trade-raid roll in TradeManager::Slot13RngCalleeA — lane AC's site, priced
per fleet by lane AD. It was at zero on Frames 16–19 and entered the tail on Frame 20, growing to 8.
That is the AI's raider fleets arriving on trade-sector nodes as its navy went from 157 to 251
ships: calls == words == the number of qualifying fleets, exactly lane AD's per-fleet model,
now reproduced on a different map, a different lineage and an AI-owned fleet rather than a
player-owned one. AD measured 2; this run measured 2, 4, 7, 7, 8, 7, 8 on seven consecutive turns.
The lesson is rule 20's, in the direction nobody watches: I checked that my sites were priced, and then quoted a bracket total as if the bracket contained only my sites. A bracket is a sum over subsystems, and predicting one requires predicting all of them. The correct form of PB4 was "the spy contribution to the tail is 2 words per turn from Frame 19", which is what was measured and what should have been written.
It is also the most useful failure available, because a wrong bracket prediction that decomposes cleanly into "my model was right, an unmodelled neighbour moved" is a free confirmation of the neighbour's model. The residual was 0 on every turn, so nothing is unaccounted for.
9. Coverage — what this did not touch, said as loudly as what it did
0x00840929and0x008409c7remain unfired, and are now known to need a Morrigi empire. That is a stronger negative than "not reached in the states measured": the gate is decoded, the branch was taken twelve times in the other direction, and the predicate is a single field on the spy's owner. The workload that closes them, named from the failed conjunct: a Morrigi single-player game, four spy techs, a spy deployed at a foreign colony with a belt, and ~7 turns of waiting.0x008409c7needs one more thing on top — the roll at0x00840929must succeed (75 %) and the candidate list must be non-empty, which needs a third non-eliminated empire that is neither the spy's owner nor the target's. This map has one (Spengler), so a 3-empire Morrigi game reaches both.Pran exactly once. Everything about its cost is a single observation. The loop is over the spy vector, so two spies detected three turns apart should give two calls on different turns and two spies detected on the same turn should give two calls on one turn — that is a reading, not a measurement (rule 20: do not fit a constant to one observation).- The
sdoarithmetic is still validated only in its first and third terms.countA,countBand the species multiplier were 0 / 0 / 1.0 for all eleven turns.countCheld at 2 throughout, which is why the line is straight; nothing here identifies whatcountCcounts, only that it did not change while one fleet of one ship sat at Kepler. - The auto-detect arm
(TerrFl & 1) == 0was never taken. Lane AS flagged it as the most load-bearing unmeasured branch in the chain and it still is. Nothing in this lane touched it. - The
ncp-blocks-rebootstrap gate was never reached (§5.4 item 3).cmnever wrapped 4 → 0 because the spy was destroyed at Frame 25 withcm = 4. Two more turns of survival would have tested it; a second spy deployed later than the first would too. P's return value was not traced. The Frame-25 roll's outcome (whetherrewas named in Kepler'sEVENT_SPY_DESTROYEDevent, a 25 % chance) was not read out of the event text. The ledger prices the site; the semantics in §1.2 are from the instruction stream.- One turn, one process for the eleven-turn series. W is a single process. The control pair covers only its first turn. Frames 17–26 have no two-process control at all, and by §7.3 every one of them is an exposed turn, so they should not be assumed reproducible.
- Turn 27 was not played. The driver stopped on a Von Neumann encounter dialog at Rigel, which is where the series ends. Nothing after Frame 26 is measured.
probes=11was not used anywhere in this lane, and no number here is comparable to lane AC's turn totals.- No
Guardregion is declared by any hook in this family — the same gap lanes Z, H, AS and AR reported.undeclared = 0in these traces is vacuous. - The species table is read from the save's own
ISsplist, cross-checked on one Zuul save and one hook report. No Morrigi, Hiver, Liir or Tarka save exists in the corpus, so indices 1, 2, 3 and 6 are named but not exercised.
10. Corrections to earlier findings (rule 11)
- Lane AS §6, board row 207, lane AG §6 and this lane's own brief: "
P's three draws".0x00840929,0x008409c7and0x00840a3care not three sites behind one gate. They are two mutually exclusive branches selected byspyOwner->Species == 6, and no call can reach more than one of them. Every inventory that lists them as a group of three should say so. §1.2. - Lane AG §3.2's caution to lane AS: "a spy program run without FTL Economics takes a different
branch of the machine". The
CnTrdthe stage machine consults atcm 1 → 2/3is the target system owner's, not the spy owner's. My empire hasCnTrd = Falseand the machine still took the1 → 2arm, because Kepler's owner hasCnTrd = True. §5.4 item 4. - Lane AG §3.2's
ncpreading.ncpis a scalarintin the stream, and it behaves as a counter of completed counter-mission stages (0,0,1,1,2,2,3 in lock-step withcm). "Non-empty" is therefore!= 0, and the branch that consults it was never reached here. §5.4 item 3. - Lane AS §1.4 / §6.1: "
cbh = NextFloat() * 2*pi". Correct in value; the code islerp(0.0, 2*pi, r)with a float32 round in the middle, and the angle isDeploySpy's second argument, set up before0x0080c860(a zero-argument getter) is called across the half-built frame. AS's "spy->cbh := <the float argument>" is right; §6.2 only names which argument. - Lane AS §6's detection-probability table (38 % / 65 % / 85 % at +10 / +15 / +20) is the right
model evaluated one turn late.
AccumulateDetectionOddsruns before the gate, so the roll on the End Turn producing Framefusessdo = 0.0084 × (f − 14), not0.0084 × (f − 15). The corrected figures are 43 % / 70 % / 87 %. Measuredsdo(§5.3) settles it: the Frame-16 autosave already holds 0.0168, so the Frame-16 roll was at 0.0168. - Lane AG's audit and lane V2's inventory have a column they need.
0x0078c97f's predicate is "aSHIPACTION_DEPLOYSPYcommand in this turn's command stream". It is now measured, so the gate-indexed audit's two cell kinds (a firing, or a save predicate) become three: a firing, a save predicate, or a stream predicate. This is the first entry in that column.
11. Artifacts
| what | where |
|---|---|
the instrumented eleven-turn trace (per-turn draw_sites + probe_entries + brackets) |
verify/traces/ap-probes8-turn16-turn26.jsonl.gz |
| the deploy-turn trace | verify/traces/ap-probes8-deploy-turn15.jsonl.gz |
| shim log for the eleven-turn run | verify/results/shim/ap/ap-probes8-w.shim.log |
| shim log for the deploy run | verify/results/shim/ap/ap-probes8-d.shim.log |
the detected-spy save — first sdet != -1, first non-zero cm/cmo/ncp/spyon in the corpus |
verify/results/saves/ap-turn22-spydetected.sav (83,188 B, --strict 0 errors / 0 warnings) |
the post-P save — the spy destroyed, nspy back to 0, spies2 emptied |
verify/results/saves/ap-turn25-spydestroyed.sav (84,487 B, --strict clean) |
the deploy run's Frame-15 autosave (cbh reproduced) |
verify/results/saves/ap-deploy-turn15.sav |
| the two control post-turn saves | verify/results/saves/ap-oa-turn16.sav, ap-ob-turn16.sav |
| addresses this lane mints | ghidra/addresses.d/ap.json |
| predictions, committed before the build | this file §0–§3, commit f8ff1b6 |
| instrument | sots-engine src/shim/shim.cfg.hp8 and shim.cfg.hoff, both unchanged — this lane wrote no engine code |
11.1 The hashes, for the record
input MyGameas1spydep.sav 854a10fa1ea602f0f7909f9bf9154942e56ea5fa1db4cce47d6d32bf53952e08
A/B/M/OA/OB (Autosave EndTurn).sav 98e45d3745b914506fc4c409ad5a5ad0938a2003ab4c845421a99a8065149c3a
W (probes=8) turn 16 262f8bda97c9511d8d2b41e6a8a0582d6f5364c2b01b1caf99e639d0e4f53ebe
OB (hooks=off) turn 16 262f8bda97c9511d8d2b41e6a8a0582d6f5364c2b01b1caf99e639d0e4f53ebe
OA (hooks=off) turn 16 e296394f578b3a5668e5934b9eec8d6c4638ca7411200078f97de1abd346f4c5
turn 22 (detection) 91381bd7e722ac3847795a402d78ebb31d74dbb5d2909f33d243aec3ef8abaf8
turn 25 (P ran; spy destroyed) 46380fcf4498d67507e4471eb9ee3762647c53ee5473be142fe5c34e37344bc3
deploy run, turn 15 844f2b746060dc2c4e2099cdc6ceffd5f55ef858c9e347df28c8aca3041f7165
shim binkw32.dll ap-989c692-… ae084ed4774caf24ea7927fcb012d1999633f10556c7c99f0209f66067416a17
None of these is a calibration pair. §7.
12. VM144 as left
Restored and verified by screenshot at the main menu, profile re, 2026-09-09.
C:\SOTS\binkw32.dllrestored to lane L3's build fromC:\SOTS\shimdist-l3\binkw32.dll(sha256479B8614D2417603…, byte-identical to what this lane found in place).C:\SOTS\shim.cfgrestored fromC:\SOTS\ui\preAS-shim.cfg(L3'sshim.cfg.l3probe,probes=11).- The three autosaves restored byte-identical from
C:\SOTS\ui\preAS-SavedGames(1985E6F4…/24B2E072…/5EC80C1E…). This lane's eleven turns had overwritten their contents; lane AS's snapshot is what made the restore possible and it is worth keeping. C:\SOTS\shim.trace.jsonlandshim.logremoved before the relaunch.
SavedGames is still 15 files — this lane added none and removed none, so lane AS's row
positions still hold: MyGameas1predeploy at (400, 347), MyGameas1spydep at (400, 376),
OK at (682, 624). Verified by screenshot twice this session. All fifteen files hash as AS left
them.
Left in place, all harmless: C:\SOTS\shimdist-ap\ (build ap-989c692-20260909T0425Z, 63 files),
C:\SOTS\ui\apgo.ps1, C:\SOTS\ui\aphash.ps1, C:\SOTS\ui\ap\ (the End-Turn driver, its per-turn
saves and screenshots), and the scheduled task SOTSAP. click_helper.ps1 was not touched —
this lane needed no new verbs either.
12.1 Guest notes worth carrying forward
- The End-Turn driver is the reusable part.
C:\SOTS\ui\ap\ap_loop.ps1, run through theSOTSAPscheduled task (anInteractiveTokenclone ofSOTSUI), readsC:\SOTS\ui\ap\job.txt(turns=,tag=,first=,maxwait=), clicks End Turn, polls the autosave's mtime until it moves, snapshots(Autosave).savtoap\saves\<tag>-<NN>.sav, and stops on the first stall with a screenshot. Eleven turns cost 11 minutes unattended. Setjob.txt,schtasks /Run /TN SOTSAP, then watchap\loop.logfrom outside. - Nine minutes of this lane went into a screenshot-lag artefact, and the lesson is general.
Four consecutive clicks appeared to select the wrong Load-dialog rows, which read exactly like a
coordinate offset and nearly produced a "the click helper is 87 px out on this guest" note. It was
not: the click task takes ~3 s to start and ~3 s to run, and a screenshot taken 4 s after
schtasks /Runshows the state before the click. Wait ≥ 9 s between triggering the click task and screenshotting. Every documented coordinate on this guest is correct. - The first click after
fgis swallowed by the focus activation. Sendfg, thenmove, thensleep, thenclick— or spend a throwaway click. - A
wheelzoom on the star map separates systems that overlap at the default zoom. Lane AS lost a run to Kepler and Midway sharing a hit test; six notches at the cluster centre and then eight more at the target spread them to ~120 px apart, and the labels render. No affine fit was needed. - Selecting the system, not the fleet, is what enables
Special. With Kepler selected the left panel grows aFleets at Keplerlist andManage Fleets/Move/Speciallight up;Special → Deploy Spythen opens a ship picker whose ship is already selected, soOKis the only further click. The whole deploy is four clicks once the right star is under the cursor. - The pixel test for "is the main menu up" must sample the button, not its label. (511, 536) is
inside the white text of
Load Gameand reads ~(195,191,191) on a bright frame; (470, 530) is the button itself and reads (184, 0, 0). A test on the first point passes only by luck on a dark frame. - An End Turn on this state costs 6 seconds under
probes=8with ~150–250 AI ships. The campaign's "30–45 s per End Turn" figure is from a different guest and a different config. - Turn 27 raised a Von Neumann encounter at Rigel ("No ships available",
Doneat (233, 673)). A driver that polls the autosave rather than the screen stalls on it cleanly instead of clicking through something it cannot see.
Proposed board rows
New row:
| 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` |
Edits to existing rows:
- Row 62 (guest holders) —
VM144 = FREE (lane AP released 2026-09-09; restored to L3's build 479B8614… + L3's shim.cfg (probes=11) + the three pre-AS autosaves BYTE-IDENTICAL from ui\preAS-SavedGames, main menu verified by screenshot). SavedGames is STILL 15 FILES and lane AS's row positions STILL HOLD - MyGameas1predeploy (400,347), MyGameas1spydep (400,376), OK (682,624), verified by screenshot 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 scheduled 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). click_helper.ps1 untouched. GOTCHA THAT COST THIS LANE NINE MINUTES: the click task takes ~6 s end to end, so a screenshot taken 4 s afterschtasks /Run /TN SOTSUIshows the state BEFORE the click - four such shots read exactly like an 87 px coordinate offset and nearly became a false gotcha. WAIT >= 9 s. GOTCHA: the first click afterfgis swallowed by the 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:wheelzoom separates stars that share a hit test at the 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. - Row 207 (V2's eight sites) — append:
CORRECTED AND ADVANCED 2026-09-09 by lane AP. 0x00840a3c FIRED (1 word, Frame sdet+3 from as-turn15-spydeployed.sav). 0x00840929 and 0x008409c7 are NOT two more sites behind the same gate - they are the MORRIGI branch of SpyManager::Slot13RngCallee (cmp [spyOwner+0x5c],6), mutually exclusive with 0x00840a3c, and unreachable in any Human/Hiver/Tarka/Liir/Zuul game. Closing them needs a MORRIGI empire (and, for 0x008409c7, a third surviving empire). Of V2's eight, only 0x0082cdb8 (trade 15) is now both reachable and unfired. - Row 418 (lane AS's detection-roll row) — append:
EXTENDED 2026-09-09 by lane AP: the roll fired on seven consecutive turns and SUCCEEDED on Frame 22;sdois exactly 0.0084*(f-14) as float32 over NINE points and FREEZES the moment sdet stamps. AS's probability table is one turn late - the corrected figures are 43%/70%/87% at +10/+15/+20. AS's 0x0078c97f is now MEASURED (1 word, ProcessTurn bracket) andcbhreproduced bit-identically across processes and builds. - The
0x0088dc43row of lane AG's audit (§3.2) —FIRED 2026-09-09 by lane AP, 1 word/turn from Frame 19 (= tdep + 4). The bootstrap turn draws nothing. TWO CORRECTIONS: the CnTrd consulted at cm 1->2/3 is the TARGET OWNER's, andncpis a scalar counter of completed stages, not a container. - The gate-indexed audit's cell taxonomy —
A THIRD CELL KIND EXISTS AND NOW HAS ITS FIRST MEASURED ENTRY: a STREAM predicate. 0x0078c97f fires once per SHIPACTION_DEPLOYSPY command applied; no predicate over the save can express it, and no call-graph sweep can find it (stack-built ship-action table, lane B6's class).