AR: predictions for the raid-target-pick verdict, committed before the build
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.
This commit is contained in:
parent
7d49e216f1
commit
712d184599
1 changed files with 198 additions and 0 deletions
198
findings/control-flow/raid-target-pick-verdict.md
Normal file
198
findings/control-flow/raid-target-pick-verdict.md
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
# 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 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.*
|
||||
Loading…
Add table
Reference in a new issue