# `SnLv` measured — the sensor branch is what refreshes Spica, and the contact level is 2, not 1 - **Type:** subsystem (live measurement) - **Status:** **verified** for Spica; `nvo-tshn-visible-owner.md` §6's first bullet moves **[H] → [V]** - **Confidence:** high. Hardware data-write watchpoint on the word itself, with the canary self-test passing and the determinism oracle reproduced byte for byte in the same run. - **Owner / date:** lane L1 · 2026-09-08 · VM140 - **Instrument:** `src/shim/hooks/watchpoints.cpp`, new `watch.mode=snlv`, config `shim.cfg.l1snlv`, build `0117495-dirty-20260908T2113Z` - **Answers:** `findings/subsystems/nvo-tshn-visible-owner.md` §6 — *"One arming line (`sys+0x24` in slot 1 instead of `_Mysize`) settles it on the next VM run."* It did. --- ## 0. The result `ServerSystem::IsKnownTo` is `AFlags bit || (PlyrIdx < 15 && ((SnLv >> 2*PlyrIdx) & 3) != 0 && HasResearched(CCC_AdvSens))`. Lane W3 could not decide whether Spica's `NVO` record is refreshed through the `AFlags` term or the sensor term, because **`SnLv` is not serialised** and had never been measured. On `ref-turn2.sav`, at the arming point, **Spica has `AFlags = 0x0` and `SnLv = 0x00000200`**, i.e. contact level **2** for player index 4 — the very player its single `NVO` record is keyed to. `AFlags = 0` makes the first term false. The sensor term is therefore the only way `IsKnownTo` can return true for that pair, and the record is refreshed. **The sensor branch is what refreshes Spica**, measured rather than inferred. ## 1. The whole map, because it costs no debug register Printing `SnLv` and its decoded per-player 2-bit fields for every system during the arming sweep is free. All 28 systems of `ref-turn2.sav`, at `ApplyAllTurnCommands` entry on the End Turn from turn 2 (only the non-zero rows shown; the other 20 systems read `SnLv = 0`): | system | `AFlags` | `SnLv` | non-zero levels | |---|---|---|---| | Hyperion | 0x10 | 0x00000300 | player 4 → **3** | | Gamma Cephei | 0x01 | 0x00000003 | player 0 → **3** | | **Spica** | **0x00** | **0x00000200** | **player 4 → 2** | | Ke'Dolarra | 0x02 | 0x0000000c | player 1 → **3** | | Koa?Vo | 0x80 | 0x0000c000 | player 7 → **3** | | Kaa?Vaalu | 0x10 | 0x00000300 | player 4 → **3** | | Markab | 0x10 | 0x00000300 | player 4 → **3** | | Kea?Pono | 0x10 | 0x00008300 | player 4 → **3**, player 7 → **2** | | Ko'Rorkor | 0x10 | 0x00000300 | player 4 → **3** | Two regularities fall out and neither was available from any save: 1. **Level 3 occurs exactly where the `AFlags` bit is set**, on all eight rows — which is the `IsVisibleTo` early-out of `SensorPhase_ComputeContactLevel` doing what lane W3 read. 2. **Level 2 occurs exactly where it is not** — Spica for player 4, Kea'Pono for player 7. So the non-visible contact state on this save is **2**, and **level 1 was never observed**. ## 2. A correction: `ComputeContactLevel` does not return 1 there `ghidra/addresses.d/lane-w3.json` records `SensorPhase_ComputeContactLevel` as *"3 if IsVisibleTo (AFlags), else 1 if inside p's sensor envelope, else 0"*. The function has **four** return tails, read at instruction boundaries: ``` 00850d18 mov eax,3 ; IsVisibleTo -> 3 00850e2f mov eax,ecx ; ecx = setne on an FPU range compare -> 0 or 1 00850e47 lea eax,[edx+2] ; edx = setne on the same compare -> 2 or 3 00850e52 mov eax,2 ; -> 2 ``` which pair is taken is selected by `cl` and by `[ebx+0x10]`. So the value set is `{0,1,2,3}` and the `{0,1}` pair is one of three tails, not the only alternative to 3. The measurement above lands on a `2` tail. **`IsKnownTo` only tests `!= 0`, so nothing downstream changes** — but the constant in the prototype is wrong and would mislead anyone reimplementing the sensor phase. ## 3. Who writes it, and how often DR1 watched Spica's `sys+0x24` for the whole End Turn: **20 writes**, every one from the same instruction. | trap EIP (Ghidra VA) | store site | value written | count | |---|---|---|---| | 0x0086a78c | **0x0086a789** `mov [edi+0x24],edx` | 0x00000200 every time | **20** | 0x0086a789 is exactly the pack site lane W3 named from static reading — the trap reports the instruction after the store, and 0x0086a789 + 3 = 0x0086a78c. Confirmed to the byte. The value never changed across the turn: the recompute is idempotent on this workload, which is why the *absence* of `SnLv` from the wire costs nothing on these saves and why no save could ever have shown it. The enclosing loop at 0x0086a761..0x0086a79b walks `GetGame()->Systems` and calls `ComputeContactLevel` once per system, masking `3 << (2*idx)` out and OR-ing the result in, guarded by `[ebp-0x10] >= 0xf` — the `PlyrIdx < 15` test lane W3 read in `IsKnownTo`, here on the writing side. Twenty full sweeps of the systems vector happen in one End Turn. ## 4. `TShn` refreshed, in the same run, twice DR0 watched Spica's `NVO` record at `root+0x10` (`{int16 touched, int16 TShn}`): **4 writes**, in two pairs, all with a `SetVisibleOwner` frame: | seq | trap EIP (VA) | word after the store | reading | |---|---|---|---| | 8 | 0x0075b95a | `0x00020003` | `touched` := 3 (the new `Frame`), `TShn` still 2 | | 9 | 0x0075b965 | `0x00030003` | `TShn` := 3 | | 20 | 0x0075b95a | `0x00030003` | same pair again | | 21 | 0x0075b965 | `0x00030003` | | Return addresses 0x0075bd4a / 0x0075bdc3 with 0x0086a806 on the stack put the caller chain in the sensor phase, next to the `SnLv` writer. This confirms three things lane W3 stated as a model: `SetVisibleOwner` stamps `touched` and `TShn` in **two separate stores**; it runs **twice per turn** for one (system, player) pair; and on this turn both are stamped with the new `Frame`. ## 5. Coverage — what this does NOT settle (rule 15) * **Bismol is not on this map.** `nvo-tshn-visible-owner.md` §6's open question — *which* conjunct fails at Bismol — belongs to `zuul-turn23-fleet23.sav`, a different game with a different star list. This run settles the Spica half of that bullet and leaves the Bismol half open. The same config on the Zuul save answers it: the arming sweep prints every system's `SnLv`, so one launch and one End Turn is the whole experiment. * **One system watched, 28 printed.** The 20 `SnLv` writes and the 4 `TShn` writes are Spica's. Every other system's row above is a **read at arm time**, not a watched write. * **`SnLv` never changed value** during the turn, so nothing here says what a *transition* looks like or when a contact is lost. * **Level 1 was never observed.** §2's four-tail reading is static; only 0, 2 and 3 have been seen. * **Per-thread blind spot.** Debug registers are per-thread and the arm is on the turn thread only. * **`ShareVisibleOwner` still has never executed** — no corpus save has an alliance (unchanged). ## 6. Instrument neutrality (rule 19) The armed run **is** the oracle run: `ref-turn2.sav`, one End Turn, four watchpoints live and ~24 exception deliveries, plus the five lane-L1 hooks and lane Z's ledger: | file | size | sha256 prefix | historical | |---|---|---|---| | `(Autosave EndTurn).sav` | 66,732 | `bb4fd9ac89f41e3b` | **identical** | | `(Autosave).sav` | 67,219 | `978041acd168b56e` | **identical** | Canary self-test: `writes=1 traps=1 dr7=0xdddd0055 PASS`. ## 7. Files - Arming log (all 28 systems with `SnLv` decoded, the base probe, the canary): `verify/results/shim/l1/watch-arm.txt` - Hit log: `verify/results/shim/l1/watch-hits.txt` - Instrument: `sots-engine` `wip/l1`, `src/shim/hooks/watchpoints.cpp` (`watch.mode=snlv`), configs `shim.cfg.l1snlv` / `shim.cfg.l1control`