acdream/docs/plans/2026-08-10-options-panel-campaign.md
Erik b4edee970f feat(ui): Campaign OP slice OP8 — Configure Keyboard
Ports retail's Configure Keyboard screen (gmKeyboardUI, LayoutDesc
0x21000009) — its own separate full-screen window, not a fifth Options-
panel tab. Retires OP3's INERT contract for the Gameplay tab's Configure
Keyboard button (0x10000204).

DAT reader (src/AcDream.Core/Input/RetailActionMap.cs): reads the
ActionMap singleton (DID 0x26000000, empirically the only one — not
0x27000000 as GetDBOType's Turbine-internal tag would suggest) and both
MasterInputMap defaults (0x14000000 "gmDefaultMap"/0x14000002
"DefaultMap"), union-merged per (InputMapId, ActionId) — proven order-
independent since the two maps' one shared context (0x5) has disjoint
action-id sets. Empirically resolved three lane-D unknowns against the
live DAT: the six ActionClass values (1=Movement, 2=Camera, 3=UI,
4=Combat, 5=Emote, 7=CharacterSettings — 6 is genuinely absent), that
the six unnamed InputMaps are 100% non-bindable (render nothing, not an
unlabeled group), and that the enum-to-DID pairing for the two master
maps is inconsequential to the merge result.

Identity table (src/AcDream.UI.Abstractions/Input/RetailActionIdentityTable.cs):
maps DAT (InputMapId, ActionId) pairs to acdream's InputAction where a
live consumer exists (~140 of 306 user-bindable rows — Movement/Camera/
Combat map almost completely; UI/Quickslot/Chat partially; only 5 of 87
Emotes and none of 48 CharacterSettings hotkeys, since acdream has no
general emote player or hotkey-to-option-toggle dispatcher yet). Every
entry cross-verified by label match AND a DAT-default-vs-
KeyBindings.RetailDefaults() byte comparison (RetailActionIdentityRoundTripTests),
which caught a real off-by-one in the Quickslot 13-18 block before it
shipped and found three genuine pre-existing RetailDefaults() gaps
(walk-mode's Shift-echoed chord, ten CameraAlternateControls arrow-key
alternates, and the Quickslot Ctrl+N use-vs-select ambiguity) — none
introduced by this slice, all documented rather than silently patched.

KeyboardConfigController: six ActionClass list boxes built from the
DAT, merged with live KeyBindings for mapped rows (rebind applies
immediately through the same InputDispatcher every other input path
uses) and a new sibling RetailUnmappedKeyBindings store for rows with
no InputAction yet. Left-click a key button opens real InputDispatcher
modal capture; right-click erases that slot. N-way conflict detection
scans every other row plus the live KeyBindings table for acdream-only
actions (Ctrl+M mute, debug F-keys) as the non-user-bindable refusal
analogue, using retail's own byte-verified "Could not overwrite "
string (table 0x23000004). OK/Cancel/Defaults/Revert reuse the
OptionPage/IOptionRow verb model via a new ActionKeyMapOptionRow.
Persistence is keybinds.json only (D4 — no .keymap file interchange).

Five register rows: AP-202 (.keymap interchange narrowing), AP-203
(store-only rows with no live consumer), AP-204 (silent auto-reassign
instead of retail's confirm dialog; OK/Cancel ported as left-click not
right-click-release).

Small supporting additions: UiButton.OnRightClick (additive, no
existing behavior changed), InputDispatcher.Bindings getter (the
screen's single live-truth read seam), RetailScanCodeMap (DIK scan
code <-> Silk.NET Key, keyboard + the one mouse-device row).

19 new tests (6 ActionMap reader conformance incl. live-DAT row-count/
label pins, 1 DAT-vs-RetailDefaults round-trip, 12 controller
behavior tests against the committed keyboard_config_21000009.json
fixture) — full solution suite 13,147 passed / 4 skipped / 0 failed
(baseline 13,128/4/0, zero regressions).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 09:19:54 +02:00

26 KiB
Raw Blame History

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 0x34CharacterOptions2 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 (this branch's commit) not yet reviewed connected gate OWED (script §OP8)
OP9