sots-re/findings/control-flow/raid-target-pick-verdict.md

54 KiB
Raw Permalink Blame History

The raid target pick — a verdict on TradeManager+0x0c

  • Type: control-flow (static decode of the writer + live measurement)
  • Owner / date: lane AR · 2026-09-08 → 2026-09-09 · guest VM141 (sots-re-win10-b, 192.168.10.143) — held from the time the predictions were committed, released (§10)
  • Status: verified for the verdict, for A's per-fleet cost and for the oracle pair; one observation of the pick itself, and the coverage limits are listed in §7
  • Decides: lane AG's gate-indexed-rng-audit.md §3.3 final paragraph — per-system containing-sector table versus sector-indexed vector of six — which lane AD (raid-gate-multiplicity.md §4) measured and correctly reported it could not separate on sector TradeID 832
  • Instrument: lane Z's return-address ledger (draw_sites) + lane H's entry probes at probes=8, the byte-neutral set. Not probes=11 (lane H bisected index 8 as not byte-neutral)

1. Predictions, committed before the build (rule 2)

Everything in §1 was written and committed before the shim was built, before the workload was played and before any counter was read. It is reproduced verbatim below when the results are added; nothing in it is edited after the fact, only annotated with verdicts.

1.0 The static result this lane found first, stated as a falsifiable prediction

Lane AG's §7 lists "ServerTradeManager+0x0c's writer was not found" as the one load-bearing inference in its document. This lane found the writer on the host before touching the guest, and it decides the question on its own. It is written here as a prediction, because a live run can falsify it, and because rule 3 says static reading and behavioural comparison are different instruments and neither is trusted alone.

FUN_00841700 (this in ecx → ebx; 214 bytes, 0x00841700–0x008417d5, decoded to the next function start per rule 17) does exactly three things:

;  1. CLEAR the +0x0c vector
0084170b  lea  ecx,[ebx+0xc]                    ; &this->vec0c
0084170e  call 0x459f70                         ; vector<T*>::resize(0)  -- arg pushed at 0x841709

;  2. RESIZE it to the length of the vector at [this+4]+0x40 .. +0x44
00841713  mov  ecx,[ebx+0x4]
00841716  mov  eax,[ecx+0x44]
00841719  sub  eax,[ecx+0x40]
0084171c  mov  edx,[ebx+0x10]                   ; vec0c._Mylast
0084171f  mov  edi,[ebx+0xc]                    ; vec0c._Myfirst
00841726  sar  eax,0x2                          ; N = count([this+4]+0x40..+0x44)
00841729  sar  ecx,0x2                          ; cur = count(vec0c)
0084172c  mov  DWORD PTR [ebp-0x4],0x0          ; fill value = NULL
00841733  cmp  eax,ecx
00841735  jbe  0x841749
0084173f  lea  ecx,[ebx+0xc]                    ; grow:  _Insert_n(_Mylast, N-cur, NULL)
00841742  call 0x50e6a0
00841754  lea  ecx,[ebx+0xc]                    ; shrink: erase(_Myfirst+N, _Mylast)
00841757  call 0x4ddfc0

;  3. FILL it by walking sectors x their member systems
0084175c  ecx = this->+0x1c ; eax = (this->+0x20 - ecx)/4       ; the SECTOR vector
00841778    sect = sectors[i]
0084177f    inner count = (sect->+0x7c - sect->+0x78)/4          ; the sector's MEMBER vector
00841798    m   = sect->members[j]
008417a2    idx = m->+0x5c                                       ; the member's OWN INDEX field
008417a5    edi = this->+0x0c
008417a8    mov DWORD PTR [edi + idx*4], ecx                     ; table[idx] = sect

Three things follow, and each is checkable by a reader who disagrees:

  1. The table's length is, by construction, the length of the container that trfr indexes. FUN_00841cd0 — the function that settles what trfr means, and the one AG cites for exactly that — resolves a route's source endpoint at 0x00841d36 as mov eax,[edi+0x4]; mov ecx,[eax+0x40]; mov edx,[esi+0xc]; mov eax,[ecx+edx*4], i.e. ([this+4]+0x40)[rt->trfr]. Step 2 above resizes the +0x0c vector to the length of that same vector on that same object. So G_B1a (0 <= rt->trfr < count(this->+0x0c..+0x10)) is satisfied by every route with a valid trfr, by construction, in the same way that AG showed vslot15's G1 is satisfied by every rt in every save by construction.
  2. The slot index is a system's own index field, and the slot value is a TradeSector*. Step 3 writes table[m->+0x5c] = sect where sect came out of the sector vector and m came out of that sector's member list. That is the definition of a per-system containing-trade-sector table. FUN_00841c70 reads it back the same way (arg->+0x5c → bounds check against the same count → table[idx], then table[idx] + 0x78 — the member vector — is handed to FUN_0059ec00), which is the independent confirmation that +0x5c on the indexing object and +0x78 on the table element are the two halves of one relation.
  3. The table is rebuilt on load. FUN_00841700 has five callers; one of them is FUN_00858a10, which is full of ReadInt/ReadFloat calls and invokes FUN_00841700 at 0x00858f07 as its last real act. So a loaded save has a populated table, and this is not a galaxy-generation-only structure.

P0 — the verdict, predicted. TradeManager+0x0c is a std::vector<TradeSector*> of length numSystems, indexed by system index. AG's per-system reading is right; the sector-indexed-vector-of-six reading is false, and 0x0088b613 is therefore reachable in principle. ad-turn27-two-raiders.sav has systemIds of length 28, so the table has 28 slots and trfr ∈ {14,16,18,20} clears G_B1a with room to spare.

How P0 could be wrong, and the symptom of each way (rule 2 requires this):

way it could be wrong symptom in the run
[this+4]+0x40 is not the systems vector — some other container of a coincidentally similar length B is entered on a success and still costs 0 words
m->+0x5c is not a system index (e.g. the member list holds something else) same symptom; table[trfr] would then be null or a wrong sector, and G_B1b rejects
the members of sector 816 do not include the systems the routes are sourced at B entered, 0 words; and the save's tssys list would contradict the route's trfr
FUN_00841700 is not called on this path after all (my caller read is wrong) B entered, 0 words, table all-null

In every one of those cases the observable is the same: B entered, 0 words. That single number is the whole experiment, and it is why the run is worth taking even though the static read looks closed.

1.1 The state, read fresh from the save (rule: do not inherit a number)

Read from verify/results/saves/ad-turn27-two-raiders.sav with verify/save-reader/save_reader.py --dump, before touching the guest.

