Compare commits

..

No commits in common. "429320061596e74f413e5cc9b063a8295af6276c" and "6e34c72906503ae5815d1b10c7c295680ecc9709" have entirely different histories.

2 changed files with 22 additions and 66 deletions

File diff suppressed because one or more lines are too long

View file

@ -2,7 +2,7 @@
- **Type:** subsystem (live measurement + static classification)
- **Status:** **verified — the answer is NO**, at every scope the question can be asked at
- **Confidence:** high on the negative (measured on two boards, three AI clients, six runs in six fresh
- **Confidence:** high on the negative (measured on two boards, four AI clients, five fresh
processes, with the instrument's autosaves byte-identical to the published oracle);
medium on the completeness of the site inventory (§6 says what is not covered)
- **Owner / date:** lane PAR · 2026-09-08 · VM140 (reference) + VM145
@ -16,11 +16,11 @@
---
## 0. The verdict, at five scopes
## 0. The verdict, at four scopes
**The hypothesis is false, and it is false in the strongest available way: it fails at the
primitive, at the site, at the client, at the turn, and even at the point where the turn begins.**
There is no scope at which an AI client's word count is fixed.
primitive, at the site, at the client and at the turn.** There is no scope at which an AI client's
word count is fixed.
| scope | claim | measured |
|---|---|---|
@ -28,7 +28,6 @@ There is no scope at which an AI client's word count is fixed.
| **per client, across turns** | one client costs the same each turn | client 32: **3** on turn 2, **7** on turn 1 |
| **per client, across processes (path fixed)** | one client's count is a property of the board | client 512, turn 1, **one and the same `cl_RandRange` call** (the research tie-break, `0x006a8495`): **1 word** in one process, **3 words** in another |
| **per site** | a site that is reached always costs the same | `RNG_Chance` costs **0 words** at `p ≤ 0` and `p ≥ 1`; `RNG_NextInt` is an **unbounded rejection loop** |
| **at the start of the turn** | a client's stream position is a function of the save | client 32 began its turn at block index **9** in one process and **11** in another, from the same file |
The two shadow empires (496, 512) own nothing at all — no colonies, no fleets, no systems — and
they still do not agree with each other: on turn 1, 496 spends **0** and 512 spends **1 call
@ -231,30 +230,19 @@ It is also the sharpest available warning against the wrong conclusion: **"the c
when the seed changed" is not evidence for roll parity.** The count is stable here because the
*path* was stable. §3.2 holds the path fixed and moves the seed, and the count moves.
### 3.5 The `turn1-state` outcome set is not being sampled the way the campaign assumes
### 3.5 An observation this lane could not resolve
All three `turn1-state` runs produced the same post-turn autosave, `d59bb9f2fd0eb535` — **and they
were unpinned**, each process drawing its own AI seeds, with client 512's stream demonstrably
different between them (1 word, 3 words, 1 word: the same single `cl_RandRange` call resolving with
different numbers of rejections, so different generator values reached the decision).
All three `turn1-state` runs produced the same post-turn autosave, `d59bb9f2fd0eb535`, even though
client 512's stream demonstrably differed between them (1 word, 3 words, 1 word). Lane L5 reports
**three different files** from three runs of this pair on VM146, and `campaign/board.md` records
`turn1-state → turn2` as non-deterministic on that basis.
Lane CB, concurrently and on a different guest, reports the same file from **three pinned** runs,
and notes that lane L5's own `hooks=off` run produced it too. So the tally on that workload now
stands at roughly **seven observations of `d59bb9f2` against lane L5's report of three different
files**, and `campaign/board.md`'s "`turn1-state → turn2` is non-deterministic" is true but much
narrower than it reads.
The new thing this lane can add is that **the draw differed and the outcome did not**. Lane CB
pinned the seed and removed the variation; this lane left the seed free, watched the variation
happen *inside* the generator, and still got one file. Those are different experiments and they
agree in a way that constrains the mechanism: whatever maps client 512's drawn word onto a research
target is **not a uniform index into a six-member shortlist**. If it were, three unpinned runs
agreeing would be a 1-in-36 coincidence on top of lane CB's 1-in-36.
Not resolved, and it is one line to resolve: log the chosen tech id beside the draw in a probe on
`0x006a8390`, whose `cl_RandRange(0, n-1)` bound `n` is the shortlist size the whole `k` question is
about. Lane CB's own honest note — that player 512's target `282` is outside the named set and
"k = 6, all six nameable" is not closed — is the same open item seen from the outcome side.
Both cannot be a complete description. Three identical outcomes from a k = 6 tie set (lane L4) is a
1-in-36 coincidence, so "it is deterministic after all" is not supported either. The honest
statement is that **the draw differs and the outcome did not**, on this guest, three times — and
that whatever maps the drawn word onto the chosen tech is not a plain uniform index into the
shortlist. Flagged, not resolved; it wants the tech id logged next to the draw, which is one line in
a probe on `0x006a8390`.
---
@ -377,20 +365,11 @@ Listed so a later lane does not double-count them:
as a 0/1/2 selector, while the four real `AIObject` sinks read `[pkt+0]` as the code — and none of
those four draws anything.)*
**The load-time cost is large, asymmetric, and itself not fixed.** With the lifetime census on the
canonical pair, the three AI clients arrive at their first turn already **9, 411 and 421** words
into their streams (build `par4`, in which the nested double count is corrected), while the human
client's generator is at word 0. And the figure moves between processes: client 32 began its turn at
block index **9** in one run and **11** in another, because the rejection loops it runs during
Prepare Turn resolve differently under different seeds.
So the AI's generator position at the start of a turn is **not** a function of the save. Two loads of
the same file put the same client at two different stream offsets. That is a second, independent
reason a reimplementation cannot reach a byte match on an AI empire by counting draws.
`g_GlobalRNG 0x00af6e58` shows **3 words drawn, one per AI client construction** — a second,
independent falsification of `ai-turn-logic.md` §5.1's "every draw from it returns 0", after lane
L1's, and from a different instrument.
The load-time cost is large and asymmetric. Measured with the lifetime census on the canonical pair:
the three AI clients arrive at their first turn already **17, 424 and 427** words into their
streams, and the human client's generator is at 0. `g_GlobalRNG 0x00af6e58` shows **3 words drawn,
one per AI client construction** — a second, independent falsification of
`ai-turn-logic.md` §5.1's "every draw from it returns 0", after lane L1's.
---
@ -502,21 +481,4 @@ extend it.
`par-{br-p1,br-p2}` (canonical pair, unpinned, two fresh processes),
`par-pin-a1` (canonical pair, pinned), `par-{t1-br-p1,t1-br-p2,t1-br-p3}` (`turn1-state`, three
fresh processes, the third with façade attribution), plus `parse_airng.py`, the reader
- Addresses: `ghidra/addresses.d/lane-par.json` (2 entries; validated to a scratch path,
1,219 → 1,221, no duplicate names and no same-address-different-name)
### Gates, run as separate commands on a fresh build directory (rules 13, 24)
* `tools/clean_room_check.sh` — **OK**
* host `ctest` — **55/55**
* CT111 shim cross-build — **OK**, `build-shim` removed first, `par-gate-20260908T2330Z`
The build **deployed to the guests** was `par4-6ba2c79-20260908T2322Z`, which differs from the
committed source only in that the two façade hook targets were written as RVA literals rather than
`sots::addr::cl_Chance` / `cl_RandRange`. Those constants resolve to the same two RVAs
(`0x00178cf0`, `0x001798e0`), so the gate build and the measured build are the same instrument;
the rename is recorded here rather than left for a reader to notice.
`sots-engine` `main` moved to lane CB's merge while this lane ran, so the generated header on
`wip/par` was regenerated against the pre-merge base. It must be **regenerated, never
hand-resolved**, at merge time (rule 14).
- Addresses: `ghidra/addresses.d/lane-par.json`