204 -> 158 and 103 -> 87, closed 46 and 16, regressed 0 -- the predicted table line for line, including the 32-leaf target in full. Three further pairs the model was never fitted to close 42, 12 and 12 with zero regressions; 128 leaves across five pairs. The AFlags-vs-VFlags falsification is decided on a real save rather than in a comment: running the engine on zuul-turn23-fleet23 reproduces the frozen stamp at the one system whose active mask is clear while its sticky and explored masks are not. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ARBgSooAfokKUy6wKUKEyZ
7.6 KiB
Prediction: the visibility phase, before it is built
Lane E3, 2026-09-08. Written and committed before the engine change exists (rule 2).
Model: findings/subsystems/system-visibility-record.md.
Baseline, measured on sots-engine main 0592104 this morning:
turn1-state -> turn2-state 204 leaf differences
turn2-state -> turn3-state 103 leaf differences
The model, as three rules
- T17
RecordObservation. For every playerpand systemswiths.AFlags & (1 << p.PlyrIdx): setNVE[p] = { EPid = p's handle id, ETS = frame, Eid = the encounter id at s }.frameis the post-increment frame, i.e. the turn number the autosave carries. Entries are ordered byPlyrIdxascending (astd::map). Nothing is ever erased. - T21
SetExploredBy.s.EFlags |= s.AFlags. - S29
ltis. Ifs.AFlags != 0thens.ltis = frame.
Eid is taken from an existing NVE entry when the system already has one (the field it
mirrors is fixed for the life of the game); otherwise from a fleet at the system with
FtEnc != 0; otherwise -1.
AFlags is read from the save, not recomputed. Justification, and it is checkable: neither
reference pair moves an AFlags leaf, so the loaded value is the value the phase would see.
Predicted leaves closed
turn1-state -> turn2-state (the 32-leaf target)
Eight systems — 64 Hyperion, 112 Gamma Cephei, 288 Ke'Dolarra, 304 Koa'Vo, 336 Kaa'Vaalu,
400 Markab, 448 Kea'Pono, 480 Ko'Rorkor — are exactly the systems with AFlags != 0.
| leaves | rule | count |
|---|---|---|
Sys[*]/NVE count 0 -> 1 |
1 | 8 |
Sys[*]/EPid only-in-B |
1 | 8 |
Sys[*]/ETS only-in-B |
1 | 8 |
Sys[*]/Eid only-in-B |
1 | 8 |
| the brief's target | 32 | |
Sys[*]/EFlags 0 -> {16,128,16,16,16,16} on 64, 304, 336, 400, 448, 480 |
2 | 6 |
Sys[*]/ltis 1 -> 2 on all eight |
3 | 8 |
| total predicted closed | 46 |
Predicted EPid values, per system, in order: 528, 16, 32, 576, 528, 528, 528, 528.
Predicted Eid values: 5, -1, -1, -1, 3, 3, 4, 4. Predicted ETS: 2 everywhere.
Expected result: 204 -> 158, closed 46, regressed 0.
turn2-state -> turn3-state
The entries already exist, so only their stamps move.
| leaves | rule | count |
|---|---|---|
Sys[*]/ETS 2 -> 3 on the same eight |
1 | 8 |
Sys[*]/EFlags |
2 | 0 (already equal to AFlags) |
Sys[*]/ltis 2 -> 3 on the same eight |
3 | 8 |
| total predicted closed | 16 |
Expected result: 103 -> 87, closed 16, regressed 0.
EPid and Eid must be re-emitted unchanged; if either moves, the model has regressed a
leaf that agreed by construction, and that is a failure of rule 3, not a rounding detail.
Falsification
Each of these is a way the model is wrong and the symptom that would show it.
- The gate is
VFlags, notAFlags. On both reference pairsVFlags == AFlagson every system, so the pairs cannot separate them. The discriminator already in the corpus iszuul-turn23-fleet23.savBismol:VFlags = 2,AFlags = 0,ETSfrozen at 22 while the save is turn 23. AVFlagsmodel refreshes it to 23. Symptom: a host test that replays Bismol's row fails. That test is written. - The gate is
EFlags. Same corpus discriminator: Bismol hasEFlags = 2. ETSis the sighting turn, set once, not refreshed. Thenturn2 -> turn3closes 0 of its 8, and the eightETS 2 -> 3leaves stay. Symptom: pair 2 closes 8 instead of 16.Eidis re-derived from the fleet every turn rather than parked on the system. Invisible on this corpus — no encounter fleet dies in it. Symptom: none available; that is why §8 of the model doc names the workload instead of claiming the rule.- The
Eidtest isFtFlg & 0x10, notFtEnc != 0. Invisible on this corpus; the six encounter fleets agree on both. Symptom: none available. EPidis the player index, not the handle id. Then all eightEPidleaves regress to 0..7 instead of closing. Symptom: 8 closed becomes 8 regressed, and the run's closed count drops to 38 / 16.- The map is ordered by insertion, not by key. Invisible: every system in the corpus has exactly one entry. Symptom: none available; the ordering is asserted from the container type, and §8 names the two-empire-contact workload that would exercise it.
ltisis not the frame but something that merely equals it here.ltisis1at turn 1 and2at turn 2 on the same systems, which is consistent with a great many counters. The reading is a 41-byte function body; if it is wrong the 8ltisleaves regress rather than close.- The phase runs before the frame increment. Then every stamp is one low and 24 leaves regress across the two pairs.
What the model deliberately does not write
NVO.TShn(10 leaves per pair). Its gate is notAFlags— Spica hasAFlags == 0andTShnstill moves. Formula not held; evaluated and reported, not written (lane S2's rule).rcex(6 leaves per pair). Unexplained.NVs/PlayerView. Count 0 on both reference pairs; thePlayerViewrefresh loop is a separate phase and no leaf of it diverges on either pair.
Measured
Run after the build, against the prediction above, which was committed in 55ea86f before
any engine file existed. Engine branch wip/visibility, host ctest 46/46, clean-room OK,
shim cross-built on CT111 with exports byte-identical.
| pair | baseline | after | closed | regressed |
|---|---|---|---|---|
turn1-state -> turn2-state |
204 | 158 | 46 | 0 |
turn2-state -> turn3-state |
103 | 87 | 16 | 0 |
Closed, by leaf name — the prediction's table, line for line:
turn1 -> turn2 EPid 8 ETS 8 Eid 8 NVE 8 (= the brief's 32) ltis 8 EFlags 6
turn2 -> turn3 ETS 8 ltis 8
tools/standalone_report.py's own numbers, whose baseline is the do-nothing distance from
the input save rather than from main, are 209 -> 158 (closed 51, regressed 0) and
108 -> 87 (closed 21, regressed 0); the five-leaf difference in each is what main had
already closed.
Three pairs the model was never fitted to
Rule 2's strong form. None of these was looked at while the model was being built; the
human-* pair is a different game on a different map with a different species mix.
| pair | baseline | after | closed | regressed |
|---|---|---|---|---|
human-turn2-orders -> human-turn3-noderoute |
353 | 311 | 42 (21 ETS, 21 ltis) |
0 |
zuul-turn15-orders -> zuul-turn16-noderoute |
276 | 264 | 12 (6 ETS, 6 ltis) |
0 |
zuul-turn16-noderoute -> zuul-turn17-orders2 |
341 | 329 | 12 (6 ETS, 6 ltis) |
0 |
128 leaves closed across five pairs, 0 regressed.
The falsification that mattered
The AFlags-vs-VFlags case is the one the reference pairs could not decide, and it is
decided. Running the engine on zuul-turn23-fleet23.sav reproduces the freeze on the save
itself: every observed system's stamp moves to 24 and Bismol's — sticky mask 2, explored
mask 2, active mask 0 — stays at 22, in both ETS and ltis. A VFlags or EFlags
model moves it to 24. That row is also a host test (tests/game_sim/test_visibility.cpp,
test_bismol_freeze), which asserts the freeze and asserts what the wrong gate would have
produced, so a later edit that swaps the mask fails loudly rather than quietly agreeing with
the five pairs above.
None of the other falsification cases fired. The remaining ones — the two Eid derivations
and the map ordering — are exactly the ones the section marked "symptom: none available",
and they are still not settled; §8 of the model doc names the workloads.