diff --git a/verify/results/standalone/report.txt b/verify/results/standalone/report.txt index a019b9a..f456306 100644 --- a/verify/results/standalone/report.txt +++ b/verify/results/standalone/report.txt @@ -1,8 +1,8 @@ # standalone vs the oracle -generated 2026-09-08T15:35:00Z binary /home/alex/sots-engine-wt-yield/build-host/src/app/sots_turn +generated 2026-09-08T17:23:54Z binary /home/alex/sots-engine-wt-turnrecord/build-host/src/app/sots_turn -phases: 14/44 of the two turn drivers modelled, 8 committed (implemented 2, partial 6, blocked 6, stub 30) +phases: 15/44 of the two turn drivers modelled, 9 committed (implemented 3, partial 6, blocked 6, stub 29) 3/37 of the post-combat tail modelled ## turn1-state.sav -> turn2-state.sav (real End Turn) diff --git a/verify/results/standalone/status.json b/verify/results/standalone/status.json index c4db225..2e29f14 100644 --- a/verify/results/standalone/status.json +++ b/verify/results/standalone/status.json @@ -1,7 +1,7 @@ { "schema": "sots-standalone-status/1", - "generated": "2026-09-08T15:35:00Z", - "binary": "/home/alex/sots-engine-wt-yield/build-host/src/app/sots_turn", + "generated": "2026-09-08T17:23:54Z", + "binary": "/home/alex/sots-engine-wt-turnrecord/build-host/src/app/sots_turn", "reference": { "input": "turn1-state.sav", "oracle": "turn2-state.sav", @@ -30,12 +30,12 @@ "phases": { "total": 44, "verified": 0, - "implemented": 2, + "implemented": 3, "partial": 6, "blocked": 6, - "stub": 30, - "modelled": 14, - "committed": 8 + "stub": 29, + "modelled": 15, + "committed": 9 }, "tailPhases": { "total": 37, @@ -62,16 +62,17 @@ " 591376 inflated bytes, 0 error(s), 0 warning(s)", " turn 1, frame 1, modCount 0, 8 player(s), 28 system(s), 6 fleet(s)", "roundtrip (untouched): byte-identical (591376 bytes)", + "data: /tmp/claude-1000/-home-alex/ec8e34f8-af37-4ef2-a309-ed6a15293097/scratchpad/data -- 885 section(s) over 7 race(s), 46 load problem(s)", "", "phases", - " turn drivers (the milestone's denominator): 14 of 44 modelled, 8 committed", - " verified 0 implemented 2 partial 6 blocked 6 stub 30", + " turn drivers (the milestone's denominator): 15 of 44 modelled, 9 committed", + " verified 0 implemented 3 partial 6 blocked 6 stub 29", " post-combat tail (written to the autosave, tracked separately): 3 of 37 modelled", " verified 0 implemented 1 partial 0 blocked 2 stub 34", "", "this run", " leaves written 7", - " leaves NOT written by a blocked phase 64", + " leaves NOT written by a blocked phase 120", " generator words consumed 16 (state loaded, left untouched)", " generator words NOT accounted (never netted off the above):", " - encounter detection draws one unit value and one bounded integer per turn on every turn measured (2 words), with no derived rule behind the count -- its bound is the product of the contact and detector counts, so it is left unmodelled", @@ -80,8 +81,8 @@ " ! the generator advanced during this run but the save keeps its original state (--commit-rng to write it)", " ! the modelled words are a LOWER BOUND on the turn's cost, so a committed generator is short by the unaccounted sites below and its drawn VALUES are not the game's", "", - "wrote /tmp/tmp_cz5dsbf/post-turn1-state.sav (65145 bytes gzipped, 591376 inflated)", - "metric -> /tmp/tmp_cz5dsbf/metric-turn1-state.sav.json" + "wrote /tmp/tmpbodkynxg/post-turn1-state.sav (65145 bytes gzipped, 591376 inflated)", + "metric -> /tmp/tmpbodkynxg/metric-turn1-state.sav.json" ], "coverage": { "input": true, @@ -89,9 +90,9 @@ "ours": true }, "roots": { - "input": "ce36f6e5a2e41ae1a90d5b7fefda8f8f", - "oracle": "b0e2448b83ad1da254d526f4f553ff8d", - "ours": "8836ea25910897ec7a9b7d69fcaf6c87" + "input": "64b836b966299d9b72fc02244ed31e31", + "oracle": "4bed514f853332b9fa35b388c3aa5558", + "ours": "1acc372b1d410b966ab544dffbe43cb5" }, "baselineDiverging": 209, "divergingAfterTurn": 204, @@ -151,16 +152,16 @@ "standalone": { "schema": "sots-standalone-metric/1", "input": "/home/alex/sots-re/verify/results/saves/turn1-state.sav", - "output": "/tmp/tmp_cz5dsbf/post-turn1-state.sav", + "output": "/tmp/tmpbodkynxg/post-turn1-state.sav", "spine": { "total": 44, "verified": 0, - "implemented": 2, + "implemented": 3, "partial": 6, "blocked": 6, - "stub": 30, - "modelled": 14, - "committed": 8 + "stub": 29, + "modelled": 15, + "committed": 9 }, "tail": { "total": 37, @@ -174,7 +175,7 @@ }, "run": { "leafWrites": 7, - "blockedLeafWrites": 64, + "blockedLeafWrites": 120, "rngWords": 16, "rngLoaded": true, "rngCommitted": false, @@ -239,8 +240,8 @@ "driver": "StrategyServer::ProcessTurn", "id": "S04", "name": "RebuildAllianceMasks", - "status": "stub", - "ran": 0, + "status": "implemented", + "ran": 8, "writes": 0, "blockedWrites": 0, "rng": 0 @@ -1002,7 +1003,7 @@ "status": "blocked", "ran": 8, "writes": 0, - "blockedWrites": 48, + "blockedWrites": 104, "rng": 0 }, { @@ -1028,16 +1029,17 @@ " 603360 inflated bytes, 0 error(s), 0 warning(s)", " turn 2, frame 2, modCount 12, 8 player(s), 28 system(s), 7 fleet(s)", "roundtrip (untouched): byte-identical (603360 bytes)", + "data: /tmp/claude-1000/-home-alex/ec8e34f8-af37-4ef2-a309-ed6a15293097/scratchpad/data -- 885 section(s) over 7 race(s), 46 load problem(s)", "", "phases", - " turn drivers (the milestone's denominator): 14 of 44 modelled, 8 committed", - " verified 0 implemented 2 partial 6 blocked 6 stub 30", + " turn drivers (the milestone's denominator): 15 of 44 modelled, 9 committed", + " verified 0 implemented 3 partial 6 blocked 6 stub 29", " post-combat tail (written to the autosave, tracked separately): 3 of 37 modelled", " verified 0 implemented 1 partial 0 blocked 2 stub 34", "", "this run", " leaves written 7", - " leaves NOT written by a blocked phase 58", + " leaves NOT written by a blocked phase 114", " generator words consumed 16 (state loaded, left untouched)", " generator words NOT accounted (never netted off the above):", " - encounter detection draws one unit value and one bounded integer per turn on every turn measured (2 words), with no derived rule behind the count -- its bound is the product of the contact and detector counts, so it is left unmodelled", @@ -1046,8 +1048,8 @@ " ! the generator advanced during this run but the save keeps its original state (--commit-rng to write it)", " ! the modelled words are a LOWER BOUND on the turn's cost, so a committed generator is short by the unaccounted sites below and its drawn VALUES are not the game's", "", - "wrote /tmp/tmp_cz5dsbf/post-turn2-state.sav (66883 bytes gzipped, 603360 inflated)", - "metric -> /tmp/tmp_cz5dsbf/metric-turn2-state.sav.json" + "wrote /tmp/tmpbodkynxg/post-turn2-state.sav (66883 bytes gzipped, 603360 inflated)", + "metric -> /tmp/tmpbodkynxg/metric-turn2-state.sav.json" ], "coverage": { "input": true, @@ -1055,9 +1057,9 @@ "ours": true }, "roots": { - "input": "b0e2448b83ad1da254d526f4f553ff8d", - "oracle": "7ea50b486c2e9f9f1a8ec5b9646165a8", - "ours": "6523aa25786814380a26f48c046da22b" + "input": "4bed514f853332b9fa35b388c3aa5558", + "oracle": "e9c161e311f8ef8fad6f1aa1903dcf3f", + "ours": "22071ae1303f308a93b805e940b626b5" }, "baselineDiverging": 108, "divergingAfterTurn": 103, @@ -1117,16 +1119,16 @@ "standalone": { "schema": "sots-standalone-metric/1", "input": "/home/alex/sots-re/verify/results/saves/turn2-state.sav", - "output": "/tmp/tmp_cz5dsbf/post-turn2-state.sav", + "output": "/tmp/tmpbodkynxg/post-turn2-state.sav", "spine": { "total": 44, "verified": 0, - "implemented": 2, + "implemented": 3, "partial": 6, "blocked": 6, - "stub": 30, - "modelled": 14, - "committed": 8 + "stub": 29, + "modelled": 15, + "committed": 9 }, "tail": { "total": 37, @@ -1140,7 +1142,7 @@ }, "run": { "leafWrites": 7, - "blockedLeafWrites": 58, + "blockedLeafWrites": 114, "rngWords": 16, "rngLoaded": true, "rngCommitted": false, @@ -1205,8 +1207,8 @@ "driver": "StrategyServer::ProcessTurn", "id": "S04", "name": "RebuildAllianceMasks", - "status": "stub", - "ran": 0, + "status": "implemented", + "ran": 8, "writes": 0, "blockedWrites": 0, "rng": 0 @@ -1968,7 +1970,7 @@ "status": "blocked", "ran": 8, "writes": 0, - "blockedWrites": 48, + "blockedWrites": 104, "rng": 0 }, { diff --git a/verify/results/standalone/t36-turnrecord.md b/verify/results/standalone/t36-turnrecord.md new file mode 100644 index 0000000..b2a8986 --- /dev/null +++ b/verify/results/standalone/t36-turnrecord.md @@ -0,0 +1,142 @@ +# T36 `FinalizeTurnRecords` — the ship census wired in, and what T36 is still blocked on + +Lane E2, 2026-09-08. Engine branch `wip/turnrecord` off `sots-engine` main `0592104`. +Measured with `verify/state-checksum/state_checksum.py` leaves, the same currency as +`report.txt`. **`closed` and `regressed` are reported separately on every line; nothing here +is netted.** + +`status.json` and `report.txt` in this directory were regenerated from the same binary. + +## What changed in the engine + +`ShipCensus` (lane D2) is wired into `BuildTurnRecord`. The classification walks every +player's designs — normal and legacy, one id space — resolves each design's sections against +the section catalog, and walks the global fleet list keyed by `Flt.PID` against each player's +**object id**, not its vector position. The record's modelled field count goes 7 → 13. + +Two things the standalone did not have before: + +* **a data root.** The census cannot come from a save: a design's hull size and its + defence-platform flag are recomputed from the section catalog whenever the design changes + and are never written down. `sots_turn --data DIR` (or `$SOTS_DATA_DIR`) supplies it. No + game data enters the repo, and without a root the six counters are reported unmodelled + rather than written as six zeros. +* **`--commit-blocked=IDS` / `--commit-blocked-except=IDS`.** `--commit-blocked` alone is + all-or-nothing across every blocked phase, which reports one closed count and one regressed + count for all of them together — the netting the campaign does not do. The two lists narrow + it to named phase ids, so a phase's own contribution can be measured. Every number below + that names T36 was measured with `--commit-blocked=T36`. + +## Prediction, written before the code (rule 2) + +> Default mode unchanged at 209 → 204, closed 5 / regressed 0 and 108 → 103, closed 5 / +> regressed 0. With `--commit-blocked`, the three census leaves close and the regressed list +> falls from 9 to 6 on pair 1 (`inc`×3 + `sav`×3, the budget) and from 11 to 8 on pair 2. + +Falsification hypothesis 1 was: *the census is built from the fleet list as it stands, which +is pre-construction; if a real turn's census differs from the input turn's, the leaf does not +close and shows a third value.* + +**That is what happened.** Two of the three closed; the third did not, and the symptom was +exactly the one written down. + +## Measured — reference pair `turn1-state.sav` → `turn2-state.sav` + +| run | closed | regressed | +|---|---:|---:| +| default (no commit flags) — before and after this lane | 5 | 0 | +| `--commit-blocked`, before this lane | 29 | 9 | +| `--commit-blocked`, after | 29 | **7** | +| `--commit-blocked=T36` alone, with a data root | 29 | 7 | +| `--commit-blocked=T36` alone, no data root | 29 | 9 | + +Regressed after, named: `hist[0]/stats[1]/{sav,inc}`, `hist[1]/stats[1]/{sav,inc}`, +`hist[7]/stats[1]/{sav,inc}` — the budget, lane E1 — and `hist[1]/stats[1]/shpt[0]`, ours 0, +archived 1. + +Closed by the census: `hist[4]/stats[1]/shpt[2]` (5 dreadnoughts) and +`hist[7]/stats[1]/satt[0]` (10 defence platforms). + +## Measured — second pair `turn2-state.sav` → `turn3-state.sav` + +| run | closed | regressed | +|---|---:|---:| +| default | 5 | 0 | +| `--commit-blocked`, before this lane | 13 | 11 | +| `--commit-blocked`, after | 13 | **9** | +| `--commit-blocked=T36` alone, with a data root | 13 | 7 | +| `--commit-blocked=T36` alone, no data root | 13 | 9 | + +The two extra regressions under a full `--commit-blocked` are `Player/Status` on two players. +They are S31's, not T36's, and the new id filter separates them cleanly — which is the whole +reason the filter exists. + +## The one census leaf that did not close is not a census error + +`app_test_turn_record`, extended this lane, now compares the six counters against the record +the game itself archived, on every save: **11 saves, 80 player-records, 1040 fields, 480 of +them census leaves, 0 mismatches.** That is the same 480/480 lane D2 got, reproduced through +the app's own code path (which visits slots in the original's in-memory order rather than the +wire's) rather than through D2's test. + +So the model reproduces the archived census exactly for the turn the save was written at. The +remaining leaf is `shpt[0]` for one player, and the archived value is higher than ours by +exactly one destroyer on **both** pairs (0 vs 1, then 1 vs 2). That player's build queue +completes one ship per turn, and no phase the standalone runs creates a ship — S11's +build-queue sub-pass is the declared input boundary. The leaf is short by the turn's +construction, not wrong about classification. + +**This is a new named blocker, and it is not lane E1's and not mine.** + +## T36's status: still `Blocked`, on two named things, neither of them in this phase + +1. `sav` and `inc` for the new turn come from P01/P02, blocked on the per-system money + output — **lane E1**. +2. `shpt[0]` is short by the ships the turn builds — **ship construction, unmodelled**. + +There is **no field-granular knob that could rescue this**, and the brief's +`--commit-blocked-except=` idea is not implementable as stated: the archived record +is one struct on the wire, so `sav` and `inc` cannot be left out while the other eleven fields +are written. The format has no hole. Committing is all-or-nothing at the record, so the +choice is 29 closed with 7 confidently-wrong leaves, or neither — and 7 wrong leaves is not +worth 29 that a later lane closes for free. What *is* implementable, and is implemented, is +the same convention at **phase** granularity, which is what produced the isolated rows above. + +T36 flips the moment E1 lands, minus one leaf that waits on construction. + +## Coverage, stated as loudly as the verdict (rules 6 and 15) + +Only **32 of the 480** archived census leaves are nonzero anywhere in the 11-save corpus. +Per leaf (cls0 shpt/satt, cls1 shpt/satt, cls2 shpt/satt) = **18/3, 0/0, 11/0**. + +* **cls1 (cruisers), ships and platforms both, and `satt` for cls2, have never been observed + nonzero.** Three of the six counters are unexercised hypotheses, not verified. A zero leaf + agrees for free. The corpus needs a cruiser and a dreadnought-hull platform + (roadmap §5 already lists both). +* The other four words of each class group — `shpl`, `shpk`, `satl`, `satk`, losses and kills + — are zero throughout the corpus, and this phase writes zeros with no model behind them. + Listed in the phase's own unmodelled list rather than passed off as agreement. +* Hull size is an **assignment** in slot order, so the visit order is load-bearing in + principle. The original's stats pass walks mission, command, engine; the wire writes + command, mission, engine. This code reproduces the memory order. **Design rule A6 makes + every shipped design class-homogeneous, so no save in the corpus can tell the two orders + apart** — the order is carried from the instruction stream, not confirmed by these bytes. +* `verified` stays **0**. Nothing here was compared against a running game; this lane holds no + VM. The comparison is against bytes the original wrote, which is the strongest check + available without one, and it is not the same thing. + +## Gates + +Run as separate commands, never chained (rule 13): + +* `tools/clean_room_check.sh` — OK +* host `ctest`, no `SOTS_SAVES_DIR` — 45/45 +* host `ctest`, `SOTS_SAVES_DIR` set — 45/45 +* host `ctest`, `SOTS_SAVES_DIR` + `SOTS_DATA_DIR` set — 44/45. The one failure is + `game_data_realdata`, and it **fails identically on unmodified `main`** with the same data + root: that test pins section and bank counts against the campaign's canonical extraction and + my extraction of `sots.gob` is a different data version (9 pinned counts differ, e.g. a + `human/DEWAR` section the pin says should be absent). It is a data-root mismatch, not a + regression, and the census matched all 480 archived leaves against that same root anyway. +* **Shim cross-build NOT run**: no `i686-w64-mingw32-g++` on this host. No `src/shim` file was + touched, but per rule 13 the integrator should run the CT111 shim build before pushing.