sots-engine/docs/W3-watchpoints.md
alex 9e914ef358 W3: watch.mode=tshn -- arm the NVO/TShn record and the trade+spy containers from the same S
Second mode in lane W2's watchpoint module. No second hook: only different arithmetic on the S the
ApplyAllTurnCommands detour already holds.

- picks the target system by predicate at arm time (AFlags == 0, NVO non-empty) and logs all 28
  systems, so the choice is auditable rather than a hard-coded pointer;
- probes both candidate ServerSystem bases and logs how many systems validate under each, which
  settled a documentation dispute (+0x274: 9, +0x26c: 0) by measurement;
- prints the trade-route and spy-program vector triples, which is the workload-confirmation
  instrument two earlier lanes lacked.

Rule 19 control passed: the armed run reproduced the determinism oracle byte for byte.
2026-09-08 16:38:55 -04:00

4.2 KiB

W3 — a second watch mode, and what the two runs answered

Companion to docs/W3-predictions.md (written and committed before the build) and to sots-re/findings/subsystems/nvo-tshn-visible-owner.md + sots-re/findings/control-flow/w3-containers-status-handlers.md (the full reports).

Builds on lane W2's module without changing how it works.

What was added

watch.mode=modcount|tshn in src/shim/hooks/watchpoints.{h,cpp}. No second hook, no second detour, no change to the VEH or to the arming mechanism — only different arithmetic on the S the existing ApplyAllTurnCommands detour already holds. modcount is W2's set and stays the default so its run remains reproducible; tshn arms:

slot target
0 the TShn word of an NVO map record on a system with AFlags == 0
1 that map's _Mysize
2 trade-route vector _Mylast (*(S+4+0x154) + 0x40)
3 spy-program vector _Mylast (*(S+4+0x158) + 0x14)

Two things about the tshn arming are worth keeping if the module is extended again:

  • The target is chosen by predicate at arm time, not hard-coded, and all 28 systems are logged with name, AFlags and both map sizes. A hard-coded pointer that lands on the wrong object produces the same empty hit list as "nothing writes this" (method rule 1).
  • The ServerSystem base is probed, not assumed. Two published accounts disagreed by 8 about where the NVO map lives, because ServerSystem::Write runs on the IStreamable subobject at +0x8. The code tries both candidate bases across every system and logs how many validated under each (+0x274: 9 systems; +0x26c: 0). That turned a documentation dispute into a measurement.

Configs: shim.cfg.w3tshn, shim.cfg.w3mod, shim.cfg.w3control. w3tshn and w3control differ in exactly one key (watch=), which is what makes the rule-19 control real.

Rule 19: the control was taken again, and it passed again

One End Turn from ref-turn2.sav with the four tshn watchpoints armed reproduced the determinism oracle byte for byte — (Autosave EndTurn).sav bb4fd9ac…, (Autosave).sav 978041ac…, both identical to the pre-run files. So the four new addresses are as neutral as W2's four were. That mattered: W2 proved a data breakpoint is neutral for one address set, not for all of them, and this lane's set includes a heap node that the game reallocates freely.

The second run (turn-1 workload, watch.mode=modcount) has no oracle of its own and says so.

What the two runs answered

  1. NVO.TShn's writer, trapped live, with its whole call chain confirmed frame by frame from the recorded ebp chain: RefreshVisibleOwnerIfKnown 0x0075bd70 → RecordVisibleOwner 0x0075bca0 → SetVisibleOwner 0x0075b880 → NVO::operator[] 0x0075a890 → the store 0x0075b961. The gate is ServerSystem::IsKnownTo 0x00746390, which is IsVisibleTo or (sensor contact ∧ CCC_AdvSens) — not AFlags alone, which is why lane E3 could not fit it. The resulting model predicts all 158 NVO records in the 11-save corpus with zero mismatches, including the single frozen one.
  2. Four traps, not one, and the extra pair is a result: the refresh runs twice per End Turn, in driver phase 24 and again in combat-done phase 25. Both phase attributions confirmed for free.
  3. The trade and spy containers, read out of a live game for the first time. Both managers non-null, both vectors default-constructed with all three pointers zero, zero traps on either _Mylast across the turn. The workload is absent, measured — which is the confirmation instrument two earlier lanes needed and did not have.
  4. All ten command ModCount handlers are now named, including two that only exist on a turn-1 workload (OnCommand_SetResearchProject, inlined at 0x0088fe0a, and OnCommand_CreateDesign 0x00882910).

Reusing it

watch.mode is the extension point. To watch something else, add a mode and one arming function; everything else — the canary, the VEH, the flusher, the hit format — is unchanged. Keep the canary self-test and keep the ref-turn2 oracle control: together they cost about two minutes and they are what makes the numbers evidence rather than output.