Compare commits

..

4 commits

Author SHA1 Message Date
alex
4293200615 board: roll parity is false at five scopes; the cheap game/ai does not exist; cl_RandFloat hidden by a tail jump 2026-09-08 19:39:06 -04:00
alex
e966a72fd9 PAR: record the gates, and the one difference between the deployed and committed builds
clean_room_check OK, host ctest 55/55, CT111 shim cross-build OK on a fresh build directory, all
three as separate commands. The deployed instrument (par4) differs from the committed source only
by two RVA literals that the named constants resolve to identically -- said here rather than left
for a reader to find. Also flags that main moved under the branch, so the header must be
regenerated at merge, not hand-resolved.
2026-09-08 19:37:56 -04:00
alex
4e76ec24bb PAR: reconcile the turn1-state outcome set with lane CB's concurrent pinned runs
CB pinned the seeds and got d59bb9f2 three times; this lane left the seeds free, watched client
512's single research tie-break draw resolve with a different number of rejections in each process
(1, 3, 1 words), and still got d59bb9f2 three times. Different experiments, agreeing in a way that
constrains the mechanism: whatever maps that drawn word onto a research target is NOT a uniform
index into a six-member shortlist, or three unpinned runs agreeing would be 1-in-36 on top of CB's
1-in-36. One line resolves it -- log the chosen tech id beside the draw in a probe on 0x006a8390,
whose cl_RandRange bound IS the shortlist size the k question is about.
2026-09-08 19:37:31 -04:00
alex
198695c4cc PAR: the load-time stream offset also varies between processes, and the five-scope table
Adds the lifetime-census result: the three AI clients arrive at their first turn 9 / 411 / 421
words into their streams while the human's generator is at word 0, and client 32 started its turn
at block index 9 in one process and 11 in another FROM THE SAME SAVE -- the rejection loops it runs
during Prepare Turn resolve differently under different seeds. So a client's generator position at
the start of a turn is not a function of the save either.

Also records what pinning the seed showed (the same three words, and the oracle bytes unchanged --
so every one of those words is a draw whose result never reaches the save, measured directly for
the first time), the facade call-site attribution that names client 512's single draw as the
research-target tie-break at 0x006a8495, and one observation this lane could NOT resolve: three
fresh processes on turn1-state gave the same autosave although 512's stream demonstrably differed,
which does not reproduce lane L5's three-different-files result on that pair.
2026-09-08 19:36:10 -04:00
2 changed files with 66 additions and 22 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, four AI clients, five fresh
- **Confidence:** high on the negative (measured on two boards, three AI clients, six runs in six 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 four scopes
## 0. The verdict, at five 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 and at the turn.** There is no scope at which an AI client's
word count is fixed.
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.
| scope | claim | measured |
|---|---|---|
@ -28,6 +28,7 @@ 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
@ -230,19 +231,30 @@ 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 An observation this lane could not resolve
### 3.5 The `turn1-state` outcome set is not being sampled the way the campaign assumes
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.
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).
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`.
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.
---
@ -365,11 +377,20 @@ 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 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.
**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.
---
@ -481,4 +502,21 @@ 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`
- 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).