lane A2: prediction for the alliance mask and ModCount, written before the build
This commit is contained in:
parent
b2bad30f6c
commit
49ae628220
1 changed files with 105 additions and 0 deletions
105
docs/A2-alliance-and-modcount.md
Normal file
105
docs/A2-alliance-and-modcount.md
Normal file
|
|
@ -0,0 +1,105 @@
|
||||||
|
# Lane A2 — the alliance mask (`S04`) and `ModCount`
|
||||||
|
|
||||||
|
Lane A2, 2026-09-08. Branch `wip/alliance`, off `main` `b2bad30` (lane N's output term already
|
||||||
|
merged). Host + static only; lane A2 holds no VM.
|
||||||
|
|
||||||
|
**This section is the prediction, written before the implementation, per earned rule 2.**
|
||||||
|
Everything below the horizontal rule at §3 is the result.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. The prediction — `S04`, the alliance / shared-vision mask
|
||||||
|
|
||||||
|
The spine's fourth phase is a per-player pre-pass that rebuilds one word of each player's turn
|
||||||
|
record from scratch, every turn, before anything else in the turn can read it. Read from the
|
||||||
|
instruction stream (not the decompiler), the phase is:
|
||||||
|
|
||||||
|
```
|
||||||
|
for i in 0 .. numPlayers-1: # i is the player's POSITION IN THE SERVER VECTOR
|
||||||
|
p = players[i]
|
||||||
|
rec = p.turnRecord
|
||||||
|
rec.allianceMask = 0
|
||||||
|
rec.allianceMask |= (1 << i)
|
||||||
|
if p.allianceId != -1:
|
||||||
|
rec.allianceMask |= p.allianceMembers
|
||||||
|
```
|
||||||
|
|
||||||
|
so, as one expression,
|
||||||
|
|
||||||
|
```
|
||||||
|
almem[i] = (1u << i) | (alid[i] != -1 ? al[i] : 0)
|
||||||
|
```
|
||||||
|
|
||||||
|
Both inputs are on the wire: they are the two leading ints of the player's second `Team` item
|
||||||
|
(`ALid`, `AL`). The output is on the wire too — it is the `almem` field of the player's
|
||||||
|
turn-statistics element for the turn.
|
||||||
|
|
||||||
|
**The prediction, in the form the test will check it:** over the whole eleven-save corpus, for
|
||||||
|
every player of every save, for the archive element whose `trn` equals the save's own frame,
|
||||||
|
|
||||||
|
> `almem == (1 << playerVectorIndex) | (ALid != -1 ? AL : 0)`
|
||||||
|
|
||||||
|
with **zero** mismatches, and with `playerVectorIndex` being the player's ordinal position in
|
||||||
|
`/Sim/players` — **not** its `PlyrIdx`.
|
||||||
|
|
||||||
|
Committing this closes the 8 `almem` leaves that lane Y measured as regressing `T36`, and moves
|
||||||
|
`T36` from "right in six fields, wrong in five" to "right in seven fields, wrong in four".
|
||||||
|
|
||||||
|
### 1.1 Why the bit index is the vector position and not `PlyrIdx`
|
||||||
|
|
||||||
|
The shift count is the loop induction variable, which is the index used to subscript the player
|
||||||
|
vector on the same iteration. Nothing loads `PlyrIdx` anywhere in the phase. Lane T recorded this
|
||||||
|
correctly and it is restated here because it is the single most likely place for a
|
||||||
|
reimplementation to be quietly wrong: every *other* per-player index in the tail (the turn-results
|
||||||
|
array, the battle tally, the archive key) uses `PlyrIdx`, and this one does not.
|
||||||
|
|
||||||
|
### 1.2 What this phase does NOT do
|
||||||
|
|
||||||
|
It does not read, and does not write, `NA` or `CF` — the other two ints of the same `Team` item.
|
||||||
|
It does not consult the game's alliance-enabled flag (`EnAl`). It writes exactly one word.
|
||||||
|
|
||||||
|
## 2. Falsification
|
||||||
|
|
||||||
|
Each row is a way the model above can be wrong, and the symptom that would show it.
|
||||||
|
|
||||||
|
| # | how the model could be wrong | symptom |
|
||||||
|
|---|---|---|
|
||||||
|
| F1 | the bit is `1 << PlyrIdx`, not `1 << vectorIndex` | on any save where the two orders differ, `almem`'s low bit pattern is off by a permutation. **Zero mismatches on a save where they agree proves nothing** — the check must report how many saves actually separate the two |
|
||||||
|
| F2 | the guard is on something other than `ALid != -1` (e.g. on `AL != 0`, or on the game's `EnAl`) | indistinguishable while no player is in an alliance. Only a save with a live alliance can separate them |
|
||||||
|
| F3 | `AL` is a mask over `PlyrIdx` bits while the self-bit is a vector-position bit | `almem` would mix two index spaces. Same blind spot as F2: unobservable without an alliance |
|
||||||
|
| F4 | phase 36 (`FinalizeTurnRecords`) overwrites `almem` after phase 4 sets it | mismatches everywhere, and the stored value would correlate with something else entirely |
|
||||||
|
| F5 | the archived element for the save's own frame was written by the **load** path rather than by the tail (`FinalizeTurnRecords` runs in both) | the self-bit would be missing, because the load path does not run spine phase 4. Expect `almem == 0`, or a value that is stale by one turn |
|
||||||
|
| F6 | the shift wraps | `shl` masks its count to 5 bits, so a 32nd player would set bit 0. No save has more than 8 players; the model is untested above 31 and says so |
|
||||||
|
| F7 | the corpus never exercises the alliance term at all | then this lane has closed the **self-bit** and *nothing else*, and must say so under rule 6 rather than claim the rule |
|
||||||
|
|
||||||
|
F7 is the one I expect to bite. The prior is that no save in the corpus has a player in an
|
||||||
|
alliance, in which case the measured agreement tests only `1 << i`, and the `|= AL` arm is a
|
||||||
|
**hypothesis** read from the instruction stream with no exercised evidence. That is a real result
|
||||||
|
and it is a smaller one than "the alliance mask is closed", and it will be reported as the smaller
|
||||||
|
one.
|
||||||
|
|
||||||
|
## 3. The prediction — `ModCount`
|
||||||
|
|
||||||
|
`ModCount` is a **modification counter**, not a turn number and not a phase counter, and the count
|
||||||
|
a turn adds is a function of the *command stream*, not of the pre-turn state.
|
||||||
|
|
||||||
|
**The prediction:**
|
||||||
|
|
||||||
|
1. Every writer is an **entry-block increment inside a `StrategySim` command-application method** —
|
||||||
|
the `OnCommand_*` family. The bump is unconditional and happens *before* the handler validates
|
||||||
|
its arguments, so a command that fails validation still advances the counter.
|
||||||
|
2. The two turn drivers each add exactly 1.
|
||||||
|
3. `ProcessTurn` phase 1 adds 1 per system that passes its gate; the gate is closed on all eleven
|
||||||
|
saves, so it contributes 0 to every measured turn.
|
||||||
|
4. Therefore **`ModCount` is not derivable from the pre-turn save**, and `S00`/`T00`'s two bumps
|
||||||
|
are the only part of it this standalone can ever produce without modelling the AI's order
|
||||||
|
stream. The remaining 10–42 bumps a turn are commands.
|
||||||
|
|
||||||
|
**Falsification for §3:** if the delta were a function of the pre-turn state, then two saves with
|
||||||
|
the same state shape would show the same delta. `turn1→turn2` and `turn2→turn3` both show exactly
|
||||||
|
12, which is *consistent* with a state function and is the strongest evidence against my reading;
|
||||||
|
a third pair from the same game showing a different delta with an unchanged board would settle it.
|
||||||
|
The counter-evidence I already hold is that `human-turn2→turn3` is 28 and `zuul-turn16→turn17` is
|
||||||
|
16 on boards whose system and player counts are identical (28 systems, 7 players) — so it is not a
|
||||||
|
function of the board size. If someone finds a closed-form rule over the pre-turn save that fits
|
||||||
|
all eleven saves, this section is wrong.
|
||||||
Loading…
Add table
Reference in a new issue