standalone_report.py regenerated from sots-engine wip/turnrecord. The reference pair does not move by design: T36 does not commit by default, so 209 -> 204, closed 5 / regressed 0 and 108 -> 103, closed 5 / regressed 0 are unchanged. What moved in section 6 is the phase tally, 14/44 modelled and 8 committed -> 15/44 and 9, from the A2 and D2 merges. verified stays 0 on both tables. t36-turnrecord.md carries the measurement the default run cannot show: with --commit-blocked=T36 and a data root, the reference pair closes 29 leaves and regresses 7 (was 9). Two of the three census leaves close; the third is short by exactly one destroyer on both pairs, because no phase we run builds a ship. The census model itself reproduces 480/480 archived census leaves over 11 saves with 0 mismatches. T36 stays blocked: sav and inc on lane E1's budget, and one ship count on unmodelled construction. A field-granular commit knob is not implementable -- the archived record is one struct on the wire and has no hole to leave. Coverage: 32 of 480 census leaves are nonzero anywhere in the corpus; cls1 and cls2's platform count are unexercised, not verified.
7.9 KiB
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-blockedalone 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
savandincfor the new turn come from P01/P02, blocked on the per-system money output — lane E1.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=<fields> 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
sattfor 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.
verifiedstays 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, noSOTS_SAVES_DIR— 45/45 - host
ctest,SOTS_SAVES_DIRset — 45/45 - host
ctest,SOTS_SAVES_DIR+SOTS_DATA_DIRset — 44/45. The one failure isgame_data_realdata, and it fails identically on unmodifiedmainwith the same data root: that test pins section and bank counts against the campaign's canonical extraction and my extraction ofsots.gobis a different data version (9 pinned counts differ, e.g. ahuman/DEWARsection 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. Nosrc/shimfile was touched, but per rule 13 the integrator should run the CT111 shim build before pushing.