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) - **Type:** subsystem (live measurement + static classification)
- **Status:** **verified — the answer is NO**, at every scope the question can be asked at - **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); 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) medium on the completeness of the site inventory (§6 says what is not covered)
- **Owner / date:** lane PAR · 2026-09-08 · VM140 (reference) + VM145 - **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 **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 primitive, at the site, at the client, at the turn, and even at the point where the turn begins.**
word count is fixed. There is no scope at which an AI client's word count is fixed.
| scope | claim | measured | | 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 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 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** | | **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 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 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 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. *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 All three `turn1-state` runs produced the same post-turn autosave, `d59bb9f2fd0eb535` — **and they
client 512's stream demonstrably differed between them (1 word, 3 words, 1 word). Lane L5 reports were unpinned**, each process drawing its own AI seeds, with client 512's stream demonstrably
**three different files** from three runs of this pair on VM146, and `campaign/board.md` records different between them (1 word, 3 words, 1 word: the same single `cl_RandRange` call resolving with
`turn1-state → turn2` as non-deterministic on that basis. 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 Lane CB, concurrently and on a different guest, reports the same file from **three pinned** runs,
1-in-36 coincidence, so "it is deterministic after all" is not supported either. The honest and notes that lane L5's own `hooks=off` run produced it too. So the tally on that workload now
statement is that **the draw differs and the outcome did not**, on this guest, three times — and stands at roughly **seven observations of `d59bb9f2` against lane L5's report of three different
that whatever maps the drawn word onto the chosen tech is not a plain uniform index into the files**, and `campaign/board.md`'s "`turn1-state → turn2` is non-deterministic" is true but much
shortlist. Flagged, not resolved; it wants the tech id logged next to the draw, which is one line in narrower than it reads.
a probe on `0x006a8390`.
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 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.)* those four draws anything.)*
The load-time cost is large and asymmetric. Measured with the lifetime census on the canonical pair: **The load-time cost is large, asymmetric, and itself not fixed.** With the lifetime census on the
the three AI clients arrive at their first turn already **17, 424 and 427** words into their canonical pair, the three AI clients arrive at their first turn already **9, 411 and 421** words
streams, and the human client's generator is at 0. `g_GlobalRNG 0x00af6e58` shows **3 words drawn, into their streams (build `par4`, in which the nested double count is corrected), while the human
one per AI client construction** — a second, independent falsification of client's generator is at word 0. And the figure moves between processes: client 32 began its turn at
`ai-turn-logic.md` §5.1's "every draw from it returns 0", after lane L1's. 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-{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 `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 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).