trdmgr holds FIVE rt records at turn 27, not three. Lane AD's §4 reports "three route records, trfr = 18, 14, 14"; the save has five, trfr = 18, 16, 14, 20, 14, tro = 32 (the AI) on all five, trfrs = trtos = 0 on all five. This is a correction to raid-gate-multiplicity.md §4 and to its board row (rule 11), recorded before the run rather than after.

System index ↔ system id is index = id/16 − 3, pinned from Sim.systemIds [48, 64, 80, …, 480] (28 entries, stride 16, first = 48). Applying it to each sector's tssys list reproduces lane AG's index sets for all six sectors exactly, which is the check that the mapping is right:

sector TradeID grid tsct tscr member system indices fleets present
752 1 2 253 1, 10, 20, 24, 17 6864
768 2 2 253 2, 7, 14, 21, 22, 27 6880
784 3 0 253 4, 8, 12, 25 —
800 4 0 253 6, 11, 5 —
816 5 2 253 9, 15, 16, 18, 19, 26 7072 Freighters (PID 32, 11 ships), 7088 Escorts
832 6 0 253 13, 23, 0, 3 3744 Alpha, 6544 Beta — AD's two raiders

So on the turn-27 route set:

  • sector 816 sources TWO routes (trfr 18 and 16) → candidate list n = 2, bound = 1;
  • sector 768 sources two (trfr 14 twice) → n = 2;
  • sector 752 sources one (trfr 20) → n = 1, bound = 0;
  • sectors 784, 800, 832 source none. 832 is still the one sector no route is sourced in.

Geometry, recomputed rather than inherited (node positions from trdmgr, colony positions from Sim.systems[].Sys.Pos; player 0 = PlyrIdx 0 = PID 16, HomeSys 48, CnRad true):

sector from Epsilon Eridani (idx 0) from Downbelow (idx 12) sources a route?
752 12.85 8.43 yes (1)
768 11.53 10.14 yes (2)
784 9.36 3.49 no
800 9.37 11.77 no
816 15.66 12.12 yes (2)
832 4.99 4.56 no

Reproduces lane AD's table to the last digit. Player-0 fleets at turn 27: 3744 Alpha Fleet and 6544 Beta Fleet on 832 (both DesID 17, one CR Repair and Salvage each, Range consumed to 4.0125), and 6672 Gamma Fleet at LocID 48 (home, Range 9.0).

1.2 The workload

Build several separate single-ship Extended Range destroyer fleets (stock hull, Range 27.0, cost 2,252, node speed 4.0 — no research required) and park them on sector 816's node, Pos bit-equal, for as many End Turns as the session allows. Sector 768 is a secondary target if the map allows it: it is closer (10.14 ly from Downbelow) and also sources two routes, so a fleet there is a second independent instance of the same test at no extra build cost.

Separate fleets, not one big fleet, and this is arithmetic not taste. A is per fleet (lane AD) and its odds are ODDS_DE + PER_SHIP·nShips for a destroyer fleet. One 5-destroyer fleet rolls once at 0.05 + 0.05 = 0.10; five 1-destroyer fleets roll five times at 0.06 each — expected successes 0.10/turn against 0.30/turn. Three times the signal for the same ships.

1.3 The numbered predictions

