445 lines
28 KiB
Markdown
445 lines
28 KiB
Markdown
# 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; OP3–OP6 + 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 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 | 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 |
|