From cb1ea723e1aa68826d206357113d5a5f8feb9e35 Mon Sep 17 00:00:00 2001 From: alex Date: Tue, 8 Sep 2026 15:25:53 -0400 Subject: [PATCH] lane W2: the three queue items not reached, with the reason and the cheapest next step for each TShn: the brief's address was wrong -- player+0x274 is observed_techs; the TShn map is ServerSystem+0x274. The real obstacle is that a std::map's nodes are heap-allocated, so there is no fixed leaf to arm; arm the map header to trap the insertion instead. rcex: closed from the corpus, no VM time needed (see rcex-explained.md). Trade/spy workload: not attempted, and lane H's reasons still hold. What is new is that the watchpoint module is the instrument for confirming the workload BEFORE spending a turn on it -- both containers are one add from the arming point, and for the spy vector the trapped return addresses would name the UI nobody has found. --- .../watchpoints-modcount-status.md | 66 ++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) diff --git a/findings/control-flow/watchpoints-modcount-status.md b/findings/control-flow/watchpoints-modcount-status.md index 218443b..8a9e724 100644 --- a/findings/control-flow/watchpoints-modcount-status.md +++ b/findings/control-flow/watchpoints-modcount-status.md @@ -231,9 +231,73 @@ one build and about six minutes of VM time for two turns, and it answered three consumed parts of four lanes of static reading. **Method rule 18, again: `what writes this?` is a watchpoint.** -Two things a future user should keep: +Two things a future user should keep — and then §8 for the queue items this lane did **not** reach, +each with the reason and the cheapest next step. - **keep the canary self-test.** It is four lines and it is the difference between "nothing writes this" and "the arm silently failed". - **keep the oracle control.** Loading `ref-turn2.sav` and ending one turn costs nothing extra and proves the run is measuring the un-instrumented game. + +--- + +## 8. Not reached, and why — with the cheapest next step for each + +Lane H's account of the trade workload was more useful than the attempt would have been. Same +discipline here. + +### 8.1 `TShn` — the brief's address is wrong, and that is the finding + +The lane brief said "watchpoint on the `player+0x274` map". **That is the wrong object.** +`player+0x274` is `observed_techs` (lane V, `eventlive-verification.md` §4.3). The `TShn` map is +`ServerSystem+0x274` — the `NVO` container — per `system-visibility-record.md` §8, whose own words +are "a watchpoint on `ServerSystem+0x274`'s map during one turn". Anyone who armed the briefed +address would have watched the tech-observation array and reported a confident nothing. + +The real obstacle is structural, and it is why this was not just a fifth line in `WatchOnApplyAll`: +**a `std::map`'s nodes are heap-allocated, so `TShn` has no fixed address to arm before the node +exists.** Two ways round it, both cheap now that the module exists: + +1. **Arm the map header** (`ServerSystem+0x274 … +0x27c`) rather than a leaf. That traps the + *insertion*, which is arguably the better question — E3's puzzle is which systems get an `NVO` + entry, and Spica-vs-Bismol is a question about the gate, not about the value. +2. **Arm the leaf directly**, by loading `turn1-state.sav`, walking the map for Spica's node once at + `ProcessTurn` entry, and pointing DR0 at that node's `TShn` word. This needs the node layout, + which `objects/` has. + +Either is one arming function away. What is *not* solved is the arming point: `ApplyAllTurnCommands` +gives `S`, and `ServerSystem`s hang off `S+4+0x40`, so reaching a named system needs a name compare +in the arming code. About twenty lines. + +### 8.2 `rcex` — closed without the VM + +See `findings/subsystems/rcex-explained.md`. It is sixteen 4-bit per-player counters; nibble *p* is +set to 1 on the turn the system enters player *p*'s `AFlags` and ticked to 0 the next turn, 7/7 +across two different games. The corpus had the answer; nobody had put the nibble index next to the +`AFlags` bit index. **The one part that still wants a watchpoint** is the site of the decrement — the +"`rcex` tick" named in `ServerSystem::ProcessTurn`'s body-order note — and `rcex` is at a fixed +offset in a fixed object, so it is the *easiest* target this module has. + +### 8.3 The trade-route / spy workload — not attempted, deliberately + +Lane H's reasons (`tail-probes.md` §3.2) all still hold: trade routes need trade-station +construction plus its tech, which from `ref-turn2` is tens of End Turns at 30-60 s each with a +Build/Research click path per turn, and the turn-23 Zuul save still has none; and **no lane has +identified which UI produces a spy-program entry**, so for spies there is still no click path to +write down. This lane spent its VM time on the multiplayer demonstration and the watchpoint run +because both had falsifiable predictions attached and this does not. + +What is different now, and it is the thing that makes the next attempt materially cheaper: +**"confirm the workload took before spending a turn measuring it" is exactly what this module does**, +and both containers are one add from the arming point already in the code. + +| container | address at `ApplyAllTurnCommands` entry (`this` = `S`) | +|---|---| +| trade-route vector (slot 15's body) | `*(void**)(S + 4 + 0x154)` → `tradeManager`, then `tradeManager + 0x3c` (`_Mylast` at `+0x40`) | +| spy-program vector (slot 14's body) | `*(void**)(S + 4 + 0x158)` → `spyManager`, then `spyManager + 0x10` (`_Mylast` at `+0x14`) | + +Arm DR0/DR1 on the two `_Mylast` words and the game announces the moment either vector grows, with +the EIP and two return addresses of whoever grew it. For the spy vector that is not just workload +confirmation — **it is the answer to "which UI produces a spy-program entry", because the return +addresses name the caller.** That is a better use of the next VM slot than playing twenty turns +blind.