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
159 lines
7.6 KiB
Markdown
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.
|