acdream/docs/plans/2026-08-10-options-panel-campaign.md
Erik 5eca35b706 docs: park Campaign OP — gate-4 fixes committed, user re-check owed at resume
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 23:11:43 +02:00

445 lines
28 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Campaign OP — retail four-tab Options panel
> **For agentic workers:** slices are executed by ONE Sonnet implementer at a
> time against this contract, then dual-lens Opus-reviewed, per the binding
> process rules in §8. The four research docs in §1 are the spec's data
> appendix — implementers MUST read the cited sections before coding; every
> table this plan references by section number is committed there in full.
**Status: CODE-COMPLETE 2026-08-11, PARKED 2026-08-11 (user direction) —
all nine slices landed and reviewed; OP1/OP2/OP7/OP9 CLOSED; OP3OP6 + OP8
owe their connected user gates.** Four connected gate rounds ran before
parking; every finding was root-caused and fixed same-day: #372
(blank tabs + click diagnostics), #374 (dropdown pointer routing; #376
split out), #375 (keyboard string resolver + parked prototypes + tab
activation), #371 (viewport clip), the AD-78 store-only dimming
(user-directed), and the gate-4 batch #378#382 (dropdown chrome, chat-only
opacity scope, slider captions from DAT catalog 0x78000000, the AP-205
footer field, the AP-206 state-cascade fix). **Resume point: the gate-4
fixes (`c1218426`..`d1c60df9`) are committed and probe-verified but have
NOT been seen by human eyes — the user's re-check of those five plus the
full §OP3§OP6/§OP8 script is the remaining work. Launch with
`ACDREAM_RETAIL_UI=1`.** Gate script:
`docs/research/2026-08-11-campaign-op-test-script.md`. Open tail: #373,
#376, #377 (fullscreen startup crash — settings.json workaround noted in
the issue), AP-198/199/202/203/205/206, the Shift-chord display cosmetics
and the radar-text-over-panel z-order noted in session.**
**Goal:** retail's four-tab in-game Options panel (Gameplay Options /
Character / Chat / Config), retail-faithful mechanics end-to-end: authored
LayoutDescs, the real option storage/wire split (`0x0005` single-option vs
the batched `0x01A1` PlayerModule blob), retail Apply/Reset/Defaults
semantics, live consumers where acdream has the subsystem and honest
store-only + register rows where it doesn't, plus the headless-bot
`characterOptions` seam. Campaign handoff:
`docs/research/2026-08-10-settings-track-handoff.md`.
**Architecture:** the panel is retained retail UI exactly like Campaign CH's
chat windows — LayoutDesc `0x2100002B` imported through `LayoutImporter`,
mounted in the `gmFloatyPanelUI` host `0x2100006E` under
`RetailWindowManager`, driven by a focused controller. All option
storage/policy is Runtime-owned (`RuntimeCharacterOptionsState` widened to
the full retail table); the graphical panel and the headless host are both
consumers of the one generation-gated `IRuntimeCharacterCommands` seam
(CH3's precedent). Wire builders live in `AcDream.Core.Net` beside the
existing `0x0005` codec.
**Tech stack:** existing retained UI stack (`DatWidgetFactory`,
`LayoutImporter`, `UiRoot`, `RetailWindowManager`), `AcDream.Runtime`
gameplay owners, `AcDream.Core.Net` message builders, `DatCollection` for
DAT reads. No new dependencies.
---
## 1. Research base (committed; the spec's data appendix)
| Doc | What it pins |
|---|---|
| `docs/research/2026-08-10-options-panel-structure.md` (lane A) | Layout `0x2100002B` structural inventory (§10.1), tab table property `0x2E`, row-template mechanism (ListBox `P0x64` + `AddItemFromTemplateList`), Apply/Reset/Defaults + visibility semantics (§6, §10.4), Chat tab's 13 checkbox masks + defaults (§8), Config tab's 27 rows + `UserPreferences.ini` keys (§9), open path (F11 action `0x1000001A`, toolbar button `0x1000019B`) |
| `docs/research/2026-08-10-character-options-map.md` (lane B) | The 50-row / 6-group Character-tab inventory with per-row storage bit, wire route, ACE handling, acdream consumer state (§2§5); implement-vs-store split (§7.1); bot tiers (§5.2) |
| `docs/research/2026-08-10-set-character-options-wire.md` (lane C) | The `0x01A1` body = `PlayerModule::Pack` field order (§2.3§2.7), header invariant `0x460`, the 21-id auto-save table (§3.2), 480 s timer + logout + Apply flush triggers (§3.3§3.5), ACE acceptance/landmines (§5), CH3 builder post-mortem (§6) |
| `docs/research/2026-08-10-keyboard-config-and-gameplay-tab.md` (lane D) | The seven Gameplay-tab button behaviours with byte-verified strings (§1§4), `gmKeyboardUI` structure + DAT ActionMap storage (§5§6), per-button implementability (§7.1), Config-tab ordered dump (§7.3) |
Coordinator-verified during planning (this session): toolbar button
`0x1000019B` authors `P0x12 = 0x1000001A` (committed fixture);
`retail-default.keymap.txt:148` binds `ToggleOptionsPanel` to `DIK_F11`;
`PlayerModulePackHeader` verbatim at `acclient.h:7835`; the headless
local-write gap at `src/AcDream.Runtime/Session/DirectGameRuntimeCommandAdapter.cs`
(`SetSingleOption`) vs `src/AcDream.App/Net/LiveSessionRuntimeFactory.cs:347`;
**U1 closed**: `UIOption::InqDefaultGameplayOptionProperty @0x004ef8d0`
resolves per-option defaults from the DAT `DBPropertyCollection` at
`DBCache::GetDIDFromEnumStatic(0x16, 2)` — the Defaults button restores
DAT-authored values.
## 2. Design decisions (stated, per the campaign directive; reactable at gates)
- **D1 — the retail Options panel is acdream's one in-client settings
surface.** Lane D established the F11 `SettingsPanel` was never rendered
post-V11 (`ToggleSettingsPanel()` no-op; only `IPanelRenderer` is a test
fake). Retail's own F11 IS `ToggleOptionsPanel`. So: F11 + the toolbar
button open THIS panel; acdream's client-only settings live on the
**Config tab** (retail's own client-settings tab — its 27 rows are
`UserPreferences.ini` preferences, nothing on the wire), backed by
acdream's existing settings store. The old `SettingsPanel`/`SettingsVM`
IPanel surface is retired in OP9; its tested keybind model feeds OP8.
- **D2 — retail's wire split ships exactly.** The 21 auto-save ids send
`0x0005` immediately; the rest dirty the module and ride the real
`0x01A1` blob with retail's three flush triggers (Apply, logout, 480 s
timer). No "send everything as 0x0005" shortcut (lane C: the split is
load-bearing in both directions).
- **D3 — the 50th Character row ships.** "Listen to PK death messages" is
2015-client; the DAT string exists (`0x0D16E9A3`), ACE maps id `0x34`
`CharacterOptions2 0x02000000` but never reads it. Ship the row
(user-gate axiom: the user's retail memory includes it), wire+store only,
register row for the ACE-sourced 2013-unverifiable mapping.
- **D4 — Configure Keyboard is the campaign's rebind screen** (it is the
ONLY rebind screen — D1). Port `gmKeyboardUI`'s shape and DAT ActionMap
data (lane D Option C) but persist to `keybinds.json`; retail `.keymap`
file interchange is a register-row deferral.
- **D5 — dead-endpoint buttons short-circuit to their own retail failure
strings.** Urgent Assistance / Report Abuse open a defunct
`support.turbine.com` URL in retail; acdream skips the browser launch and
emits the button's own byte-verified failure notice through the
interface-text seam (retail text, not invented), register rows filed.
In-Game Help mirrors retail-with-missing-`ACHelpPlugin.dll` behaviour.
- **D6 — Exit to Character Selection behaves as Exit Game** (+ register
row) until a pre-world character-select flow exists, but retail's
confirmation dialog (`ID_Client_EndCharacterSessionConfirm`) and mid-air
refusal ("Cannot log off while in mid-air.", byte-verified) ship now.
- **D7 — Group C re-points to server truth.** The seven currently-live
`settings.json`-backed options (lane B §7.1 group C) become
server-bit-authoritative per CH3 precedent (reseed from
PlayerDescription; local-write-then-send). `GameplaySettings`' 16
server-shadowing booleans die in OP9.
- **D8 — bots declare options by NAME.** The K1 strict config gains an
optional `characterOptions` block accepting exactly the lane-B tier-1+2
names; unknown names fail load (ACE throws on unknown ids — never let one
reach the wire). Diff-then-send after PlayerDescription seeds state;
idempotent on reconnect.
## 3. Slice map
Dependencies: OP1 → (OP4, OP7); OP2 → (OP3, OP4, OP5, OP6, OP8); OP3 → OP4/5/6
(the shell hosts the tabs). OP7 needs only OP1. Execution order below is the
default; OP7 may run any time after OP1 when the tree is free.
| Slice | Contract (summary) | Gate |
|---|---|---|
| OP1 | Runtime option map + dirty model + the real `0x01A1` builder + headless local-write fix | automated only |
| OP2 | Type 8 tab control + Type 5 template-list ListBox + remaining `UIOption_*` widget mappings + fixtures | automated only |
| OP3 | Panel shell + open paths + Gameplay tab end-to-end | user (connected) |
| OP4 | Character tab: 50 rows, consumers, Apply/Reset/Defaults | user (connected) |
| OP5 | Chat tab: opacity sliders + 5 per-window filter blocks | user (connected) |
| OP6 | Config tab: 27 rows over the client settings store | user (connected) |
| OP7 | Headless `characterOptions` block | automated + bot-vs-ACE run |
| OP8 | Configure Keyboard screen | user (connected) |
| OP9 | Closeout: retire dead surfaces, bookkeeping, test script | user (final matrix) |
---
## 4. Slice contracts
### OP1 — the Runtime option map, dirty model, and the real blob
**Files.**
- Modify `src/AcDream.Runtime/Gameplay/RuntimeCharacterState.cs`
(`RuntimeCharacterOptionsState`, `:628`): widen to the full table.
- Create `src/AcDream.Runtime/Gameplay/CharacterOptionTable.cs`: the ONE
typed table `PlayerOption id → (word: Options1|Options2, mask, isAutoSave,
clientDefault)` for ids `0x00..0x34`, transcribed from lane B §2 (verbatim
`acclient.h:4162` enum names) + lane C §3.2 (auto-save column) + D3's
`0x34` row. Reject `0x35`/`0x36` and unknown ids at the seam (lane C
§5.4.3 — ACE throws).
- Modify `src/AcDream.Runtime/GameRuntimeCommands.cs`: add
`IRuntimeCharacterCommands.SaveOptions(RuntimeGenerationToken)` (the
blob-flush verb) beside `SetSingleOption`.
- Modify `src/AcDream.Runtime/Session/DirectGameRuntimeCommandAdapter.cs`
and `src/AcDream.App/Runtime/CurrentGameRuntimeCommandAdapter.cs` +
`src/AcDream.App/Net/LiveSessionRuntimeFactory.cs:335-350`: move
local-write-then-send INTO the shared Runtime seam so BOTH hosts get
retail's ordering (fixes the headless gap lanes B+C independently found).
The `LiveSessionRuntimeFactory` local closure is deleted, not duplicated.
- Create `src/AcDream.Core.Net/Messages/SocialActions.cs` addition:
`BuildSetCharacterOptions(...)` per lane C §2.7 EXACTLY — header always
`0x460` OR-ed with present optional sections; four unconditional u32s
(header, options1, spellbookFilters, options2 in pack order §2.3); echo
last-parsed shortcuts / 8 spell lists / desired comps rather than zeroing
(§5.3); NEVER set `0x100` (lane C U2); omit `0x200` while acdream packs
nothing (safe per §2.5); 4-byte tail pad.
- Modify `src/AcDream.Core.Net/WorldSession.cs`: `SendSetCharacterOptions`.
- Dirty model in `RuntimeCharacterOptionsState`: `MarkDirty` on any
non-auto-save change, `FirstDirtiedAt`, flush triggers = explicit
`SaveOptions` command, session logout, and the 480 s timer (retail
constant, lane C §3.3) driven from the existing Runtime tick.
- Tests: `tests/AcDream.Runtime.Tests/` (table completeness ×53, auto-save
split ×53 vs lane B §2's byte-verified column, local-write-then-send on
BOTH adapter paths, dirty/flush state machine, unknown-id rejection);
`tests/AcDream.Core.Net.Tests/` blob conformance: golden byte vector +
round-trip through `PlayerDescriptionParser` (lane C §9 S-c — the CH3
builder died of green tests pinning a wrong shape; the golden vector is
non-negotiable).
**Register rows (same commit):** `0x34` mapping ACE-sourced (D3); the
480 s autosave if any part is deferred (target: not deferred);
`GetDefaultOptionValue @0x005D2A30` vs ctor-default disagreement recorded
when the client-default column lands (lane C §8.2 — reproduce, don't fix).
**Acceptance:** build + FULL Release suite green; blob golden vector
byte-exact; both adapters share one code path for the local write.
### OP2 — the two widget primitives + remaining UIOption mappings
**Files.**
- Modify `src/AcDream.App/UI/Layout/LayoutImporter.cs` +
`src/AcDream.App/UI/Layout/ElementReader.cs`: read tab-table property
`0x2E` (struct array `{0x30 button, 0x31 page, 0x32 isDefault}`) and
ListBox template-list property `0x64` (entries `{0x63 layout DID,
0x62 element id}`) into `ElementInfo`.
- Create `src/AcDream.App/UI/UiTabPanel.cs` (element Type 8 — retail
`UIElement_Panel`; renamed from this plan's original `UiTabControl` at the
OP2 rework, and a dormant `UiDatElement` subclass per AD-73): tab-button
↔ page-slot switching per lane A §5; default tab honoured.
- Create `src/AcDream.App/UI/UiTemplateListBox.cs` (element Type 5 with
authored template list): `AddItemFromTemplateList(index)` instantiates a
row from the authored template layout/element via `DatWidgetFactory`,
scrollbar named by `P0x72`.
- Modify `src/AcDream.App/UI/Layout/DatWidgetFactory.cs`: map Types 5/8 and
`UIOption_Slider 0x10000037`, `UIOption_Menu 0x10000038`,
`UIOption_CheckboxSlider 0x10000036`, `UIOption_CheckboxBitfield64
0x10000044` (LED checkbox `0x10000035` already maps to `BuildCheckbox`).
- Modify `tests/AcDream.App.Tests/UI/Layout/RetailLayoutFixtureGenerator.cs`
(`Layouts`, `:17`): add `0x2100002B`, `0x2100002A`, `0x21000028`,
`0x2100005C`, `0x21000029`. **Coordinator runs the generator**
(`ACDREAM_REGENERATE_UI_FIXTURES=1`, serial tree) and commits fixtures.
- Conformance tests pin: the tab table (4 entries, Gameplay default), all
three template arrays (lane A §10.1), the Character ListBox's 6-header /
49-toggle authored row build, scrollbar linkage.
**Acceptance:** build + FULL suite; fixtures committed and pinned; no
change to any existing widget's behaviour (the S2/AP-192 outline seams from
`aa6635ae` must be preserved in new widget builds).
### OP3 — panel shell + open paths + Gameplay tab (first vertical)
**Files.**
- Create `src/AcDream.App/UI/Layout/OptionsPanelController.cs`: mounts
`0x2100002B` in host `0x2100006E` slot `0x1000018D` (stack key 10) via
`RetailWindowManager`; tab control wiring; close button fires
`0x1000001A`.
- Create `src/AcDream.App/UI/Layout/OptionPageModel.cs`: the
`OptionPage`/`PlayerOptionPage` model — per-page option array of
`(current, saved, default)` triples + verbs Apply/Reset/Defaults with
retail's exact semantics (lane A §10.4): LED click applies immediately
(`SetCurrentValue → Apply(1)`); Apply commits baseline + `SaveOptions`
flush; Reset reverts to baseline; Defaults applies live without
committing and is never disabled; Apply/Reset disable when clean; hide →
revert uncommitted; show → apply + commit. Pure logic, unit-tested
without DAT.
- Input action `ToggleOptionsPanel` (`0x1000001A`, F11) in
`src/AcDream.UI.Abstractions/Input/` (`KeyBindings.RetailDefaults()`
the ONLY production table; #358's lesson) +
`src/AcDream.App/Input/GameplayInputCommandController.cs` routing +
toolbar button `0x1000019B` (already authors `P0x12`).
- Gameplay tab (`0x2100002A`, class `gmGameplayOptionsUI`), seven buttons
per D5/D6 and lane D §1: Exit Game → the existing graceful-close path;
Exit to Char Selection → retail confirm dialog (`RetailDialogFactory`) +
mid-air refusal via the interface-text seam, then D6's Exit-Game
behaviour; Configure Keyboard → opens OP8's screen; until OP8 lands the
button is INERT (authored, clickable, no handler — no invented text, no
stub screen), the OP3 gate script says so explicitly, and OP8's gate
re-tests it (the campaign cannot close with the button inert); Use Mouse Turning Settings → the six-option macro
(lane D §4.4) with its six retail chat lines, camera-mode consumer
verified against the camera digest in-slice (register row if the mode is
absent); In-Game Help / Urgent Assistance / Report Abuse per D5.
**Register rows:** Exit-to-char-select adaptation (D6); UA/RA dead-URL
short-circuit (D5); help-plugin behaviour (D5); mouse-turning consumer row
if needed.
**Gate:** connected — panel opens via F11 AND toolbar; tabs switch with
Gameplay default; the seven buttons behave per contract; window drags /
resizes / stacks like the CH6 floaties.
### OP4 — the Character tab
**Files.**
- Create `src/AcDream.App/UI/Layout/CharacterOptionsPageController.cs`:
binds layout `0x21000028` root `0x100001F9` ListBox `0x100001FA` through
OP2's template mechanism; 6 headers + 50 toggles (D3) in lane B §2's
authored order; every row bound by `PlayerOption` id through OP1's table
and the shared seam (auto-save ids → `0x0005` on click-apply; batched ids
→ dirty + blob per OP1).
- Defaults: extract the DAT `DBPropertyCollection`
(`GetDIDFromEnumStatic(0x16, 2)`, §1 U1-closure) via `DatCollection` at
import; conformance-pin extracted values; cross-check overlapping ids
against lane B/C's byte-verified default words and RECORD any
disagreement as a finding (never silently pick).
- Consumers (lane B §7.1): group B one-line binds — timestamps + filter
language at `RuntimeCommunicationState.AddText`; daylight
(`ForcedDayGroupIndex`), weather, fog; run-as-default in
`RuntimeLocalPlayerMovementState`; main-pack default at the pickup path;
the UI-display bits at their existing retained controllers. Group C
re-pointing per D7. Group A wire+store only. Group D register rows.
- Conformance test: all 50 rows ↔ table ↔ storage bit ↔ wire route pinned
in both directions (the CH4 registry-conformance pattern — an invented
row or a dropped row fails the build).
**Register rows:** group D deferrals (salvage, housing, fellowship-share
field, PK-deaths already rowed in OP1, mouse-turning row lives in OP3);
each group-C re-point that changes an observable default.
**Gate:** connected — LED rows toggle + persist across relogin (server
echo), timestamps/daylight/fog/weather/run-default observably switch, Apply
/ Reset / Defaults exercise retail semantics, tab-switch reverts
uncommitted edits.
### OP5 — the Chat tab
**Files.** Create
`src/AcDream.App/UI/Layout/ChatOptionsPageController.cs` binding
`0x2100005C` root `0x1000050A`: the two LINKED opacity sliders (bound to
the existing `RetailWindowOpacityController` values through
`ChatOpacityLink` — AP-190's model, now user-reachable) and the five
per-window filter blocks (SetUserData ids 8/2/3/4/5; main window 12 rows,
floaties 13 — lane A §8's byte-decoded masks) writing the per-window filter
state CH6 already consumes (`ChatWindowState.ShouldDisplay`). The
`0x1000008C` per-window blob stays local-only (already-anticipated register
row from `2026-08-09-chat-retail-window-shell.md` §6.3 — cite, don't
duplicate).
**Gate:** connected — filter checkboxes change window routing live;
opacity sliders drive the focus fade; settings survive relogin locally.
### OP6 — the Config tab
**Files.** Create
`src/AcDream.App/UI/Layout/ConfigOptionsPageController.cs` binding
`0x21000029` root `0x100001FF`: all 27 rows in lane A §9 / lane D §7.3's
authored order, backed by acdream's client settings store
(`%LOCALAPPDATA%\acdream\` — the D1 home). Rows with live subsystems bind
now: the three volume trios → the audio pipeline, "play sound only when
active", mouse-look sensitivity + invert Y, FOV, chat font size/face if the
chat pipeline exposes them. Rows without a subsystem (resolution +
fullscreen + sync, degrades, texture detail family, multi-pass alpha)
persist store-only under ONE register row enumerating them (the goal's
"honest store-only handling"); resolution's `SetConfirmChange` flow ships
whenever the consumer lands, not now.
**Gate:** connected — audio sliders audibly change mix; mouse sensitivity
observably changes; store-only rows persist across relaunch.
### OP7 — headless `characterOptions`
**Files.** Modify
`src/AcDream.Headless/Configuration/HeadlessConfiguration.cs` +
`HeadlessConfigurationLoader.cs`: optional `characterOptions` block, strict
— keys are exactly the lane B §5.2 tier-1+2 option NAMES, values bool;
unknown key = load failure (D8). Modify the session host
(`src/AcDream.Headless/Hosting/`) to diff declared vs
PlayerDescription-seeded state after LoginComplete and send changes through
the shared seam (auto-save ids as `0x0005`, remainder via one
`SaveOptions` blob), honouring #368's one-dedicated-update-thread contract.
Idempotent on reconnect (retail itself no-ops unchanged options — lane C
§3.5). Headless tests: schema rejection, diff-only sends, reconnect
idempotence, thread affinity preserved.
**Gate:** automated + one live bot-vs-local-ACE run (no graphical client)
showing declared options land and survive reconnect.
### OP8 — Configure Keyboard
**Files.** Create
`src/AcDream.App/UI/Layout/KeyboardConfigController.cs` (+ a
`src/AcDream.Core/...` DAT `ActionMap` (DBO type `0x27`) reader if
`DatCollection` lacks one): `gmKeyboardUI`'s six ActionClass list boxes
via OP2's Type 5 widget, rows = label + tooltip + N key buttons + Clear
from the DAT master maps (DIDs `0x14000000`/`0x14000002` — the exact
enum→DID pairing is lane D unknown #4, resolved in-slice by dumping both),
merged with live `KeyBindings`; left-click key button → `InputDispatcher`
modal capture; right-click erases; N-way cross-map conflicts + the
non-user-bindable refusal per lane D §5; Save/Cancel; Reset-to-defaults
reloads the DAT maps. Persistence: `keybinds.json` (D4).
**Register rows:** `.keymap` file interchange not implemented (D4); any
retail column/behaviour consciously narrowed.
**Gate:** connected — rebind a movement key, conflict prompt on a taken
chord, persistence across relaunch, reset restores retail defaults.
### OP9 — closeout
- Retire `SettingsPanel`/`SettingsVM`'s IPanel surface +
`SettingsDevToolsComposition` wiring + `DevToolsGameplayCommands`
no-ops; delete `GameplaySettings`' 16 server-shadowed booleans (lane B
§7.2.5), re-scoping true client-only settings into the D1 store. Every
deletion checked against consumers; no behaviour regression.
- Bookkeeping: plan status flips, ISSUES sweep (#358 retest against OP8's
screen — its Ctrl+M lesson lives in `RetailDefaults()`), register
reconciliation, CLAUDE.md Current-state paragraph (per
`feedback_claude_md_staleness`), memory digest update
(`project_chat_digest` addendum or a new settings digest).
- Write `docs/research/2026-08-10-campaign-op-test-script.md`: the
connected-gate script covering every OP3OP8 gate item, per-tab, with
expected retail behaviours — the campaign's stop condition is this
script ready plus all slices code-complete.
**Gate:** the user's final connected matrix (their eyes, their pace).
---
## 5. What is explicitly OUT of scope
- Packing the `0x200` GameplayOptions blob section (per-window chat state
on the wire) — a follow-on (CH6f shape), pre-anchored by lane C U1 and
`2026-08-09-chat-retail-window-shell.md` §6.3's register row.
- A pre-world character-select flow (D6 adapts; its register row carries
the future work).
- Retail `.keymap` file read/write (D4 register row).
- The `0x21000017` docked `gmPanelUI` host variant — acdream ships the
floating host only (register row in OP3 if the review deems it a
divergence; retail exposes both).
## 6. Verification discipline
Per commit: `dotnet build` + FULL Release suite green (baseline at plan
time: 12,611 / 4 skips / 0 failures at `29138430`+). Golden byte vectors
for every wire builder. Conformance pins for every authored inventory
(row lists, template arrays, tab table, checkbox masks). Register rows in
the SAME commit as the deviation. No user-visible placeholder text ever —
all user-facing strings resolve from the DAT string tables
(`0x23000001`/`0x23000003`) by `compute_str_hash` name, never hard-coded
English.
## 7. Review protocol
Dual-lens Opus review per slice (mechanism-faithfulness lens ×
regression/blast-radius lens), fixes applied by the implementer; REJECT →
focused re-review; TWO failures → Fable fixes directly (user-directed
2026-08-10). Review findings that feed both a fixer and a re-reviewer are
persisted to a committed findings doc BEFORE dispatch.
## 8. Process rules (binding, inherited from Campaign CH via the handoff)
Max 34 agents in parallel INCLUDING children; every agent prompt carries
an explicit no-subagent clause; ONE builder/tester on the tree at a time
(read-only research may overlap); agents never launch the graphical
client; the user runs all connected gates; screenshots transcribed into
docs immediately; ledger placeholders anchored per-row, post-amend SHAs
recorded by the coordinator; decomp claims byte-verified against the
PDB-paired binary (`check_exe_pdb.py` MATCH first); stalled agents resumed
via SendMessage before any redo; agent claims spot-verified at the seams
before anything builds on them.
## 9. Ledger
| Slice | Status | Commit(s) | Review | Gate |
|---|---|---|---|---|
| OP1 | CLOSED | `86c0a7e0` + fixes `09029f9f` + residuals `6f48e341` | dual APPROVE-WITH-FIXES (`2026-08-10-op1-review-{mechanism,blast}.md`) → re-review CLOSED (`2026-08-11-op1-rereview.md`); residuals R1/R2/R3 landed | automated only — n/a |
| OP2 | CLOSED | `df9c7a35` (REJECTED) → rework `b236a442` → closure (this commit) | dual REJECT (`2026-08-11-op2-review-{mechanism,blast}.md`) → re-review blast CLOSED / mechanism REOPEN-on-one (`2026-08-11-op2-rereview-{mechanism,blast}.md`) → coordinator closure: AP-195 filed, AD-73 addendum, tooltip port, zero-children pin | automated only — n/a |
| OP3 | CODE-COMPLETE, gate READY | `9d26ecc6` → fixes `386076af` → residuals `cb334690` | dual APPROVE-WITH-FIXES (`2026-08-11-op3-review-{mechanism,blast}.md`) → re-review REOPEN-narrow (`2026-08-11-op3-rereview.md`) → coordinator residuals landed | connected gate OWED (script §OP3) |
| OP4 | CODE-COMPLETE, gate READY | `22b86b9f` → fixes `bc43fb1d` → residuals `ac0304dc` | dual APPROVE-WITH-FIXES (`2026-08-11-op4-review-{mechanism,blast}.md`) → re-review REOPEN-narrow (`2026-08-11-op4-rereview.md`) → coordinator residuals landed | connected gate OWED (script §OP4) |
| OP5 | CODE-COMPLETE, gate READY | `e71e5a96` (AP-195 retired) → fixes `6d0b0f92` → residuals `67b0815c` | combined APPROVE-WITH-FIXES (`2026-08-11-op5-review.md`) → re-check CLOSED (`2026-08-11-op5-recheck.md`) → coordinator drag residuals landed | connected gate OWED (script §OP5) |
| OP6 | CODE-COMPLETE, gate READY | `f5ac1742` (REJECTED) → rework `472525b9` → doc residuals (coordinator) | REJECT (`2026-08-11-op6-review.md`) → re-review CLOSED, all six caption sites byte-decoded (`2026-08-11-op6-rereview.md`) | connected gate OWED (script §OP6) |
| OP7 | CLOSED | `09cb548a` → fixes in `7b60e71b` (shared commit, see its message) | combined-lens APPROVE-WITH-FIXES (`2026-08-11-op7-review.md`); all nine findings closed | live bot-vs-ACE gate PASSED 2026-08-11 (coordinator; evidence in script §OP7) |
| OP8 | CODE-COMPLETE, gate READY | `b4edee97` (REJECTED) → rework `b1968ce9` → residuals `f1d50207` → merge `1c5cd969` | dual REJECT (`2026-08-11-op8-review-{mechanism,blast}.md`) → rework → re-review REOPEN-narrow (`2026-08-11-op8-rereview.md`) → coordinator round-2 residuals (inert-row conflict exclusion, DAT-default display, injectivity pin; #373 filed) | connected gate OWED (script §OP8) — merged onto the campaign tip AFTER `057d8cd7` per the re-review's merge note, so the #372 viewport fix covers OP8's six ListBoxes |
| OP9 | CLOSED | `371197a3` → review residuals `07f2b3f7` | combined-lens APPROVE-WITH-FIXES (`2026-08-11-op9-review.md`, `289bf5bc`): MUST-FIX 1 (SaveAudio→ApplyAudio live-apply lost its only assertion — restored + failure-ordering pin), SF-2 (code-structure.md seam list), SF-3 (dead residues: private SaveCharacter, ISettingsStorage.SaveCharacter, IngressShutdownRoots.Settings — deleted; SettingsStore's public SaveCharacter kept as the tested storage API), SF-4 (test-delta was 84 = 94 removed/10 added, not the commit message's "80 exactly"; no live-behavior test in the gap besides MUST-FIX 1's), SF-5 (dangling comment), NIT 6 (AP-196 channel attribution corrected in-register) — all closed by coordinator residuals | closeout gate = the user's final connected matrix over §OP3§OP8 |