diff --git a/campaign/DASHBOARD.md b/campaign/DASHBOARD.md index 123264a..1856112 100644 --- a/campaign/DASHBOARD.md +++ b/campaign/DASHBOARD.md @@ -1,16 +1,16 @@ # SotS RE campaign — coverage dashboard -Generated 2026-09-09 14:13 UTC · `sots-re` @ d4e9b58,2026-09-09 · `sots-engine` @ 52db23c,2026-09-09 (250 commits) · regenerate with `tools/dashboard.py` +Generated 2026-09-09 14:24 UTC · `sots-re` @ d9b06ca,2026-09-09 · `sots-engine` @ 7741d42,2026-09-09 (252 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) -453 targets · mapped-or-better **404/453** `[█████████░] 89%` · verified **361/453** `[████████░░] 80%` +454 targets · mapped-or-better **405/454** `[█████████░] 89%` · verified **362/454** `[████████░░] 80%` | Status | Count | % | |---|---:|---:| -| verified | 361 | 80% | +| verified | 362 | 80% | | mapped | 43 | 9% | | in-progress | 5 | 1% | | backlog | 41 | 9% | @@ -21,7 +21,7 @@ Generated 2026-09-09 14:13 UTC · `sots-re` @ d4e9b58,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 | 33 | 1 | 0 | 0 | 0 | 34 | +| engine | 34 | 1 | 0 | 0 | 0 | 35 | | verify | 106 | 15 | 3 | 35 | 0 | 159 | | phase2 | 15 | 3 | 1 | 0 | 0 | 20 | | meta | 95 | 6 | 0 | 1 | 0 | 102 | @@ -31,7 +31,7 @@ Generated 2026-09-09 14:13 UTC · `sots-re` @ d4e9b58,2026-09-09 · `sots-engine - RTTI type descriptors: **1,924** (`Game::` 1,404, `Mars::` 194; serializable types 179) - Classes with recovered member layouts: **384** / 1,598 named classes `[██░░░░░░░░] 24%` — `objects/layouts.json` (serializer recovery) plus classes recovered by hand in `struct-recovery.md` + `schema-gaps-resolved.md`. Note 179 types are *serializable*; the recovery also reaches non-serializable ones, so this is not a subset of that -- Functions: **41,411** (parsed from `01-fingerprint.md`); named/annotated in the **address contract** (`ghidra/addresses.json`, not Ghidra's full rename count): **1310**, verified **1179** `[█████████░] 90%` +- Functions: **41,411** (parsed from `01-fingerprint.md`); named/annotated in the **address contract** (`ghidra/addresses.json`, not Ghidra's full rename count): **1320**, verified **1187** `[█████████░] 90%` ## 3. Data layer @@ -46,7 +46,7 @@ Generated 2026-09-09 14:13 UTC · `sots-re` @ d4e9b58,2026-09-09 · `sots-engine | Module | LOC | Test files | Checks | Wired | Doc | |---|---:|---:|---:|---|---| -| `app` | 4,838 | 9 | 207 | yes | N-output-term.md | +| `app` | 5,092 | 9 | 207 | yes | N-output-term.md | | `game/ai` | 2,138 | 8 | 322 | yes | L4-ai-orders.md | | `game/combat` | 582 | 2 | 34 | yes | L2-predictions.md | | `game/config` | 558 | 4 | 131 | yes | M1.md | @@ -55,7 +55,7 @@ Generated 2026-09-09 14:13 UTC · `sots-re` @ d4e9b58,2026-09-09 · `sots-engine | `game/effects` | 973 | 3 | 231 | yes | game-effects.md | | `game/events` | 558 | 3 | 152 | yes | E-events.md | | `game/nav` | 581 | 2 | 113 | yes | — | -| `game/sim` | 4,780 | 14 | 1038 | yes | game-sim.md | +| `game/sim` | 4,918 | 15 | 1086 | yes | game-sim.md | | `mars/parse` | 875 | 12 | 277 | yes | mars-parse.md | | `mars/rng` | 273 | 0 | 0 | yes | mars-rng.md | | `mars/stream` | 6,021 | 8 | 331 | yes | mars-stream.md | @@ -64,9 +64,9 @@ Generated 2026-09-09 14:13 UTC · `sots-re` @ d4e9b58,2026-09-09 · `sots-engine | `shim` | 16,777 | 0 | 0 | direct (WIN32) | H-probes.md | | `shim/hooks` | 13,658 | 0 | 0 | direct (WIN32) | L1-predictions.md | | `shim/trace` | 2,258 | 9 | 273 | direct (WIN32) | shim-trace.md | -| **total** | **59,834** | **122** | **4259** | | | +| **total** | **60,226** | **123** | **4307** | | | -Board `engine:` rows: verified **33**, mapped 1, in flight 0 (of 34) — verified & merged `[██████████] 97%` +Board `engine:` rows: verified **34**, mapped 1, in flight 0 (of 35) — verified & merged `[██████████] 97%` ## 5. Phase 2 milestones @@ -133,11 +133,11 @@ Most recent open: ## 9. Delta since previous dashboard -- verified targets: 356 → 361 (+5) · mapped-or-better: 399 → 404 (+5) -- engine LOC: 59,514 → 59,834 (+320) · test files: 122 → 122 (+0) · checks: 4,257 → 4,259 (+2) -- addresses verified: 1,167 → 1,179 (+12) · recovered layouts: 384 → 384 (+0) · open questions: 26 → 26 (+0) +- verified targets: 361 → 362 (+1) · mapped-or-better: 404 → 405 (+1) +- engine LOC: 59,834 → 60,226 (+392) · test files: 122 → 123 (+1) · checks: 4,259 → 4,307 (+48) +- addresses verified: 1,179 → 1,187 (+8) · 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 line 459: unknown status 'open' for '**A replace of `ProcessResearch` is gated on `ServerPlayer::OnTechResearched`, and partly on a policy question**'; board.md: unknown types engine + gate, subsystems, tooling; 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 1665311..2cad91a 100644 --- a/campaign/board.md +++ b/campaign/board.md @@ -458,3 +458,4 @@ Status flow: `backlog → in-progress → mapped → verified` (or `blocked`). | **`# exhaustive` is exhaustive over the 27 template hooks only — six detours are installed, not one** | phase2 | verified | high | 100% | 2026-09-09 | Lane CR. A config naming all 27 registered hooks `off` except one, passing `tools/check_shim_configs.py`, still installs **6** detours: the M0 `Application::Initialize` asm stub (unconditional whenever `hooks != off`) and the **FPU-force module's four sampling detours** (`force=off value=0x0000 sample_ticks=on sample_turn=on`, on by default). `check_shim_configs.py` cannot see either group — no `hook.` key names them. What made lane CR's runs safe was the **neutrality check** (compare run byte-identical to two `hooks=off` controls), not the config check. A lane reading "exhaustive, therefore one detour" is wrong by five. | | **A replace of `ProcessResearch` is gated on `ServerPlayer::OnTechResearched`, and partly on a policy question** | phase2 | open | high | – | 2026-09-09 | Lane CR. To reach `replaced`, three things are needed: the ~90-field tech-effect write-back applied live (B2 has `game/effects/tech_effects` host-tested), the `ObservedTech` element constructed (`ours` already decides the append), and the two research event **records** written. The third is not an implementation gap: their `EvDsc`/`EvMsg` text comes from the game's string table, which the engine must not carry, so a byte-identical oracle on any completion turn requires calling the game's own `PostEvent` and accepting a `ComputeBudget`-shaped **QUALIFIED** `replaced`. Settle that before spending a lane. | | `# exhaustive` WAS EXHAUSTIVE OVER THE WRONG LIST - five determinism probes ran with four extra detours | meta | verified | high | 100% | 2026-09-09 | Lane CR found it: a config naming all 27 template hooks `off`, passing `tools/check_shim_configs.py`, still installed **six** detours - the M0 `Application::Initialize` asm stub (unconditional whenever `hooks != off`, nameable by nothing) and the **FPU-force module's four sampling detours** (`fpu.sample_turn`/`fpu.sample_ticks` default ON, keys that do not start with `hook.`). The checker I built this morning could not see either. **So `bupin`, `cbpin`, `crcompare`, `crreplace0`, `crreplace1` all ran with four detours they did not declare.** The measurements STAND - every one was proved byte-neutral against a control (rule 26(d)), which is what made them safe; the config check was not. Fixed: the checker now requires both FPU keys `off` in an exhaustive config and names the M0 stub in its OK line; all five configs amended. A rerun of any of them is a slightly different instrument (four fewer detours) and should say so | +| T34 RecordObservedDesigns IMPLEMENTED: THE OBSERVED-DESIGN LIST IS A MOVE-TO-BACK VECTOR CAPPED AT 20 PER DESIGN OWNER, AND THAT IS WHY IT LOOKED LIKE 55 UNRELATED LEAVES | engine | verified | high | 100% | 2026-09-09 | Lane DT, HOST ONLY (no VM; 140/141/145/146 untouched). First item of Track 2, taken from lane CV's ranked worklist (rank 4, "79 leaves, no upstream dependency"). **THE PHASE IS `0x007c2350`** (lane T's tail phase 34, at 0x007d98aa): for each player, over EVERY ship of EVERY fleet, gated on `PlyrIdx < 15` and on a TWO-BIT-PER-PLAYER word at `Ship+0x54` that is **NOT SERIALISED**, call `RecordObservedDesign` `0x007be340` -- once per SHIP, not once per design. **THREE RULES IN THAT FUNCTION ARE INVISIBLE FROM ANY SAVE AND ALL THREE CHANGE THE ANSWER:** the dedup key is the **design id ALONE** (`cmp [eax+0x8],ecx`, stride **0x10** = `sizeof(ObservedDesign)`, verified three ways); a re-observation **ERASES the record and PUSHES A COPY ON THE BACK** (0x007be618: fields copied out, `vector::erase` 0x00795c40, `push_back` 0x00799d80) rather than updating in place, so the list ends in LAST-OBSERVATION order and `otnF` survives the move -- **which is why a turn's output is a PERMUTATION of its input and not an append**, and why 55 leaves looked unrelated; and the list is **CAPPED AT 20 RECORDS PER DESIGN OWNER**, counted from the most recent end, **applied after EVERY record call, not once per sweep** (0x007be67f, `cmp [ebp-0x10],0x14; jge erase`) -- so on an empire with 28 designs in service against a cap of 20 it EVICTS AND RE-CREATES 26 RECORDS INSIDE ONE SWEEP and each re-creation resets `otnF`. A FOURTH rule is a guard on the **DESIGN'S OWNER**, not on the observer: `owner->+0xfb && !owner->+0xfc` = NPC-and-not-rebel-AI, and every observation record in every corpus save agrees (the four NPC factions own 26 designs and appear in NOBODY's list). **MEASURED, CLOSED AND REGRESSED NEVER NETTED. Rich turn** (`ad-turn27` + BR's deep tcb vs `bp-pinB-turn28`): **1092 -> 1058**, i.e. **36 CLOSED / 2 REGRESSED** against CV's run and **116 / 8** against the 1166 do-nothing baseline. The 2 are two `otnF` words at positions whose `odid` also moves -- positions that now hold a DIFFERENT record, where CV's un-reordered list matched by coincidence. The 79-leaf group is now **45**: **every one of the 21 `odes/otnL` leaves is closed** and 13 of the 17 `otnF`, while all 17 `odid` and the whole 24-leaf tech/weapon half remain. **Canonical pair** (`turn2-state` + CB's tcb vs `turn3-state`): **62 -> 61, 1 CLOSED / 0 REGRESSED**, set-differenced by leaf PATH. **CV'S "NO UPSTREAM DEPENDENCY" IS RIGHT FOR THE DESIGN HALF AND WRONG FOR THE OTHER 24 LEAVES, TWO SEPARATE WAYS.** (a) The `otch`/`owep` arms need three undecoded `std::set` builders (`0x00862c90`, `0x008629b0` -> `RecordObservedTech`; `0x008626a0` -> `RecordObservedWeapon` `0x007be1b0`); the obvious wire candidate, the per-section `DOpts` option-tech list, covers only **13 of the 18 tech names the turn moves** and misses `WEP_GrnLas`/`WEP_PlsmCan`/`WEP_Dsrptr`/`CCC_BtlCmp` and the one newly APPENDED record `BIO_TerBac` -- so it is ONE of the three sources, not all of them, and the set ORDERING decides the append order. (b) The design half is itself capped by **SHIP CONSTRUCTION**: two designs (1826 "Egg Thief Mk 3", 1522 "Bravestar Mk 2") get their FIRST ships during that turn, no phase here builds a ship, so those two observations cannot happen and neither can the two evictions they would cause -- **that is the whole 21-leaf residual**. **PROOF THAT THE MECHANISM IS NOT THE RESIDUAL: fed the TARGET's post-turn ship list, the same code leaves 1 leaf of 55, not 21.** That last leaf is unexplained and named: the original's eviction sequence differs by exactly one eviction, and the two candidates (a runtime fleet order that is not the save's, or the three OTHER `RecordObservedDesign` call sites firing mid-turn) are separated by an entry probe on 0x007be340, not by more reading. **CONTROL, RUN (rule 1): with the NPC guard compiled out the canonical pair goes 61 -> 70 and the rich turn stays at 1058** -- the guard is load-bearing on one pair and inert on the other, which is why both were run and why a lane that ran only the rich turn would have shipped the wrong rule. **COVERAGE, STATED AS LOUDLY AS THE DIVERGENCE (rules 15/23): 8 players swept, TWO produce any record at all; the canonical pair offers 16 (player, ship) pairs of which the NPC guard drops 15, leaving ONE record call on ONE design.** No observation of a FOREIGN design moves on either pair, so the visibility gate, the cross-player notify path and the multi-owner cap are **entirely unexercised** -- the ownership stand-in for the gate is a HYPOTHESIS whose falsifier is one save in which a foreign record moves. Two further early returns (`0x0080baf0`, the `0x400` design flag) are assumed false, never read. `ServerPlayer+0xfb = NPC` is by ADJACENCY to lane T's `+0xfc = RebAI` plus the wire's field order, corroborated by the corpus and by the 9-leaf control, not by reading its writer. GATE: host build 255/255, host ctest **57/59** with the SAME two pre-existing failures (`app_test_turn`: 43 saves driven, 12 write_save failures = lane DW's `usp` item; denominator intact, rule 24), clean-room OK, shim-configs OK, **shim cross-build green on the host** (rule 13), `gen_addresses.py` clean with `dt.json` merged (1320 entries, rule 14). `tools/gate.sh` itself was NOT run: it `rsync --delete`s into CT111's single shared tree and two concurrent lanes would mix trees -- every check it runs was run here on this lane's own worktree instead, and the integrator should run the real gate before merging. Artefacts: `findings/subsystems/t34-observed-designs.md`, `verify/results/standalone/dt/*`, `ghidra/addresses.d/dt.json`; engine branch `wip/dt` off `e7e2bd6` |