49 KiB
Is the residual fleet visit order an RNG draw or heap order? — the per-client draw ledger on ad-turn27
- Owner / date: lane BR · 2026-09-09 · guest VM146 (
sots-re-win10-146,re@192.168.10.146) - Held: VM146, from the time this prediction was committed. VM140 (reference, exclusive), VM141 (lane AZ) and VM144 (lane BS) untouched. VM145 free and untouched.
- Trigger:
findings/resolutions/2026-09-09-fleet-id-order-residue.md§4 / §8 — the single probe the resolver chose over two alternatives, because it is simultaneously run C (a third pinned sample, sizing the residual outcome set) and probe 3 (the per-client draw ledger). - Build:
bp-989c692-20260909T0505Z, lane BP's dist, still deployed atC:\SOTS\shimdist-bp(binkw32.dllsha2569de1c57a38e66800a9487f46f7e7abc4fea6efc05cd72319d911733e9d971f47). No build for this lane — the resolution costed it at "one fresh process, no build". - Input:
C:\SOTS\SavedGames\ad-turn27-two-raiders.sav, sha2561c8baa27680809d585ab1728391e82729642d32aaedffd5bf95b616f0a11a436, 100,809 B — re-verified on this guest before the run, equal to lane AD's and lane BP's published value. - Route: load (
ad-turn27-two-raiders.sav→ one End Turn), the same route as lane BP's three runs. This lane holds the route fixed, so it is unconfounded by lane BQ's per-history input (resolution §9.1).
0. The question, in one paragraph
Lane BP pinned all three AI client seeds on the campaign's richest AI turn and two fresh processes
still disagreed — but only in 35 leaves of 61,147, and all 35 are a single transposition: two
newly formed fleets exchange contents. The resolver decoded the ids and changed the frame: the ids
are not the variable (1970/1986/2002 are Player[32]'s own client, counters 123/124/125, minted in
that order in every process), the visit order over the ship groups needing a new fleet is. Two
mechanisms remain, and only two. H2: a draw on the per-process global generator at
0x00af6e58, which the seed pin does not touch and which already has two unnamed AI-band consumers
in the record (0x0079ea90+0x73, 0x005b9f00+0xc0). H1/H1b: an address-keyed container (or an
unstable comparator) walked in heap order — for which BP's own aielem node addresses supply a
measured precondition (same-size allocations landing in different relative orders in two processes
with identical decisions), the image base being constant on both guests (lane BQ). This probe
separates them by measurement: airng=on brackets each AI client's whole turn at
StrategyClient::OnResumePlaying and attributes every draw on every generator inside it to a
return address and a generator address. Lane I's inlined-draw inventory places no inlined draw
site between 0x680000 and 0x6e0000, so the observer is complete in the AI band.
1. Predictions, committed before the run
These are the resolution's §4 P1–P4, restated verbatim in substance, with the falsifier of each named. They are committed in their own commit, with no result in the file.
P1 (run C — the outcome set).
(Autosave).sav∈ {e913ff41188211c02d536da616337544e64133996cd7cc7a6521a8df1960226f,724528ffde9a7514b69ab9e82ed3b8c8ade63d622c0720de30503a73a92df11e} — lane BP's A/L file or its B file. A third file does not immediately mean either thing: it is ambiguous between the order outcome set exceeding 2 (k ≥ 3) and theairngdetour perturbing the run. The resolution names the disambiguation and this lane will run it if the guest allows: one more run ofshim.cfg.bppin(airng=off, plain run C). Under thek ≥ 3reading that plain run cannot land on a third distinct file with probability better than 1/3; under the perturbation reading it lands on one of BP's two.P2 (the headline — probe 3). In Player[32]'s
OnResumePlayingbracket:foreign_words=0,foreign_calls=0,residual=0, and noairngcensusrow whoserngis anything but Player[32]'s own client generator atclient+0x134— in particular none on0x00af6e58and none onSeedrecord #2's object. The same for clients 496 and 512.P3 (the ledger's shape — not load-bearing, recorded so it can be disagreed with). Player[32]'s own generator spends single-digit words inside its bracket, at return addresses in the AI band (
0x0068xxxx–0x006exxxxRVA-wise, i.e.vain0x0068_0000–0x006e_0000atexe base = 0x005e0000); clients 496 and 512 spend fewer than Player[32].P4 (the block). The
aiordersdump differs from lane BP's runs A and B only inside the proven noise set (the L/A mask: list 1 → words 1, 5, 8, 11; list 3 → 4–11; list 5 → 8–11; list 7 → 2–11; list 8 → 1–11; list 10 → 2–11; list 12 → 5–11; list 14 → 2–11; list 23 → 2–11) — plus, at most, the three-word transposition (list 8 idx=1 word0,list 10 idx=0 word0,list 10 idx=1 word0). Structure identical: 9 blocks, 81 elements, the same(pid, list, idx)sequence, the same per-list counts, player 32 emitting{1:3, 3:19, 5:15, 7:2, 8:3, 10:4, 12:12, 14:8, 23:15}.
1.1 How each prediction could be wrong, and the symptom
| way | symptom | what it would mean |
|---|---|---|
| H2 is right — some AI-band site draws on the global generator or on another object | ≥ 1 airngcensus row with rng ≠ client+0x134 inside a bracket |
P2 falsified, and that is a success. The row's ret_rva/va names the consumer. Follow-up is a global pin (RNG_Seed(0xaf6e58, K) after Application::Initialize), two fresh processes, predict byte-identical. Stop and post the address. |
| H1/H1b is right | foreign_words=0 everywhere; the census carries only the client's own generator |
P2 holds. RNG is excluded by measurement for the AI turn, and stage 2 becomes one bounded static read to place a hook on the group walk. Stop. Do not read the binary. Do not start stage 2. |
| an inlined draw on the AI path that the seven entry-point detours cannot see | residual > 0 on a bracket (left_delta > observed) |
P2 falsified in its residual=0 clause. The residual is a per-generator number measured off the object at +0x9c8, so it reports the invisible draw's existence without its address; the follow-up would be an image-wide immediate scan restricted to the AI band, which lane I already ran and which found nothing there — so this outcome would also contradict lane I. |
the airng detour perturbs the turn (rule 19) |
a third autosave file | Not separable from k ≥ 3 by this run alone — see P1. This is the caution the resolver put on the record and it will be reported as ambiguity, not resolved by preference. The disambiguation is one plain shim.cfg.bppin run. |
| the pin is half-applied | any aiseed call=… pinned=0, or a PIN MODE WITH NO PINS line, or a used=deadbeef wildcard hit |
The run is void and nothing is read from it. Checked in shim.log before any output file is opened. |
| a stale or wrong binary (rule 24) | BUILD_ID ≠ bp-989c692-20260909T0505Z in shim.log |
Void. No build is done by this lane, so the only failure mode is deploying the wrong dist. |
| the config carries an inherited defect | a live line inside what was meant to be a comment; an unnamed registered hook silently installed | Defended by the check that caught it in BP's own first draft: the non-comment diff of shim.cfg.bppin against shim.cfg.brprobe, quoted in §2.1, must be exactly three lines. |
| the click path lands somewhere else | wrong screen in a screenshot | Every step screenshotted and verified; never sleep-and-click. And the wall's /shot/<id>.png is a cached frame up to ~5 s stale (BP's gotcha, four wasted clicks) — wait 12–16 s or take two shots and use the second. |
1.2 What this probe cannot decide, whatever it says
It is one save, one procedure, one build, one route (load), and — for the ledger half — the
turn's own AI band. A foreign_words=0 result excludes a draw inside the bracket; it says
nothing about a draw taken before OnResumePlaying is entered whose value is carried into the
visit order (nothing in the record proposes such a path, and the seed pin is applied earlier still,
at RunAI, so a pre-bracket draw would have to be on a generator the pin does not touch and be
consumed later — named here so nobody reads P2 holding as more than it is). It also cannot identify
the container: that is stage 2's hook, and the resolver was explicit that the read must not begin
before this probe returns. The 3–40-turn fuse at 0x0069dbb0 cannot fire inside one turn of a load
and is untested here, exactly as in lane BP.
2. The procedure
2.1 The config, and the non-comment diff (the check this lane was briefed to run)
shim.cfg.brprobe is shim.cfg.bppin with exactly three non-comment lines changed. Diffed
twice, once on the host before the file was pushed and once on the guest, from the two files that
were actually there:
$ diff <(grep -v '^\s*#' shim.cfg.bppin | grep -v '^\s*$') \
<(grep -v '^\s*#' shim.cfg.brprobe | grep -v '^\s*$')
6c6,8
< airng=off
---
> airng=on
> airng.pin_seed=off
> airng.out=C:\SOTS\shim.airng.txt
bppin has 43 live lines, brprobe 45. Everything else — hooks=trace, all 28 registered
hooks off by name, aiorders=on with deep=off and words=12, aiprobes=off,
airesearch=off, aiseed=pin with 32=156ebbbd,496=fe7b2826,512=0ed341d1,*=deadbeef — is
byte-for-byte BP's. airng.pin_seed=off is stated explicitly even though it is the default: a
pinned generator is a declared perturbation and the resolution forbids it here, so it is written
down rather than assumed.
The same diff, taken from the runtime config: lines of the two shim.logs rather than from
the files, is identical — an independent confirmation that the three lines are the three lines that
reached the process:
$ diff <(grep -o 'config: .*' bp/A-shim.log) <(grep -o 'config: .*' br/BR-shim.log)
6c6,8
< config: airng=off
---
> config: airng=on
> config: airng.pin_seed=off
> config: airng.out=C:\SOTS\shim.airng.txt
BP's report says its own first draft of a config reproduced an inherited defect (a wrapped header
comment leaving a live line in the body) and that this diff is what caught it. This lane's inserted
header is seven lines, every one #-prefixed; the 45 live lines were printed and read before the
run.
2.2 The instrument, verified from shim.log before anything was read
-
build bp-989c692-20260909T0505Z— BP's dist, unmodified, no build by this lane;binkw32.dllsha2569de1c57a…d971f47. -
exe base=0x005e0000 (link-time image base 0x00400000, ASLR delta +1966080) pid=7064— the same base lane BQ measured in three of BP's processes on this guest, so image base is constant here for a fifth time and "pointer order" can still only mean heap order. -
27
hook: … mode=off (not installed)lines plusselftest: … mode=off= 28 of 28 registered hooks off. Zeroignoring unknown key. ZeroCOVERAGE:. -
seven
drawsite:detourscreate=MH_OK enable=MH_OK;aiorders: … probes=0 of 20;aicfg words=12 deep=0 research=0 seed=pin pins=4 detours=2; one batch detourMH_OK; oneaiseeddetourMH_OK. -
the two lines the brief names:
airng: bracket StrategyClient::OnResumePlaying rva=0x00377480 va=00957480 create=MH_OK enable=MH_OK airng: out=C:\SOTS\shim.airng.txt pin_seed=off:00000000 airng: facade cl_Chance rva=0x00178cf0 va=00758cf0 create=MH_OK enable=MH_OK airng: facade cl_RandRange rva=0x001798e0 va=007598e0 create=MH_OK enable=MH_OKNo
airng: PINNING IS ONline, as required. -
the pin, three of three, and no
PIN MODE WITH NO PINS, nopinned=0, no wildcard hit:aiseed call=1 netId=32 observed=0xd0598a7d used=0x156ebbbd pinned=1 aiseed call=2 netId=496 observed=0x91193aec used=0xfe7b2826 pinned=1 aiseed call=3 netId=512 observed=0x9733ed32 used=0x0ed341d1 pinned=1Three new natural seeds, shared with none of BP's nine — lane L1's per-process seeding reproduced a fifth time, now on a turn-27 state.
2.3 Standing instruction: who actually runs, and by which route
RunAInet-id list: 32 / 496 / 512 — three AI clients, in that order, exactly as lane BP measured. The save's other four non-human players (528 Alien Menace, 544 Peacekeeper Enforcer, 560 Von Neumann, 576 Independent Colony) get no client, no call and no seed. Independently confirmed a second way this run: theairngbracket fired five times —pid=16(human,agent=0x00000000) at load, thenpid=32,pid=496,pid=512each with a non-null agent, thenpid=16again — and never for 528–576.- Route: load.
ad-turn27-two-raiders.sav→ Load Game → Single Player → OK → row 1 (400,260) → OK → Launch → one End Turn. No continuation. Every dialog step verified from a fresh QEMU screendump (screendump -f pngover the guest's QMP socket, which bypasses the lab wall's ~5 s frame cache entirely — see §5). - Exposure (rule 26 (c), unchanged from BP and re-derived from this run's own outputs):
Player[32] enters with
ResTNm = BIO_TerBacheld,NumDes46,NumOwn15, 13 ships completing into new fleets; 496 and 512 hold targets withNumOwn = 0; the four NPC factions haveResTNm == ''and no client. Exposed on the design, fleet-assignment and colony counts, not on the research pick. No encounter query — End Turn ran straight to the post-turn autosave.
Timings on this guest, for the next lane: schtasks /Run /TN SOTS → fg + key {ESC} ×3 spaced
2.5 s → main menu at ~2 min 45 s (screenshot-verified, never slept-and-clicked); the click path
through the Load dialog ~2 min at one click per helper call; the turn-27 load 3 min 20 s the
first time and 1 min 55 s the second; End Turn ~4 s with this hook set — BP's "~35 s" is
generous, and the autosave mtime is the reliable completion signal.
3. The result
3.0 Lead
P1 holds, P2 holds, P4 holds, P3 is falsified in its stated form. There is no foreign
draw — not one word, in any of the three AI clients' brackets. And the ledger says something
sharper than "none": over the entire process lifetime, the per-process global generator at
0x00af6e58 was drawn exactly three times, three words, from exactly one return address, and
that address sits 16 bytes before the RunAI call site — it is the AI client seed mint itself,
one draw per client, the thing the pin overwrites. RNG is excluded by measurement. H2 is dead.
A second run followed (§4.2, the Rung B record) and landed on the other branch, which makes the
outcome set two-for-two: five processes on this state, three on e913ff41…, two on
724528ff…, no third file.
3.1 P1 — the autosave, and which file it is
| file | sha256 | bytes | |
|---|---|---|---|
| pre-turn | (Autosave EndTurn).sav |
16f63adbf383946db17c381998957172bca803406b7df78f3b03805fef0bbcb8 |
100,758 |
| post-turn | (Autosave).sav |
e913ff41188211c02d536da616337544e64133996cd7cc7a6521a8df1960226f |
101,375 |
It equals e913ff41… — lane BP's run A, which is also lane BP's run L. Not a third file, so the
resolver's ambiguity caution does not fire and no disambiguation run was needed on that account.
state_checksum.py --floats bits --mask none:
- post-turn BR vs
bp-pinA-turn28.sav:IDENTICAL, root8100b28960f729ef9a24a22f13397b4a— the same root BP recorded for L and A; - post-turn BR vs
bp-pinB-turn28.sav:DIVERGED: 35 leaf difference(s), and the 35 are BP's transposition, leaf for leaf (Flt[1970]/Flt[1986]exchangingPos,PrvPos,FtOrig,FPlan,LocID384↔0,HFPlan,FtTrans,FtFlg12↔13,Ship[6976]↔Ship[5264], plusSys[384]/Flt[1],ShipIDs[]list-reordered andSummary/Checksum). Nothing else moved; - pre-turn BR vs
bp-turn28-pre.sav:IDENTICAL, root6bbaf382c9e570ae882b599f566ae62e.
This is also the instrument's own control, in rule 26 (d)'s shape. The airng detour cannot be
removed and cannot be argued away; what it can do is reproduce a natural run. It did:
a process carrying the OnResumePlaying bracket and two facade detours wrote the same bytes as
lane BP's unpinned run L. airng's behavioural neutrality was previously an argument from
roll-parity.md; on this state it is now a measurement.
3.2 P2 — the per-client draw ledger. The bracket rows, all five
airng seq=1 pid=16 agent=0x00000000 rng=0x0d820d18 pin=off left=624->624 idx=0->0 left_delta=0 observed=0 calls=0 residual=0 foreign_words=0 foreign_calls=0 sites=0 overflow=0 life_in=0 life_out=0
airng seq=2 pid=32 agent=0x327811e0 rng=0x33a90010 pin=off left=612->533 idx=12->91 left_delta=79 observed=79 calls=75 residual=0 foreign_words=0 foreign_calls=0 sites=4 overflow=0 life_in=12 life_out=91
airng seq=3 pid=496 agent=0x32780038 rng=0x1f324540 pin=off left=219->219 idx=405->405 left_delta=0 observed=0 calls=0 residual=0 foreign_words=0 foreign_calls=0 sites=0 overflow=0 life_in=405 life_out=405
airng seq=4 pid=512 agent=0x32780ad0 rng=0x33968010 pin=off left=206->206 idx=418->418 left_delta=0 observed=0 calls=0 residual=0 foreign_words=0 foreign_calls=0 sites=0 overflow=0 life_in=418 life_out=418
airng seq=5 pid=16 agent=0x00000000 rng=0x0d820d18 pin=off left=624->624 idx=0->0 left_delta=0 observed=0 calls=0 residual=0 foreign_words=0 foreign_calls=0 sites=0 overflow=0 life_in=0 life_out=0
foreign_words=0 foreign_calls=0 residual=0 in every bracket, including the one that matters.
left_delta == observed on Player[32] (79 = 79), so the two independent measurements agree and
there is no inlined draw on the AI path either — which is what lane I's inventory predicted and
what a call-graph-shaped observer could not have asserted on its own.
The four in-bracket sites for Player[32], by return address (va in these rows is the Ghidra
VA, 0x00400000 + rva, not the runtime VA the install lines print):
ret_rva |
Ghidra va |
entry | calls | words |
|---|---|---|---|---|
0x004e6e09 |
0x008e6e09 |
NextFloat | 10 | 0 |
0x00178d15 |
0x00578d15 |
Chance | 10 | 10 |
0x002ad878 |
0x006ad878 |
NextFloat | 22 | 22 |
0x00179915 |
0x00579915 |
NextInt | 43 | 47 |
and the facade call sites, all six in the AI band 0x0068–0x006e: cl_Chance from 0x0069086f
(2 calls), 0x006adf35 (2), 0x006adfca (3, 2 true), 0x006ae418 (3); cl_RandRange from
0x006ad94c (22) and 0x00691ea0 (21).
3.3 The census rows whose generator is not that client's own — and the one that matters
The airngcensus table is a whole-process lifetime ledger (its per-generator totals are the
airngen life_words, and rows for clients 496/512 are already present at seq=2, before those
brackets ran). So "foreign rows" here means rows on a generator other than the bracket's own,
and the bracket row's foreign_words=0 is what says none of them moved inside the bracket. Five
generators were seen in the whole run:
runtime rng |
what it is | lifetime words |
|---|---|---|
0x00cd6e58 |
the per-process global generator — static 0x00af6e58 (runtime = static + the 0x1e0000 ASLR delta) |
3 |
0x33a90010 |
Player[32]'s client generator (the word at client+0x134) |
91 → 94 |
0x1f324540 |
client 496's | 405 → 407 |
0x33968010 |
client 512's | 418 → 420 |
0x0d820d18 |
human client 16's | 0 |
0x0d83b170 |
a server-side generator, first seen at seq=5 (post-AI) |
20 |
The whole of the global generator's activity, every row it has in the file, at every seq from 2 to 5:
airngen seq=2..5 rng=0x00cd6e58 life_words=3 life_calls=3
airngcensus seq=2 rng=0x00cd6e58 ret_rva=0x00384f9e va=0x00784f9e entry=NextUInt calls=3 words=3
airngcensus seq=5 rng=0x00cd6e58 ret_rva=0x00384f9e va=0x00784f9e entry=NextUInt calls=3 words=3
Three calls, three words, one return address, and the count does not move between the first AI
bracket and the end of the turn. The two AI-band consumers the resolution named as candidates —
0x0079ea90+0x73 (RNG_Chance) and 0x005b9f00+0xc0 — appear nowhere in the census: they did
not fire in this process at all.
What 0x00784f9e is — inference from the record, not from the binary, which this lane was told
not to read. Lane AI1 records that StrategyApp::RunAI is "reached only from
StrategyNetworkClient::OnMessage 0x00784640 + 0x96e", i.e. the call is at 0x00784fae. The
return address of the only global-generator draw in the process is 0x00784f9e, sixteen bytes
earlier, in the same function, and it fires exactly three times for exactly three AI clients,
at 02:30:34 — the same log timestamps as the three aiseed call=… lines. That is the seed mint:
board row 326's "one 32-bit word drawn per AI client at construction from a per-process global",
now measured live, with its consumer's address, its generator's address and its call count. It is
also the only thing that generator does. A one-line static read would confirm the instruction;
this lane did not take it.
3.4 P3 — falsified, as the weak prediction it was labelled
P3 said Player[32] would spend single-digit words in its bracket. It spent 79 (75 calls, four sites). The second half — "496 and 512 spend fewer" — holds degenerately: they spend zero. Recording it because rule 2 says a prediction exists to be disagreed with, and this is exactly rule 23's corollary again: a bracket total is a sum over every subsystem inside it, and this one was guessed rather than modelled.
Two things in the ledger are worth more than the number:
- Clients 496 and 512 run a full AI turn and draw nothing.
leftdoes not move,idxdoes not move,agentis non-null. Rule 20's design working: "ran and drew nothing" is visibly different from "never ran". Their 405 / 418 lifetime words were all spent before the bracket, at client construction. - The 3–40-turn fuse consumer
0x0069dbb0DID draw — before the bracket, on each client's own (pinned) generator. Census rows0x0069dc02(=+0x52) and0x0069dc2e(=+0x7e): 2+1 words for client 32, 1+1 for 496, 1+1 for 512, all insidelife_in. Both lane BP and lane BQ recorded that this fuse "cannot fire inside one turn of a load and is therefore untested"; that is right about the turn, and this run shows the site is nevertheless entered on the load path (RunAI→RaiseAIPrepareTurn), on the seeded generator. Rule 28 practice 4 exactly: an entry is not a cost, and a zero in a bracket is not a statement about the subtree.
3.5 P4 — the aiorders diff against BP's runs A and B, under BP's noise mask
Mask = the (list, word) positions BP's byte-identical L/A pair proves cannot be read by the
applier (list 1 → 1, 5, 8, 11; list 3 → 4–11; list 5 → 8–11; list 7 → 2–11; list 8 → 1–11;
list 10 → 2–11; list 12 → 5–11; list 14 → 2–11; list 23 → 2–11), plus — named by BP in the same
section and separated out explicitly here — aiblk gate payloads whose SET flag is 0, which are
lane L4's uninitialised-when-unset words.
The comparator was self-tested against BP's own published result before being pointed at this run:
on A vs B it reproduces §3.4's three words and nothing else; on L vs A (the pair that defines
the mask) it reports NONE. Full output in verify/results/shim/br/aiorders-diff.txt.
BR vs run A — 9 blocks, 81 elements, identical (pid, list, idx) key sets, identical per-list
sizes ({1:3, 3:19, 5:15, 7:2, 8:3, 10:4, 12:12, 14:8, 23:15} for pid 32), identical set gates
(rate=1:1 for pid 16, rate=1:0.8 for 32/496/512):
== word diffs, masked ==
NONE. Every differing word is inside the proven noise set.
BR vs run B — same structure, and exactly the three transposition words:
blk=1 pid=32 list= 8 idx=1 word0 B=0x000007b2 (1970) BR=0x000007c2 (1986)
blk=1 pid=32 list=10 idx=0 word0 B=0x00000050 (80) BR=0x00000180 (384)
blk=1 pid=32 list=10 idx=1 word0 B=0x00000180 (384) BR=0x00000050 (80)
The visit order, read straight off list 10 as the resolution says it can be:
L : (384,1970) (80,1986) (384,2002) (816,7072)
A : (384,1970) (80,1986) (384,2002) (816,7072)
BR: (384,1970) (80,1986) (384,2002) (816,7072) <- system 384 visited first
B : (80,1970) (384,1986) (384,2002) (816,7072) <- system 80 visited first
P4 holds in its strong form: the difference from A is empty, and the difference from B is the transposition and nothing else.
3.6 A free measurement the dumps already contained: heap order does not track visit order
The resolution's §2.2 read node heap addresses out of BP's aielem rows to show that same-size
allocations land in different relative orders in two processes. The same rows in this lane's two
runs extend that, and add one negative result that matters for stage 2.
Player[32]'s list 3 — nineteen nodes allocated in one pass — low 16 bits of the first eight, in emission order, across four processes:
| run | visit order | first eight list-3 node addresses |
|---|---|---|
| L | 384 first | 6338 6238 6358 6378 6418 64b8 63b8 6458 |
| A | 384 first | f838 f538 f898 f4f8 f518 fa38 fc98 0378 |
| BR | 384 first | 2f78 2df8 3038 3018 3078 2cd8 3a78 c458 |
| B | 80 first | e1c0 e320 e200 e380 e460 e500 e260 e4e0 |
Four processes, four different relative orders, for an allocation sequence that is identical (identical decisions, identical block structure). §2.2's precondition is reproduced on four processes, and L vs A shows it holds even between two processes whose autosaves are byte-identical.
And the negative: the emitted command nodes' relative address order does not track the visit
order. For list 10's four elements, idx1 < idx0 in BR and in B (different visit orders) and
idx1 > idx0 in A and in L (same visit order as BR). So you cannot read the visit order off the
block's own node addresses — which is expected, since those nodes are allocated after the visit
decision, but it is worth stating because it closes the cheapest imaginable shortcut. Stage 2 must
hook the walked container; the dump cannot substitute for it.
4. Verdict
4.1 Which hypothesis survives
H2 — "a foreign RNG draw sets the visit order" — is DEAD, by measurement.
Zero foreign words and zero foreign calls in all three AI clients' brackets;
residual = 0, so not an inlined draw either; and the per-process global generator at0x00af6e58was touched three times in the entire process, all three at one return address0x00784f9einsideStrategyNetworkClient::OnMessage, sixteen bytes before theRunAIcall — one seed per AI client, which is precisely the word the pin overwrites. Neither of the two unnamed AI-band consumers the resolution named fired at all.H1 / H1b — an address-keyed container, or a comparator reading an uninitialised word, on the fleet-assignment path — is the only hypothesis left standing, and this run neither confirms nor distinguishes them. It does strengthen their shared precondition to four processes (§3.6) and it rules out reading the answer off the block (§3.6).
Per the brief, this lane stops here. The binary was not read and stage 2 was not started. The
follow-up the resolution names is now unblocked and unchanged: one bounded static read of
AssignFleetsAndIssueOrders 0x006c16c0 and the gathering hub to place a hook on the loop that
visits the ship groups, logging (element address, systemId, minted fleetId) in visit order, two
fresh processes. Under H1 the visit order equals ascending element address in both and the two
processes' address orders differ exactly where their visit orders differ; under H1b it tracks
neither.
On the caution the resolver put on the record: it does not apply. The probe run produced BP's
e913ff41…, not a third file, so this lane is in neither the k ≥ 3 case nor the
instrument-perturbation case; and the second run (§4.2) produced BP's 724528ff…. The disambiguation
run was therefore not needed and was not performed. No pinned file was compared with any of lane
AD's unpinned files.
4.2 The Rung B record — run 2, taken while the guest was warm
Config shim.cfg.brdeep = shim.cfg.bppin with one non-comment line changed
(aiorders.deep=off → on); airng stays off, which is bppin's own value. Verified in the log:
aicfg words=12 deep=1 research=0 seed=pin pins=4 detours=2, three pinned=1, no airng: lines,
no unknown keys.
Hash: (Autosave).sav = 724528ffde9a7514b69ab9e82ed3b8c8ade63d622c0720de30503a73a92df11e,
101,370 B — byte-identical to lane BP's run B, verified with cmp. Pre-turn autosave
16f63adb… as always. So the resolution's stated target ("if it lands on e913ff41…") was not
hit; it landed on the other branch. That does not cost the record anything, and §6's own logic
is why: the residual input is written into the block, so the record is complete as
(ad-turn27-two-raiders.sav, load → one End Turn, seeds 32=156ebbbd / 496=fe7b2826 / 512=0ed341d1, visit order = list 10's order in the captured deep block, deep block, → 724528ff…). It is the
campaign's first replayable rich-turn record — thirteen ships completing, a design created, a
system colonised — and it is on the branch that lane BP's bp-pinB-turn28.sav already holds.
Does the deep dump follow list 10's ship vectors and list 8's routes? Yes, both, and list 1's strings as well:
aistr blk=1 pid=32 list=1 idx=0 at=w2 sso=1 len=14 text="Egg Thief Mk 4"
aistr blk=1 pid=32 list=1 idx=1 at=w2 sso=1 len=14 text="Egg Thief Mk 5"
aistr blk=1 pid=32 list=1 idx=2 at=w2 sso=1 len=14 text="Bravestar Mk 3"
ailist blk=1 pid=32 list=8 size=3
aielem ... list=8 idx=0 ints=[ 2002 ... ] aivec ... at=w1 cap=1 count=1 ints=[ 256 ]
aielem ... list=8 idx=1 ints=[ 1970 ... ] aivec ... at=w1 cap=1 count=1 ints=[ 384 ]
aielem ... list=8 idx=2 ints=[ 1538 ... ] aivec ... at=w1 cap=1 count=1 ints=[ 240 ]
ailist blk=1 pid=32 list=10 size=4
aielem ... list=10 idx=0 ints=[ 80 1970 ... ] aivec ... at=w2 cap=1 count=1 ints=[ 5264 ]
aielem ... list=10 idx=1 ints=[ 384 1986 ... ] aivec ... at=w2 cap=1 count=1 ints=[ 6976 ]
aielem ... list=10 idx=2 ints=[ 384 2002 ... ] aivec ... at=w2 cap=1 count=1 ints=[ 6992 ]
aielem ... list=10 idx=3 ints=[ 816 7072 ... ] aivec ... at=w2 cap=11 count=11 ints=[ 2992 3456 4304 4912 5680 3184 5856 6000 6256 6464 2720 ]
35 aivec rows and 3 aistr rows across the block; list 3 and list 12 carry their vectors too
(list 12's twelve elements resolve 3, 15, 1, 5, 4, 6, 20, 22, 11, 7, 1 and 7 ship ids). Under the
noise mask the deep run's shallow words are element-for-element identical to BP's run B, so
deep=on is confirmed to be additive: it adds followed rows, it does not disturb the block.
Two cross-checks the deep dump makes possible for the first time, both pass. (1) The block's
ship↔fleet pairing matches the save it produced: in this run list 10 gives fleet 1970 ← ship 5264
at system 80 and fleet 1986 ← ship 6976 at system 384, and bp-pinB-turn28.sav is exactly the
save in which Flt[1970] holds Ship[5264]. The resolution's "the visit order is written into the
block" is now verified end to end, block → save. (2) The move goes to the group at system 80 in
both orders: list 8 idx=1 is fleet 1970 with route [384] here, and fleet 1986 with the same
route in run A — and 1970-here and 1986-there are both the system-80 group. That is the resolution's
§3 qualification ("on ad-turn27 the move in list 8 went to the group at system 80 in both orders")
confirmed from the command stream rather than inferred.
4.3 The outcome set, after five processes
| file | processes | visit order |
|---|---|---|
e913ff41… |
3 — BP's L (unpinned), BP's A (pinned), BR's probe (pinned + airng) |
384 first |
724528ff… |
2 — BP's B (pinned), BR's deep (pinned + deep) |
80 first |
No third file in five processes. The outcome set is still observed at 2, now with both members
reproduced at least twice and across three different instrument configurations. That is not a proof
that k = 2 — a binary order over two groups makes 2 the natural size, but nothing here rules out a
turn where more groups make it larger — and it is enough to say the residue is a small,
enumerable order rather than an unbounded scramble, which is what C-set's shape needs.
4.4 What this lane did NOT establish
- Nothing about a draw taken before
OnResumePlayingis entered whose value is carried into the visit order. The bracket is an exact bracket around the turn, not around the client's life. The global generator's constancy (3 words, lifetime, one site) is what actually closes that door, and it closes it for that generator only. - Nothing about the container's identity. §3.6 shows the block cannot tell you.
- Nothing about a turn other than this one, a route other than load, or a build other than
bp-989c692. - The
0x0069dbb0fuse's turn-time behaviour remains untested (it fired on the load path here, which is a different statement). - The
0x00784f9eattribution is an inference from lane AI1's recorded call-site offset plus a count that matches the client count exactly. It is one instruction away from being a measurement and this lane did not take that step, by instruction.
5. Guest notes for the next lane (VM146)
- The lab wall's cached frame was avoided entirely, and there is a better instrument. A direct
QEMU screendump —
echo 'screendump /tmp/x.png -f png' | qm monitor 146on spicy, thenscp— returns the live framebuffer with no poll cycle in it. Twelve screenshots this session, zero ambiguous frames, zero wasted clicks. BP's "wait 12–16 s" is the right advice for/shot/<id>.pngand is unnecessary if you take the screendump yourself. (tools/vmshot.pycannot be used from this host: it imports PIL and PIL is not installed here.) bpshot.ps1readsC:\SOTS\ui\log.txtafter a 3-second sleep, and the click-helper task writes that file at the END of its batch. So for any batch longer than ~3 s the log you get back is the previous batch's — it printed a timestamp 37 minutes stale on my first call. Harmless once you know; read the log in a separate call, or ignore it and screenshot.- Load-dialog geometry confirmed unchanged: with the two autosaves deleted the list is 7 rows
from y=260 at 29 px pitch and
ad-turn27-two-raidersis row 1 at (400,260). Path: Load Game (512,536) → Single Player (512,290) → OK (551,523) → row (400,260) → OK (682,624) → Launch (511,663) → End Turn (100,714). One click per helper call. - Timings, both runs (§2.3): main menu ~2 min 45 s; turn-27 load 3 min 20 s then 1 min 55 s; End Turn ~4 s.
- Released restored, hash-verified:
SavedGamesback to the 9-file pre-BR set with both autosaves byte-identical toC:\SOTS\ui\preBP\(a3f9dc4b…,d59bb9f2…),shim.cfg=hooks=off,binkw32.dll=07c962946d93c2cfef336803ac041150bda2640c05980b266902b1aa1a618359(the pre-BP file),shim.trace.jsonlremoved, game process not running — exactly as found. Left in place:C:\SOTS\shimdist-bp\shim.cfg.brprobeandshim.cfg.brdeep, andC:\SOTS\ui\br-probe-turn28{,-pre}.sav,br-deep-turn28.sav,br1-{airng,aiorders,shim.log},br2-{aiorders,shim.log}.click_helper.ps1,bpdeploy.ps1,bpshot.ps1,bphash.ps1untouched.
Artefacts: verify/results/shim/br/ — BR-shim.log, BR-airng.txt, BR-aiorders.txt,
BR2-shim.log, BR2-deep-aiorders.txt, aiorders-diff.txt (all four comparisons including the two
self-tests), cmp_aiorders.py (the masked comparator), shim.cfg.brprobe, shim.cfg.brdeep. The
two output saves are byte-identical to verify/results/saves/bp-pinA-turn28.sav and
bp-pinB-turn28.sav and are deliberately not duplicated in the repo.
Proposed board rows
New row:
| RNG IS EXCLUDED BY MEASUREMENT FOR THE AI VISIT ORDER - the global generator is drawn THREE TIMES IN A PROCESS, all three to mint the AI client seeds | control-flow | verified | high | 100% | 2026-09-09 | Lane BR, VM146, the single probe of `findings/resolutions/2026-09-09-fleet-id-order-residue.md` section 4 (`findings/control-flow/ai-visit-order-probe.md`). No build: lane BP's dist `bp-989c692-20260909T0505Z`, `shim.cfg.brprobe` = `shim.cfg.bppin` with EXACTLY THREE non-comment lines changed (`airng=on`, `airng.pin_seed=off`, `airng.out=`), diffed twice - once file-to-file on the guest, once from the two runs' runtime `config:` lines, both giving the same three lines. 28 of 28 registered hooks off BY NAME, zero unknown keys, zero COVERAGE lines, `aiseed call=1/2/3 pinned=1`, no wildcard hit, `airng: bracket ... MH_OK` + `pin_seed=off`. Route: LOAD. RunAI net-id list: 32/496/512 (three clients; the four NPC factions get none) - confirmed a second way by the bracket firing 5x (pid 16 human agent=0, then 32/496/512 with agents, then 16). **P1 HOLDS: `(Autosave).sav` = `e913ff41...`, BYTE-IDENTICAL to lane BP's runs A and L** (state_checksum IDENTICAL, root 8100b289..., 61147 leaves; vs BP's B, the same 35-leaf transposition, leaf for leaf; pre-turn `16f63adb...` IDENTICAL). Not a third file, so the resolver's ambiguity caution does not fire - AND this is the airng detour's own rule-26(d) control: an instrumented process reproduced an unpinned natural run byte for byte, so `airng`'s behavioural neutrality is now MEASURED on this state, not argued from roll-parity.md. **P2 HOLDS, AND HARDER THAN PREDICTED. `foreign_words=0 foreign_calls=0 residual=0` in ALL THREE AI brackets** (pid 32: left_delta=79 observed=79 calls=75 sites=4; pid 496 and pid 512: left_delta=0, observed=0 - they run a full AI turn and DRAW NOTHING, rule 20's design working). left_delta == observed on pid 32 so there is NO INLINED DRAW on the AI path either, confirming lane I's inventory from the other side. **AND THE LIFETIME CENSUS CLOSES IT: the per-process global generator 0x00af6e58 (runtime 0x00cd6e58) was drawn EXACTLY 3 TIMES, 3 WORDS, FROM ONE RETURN ADDRESS, `va=0x00784f9e`, entry NextUInt - and the count is 3 at every seq from 2 to 5, i.e. it does not move during the turn at all.** `0x00784f9e` is INSIDE `StrategyNetworkClient::OnMessage 0x00784640` and sits 16 BYTES BEFORE the `RunAI` call site that lane AI1 recorded at OnMessage+0x96e = 0x00784fae, and it fires exactly three times for exactly three AI clients at the same log timestamps as the three `aiseed call=` lines: **it is the AI client seed mint - board row 326's mechanism, measured live with its consumer address, generator address and call count, and it is the ONLY thing that generator does in the whole process.** (Inference on the identification, one instruction from a measurement; this lane was instructed not to read the binary.) The two unnamed AI-band consumers the resolution named as H2 candidates (0x0079ea90+0x73, 0x005b9f00+0xc0) DO NOT APPEAR IN THE CENSUS AT ALL. **VERDICT: H2 IS DEAD. H1/H1b (an address-keyed container or an uninitialised-word comparator on the fleet-assignment path) IS THE ONLY HYPOTHESIS LEFT.** **P4 HOLDS in its strong form**: under BP's L/A-proven noise mask (plus unset `aiblk` gate payloads, lane L4's class), BR vs run A = 9 blocks / 81 elements / identical key sets / identical per-list sizes / identical SET gates and **NOT ONE differing word outside the noise set**; BR vs run B = **EXACTLY the three transposition words** (list 8 idx1 w0 1986/1970, list 10 idx0 w0 384/80, list 10 idx1 w0 80/384). The comparator was self-tested against BP's published A/B and L/A results first. **P3 FALSIFIED** (it was labelled not load-bearing): pid 32 spent 79 words, not single digits - rule 23's corollary again, a bracket total is a sum over every subsystem in it. INCIDENTAL, and it is a rule-28 practice-4 case: **the 3-40-turn fuse consumer 0x0069dbb0 DOES draw - on the LOAD path, before the bracket, on each client's own (pinned) generator** (census 0x0069dc02 and 0x0069dc2e: 2+1 words for client 32, 1+1 for 496 and 512, all inside life_in). Its turn-time behaviour is still untested. FREE MEASUREMENT FROM THE DUMPS: section 2.2's LFH precondition now holds on FOUR processes (list-3 node addresses in four different relative orders for an identical allocation sequence - including between L and A, whose autosaves are byte-identical), AND a negative that matters for stage 2 - **the emitted command nodes' relative address order does NOT track the visit order** (BR and A share a visit order and differ in node order; BR and B share a node order and differ in visit order), so the block cannot substitute for the container hook. NEXT (unchanged, now unblocked): one bounded static read of AssignFleetsAndIssueOrders 0x006c16c0 + the gathering hub TO PLACE A HOOK on the group walk, log (element addr, systemId, minted id) in visit order, x2 processes. Artefacts: `verify/results/shim/br/` |
| THE FIRST REPLAYABLE RICH-TURN RECORD - a deep AI command block for ad-turn27, on the 80-first branch | control-flow | verified | high | 100% | 2026-09-09 | Lane BR run 2, VM146, the Rung B record of `findings/resolutions/2026-09-09-fleet-id-order-residue.md` section 6 (`findings/control-flow/ai-visit-order-probe.md` section 4.2). `shim.cfg.brdeep` = `shim.cfg.bppin` with ONE non-comment line changed (`aiorders.deep=off` -> `on`); `airng` stays off. Verified: `aicfg words=12 deep=1 research=0 seed=pin pins=4 detours=2`, three `pinned=1`, zero unknown keys. **The record: (`ad-turn27-two-raiders.sav` 1c8baa27..., LOAD -> one End Turn, seeds 32=156ebbbd/496=fe7b2826/512=0ed341d1, build bp-989c692-20260909T0505Z, visit order = list 10's order in the captured block) -> pre `16f63adb...`, post `724528ffde9a7514b69ab9e82ed3b8c8ade63d622c0720de30503a73a92df11e`** - BYTE-IDENTICAL to lane BP's run B (`cmp`, not just the hash). The resolution's stated target was `e913ff41...`; it landed on the OTHER branch and **that costs the record nothing**, because the residual input is written into the block (section 6's own logic): list 10 records the visit order on every captured run. THE DEEP DUMP FOLLOWS EVERYTHING IT WAS SUPPOSED TO: 35 `aivec` rows and 3 `aistr` rows. **List 10's ship vectors** - idx0 (80,1970)<-[5264], idx1 (384,1986)<-[6976], idx2 (384,2002)<-[6992], idx3 (816,7072)<-[11 ship ids]. **List 8's routes** - (2002)->[256], (1970)->[384], (1538)->[240]. Plus list 1's names as strings ("Egg Thief Mk 4", "Egg Thief Mk 5", "Bravestar Mk 3"), list 3's and list 12's vectors (12 elements resolving 3/15/1/5/4/6/20/22/11/7/1/7 ship ids). Under BP's noise mask the deep run's shallow words are ELEMENT-FOR-ELEMENT IDENTICAL to BP's run B, so `deep=on` is additive and does not disturb the block. **TWO CROSS-CHECKS NOW POSSIBLE FOR THE FIRST TIME, BOTH PASS: (1) the block's ship<->fleet pairing matches the save it produced** - list 10 gives fleet 1970 <- ship 5264 at system 80, and `bp-pinB-turn28.sav` is exactly the save where Flt[1970] holds Ship[5264]; the resolution's "the visit order is written into the block" is now verified END TO END, block -> save. **(2) The list-8 move goes to the group at system 80 in BOTH orders** (fleet 1970 with route [384] here; fleet 1986 with the same route in run A - both are the system-80 group), which confirms the resolution's section 3 qualification from the command stream instead of by inference. OUTCOME SET AFTER FIVE PROCESSES ON THIS STATE: `e913ff41...` x3 (BP's L unpinned, BP's A pinned, BR's probe pinned+airng, all 384-first), `724528ff...` x2 (BP's B, BR's deep, both 80-first). **NO THIRD FILE IN FIVE PROCESSES, ACROSS THREE INSTRUMENT CONFIGURATIONS**, with both members reproduced at least twice - the residue is a small enumerable order, which is what C-set's shape needs. Not a proof that k=2. Artefacts: `verify/results/shim/br/BR2-deep-aiorders.txt`, `BR2-shim.log`, `shim.cfg.brdeep`; the save is byte-identical to `verify/results/saves/bp-pinB-turn28.sav` and is deliberately not duplicated |
Edits to existing rows:
- Row 62 — VM146: lane BR held it 2026-09-09 02:21–02:47 local for the visit-order probe and
the Rung-B deep record, and released it restored and hash-verified:
SavedGamesback to the 9-file pre-BR set with both autosaves byte-identical toC:\SOTS\ui\preBP\(a3f9dc4b…,d59bb9f2…),shim.cfghooks=off,binkw32.dll07c96294…,shim.trace.jsonlremoved, game process not running, exactly as found.ad-turn27-two-raiders.savstill on disk, so BP's Load-dialog geometry still holds and was re-verified from a screenshot: 7 rows from y=260 at 29 px pitch, row 1 at (400,260). LeftC:\SOTS\shimdist-bp\shim.cfg.{brprobe,brdeep}andC:\SOTS\ui\br-probe-turn28{,-pre}.sav,br-deep-turn28.sav,br1-*,br2-*;click_helper.ps1and BP's three helpers untouched. BETTER INSTRUMENT THAN THE WALL, AND IT RETIRES BP'S FOUR-WASTED-CLICKS GOTCHA: take the screendump yourself —echo 'screendump /tmp/x.png -f png' | qm monitor 146on spicy, thenscp— that is the live framebuffer with no poll cycle in it. Twelve screenshots, zero ambiguous frames, zero wasted clicks; BP's "wait 12–16 s" is only needed for the wall's/shot/<id>.png. Notetools/vmshot.pycannot be used from the WSL host: it imports PIL and PIL is not installed. GOTCHA:bpshot.ps1sleeps 3 s then printsui\log.txt, but the click-helper task writes that file at the END of its batch — for any batch longer than ~3 s you get the previous batch's log (mine printed a 37-minute-stale timestamp on the first call). Read the log in a separate call or ignore it and screenshot. TIMINGS, TWO RUNS: main menu ~2 min 45 s (fg+key {ESC}×3 spaced 2.5 s, screenshot-verified, never slept-and-clicked); the click path ~2 min at one click per helper call; turn-27 load 3 min 20 s then 1 min 55 s; End Turn ~4 s with this hook set — BP's "~35 s" is generous and the autosave mtime is the reliable completion signal. - Row 326 (the per-process AI client seed) — append:
MECHANISM MEASURED LIVE 2026-09-09 (lane BR, VM146): the per-process global generator at 0x00af6e58 is drawn EXACTLY THREE TIMES IN THE WHOLE PROCESS, three words, from ONE return address va=0x00784f9e - inside StrategyNetworkClient::OnMessage 0x00784640 and 16 bytes before the RunAI call site lane AI1 recorded at OnMessage+0x96e - once per AI client, at the same log timestamps as the three aiseed calls. That is this row's mechanism, with a consumer address and a call count instead of an inference. It is also the ONLY draw that generator takes: its count is 3 at every airng bracket from the first AI client to the end of the turn. - Row 360 — append to the resolver's queued note:
AND THE RESIDUE IS NOT A DRAW: lane BR (2026-09-09, VM146) measured zero foreign words and zero foreign calls in all three AI clients' OnResumePlaying brackets, residual=0 (so not an inlined draw either), and the global generator motionless through the turn. RNG is excluded by measurement; an address-keyed container is the only hypothesis left. - Row 415 — append:
Lane BR reproduced the 384-first branch a third time (e913ff41..., byte-identical to BP's A and L) and the 80-first branch a second time (724528ff..., byte-identical to BP's B). Five processes, two files, no third. - Row 333 / the Rung C row — append:
PRECONDITION MET 2026-09-09 (lane BR): the per-client draw ledger is taken and it is clean - zero foreign draws in every AI bracket on ad-turn27, so the residual visit order is not an RNG input and (save, seeds, visit order) is the complete input tuple as far as any generator is concerned. - The Rung B row / backlog section 2 Rung B — append:
DONE 2026-09-09 (lane BR run 2): the first replayable rich-turn record exists. It is on the 80-first branch (724528ff..., byte-identical to bp-pinB-turn28.sav), not the e913ff41 branch section 6 named, and that is immaterial because list 10 records the visit order. The deep block follows list 10's ship vectors and list 8's routes, and its ship<->fleet pairing matches the save it produced - "the visit order is written into the block" is now verified end to end. method-rules.mdrule 26 (d) — no change proposed to the wording; note in passing that lane BR is its second application and its first successful one: the pinned+instrumented run reproduced lane BP's natural run L byte for byte, which is what licenses reading the ledger.
Docs to correct in place (rule 11), proposed, not done by this lane:
findings/subsystems/roll-parity.md—airng's behavioural neutrality can be upgraded from "two fresh processes on the published oracle" to "and it reproduced a natural unpinned run ofad-turn27byte for byte on a rich AI turn (lane BR)".guides/lab-screen-wall.md— add the direct-screendump one-liner as the recommended path for a lane that is driving a guest (the wall is for watching), and record thattools/vmshot.pyneeds PIL, which the WSL host does not have.- Any doc that still says the
0x0069dbb0fuse "has never been observed firing" — it fired on the load path in this run, on each client's own generator, three sites' worth of words insidelife_in. Its turn-time behaviour is still untested and that distinction should be written down.