sots-re/findings/subsystems/rcex-explained.md
alex 92ad44efb0 lane W2: multiplayer Tier 0 played end to end with no server; ModCount/Frame/Status watchpoints; rcex explained
Multiplayer: two clients on one guest, joined by typed IP and by LAN browse, launched a
2-player game and played two turns in lockstep. 44,319 packets captured outside the guest,
ZERO to any GameSpy port. Availability check fails open exactly as lane G2 read it; Join
Manually is enabled with every gamespy.com name dead, falsifying G2's own caveat (a). The
shipped MOTD is fetched live from kerberos-productions.com and tells players to host in LAN
mode -- the developer's own statement of the same finding.

Watchpoints: a new shim module arms DR0-DR3 from StrategyServer::ApplyAllTurnCommands.
ModCount takes exactly 12 writes per End Turn (lane A2's prediction, confirmed twice, both
predicted addresses exact); Frame takes exactly one, from BeginProcessTurn+0x2a, settling the
ModCount-vs-Frame naming in A2's favour. The Player.Status writer between tail phase 31 and
the autosave EXISTS and is StrategyNetworkClient::OnMessage+0xa15 -- correcting lane T2.
Rule 19 control: the armed run reproduced the determinism oracle byte for byte.

rcex: closed from the corpus alone. 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.
2026-09-08 15:23:52 -04:00

90 lines
5.5 KiB
Markdown

# `rcex` — a per-player nibble array, and the rule is one line
- **Type:** subsystem (field semantics)
- **Address / RVA:** `ServerSystem` field, streamed as `int64` (`struct-recovery.md` row `0xf8`);
ticked inside `ServerSystem::ProcessTurn` (the "`rcex` tick", already in `addresses.json`'s
body-order note for `0x0074…`)
- **Status:** **mapped** — rule holds 7/7 across the corpus, from the saves alone, no VM time
- **Confidence:** high on the rule, medium on the *name* of the event that sets it
- **Owner / date:** lane W2 · 2026-09-08
- **Closes:** `system-visibility-prediction.md` §"What the model deliberately does not write" —
"`rcex` (6 leaves per pair). **Unexplained.**"; `system-visibility-record.md` §8 row
"`rcex` | unassigned"
---
## The rule
`rcex` is a **64-bit array of sixteen 4-bit counters, one per player index**. Player *p*'s counter
is nibble *p*, i.e. bits `4p … 4p+3`.
On the turn where a system enters player *p*'s `AFlags` — the per-player acquisition/observation
bit — nibble *p* is set to **1**. On the next turn the tick in `ServerSystem::ProcessTurn`
decrements it to **0**. That is the whole of the observed `0 → 1<<16 → 0` behaviour, and it is
exactly the shape `strategic-turn-internals.md` guessed at ("`Bats2` / `rcex` 64-bit nibble arrays:
per-player 4-bit countdowns (battle / recon cooldown)") without being able to pin the index.
## The evidence
Every system in the 11-save corpus with a non-zero `rcex`, with its `AFlags` beside it:
| save | frame | system | `rcex` | set bit | ⇒ nibble | `AFlags` | ⇒ set bit | nibble value |
|---|---:|---|---:|---:|---:|---:|---:|---:|
| `turn2-state` | 2 | Hyperion | 65536 | 16 | **4** | 16 | **4** | 1 |
| `turn2-state` | 2 | Koa'Vo | 268435456 | 28 | **7** | 128 | **7** | 1 |
| `turn2-state` | 2 | Kaa'Vaalu | 65536 | 16 | **4** | 16 | **4** | 1 |
| `turn2-state` | 2 | Markab | 65536 | 16 | **4** | 16 | **4** | 1 |
| `turn2-state` | 2 | Kea'Pono | 65536 | 16 | **4** | 16 | **4** | 1 |
| `turn2-state` | 2 | Ko'Rorkor | 65536 | 16 | **4** | 16 | **4** | 1 |
| `human-turn2-orders` | 2 | Terra | 4096 | 12 | **3** | 8 | **3** | 1 |
**7 for 7.** `nibble index == the index of the single set `AFlags` bit`, and the nibble's value is
always 1. The seventh row is the strong one: `human-turn2-orders.sav` is a **different game** with a
different map, a different system and a different player index, and it obeys the same rule.
Every other save in the corpus — `turn1-state`, `turn3-state`, `human-turn3-noderoute`, and all six
Zuul saves from frame 5 to frame 23 — has `rcex == 0` on all 28 systems, which is what a counter
that decays to zero in one turn looks like when nothing was acquired that turn.
That also explains the `system-visibility-record.md` §7 observation directly: `rcex` moves
`0 → 65536` on `turn1 → turn2` and back `65536 → 0` on `turn2 → turn3` **on the same six systems**,
and Koa'Vo takes `1 << 28` rather than `1 << 16` — because Koa'Vo was acquired by player 7 and the
other five by player 4.
## Why this had to be six leaves per pair and not more
The visibility model's residual was "6 leaves per pair" for `rcex`. It is six because six systems
changed hands in the turn-1→turn-2 pair and each contributes one `int64` leaf. The count was never
about `rcex` being complicated; it was about nobody having connected the nibble index to the player
index.
## How this could be wrong, and the symptom of each way (rule 2)
- **The index is not the player index but something correlated with it on this corpus.** Every save
here has **single-bit** `AFlags` on every system — the corpus limitation
`system-visibility-prediction.md` already flags — so "nibble index == player index" and "nibble
index == index of the lowest set `AFlags` bit" are indistinguishable. *Falsifying workload:* a
save where **two** players acquire the same system on the same turn. The rule predicts **two**
nibbles set to 1; a lowest-bit rule predicts one.
- **The initial value is not always 1.** Every observed nibble is 1. A longer cooldown (2, 3) would
appear on some other event. *Symptom:* a nibble > 1, and a system whose `rcex` takes two turns to
reach zero. Nothing in the corpus shows one.
- **It counts something other than acquisition.** `AFlags`, `VFlags` and `EFlags` are all equal on
all seven rows, so this corpus cannot separate them. *Falsifying workload:* any save where a
system's `VFlags` and `AFlags` disagree.
- **The decrement is not in `ServerSystem::ProcessTurn`.** The "rcex tick" is named in that
function's body-order note in `addresses.json`, between the `Bats2` tick and the `haltv` clear;
the corpus is consistent with it but does not prove the site. *This is the one item that wants a
watchpoint* — and it is now cheap: `src/shim/hooks/watchpoints.cpp` arms four 4-byte write
watchpoints from a known `this`, and `rcex` is at a fixed offset in `ServerSystem`. See
`findings/control-flow/watchpoints-modcount-status.md` §7.
## Cross-refs
- `findings/objects/struct-recovery.md` (the `int64` typing, and the warning that "R2 int is wrong")
- `findings/subsystems/strategic-turn-internals.md` (the nibble-array reading this confirms)
- `findings/subsystems/system-visibility-record.md` §7-8 and
`findings/subsystems/system-visibility-prediction.md` (the open item this closes)
- **`Bats2` is the same shape and is still unassigned.** It is `0` on every system of every corpus
save, so it is a rule-6 hypothesis: the nibble-per-player reading is inherited from `rcex` and has
never been exercised. The workload is a save taken on the turn after a battle.