sots-re/findings/subsystems/relabel-new-ids.md
alex d46b3f8391 state_checksum: --relabel-new-ids, comparing a pair modulo this turn's new id labelling
Implements the 2026-09-09 fleet-id-order resolution, section 3 item 1.  Given the
pre-turn save, compute the ids new in each post-turn save, match the client-minted
(node nibble != 0) new fleets by a key that does not mention the id -- (LocID or
FPlan destination, sorted ship-id set) -- build the bijection pi, rewrite every
fleet reference, compare the master id lists as sets, mask /Summary/Checksum with
its reason on the line, and print pi.

Acceptance, both halves:
  bp-pinA vs bp-pinB     IDENTICAL modulo pi = {1970<->1986}   (35 leaves -> 0)
  ad-oracle-A vs -B      REFUSED, then DIVERGED: 94 leaves     (unchanged)

Five guards, every one refusing rather than degrading: only ids absent from the
pre-turn save; only non-zero node nibbles; pi must permute one set; content keys
must correspond one-to-one and be unique per side; and no leaf anywhere may hold a
permuted id at an unmodelled site (matched on raw bytes, not the reader's typed
value).  A refusal rewrites nothing and falls back to the ordinary comparison.

Three corrections to the specification from contact with the data, in
findings/subsystems/relabel-new-ids.md section 4: FtName is an id-attached label
and needs the same treatment as the id; relabelling the Flt[] keys is the wrong
operation (exchange the bodies -- the fleet table is id-ordered and identical in
both saves); a node's new-id set spans object kinds.

Default path proven unchanged: pre- and post-change modules agree on the root
digest, coverage, mask hits and every (path, digest) in the tree over all 43 saves
under two policies, and on 5,602 lines of CLI stdout across every mode.

Also fixes a pre-existing, unrelated test failure: the re-save localisation test
enumerated pairs over sorted filenames and hard-coded the direction 4 -> 0, which
a later corpus addition reversed.  Suite 38 -> 62 tests, all passing.
2026-09-09 04:12:01 -04:00

374 lines
27 KiB
Markdown

