docs: Campaign OP plan — nine review-gated slices for the retail Options panel
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 <noreply@anthropic.com>
This commit is contained in:
parent
e3e0559f65
commit
b585d80e7a
1 changed files with 426 additions and 0 deletions
426
docs/plans/2026-08-10-options-panel-campaign.md
Normal file
426
docs/plans/2026-08-10-options-panel-campaign.md
Normal file
|
|
@ -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 | — | | | |
|
||||
Loading…
Add table
Add a link
Reference in a new issue