sots-re/verify/results/standalone/t36-turnrecord.md
alex fbe3c7bd86 lane E2: T36 measured with the ship census wired in -- 29 closed / 7 regressed, and the two things it is still blocked on
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.
2026-09-08 13:25:31 -04:00

7.9 KiB
Raw Blame History

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=<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 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.