sots-re/findings/subsystems/system-visibility-prediction.md
alex 55ea86f0d5 lane E3: the per-system visibility record read from the instruction stream, and the prediction before the build
NVE is a per-(system, player) map of {ETS = turn of sighting, Eid = encounter type
present}. Primary writer 0x00756300, called from tail phase 17 under the gate
(AFlags >> PlyrIdx) & 1 -- byte-decoded at 0x007cf7a7..0x007cf7ce, not from the
decompiler. Three further accessors named, including the intel-sharing copy that
keeps the older sighting stamp. operator[] has exactly three callers and none of
them erases, so the record is a memory, not a state.

Also names ltis's writer (driver phase 29, AFlags != 0 -> ltis = Frame), closing
half of board.md's 'TShn/ltis: NOTHING NAMES THEIR WRITER'. TShn's own gate is
still unnamed and demonstrably NOT AFlags -- Spica is the counterexample.

Resolves lane B5's flagged indirect edge: SetExploredBy's vft[0x1c] is
ServerSystem vtable slot 7 at 0x007480b0, and it writes only an unserialised
runtime mask.

Prediction committed before the engine change exists: 204 -> 158 (46 closed, 32
of them the brief's target) and 103 -> 87 (16 closed), 0 regressed, with a
falsification section naming the corpus save that separates AFlags from VFlags.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARBgSooAfokKUy6wKUKEyZ
2026-09-08 13:26:46 -04:00

106 lines
5.1 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.