L4: predictions for the research-selection tie set, before the instrument

This commit is contained in:
lane-l4 2026-09-08 18:06:31 -04:00
parent c769394b57
commit ec8d841dba

View file

@ -244,3 +244,95 @@ happened to.
5. **The dump's own coverage check**: for every list it walks the node chain *and* reads `_Mysize`,
and logs a `MISMATCH` line if they disagree. A wrong list layout would otherwise print a
confident, wrong zero (rule 1).
---
# Addendum — the research-selection tie set
Added after the first two runs, before the research instrument was written. The coordinator's
question: if the one varying AI decision is a **tie** broken by something per-process, the
original's possible outcomes form a small enumerable set, and a deterministic `game/ai` can pick
canonically and claim *"our choice is one of exactly k the original can produce, and here are all
k"*. That is a stronger claim than behavioural equivalence, and it needs the candidate set.
## What the selection site actually is (read before predicting)
Process Turn phase 18 is `0x006caf70`. It tries three producers in order and takes the first
non-null:
```
eax = 0x006a84f0(agent) ; producer A
if (!eax) eax = 0x006c27c0(agent) ; producer B
if (!eax) eax = 0x006c8890(agent, &agent+0x13c); producer C -- the candidate walk
if (eax && eax != player->+0x294) cl_SetResearchTarget(<eax+4 as a C string>)
```
`player->+0x294` is the current target, and `cl_SetResearchTarget 0x00578f60` takes the tech's
**name string**, not an id — the object's `+0x4` is a `std::string` and the caller resolves the
short-string union before pushing it.
Producer C, `0x006c8890`, has two halves:
**C1, the candidate walk.** `if (player->+0x294 != 0) return 0`. Otherwise build a `std::vector`
of 0x0c-stride candidates with `0x006c2490` — which constructs a working object, fills it
(`0x006bcca0`), emits the vector (`0x006bc500`, a nested walk over groups, **no `std::sort` at that
level**) and destroys it — then walk the vector **front to back** calling
`0x006c8580(ecx = the target slot, edx = cand[1], stack: agent, cand[0])` and **return the first
one that answers non-zero**. First-acceptable in arrival order. No score, no comparator, no tie
resolution: **whatever the vector's order is, is the answer.**
**C2, the fallback**, reached only when the walk accepts nothing: `0x006b36e0(agent) & 0x80000007`
indexes one of two eight-entry `.data` tables, and the value there seeds a three-arm rotation
`(i + seed) % 3` over `0x006c8670`. Both tables are in the image and both hold values in {0,1,2}:
```
0x00a1a544 = 2 0 2 0 2 1 2 0
0x00a1a564 = 0 2 0 2 1 0 2 0
```
`0x006b36e0` is not an RNG call — it reads `player->+0xf4`, calls `0x0080da80` and computes. So C2's
"roll" is a **hash of player state**, and its outcome space is at most **three arms**.
## Predictions
### P10 — where the variation lives
**Prediction: `0x006c8890` (producer C) is entered once per AI player on turn 1 and the variation is
in C1's candidate vector, not in C2.** Specifically, across two runs of the same workload:
* players 32 and 496 (stable across five runs between lanes L4 and L5) produce **identical candidate
streams in identical order**, and the accepted candidate sits at the same position;
* player 512 produces a candidate stream that is **the same set in a different order**, and the
first-acceptable one is therefore a different tech.
*Falsified if:* the streams are identical for 512 and the accepted position still differs ⇒ the
decision is inside `0x006c8580`, i.e. state or a seed, and arrival order is not the mechanism.
*Also falsified if:* `0x006b36e0` fires for 512 ⇒ the pick came from **C2**, which is not a tie at
all but a three-arm rotation seeded by a state hash, and `k <= 3` by construction.
### P11 — k is enumerable, and small
**Prediction: the tie set for player 512 is the set of candidates in its stream that
`0x006c8580` would accept, and the stream is short — tens, not thousands.** A player with no
colonies and no research history has few reachable techs.
*Falsified if:* the stream is hundreds of entries ⇒ "name all k" is not a practical verification
claim for this decision and the honest move is to mask the leaf, as lane L5 already does.
### P12 — the two stable players are the control
**Prediction: 32 and 496 accept a candidate at position 0 or very near it**, because a player whose
choice never varies is one whose first candidate is always acceptable. If instead they accept deep
in the stream and are still stable, then order is stable for them and unstable for 512 — which
would point at 512's *container*, not at the walk.
### P13 — what our engine should do
If P10 holds, a deterministic `game/ai` sorts the candidate vector by a canonical key (the tech
name, which is what the original ships in the command anyway) before the walk. The verification
claim becomes: **our pick is the canonical member of the tie set; every observed original run picks
some member of the same set; here is the set.** If P10 is falsified in the C2 direction instead, the
claim is different and weaker in kind but stronger in size: three arms, both tables in the image,
so k <= 3 and all three are nameable without any capture at all.
*Either way the capture names k.* That is the point of running it.