From 2338ad40dded8d8ce0708db5abc0e64b4bda9388 Mon Sep 17 00:00:00 2001 From: alex Date: Wed, 9 Sep 2026 03:41:44 -0400 Subject: [PATCH] board: the gather ordering contract is bound in the engine --- campaign/DASHBOARD.md | 26 +++++++++++++------------- campaign/board.md | 1 + 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/campaign/DASHBOARD.md b/campaign/DASHBOARD.md index d84def3..82880db 100644 --- a/campaign/DASHBOARD.md +++ b/campaign/DASHBOARD.md @@ -1,17 +1,17 @@ # SotS RE campaign — coverage dashboard -Generated 2026-09-09 07:39 UTC · `sots-re` @ 696935e,2026-09-09 · `sots-engine` @ dea127d,2026-09-09 (242 commits) · regenerate with `tools/dashboard.py` +Generated 2026-09-09 07:41 UTC · `sots-re` @ d6f725a,2026-09-09 · `sots-engine` @ 45b16ef,2026-09-09 (244 commits) · regenerate with `tools/dashboard.py` > **North star:** A functional reimplementation of the engine — behavior-equivalent, NOT byte-for-byte ## 1. Map coverage (campaign/board.md) -442 targets · mapped-or-better **394/442** `[█████████░] 89%` · verified **352/442** `[████████░░] 80%` +443 targets · mapped-or-better **395/443** `[█████████░] 89%` · verified **353/443** `[████████░░] 80%` | Status | Count | % | |---|---:|---:| -| verified | 352 | 80% | -| mapped | 42 | 10% | +| verified | 353 | 80% | +| mapped | 42 | 9% | | in-progress | 5 | 1% | | backlog | 41 | 9% | | blocked | 2 | 0% | @@ -21,7 +21,7 @@ Generated 2026-09-09 07:39 UTC · `sots-re` @ 696935e,2026-09-09 · `sots-engine | objects | 46 | 6 | 0 | 3 | 1 | 56 | | control-flow | 41 | 2 | 1 | 0 | 0 | 44 | | subsystems | 4 | 8 | 0 | 2 | 1 | 15 | -| engine | 31 | 0 | 0 | 0 | 0 | 31 | +| engine | 32 | 0 | 0 | 0 | 0 | 32 | | verify | 105 | 15 | 3 | 35 | 0 | 158 | | phase2 | 13 | 3 | 1 | 0 | 0 | 17 | | meta | 93 | 6 | 0 | 1 | 0 | 100 | @@ -47,7 +47,7 @@ Generated 2026-09-09 07:39 UTC · `sots-re` @ 696935e,2026-09-09 · `sots-engine | Module | LOC | Test files | Checks | Wired | Doc | |---|---:|---:|---:|---|---| | `app` | 4,838 | 9 | 207 | yes | N-output-term.md | -| `game/ai` | 2,089 | 8 | 322 | yes | L4-ai-orders.md | +| `game/ai` | 2,128 | 8 | 322 | yes | L4-ai-orders.md | | `game/combat` | 582 | 2 | 34 | yes | L2-predictions.md | | `game/config` | 558 | 4 | 131 | yes | M1.md | | `game/data` | 2,077 | 12 | 462 | yes | game-data.md | @@ -61,12 +61,12 @@ Generated 2026-09-09 07:39 UTC · `sots-re` @ 696935e,2026-09-09 · `sots-engine | `mars/stream` | 5,834 | 8 | 329 | yes | mars-stream.md | | `mars/text` | 899 | 8 | 245 | yes | mars-text.md | | `mars/vfs` | 788 | 9 | 140 | yes | mars-vfs.md | -| `shim` | 16,295 | 0 | 0 | direct (WIN32) | H-probes.md | -| `shim/hooks` | 13,189 | 0 | 0 | direct (WIN32) | L1-predictions.md | +| `shim` | 16,709 | 0 | 0 | direct (WIN32) | H-probes.md | +| `shim/hooks` | 13,593 | 0 | 0 | direct (WIN32) | L1-predictions.md | | `shim/trace` | 2,258 | 9 | 273 | direct (WIN32) | shim-trace.md | -| **total** | **58,647** | **122** | **4257** | | | +| **total** | **59,504** | **122** | **4257** | | | -Board `engine:` rows: verified **31**, mapped 0, in flight 0 (of 31) — verified & merged `[██████████] 100%` +Board `engine:` rows: verified **32**, mapped 0, in flight 0 (of 32) — verified & merged `[██████████] 100%` ## 5. Phase 2 milestones @@ -133,11 +133,11 @@ Most recent open: ## 9. Delta since previous dashboard -- verified targets: 351 → 352 (+1) · mapped-or-better: 393 → 394 (+1) -- engine LOC: 58,647 → 58,647 (+0) · test files: 122 → 122 (+0) · checks: 4,257 → 4,257 (+0) +- verified targets: 352 → 353 (+1) · mapped-or-better: 394 → 395 (+1) +- engine LOC: 58,647 → 59,504 (+857) · test files: 122 → 122 (+0) · checks: 4,257 → 4,257 (+0) - addresses verified: 1,163 → 1,163 (+0) · recovered layouts: 384 → 384 (+0) · open questions: 26 → 26 (+0) - standalone leaves closed: 45 → 45 (+0) · leaves still diverging: 63 → 63 (+0) --- warnings: board.md: unknown types subsystems; mars-rng.md: no oracle total row parsed; mars-stream.md: no oracle total row parsed; mars-vfs.md: no oracle total row parsed - + diff --git a/campaign/board.md b/campaign/board.md index 7c02f4f..fdba644 100644 --- a/campaign/board.md +++ b/campaign/board.md @@ -447,3 +447,4 @@ Status flow: `backlog → in-progress → mapped → verified` (or `blocked`). | TWO PREDICTIONS FAILED, BOTH BECAUSE THE LOAD ROUTE DOES LESS THAN ANYONE EXPECTED - and one block difference nobody predicted | meta | verified | high | 100% | 2026-09-09 | Lane BS, from the same runs. (1) **P1's list-14 clause FALSIFIED**: the continuation's turn-39 block has NO list 14 AT ALL - list 14 is emitted on turn 38 (six elements, for the two fleets minted there), and on turn 39 `IssueRouteForFleets` is entered twice and emits nothing. So "a list-8 element and its list-14 pair" was wrong about the shape of a move order. (2) **P2's "probe 11 is entered in BOTH routes" FALSIFIED**: `AssignFleetsAndIssueOrders` is entered 2x in the continuation and **0x** in the load. The brief and I both expected the load to run the same machinery to a different answer; it does not run the machinery. (3) **UNPREDICTED AND IT STRENGTHENS THE RESULT**: the block ALSO differs by one `list=12` element, fleet **914**, which is the counterpart of the OTHER save-side divergence (`Flt[914 "Nu Fleet III"]` HLay False->True, Lay only-in-load). I predicted half the block difference and the other half was there waiting - which is what turns "the AI emitted a move order" into "**every** diverging leaf is in the block". List 12's elements are fleet ids (274 290 306 322 626 642 786 882 [914] 930 1746 2338); its meaning is NOT decoded and the causal direction is NOT established (rule 20) - one watchpoint would settle it | | A TURN THAT MINTED TWO NEW FLEET IDS DID NOT VARY - four processes, byte-identical, on a turn whose visit order was exercised | control-flow | verified | med | 100% | 2026-09-09 | Lane BS, VM144, a by-product of the continuation run. Turn 38 of the AR lineage IS a fleet-assignment turn: `AssignFleetsAndIssueOrders` entered **6x**, list 10 carries four `{systemId, fleetId}` elements `(384,2466) (384,2178) (336,2482) (384,2338)`, and decoding by `id = (counter << 4) | node` (Player[32] = node 2) all four are CLIENT-MINTED - with **2466 (counter 154) and 2482 (counter 155) absent from `ar-turn37-816raiders.sav`**, i.e. two fleets minted on this turn, in that order. That turn's output is `15b99255...`, produced by **lane AR's three `hooks=off` processes AND this instrumented run - four processes, byte for byte**. So the fleet-group visit order was exercised over two new fleets and its **outcome set was 1**. This does NOT contradict lane BP (different turn, different groups, and BP's outcome set of 2 rests on two observations) but it BOUNDS the claim: **a turn that mints new fleet ids is not thereby a turn whose visit order varies.** One turn, one datum, handed to lane BR for stage 2 - if the container is address-keyed, this turn is a case where two same-size allocations landed in the same relative order in four processes, which the LFH randomisation model has to accommodate | | THE AI'S FLEET-VISIT ORDER IS AN ADDRESS-KEYED CONTAINER AND THE KEY IS THE FLEET'S `Location*` - H1 CONFIRMED, H1b DEAD, and the campaign's two-file outcome set IS the two orderings of two heap pointers | control-flow | verified | high | 100% | 2026-09-09 | Lane BU, VM146, stage 2 of the chain lane BR unblocked (`findings/control-flow/fleet-visit-order-mechanism.md`). Build `bu-55408a9-20260909T071101Z`, fresh CT111 tree (rule 24), lane worktree (rule 21), `shim.cfg.bupin` = `shim.cfg.bppin` with EXACTLY THREE non-comment lines added (`aivisit=on/.out/.ships`), diffed against the real bppin PULLED OFF THE GUEST and quoted in the finding, marked `# exhaustive` so `tools/check_shim_configs.py` ENFORCES that all 27 registered hooks are named. THREE fresh processes, route LOAD, seeds pinned to lane L's observed values; 28 of 28 hooks off, zero unknown keys, zero COVERAGE, 3/3 `pinned=1` in every run, three `aivisit ... MH_OK` in every run, `exe base=0x005e0000` for the 8th process on this guest. THE HOOK: `AssignFleetsAndIssueOrders 0x006c16c0` walks a `std::vector` by index (cursor `[ebp-0x38]`, `_Mylast` REFETCHED every iteration, back-edge `jne 0x6c17e0` at 0x006c21be), and that walk is BRACKETED BY TWO LOOPS OVER THE SAME VECTOR that call one function each - **`StrategyAIAgent::ClaimShipsOfFleet 0x006a4290` and `ReleaseShipsOfFleet 0x0069da10`, which have EXACTLY ONE CALLER EACH IN THE WHOLE IMAGE and it is that function**. So the instrument is two ordinary function-entry detours plus a bracket on 0x006c16c0, with NO return-address filter and NO mid-function patch - the loop body could not be patched anyway, its first instruction 0x006c17e6 IS a branch target. **RESULT: the visit order is NOT ascending element address (0 of 6 multi-element sequences over three processes), NOT ascending fleet id (0/6), NOT ascending systemId (0/6), NOT ascending minimum ship id (0/6). It is ASCENDING `fleet->Location` (StarFleet+0xa0): 6 OF 6**, with the fleet pointer breaking the one tie (two fleets at system 384 share one interned Location object and are ordered by element address within it). So the container is address-keyed and the comparator is lexicographic in (Location*, StarFleet*), BOTH heap addresses. **P3 - the committed prediction, "the visit order is ascending element address" - IS FALSIFIED, and the hypothesis class it tested is CONFIRMED with a sharper key.** THE CROSS-PROCESS PROOF, and it does not rest on one sample: three processes visited THE SAME SIX FLEETS IN THREE DIFFERENT ORDERS (run1 1970/1602/1986/354/1570/1346; run2 1602/1970/1986/354/1346/1570; run3 1970/1986/1602/354/1570/1346) and EVERY difference is a pair whose Location addresses exchanged relative order. Runs 1 and 2 wrote BYTE-IDENTICAL autosaves and still visited in different orders - the reordered pairs were pre-existing fleets, which is why the save did not move. **AND THE BRANCH FLIP IS MEASURED: run 3's `loc(system-80 group) = 0x333ed4c8 < loc(system-384 group) = 0x333ed710`, the OPPOSITE of runs 1 and 2, it visited system 80 first, and its autosave is `724528ff...` BYTE-IDENTICAL (cmp) to lane BP's run B - while runs 1 and 2 have `loc(384) < loc(80)`, visit 384 first, and are BYTE-IDENTICAL to BP's runs A and L.** So the campaign's two-file outcome set on `ad-turn27` IS the two orderings of two heap pointers. **H1b (an uninitialised-word comparator) is EXCLUDED for this path**: an uninitialised key cannot reproduce a strict total order by a specific live pointer field in 6 of 6 sequences and cannot track that field across a branch flip. **A SECOND FINDING THE STATIC READ HAD MISSED: the newly minted fleet ids are ALREADY ON THE FLEET OBJECTS when the head loop first touches them** (`fid=1970` on the element at idx=0 of the first burst, before the pass gate and before any emission), so `AssignFleetsAndIssueOrders` INHERITS the id-to-group pairing rather than deciding it; the mint is upstream in the gather (`AcquireFleetsForTask 0x006ceef0` + 0x006abf80/0x006b7c90/0x006cb310), and since the delivered vector is Location-ordered and the ids follow that order, the natural reading is that the SAME container orders the mint (INFERENCE - the gather was not hooked). ENGINE CONSEQUENCE, and it MOVES deliverable 2 by one level: the canonical order must be imposed WHERE THE GROUP SET IS GATHERED AND THE IDS ARE MINTED, not at the assignment step, or the engine canonicalises the emission order while leaving the minting order to its own container. On the choice of canonical order this lane has evidence rather than convention: the original's order is NOT ascending systemId and NOT ascending minimum ship id in ANY of the three processes, so THERE IS NO ORIGINAL ORDER TO MATCH and `(systemId, min ship id)` is as good as any - keep it, and keep the tie-break, because the original's own tie-break is a second pointer. RULE 26(d) AND RULE 19, BOTH MET BY MEASUREMENT: runs 1 and 2 are BYTE-IDENTICAL to lane BP's UNPINNED run L, so a process carrying a seed pin PLUS three new detours inside the AI's fleet-assignment path reproduced a natural run byte for byte, twice; run 3 reproduced BP's other branch byte for byte. `aivisit` is behaviourally neutral on this state, measured, not argued. P1/P2/P4/P5 all hold; under BP's noise mask every run's block differs from BP's same-branch run in NOTHING outside the noise set and from the other branch in EXACTLY the three transposition words. Rule 17 discharged: 0x006c16c0's body is 0x006c16c0-0x006c247a `ret` with a FOUR-ENTRY JUMP TABLE living PAST the ret at 0x006c247c (target of `jmp [eax*4+0x6c247c]` at 0x006c22fb). Six more per-process AI seeds, none shared - L1 reproduced a 6th and 7th time. Artefacts: `verify/results/shim/bu/`; the three output saves are byte-identical to `bp-pinA-turn28.sav` x2 and `bp-pinB-turn28.sav` x1 and are deliberately not duplicated | +| the fleet-gather ordering contract is BOUND IN THE ENGINE, not just recorded | engine | verified | high | 100% | 2026-09-09 | Lane BU's finding turned into a thing the engine cannot get wrong later. `game/ai` takes `fleetId` as a **parameter** - it emits orders and does not form fleets or mint ids - so **the gather/mint subsystem does not exist yet**, and building it now to satisfy a contract would be speculative. Instead the requirement sits in `src/game/ai/orders.h` at the emission layer where the gather will attach, with the three consequences that bind: **(1) order the groups by a key that is a function of the SAVE and mint in that order**, `(systemId, min ship id)` with a total tie-break - **and say plainly that there is NO original order to match** (the original's is neither, in any of three processes), so we are choosing one member of its outcome set, not recovering a hidden rule; **(2) impose it AT THE GATHER, not at assignment** - the fleets already carry their ids when the assignment walk first touches them, so canonicalising there is one level too low and fixes nothing; **(3) a byte-exact replay needs the original's order and the block already carries it** - list 10 records `{systemId, fleetId, ships[]}` per group in visit order, so the optional override is three words per new fleet, the same cost class as pinning a seed. Lane BU's `aivisit` instrument merged; engine `45b16ef`, host 59/59 and shim both green |