sots-re/findings/subsystems/system-visibility-prediction.md
alex d639c9a112 lane E3: the prediction's outcome, leaf for leaf
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
2026-09-08 13:39:30 -04:00

159 lines
7.6 KiB
Markdown

# 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
1. **T17 `RecordObservation`.** For every player `p` and system `s` with
`s.AFlags & (1 << p.PlyrIdx)`: set `NVE[p] = { EPid = p's handle id, ETS = frame,
Eid = the encounter id at s }`. `frame` is the post-increment frame, i.e. the turn number
the autosave carries. Entries are ordered by `PlyrIdx` ascending (a `std::map`). Nothing is
ever erased.
2. **T21 `SetExploredBy`.** `s.EFlags |= s.AFlags`.
3. **S29 `ltis`.** If `s.AFlags != 0` then `s.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`, not `AFlags`.** On both reference pairs `VFlags == AFlags` on every
system, so the pairs cannot separate them. The discriminator already in the corpus is
`zuul-turn23-fleet23.sav` Bismol: `VFlags = 2`, `AFlags = 0`, `ETS` frozen at 22 while the
save is turn 23. A `VFlags` model 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 has `EFlags = 2`.
* **`ETS` is the sighting turn, set once, not refreshed.** Then `turn2 -> turn3` closes 0 of
its 8, and the eight `ETS 2 -> 3` leaves stay. **Symptom:** pair 2 closes 8 instead of 16.
* **`Eid` is 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 `Eid` test is `FtFlg & 0x10`, not `FtEnc != 0`.** Invisible on this corpus; the six
encounter fleets agree on both. **Symptom:** none available.
* **`EPid` is the player index, not the handle id.** Then all eight `EPid` leaves 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.
* **`ltis` is not the frame but something that merely equals it here.** `ltis` is `1` at
turn 1 and `2` at 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 8 `ltis` leaves 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 not `AFlags` — Spica has `AFlags == 0` and
`TShn` still 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; the `PlayerView` refresh 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.