From b585d80e7af82f7279cef320905154d927d5be06 Mon Sep 17 00:00:00 2001 From: Erik Date: Mon, 10 Aug 2026 22:52:16 +0200 Subject: [PATCH] =?UTF-8?q?docs:=20Campaign=20OP=20plan=20=E2=80=94=20nine?= =?UTF-8?q?=20review-gated=20slices=20for=20the=20retail=20Options=20panel?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fable-authored plan from the four seam-verified research lanes. Eight design decisions stated per the campaign directive (reactable at gates): the retail panel is acdream's ONE in-client settings surface (the F11 SettingsPanel was never rendered post-V11 — lane D's verified correction), retail's 21-vs-batched wire split ships exactly with all three flush triggers, the 2015-only PK-deaths row ships wire+store with a register row, Configure Keyboard is the campaign's only rebind screen (DAT ActionMap data, keybinds.json persistence), dead support-URL buttons short-circuit to their own byte-verified retail failure strings, Exit to Character Selection adapts to Exit Game behind retail's confirm dialog + mid-air refusal, lane B group C re-points to server truth per CH3 precedent, and bots declare options by NAME in the K1 strict schema. Slices: OP1 Runtime map/dirty/blob (+ the headless local-write fix both lanes found), OP2 the two missing widget primitives + UIOption mappings, OP3 shell + Gameplay tab vertical, OP4 Character tab, OP5 Chat tab, OP6 Config tab, OP7 headless characterOptions, OP8 Configure Keyboard, OP9 closeout + connected test script. U1 closed during planning: the Defaults button restores DAT DBPropertyCollection values (UIOption::InqDefaultGameplayOptionProperty @0x004ef8d0, GetDIDFromEnumStatic(0x16, 2)). Co-Authored-By: Claude Fable 5 --- .../2026-08-10-options-panel-campaign.md | 426 ++++++++++++++++++ 1 file changed, 426 insertions(+) create mode 100644 docs/plans/2026-08-10-options-panel-campaign.md diff --git a/docs/plans/2026-08-10-options-panel-campaign.md b/docs/plans/2026-08-10-options-panel-campaign.md new file mode 100644 index 00000000..97a8a030 --- /dev/null +++ b/docs/plans/2026-08-10-options-panel-campaign.md @@ -0,0 +1,426 @@ +# 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: ACTIVE — planned 2026-08-10. No slices landed yet.** + +**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/UiTabControl.cs` (element Type 8): 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 OP3–OP8 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 3–4 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 | — | | | | +| OP2 | — | | | | +| OP3 | — | | | | +| OP4 | — | | | | +| OP5 | — | | | | +| OP6 | — | | | | +| OP7 | — | | | | +| OP8 | — | | | | +| OP9 | — | | | |