494 lines
42 KiB
Markdown
494 lines
42 KiB
Markdown
# Resolution — the residue under pinned seeds is a visit order, it is written into the block, and it is not yet known to be RNG or heap
|
||
|
||
- **Type:** resolution (course-setting; two new measurements taken here, from BP's logs already on disk)
|
||
- **Trigger:** lane BP's P1 falsified — `findings/control-flow/pinned-seed-rich-turn.md` §3 (probe 2 of
|
||
`2026-09-09-seed-exposure-is-a-predicate.md`)
|
||
- **Date / author:** 2026-09-09 · re-resolver
|
||
- **Touches:** board rows 326 / 333 / 360 / 415; `backlog.md` §2 Rung B and Rung C; `method-rules.md`
|
||
rule 26 (c) wording, new corollary (d); `id-allocation.md` §5; `determinism-oracle.md`;
|
||
`verify/state-checksum/state_checksum.py` (a new mode); `src/shim/shim.cfg.cbpin`
|
||
|
||
---
|
||
|
||
## 0. Decision
|
||
|
||
1. **BP's restatement is accepted and sharpened.** Given (save, per-client seeds) the AI's command
|
||
block on `ad-turn27` is determined **up to the order in which `AssignFleetsAndIssueOrders`
|
||
visits the groups that need a new fleet.** The ids are not the variable — the node-2 counter
|
||
hands out 1970, 1986, 2002 in that order in every process (§2.1). The visit order is. On this
|
||
turn the visit order changes exactly three things: which client-minted id each group receives,
|
||
the element order of lists 8/10, and — downstream — the apply order, which is why `ShipIDs[]`
|
||
comes out reordered. Every decision is invariant under it.
|
||
2. **The residual input is not lost; it is written into the block.** List 10 carries
|
||
`{systemId, fleetId, ships[]}` per group, so any captured original run records its own visit
|
||
order for free. C-exact therefore survives **literally**, with one more per-run input that costs
|
||
nothing to capture: (save, seeds, **visit order as read off list 10**) → byte match. C-set's
|
||
shape covers the uncaptured case: our canonical order is one member of the original's outcome
|
||
set of orders. §3 says what the engine and the comparison tool must do.
|
||
3. **The pointer-order reading is not yet the leading hypothesis — it is one of two, and the
|
||
other is already named in the record.** AI1's consumer list for the per-process global generator
|
||
at `0x00af6e58` includes two AI-band sites nobody has named (`0x0079ea90+0x73`, an `RNG_Chance`,
|
||
and `0x005b9f00+0xc0`); the seed pin does not touch that generator. What *has* moved since 09-08
|
||
is that pointer order now has a **measured precondition** (§2.2): in two processes with identical
|
||
configuration, BP's own dumps show the same run of consecutive same-size heap allocations landing
|
||
in different relative address orders. So the mechanism the reading needs is present in this
|
||
process. That is a precondition, not the cause.
|
||
4. **One probe, one run, no build, VM146, ~12 minutes** (§4): BP's exact pinned procedure with
|
||
`airng=on` added (pin_seed off). The `OnResumePlaying` bracket attributes **every draw on every
|
||
generator** during each AI client's turn to a return address and a generator address, and the
|
||
inlined-draw inventory has no site in the AI band, so the observer is complete there. It is also
|
||
run C. **Prediction committed:** zero foreign draws in Player[32]'s bracket — the global does not
|
||
move — and the autosave is one of the two known files. If a foreign draw appears, its return
|
||
address names the consumer and the follow-up is a global pin; if none, RNG is excluded by
|
||
measurement and the follow-up is the container hook, placed by one static read.
|
||
5. **Rung C is restated (§6). Rung B is not restated; its value is confirmed and it gains a
|
||
concrete target.** Replay applies ids off the wire in block order, so it is insulated from this
|
||
residue by construction. One `aiorders.deep=on` run of BP's pinned procedure that lands on
|
||
`e913ff41…` is the first fully reproducible rich-turn record — (pre-turn save, seeds, deep block,
|
||
autosave) — and the engine can be measured against it whenever the sim reaches that turn.
|
||
6. **No rule 29.** BP's third run is the right practice and it belongs in rule 26 as corollary (d):
|
||
when the instrument cannot be removed, pin it to a natural run's observed values and require it to
|
||
reproduce that run before reading anything from a pinned pair. BP's amendment to (c) is right in
|
||
substance and overreaches in one clause; corrected wording in §7. The `shim.cfg` trap BP found
|
||
(an unnamed hook is silently installed) gets a structural fix, not a rule.
|
||
7. **Lane BQ's result, which arrived mid-ruling, is folded in (§9) and does not change the probe.**
|
||
Image base is constant across processes on *both* guests (`0x00f40000` ×5 on VM145,
|
||
`0x005e0000` ×3 on VM146), so "pointer order" can only mean **heap** order, which §2.2 measures
|
||
as per-process. BQ's per-*history* input (a fleet moved by continuation, not after a load) is a
|
||
different input from BP's per-*process* one, and the probe holds history fixed (every run is a
|
||
load), so it is unconfounded. The leading mechanism for BQ's residue is **AI agent state the save
|
||
does not carry** (inference; `AISystem::Write` emits nothing), and the consequence for the rungs
|
||
is a scope, not a loss: **every byte-match claim is a claim about load→turn procedures.** Rung A:
|
||
BQ's reading is confirmed — the load-form pair enters `determinism-hashes.txt`, AR's continuation
|
||
hash does not.
|
||
|
||
---
|
||
|
||
## 1. The surprise, and the instrument
|
||
|
||
Two fresh processes with the three AI client seeds pinned to identical words, identical build,
|
||
identical config, identical input, identical pre-turn autosave, produced different turn-28 states —
|
||
35 leaves, one transposition. The prediction was IDENTICAL.
|
||
|
||
The evidence is as strong as reported, and stronger than a bare falsification, because the run that
|
||
*should* have been the weak one — L, unpinned — is byte-identical to A. Instrument: fresh tree (rule
|
||
24), lane worktree (rule 21), every registered hook off by name with the count in the log (27), zero
|
||
unknown keys, three of three `pinned=1` in both pinned runs, identical `config:` lines, no wildcard
|
||
hit, no fourth client. **The instrument is exonerated for the A/B difference** — the two processes
|
||
carried the same instrument and one of them reproduced an unpinned run byte for byte, which a
|
||
perturbing instrument could only do by a 1/k coincidence. Rule 26 applies to the A/B pair exactly as
|
||
BP says: it does not agree with itself, so it certifies nothing. What carries weight is L = A.
|
||
|
||
What no `hooks=off` control could have said here, stated so nobody looks for one: a pin cannot be
|
||
applied without hooks, so "the un-instrumented pinned game" does not exist. The L/A agreement is the
|
||
control this design admits (§7, corollary (d)).
|
||
|
||
## 2. Two measurements this resolution adds, from BP's files
|
||
|
||
### 2.1 The ids decode, and they say the counter is deterministic
|
||
|
||
`id = (counter << 4) | node` (`id-allocation.md`, verified on twenty saves and the instruction stream).
|
||
Player[32] is `PlyrIdx 1`, so its client is node **2**:
|
||
|
||
| id | hex | counter | node | what |
|
||
|---:|---|---:|---:|---|
|
||
| 1970 | 0x7b2 | 123 | 2 | client-minted, first new fleet |
|
||
| 1986 | 0x7c2 | 124 | 2 | second |
|
||
| 2002 | 0x7d2 | 125 | 2 | third |
|
||
| 7072 | 0x1ba0 | 442 | 0 | server id, an existing fleet |
|
||
| 6976 / 5264 | 0x1b40 / 0x1490 | 436 / 329 | 0 | server-minted ships |
|
||
|
||
So the AI minted three fleet ids at emission, counters 123–125 contiguous, in both processes. List
|
||
10's elements in A are `(384,1970) (80,1986) (384,2002) (816,7072)`; in B `(80,1970) (384,1986)
|
||
(384,2002) (816,7072)`. The id follows the visit; the group at system 80 was visited first in B and
|
||
second in A. **The variable is the visit order of the first two groups**; 2002's group and the
|
||
existing fleet came third and fourth in every process. (Two of three processes agree, so "third in
|
||
every process" is two observations, not a law.)
|
||
|
||
### 2.2 Same-size heap allocations land in different relative orders across processes — measured
|
||
|
||
The `aielem` dump records each command node's heap address (`node=0x…`). Runs A and B have identical
|
||
configuration and identical decisions, so the allocation sequence through the AI turn is the same
|
||
(inference: supported by the identical outputs, not proved). Player 32's list 3 — nineteen nodes
|
||
allocated in one pass — in visit order:
|
||
|
||
- **A:** `f838 f538 f898 f4f8 f518 fa38 fc98 …` (idx1 < idx0, idx3 < idx2)
|
||
- **B:** `e1c0 e320 e200 e380 e460 e500 e260 …` (idx1 > idx0, idx3 > idx2)
|
||
|
||
Relative order differs between two processes for the same allocation sequence. That is what the
|
||
Windows 8+ low-fragmentation heap does by design (randomised slot selection within a bucket), and it
|
||
is the precondition any address-keyed container needs to walk in a per-process order. **Measured:
|
||
allocation order is per-process here. Inferred: that some container on the fleet-assignment path is
|
||
address-keyed.** The second half is what the probe chain settles.
|
||
|
||
The image, by contrast, does not move: `exe base=0x005e0000` in all three of BP's processes on
|
||
VM146 and `0x00f40000` in all five of BQ's on VM145 — per boot, not per process. So any "ASLR"
|
||
reading that reaches for image-base randomisation is excluded on these guests; the only addresses
|
||
that vary between processes are heap (and stack). "Pointer order" in this document means heap order.
|
||
|
||
### 2.3 Time is not a live hypothesis with a mechanism
|
||
|
||
`AIProcessMinTime` is a trailing minimum-duration `Sleep` (`ai-task-system.md`), not a slice; the AI
|
||
turn runs synchronously inside `StrategyClient::OnResumePlaying` (lane PAR), and stepping order is
|
||
save order × stable priority (AI2). No wall-clock input to the visit order is in the record. Not
|
||
excluded — nothing is — but it has no mechanism and is ranked last.
|
||
|
||
## 3. Ruling on the restatement, and what "canonicalisable" means concretely
|
||
|
||
**Accepted, sharpened as in §0.1.** Two qualifications, both rule 23's shape:
|
||
|
||
- "Every decision is invariant under the visit order" is a statement about *this turn*. A visit
|
||
order can change a decision wherever an earlier visit consumes something a later one wanted (an
|
||
escort, a build slot, the one move order). On `ad-turn27` the move in list 8 went to the group at
|
||
system 80 in both orders. The general claim needs the mechanism named and its scope read.
|
||
- The outcome set of orders has been observed at size 2 from two distinct observations. Its true
|
||
size is unknown; run C (the probe run) is the first datum on it.
|
||
|
||
**For `sots-engine`, three deliverables, ranked by what they unblock:**
|
||
|
||
1. **Comparison side — `state_checksum.py --relabel-new-ids <pre-turn.sav>` (sots-re, host
|
||
only).** Given the pre-turn save, compute the ids new in each post-turn save; among them, match
|
||
fleets with node nibble ≠ 0 by a content key that does not depend on the id — `(LocID or FPlan
|
||
destination, sorted ship-id set)` — and build the bijection π between the two saves. Apply π to
|
||
every id-valued leaf (`Flt[]` keys, `Sys[*]/Flt[*]`, ship→fleet references, `FleetIDs[]`
|
||
entries, list-8/10/14 words when comparing blocks), treat the master id lists `ShipIDs[]` /
|
||
`FleetIDs[]` / `DesignIDs[]` as sets, and report `IDENTICAL modulo π = {1970↔1986}` with π
|
||
printed. `Summary/Checksum` is derived and its inputs are unmodelled (rule 18's open item), so it
|
||
is masked *with that reason on the line* until the checksum is modelled. Acceptance: BP's
|
||
`bp-pinA` vs `bp-pinB` reads IDENTICAL modulo one transposition and nothing else; AD's
|
||
`ad-oracle-A/B-post` still DIVERGE (their difference is seed-driven and must not be absorbed).
|
||
2. **Engine side — `game/ai`, at the point where a task forms a fleet from a ship group** (the
|
||
original: `AssignFleetsAndIssueOrders 0x006c16c0`, pass 1, minting via
|
||
`IDMap::AllocateOnLocalNode` on node `PlyrIdx+1`, emitting list 10 then any list 8/14 for that
|
||
fleet). Visit the groups in a **canonical order that is a function of the save alone** —
|
||
ascending `systemId`, then ascending minimum ship id in the group — and mint in that order. This
|
||
is deterministic across our own processes by construction and is one member of the original's
|
||
outcome set. Nothing else in `id-allocation.md` §5 changes: mint at emission, carry the id in
|
||
the command, the applier honours non-zero ids.
|
||
3. **Engine side, exactness knob — an optional visit-order override** read from a captured block:
|
||
`--ai-visit-order <netid>=<systemId>/<minShipId>,…` (or simply "take the list-10 order from the
|
||
`--turn-commands` capture when both are given"). With it, (save, seeds, order) reproduces the
|
||
original's exact bytes on every captured run — C-exact literal. Three words per new fleet, the
|
||
same cost class as `--ai-seed`. Build it after (1) and (2); it is what turns "modulo π" into "=".
|
||
|
||
The comparison should never accept a relabelling that is not a bijection between *new* ids of the
|
||
same node, and it should refuse to relabel anything present in the pre-turn save. That is the guard
|
||
that keeps (1) from absorbing a real divergence.
|
||
|
||
## 4. Hypotheses and the one probe
|
||
|
||
| | hypothesis | mechanism in the record | what the probe shows |
|
||
|---|---|---|---|
|
||
| **H1** | an address-keyed container (or an unstable sort seeded by one) on the fleet-assignment path walks in heap order | precondition measured (§2.2); container **not** identified | zero foreign draws in the bracket; order then needs the container hook (stage 2) |
|
||
| **H1b** | an uninitialised word read by a comparator on that path | this codebase leaves uninitialised words in command elements (L4/CB's noise set) | same as H1 at stage 1; stage 2 separates: visit order tracks neither address nor id |
|
||
| **H2** | a draw on the per-process global generator `0x00af6e58` inside the AI turn — AI1's `0x0079ea90+0x73` (`Chance`) or `0x005b9f00+0xc0`, both unnamed; or on Seed record #2's object | named consumers exist; none has been seen firing on a turn | ≥1 `airngcensus` row with `rng != client+0x134` inside Player[32]'s bracket, with its return address |
|
||
| H3 | wall clock | none (§2.3) | — |
|
||
|
||
**The probe — lane "BR", VM146, one fresh process, no build.** `C:\SOTS\shimdist-bp` is still
|
||
deployed with BP's build; take `shim.cfg.bppin` verbatim (seeds `32=156ebbbd,496=fe7b2826,
|
||
512=0ed341d1` plus the wildcard, every hook off by name) and change exactly three lines: `airng=on`,
|
||
`airng.pin_seed=off`, `airng.out=<path>`. `aiorders` stays on with `deep=0` so the block is comparable
|
||
with A/B. Load `ad-turn27-two-raiders.sav`, one End Turn, collect `(Autosave).sav`, `shim.log`, the
|
||
`airng` output and the `aiorders` dump.
|
||
|
||
Why this and not run C alone or probe 3's ledger alone: it **is** run C (a third pinned sample), and
|
||
it **is** probe 3 on AD's state (the per-client ledger with `this`), and the `airng` detour has a
|
||
measured neutrality — byte-identical to the published oracle in two fresh processes (`roll-parity.md`).
|
||
The seven draw entry points are the only way to draw in the AI band: lane I's inlined-draw inventory
|
||
has its eleven functions in the network, encounter, lobby and combat subsystems and **none between
|
||
`0x680000` and `0x6e0000`**, so a draw the observer does not see during the bracket would have to
|
||
come through a path the direct-call closure cannot reach — which the `left_delta − observed`
|
||
residual on the client generator reports for that generator, and the census reports for every other.
|
||
|
||
**Predictions, committed before the run:**
|
||
|
||
- **P1.** `(Autosave).sav` ∈ {`e913ff41…`, `724528ff…`}. A third file means either the order
|
||
outcome set exceeds 2 or the added detour perturbed the turn; those are separated by one more run
|
||
with `airng=off` (plain run C), which cannot land on a third file under the first reading with
|
||
probability better than 1/3.
|
||
- **P2.** In Player[32]'s `OnResumePlaying` bracket: `foreign_words=0 foreign_calls=0`, no
|
||
`airngcensus` row whose `rng` is `0x00af6e58` or Seed record #2's object, `residual=0`. The same
|
||
for clients 496 and 512.
|
||
- **P3.** Player[32]'s own generator spends single-digit words in the bracket, at return addresses
|
||
in the AI band; 496 and 512 spend fewer. (Not load-bearing; recorded so the ledger has a number to
|
||
disagree with.)
|
||
- **P4.** The `aiorders` dump differs from A and B only inside the proven noise set plus, at most,
|
||
the three-word transposition.
|
||
|
||
**Course after the probe, either branch:**
|
||
|
||
- *P2 fails* (a foreign draw in the bracket): the return address names the consumer. Follow-up is
|
||
the global pin — `RNG_Seed(0xaf6e58, K)` from the shim after `Application::Initialize` (09-08
|
||
resolution §6 named it for combat; ~20 lines), two fresh processes, prediction byte-identical. If
|
||
the draw is on record #2's object, pin that object's seed in the `Seed` detour instead.
|
||
- *P2 holds*: RNG is excluded by measurement for the AI turn. Stage 2, one lane: read
|
||
`AssignFleetsAndIssueOrders 0x006c16c0` and the fleet-gathering hub it feeds (the 992-byte body
|
||
AI2 mapped; result vector at `[ebp-0x3c]`) far enough to find the loop that visits the ship groups
|
||
and the container it walks — that is a bounded static read *to place a hook*, not to derive an
|
||
answer (rule 18). Hook the loop; log `(element address, systemId, minted fleetId)` in visit
|
||
order; two fresh processes. Prediction under H1: visit order equals ascending element address in
|
||
both, and the two processes' address orders differ exactly where their visit orders differ. Under
|
||
H1b: order tracks neither. Either way the container's identity is what `game/ai` needs to know
|
||
it is canonicalising the right thing.
|
||
|
||
Do not start stage 2's read before the probe returns; the probe is twelve minutes and a foreign
|
||
draw would make the read moot.
|
||
|
||
## 5. Standing claims
|
||
|
||
| claim | verdict | scope / correction |
|
||
|---|---|---|
|
||
| Row 326 / 09-08: the AI client seed is per-process and is the cause of `turn1-state`'s variation | **survives unchanged** | L1's per-process seeding reproduced a fourth time (nine distinct words, three processes). |
|
||
| Row 360: pinned seeds collapse three processes to one | **survives with scope** | True on `turn1-state`. On `ad-turn27` pinning collapses 94 leaves to 35 and every *decision* leaf; it does not collapse the fleet visit order. |
|
||
| Row 415 / AD: "the variation is the AI's decisions" | **survives with a qualification** | Right for 59 of the 94 leaves and for every substantive choice (build at `Sys[272]`, fleet split, design, money); incomplete — the remaining 35 are a non-seed per-process visit order. |
|
||
| 09-09 resolution §4 reading 3: "a non-seed per-process cause, not excluded on rich states" | **confirmed, and localised** | It is alive; it is confined to the fleet-assignment visit order on this turn; it is written into list 10. |
|
||
| 09-09 §3: "AD's varying design is not the composer varying" | **survives, strengthened** | BP extends it from two save bodies to the *commands that created them*: list 1 identical including the name bytes. |
|
||
| 09-09 §6 probe 2's cost/mechanism brief: "seven non-human players; wildcard or list every id" | **corrected** | Three AI clients (32/496/512); four NPC factions have no client. The wildcard would have pinned three and said nothing. BP's log run is what made the count a measurement. |
|
||
| 09-08 resolution: "pointer-ordered tie under ASLR — withdrawn" | **survives as withdrawn for the research pick; re-opened as H1 for the fleet visit order** | Different decision, different consumer, and now a measured precondition (§2.2). It was wrong there; it is untested here. |
|
||
| Backlog §2 C-exact: "nothing else per-process reaches the turn" | **overturned as written; restated (§6)** | One more per-process input reaches rich turns; it is observable in the block. |
|
||
| Backlog §2 Rung B: replay of a captured block byte-matches | **survives, strengthened** | The residual input is in the block; replay applies ids off the wire in block order. |
|
||
| `id-allocation.md` §5 (mint at emission; honour ids off the wire; the client counter is a function of allocation order) | **survives; one addition** | Add: the *visit order* that determines which group gets which counter is per-process in the original; `game/ai` visits canonically (§3.2). |
|
||
| Rule 26 (c) as BP amended it | **survives with a wording fix** | §7. |
|
||
| `determinism-oracle.md` "Qualified" | **needs the paragraph BP proposed** | Pinned seeds do not make a rich turn reproducible; the residual is the fleet visit order; comparison modulo π is defined in §3.1. |
|
||
|
||
## 6. Rungs
|
||
|
||
**Rung C bullet in `backlog.md` §2 — replace the C-exact sub-bullet with:**
|
||
|
||
> - **C-exact.** Given the seeds captured from an original run (`sots_turn --ai-seed <netid>=<hex>`),
|
||
> our AI's command block equals that run's block element for element and the autosave byte-matches
|
||
> — **up to one further per-process input, measured on `ad-turn27` (lane BP, 2026-09-09): the order
|
||
> in which the AI visits the ship groups that need a new fleet.** The seed determines every
|
||
> decision on that turn (94 unpinned leaves → 35 pinned, all one transposition); the visit order
|
||
> determines which client-minted id each group gets and the element order of lists 8/10. That
|
||
> input is **written into the block** (list 10's `{systemId, fleetId, ships[]}`), so on every
|
||
> captured run it is available for free: `(save, seeds, visit order)` → byte match, with the
|
||
> engine visiting canonically by default (ascending system id, then minimum ship id) and taking
|
||
> the captured order as an override for exact replay. Comparison is `state_checksum
|
||
> --relabel-new-ids`: identical modulo a bijection on that turn's new ids, with the bijection
|
||
> printed and `Summary/Checksum` masked with its reason. Precondition unchanged: the per-client
|
||
> draw ledger. **Scope (lane BQ, 2026-09-09): every byte-match claim in this rung is a claim
|
||
> about a load→one-turn procedure.** The original's AI carries state across turns that no save
|
||
> holds (a fleet moved by continuation and not after a load, on the same file), so an engine run
|
||
> from a save can only be compared with an original run from that save; a continuation is not a
|
||
> target, by construction. **Open:** whether the visit order is heap order or a draw on the
|
||
> per-process global generator (probe BR), and whether it can change a *decision* on some other
|
||
> turn — on this one it cannot.
|
||
|
||
and append to the mechanism paragraph:
|
||
|
||
> The residual visit order is not the seed and not the client stream. It is per-process, and the
|
||
> only mechanisms in the record are an address-keyed container (heap allocation order is measured
|
||
> per-process in this game — BP's node addresses) or a draw on the global generator at
|
||
> `0x00af6e58`, which the seed pin does not touch. One run separates them.
|
||
|
||
**Rung B bullet — append, no restatement:**
|
||
|
||
> **2026-09-09.** Lane BP's result makes the case for this rung from the other side: with seeds
|
||
> pinned, the only thing that still varies between processes is a visit order that the block
|
||
> itself records (list 10 carries the fleet id each ship group received). Replay applies ids off the
|
||
> wire in block order and is insulated from it by construction. The first rich-turn record is one
|
||
> `aiorders.deep=on` run of BP's pinned procedure away: if it lands on `e913ff41…` it is
|
||
> (`ad-turn27-two-raiders.sav`, seeds `32=156ebbbd,496=fe7b2826,512=0ed341d1`, deep block,
|
||
> `e913ff41…`) — the workload that completes thirteen ships, creates a design and colonises a
|
||
> system, which no replay can be measured on until the sim runs `ProcessTurn` on a state that rich.
|
||
|
||
Rung A: untouched.
|
||
|
||
## 7. Rules
|
||
|
||
**Rule 26 (c) — BP's amendment, corrected.** BP's inserted clause reads "for a player that has an AI
|
||
client and something to decide with … because they have `NumOwn == 0` and the engine builds three AI
|
||
clients". The measured fact is the first half only: **the four NPC factions have no client, so their
|
||
`ResTNm == ''` is inert.** `NumOwn == 0` is not shown to be a gate — players 496 and 512 have
|
||
`NumOwn == 0` and *do* have clients (they held targets, so the pick was not exercised either way). Replace
|
||
the parenthetical with:
|
||
|
||
> (lane BP, 2026-09-09: `ad-turn27` has seven non-human players and the engine builds **three** AI
|
||
> clients — `RunAI` fires for net ids 32/496/512; the four NPC factions 528–576 get no client and
|
||
> their `ResTNm == ''` is inert. A save-field predicate must be read against **who actually runs**,
|
||
> which is a property of the player kind, not of the field. Whether a client with `NumOwn == 0`
|
||
> reaches the pick is untested: 496/512 held targets.)
|
||
|
||
**Rule 26, new corollary (d) — draft:**
|
||
|
||
> **(d) When the instrument cannot be removed, make it reproduce a natural run.** A seed pin needs
|
||
> hooks, so "the un-instrumented pinned game" does not exist and rule 19's control is unavailable.
|
||
> Lane BP's answer: run the save once *unpinned* with the seeds logged, pin the next processes to
|
||
> **those observed values**, and require a pinned run to reproduce the unpinned one **byte for
|
||
> byte** before anything is read from a pinned pair. That agreement is the pin's own control — a
|
||
> perturbing pin could match a natural run only by a 1/k coincidence — and it is a **noise mask**:
|
||
> any word that differs between two runs with identical outputs cannot be a word the applier reads
|
||
> (lane CB's argument). On `ad-turn27` that mask reduced a 9-block, 81-element comparison to three
|
||
> words. The wildcard form of a pin (every client to one constant) is a valid intervention and a
|
||
> worse one: it certifies nothing about a turn that happened, and it would have hidden that the
|
||
> save's seven non-human players produce three clients. Pin to what was observed; count what ran.
|
||
|
||
Ranked by damage: none — nothing wrong was published; BP stopped where the brief said to stop and the
|
||
localisation is the best this campaign has produced on an AI turn. That is the brief working, not a
|
||
rule. Corollary, not rule 29, on the same grounds as last time.
|
||
|
||
**A structural fix instead of a rule, for BP's inherited defect.** Under `hooks=trace`, a registered
|
||
hook *not named* in the config is silently installed in trace mode; `shim.cfg.cbpin` omitted six. The
|
||
fix is in the shim, not in discipline: when any pin or capture module is on (`aiseed=pin`,
|
||
`airng.pin_seed`, `aiorders=on`), an unnamed registered hook is a **configuration error** that aborts
|
||
before `Application::Initialize`, and `shim.log` prints the installed-hook count next to the
|
||
registered count on every run (BP read "27 of 28 off" by hand; make the shim print it). Also fix the
|
||
wrapped header comment on `shim.cfg.cbpin` line 4 and add the six `hook.*=off` lines. Engine item,
|
||
one lane-hour.
|
||
|
||
## 8. Course
|
||
|
||
**VM146 — lane BR, the probe, now.** Brief, one paragraph: *BP's dist is still deployed at
|
||
`C:\SOTS\shimdist-bp`. Copy `shim.cfg.bppin` to `shim.cfg.brprobe` and change exactly three lines:
|
||
`airng=on`, `airng.pin_seed=off`, `airng.out=<path>`; diff the two configs' non-comment lines and quote
|
||
the diff in the report (BP's own check). Verify `aiseed pinned=1` ×3 and `airng: bracket … MH_OK` in
|
||
`shim.log` before reading anything. One fresh process, load `ad-turn27-two-raiders.sav` (Load-dialog
|
||
row 1 at (400,260) per BP's row-62 note; wait 12–16 s before trusting a wall frame), one End Turn,
|
||
collect `(Autosave).sav`, `shim.log`, the airng output, the aiorders dump. Report: the autosave hash
|
||
and which of `e913ff41…` / `724528ff…` it equals; for each of the three brackets the `airng` row
|
||
(`left_delta`, `observed`, `residual`, `foreign_words`, `foreign_calls`) and every `airngcensus` row
|
||
whose `rng` is not that client's `+0x134`, with return addresses; the aiorders diff against A and B
|
||
under BP's noise mask. Predictions are §4's P1–P4. If a foreign draw appears, stop and post its
|
||
return address; if none, stop and post the row. Do not read the binary. Do not compare any pinned
|
||
file with AD's unpinned files.*
|
||
|
||
**VM146, same lane, after the probe, if the guest is still warm:** one more run of `shim.cfg.bppin`
|
||
with `aiorders.deep=on` and `airng=off` — the Rung B record (§6). Report only the hash and whether the
|
||
deep dump follows list 10's ship vectors and list 8's routes.
|
||
|
||
**VM144 — lane "BS", BQ's per-history residue, now (§9.3).** Two instrumented runs on the AR
|
||
lineage, both routes to turn 39: *(continuation)* load `ar-turn37-816raiders.sav`, End Turn, Auto
|
||
Resolve Peacefully, End Turn; *(load)* load `ar-oracle-A-pre.sav`, End Turn. Config: `aiorders=on`
|
||
(`deep=off`), `airng=on` (pin_seed off), `aiprobes` on every task `Execute` entry the probe table
|
||
carries, every template hook off by name. Both routes have self-agreeing controls, so instrument
|
||
neutrality is checkable at k = 1: the continuation must give `15b99255…`/`7a8b3d5e…`, the load
|
||
`33e30092…`/`4c356f59…`. Predictions in §9.3. If either hash misses, stop: the instrument is not
|
||
neutral on that route and the run is void. Stage 2 of BR (the container hook) or the global pin goes
|
||
to whichever of VM144/VM146 frees first.
|
||
|
||
**Lane AZ (VM141) — no redirect.** BQ has released VM145; its next work is BS if it wants it, since
|
||
it holds the click path and the `Done`-pixel test for that lineage.
|
||
|
||
**All lanes, one line:** *when you record pre-turn exposure facts next to a hash, record which
|
||
players have an AI client (the `RunAI` net-id list, or "not logged"), not just `ResTNm`/`NumOwn` —
|
||
and record the route (load or continuation) as part of the procedure.*
|
||
|
||
**Host, no VM — three items, in order:** (1) `state_checksum.py --relabel-new-ids` per §3.1, with
|
||
BP's pair as the positive acceptance and AD's pair as the negative; (2) the shim config-strictness fix
|
||
and `shim.cfg.cbpin` repair per §7; (3) `id-allocation.md` §5 gets the canonical-visit-order item and
|
||
`determinism-oracle.md` gets BP's "Qualified" paragraph with §3.1's definition of "modulo π".
|
||
|
||
**Docs to correct in place (rule 11):** `raid-gate-multiplicity.md` §5 footnote — the 94 leaves are
|
||
59 seed + 35 visit-order; `ai-order-emission.md` §4.5 and `turn-command-replay.md` §4 row 5 — list 10
|
||
is `StrategyServer::OnCommand_TransferShips` (lane W3 named it; the AI-side notes still call it
|
||
"unnamed"); `pinned-seed-rich-turn.md` §2.4's "a player with no colonies has no research income and
|
||
does not reach the pick" — mark as inference; the measured fact is "no client";
|
||
`determinism-oracle.md` round-trip rule — BQ's `/CD[1]/NPrvVa` correction (+5.00 per load; not
|
||
covered by `--mask resave`); `determinism-hashes.txt` — the load-form AR pair (§9.3), route on the
|
||
line.
|
||
|
||
## 9. Lane BQ's result, folded in
|
||
|
||
BQ's `seed-exposure-forward-test.md` landed while this was being written. Three of its results bear
|
||
on this case; each is ruled here rather than in a second document, because they are one picture.
|
||
|
||
### 9.1 The frame of the probe is not wrong, but there are two residues and they get two names
|
||
|
||
- **BP:** two processes, *same route* (both loads), **disagree** → a **per-process** input.
|
||
- **BQ:** two processes, same route (both loads), **agree**; the *other route* (continuation from
|
||
turn 37) disagrees with both → a **per-history** input, and *no* per-process input on that turn.
|
||
|
||
These are different inputs in the same layer, and the second is consistent with the first: BP's
|
||
residue lives in the visit order over groups that need a *new* fleet, and AR's turn 39 formed none
|
||
(two ships into existing `Freighters` fleets). The visit-order model therefore predicts, after the
|
||
fact, exactly what AR and BQ measured — no per-process variation on a turn with nothing to visit.
|
||
Retrospective, so worth one line and no more.
|
||
|
||
Probe BR holds the route fixed (every run is a load, as BP's were), so it is unconfounded by BQ's
|
||
input. BQ's proposal — the per-client ledger on `ar-oracle-A-pre → 39` by both routes — is the right
|
||
probe for the *second* input and is adopted as lane BS (§8), with two additions: `aiorders=on`, and
|
||
the task-entry probes. The reason is §9.2: the leading mechanism for a per-history input is not a
|
||
draw, and a ledger alone would report it only indirectly.
|
||
|
||
### 9.2 What the per-history input can be, ranked — inference throughout
|
||
|
||
1. **AI agent state the save does not carry.** `AISystem::Write` emits nothing and the three
|
||
`AIAgent` save blocks are cache (backlog §4.1); tasks, claims and goals created on turn 38 exist
|
||
on turn 39 only in the process that ran turn 38. A task that took `Zeta Fleet V` on turn 38 and
|
||
issued its move on 39 is exactly a continuation-only order. This is the same class as two things
|
||
already in the record — the `0x0069dbb0` fuse (`agent+0x36c` not persisted) and the client id
|
||
counter (`id-allocation.md` §3.2, restarts on load). Three named siblings; leading.
|
||
2. **Client generator stream position** — *rejected as the mechanism.* Both routes are
|
||
seed-insensitive on this turn (AR's three continuation processes had three seed sets and agreed;
|
||
BQ's two loads had two and agreed), so no draw-dependent decision is what differs.
|
||
3. **Server-side state not in the save** — the sim reproduces by load on the canonical pair and on
|
||
BQ's own 1b pair; nothing in the record points here. Last.
|
||
|
||
**Predictions for lane BS, committed:** (a) the continuation's turn-39 block for pid 32 carries a
|
||
list-8 element for fleet 1570 and its list-14 pair, and the load's block does not; (b) a task
|
||
`Execute` entry probe fires in the continuation and not in the load — or the same task fires in both
|
||
and gathers a different fleet set; (c) `airng` shows the two routes' per-client word counts
|
||
differing by at most that task's cost, with zero foreign draws in both; (d) the pre-turn autosaves
|
||
differ only in `/CD[1]/NPrvVa`. If (a) holds and (b) shows no task difference, the gathering hub's
|
||
fleet set is the next place to look, and that is BR's stage 2 territory — the two lanes converge on
|
||
the same 992-byte function from two directions.
|
||
|
||
### 9.3 Rung A — BQ's reading confirmed
|
||
|
||
`ar-oracle-A-pre.sav → (load, one End Turn) → (Autosave EndTurn) 33e30092… / (Autosave) 4c356f59…`
|
||
enters `determinism-hashes.txt` as the standalone's first trade-raid pair, with the route, the
|
||
exposure facts (Player[32] `BIO_EnvTail` held, no design, two completions into existing fleets, four
|
||
raiders parked, no encounter) and the `NPrvVa` mask on the line. `ar-oracle-A-pre → 7a8b3d5e…` does
|
||
**not** enter as a load→turn pair; AR's two-turn continuation from `ar-turn37-816raiders.sav` stays
|
||
certified as *that* procedure (three processes) and is not an engine target, because the engine runs
|
||
from a save. This is the §6 scope line: every engine comparison is load→turn. The canonical pair
|
||
`turn2-state → turn3-state` already satisfies it — it was reproduced by load (CB, ID §3.2) and
|
||
matched the continuation, which is the case where the agent carried nothing that mattered.
|
||
|
||
### 9.4 Rule 26 (c) — BQ's four amendments, ruled, and the consolidated text
|
||
|
||
(i) **accepted:** `ResTNm == ''` names a pick; exposure is `|candidates| > 1`, not readable from the
|
||
save but readable from one `airesearch=on` run of it. (ii) **accepted with the §7 wording fix** —
|
||
the false-positive class is "no AI client", and `NumOwn` is a proxy that has not been shown to gate
|
||
(496/512). (iii) **accepted:** the combat term, and BQ's sentence for it. (iv) **accepted and
|
||
generalised:** exposure is a property of (save, route); (c)'s "(save, procedure, hashes)" already
|
||
carries it, and the route is part of the procedure.
|
||
|
||
Replace the predicate sentence of rule 26 (c) — from "rule 28's shape:" to the semicolon before
|
||
"one consumer (`0x0069dbb0`)" — with:
|
||
|
||
> rule 28's shape, with the terms measured so far: **(1)** `ResTNm == ''` at turn start, for a
|
||
> player that actually runs an AI client (a property of the player kind — on `ad-turn27` and the AR
|
||
> lineage seven non-human players yield three clients, and the four NPC factions' `ResTNm == ''`
|
||
> is inert), names a turn on which a pick is **made**; it is **exposed** only if the candidate walk
|
||
> is offered more than one candidate, which one `airesearch=on` run of the save reads
|
||
> (`candidatesTried`; lane BQ: 1 on `ar-oracle-A-post`, and the pick agreed); **(2)** `NumDes`
|
||
> moving names a design turn; **(3)** a batch of completions that forms *new* fleets names a
|
||
> fleet-assignment turn, and on such a turn a per-process visit order survives the seed pin (lane
|
||
> BP); **(4)** an AI fleet standing at an enemy colony names a combat turn, and `Auto Resolve
|
||
> Peacefully` means "I commit no ships", not "no combat occurs" — the planet fights and
|
||
> `RunCombatRound` is per-process (lane BQ); **(5)** the route is part of the procedure: the same
|
||
> file reached by continuation and by load can run its next turn differently, both reproducibly
|
||
> (lane BQ), so a continuation's hash is never a load→turn target;
|
||
|
||
---
|
||
|
||
## Proposed `campaign/board.md` rows
|
||
|
||
New row:
|
||
|
||
```
|
||
| THE PINNED-SEED RESIDUE IS A VISIT ORDER, IT IS WRITTEN INTO THE BLOCK, AND ONE RUN SEPARATES HEAP ORDER FROM A GLOBAL-RNG DRAW | meta | verified | high | 100% | 2026-09-09 | Resolver, on lane BP's falsified P1 (`findings/resolutions/2026-09-09-fleet-id-order-residue.md`). RULING ON BP'S RESTATEMENT: accepted and sharpened. The ids are NOT the variable - 1970/1986/2002 decode (id-allocation.md) to node 2 = Player[32]'s client, counters 123/124/125, minted in that order in every process. The variable is the ORDER in which AssignFleetsAndIssueOrders (0x006c16c0, pass 1) visits the ship groups needing a new fleet: A visited the group at Sys 384 first, B the group at Sys 80 first; 2002's group third in both. That order changes the id each group gets, the element order of lists 8/10 and the apply order (hence ShipIDs[] reordered) and NO decision on this turn. **AND IT IS WRITTEN INTO THE BLOCK**: list 10 = StrategyServer::OnCommand_TransferShips {systemId, fleetId, ships[]} records the assignment on every captured run, so C-EXACT SURVIVES LITERALLY with one more free input: (save, seeds, visit order) -> byte match; C-set's shape covers the uncaptured case (canonical order = one member of the outcome set). ENGINE: (1) `state_checksum --relabel-new-ids <pre.sav>` - bijection on that turn's new node-k ids matched by (location, ship set), master id lists as sets, Summary/Checksum masked WITH REASON, refuses to relabel anything in the pre-turn save; accept = BP's pair IDENTICAL modulo {1970<->1986}, AD's pair still DIVERGED; (2) game/ai visits groups canonically (ascending systemId, then min ship id) and mints in that order; (3) optional captured-order override for exact replay. RULING ON POINTER ORDER: not yet leading - ONE OF TWO. The record already names two unnamed AI-band consumers of the per-process global generator 0x00af6e58 (0x0079ea90+0x73 Chance, 0x005b9f00+0xc0; AI1's list) which the seed pin does not touch. What moved: pointer order now has a MEASURED PRECONDITION - BP's aielem dumps carry node heap addresses, and runs A and B (identical config, identical decisions) show the same run of consecutive same-size allocations in DIFFERENT relative address orders (list 3: A idx1<idx0, idx3<idx2; B idx1>idx0, idx3>idx2) - Win8+ LFH randomisation, live in this process. Precondition, not cause. Wall clock has no mechanism (AIProcessMinTime is a trailing Sleep; AI turn is synchronous in OnResumePlaying). THE PROBE (lane BR, VM146, one fresh process, no build, ~12 min): BP's shim.cfg.bppin + `airng=on` (pin_seed off) - the OnResumePlaying bracket attributes EVERY draw on EVERY generator in each client's turn by return address and generator (airngcensus rows, foreign_words/foreign_calls); lane I's inlined-draw inventory has NO site in the AI band so the observer is complete there; airng is measured behaviour-neutral (roll-parity.md). It is also run C. PREDICTIONS: P1 autosave in {e913ff41, 724528ff}; P2 foreign_words=0 in all three brackets, no census row on 0xaf6e58 or Seed record #2; P4 aiorders diff = noise set + at most the transposition. Foreign draw -> its retaddr names the consumer, follow-up = global pin (RNG_Seed(0xaf6e58,K) after Initialize) x2, predict identical. None -> RNG excluded by measurement; stage 2 = one static read of 0x006c16c0 + the gathering hub TO PLACE A HOOK on the group walk, log (element addr, systemId, minted id) in visit order x2 processes, predict order = ascending address. RUNG C restated (backlog text in §6); RUNG B not restated - value confirmed, replay is insulated by construction, and a `aiorders.deep=on` run of BP's pinned procedure landing on e913ff41 is the first rich-turn record. RULE 26(c): BP's wording corrected - the measured fact is "no client", NumOwn==0 is NOT shown to gate (496/512 have NumOwn==0 AND clients). NEW COROLLARY 26(d): when the instrument cannot be removed, pin to a natural run's observed values and require byte-identity with it before reading a pinned pair; the agreement is the pin's control AND a noise mask. No rule 29. STRUCTURAL FIX for BP's shim.cfg trap: an unnamed registered hook under a pin/capture config is a config error; print installed/registered counts. VM144 held for stage 2 / the global pin |
|
||
```
|
||
|
||
Edits to existing rows:
|
||
|
||
- **Row 360** — take BP's proposed scope note, then append: `RESOLVER 2026-09-09: the residual is the AI's fleet-group VISIT ORDER, observable in list 10 of the block; C-exact survives with it as a third input. Heap order vs global-RNG draw separated by lane BR (VM146).`
|
||
- **Row 415** — take BP's proposed append; add: `The 35 are a visit order, not a decision; see the resolver row of 2026-09-09.`
|
||
- **Row 326** — take BP's proposed append unchanged.
|
||
- **Row 333** — replace BP's proposed append with: `C-EXACT RESTATED 2026-09-09 (resolver, on lane BP): determined up to the fleet-group visit order in AssignFleetsAndIssueOrders, which the block itself records (list 10); engine visits canonically, comparison relabels new ids (state_checksum --relabel-new-ids). Rung B unchanged and strengthened.`
|
||
- **Row 424** (AR's oracle pair) — take BQ's proposed append unchanged; add: `Resolver 2026-09-09: confirmed. The load-form pair enters determinism-hashes.txt; the continuation form is not an engine target. Every rung's byte-match claim is load->turn.`
|
||
- **BQ's three proposed new rows** — accept as written, with one wording change in the third: replace `Read the predicate only for a non-eliminated AI player with NumOwn > 0` with `Read the predicate only for a player that actually runs an AI client (RunAI's net-id list); NumOwn is a proxy not shown to gate - 496/512 have NumOwn == 0 and clients.`
|
||
- **BQ's `determinism-oracle.md` correction** (`/CD[1]/NPrvVa` +5.00 per load) — accept; `--mask resave` gains the leaf with its reason, and `--relabel-new-ids` (§3.1) must compose with it.
|
||
- **Row 62** — VM146: lane BR (probe, then the deep Rung-B record); VM144: lane BS (BQ's per-history residue, both routes, `aiorders` + `airng` + task-entry probes); VM145: FREE (BQ released, restored); VM141: AZ unchanged; VM140 untouched.
|