Includes the static decode of FUN_00841700 -- the writer of TradeManager+0x0c that lane AG's section 7 lists as not found -- stated as a falsifiable prediction (P0) with the symptom of each way it could be wrong. Also corrects the route count in ad-turn27-two-raiders.sav: five rt records, trfr 18/16/14/20/14, not three.
14 KiB
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 · guest VM141 (
sots-re-win10-b, 192.168.10.143) — held from the time this file was committed - 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 sectorTradeID 832 - Instrument: lane Z's return-address ledger (
draw_sites) + lane H's entry probes atprobes=8, the byte-neutral set. Notprobes=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:
- The table's length is, by construction, the length of the container that
trfrindexes.FUN_00841cd0— the function that settles whattrfrmeans, and the one AG cites for exactly that — resolves a route's source endpoint at0x00841d36asmov 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+0x0cvector to the length of that same vector on that same object. SoG_B1a(0 <= rt->trfr < count(this->+0x0c..+0x10)) is satisfied by every route with a validtrfr, by construction, in the same way that AG showedvslot15's G1 is satisfied by everyrtin every save by construction. - The slot index is a system's own index field, and the slot value is a
TradeSector*. Step 3 writestable[m->+0x5c] = sectwheresectcame out of the sector vector andmcame out of that sector's member list. That is the definition of a per-system containing-trade-sector table.FUN_00841c70reads it back the same way (arg->+0x5c→ bounds check against the same count →table[idx], thentable[idx] + 0x78— the member vector — is handed toFUN_0059ec00), which is the independent confirmation that+0x5con the indexing object and+0x78on the table element are the two halves of one relation. - The table is rebuilt on load.
FUN_00841700has five callers; one of them isFUN_00858a10, which is full ofReadInt/ReadFloatcalls and invokesFUN_00841700at0x00858f07as 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+0x0cis astd::vector<TradeSector*>of lengthnumSystems, indexed by system index. AG's per-system reading is right; the sector-indexed-vector-of-six reading is false, and0x0088b613is therefore reachable in principle.ad-turn27-two-raiders.savhassystemIdsof length 28, so the table has 28 slots andtrfr ∈ {14,16,18,20}clearsG_B1awith 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 (
trfr18 and 16) → candidate listn = 2,bound = 1; - sector 768 sources two (
trfr14 twice) →n = 2; - sector 752 sources one (
trfr20) →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
Posbit-equal,Asucceeds for it,Bis entered, andBstill costs 0 words — then P0 is false,TradeManager+0x0cis 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
Freightersfleet, soparam_2is finally testable in principle — but separatingp = 0.06fromp = 0.12needs 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 > 1short-circuit. Still unpriced; the Dreadnought category was empty for lane AD and nothing in this workload changes that. bestScale.0x009f8d48 = 0.33fapplies whenship+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.