# `state_checksum --relabel-new-ids`: comparing two post-turn saves modulo this turn's new id labelling
Lane BT, 2026-09-09. **Host only — no VM was touched.** VM140 (reference guest) untouched; VM141,
VM144 and VM146 were held by lanes AZ, BS and BR throughout and were not approached.
Implements `findings/resolutions/2026-09-09-fleet-id-order-residue.md` §3 item 1, verbatim as the
specification and with the two engine-side items (§3.2 canonical visit order, §3.3 the
`--ai-visit-order` override) deliberately **not** started — they belong in `sots-engine` and the
resolver ranked them after this one.
Consumes: `findings/control-flow/pinned-seed-rich-turn.md` (lane BP — the acceptance data),
`findings/subsystems/id-allocation.md` (the id scheme), `findings/control-flow/raid-gate-multiplicity.md`
(lane AD — the negative acceptance data).
Touches only `verify/state-checksum/*` and this file.
---
## 0. Result
Both halves of the acceptance pass, and the second half passes **because a guard fires**, not
because the matcher happened to find nothing.
| half | pair | verdict |
|---|---|---|
| **positive** | `bp-pinA-turn28` vs `bp-pinB-turn28`, pre `bp-turn28-pre` | `IDENTICAL modulo pi = {1970<->1986}` — 35 leaves → **0**, and π is exactly the one transposition |
| **negative** | `ad-oracle-A-post` vs `ad-oracle-B-post`, pre `ad-turn27-two-raiders` | `REFUSED` (the two runs minted different id **sets**), then `DIVERGED: 94 leaf difference(s)` — byte-for-byte the same 94 the tool reports without the flag |
Existing behaviour is unchanged when the flag is absent: over every save in
`verify/results/saves/` under both `--floats bits --mask none` and `--floats canonical --mask
resave`, the pre-change and post-change modules produce identical root digests **and identical
per-node digests at every node of the tree** (§5.1).
---
## 1. What was built
`verify/state-checksum/state_checksum.py` gains one flag:
```sh
state_checksum.py POST-A.sav POST-B.sav --relabel-new-ids PRE-TURN.sav
```
The operation, in the order the code does it:
1. Parse all three saves. **Prove coverage on the unmodified parse** (`audit_coverage` runs before
any rewrite), so `coverage: PROVED` still means what it has always meant: every byte of the
file is a function of the digest's inputs.
2. `new(X) = { fleet ids in X } \ { fleet ids in PRE }`, restricted to **node nibble ≠ 0** — the
client-minted space (`id-allocation.md` §1: `id = (counter << 4) | node`, node 0 is the sim
that owns the board, node `PlyrIdx + 1` is a player's own client).
3. Key each new fleet by a content key that does not mention its id:
**`(LocID if non-zero else FPlan/pnd destination, sorted ship-id set)`**, with the two
location kinds tagged apart so a fleet *standing at* system 384 never keys the same as one
*bound for* 384.
4. Build **π** from the key correspondence, one node at a time.
5. **Exchange the matched fleets' bodies between their slots** (see §4.2 — this is not what the
spec said, and the spec was wrong), keeping each slot's id and its id-attached label `FtName`.
6. Rewrite every *reference* to a permuted id under π: a ship's `FltID` back-reference and a
system's `Flt` entry.
7. Compare `ShipIDs[]` / `FleetIDs[]` / `DesignIDs[]` as **sets**.
8. Mask `/Summary/Checksum`, **with the reason printed on its own line**.
9. Print π and the verdict.
New public surface: `RelabelRefused`, `Relabelling`, `build_relabelling`, `apply_relabelling`,
`format_permutation`, `id_node`, and `checksum_result` (the parse/digest split that lets the mode
parse once, audit, then rewrite).
The relabelled root is **domain-separated** — `relabel-new-ids` is folded into the root preimage —
so a "modulo π" root can never be mistaken for a strict one. The extra preimage word is appended
*only* in this mode, so every root recorded before the flag existed is bit-for-bit unchanged.
---
## 2. Acceptance, positive half — verbatim
```
$ uv run python3 verify/state-checksum/state_checksum.py \
verify/results/saves/bp-pinA-turn28.sav verify/results/saves/bp-pinB-turn28.sav \
--floats bits --mask none \
--relabel-new-ids verify/results/saves/bp-turn28-pre.sav
A a83c425215b6d7e88bc998f4948081e7 verify/results/saves/bp-pinA-turn28.sav
B a83c425215b6d7e88bc998f4948081e7 verify/results/saves/bp-pinB-turn28.sav
policy: floats=bits mask=none reader=74fd562c75e2679e
relabel: pre-turn save verify/results/saves/bp-turn28-pre.sav
relabel: pi = {1970<->1986}
relabel: 1970 (counter 123, node 2) -> 1986 at 384, ships [6976]
relabel: 1986 (counter 124, node 2) -> 1970 bound-for 80, ships [5264]
relabel: 2002 (counter 125, node 2) -> 2002 bound-for 384, ships [6992]
relabel: 3 new client-minted fleet id(s) on node(s) [2]; 2 relabelled, 2 fleet body/bodies exchanged, Fltx1, FltIDx2 rewritten
relabel: /Summary/Checksum is MASKED: it is derived from the whole state and its inputs are unmodelled (rule 18's open item), so it cannot be recomputed under a relabelling and its move is not independent evidence. Unmask by dropping --relabel-new-ids.
relabel: Master id lists (ShipIDs/FleetIDs/DesignIDs) are compared as SETS: their element order is the order the turn's commands were applied in, which is the same visit-order residue. Membership is still exact -- an added or removed id still reports.
IDENTICAL modulo pi = {1970<->1986}
$ echo $?
0
```
Read the per-id lines: the counters printed are the decode the resolver did by hand
(`123/124/125`, node 2 = Player[32]'s client), and the content keys are what carries the match —
the group **at system 384 with ship 6976** is fleet 1970 in A and fleet 1986 in B; the group
**bound for system 80 with ship 5264** is 1986 in A and 1970 in B; the group bound for 384 is 2002
in both. Exactly the resolver's §2.1 reading, recovered from the saves alone.
Without the flag the same pair is `DIVERGED: 35 leaf difference(s)` (regression-locked by
`test_POSITIVE_the_same_pair_still_diverges_without_the_flag`, which asserts the count is 35). So
the mode removes **35 leaves and no others**: `IDENTICAL modulo one transposition and nothing else`.
---
## 3. Acceptance, negative half — verbatim, and why it is the right kind of failure
```
$ uv run python3 verify/state-checksum/state_checksum.py \
verify/results/saves/ad-oracle-A-post.sav verify/results/saves/ad-oracle-B-post.sav \
--floats bits --mask none \
--relabel-new-ids verify/results/saves/ad-turn27-two-raiders.sav
A 1faf04cad2bbf2d7e5ecf560b8807504 verify/results/saves/ad-oracle-A-post.sav
B 53043168ad03cae13355db9f5f381b50 verify/results/saves/ad-oracle-B-post.sav
policy: floats=bits mask=none reader=74fd562c75e2679e
relabel: pre-turn save verify/results/saves/ad-turn27-two-raiders.sav
relabel: REFUSED -- the new client-minted fleet ids differ between the two saves -- A minted [1970, 1986], B minted [1970, 1986, 2002]; only in B [2002]. That is a difference in what was allocated, not in how it was labelled, so no relabelling is defensible
relabel: NO relabelling was applied; the comparison below is the ordinary one, and nothing has been absorbed.
DIVERGED: 94 leaf difference(s)
/Summary/Checksum: -1043321805 -> 1995746704
/Sim/DesignIDs[]: removed [2002], added [2018] (85 -> 85 entries)
/Sim/FleetIDs[]: removed [], added [2002] (58 -> 59 entries)
/Sim/ShipIDs[]: list-reordered
/Sim/ModCount: 1496 -> 1500
... 89 more ...
$ echo $?
1
```
94 is the number the tool reports for that pair **without** the flag, unchanged. On a refusal
nothing is rewritten, nothing is masked and no list is set-compared: the refusal path *is* the
ordinary comparison. That is deliberate — a mask applied on a run whose relabelling was refused
would be a mask nobody asked for.
**The refusal is by cardinality, not by content.** AD's run A minted **two** client fleets and run
B minted **three**. The guard that fires is G3 (π must permute one set), and it fires before the
content matcher is ever consulted. That matters, because it means AD's pair does **not** exercise
the content key negatively. I exercised that separately, two ways (§4.3, §5.3): a synthetic pair
whose new ids agree and whose contents differ, and — the belt-and-braces version — **forcing the
transposition onto AD's pair with the guard bypassed**, which still leaves **82 differing
leaves**, including `DesignIDs[]`, `ModCount`, four `turnstats` rows, both AI build queues and
`Player[32]`'s money. The guard is not the only thing standing between AD's pair and a green
verdict; it is the first of several.
---
## 4. Where the specification did not survive contact
Four places. The first two changed the implementation; the third and fourth are scope notes the
next lane needs.
(Lane BU landed mid-lane, confirming H1 — the visit order is an address-keyed container whose key
is the fleet's `Location*`. That settles the *cause* of the residue and changes nothing here: the
comparison side quotients the residue out regardless of what produces it, and it must go on
refusing when the difference is not a relabelling. The forward notes in §4.1 and §4.2 are
addressed to the engine-side canonical order and stand as written.)
### 4.1 `FtName` is an **id-attached label**, and "every id-valued leaf" does not reach it
In *both* of BP's saves, fleet **1970 is "Sigma Fleet VI"** and **1986 is "Tau Fleet VI"**. The
name follows the **id**, not the ship group. It is a Greek-letter ordinal minted from a per-player
counter that advances with the same pass that mints the id, so it is exactly as much a function of
the visit order as the id is — and exactly as little a decision.
Consequence: applying π only to id-valued leaves would move the group at system 384 from slot 1970
to slot 1986 **carrying the name "Sigma Fleet VI"**, and compare it against B's 1986 "Tau Fleet
VI". Two spurious leaf differences, and the acceptance "and nothing else" would have failed.
Handled by keeping the name with the slot rather than with the body, and **gating it**: the mode
refuses unless `FtName` is the same at every matched id in both saves. If the name does not track
the id, something real differs and the tool must not paper over it
(`test_a_name_that_does_not_track_the_id_is_refused`).
Cross-check that this is a naming *counter* and not a coincidence, from AD's pair: A minted two
client fleets, so A's node-0 fleet **7120 is "Upsilon Fleet VI"**; B minted three, so B's **7120 is
"Phi Fleet VI"**. The Greek sequence is **per player and shared between the client's and the
server's fleets** — one more thing an engine-side canonical visit order (§3.2 of the resolution)
has to reproduce, and one the resolver's item 2 does not currently mention.
### 4.2 Applying π to the `Flt[]` **keys** introduces a spurious `reordered`
The master fleet table's element order, and the `FleetIDs[]` list, are **identical in A and B**
(measured: both are `[…, 6960, 1970, 1986, 2002, 7120, …]`) and are a function of the id, not of
the visit order. Renumber A's slots under π and A's table reads `…1986, 1970, 2002…` against B's
`…1970, 1986, 2002…`, which the differ correctly reports as a reorder — an artefact of the tool,
not of the saves.
The equivalent operation with no artefact is to **exchange the fleet bodies between the slots**,
leaving slot ids, `FleetIDs[]`, and every object label exactly as the file wrote them. That is what
the implementation does. It is the same quotient, taken from the other side.
Worth carrying into the engine-side item: **the fleet table is id-ordered, not visit-ordered**, so
canonicalising the visit order in `game/ai` will reorder lists 8/10 and the apply order, and will
*not* reorder the save's fleet table.
### 4.3 "new ids of the same node" is not a fleet-only id space
`ad-oracle-A-post` carries **2002 as a `DesignID`**; `ad-oracle-B-post` carries **2002 as a
`FltID`**. The per-node counter is shared across object kinds, so a node's new-id set spans fleets,
designs and (in principle) ships. This mode matches **fleets only**, as the spec says. What keeps
that safe is guard G5 (§5.2): any leaf anywhere holding a permuted id at a site the tool does not
model as a fleet reference is a **refusal**. Anyone extending the mode to designs must read this
paragraph first — the two spaces interleave.
### 4.4 "list-8/10/14 words when comparing command blocks" is **not** implemented, and does not belong here
Lists 8/10/14 live in the `aiorders` **shim dump**, not in a `.sav`. `state_checksum.py` parses
saves; it has no reader for the block. Wiring π into a block comparison belongs wherever those
dumps are differed (lane BP did it by hand under its proven noise mask, `pinned-seed-rich-turn.md`
§2). Recorded as not-done with the reason rather than silently dropped.
### 4.5 Two smaller notes
- **Master id lists as sets is unconditional in this mode**, including when π is the identity.
`ShipIDs[]`'s element order is the apply order, which is the same residue; membership stays
exact, so an added or removed id still reports. It is nonetheless a real weakening and it is
printed on every run. A lane that wants that order back should reach for a separate flag, not
for this one.
- **The `--mask resave` composition the resolver asked for works**: `--mask resave
--relabel-new-ids …` on BP's pair reports `[masked: Checksumx1, Statusx8]` and
`IDENTICAL modulo pi = {1970<->1986}`. The relabel mask is added to whichever preset is in
effect and de-duplicated against it.
---
## 5. The guards, and how each was made to fire
Five guards. **Every one refuses; none degrades to a partial relabelling.** A refusal prints its
reason, applies nothing, and runs the ordinary comparison — the conservative direction, because a
real divergence then still reports.
| | guard | how it was made to fire |
|---|---|---|
| **G1** | only ids **absent from the pre-turn save** may be relabelled | `test_ids_present_in_the_pre_turn_save_are_never_in_pi`; and the strong form, `test_two_pre_turn_fleets_that_swapped_contents_are_NOT_absorbed` — two *pre-existing* fleets exchanging contents is the very shape this mode absorbs, and it must still report |
| **G2** | only ids with a **non-zero node nibble** | `test_server_minted_ids_are_never_relabelled` — two node-0 (server) fleets exchanging contents still diverges |
| **G3** | π must be a **permutation of one set** — so the two saves must have minted the same ids | `test_different_new_id_sets_are_refused` (synthetic: one run mints a third fleet) and **lane AD's real pair** (§3) |
| **G4** | the content keys must **correspond one-to-one**, and be **unique on each side** | `test_new_fleets_whose_contents_differ_are_refused`; `test_an_ambiguous_content_key_is_refused` (two new fleets with the same location and ship set) |
| **G5** | **no leaf anywhere** in either save may hold a permuted id at a site the tool does not model | `test_an_unmodelled_reference_site_is_refused` (a `SomeOtherRef` leaf holding 1970); `test_the_completeness_scan_matches_raw_bytes_not_the_typed_value` |
| — | `FtName` must track the id in both saves | `test_a_name_that_does_not_track_the_id_is_refused` (§4.1) |
| — | a refusal leaves both parse trees **untouched** | `test_a_refusal_leaves_both_trees_untouched` — the digest of A before and after a refused `build_relabelling` is identical |
### 5.1 The default path is unchanged
Checked two ways, because "the tool is used by several lanes and by the determinism oracle":
- **Digest equivalence, whole corpus.** The pre-change module (`git show HEAD:…`) and the
post-change module were run over all **43** `.sav` files in `verify/results/saves/` under
`--floats bits --mask none` and under `--floats canonical --mask resave`, comparing the root
digest, the coverage record, the mask hits, **and the `(path, digest)` of every node in the
tree**. `saves checked: 43 mismatches: 0`.
- **CLI stdout equivalence, every mode.** Single-save; `--tree --depth 3`; `--json`; the BP pair;
the AD pair at `--limit 500`; `turn2/turn3 --mask resave --ulps 2`; `--record-chain` and
`--chain` over turns 1/2/3. **5,602 lines, byte-identical** between the two modules (only the
chain file's own path normalised).
The one change that *could* have moved a recorded root — folding a policy word into the root
preimage — is appended **only** when the new mode is active. An intermediate version of this work
appended an empty word unconditionally, which moved the root; that was caught by the equivalence
check above, which is the reason it exists.
### 5.2 G5 matches raw bytes, not the reader's typed value — on purpose
The digest is a function of *(save bytes, reader schema)*, never of the bytes alone (STATE_CHECKSUM.md
§2.4). A leaf the schema happens to type as a float or a raw blob still carries the id, and a
value-based completeness scan would not see it — a guard a typing slip can silently empty is the
"green verdict over an empty region set" of earned rule 1. This is not hypothetical: while
building the synthetic fixture, the reader typed a `Flt` reference as a **float** (`2.76e-42`, the
denormal whose bit pattern is 1970) and a value-based scan missed it. Matching on the raw four
bytes catches it. The cost is that a genuine 4-byte float in the denormal range around a permuted
id would refuse — a refusal is the safe direction, and no such value exists in the corpus.
### 5.3 The strongest negative: bypass the guard and it still diverges
`test_NEGATIVE_even_forcing_a_partial_relabelling_cannot_hide_it` hands `apply_relabelling` the
transposition `{1970↔1986}` it *would* have built if G3 did not exist, applies it to AD's A save,
and compares under the full "modulo π" policy (checksum masked, master lists as sets). **82 leaves
still differ.** The guards are the tool's honesty, not its only defence.
### 5.4 Tests
24 new tests (20 synthetic in `RelabelTest`, 4 real-save in `RealSaveTest`), **7 of which assert a
refusal**. The whole suite is 38 → **62 tests, all passing**:
```
$ uv run python3 -m unittest test_state_checksum.RelabelTest # 20 tests, OK, 0.07 s
$ uv run python3 -m unittest test_state_checksum.RealSaveTest.test_POSITIVE_… \
…test_NEGATIVE_… # 4 tests, OK, 88.7 s
$ uv run python3 -m unittest discover -s . -t . # 62 tests, OK
```
### 5.5 One pre-existing test failure, fixed, that is not about this change
The first full-suite run failed one test —
`test_known_resave_delta_localises_to_five_named_leaves` — and **the pre-change module fails it
identically**, so it is not a regression. The test enumerates candidate pairs over *sorted
filenames* and asserted the re-save direction as `4 -> 0`; `cb-turn2to3-endturn.sav`, added to the
corpus by an earlier lane, sorts before `turn2-state.sav`, so that pair reads `0 -> 4`. The claim
is symmetric and the assertion now is too (all four `Status` leaves must still move the *same*
way, with `Checksum` following by ∓16). Recorded rather than quietly patched, per earned rule 11.
---
## 6. What this lane did NOT do
1. **No VM, no game, no build.** Host only. The three held guests were not approached.
2. **Neither engine-side deliverable** (`sots-engine` `game/ai` canonical visit order; the
`--ai-visit-order` override). Out of scope by the brief, and ranked after this one by the
resolver.
3. **No command-block comparison** (§4.4).
4. **Designs and ships are not relabelled**, only fleets — per the spec. §4.3 says what has to be
read before that changes.
5. **`Summary/Checksum` is still unmodelled.** It is masked with its reason, not solved.
STATE_CHECKSUM.md §4.6 records the two derivations already ruled out; nobody should re-run them.
6. **π's *existence* is not evidence that the two runs agree on the turn.** It is evidence that
they agree *up to the labelling of the ids created on it*. The claim the mode licenses is
exactly the resolver's C-set shape and no more.
---
## 7. Reproducing every number here
```sh
cd ~/sots-re
S=verify/results/saves
# positive
uv run python3 verify/state-checksum/state_checksum.py \
$S/bp-pinA-turn28.sav $S/bp-pinB-turn28.sav --floats bits --mask none \
--relabel-new-ids $S/bp-turn28-pre.sav
# negative
uv run python3 verify/state-checksum/state_checksum.py \
$S/ad-oracle-A-post.sav $S/ad-oracle-B-post.sav --floats bits --mask none \
--relabel-new-ids $S/ad-turn27-two-raiders.sav
# guards
cd verify/state-checksum && uv run python3 -m unittest -v test_state_checksum.RelabelTest
```
---
## Proposed board rows
New row:
```
| STATE_CHECKSUM --relabel-new-ids: BP'S PINNED PAIR IS IDENTICAL MODULO ONE TRANSPOSITION, AD'S UNPINNED PAIR IS REFUSED AND STILL DIVERGES | tooling | verified | high | 100% | 2026-09-09 | Lane BT, HOST ONLY (no VM; 140 untouched, 141/144/146 held by AZ/BS/BR and not approached). Implements the 2026-09-09 fleet-id-order resolution §3 item 1 (`findings/subsystems/relabel-new-ids.md`). `state_checksum.py POST-A POST-B --relabel-new-ids PRE` computes the ids NEW in each post-turn save, restricts to node nibble != 0 (client-minted; `id-allocation.md` id=(counter<<4)|node), matches those fleets by a key that does not mention the id - (LocID if non-zero else FPlan/pnd destination, sorted ship-id set), with "at 384" and "bound-for 384" tagged apart - builds the bijection pi, rewrites every fleet reference, compares ShipIDs/FleetIDs/DesignIDs as SETS, masks /Summary/Checksum WITH ITS REASON ON THE LINE, and prints pi. **POSITIVE: `bp-pinA` vs `bp-pinB` = `IDENTICAL modulo pi = {1970<->1986}`, 35 leaves -> 0, exit 0**, with pi recovered from the saves alone and printed with the counters (123/124/125, node 2) and the content keys - the group AT Sys 384 with ship 6976 is 1970 in A and 1986 in B; the group BOUND FOR Sys 80 with ship 5264 is 1986 in A and 1970 in B; 2002's group is 2002 in both. **NEGATIVE: `ad-oracle-A/B-post` REFUSED and `DIVERGED: 94 leaf difference(s)`, exit 1** - byte-identically the 94 the tool reports without the flag, because on a refusal NOTHING is rewritten, masked or set-compared. The refusal is by CARDINALITY (A minted 2 client fleets, B minted 3), so AD's pair does not exercise the content matcher; that is exercised separately, and FORCING the transposition onto AD's pair with the guard bypassed STILL LEAVES 82 LEAVES. FIVE GUARDS, EVERY ONE REFUSES RATHER THAN DEGRADES: G1 only ids absent from the pre-turn save (tested with two PRE-EXISTING fleets exchanging contents - must still report); G2 only non-zero node nibbles (node-0 swap still reports); G3 pi must permute ONE set; G4 content keys correspond one-to-one and are unique per side; G5 NO leaf anywhere may hold a permuted id at an unmodelled site, matched on RAW BYTES not the reader's typed value (a value scan is empty the moment the schema types a reference as a float - this happened during development). Refusal prints its reason and falls back to the ORDINARY comparison, never to a partial relabelling. THREE SPECIFICATION CORRECTIONS FROM CONTACT WITH THE DATA: (1) **FtName is an ID-ATTACHED LABEL** - 1970 is "Sigma Fleet VI" and 1986 "Tau Fleet VI" in BOTH saves, a Greek ordinal minted by the same pass as the id, so "apply pi to every id-valued leaf" would have produced two spurious name diffs; the name stays with the SLOT, gated by a guard requiring it to track the id in both saves. The counter is PER PLAYER AND SHARED WITH THE SERVER'S FLEETS - AD's A (2 client fleets) names node-0 fleet 7120 "Upsilon", B (3) names it "Phi" - which the engine-side canonical visit order must reproduce and §3.2 does not mention. (2) **Applying pi to the Flt[] KEYS introduces a spurious `reordered`**: the fleet table and FleetIDs[] are ID-ordered and IDENTICAL in A and B, so renumbering slots reorders A against B; the artefact-free equivalent is to EXCHANGE THE FLEET BODIES between slots, leaving slot ids, FleetIDs[] and every label as written. (3) **A node's new-id set is NOT fleet-only**: 2002 is a DesignID in ad-oracle-A-post and a FltID in ad-oracle-B-post - the per-node counter is shared across object kinds, and G5 is what keeps a fleet-only matcher safe. NOT DONE, with reasons: lists 8/10/14 (they are aiorders SHIM DUMP words, not save leaves - this tool has no block reader); designs/ships; the two engine-side items. DEFAULT PATH PROVEN UNCHANGED: pre- and post-change modules compared over the whole save corpus under two policies on root digest, coverage, mask hits AND every (path,digest) in the tree - zero mismatches; plus byte-identical CLI stdout on the single-save, BP, AD and resave invocations. The relabelled root is DOMAIN-SEPARATED so a "modulo pi" root can never be read as a strict one. 24 new tests (20 synthetic + 4 real-save acceptance), 7 of them asserting a refusal; suite 38 -> 62, all passing. ALSO FIXED, PRE-EXISTING AND NOT A REGRESSION (the pre-change module fails it identically): `test_known_resave_delta_localises_to_five_named_leaves` enumerated pairs over SORTED FILENAMES and hard-coded the re-save direction 4->0; `cb-turn2to3-endturn.sav` sorts before `turn2-state.sav`, so that pair reads 0->4 and the test failed on a corpus fact. Assertion is now direction-symmetric (all four Status leaves must still move the SAME way, Checksum following by -/+16). Artefacts: `verify/state-checksum/state_checksum.py`, `test_state_checksum.py`, `STATE_CHECKSUM.md` §2.6 |
```
Edits to existing rows:
- **Row 424 / the resolver row of 2026-09-09** — append: `ENGINE ITEM 1 IS DONE (lane BT, host):
state_checksum --relabel-new-ids ships with both acceptance halves green - BP's pair IDENTICAL
modulo {1970<->1986}, AD's pair refused and still 94-leaf DIVERGED. Three corrections to §3.1
from the data: FtName is an id-attached label and needs the same treatment as the id (and its
counter is per-player and shared with the server's fleets, which §3.2's canonical visit order
must also reproduce); relabelling the Flt[] keys is the wrong operation - exchange the bodies,
because the fleet table is id-ordered and identical in both saves; and a node's new-id set spans
object kinds (2002 is a DesignID in ad-oracle-A-post and a FltID in ad-oracle-B-post). Lists
8/10/14 are not in scope for this tool - they are shim-dump words, not save leaves.`
- **Row 333 (C-exact)** — append: `Comparison side delivered 2026-09-09 (lane BT):
`state_checksum --relabel-new-ids <pre.sav>`, guarded so it refuses rather than absorbs; see
findings/subsystems/relabel-new-ids.md.`
- **`determinism-oracle.md`'s "Qualified" paragraph** — when it is written, "modulo π" now has an
executable definition and a printed π; cite `relabel-new-ids.md` §1 for the operation and §5 for
the guards.
- **`id-allocation.md` §5** — alongside the canonical-visit-order item the resolver asked for, add
the two measured facts from §4.1/§4.2 of this document: the **fleet-name ordinal** is per player
and shared between client- and server-minted fleets, and the **master fleet table is id-ordered**,
not visit-ordered.
- **Row 62** — VM140/141/144/146 unchanged by this lane; it took no guest.