sots-re/findings/control-flow/ai-visit-order-probe.md

8.8 KiB
Raw Blame History

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 at C:\SOTS\shimdist-bp (binkw32.dll sha256 9de1c57a38e66800a9487f46f7e7abc4fea6efc05cd72319d911733e9d971f47). No build for this lane — the resolution costed it at "one fresh process, no build".
  • Input: C:\SOTS\SavedGames\ad-turn27-two-raiders.sav, sha256 1c8baa27680809d585ab1728391e82729642d32aaedffd5bf95b616f0a11a436, 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 the airng detour perturbing the run. The resolution names the disambiguation and this lane will run it if the guest allows: one more run of shim.cfg.bppin (airng=off, plain run C). Under the k ≥ 3 reading 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 OnResumePlaying bracket: foreign_words=0, foreign_calls=0, residual=0, and no airngcensus row whose rng is anything but Player[32]'s own client generator at client+0x134 — in particular none on 0x00af6e58 and none on Seed record #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–0x006exxxx RVA-wise, i.e. va in 0x0068_0000–0x006e_0000 at exe base = 0x005e0000); clients 496 and 512 spend fewer than Player[32].

P4 (the block). The aiorders dump 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

(filled in with the run)


3. The result

(filled in with the run)


4. Verdict

(filled in with the run)