Ghidra does not index ModRM displacements, so `lea reg,[reg+disp]` -- the MSVC idiom for taking a member's address -- is invisible to find-constant-uses. That blind spot parked ServerPlayer+0x274 and covers every non-trivial member of the ~1,600 classes still to map. tools/x86disp.py: full x86-32 length decoder (prefixes, 1/2/3-byte opcodes, ModRM, SIB, sign-extended disp8, disp32, every immediate form) swept from Ghidra's 41,089 function starts so decodes begin on real instruction boundaries. 2,174,504 instructions, 612,166 displacement sites, 100.0% code coverage, 70 desyncs (0.17%), zero unknown opcodes. Excludes no-base disp32 forms (mod=0/rm=5, sib.base=5) which are absolute globals, not member offsets. Commands: build/query/cohort/func/dis/stats/brute. Works off a gitignored local cache in dumps/ rather than hammering CT111. Validated before use: re-finds lea eax,[ecx+0x29c] in ServerPlayer::GetEventStorage (0x0080db00) and both known OnTechResearched +0x29c sites, plus a new one in ProcessTurn. Positive control: the ServerPlayer serializer scores 50/50 known offsets. sizeof(Game::ObservedTech) = 0x2c (44), proven three ways: the exact magic divide 0x2e8ba2e9 sar 3 at 0x0087239f, imul reg,reg,0x2c at 0x0087243a and 0x007b735b, and the search stride add edi,0x2c at 0x007ba257. Append site: RecordObservedTech+0xdf (0x007ba27f) -- lea ecx,[player+0x274]; call vector_ObservedTech_push_back 0x007b7320 RecordObservedTech (0x007ba1a0) is a direct callee of OnTechResearched and de-duplicates by tech name before appending. The realloc through 0x007b5820 is why lane R's guard saw all three vector words move. Element carries a vptr (RTTI .?AVObservedTech@Game@@) at +0 and a 0x18-byte std::string at +0x0c; the four on-disk ints map onto +0x04/+0x06/+0x08/+0x24/+0x28 in an order this read does NOT determine, and is not guessed. Also corrects harness-audit row 11: ComputeBudget has no store to Budget+0x64 (its only +0x64 accesses are loads off a different base), and ProcessResearch's int* overbudget arg is a ProcessTurn stack local, not Budget+0x64. Agrees with lane R's guard seeing 0 changes in 4284 calls. Honest limits are recorded in the note and the board: this is a recall tool, not an oracle. Class-level precision at 0x274 is ~13% by function, i.e. a ~900x search-space cut that still needs one call-graph check. Cohort ranking must not be used as a hard filter -- it would have discarded the correct answer here. Ghidra writeback: labels + plate comments on RecordObservedTech, vector_ObservedTech_push_back, ObservedTech_ctor, vector_ObservedTech_assign, vector_44B_grow, vftable_ObservedTech.
296 lines
18 KiB
Markdown
296 lines
18 KiB
Markdown
# Golden-trace recapture with guards (lane R, 2026-09-08)
|
||
|
||
Every golden trace went stale when the harness grew `Coverage` + guard regions
|
||
(`sots-engine` 3f0721f+, `docs/harness-audit.md`). This is the recapture on the live game.
|
||
**Read the coverage, not the verdict.** Two of the five hooks now report divergences that
|
||
their previous "verified" rows did not, and both are real.
|
||
|
||
* Build: `recap-7584bad-20260908T0615Z` (sots-engine `7584bad` + four `shim.cfg.recap*`
|
||
variants; no source change). Staged `C:\SOTS\shimdist-recap`, VM140.
|
||
* Workload: `ref-turn2.sav` → Launch → **End Turn**. The B3/misc runs continue to turn 7
|
||
(5 End Turns) to reach a research completion, which turn 1 does not contain.
|
||
* Oracle held on every run's first End Turn: `(Autosave EndTurn).sav` = `bb4fd9ac89f41e3b…`,
|
||
`(Autosave).sav` = `978041acd168b56e…` — trace and compare mode do not perturb the game.
|
||
* Artefacts: `verify/traces/recap-*.jsonl[.gz]`, reports in `verify/results/compare/recap-*`,
|
||
shim logs + screenshots in `verify/results/shim/recap-*`.
|
||
|
||
## 1. Headline table (coverage first)
|
||
|
||
| hook | run | calls | compared | diverged | exit | coverage line |
|
||
|---|---|---|---|---|---|---|
|
||
| `TechTree::ProcessResearch` | `recap-b3-compare` (t2→t7) | 15 | 15 | **3** | **1** | 15 guarded calls, **13 undeclared writes in 2 calls**, 0 unstated, 0 contradicted |
|
||
| `TechTree::ProcessResearch` | first End Turn only | 3 | 3 | **1** | **1** | 3 guarded calls, 0 undeclared writes |
|
||
| `ServerPlayer::ComputeBudget` | `recap-b1-compare` | 4284 | 4284 | 0 | 0 | 4284 guarded calls, **0 undeclared writes** |
|
||
| `ServerPlayer::OnTechResearched` | `recap-misc-compare` | 2 | 2 | 0 | (1)¹ | 2 guarded calls, **6 undeclared writes in 2 calls** |
|
||
| `ServerSystem::ProcessTurn` | `recap-misc-compare` | 140 | 140 | 0 | (1)¹ | 140 guarded calls, **15 undeclared writes in 6 calls** |
|
||
| `StrategyServer::MoveFleet` | `recap-misc-compare` | 45 | 45 | **8** | (1)¹ | 45 guarded calls, **42 undeclared writes in 15 calls** |
|
||
| all five | `recap-golden-trace` (trace mode) | 3630 | — | — | 0 | 3630 guarded calls, 9 undeclared writes in 4 calls |
|
||
|
||
¹ the three misc hooks share one log; its exit code 1 comes from `MoveFleet` alone.
|
||
|
||
## 2. `TechTree::ProcessResearch` — the over-budget event is now visible
|
||
|
||
**Yes. The defect became a divergence, on exactly the call the audit predicted.**
|
||
|
||
On the first End Turn, one call of three diverges, and in exactly one field:
|
||
|
||
```
|
||
### Game::TechTree::ProcessResearch: first 1 of 1 divergent call(s)
|
||
- call_id 0 (recap-b3-compare.jsonl:2)
|
||
side.events.after.v.next_id [exact] orig={"t":"i32","v":4} ours={"t":"i32","v":3}
|
||
```
|
||
|
||
That is bit-for-bit the same signal B3's save-file oracle produced (`docs/B3.md`:
|
||
`EvNxID 4 → 3`, one `EVENT_RESEARCH_OVERBUDGET` missing across 40,300 items). The 609 KB
|
||
save diff has been replaced by one line in the compare report.
|
||
|
||
Corroboration that this is the right branch (lane E: the post site is 0x00587b97, reachable
|
||
only from the completion-roll-**failed** path, gated `!wasDone && nowDone && owner`, and it
|
||
sets `node->flag = 2` in the same branch):
|
||
|
||
| region | before | after (orig) | after (ours) |
|
||
|---|---|---|---|
|
||
| `node[144].progress` | 2879 | 5768 | 5768 ✓ |
|
||
| `node[144].flag` | 1 | **2** | **2** ✓ |
|
||
| `events.next_id` (= `ServerPlayer+0x2b0`, `EvNxID`) | 3 | **4** | **3** ✗ |
|
||
| `rng` (`left` 432→431, `mt[624]` hash, `next_index` 192→193) | | | identical ✓ |
|
||
| `overbudget` (the caller's `int*`) | 0 | 0 | 0 ✓ |
|
||
|
||
So the node arithmetic, the flag and the single RNG draw are all reproduced; the *only*
|
||
thing missing is the event. Alloc for that call was `{tech 144 (IND_Waldo), 2889 points}`,
|
||
species 2 — B3's call 0 exactly.
|
||
|
||
### Across five turns: 15 calls, 3 divergent
|
||
|
||
| call | alloc | draws | events `next_id` orig → ours | guard hits |
|
||
|---|---|---|---|---|
|
||
| 0 | {144, 2889} | 1 | **4 → 3** | none |
|
||
| 1, 2 | {90,0}, {9,0} | 0 | 0 → 0 | none |
|
||
| **3** | {144, 2898} | 0 (ceiling ⇒ guaranteed) | **7 → 5** (two events) | 9 |
|
||
| 4, 5 | zero-spend | 0 | 0 → 0 | none |
|
||
| 6 | {9, 3064} | 1 (rolled, failed) | 8 → 8 | none |
|
||
| 7, 8 | zero-spend | 0 | 0 → 0 | none |
|
||
| **9** | {9, 3074} | 0 | **12 → 10** (two events) | 4 |
|
||
| 10, 11 | zero-spend | 0 | 0 → 0 | none |
|
||
| 12 | {18, 3087} | 1 (rolled, failed) | 14 → 14 | none |
|
||
| 13, 14 | zero-spend | 0 | 0 → 0 | none |
|
||
|
||
* **RNG matched on 15 of 15** (`mt[624]` hash, `left` and `next_index` identical on every
|
||
call) — stronger than B3's 14/15, because in this session no tech-effect callback drew.
|
||
* Calls 3 and 9 are the completions. Their non-`events` divergences are exactly the
|
||
`SetResearched`-owned fields B3 declared out of scope (`cost_rp`/`state`/`turn_available`
|
||
on unlocked children; `ours` leaves them at `INT_MAX`/0/−1). Nothing new there.
|
||
* Note the dedup rule (lane E): a duplicate post inside a turn bucket returns the existing
|
||
id **without** advancing `EvNxID`, so `next_id` not moving is not proof nothing was posted.
|
||
Calls 6 and 12 rolled and failed *without* completing by cost, so no post is expected there.
|
||
|
||
### What the guards mapped: `SetResearched`, measured
|
||
|
||
This is the "cheapest map of what `SetResearched` actually touches" the audit asked for.
|
||
Undeclared byte runs inside `guard:player` / `guard:tree_header` on the two completion calls,
|
||
resolved against the recovered `ServerPlayer` table:
|
||
|
||
| offset | len | field | call 3 | call 9 |
|
||
|---|---|---|---|---|
|
||
| `player+0x10c/0x110/0x114` | 3 each | `ConMod[0..2]` (construction cost per hull class) | ✓ | |
|
||
| `player+0x124` | 3 | `OutMod` (industrial output multiplier) | ✓ | |
|
||
| `player+0x130` | 3 | `PopMod` | | ✓ |
|
||
| `player+0x274 / 0x278 / 0x27c` | 3 each | **`vector<ObservedTech> otch`** — all three vector words move (a realloc) | ✓ | ✓ (+0x278 only, 1 byte) |
|
||
| `player+0x294` | 4 | `ResTNm` — the current research target pointer is cleared | ✓ | ✓ |
|
||
| `tree_header+0x20` | 1 | **the tree's completion-order counter** | ✓ | ✓ |
|
||
|
||
Two things worth carrying forward:
|
||
|
||
1. `tree_header+0x20` is harness-audit row 9, confirmed on the game: the per-node `order`
|
||
word is compared but the counter it is read from moves outside every Result region.
|
||
2. **`otch` (`vector<ObservedTech>`) grows on a tech completion, and no coverage note in
|
||
either B2 or B3 mentions it.** It is a third list append in the same neighbourhood as the
|
||
event list, and it *is* save state (`ServerPlayer` serializer). B3's replace-mode oracle
|
||
never saw it because turn 1 contains no completion.
|
||
3. The `player+0x10c…0x124` hits are precisely `OnTechResearched`'s own declared regions
|
||
(§4) — a clean cross-check that the guard is reporting the callee, not noise.
|
||
|
||
## 3. `ServerPlayer::ComputeBudget` — verdict held, and the guard is quiet
|
||
|
||
**4284 calls, 4284 compared, 0 divergences, `tracecmp` exit 0.** The `budget_object` guard
|
||
(Budget+0x00..0x68) caught **0 undeclared writes in 4284 calls**. In particular the
|
||
over-budget int at **`Budget+0x64`** (harness-audit row 11 — the word B1 captured only as an
|
||
argument, and arguments are never compared) **never changed value on any call**. That does
|
||
not prove the original never writes it, only that it never writes a *different* value here.
|
||
|
||
Coverage, honestly:
|
||
|
||
* **Only 20 distinct `(player, budget-output)` states exist across the 4284 calls.** 4278 of
|
||
the calls are the strategy-map UI polling the human player (one pointer accounts for 4115
|
||
of them); the End-Turn pass itself is ~8 calls, one per player in server order. The
|
||
headline number is repetition, not evidence.
|
||
* **13 of the 22 named slots are 0 on every single call**: `tradeIncome`, `shipCarriedPop`,
|
||
`secondaryManager`, `bonusIncome`, `systemIncomeNeg`, `debtInterest`, `construction`,
|
||
`expenses`, `researchMoneyGiven`, `savingsGiven`, `tra`, `researchPointsGiven`, `trp`.
|
||
The board's "budget tail coverage" backlog row is confirmed, and it is worse than stated:
|
||
five of the six *input-boundary* slots (1,2,3,4,7,11 = `systemIncomePos`, `tradeIncome`,
|
||
`shipCarriedPop`, `secondaryManager`, `systemIncomeNeg`, `construction`) are also always
|
||
zero, so they prove nothing twice over. Only `systemIncomePos` carries a value.
|
||
* Nine slots ever moved: `savings`, `systemIncomePos` (input), `savingsInterest`,
|
||
`maintenance`, `researchMoneyKept`, `available`, `researchMoney`, `researchPoints`,
|
||
`totalResearchPoints`. **Eight of those are genuinely modelled.** That is the real width
|
||
of the B1 result.
|
||
* Cross-check with B3: the End-Turn call for the AI Tarkas player yields
|
||
`researchPoints = 2889`, which is exactly the allocation `ProcessResearch` received.
|
||
* The B1 replace-mode double-run defect was **not** exercised: this lane ran compare only.
|
||
|
||
## 4. `ServerPlayer::OnTechResearched` — first guarded capture; the roll branch finally fired
|
||
|
||
**2 calls, 2 compared, 0 divergences.** The workload had to run to turn 7; the reference
|
||
turn contains no completion at all, which is why B2 never captured this hook on it.
|
||
|
||
| call | tech | branch reached | declared regions the original moved |
|
||
|---|---|---|---|
|
||
| 71 | 10001 `IND_Waldo`, node 144, `silent=false` | economy modifiers | `modifiers` (`con_mod[0..2]` 1.0 → 0.899999976), `research_target` (ptr → null) |
|
||
| 147 | 10094 `IND_TRKSTL`, node 142 | **design-option mask + the pending research roll** | `design_masks` (`b` 64 → 2097216), `research_target`, `roll`, **`rng`** |
|
||
|
||
**The `RollResearchEvent` draw is now behaviourally verified** — the board's backlog row.
|
||
On call 147 `research_roll_pending` was `true` going in; the original drew exactly one word
|
||
(`left` 375 → 374, `next_index` 249 → 250) and cleared the flag, and `ours` reproduced both
|
||
on the scratch generator bit-for-bit. That closes "region declared, ours reproduces it, but
|
||
the branch never fired".
|
||
|
||
Guard `player` (0x3e0 span) findings — **B2's known gap is now measured, not assumed**:
|
||
|
||
| offset | len | field | calls |
|
||
|---|---|---|---|
|
||
| `player+0x2b0` | 1 | **`EvNxID`** (`EventStorage+0x14`, `EventStorage` embedded at `ServerPlayer+0x29c`) | both |
|
||
| `player+0x274/0x278/0x27c` | 3 | `vector<ObservedTech> otch` — the vector grew | 71 |
|
||
| `player+0x278` | 1 | same vector, end pointer only | 147 |
|
||
|
||
So the completion events (`EVENT_RESEARCH_COMPLETE` / `_UNDERBUDGET` / `_TEMPERANCE`) are
|
||
confirmed to post on **every** completion, and `ours` posts none. The guard names the byte
|
||
rather than leaving it to a save hash that `docs/B2.md` gotcha 4 explicitly told readers to
|
||
ignore.
|
||
|
||
Not exercised at all: `silent = true`, a Zuul boarding-pod grant, a plague cure, a
|
||
node-bore allocation, the temperance sweep, the arcology re-evaluation, or any of the
|
||
writes to other objects. **Two calls, two branches. This is a thin capture.**
|
||
|
||
## 5. `ServerSystem::ProcessTurn` — first guarded capture
|
||
|
||
**140 calls (28 systems × 5 turns), 140 compared, 0 divergences.**
|
||
|
||
The coverage is the finding:
|
||
|
||
* **Only two declared regions ever moved in 140 calls**: `ntdev` (15 times) and `rcex`
|
||
(7 times). `infra`, `ibon`, `pbon`, `tres`, `haltv`, `bats2`, `bats_mask`, `rcex_mask`
|
||
and `rng` were byte-identical before and after on **every one of the 140 calls**. A
|
||
0-divergence verdict here means "two integer counters agreed and nothing else moved" —
|
||
it does not exercise the decay, the bonus applies, or the battle countdown.
|
||
* `rng` unchanged on all 140 confirms B4's headline over five turns: no colony drew a word,
|
||
i.e. no rebellion fired. `fpu_cw = 0x127f` on every call.
|
||
* Owners: 3 owned systems (`owner` 0, 1, 7) × 5 turns = 15 calls; the other 125 are unowned.
|
||
|
||
Guard `system` (0x2d8 span) hits, 15 writes on 6 calls:
|
||
|
||
| offset | len | plausible field | when |
|
||
|---|---|---|---|
|
||
| `system+0x1a4` | 12 | inside `Pop2` (civilian `Population`) at 0x1a0 — the population-group vector's three words | sys 4, 15, 16 on **turn 1 only** |
|
||
| `system+0x170` | 1 | inside `Fleets` (`vector<StarFleet*>` at 0x16c) — the end pointer | sys 15, every turn |
|
||
| `system+0xd8` | 1 | **unnamed in the contract** | sys 15, every turn |
|
||
| `system+0x238` | 4 | **unnamed in the contract** | sys 15, every turn |
|
||
|
||
System 15 is the AI's home world and it emits one new fleet per turn (see §6: fleets 50, 66,
|
||
98, 114 appear one per turn) — so `system+0x170` is the build queue pushing the new fleet
|
||
into the system's fleet vector, harness-audit row 14 observed live. `+0xd8` and `+0x238` are
|
||
unidentified and worth an addresses.json entry.
|
||
|
||
**The morale-event append (audit row 3) never fired** — no addiction on this save — so that
|
||
risk is still unmeasured, not cleared.
|
||
|
||
## 6. `StrategyServer::MoveFleet` — 8 of 45 diverge; B4's clean verdict was a one-sample verdict
|
||
|
||
**45 calls, 45 compared, 8 divergent, `tracecmp` exit 1.** Every divergence is a 1-ULP
|
||
position component.
|
||
|
||
```
|
||
- call_id 42 side.pos.after.v.y [exact] orig=3.1515913 ours=3.15159106
|
||
- call_id 79 side.pos.after.v.y [exact] orig=1.58417809 ours=1.58417821
|
||
- call_id 116 side.pos.after.v.y [exact] orig=0.0167649984 ours=0.0167651176
|
||
```
|
||
|
||
Structure of the run: 30 of the 45 calls are the six waypointless fleets
|
||
(`wpt_type = -1`, early-out, no guard hit). **15 calls do real work and every one of them is
|
||
`wpt_type = 1`** (a straight run at `speed 2 × dt 1.0`). Types 2/3/4/5 never occurred, so
|
||
node-line travel, node routes, gate teleports and the probabilistic jump remain untested,
|
||
and the generator never moved in this hook.
|
||
|
||
Of the 15 moving calls, ULP deltas (orig − ours) on the position:
|
||
|
||
| call | fleet | Δx | Δy | Δz |
|
||
|---|---|---|---|---|
|
||
| 6, 41, 78, 115, 154 | 34 | 0 | 0 | 0 / 0 / 0 / 0 / **+4** |
|
||
| 42, 79, 116 | 50 | 0 | **+1 / −1 / −64** | 0 |
|
||
| 80, 117 | 66 | 0 | **+1** / 0 | 0 |
|
||
| 118, 157 | 98 | 0 | **+1 / −1** | 0 / **+1** |
|
||
| 155, 156, 158 | 50/66/114 | 0 / **−1** / 0 | 0 / **−1** / 0 | 0 |
|
||
|
||
The 64-ULP case is a near-zero result (0.0167…): the **absolute** error is ~1.2e-7 in every
|
||
case, i.e. half an ULP at the magnitude of the *inputs* (y ≈ 1.6–4.7), not of the result.
|
||
That is the signature of one rounding too many (or too few) in the position update —
|
||
`sim::AdvanceAlongDirection` narrowing an intermediate to float32 where the original keeps
|
||
the x87 53-bit double, or the reverse. It is not a formula error: x, y and z all diverge in
|
||
some call, the step length is right, and every ship-range region matched.
|
||
|
||
**Why B4 missed it:** B4 had exactly one moving call, fleet 34 — and fleet 34 matches
|
||
bit-for-bit on four of its five moves here too. The bug only shows once other fleets start
|
||
moving. A reader of B4's trace would have seen "1 straight move, verified"; the verdict said
|
||
"MoveFleet verified".
|
||
|
||
Guard `fleet` (0x120 span), 42 writes on the 15 moving calls, resolved against the
|
||
`StarFleet` table:
|
||
|
||
| offset | len | field | count |
|
||
|---|---|---|---|
|
||
| `fleet+0x10c` | 1 | `Flags` (bit 0x100 = held this turn) | 15 — every moving call |
|
||
| `fleet+0xdc` | 1 | unnamed, just past `Speed` (`FPsp2` @0xd8) | 13 |
|
||
| `fleet+0xa0` | 4 | **`Location*`** — the fleet's Location object pointer is replaced | 8 |
|
||
| `fleet+0xcc` | 1 | `Waypoints` vector `last` pointer — **a waypoint was popped** | 2 |
|
||
| `fleet+0xdb` | 2 | straddles `Speed`/0xdc | 2 |
|
||
| `fleet+0xe0` | 26 | `FPogn2` @0xe0 … `DestPos` @0xec — the destination position rewrite | 2 |
|
||
|
||
The two calls carrying `+0xcc` and `+0xe0` (115 and 155) are the **two arrivals** in the
|
||
whole run — and both compared clean, because none of the arrival machinery is declared. B4
|
||
said "an arriving call was simply clean"; that is still true and still means nothing.
|
||
|
||
## 7. What a reader of the trace would have noticed that no verdict did
|
||
|
||
* `ProcessResearch` fires **3 times per End Turn**, not 15 — B3's 15 was five turns. Turn 1
|
||
of `ref-turn2` contains **no tech completion at all**, which is why `OnTechResearched`
|
||
produced zero records on the reference workload and B2's three completions must have come
|
||
from a longer run. A lane that captures `OnTechResearched` on the documented one-turn
|
||
recipe will get an empty log and a green report.
|
||
* **The multi-turn continuation is not reproducible across sessions.** Turn 1 is
|
||
byte-identical to the oracle every time, and calls 0–3 reproduce `docs/B3.md` exactly
|
||
(`{144,2889}`, `{90,0}`, `{9,0}`, `{144,2898}`), but from turn 4 the AI picks a different
|
||
research target than B3 recorded (`{9,3064}` here vs `{142,3064}` there, `{18,3087}` vs
|
||
`{9,3086}`) while the point totals stay nearly identical. Anything past the first End Turn
|
||
should be treated as *a* run, not *the* run.
|
||
* `ComputeBudget` is called ~4280 times per session by the UI and ~8 times by the turn pass.
|
||
Any "N compares" headline for that hook is dominated by UI polling of one player.
|
||
|
||
## 8. Still owed
|
||
|
||
* The count-only `events` region for `ProcessResearch` recommended by lane E's
|
||
`docs/E-events.md` (converts audit row 1 from "known defect" to "checked"). Not wired —
|
||
this lane's captures predate that module and re-capturing a good trace was not worth it.
|
||
* `MoveFleet`'s 1-ULP position divergence needs the rounding in `AdvanceAlongDirection`
|
||
pinned against the instruction stream.
|
||
* `ServerSystem+0xd8` and `ServerSystem+0x238` are unnamed and are written every turn by the
|
||
AI home system. `StarFleet+0xdc` likewise, on every move.
|
||
> **Follow-up 2026-09-08 (lane X):** the append is now named — `RecordObservedTech` `0x007ba1a0`,
|
||
> called directly from `OnTechResearched`; `sizeof(ObservedTech) = 0x2c`; the realloc that moves all
|
||
> three words is `vector_44B_grow` `0x007b5820`. See `findings/subsystems/observedtech-append.md`.
|
||
> That note also corrects harness-audit row 11: nothing in `ComputeBudget` stores to `Budget+0x64`.
|
||
|
||
* `vector<ObservedTech> otch` (`ServerPlayer+0x274`) is undeclared everywhere and is save
|
||
state.
|
||
* Nothing here exercises: replace mode (any hook), a Zuul double roll, a rebellion, an
|
||
addiction sweep, a plague, gate traffic, waypoint types 2–5, `silent = true` completions,
|
||
the budget expense/aid/debt tail, or the `Budget+0x64` over-budget write.
|