# prediction
P0 (above) +0x0c is the per-system containing-sector table; AG's reading is right and the sector-indexed reading is dead
P1 TradeManager::Slot13RngCalleeA is entered once per parked permitted fleet per turn and costs 1 word each — lane AD's per-fleet result, re-confirmed on a different sector and a different hull
P2 On a turn A succeeds for a fleet on 816, Slot13RngCalleeB is entered and reaches 0x0088b613, costing ≥ 1 word — the number lane AC and lane AD both measured as 0
P3 B's full cost on such a success is 2 words: NextInt at 0x0088b613 (exactly 1 — RNG_NextInt has no zero-bound early-out, and with bound = 1 the mask is 1 so no rejection is possible either) plus NextFloat at 0x00820c1b inside FUN_00820af0, unless that function's species short-circuit fires (owner->+0x5c == 1, or either of the two SpeciesDef tests)
P4 Therefore OnAllCombatDone_Tail costs k + 2s words on a turn, where k = parked permitted fleets and s = successes among them. With one raider that is 1 on a failing turn and 3 on a succeeding turn — AG's committed number in §3.3, tested
P5 draw_sites shows a strategic row at call 0x0088b613 (NextInt) with calls == words == s, and one at 0x00820c1b (NextFloat) with calls == s, words == s − (species short-circuits). Both are sites no lane has ever fired; 0x00820c1b is one of the six lane AG added to the inventory
P6 0x00820e18 (A's NextFloat) shows calls == words == k, reproducing AD's per-fleet pricing on a hull and a sector AD did not use
P7 The hooks=off control will not agree with itself — I expect AD's failure to repeat, because it is the per-process client-AI seed and the AI is larger by turn 33 than at 27, not smaller. I predict the variation is again confined to Player[32]'s designs / build queues / fleet ids, and that /Sim/RNG and /Sim/trdmgr are bit-identical across all runs. If a /Sim/trdmgr leaf, a trade-sector leaf or a player-0 fleet leaf moves, my numbers are not off a reproducible sub-tree and I will say exactly that instead of reporting them
P8 If A is entered k times but B is never entered across the run, that is not a negative — it is a run of failed Bernoulli trials, and I will report the exact binomial probability rather than the word "unremarkable" (rule 20)

1.4 The falsifier, stated as the sentence I will have to write

If a raider is parked on sector 816 with Pos bit-equal, A succeeds for it, B is entered, and B still costs 0 words — then P0 is false, TradeManager+0x0c is not the per-system table, lane AG's §3.3 decode is retired, and the campaign loses a finding it is currently leaning on.

That is a real result and it is the one I am most interested in, because it would mean a careful static read of the writer produced a confident wrong answer — which is rule 3's warning pointing the other way from its usual direction.

1.5 What this lane will not be able to settle, predicted in advance (rule 15)

  • The freighter doubling. Sector 816 holds the AI's Freighters fleet, so param_2 is finally testable in principle — but separating p = 0.06 from p = 0.12 needs on the order of a hundred fleet-rolls. At the sample size a session affords, the success count is consistent with both and I will report it as consistency, not validation.
  • The design+0x12c > 1 short-circuit. Still unpriced; the Dreadnought category was empty for lane AD and nothing in this workload changes that.
  • bestScale. 0x009f8d48 = 0.33f applies when ship+0x18 & 0x100000. I will read the destroyer's flag from the save rather than assume it is clear.
  • A rung-A calibration pair. P7 predicts there is none to be had on this lineage.

Results, and the verdict, follow below. Nothing above this line is edited after the run.


2. The verdict

TradeManager+0x0c is the per-system containing-trade-sector table. Lane AG's §3.3 reading is correct; the sector-indexed-vector-of-six reading is dead.

Two independent instruments agree, and they are the two rule 3 names.

Static. The writer AG's §7 lists as not found is FUN_00841700. It resizes the +0x0c vector to the length of the systems vector — the same container FUN_00841cd0 indexes with rt->trfr — and fills it with containingSector[system.Idx] = sector. G_B1a cannot reject a valid trfr.

Behavioural. On turn 42 of the instrumented run, with a raider parked on sector TradeID 816 and Pos bit-equal, TradeManager::Slot13RngCalleeB was entered once and the return-address ledger priced it: NextInt at call 0x0088b613, calls = 1, words = 1. OnAllCombatDone_Tail cost 5 words on that turn against 4 on every other turn of the same run. Bracket residual 0.

And the same run carries its own control. On turns 38 and 40 Slot13RngCalleeB was entered once each — by a cruiser parked on sector TradeID 832 — and cost 0 words, exactly as lane AC and lane AD measured. Same process, same build, same instrument, same function, same turn structure; the only difference is which sector the raider stands on. That is the cleanest available demonstration that AC's and AD's proof-carrying zero is a fact about the sector, and that the code path itself is live.

0x0088b613 had never fired in this campaign. It has now.


3. What was run

Four fresh processes on VM141, all on the same guest with the same dist (C:\SOTS\shimdist-ar, BUILD_ID ar-989c692-20260909T0313Z, built in /srv/re-lab/build/sots-engine-ar after ssh spicy 'rm -rf …' of the whole tree, from a lane-private git worktree at ~/sots-engine-ar with no local build* shipped — rules 21 and 24) and the same input save.

run config End Turns purpose
play-forward shim.cfg.hoff 10 (turn 27 → 37) build the workload
R1 shim.cfg.hp8 (probes=8) 6 (turn 37 → 43) the measurement
OA shim.cfg.hoff 1 (turn 37 → 38) control
OB shim.cfg.hoff 1 (turn 37 → 38) control, second fresh process

Instrument armed and verified from shim.log, not assumed (rule 1): all 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 config: ignoring unknown key lines. Probe indices 8–11 report NOT INSTALLED, never 0 — index 8 is GenerateTradeRaidEncounters, the detour lane H bisected as not byte-neutral, excluded by construction. shim.cfg.hp8 was used unmodified.

3.1 The workload, and what it cost

From ad-turn27-two-raiders.sav: five stock Extended Range destroyers queued at Epsilon Eridani (Build screen confirms Range 27.0, Construction Cost 2,252, Speed (Node Speed) 0.2 (4.0) — lane AD's costing is exact), completed over turns 28–31, then split into four separate fleets and each ordered to sector 5's node. They arrived on turn 35; the state was saved on turn 37 as verify/results/saves/ar-turn37-816raiders.sav (sha256 b6f4e05ff226eabd1695003b36293553642553cea96ef417364393e2332a7094).

Three UI facts the next lane should have, because each cost this lane time.

  1. A new ship joins an existing fleet at the colony, and a fleet's Range is the minimum over its ships. The five 27-ly destroyers landed in Gamma Fleet alongside AD's 9-ly Repair and Salvage cruiser and the fleet read Range: 9.0 (9.0) — sector 816 is 15.66 ly away and was not a legal destination until the cruiser was split out. Split the cruiser off first, then read the range.
  2. Splitting is a right-click context menu on a ship row in the map's fleet panel, not the Manage Fleets screen (which offers only Rename Fleet and "Fleet Layout Not Available" here). The menu is Add To New Fleet / Split Ships / Add Note, and its first item sits +15 px below the click. The click helper has no right-click verb, but rdrag x y x y — a right-drag of zero length — works as one, and that is how every split in this run was made.
  3. A trade-sector node is a legal move target even when its cube is not drawn. Lane AD reported that only sector 6's cube renders at any zoom, and that is still true. It does not mean the other nodes are unreachable: in Move mode the hover readout names them (Sector 5 / Range: 15.7 / ETA: 4 Turns) and a plain click commits. Two known screen points are enough to fit screen = (A·wx + B·wz + C, D·wx + E·wz + F) from the save's world coordinates and land the cursor on any node first try; this run fitted it from Epsilon Eridani and sector 800 and hit sector 816 at (825, 444) on the first probe. Hovering near a sector also makes its cube appear, which is the confirmation that the aim was right.

3.2 The gate, read from the save for the measured turns (rule 28)

ar-turn37-816raiders.sav, player 0 = PlyrIdx 0 = PID 16, CnRad true:

fleet id LocID Pos vs sector Pos rolls A?
Alpha Fleet (1 CR) 3744 832 [0, 0, 8.0] — bit-identical yes
Beta Fleet (1 CR) 6544 832 [0, 0, 8.0] — bit-identical yes
Epsilon Fleet (1 DE) 49 816 [8.307682991027832, 0.05633879080414772, 0.4591276943683624] — bit-identical yes
Eta Fleet (1 DE) 81 816 same three floats — bit-identical yes
Zeta Fleet (1 DE) 65 816 [9.2771635055542, 0.03959554061293602, 1.8529905080795288] — not the node no (G4 fails)
Delta Fleet (1 CR) 33 48 home no

Trade[816]: tsct 2, tscr 253, tsnumflt 5, tsflt 49, 65, 81, 8752, 8768 — my three plus the AI's. tsct = 2 is the AI's bit, so TradeSector_PlayerTradesHere is false for player 0 and no FtFlg & 0x800 is required, exactly as lane AD read it; every fleet above carries FtFlg 4.

Four qualifying fleets, and Slot13RngCalleeA was entered exactly four times on every turn — which is both the confirmation of lane AD's per-fleet result on a new sector and a new hull, and the proof that the two 816 destroyers clear G0–G4.

trfr re-read on the state actually built, as instructed. trdmgr at turn 37 holds eight rt records — not lane AG's four at turn 22 and not lane AD's three at turn 27 — with trfr = 18, 16, 14, 20, 14, 20, 14, 25, tro = 32 and trfrs = trtos = 0 on all eight. Mapping each through the sector membership lists: sector 816 sources two of them (trfr 18 and 16), 768 sources three, 752 two, 784 one, and 832 still sources none. So the candidate list on 816 has n = 2, bound = n − 1 = 1, the mask is 1 and no rejection is possible — the site costs exactly one word, which is what it cost.

Correction to lane AD (rule 11). raid-gate-multiplicity.md §4 reports *"three route records, trfr = 18, 14, 14"* in ad-turn27-two-raiders.sav. That save holds **five**, trfr = 18, 16, 14, 20, 14. Read directly with save_reader.py --dump` before this lane touched the guest. The consequence matters: sector 816 already sourced two routes at turn 27, not one.


4. The numbers

4.1 Per turn, all three instruments side by side

A = TradeManager::Slot13RngCalleeA (probe 5) · B = Slot13RngCalleeB (probe 6) · "tail" = OnAllCombatDone_Tail's word delta from the boundary ledger · 0x0088b613 = the raid target pick, from draw_sites return-address attribution.

turn A entries 0x00820e18 calls/words B entries 0x0088b613 tail words bracket residual
38 4 4 / 4 1 — 4 0
39 4 4 / 4 0 — 4 0
40 4 4 / 4 1 — 4 0
41 4 4 / 4 0 — 4 0
42 4 4 / 4 1 calls = 1, words = 1 5 0
43 3 3 / 3 0 — 3 0

Read the two rows that matter together. Turns 38 and 40: B entered, tail 4 — B cost 0. Turn 42: B entered, tail 5 — B cost 1, and the ledger names the site. The difference is which sector the successful raider stood on, and nothing else moved: A's row is calls = words = 4 on all five of those turns and the bracket residual is 0 on every one.

Turn 43's A = 3 is not a defect: Epsilon Fleet had been displaced off the node by its own successful raid on turn 42 (§4.3), so only three fleets satisfied G4.

4.2 B costs one word on a success, not two — and why

No 0x00820c1b row appears on turn 42. FUN_00820af0 was called (it always is, immediately after the pick) and returned without drawing. That is the species short-circuit, and this lane pinned which disjunct fires, from the image:

FUN_00820af0:
  if (routeOwner->Species == 1)                                  frac = 1.0   ; AI here is Species 2 -- no
  if (StarFleet_GetCrewSpeciesForIntercept(fleet) == 1)          frac = 1.0   ; raider is Species 0 -- no
  if (SpeciesDef_HasInterceptFlag(Get(that same species)))       frac = 1.0   ; <-- THIS ONE
  else frac = NextFloat()                                                     ; 0x00820c1b, 1 word

Get 0x00545cc0 returns 0x00b10a00 + k*0x184; InitTable 0x005453a0 fills element k at that same address and, at 0x0054562f/0x00545638, sets elem->+0x144 |= 1 when k == 0 and |= 2 when k == 5 — and for no other species. (Both of those addresses were already named in ghidra/addresses.json as SpeciesDef_Get and g_SpeciesDefTable, from handoff/tech-effects.md. This lane derived them independently from the FUN_00820af0 call chain and the generator's duplicate-name check is what surfaced the agreement — rule 14 working as designed. Only the +0x144 flag word is new, and it is the fragment's one data entry.) SpeciesDef_HasInterceptFlag 0x0053baf0 is twelve bytes: xor eax,eax; cmp [ecx+0x144],eax; setne al; ret. So the flag is set for Human (0) and Zuul (5) and clear for the other five.

A Human or Zuul raider intercepts at frac = 1.0 and draws nothing; every other species draws a NextFloat at 0x00820c1b.

This corrects two committed numbers, and the corrections point the same way (rule 11):

  • lane AG §5.2, "B's cost on success is 1 (NextInt) + 1 (NextFloat)" — true for five of the seven species, false for the two whose flag is set, and the player in this lineage is one of them;
  • lane AG §3.3's committed prediction, *"3 words on a turn it succeeds (A 1 + B's NextInt 1
    • FUN_00820af0's NextFloat 1)"* — the measured figure for one raider is 2. My own P3/P4 carried the same error with a hedge on it; the hedge was the right half.

The general model, then, and it is a model of the state, not a constant (rule 20):

OnAllCombatDone_Tail costs k + s + z words, where k is the number of permitted raiders parked on a trade-sector node with Pos bit-equal, s is how many of their raid rolls succeeded on a sector that sources at least one raidable route, and z is s again for a raider whose species does not set SpeciesDef+0x144. Measured here at k + s with k = 4, 4, 4, 4, 4, 3 and s = 0, 0, 0, 0, 1, 0.

4.3 A success moves the raider off the node — so a fleet raids at most once per parking

Slot13RngCalleeB writes fleet->Pos = out at 0x0088b672, after the pick. G4 then fails for that fleet on the following turn, because Pos is no longer bit-equal to the sector's. This is visible in the saves and it explains turn 43's A = 3:

fleet Pos at turn 37 Pos at turn 43
Eta Fleet (81) the node, bit-equal the node, still bit-equal
Epsilon Fleet (49) the node, bit-equal [7.13520622253418, 5.090692520141602, -2.1875174045562744] — displaced, LocID still 816
Alpha/Beta (832) the node, bit-equal the node, still bit-equal

The 832 raiders are never displaced, because on 832 B returns at G_B4 before the Pos write. So the within-run control is durable by construction and the 816 arm exhausts itself — which is a real constraint on this workload and the reason the run has one firing of the pick and not six.

4.4 Two more of lane AG's unread gates opened, on turn 43

turn 43   * STRAT NextInt    call 0x008939ee  FUN_008938a0+0x14e   calls= 1  words= 1
          * STRAT NextFloat  call 0x00820c1b  FUN_00820af0+0x12b   calls= 1  words= 1
          probe: ServerTradeManager::CreateRaidEncounter  calls=3
  • 0x008939ee fired. That is the site behind CreateRaidEncounter's empty-candidate-list gate — lane H's measurement of it (entered 2× on Zuul turn 24, 1× on turn 25, 0 words every time) is the campaign's case study for rule 20, and tail-rng-ledger.md has carried it as a zero ever since. The candidate list is not always empty, and the first non-zero reading of that site is in this trace.
  • 0x00820c1b fired, and not through Slot13RngCalleeB — B was entered 0 times on turn 43. It came through the other caller, 0x0082cf65 inside the CreateRaidEncounter subtree, i.e. lane AG's §5.1 chain CreateRaidEncounter → 0x00892640 → 0x0082ce00 → FUN_00820af0. Both of AG's §5.1 and §5.2 readings of that function's two call sites are therefore confirmed live, from opposite directions.

4.5 The odds arithmetic — consistency, not validation (rule 15)

arm hull odds per fleet-turn rolls expected observed
sector 832 1 CR (double)0.2f + (double)0.01f = 0.21000000275671482, no freighter 12 2.52 2
sector 816 1 DE (double)0.05f + (double)0.01f = 0.06, doubled to 0.12 if the AI's Freighters fleet (id 7072, LocID 816) satisfies param_2 11 0.66 / 1.32 1

The freighter doubling is still not decided and I said in advance it would not be. One success in eleven rolls sits comfortably inside both hypotheses; separating 0.06 from 0.12 needs on the order of a hundred rolls, and a fleet retires itself from the 816 arm the moment it succeeds (§4.3), which is the structural reason this workload cannot cheaply accumulate them. The honest statement is that the observed rate is consistent with the read constants and validates neither.

4.6 The news event B posts, named

Slot13RngCalleeB's tail loop formats two strings held at ds:0x00af0b5c and ds:0x00af0b64. Resolving their GlobalConst registrations in FUN_009bf960 (0x009bfcf0, 0x009bfd10) names them EVENTSUM_SECTOR_RAIDERS_DETECTED and EVENTMSG_SECTOR_RAIDERS_DETECTED, and each of those two globals is referenced from exactly one function in the whole image — 0x0088b440, B itself. So that event pair is a signature of the raid target pick having fired, visible in the news feed without any instrument at all. It is posted to every player whose bit is in sector->tssec, which is why the raider does not see it.


5. Verdicts on the committed predictions

# prediction verdict
P0 +0x0c is the per-system containing-sector table; AG's reading right, sector-indexed reading dead CONFIRMED, by the writer statically and by 0x0088b613 firing live
P1 A entered once per parked permitted fleet, 1 word each CONFIRMED — 4/4 on five turns and 3/3 on the sixth, 0x00820e18 calls == words every turn
P2 B reaches 0x0088b613 and costs ≥ 1 word on an 816 success CONFIRMED — calls = 1, words = 1
P3 B costs 2 words (NextInt + NextFloat), unless the species short-circuit fires HALF WRONG, and the hedge was the right half. It costs 1. The short-circuit fires, and §4.2 names the disjunct: SpeciesDef[Human]+0x144 bit 0, set by InitTable for species 0 and 5 only
P4 tail = k + 2s; 1 word on a failing turn and 3 on a succeeding one for one raider CORRECTED to k + s — 4, 4, 4, 4, 5, 3 against k = 4, 4, 4, 4, 4, 3. Lane AG's committed "3 words on a success" is 2 for this species
P5 a draw_sites row at 0x0088b613 with calls == words == s, and one at 0x00820c1b CONFIRMED for 0x0088b613 (calls = words = 1). FALSIFIED for 0x00820c1b via B — no such row on turn 42; the site did fire on turn 43 but through CreateRaidEncounter, not through B
P6 0x00820e18 shows calls == words == k, reproducing AD's per-fleet pricing on a new hull and sector CONFIRMED on all six turns
P7 the hooks=off control will not agree with itself; the variation will be the AI's decisions; /Sim/RNG and /Sim/trdmgr bit-identical §6
P8 if A fires but B never does, report the binomial probability rather than "unremarkable" not triggered — B fired three times and the pick once

The prediction that was wrong is the useful one. P3/P4 were written from lane AG's §5.2 and they are wrong for exactly the reason rule 23 exists: the deciding value is a flag word set by a constructor for two species out of seven, and no amount of reading FUN_00820af0 alone reveals it. The measurement found it because the word count came out one short.


6. The control — and it is an oracle pair (rules 19 and 26)

P7 predicted the hooks=off control would fail to agree with itself, as lane AD's did on ad-turn27-two-raiders.sav. P7 is FALSIFIED. The control agrees, byte for byte.

Two fresh processes, same guest, same build, same shim.cfg.hoff, the same input save and the same procedure — load, End Turn, resolve the one encounter query with Auto Resolve Peacefully, End Turn:

input   ar-turn37-816raiders.sav                b6f4e05ff226eabd1695003b36293553642553cea96ef417364393e2332a7094

OA  (Autosave EndTurn).sav   Frame 38           15b99255e1f03dab3e35ab8c1ac64f221cb5aa9321f113c7a6e3d1263c3f34ca
    (Autosave).sav           Frame 39           7a8b3d5eb3a60ebac9f40646d3e4b15768a24a0af047a439ce090c7cf38e8b38

OB  (Autosave EndTurn).sav   Frame 38           15b99255e1f03dab3e35ab8c1ac64f221cb5aa9321f113c7a6e3d1263c3f34ca
    (Autosave).sav           Frame 39           7a8b3d5eb3a60ebac9f40646d3e4b15768a24a0af047a439ce090c7cf38e8b38

state_checksum.py --floats bits --mask none on the two post-turn saves prints IDENTICAL, root 237020deca931f5a180289592cece7ae, coverage: PROVED (1,173,884 bytes rebuilt == inflated; 67,239 leaves), 0 error, 0 warn.

This is a rung-A oracle pair, from a lineage that did not have one. Lane AD ran the same check on ad-turn27-two-raiders.sav and got 94 differing leaves — all of them the AI empire's designs, build queues and fleet ids — and correctly refused to call that state calibration data. Ten turns later on the same lineage, the same check comes out byte-identical.

I do not have an explanation for the difference and I am not going to invent one. What can be said precisely: AD's variation was confined to Player[32]'s decision sub-tree, and on this state that sub-tree does not vary across processes. Whether that is because the AI's choices are forced here, or because the per-process seed AD pinned enters somewhere this workload does not reach, is not settled by anything I measured. The load-bearing consequence for the campaign is narrower and solid: AD's rule-26 result is a fact about that state, not about the lineage or the engine, and a lane that treats "the AI varies per process" as a general property of this game will be wrong at least once.

Two honest limits on this pair, stated rather than glossed:

  • Two processes, not three. Rule 26's bar is "reproduced in two fresh processes and agrees with itself", which this clears; lane AD's turn1-state episode needed three to expose the variation, and a third run here would be cheap for whoever wants it.
  • One workload. Both runs took the same two End Turns and resolved the same encounter query the same way. A pair is a statement about that procedure.

6.1 Rule 19 — the same two turns with the instrument installed

Run R2: a fourth fresh process, shim.cfg.hp8 (probes=8 + the seven draw_sites detours + the boundary-ledger template hooks), same save, same two End Turns, same encounter resolution.

input                        ar-turn37-816raiders.sav   b6f4e05ff226eabd1695003b36293553642553cea96ef417364393e2332a7094

OA  hooks=off   Frame 38     15b99255e1f03dab3e35ab8c1ac64f221cb5aa9321f113c7a6e3d1263c3f34ca
                Frame 39     7a8b3d5eb3a60ebac9f40646d3e4b15768a24a0af047a439ce090c7cf38e8b38
OB  hooks=off   Frame 38     15b99255e1f03dab3e35ab8c1ac64f221cb5aa9321f113c7a6e3d1263c3f34ca
                Frame 39     7a8b3d5eb3a60ebac9f40646d3e4b15768a24a0af047a439ce090c7cf38e8b38
R2  probes=8    Frame 38     15b99255e1f03dab3e35ab8c1ac64f221cb5aa9321f113c7a6e3d1263c3f34ca
                Frame 39     7a8b3d5eb3a60ebac9f40646d3e4b15768a24a0af047a439ce090c7cf38e8b38

All three processes are byte-identical at both snapshot points, instrumented and not. The probes=8 + draw_sites + boundary-ledger instrument is behaviour-neutral on this state, at whole-save byte granularity — not "neutral on the sub-tree the measurement reads", which is the weaker statement lane AD could make, but neutral outright.

Rule 26's coincidence caveat does not apply here and it is worth saying why. AD warns that one instrumented run agreeing with one control is a 1/k coincidence when the control has an outcome set of size k. Here k = 1: the control was reproduced first and shown to be a single outcome, and only then did the instrumented run match it. That is the order the rule requires.

R2's own counters reproduce R1's turn 38 line for line — A calls = words = 4, B entered once at 0 words, OnAllCombatDone_Tail 4, bracket total 24, residual 0, all seven drawsite: and all eight probe: detours create=MH_OK enable=MH_OK, zero ignoring unknown key. So the measurement is reproducible across processes as well as neutral.

What this licenses, and what it does not. It licenses treating ar-turn37-816raiders.sav + two End Turns as a rung-A calibration pair for the standalone, which is the first one this lineage has produced. It does not license the six-turn R1 sequence as one: R1 resolved encounter queries on turns its own play produced, and that path was exercised once here, not twice.


7. Coverage, and what was not done (rule 15)

  • Six instrumented turns, one process, four qualifying fleets, two sectors, two hull classes, one species. The pick fired once. One firing is enough to decide reachability — that was the question — and it is not enough to price the site as a constant. The word count 1 is corroborated by the static read of RNG_NextInt (mask smear, bound = 1 ⇒ mask 1 ⇒ no rejection possible), which is why I am willing to state it; if the candidate list on 816 ever exceeds two, the rejection loop becomes live and the cost stops being 1 (rule 20 — do not fit a constant to one observation).
  • The k + s + z model is fitted to s ∈ {0, 1} and z = 0. z — the FUN_00820af0 NextFloat charged to B — has never been observed through B, in this lane or any other, because the only species that has ever raided in this campaign is one of the two whose short-circuit flag is set. A non-Human, non-Zuul raider is now the cheapest unexercised state in the trade half, and it is one save away: the same workload played as Tarka, Liir, Hiver or Morrigi.
  • The freighter doubling is still untested (§4.5), and the structure of the workload is why: a successful raider leaves the node (§4.3), so the 816 arm cannot accumulate rolls without re-issuing move orders every time.
  • bestScale was not read. 0x009f8d48 = 0.33f applies when ship+0x18 & 0x100000; that flag is not obviously on the wire and this lane did not chase it. A cost exactly one word per fleet on every turn regardless, which is independent of the scale.
  • The design+0x12c > 1 short-circuit is still unpriced, as it was for lane AD — the Dreadnought category is empty in this game.
  • The spy half is untouched.
  • No probe wanted probes=11. Indices 8–11 are reported as NOT INSTALLED, never as zeros.
  • One accidental loss, disclosed. Gamma Fleet (2 destroyers) and later Zeta Fleet (1) were destroyed resolving encounters during the play-forward and the measured run. Both losses are inside the workload, not the measurement: the input save is on disk and every number in §4 is read off the trace of a run that loaded it.
  • What no lane has yet done on this state: a Rung B capture. --turn-commands replay runs no AI, and this state now has an unconditional rung-A pair, which makes it the obvious candidate for the standalone's first trade-raid comparison.

8. Corrections to earlier findings (rule 11)

  1. Lane AG gate-indexed-rng-audit.md §7 — "ServerTradeManager+0x0c's writer was not found. This is the one load-bearing inference in the document and it is flagged as one." Found: FUN_00841700 (§1.0). AG's §3.3 reading is upheld and the alternative it offered is dead. The flagging discipline is what made this a one-lane job, and it is worth saying so.
  2. Lane AG §3.3's committed prediction — "3 words on a turn A succeeds" — is 2 for a Human or Zuul raider, because FUN_00820af0's third short-circuit fires (SpeciesDef[k]+0x144, set by InitTable only for species 0 and 5). §4.2.
  3. Lane AG §5.2 — "B's cost on success is 1 (NextInt) + 1 (NextFloat), minus the species short-circuit" — right about the code, and the subtraction applies to the only species that has ever raided in this campaign. B costs 1.
  4. Lane AG's row-186 correction and tail-rng-ledger.md §11.1 — "a success costs 0 or 1 further word" was corrected by AG to "at least six draws". Both sentences are now measured on the same turn: CreateRaidEncounter entered 3× on turn 43 and drew two words in its subtree (0x008939ee and 0x00820c1b, one each). The correct statement is that the cost is unbounded above and state-dependent, and the first non-zero observation is 2.
  5. Lane AD raid-gate-multiplicity.md §4 — *"three route records, trfr = 18, 14, 14"* in ad-turn27-two-raiders.sav. It is **five**, trfr = 18, 16, 14, 20, 14`. §3.2.
  6. Lane AD §5 and its board row — "there is no oracle pair for this state". True of ad-turn27-two-raiders.sav; not true of the same lineage ten turns later. §6.
  7. Lane H's CreateRaidEncounter zero (tail-rng-ledger.md, rule 20's case study) — the candidate list is not always empty. First non-zero reading of 0x008939ee is in verify/traces/ar-r1-turn38-turn43.jsonl.

9. Artefacts

what where
this document findings/control-flow/raid-target-pick-verdict.md
input state — 4 raiders on two sectors, turn 37 verify/results/saves/ar-turn37-816raiders.sav (b6f4e05f…)
probes=8 trace, six instrumented End Turns (the pick fires on turn 42) verify/traces/ar-r1-turn38-turn43.jsonl
probes=8 trace, the two control turns verify/traces/ar-r2-control-turn38-turn39.jsonl
site / ledger / probe reports for R1 verify/results/shim/ar/ar-r1-{sites,ledger,probes}.txt
shim logs (detour + probe install status) verify/results/shim/ar/ar-r{1,2}-shim.log
oracle pair, two fresh hooks=off processes verify/results/saves/ar-oracle-{A,B}-post.sav (both 7a8b3d5e…)
pre-turn half of the pair verify/results/saves/ar-oracle-A-pre.sav (15b99255…)
the instrumented run's post-turn autosave, byte-identical to both verify/results/saves/ar-r2-probes8-post.sav (7a8b3d5e…)
R1's final state, turn 43 verify/results/saves/ar-r1-turn43-post.sav
new addresses (7 entries, no name collides) ghidra/addresses.d/ar.json
instrument sots-engine src/shim/shim.cfg.hp8, unchanged; build ar-989c692-20260909T0313Z
build tree lane worktree ~/sots-engine-ar (branch wip/ar) → CT111 /srv/re-lab/build/sots-engine-ar, rm -rf'd before the build; dist /srv/re-lab/shim/dist-ar

New content for the coverage ratchet (rule 27): ar-turn37-816raiders.sav carries a TradeSector with tsnumflt 5 and five tsflt entries mixing both empires' fleets; a trdmgr with eight rt records where the corpus maximum was four; and — the first in the corpus — a StarFleet whose LocID is a TradeSector id while its Pos is not the sector node's, which is the serialised signature of a raider displaced to an interception point. Nothing here is a new kind of item, so the ratchet should hold.


10. VM141 as left

Guest restored, hold released. Game stopped; C:\SOTS\shim.cfg is shim.cfg.hoff; C:\SOTS\binkw32.dll is this lane's build (ar-989c692-20260909T0313Z), with the original still at binkw32_real.dll. C:\SOTS\SavedGames is lane AD's set plus ar-turn37-816raiders.sav, so Load-dialog row positions have moved again — screenshot the dialog. With the three autosaves deleted the list is 12 rows from y = 260 at 29 px pitch, and ar-turn37-816raiders is row 3 at (400, 318).

Left in place, all additive: C:\SOTS\shimdist-ar (4 files) and C:\SOTS\ui\ar-*.ps1 (ar-go.ps1 takes -Cfg, ar-ui.ps1 writes cmd.txt and runs the helper once, plus three one-shot copy helpers). click_helper.ps1 is unmodified from lane AC's version.

Four things that cost this lane time, for the next holder.

  • Startup is faster than the board says if you drive it. fg + key {ESC} three times spaced ~2.5 s, then ~90 s, put the main menu up in about two minutes, not four and a half. Loading a turn-37 save is a further 3–4 minutes and that part is not compressible. Verify both from a screendump.
  • rdrag x y x y is a right-click. The click helper has no right-click verb and the fleet panel's Add To New Fleet / Split Ships menu is right-click-only. A zero-length right-drag opens it.
  • A trade-sector node is a legal move target even when its cube is not drawn (§3.1), and its screen position is computable from the save. Do not conclude a sector is unreachable because you cannot see it.
  • The encounter query blocks the End Turn, so a scripted turn loop must detect it. This lane detected it by sampling one pixel: the Done button at (233, 673) reads ≈ (183, 18, 0) when a query is up and ≈ (8, 8, 8) on the map. Auto Resolve Peacefully is the fourth icon at (667, 641) and Done at (233, 673).

Proposed board rows

New rows:

| VERDICT: `TradeManager+0x0c` IS the per-system containing-sector table - the writer is found and the raid target pick FIRES | control-flow | verified | high | 100% | 2026-09-09 | **Lane AR**, VM141, `findings/control-flow/raid-target-pick-verdict.md`. Decides lane AG's §3.3 final paragraph, the one load-bearing inference AG flagged as unproven, and lane AD correctly reported it could not separate on sector 832. **BOTH INSTRUMENTS AGREE (rule 3).** STATIC: the writer AG's §7 lists as NOT FOUND is **`FUN_00841700`** (214 B, 0x00841700-0x008417d5) - it clears the +0x0c vector, RESIZES it to `count(([this+4])+0x40..+0x44)` (the SYSTEMS vector, the very container `FUN_00841cd0` indexes with `rt->trfr` at 0x00841d36), then walks the SECTOR vector x each sector's MEMBER SYSTEM vector executing `mov [this->+0x0c + m->+0x5c * 4], sector` at 0x008417a8. `ServerSystem+0x5c` is the serialised **`Idx`** field (objects/layouts.md, grade verified), so this is literally `containingSector[system.Idx] = sector` and **G_B1a cannot reject a valid trfr, BY CONSTRUCTION**. It is called from the DESERIALISER `FUN_00858a10` at 0x00858f07, so a loaded save has the table populated. LIVE: with 4 raiders parked (2 CR on sector 832, 2 DE on sector **816**, all `Pos` bit-equal), `Slot13RngCalleeA` is entered **4x/turn, 4 words**, every turn - and on turn 42 `Slot13RngCalleeB` reached its target pick: **`NextInt` at call `0x0088b613`, calls=1 words=1**, the FIRST firing of that site in the campaign. `OnAllCombatDone_Tail` cost **5** words that turn against **4** on every other turn; bracket residual **0** on all six turns. **THE SAME RUN CARRIES ITS OWN CONTROL**: on turns 38 and 40 `B` was entered by a cruiser on sector 832 and cost **0 words** - same process, same build, same instrument, same function, only the sector differs. AC's and AD's proof-carrying zero is a fact about the SECTOR, not the code path. Instrument `probes=8` (NOT 11), shim `ar-989c692-20260909T0313Z`, cfg `shim.cfg.hp8` unmodified, lane worktree + own build dir `/srv/re-lab/build/sots-engine-ar` rm -rf'd first (rules 21, 24) |
| `B` costs ONE word on a success, not two - and the deciding value is a SPECIES FLAG set by a constructor for exactly two species | control-flow | verified | high | 100% | 2026-09-09 | Lane AR. Lane AG's committed §3.3 prediction was **3 words on a succeeding turn** (`A` 1 + `B`'s NextInt 1 + `FUN_00820af0`'s NextFloat 1) and §5.2 says `B` costs 2 on success. **MEASURED: the tail cost 5 with k=4 raiders and one success, i.e. `B` cost 1, and there is NO `0x00820c1b` row on that turn.** Why: `FUN_00820af0`'s third short-circuit is `SpeciesDef_HasInterceptFlag 0x0053baf0` - twelve bytes, `cmp [ecx+0x144],0; setne al` - and `InitTable 0x005453a0` sets that flag word at 0x0054562f/0x00545638 for **species 0 (HUMAN) bit 0 and species 5 (ZUUL) bit 1 ONLY**. `Get 0x00545cc0` returns `0x00b10a00 + k*0x184`, the same table InitTable fills. So **a Human or Zuul raider intercepts at frac=1.0 and draws nothing; the other five species draw a NextFloat at 0x00820c1b**. The general tail model is **`k + s + z`** words: k = permitted raiders parked with `Pos` bit-equal, s = their successes ON A SECTOR THAT SOURCES A ROUTE, z = s again unless the raider's species sets the flag. Measured at k+s with k = 4,4,4,4,4,3 and s = 0,0,0,0,1,0. THE CHEAPEST UNEXERCISED STATE IN THE TRADE HALF IS NOW A NON-HUMAN, NON-ZUUL RAIDER - z has never been observed through `B` by anyone. Rule 23's lesson pointing a new way: the deciding value was not a float literal but a FLAG WORD SET BY A CONSTRUCTOR, invisible to any amount of reading `FUN_00820af0` alone |  **[POLARITY CORRECTED 2026-09-09 by lane AZ (rule 11): the short-circuit arm sets `frac = 0.0`, NOT 1.0 (`fldz; fstp [ebp+0xc]` at 0x00820bf8); the DRAW arm computes `0.0 + (1.0-0.0)*NextFloat()`. So a Human/Zuul/Hiver interception happens at the raided route's SOURCE end, not its destination. Word counts unaffected; the sentence was wrong. Rule 28 practice 6 - read the polarity before the count.]**
| ORACLE PAIR on the turn-37 raider state - and the probes=8 instrument is BYTE-NEUTRAL, whole-save | meta | verified | high | 100% | 2026-09-09 | Lane AR (rules 19, 26). Two fresh `hooks=off` processes loading `ar-turn37-816raiders.sav` and running the same two End Turns produced **BYTE-IDENTICAL** autosaves at both snapshot points: Frame 38 `15b99255e1f03dab…` and Frame 39 `7a8b3d5eb3a60eba…`; `state_checksum --floats bits --mask none` prints **IDENTICAL**, root `237020deca931f5a180289592cece7ae`, coverage PROVED (1,173,884 B rebuilt, 67,239 leaves), 0 error 0 warn. **THEN** a fourth process with `shim.cfg.hp8` installed produced the SAME TWO HASHES. So the instrument is behaviour-neutral **at whole-save byte granularity**, not merely on the sub-tree the measurement reads - the stronger form of what lane AD could show. Order matters and it was the right order: the control was reproduced FIRST and shown to be a single outcome, so rule 26's `1/k` coincidence caveat does not apply (k=1). **THIS CORRECTS THE SCOPE OF LANE AD'S ROW**: "no oracle pair for this state / the AI varies per process" is true of `ad-turn27-two-raiders.sav` and NOT true of the same lineage ten turns later, so it is a fact about that state, not about the engine. No explanation for the difference is offered and none should be inferred. `ar-turn37-816raiders.sav` + 2 End Turns is the first RUNG-A CALIBRATION PAIR from this lineage and the obvious candidate for the standalone's first trade-raid comparison |
| `CreateRaidEncounter`'s candidate list is NOT always empty - 0x008939ee fires, and rule 20's case study gets its positive | control-flow | verified | high | 100% | 2026-09-09 | Lane AR, turn 43 of `verify/traces/ar-r1-turn38-turn43.jsonl`. `ServerTradeManager::CreateRaidEncounter` entered **3x** and its subtree drew **two** words: `NextInt 0x008939ee calls=1 words=1` - the site lane H measured at 0 on every turn and that `tail-rng-ledger.md` has carried as a zero ever since, the campaign's own case study for rule 20 - and `NextFloat 0x00820c1b calls=1 words=1` through the OTHER caller `0x0082cf65`, i.e. lane AG §5.1's chain `CreateRaidEncounter -> 0x00892640 -> 0x0082ce00 -> FUN_00820af0`. `Slot13RngCalleeB` was entered **0** times that turn, so neither word is `B`'s. **Two of AG's §5.1/§5.2 unread gates are now read from opposite directions.** The honest form of the cost claim is neither "0 or 1 further word" nor "at least six draws" but **unbounded above and state-dependent**; the first non-zero observation is 2 |

Edits to existing rows:

  • Row 62 (guest holders) — VM141 = FREE (lane AR released 2026-09-09; guest restored, shim.cfg.hoff, this lane's binkw32.dll, SavedGames = lane AD's set PLUS ar-turn37-816raiders.sav, so Load-dialog rows have MOVED AGAIN - with the autosaves deleted the list is 12 rows from y=260 at 29 px pitch and ar-turn37-816raiders is row 3 at (400,318)). Lane AR left C:\SOTS\shimdist-ar + C:\SOTS\ui\ar-*.ps1; click_helper.ps1 unmodified. GOTCHA (corrects the startup figure): fg + key {ESC} x3 spaced ~2.5 s then ~90 s puts the main menu up in about TWO minutes, not 4.5 - the 4.5 figure is what you get by waiting the movies out. Loading a turn-37 save is a further 3-4 min and that part is not compressible. GOTCHA: rdrag x y x y (a zero-length right-drag) IS A RIGHT-CLICK - the fleet panel's Add To New Fleet / Split Ships menu is right-click-only and the helper has no right-click verb. GOTCHA: a trade-sector node is a legal move target EVEN WHEN ITS CUBE IS NOT DRAWN - in Move mode the hover readout names it (Sector 5 / Range: 15.7 / ETA: 4 Turns); fit screen = (A*wx + B*wz + C, D*wx + E*wz + F) from two known points in the save and you land on any node first try. GOTCHA: the encounter query BLOCKS the End Turn - detect it by sampling the Done button pixel at (233,673): ~(183,18,0) when a query is up, ~(8,8,8) on the map; Auto Resolve Peacefully is (667,641), Done (233,673).`
  • Row 399 / the AC-AD raid rows — append: RESOLVED 2026-09-09 (lane AR): AD's "my data does NOT distinguish AG's per-system vs sector-indexed reading" is now decided BOTH WAYS - FUN_00841700 is the writer and the pick fired live at 0x0088b613 with the raider on sector 816. AD's zero on 832 stands and is reproduced inside lane AR's own run as the within-run control.
  • Row 207 — append: 0x0088b613 IS NO LONGER UNFIRED (lane AR 2026-09-09): calls=1 words=1 on turn 42 of ar-r1-turn38-turn43.jsonl, with the raider on sector 816. 0x00820c1b also fired, but through CreateRaidEncounter's caller, never through B - the species short-circuit blocks B's second draw for Human and Zuul raiders.
  • Row 186 (RNG LEDGER CLOSED) — append: Lane AR 2026-09-09: the tail's cost is k + s + z - one word per parked permitted raider, PLUS one per success on a route-sourcing sector, PLUS one more per success unless the raider's species sets SpeciesDef+0x144 (Human and Zuul only). Measured 4,4,4,4,5,3 over six turns with k = 4,4,4,4,4,3.
  • Lane AD's "Sector 816 is UNREACHABLE" row — append: REACHED 2026-09-09 (lane AR) exactly as costed: five stock Extended Range destroyers (Range 27.0, 2,252 each) built at Epsilon Eridani over turns 28-31, split into four fleets, ETA 4 turns, parked on sector 5's node turn 35. AD's geometry table and its build costing are exact. TWO THINGS AD's recipe did not say: a new ship JOINS an existing fleet and a fleet's Range is the MINIMUM over its ships, so the 9-ly cruiser has to be split off before the node is a legal target; and the sector node is clickable even though its cube is not drawn.