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.
142 lines
7.9 KiB
Markdown
142 lines
7.9 KiB
Markdown
# 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.
|