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

5.1 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

  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.