Three findings from the user's first Configure Keyboard look (OP8 gate, 2026-08-14), each root-caused against the named retail decomp: - #394 row-caption font: the synthesized action-label UiText never set DatFont and fell to the debug bitmap font. The authored row template (0x21000009/0x1000002F, retail UIOption_ActionKeyMap) carries FontDid 0x4000000A (18px serif) — Bind now takes resolveTemplateFont and applies the template's own authored font, resolved once per template pair. - #395 key captions: raw enum spellings ("Shift+ShiftLeft") replaced by the port of CInputManager_WIN32::GetNameFromKey @0x00687F40 / GetNameFromKey_Internal @0x00687800 (RetailKeyNames): DAT string-table override by DIK-name hash (key enum 4 -> 0x2300000A, meta enum 5 -> 0x2300000B, delimiter enum 3 -> 0x23000007 — GetDIDByEnum category 4, live-probed), else the OS keyboard layout's own key name ("SKIFT") via PlatformKeyNameProvider (Win32 GetKeyNameTextW — register row AD-96 for the DirectInput-vs-GetKeyNameText adaptation), else the DIK-suffix spelling. Bare modifier-key bindings show only the key name. - #396 capture feedback: clicking a mapping button now opens retail's instruction dialog (InitiateBinding @0x004899D0 -> OpenMapWarnDialog @0x00488A00): a type-2 WAIT dialog on retail's MapWarn queue key 0x10000001 with ID_ActionKeyMap_MapInstructions (0x23000004, ACTION variable interpolated), closed on key hit or ESC through the capture callback; capture is not armed if the dialog cannot open, matching retail. New RetailWaitDialogView (wait root 0x31 — same authored popup/message pair 0x3D/0x3E as the confirmation root, live-DAT probed) behind a shared IRetailDialogView presenter seam. Probe evidence (env-gated, kept): KeyboardConfigLiveMountProbeTests.ProbeKeyboardFontsAndKeyNameStrings. Register: AD-96 filed. Gate script OP8 section updated (step 4 rewritten; the "pressed/active state is enough" contract is retired). Full Release solution suite green (13,424 passed / 4 skips). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1128 lines
63 KiB
Markdown
1128 lines
63 KiB
Markdown
# Campaign OP connected-gate test script
|
||
|
||
**Status: COMPLETE (2026-08-11) — the campaign is code-complete and this
|
||
script is its connected-gate contract.** §OP3–§OP6 and §OP8 are the gates
|
||
the user still owes their eyes; §OP7's gate already PASSED (coordinator,
|
||
evidence in-section). Launch with `ACDREAM_RETAIL_UI=1` against live ACE.
|
||
Re-gate note for the previously-blank tabs: #372's viewport fix landed
|
||
(`057d8cd7`), and every wired Gameplay-tab button now logs
|
||
`[options] gameplay button 0x... clicked` — a silent click in that log IS
|
||
evidence, report it per-button.
|
||
|
||
**Dropdown appearance (fix #385, 2026-08-13 happy-testing round).** Every
|
||
Config-tab dropdown (Sound Features, Resolution, texture menus, Chat Font
|
||
Size, …) now matches the authored retail style: the button caption and the
|
||
popup rows draw WHITE and CENTERED (not gold/left), and the popup's height
|
||
conforms to its item count (a 3-choice menu opens 3 rows tall, a 10-mode
|
||
Resolution list opens 10 rows tall — retail's `RecalculatePopupSize`
|
||
size-to-content port; there is no 6-row scroll window on these menus any
|
||
more). A clicked dropdown with NO choices no longer opens an empty popup.
|
||
Report any dropdown still showing gold/left text or a fixed-height popup.
|
||
|
||
**Caption dimming (AD-78, user-directed, gate 2).** As of this session, every
|
||
row this script marks as a store-only row (see each section's own "Store-only
|
||
rows" subsection, or the register row AD-78 for the complete four-controller
|
||
enumeration) renders its caption in a dimmed neutral grey instead of the
|
||
normal white/DAT-authored color — the Character tab, the Config tab, and
|
||
Configure Keyboard's unmapped rows. A dimmed caption = stored (and
|
||
wire-synced where the row is auto-save) but drives nothing yet — this is
|
||
CONTRACTED, not a bug; do not report it. What IS worth reporting: a row this
|
||
script or AD-78 lists as LIVE that renders dimmed (a wiring regression), or a
|
||
row listed as store-only/dimmed that visibly DOES something (its "no
|
||
consumer" premise going stale without its caption catching up).
|
||
|
||
This document is the script the user runs against the live connected
|
||
client (`ACDREAM_LIVE=1` against the local ACE server) to accept each
|
||
slice. Each item states what to do and what retail-faithful behavior to
|
||
expect. Anything marked **INERT** is authored and clickable but
|
||
deliberately does nothing yet — that is the correct, contracted behavior
|
||
for this slice, not a bug.
|
||
|
||
---
|
||
|
||
## OP3 — panel shell, open paths, Gameplay tab
|
||
|
||
### Opening and closing the panel
|
||
|
||
1. **Press F11.** The Options panel opens — a floating, resizable window
|
||
titled by its own chrome (no title-bar text is authored; the window
|
||
itself is the visual identity). The **Gameplay Options** tab is
|
||
selected by default (leftmost tab, matching the user's own retail
|
||
screenshot).
|
||
2. **Press F11 again.** The panel closes.
|
||
3. **Click the toolbar's Options button** (the icon at the right end of
|
||
the shortcut row, next to Inventory). The panel opens. If a DIFFERENT
|
||
`gmPanelUI`-family panel was already open (Character Info, Skills,
|
||
Vitae, Inventory, etc.), opening Options closes it first — this is
|
||
retail's real "one active panel" behavior (`gmPanelUI`'s shared
|
||
geometry/exclusive-child model), not new to this panel. **Note the
|
||
button's STATE change, not just its behavior:** before this slice this
|
||
button was greyed out/disabled (panel id 10 was not in the catalog) and
|
||
clicking it did nothing. It should now be fully enabled, AND it should
|
||
highlight (same visual as the Inventory/Character/Magic buttons) while
|
||
the Options panel is open, un-highlighting when it closes.
|
||
4. **Click the panel's own close (X) button**, top-right of the window
|
||
chrome. The panel closes — same action as F11.
|
||
5. **Drag the window by its border/chrome; resize it from the BOTTOM
|
||
edge only.** Options shares its geometry policy with every other
|
||
`gmPanelUI` sibling (Character/Inventory/Spellbook/the indicator-detail
|
||
panels): draggable, resizable from the bottom edge only (NOT the top,
|
||
left, or right edges, and NOT a corner — the panel does not resize
|
||
horizontally at all), and it remembers its HEIGHT across a close/reopen
|
||
within the same session, same as every sibling. This is a correction
|
||
from OP3-as-landed, which briefly gave Options all-four-edge/horizontal
|
||
resize that no sibling has and that silently reverted to 300px wide the
|
||
next time a sibling panel was shown.
|
||
|
||
### Tab switching
|
||
|
||
6. **Click each of the four tabs** (Gameplay Options, Character, Chat,
|
||
Config). Each switches the visible page; exactly one page is visible
|
||
at a time. Character/Chat/Config show their AUTHORED content (row
|
||
templates, Apply/Reset/Defaults buttons, scrollbars) but nothing on
|
||
those three tabs is wired to live data yet — that is OP4/OP5/OP6's
|
||
scope, not a bug in this slice. Only the currently-selected tab's
|
||
button highlights as "open"; the others read "closed".
|
||
7. **Switch away from Gameplay and back.** No crash, no stuck state.
|
||
|
||
### The seven Gameplay-tab buttons
|
||
|
||
8. **Exit Game.** Click it. The client logs off and closes gracefully —
|
||
same behavior as pressing Escape then confirming, or closing the
|
||
window. **No confirmation dialog** (retail has none on this path).
|
||
9. **Exit to Character Selection.** Click it. A confirmation dialog
|
||
appears ("Are you sure you want to end this character session?" or
|
||
the DAT-resolved retail equivalent). Click **Yes** while standing on
|
||
solid ground: the client logs off and closes — **today this behaves
|
||
identically to Exit Game**, NOT a return to a character-select
|
||
screen (acdream has no pre-world character-select flow yet — register
|
||
row AD-74). Click **No**: nothing happens, panel stays open.
|
||
10. **Exit to Character Selection while airborne** (jump and click the
|
||
button mid-air, or confirm while still in the air). Expect the chat
|
||
line **"Cannot log off while in mid-air."** in the SpewBox/chat
|
||
scroll instead of logging off — this is retail's own byte-verified
|
||
refusal, faithfully ported.
|
||
11. **Configure Keyboard.** Click it. **INERT — nothing happens.** This
|
||
is the contracted behavior for OP3; OP8 wires the real Configure
|
||
Keyboard screen, and OP8's own gate re-tests this exact button. Do
|
||
not report this as a bug for OP3.
|
||
12. **In-Game Help Files.** Click it. **INERT — nothing happens.**
|
||
Retail's own help viewer is a third-party plugin
|
||
(`plugins\ACHelpPlugin.dll`) acdream does not have; retail itself
|
||
fails silently with the plugin absent, and this button mirrors that
|
||
(register row AD-76).
|
||
13. **Urgent Assistance.** Click it. Expect a chat line explaining the
|
||
web browser could not be launched and pointing at
|
||
`http://support.turbine.com/ics/support/ticketnewwizard.asp?style=classic`
|
||
— retail's own byte-verified failure text, always shown (the URL is
|
||
dead in 2026, so acdream never attempts to open a browser — register
|
||
row AD-75). **No native browser window opens, no OS dialog box.**
|
||
**Legibility check (blast review SHOULD-FIX 4):** this is a
|
||
THREE-LINE, ~110-character-URL body landing in the SpewBox overlay,
|
||
which auto-expires after 5 seconds. Read the whole body BEFORE it
|
||
disappears — if you cannot finish reading it (especially the URL) in
|
||
that window, report it as a legibility problem; the honest fix would
|
||
be routing it to the scrolling chat transcript (which stays and
|
||
scrolls back) instead of shortening the timeout.
|
||
14. **Report Abuse.** Click it. Same shape as Urgent Assistance
|
||
(including the same legibility check), with its own retail text
|
||
("...to submit an abuse report...").
|
||
15. **Use Mouse Turning Settings.** Click it. Expect one retail chat
|
||
line per changed value among: Camera Stiffness, Camera Adjustment,
|
||
Mouse Sensitivity, Align To Slope, Invert Mouselook Axes, Turn to
|
||
Face Camera — a FRESH character (never touched these settings) sees
|
||
all six lines; clicking the button a second time in the same
|
||
session sees zero lines (everything already at the macro's target).
|
||
**These six lines are byte-verified `RetailLogTextType.Magic` (0x07),
|
||
NOT the client-local refusal type** — expect LIGHT BLUE text in the
|
||
SCROLLING CHAT TRANSCRIPT (not the transient SpewBox overlay). Do
|
||
NOT expect a timestamp prefix: acdream renders no chat timestamps
|
||
yet (the Display Timestamps consumer is OP4 scope, and there is no
|
||
chat-log file — TS-69), so a bare light-blue line is CORRECT here
|
||
(OP3 re-review R1). If you see bright red text in a transient
|
||
5-second overlay instead (the ClientLocal/SpewBox look used by the
|
||
mid-air refusal and UA/RA above), that is the pre-fix-round bug —
|
||
report it.
|
||
**Known, registered gap (TS-74): acdream has no persistent
|
||
"turn to face camera" mouse-turning MODE yet, so this button changes
|
||
stored preferences and sends the wire bit, but you will not see the
|
||
camera actually start turning with mouse movement** — that consumer
|
||
does not exist yet. Verify the CHAT LINES and that the button is
|
||
clickable/does not crash; do not expect a camera-behavior change.
|
||
|
||
### Cross-session persistence (mechanism review S6)
|
||
|
||
16. **Relog the SAME character** (log off through Exit Game, or
|
||
disconnect/reconnect, then log back in) after step 15 has already
|
||
shown all six lines once. Open the Options panel and click "Use
|
||
Mouse Turning Settings" again. Expect **zero** chat lines this time —
|
||
`UseMouseTurning` is an auto-save id (`SetSingleCharacterOption
|
||
0x0005`), so the bit reached ACE on the first click and the fresh
|
||
post-relog `PlayerDescription` echoes it back as already-on. Seeing
|
||
the "Turn to Face Camera was changed..." line AGAIN after a clean
|
||
relog means the bit did not actually persist server-side — report it.
|
||
17. **Fully relaunch the client** (close the window, start a new
|
||
process) after step 15. Reopen the Options panel — no button click
|
||
needed. The five CLIENT-LOCAL preferences (Camera Stiffness,
|
||
Adjustment Speed, Mouse Sensitivity, Align To Slope, Invert Mouselook
|
||
Axes) persisted to `settings.json`'s `cameraTurning` section should
|
||
still read at the macro's target values from the PREVIOUS session —
|
||
click "Use Mouse Turning Settings" once more and confirm you again
|
||
see zero lines (proving the five client-local prefs, not just the
|
||
one server bit, survived the relaunch).
|
||
|
||
### What to report
|
||
|
||
- Any of the above NOT matching (wrong text, wrong tab default, window
|
||
not draggable/resizable, close button not working, F11/toolbar not
|
||
opening the panel, opening Options not closing a sibling panel, toolbar
|
||
button not highlighting while the panel is open).
|
||
- Any exception/crash on any of the 17 steps.
|
||
- Whether the confirmation dialog's exact wording looks retail-correct
|
||
(it resolves from the DAT string table at runtime; a fallback English
|
||
string only appears if that resolution fails, which would itself be
|
||
worth reporting).
|
||
|
||
### Explicitly NOT in scope for this gate
|
||
|
||
- Character/Chat/Config tab content (rows, checkboxes, sliders, live
|
||
data) — OP4/OP5/OP6.
|
||
- Configure Keyboard's actual screen — OP8.
|
||
- Any observable camera change from "Use Mouse Turning Settings" — no
|
||
acdream consumer exists yet (TS-74).
|
||
|
||
---
|
||
|
||
## OP4 — the Character tab
|
||
|
||
The Character tab (`0x21000028`) is the biggest single tab: 6 authored
|
||
group headers and 50 toggle rows (49 from the 2013 build + D3's "Listen to
|
||
PK death messages"), each bound by its retail `PlayerOption` id through
|
||
`CharacterOptionTable` and the shared `RuntimeCharacterOptionsState.
|
||
TrySetOption` seam every other Options-panel consumer already uses.
|
||
|
||
### Opening the tab and reading the rows
|
||
|
||
1. **Open the Options panel (F11) and click the Character tab.** Six
|
||
group headers appear top-to-bottom: **User Interface Behavior** (3
|
||
rows), **User Interface Display** (15 rows), **Grouping** (6 rows),
|
||
**Other Players** (11 rows), **Character Behavior** (7 rows), **Chat**
|
||
(8 rows — 7 from retail plus "Listen to PK death messages"). Every row
|
||
has a label; hover a few and confirm a tooltip appears. If ANY row
|
||
shows no label at all, that is a DAT string-resolution miss worth
|
||
reporting (the code deliberately renders nothing rather than invented
|
||
English when a string fails to resolve — a blank row is the correct
|
||
FAILURE shape, not a crash, but still worth flagging which row).
|
||
2. **Scroll the list** via the scrollbar. All 50+6 rows are reachable;
|
||
the list does not clip or overlap the Apply/Reset/Defaults buttons at
|
||
the bottom. **Gate-4 re-test note (#381):** the footer strip behind the
|
||
three buttons previously had NO backing — scrolled row text/content was
|
||
visible bleeding through between and behind Apply/Reset/Defaults. The
|
||
footer now has an opaque backing field; report if any content is still
|
||
visible through or around the three buttons (this applies identically
|
||
on the Chat and Config tabs' own footer strips, OP5/OP6 below).
|
||
3. **Confirm the checkboxes reflect your character's actual state** —
|
||
e.g. if you have `IgnoreAllegianceRequests` on from a prior session,
|
||
its row should show checked on open (seeded from the live server bit
|
||
at panel-mount time, not always-off).
|
||
|
||
### Auto-save rows — immediate `0x0005`
|
||
|
||
4. **Toggle "Listen to General Chat" off, then on again**, without
|
||
clicking Apply. Confirm the effect is IMMEDIATE and matches the
|
||
existing Settings-panel Hear\*Chat behavior: turning it off silently
|
||
drops you from general chat (no more `/g` messages appear); turning it
|
||
back on restores them. This id is auto-save — the wire send happens on
|
||
the click itself, not on Apply.
|
||
5. **Toggle "Automatically Repeat Attacks" or "Accept Corpse Looting
|
||
Permissions"** (both auto-save). These previously flipped ONLY a
|
||
client-local flag and never reached the wire (an unfiled divergence
|
||
this slice closes) — verify the *behavior* actually changes in
|
||
combat/looting, not just the checkbox art.
|
||
6. **Relog the same character** after toggling a couple of auto-save
|
||
rows. Reopen the panel — the rows should still read whatever you left
|
||
them at (server echo), confirming the send actually reached ACE and
|
||
persisted, not just the local bit.
|
||
|
||
### Batched rows — the `0x01A1` blob via Apply
|
||
|
||
7. **Toggle a BATCHED row** (e.g. "Side By Side Vitals" or "Display Date
|
||
of Birth" — anything NOT in the auto-save set) and click **Apply**.
|
||
Relog and reopen the panel: the row should still read your new value
|
||
(Apply flushed the `0x01A1` blob, ACE persisted it, the fresh
|
||
`PlayerDescription` echoes it back).
|
||
8. **Close the Options panel (F11) FIRST, then toggle a batched row
|
||
WITHOUT clicking Apply**, then relog. The row should revert to its
|
||
PRE-toggle value on reopen — an un-flushed batched change never
|
||
reached the wire (the 480 s auto-save timer is a Runtime-level
|
||
mechanism tested at OP1; do not wait 8 minutes for this gate — just
|
||
don't click Apply). **Closing the panel first is load-bearing, not
|
||
optional**: logout flushes the dirty blob (`CPlayerSystem::
|
||
LogOffCharacter` calls `SaveToServer`), so "relog without Apply
|
||
reverts" only holds because reaching Exit Game requires switching to
|
||
the Gameplay tab first — retail's own `OnVisibilityChanged(false) ->
|
||
RestoreSavedValues` — which reverts the uncommitted edit BEFORE
|
||
logout's flush ever sees it. A user who force-quits the client (or any
|
||
route that skips hiding the Character page) will see the OPPOSITE:
|
||
the un-Applied toggle DOES persist, because logout still flushes
|
||
whatever is dirty at that moment. Both are retail-correct; this step
|
||
exercises the panel-close path specifically.
|
||
|
||
### Apply / Reset / Defaults semantics
|
||
|
||
9. **Toggle several rows (mix of auto-save and batched), then click
|
||
Reset.** Every row you touched reverts to its pre-edit value; rows you
|
||
didn't touch are unaffected. Auto-save rows you toggled will have
|
||
ALREADY sent their change on the click — Reset sends the REVERTED
|
||
value as a fresh change (a second `0x0005`), which is retail-correct
|
||
(Reset re-applies live, it does not "undo" a wire message that already
|
||
happened).
|
||
10. **Click Defaults.** Every row jumps to its retail default value
|
||
LIVE — auto-save rows whose default differs from current send
|
||
immediately; batched rows just dirty the module. Apply/Reset stay (or
|
||
become) ENABLED afterward if anything actually changed — Defaults
|
||
does NOT commit a new baseline by itself.
|
||
11. **After clicking Defaults, click Apply.** The defaulted batched rows'
|
||
values now flush via `0x01A1`; the new state becomes the baseline
|
||
(Reset can no longer undo past this point).
|
||
12. **Switch to another tab (e.g. Gameplay) without clicking Apply after
|
||
an edit.** Uncommitted edits silently revert — switching tabs is a
|
||
Reset, not a save. Switch back to Character and confirm the
|
||
reverted state.
|
||
|
||
### Group B consumers — presentation bindings
|
||
|
||
13. **Toggle "Display Timestamps" on, then send/receive a new chat
|
||
line** (e.g. `/who` or any inbound tell/say). The NEW line should be
|
||
prefixed with a timestamp like `14:32:07 ` (no leading zero on the
|
||
hour). Lines already in the transcript before you toggled the option
|
||
do NOT retroactively gain a timestamp. Toggle it back off — new lines
|
||
stop getting the prefix.
|
||
14. **Toggle "Disable Distance Fog" on** while outdoors somewhere with
|
||
visible atmospheric fog at a distance (dusk/dawn or an overcast
|
||
keyframe shows this most clearly). Distant terrain/objects should
|
||
stop fading into the fog color — toggle back off and the fog returns.
|
||
15. **Toggle "Run as Default Movement" off.** Press W (or your bound
|
||
forward-movement key) alone, with no modifier held: your character
|
||
should now WALK by default instead of run. Hold the walk-mode
|
||
modifier key while this option is off: it should now temporarily make
|
||
you RUN (the modifier always inverts whichever default is active).
|
||
Toggle the option back on and confirm W-alone runs again (today's
|
||
pre-OP4 behavior).
|
||
16. **"Display 3D Tooltips", "Side By Side Vitals", "Display Spell
|
||
Durations", "Advanced Combat Interface", "Stay in Chat Mode",
|
||
"Disable Most Weather Effects", "Always Daylight Outdoors", "Filter
|
||
Language", "Use Main Pack as Default for Picking Up Items" — verify
|
||
these are TOGGLEABLE and PERSIST (store+wire only, per the plan's own
|
||
contract), but do NOT expect any observable client behavior change
|
||
from them yet.** acdream has no existing consumer surface for these
|
||
(no side-by-side vitals layout, no 3D tooltip rendering, no discrete
|
||
weather-particle system to gate, no client-side pickup-container
|
||
preference, no profanity filter) — this is an honest, registered gap,
|
||
not a bug to report. "Always Daylight Outdoors" specifically does NOT
|
||
force daytime lighting yet; do not confuse it with the unrelated
|
||
weather-VARIETY forcing mechanism already in the codebase.
|
||
|
||
### Group C — re-pointed to server truth
|
||
|
||
17. **Toggle "Auto Target", "Automatically Repeat Attacks", "Keep Combat
|
||
Targets in View", "Vivid Targeting Indicator", "Show Coordinates By
|
||
the Radar", or "Accept Corpse Looting Permissions" and confirm the
|
||
EXISTING behavior for each (auto-targeting in combat, the combat
|
||
camera tracking your target, the vivid target overlay, radar
|
||
coordinate labels, corpse-loot consent) still works exactly as
|
||
before — these now read the SERVER bit instead of the local
|
||
`settings.json` copy, so the observable behavior should be unchanged
|
||
UNLESS your character's server-side value differs from whatever
|
||
`settings.json` had (in which case the NEW, server-driven behavior is
|
||
correct and the old local-only behavior was the bug).
|
||
18. **Relog and confirm all six Group-C options above read their
|
||
server-persisted value**, not a locally-cached default.
|
||
|
||
### Apply/Reset enable-gating (MUST-FIX 2, OP4 review-fix round)
|
||
|
||
19. **On first open of the Character tab, Apply and Reset are greyed out**
|
||
(disabled — retail's `PostInit` runs `OnOptionChanged(0)` so the pair
|
||
starts disabled). **Click any LED and they light up** (enabled).
|
||
**Click Apply and they grey out again.** **Defaults is never greyed
|
||
out** — before a change, immediately after clicking it, or after
|
||
Apply — at any point in this sequence.
|
||
|
||
### Combat panel — the SAME LEDs, a second surface (MUST-FIX 3 / blast M2)
|
||
|
||
20. **Open the Combat window (its own toolbar button, not the Options
|
||
panel) and confirm its own Repeat Attacks / Auto Target / Keep in
|
||
View checkboxes match the Character tab's rows for the SAME three
|
||
options** (Automatically Repeat Attacks / Auto Target / Keep Combat
|
||
Targets in View) — both surfaces now read the identical server bit,
|
||
so toggling one and reopening/refreshing the other must show the
|
||
SAME state. Before this fix round the Combat panel read a separate,
|
||
disconnected client-local copy that could silently disagree with the
|
||
Character tab and never reached the wire for two of the three.
|
||
|
||
### What to report
|
||
|
||
- Any row with a missing label/tooltip (note which one).
|
||
- Any auto-save row whose behavior doesn't persist across a relog.
|
||
- Any batched row that persists WITHOUT clicking Apply, or reverts
|
||
DESPITE clicking Apply.
|
||
- Any Reset/Defaults button that stays permanently disabled, or Defaults
|
||
that becomes disabled (it must never be).
|
||
- Any crash, freeze, or scroll glitch anywhere in the 56-row list.
|
||
- Timestamps, fog toggling, or run-as-default NOT working as described in
|
||
13-15.
|
||
- Any Group-C option (17-18) behaving DIFFERENTLY than it did before this
|
||
slice, other than "now reads the correct server value instead of a
|
||
stale local one."
|
||
|
||
### Explicitly NOT in scope for this gate
|
||
|
||
- Chat tab / Config tab content — OP5/OP6.
|
||
- Configure Keyboard — OP8.
|
||
- Any observable change from Group A rows (privacy flags, fellowship
|
||
grouping, missile/charge-attack options, helm/cloak visibility, and
|
||
similar) beyond the checkbox itself toggling and persisting — retail's
|
||
OWN client has no local consumer for most of these either; the server
|
||
is the only place their effect is visible (and largely invisible to the
|
||
player triggering them).
|
||
- "Always Daylight Outdoors" and "Disable Most Weather Effects" actually
|
||
changing the sky/weather — no acdream consumer exists yet (see item 16
|
||
and this slice's register rows).
|
||
- "Use Main Pack as Default for Picking Up Items" actually changing
|
||
pickup routing — no client-side preferred-container consumer exists.
|
||
- "Filter Language" actually filtering profanity — no filter subsystem
|
||
exists.
|
||
|
||
---
|
||
|
||
## OP5 — the Chat tab
|
||
|
||
The Chat tab (`0x2100005C`) has six sections: **General Options** (the two
|
||
linked window-opacity sliders) and five per-window text-filter blocks — the
|
||
**Main Chat Window** (12 checkboxes, no "Gameplay" row) and four **Floaty
|
||
Chat Window 1-4** blocks (13 checkboxes each). All five blocks write
|
||
`AcDream.Core.Chat.ChatWindowState` directly — the SAME state CH6's floating
|
||
chat windows already read when deciding which lines to show.
|
||
|
||
### Opening the tab
|
||
|
||
1. **Open the Options panel (F11) and click the Chat tab.** Six group
|
||
headers appear top-to-bottom: **General Options**, **Main Chat Window**,
|
||
**Floaty Chat Window 1**, **Floaty Chat Window 2**, **Floaty Chat Window
|
||
3**, **Floaty Chat Window 4**. Under General Options, two horizontal
|
||
sliders; the second one has "Transparent"/"Opaque" range captions at its
|
||
ends. Under each window section, a block of checkboxes with labels
|
||
(Gameplay, Combat, Magic, Area Speech, Tells, Allegiance, Fellowship,
|
||
General, Trade, LFG, Roleplay, Society, Error) — the Main Chat Window
|
||
block is missing the **Gameplay** row (12 rows, not 13); every floaty
|
||
block has all 13.
|
||
2. **Scroll the list.** All six sections are reachable; the block heights
|
||
visibly differ from a flat 100px box — each block is exactly tall enough
|
||
to show all of its own rows with no dead space below the last row
|
||
(AP-195's self-sizing). **Gate-3 fix (#371, AP-201 retired): blocks now
|
||
CLIP at the viewport edges instead of vanishing whole.** At the default
|
||
scroll position you should see the Main Chat Window block in full, then
|
||
"Chat Window 1"'s header and as many of its checkboxes as fit, cut off
|
||
cleanly at the panel's bottom edge — scrolling reveals the rest
|
||
smoothly. A block that disappears ENTIRELY while its header stays (the
|
||
pre-fix void your gate-3 screenshot review caught) is a regression —
|
||
report it. **Gate-4 re-test note (#381):** see the Character-tab (OP4)
|
||
step 2 note — the SAME opaque-footer-backing fix applies to this tab's
|
||
own Apply/Reset/Defaults strip.
|
||
3. **Look closely at a row whose mask covers MULTIPLE underlying message
|
||
types** (Gameplay, Combat, Allegiance, or Fellowship — the composite-mask
|
||
rows per the research doc) versus a single-bit row (e.g. "Error" or
|
||
"General"). If your character's chat history has caused a PARTIAL match
|
||
on one of those composite rows (some but not all of its underlying types
|
||
currently enabled), the checkbox's own LED art should look visibly
|
||
different (dimmer/"ghosted") from a row that is FULLY on — this is
|
||
AP-195's LED media swap. On a fresh character every row is likely either
|
||
fully on or fully off, so this may not be observable without deliberately
|
||
creating a partial-match state via repeated single clicks inside a
|
||
composite row's underlying bits (not directly clickable per-bit through
|
||
this UI — a visual nice-to-have to note, not a blocking check).
|
||
|
||
### Opacity sliders — live drag, linked, never clamping
|
||
|
||
**Gate-4 re-test note (#380):** both sliders previously showed ONLY the
|
||
Transparent/Opaque endpoint labels on the second (Active) slider, with NO
|
||
row caption identifying which slider was which. Each slider row now shows
|
||
its own DAT-resolved caption — "Inactive Opacity" next to the first slider,
|
||
"Active Opacity" next to the second — report if either caption is missing
|
||
or shows the wrong text.
|
||
|
||
**Gate-4 re-test note (#379) — step 4 below is REWRITTEN:** the sliders
|
||
previously faded EVERY registered window (vitals, toolbar, inventory,
|
||
spellbook, radar, the Options panel itself, ...), not just chat windows —
|
||
matching retail's own bug-for-bug scope. They now affect ONLY the main
|
||
chat window and the four floating chat windows, exactly like retail's
|
||
`ChatInterface::SetDefaultOpacity`/`SetActiveOpacity` (which only
|
||
`gmMainChatUI`/`gmFloatyChatUI` ever call). **Use a chat window (main or
|
||
floating), not the toolbar/vitals/another panel, as your "other window" in
|
||
step 4** — a non-chat window's opacity should now stay FIXED (opaque)
|
||
regardless of the slider position.
|
||
|
||
4. **Drag the FIRST slider (Default Opacity) most of the way to the right**
|
||
while the Options panel itself is NOT focused/hovered by your mouse (so
|
||
you can see another CHAT window — the main chat window or a floating
|
||
chat window 1-4 — at its UNFOCUSED opacity; a NON-chat window like the
|
||
toolbar or vitals bar must stay fully opaque throughout this whole
|
||
section, per the #379 note above). Watch that other CHAT window's
|
||
transparency change LIVE, continuously, as you drag — not just on
|
||
release. This is retail's `SetCurrentValue -> Apply(1)` immediate-apply
|
||
semantic.
|
||
5. **Drag the first slider (Default) ABOVE the second slider's (Active)
|
||
current position.** Retail's link drags the SECOND slider's thumb UP to
|
||
match — watch it move on its own as you drag the first. Neither slider
|
||
should ever let Default end up above Active.
|
||
6. **Drag the second slider (Active) BELOW the first slider's (Default)
|
||
current position.** The first slider's thumb should get dragged DOWN to
|
||
match, symmetric to step 5.
|
||
7. **Focus a chat window (click into its text entry) and watch its opacity
|
||
change to the Active value; click away and watch it ease back toward
|
||
Default.** (The easing itself is AP-190's existing behavior, not new to
|
||
this slice — this step just confirms the sliders are actually driving
|
||
the SAME live opacity controller every other window already uses.)
|
||
|
||
### Filter checkboxes — live routing, per-window independence
|
||
|
||
**Gate-4 re-test note (#382):** the main chat window's own four indicator
|
||
buttons (the small LEDs that light up per floating-window 1-4 open/closed
|
||
state) previously rendered NOTHING at rest — only a mouse hover revealed
|
||
their orange numbered art. They now show their correct art immediately on
|
||
window open, with no hover needed; report if any of the four is still
|
||
blank before you've moved your mouse over it.
|
||
|
||
8. **Open a floating chat window (Alt+1 through Alt+4, or the toolbar
|
||
indicator buttons) and uncheck "Combat" in that SAME window's filter
|
||
block on the Chat tab** (e.g. Floaty Chat Window 1's block if you opened
|
||
window 1). Trigger a combat message (attack a monster, or have someone
|
||
attack you) — it should STOP appearing in that floating window while
|
||
still appearing in the MAIN chat window (whose own filter is untouched).
|
||
Re-check the box — combat messages resume in that window.
|
||
9. **Uncheck a filter row in the MAIN CHAT WINDOW's own block** (e.g.
|
||
"Trade") and generate a matching message (a `/trade` broadcast, or
|
||
whatever is easiest to trigger). It should stop appearing in the main
|
||
window specifically, while a floating window with Trade still checked
|
||
keeps showing it — proving the main window's filter is genuinely
|
||
consulted now (CH6a/b's own fixed bug: broadcast lines used to
|
||
short-circuit true for the main window regardless of its filter).
|
||
10. **Verify the five blocks are independent**: toggling a row in Floaty
|
||
Chat Window 2's block must not affect Floaty Chat Window 1, 3, 4, or the
|
||
Main window's own same-named row.
|
||
|
||
### Apply/Reset/Defaults
|
||
|
||
11. **Toggle a couple of filter checkboxes AND drag both opacity sliders,
|
||
then click Reset.** Every row you touched (checkboxes, both sliders)
|
||
reverts to its value from the last Apply/OnShown — filter writes revert
|
||
live (you should see the reverted checkbox state immediately, and the
|
||
corresponding message routing revert too).
|
||
12. **Click Defaults.** The two opacity sliders jump to their DAT-extracted
|
||
values (0.5 default / 1.0 active — the same values you'd see on a
|
||
NEVER-touched install) LIVE; every filter block reverts to its OWN
|
||
window's retail default (Main: 0xFBFFFFFF's rows — everything checked
|
||
except Error; each floaty: its own narrower default set, per the
|
||
research doc's table) — Apply/Reset light up afterward if anything
|
||
actually changed (Defaults never commits by itself).
|
||
13. **Switch to another tab (e.g. Gameplay) without clicking Apply after an
|
||
edit.** Uncommitted slider/checkbox edits silently revert — switching
|
||
tabs is a Reset, not a save. Switch back to Chat and confirm the
|
||
reverted state (both the slider positions AND the filter checkboxes).
|
||
|
||
### Persistence — local-only, survives relog AND relaunch
|
||
|
||
14. **Set a distinctive combination** (e.g. Default Opacity ~30%, Active
|
||
Opacity ~90%, Main window's "LFG" row OFF, Floaty 3's "Fellowship" row
|
||
OFF) and click Apply (or just leave the panel open — filter writes and
|
||
slider drags apply live and persist to `settings.json` on every change,
|
||
not just on Apply).
|
||
15. **Fully relaunch the client** (close the window, start a new process;
|
||
a simple relog is not required since this is LOCAL-only state, unlike
|
||
the Character tab's server-authoritative rows). Reopen the Options
|
||
panel's Chat tab — the two sliders should read back at your set values,
|
||
and the Main/Floaty-3 checkboxes should read back unchecked for the
|
||
rows you turned off. **This is expected to be LOCAL-only persistence**:
|
||
retail's own wire mechanism for this data (the `0x1000008C`
|
||
GameplayOptions blob) is explicitly out of scope for this campaign
|
||
(register row AP-187, broadened at this slice — the blob stays
|
||
unsent); acdream persists through `settings.json` instead, which is
|
||
why a relaunch (not a server round trip) is the correct way to prove
|
||
it survived.
|
||
|
||
### What to report
|
||
|
||
- Any section/row/label missing, or the Main window showing a Gameplay row
|
||
(or a floaty missing one).
|
||
- A slider that does not apply live while dragging, or that lets Default
|
||
end up above Active (or vice versa) after a drag.
|
||
- A filter checkbox whose toggle does not change live message routing in
|
||
the window it belongs to, or that leaks into a DIFFERENT window.
|
||
- Reset/Defaults not behaving as described in 11-12.
|
||
- Any block that is STILL missing rows, or leaves a large empty gap below
|
||
its last row, once fully scrolled into view (a genuine self-sizing
|
||
regression). A block that disappears ENTIRELY at any scroll position is
|
||
ALSO a regression now — the gate-3 fix (#371) made straddling blocks
|
||
clip at the viewport edge instead of vanishing whole (see step 2).
|
||
- Any setting that reverts to default after a full relaunch (a persistence
|
||
regression) — remember this is local-only, so a SERVER-side relog is not
|
||
the right test here (see item 15's note).
|
||
|
||
### Explicitly NOT in scope for this gate
|
||
|
||
- Config tab content — OP6.
|
||
- The retail `0x1000008C` wire format for this same data — explicitly
|
||
deferred (see the plan's "What is explicitly OUT of scope" section and
|
||
this slice's own register citation).
|
||
- The composite-mask LED "partial vs full" visual distinction (step 3) is a
|
||
nice-to-have confirmation, not a blocking pass/fail item — it requires a
|
||
specific partial-match state that a fresh character is unlikely to be in.
|
||
|
||
---
|
||
|
||
## OP6 — the Config tab
|
||
|
||
The Config tab (`0x21000029`) has six sections: **Sound Options** (5 rows —
|
||
a menu, three toggle+slider volume trios, one toggle), **Camera Options** (4
|
||
rows), **Graphics Options** (7 rows), **Rendering Quality Options** (6
|
||
rows), **Input Options** (3 rows), **UI Options** (2 rows) — 27 authored
|
||
rows total, byte-verified directly from `gmConfigUI::InitOptions
|
||
@0x0049E400` and `gmClient::InitUIPreferences @0x004035b0` (the retail
|
||
registration table that supplies every row's label/tooltip/range/enum-
|
||
choice — a stronger source than this campaign's own research docs, which
|
||
had left the slider-caption pairing explicitly UNVERIFIED). **CORRECTED at
|
||
the OP6 rework round (2026-08-11, review M1):** the "zero range captions"
|
||
reading was a Binary Ninja constant-folding artifact — the raw bytes at
|
||
each `SetSliderLabel` call site are reads of runtime-filled string-id
|
||
globals, not immediate zeros. **Retail DOES ship six low/high range
|
||
captions**, one per labelled slider: Camera Stiffness (Soft/Hard), Camera
|
||
Adjustment Speed (Slow/Fast), Field Of View (Narrow/Wide), Screen
|
||
Brightness (Dark/Bright), Graphics Performance (Speed/Detail), Degrade
|
||
Distance (Close/Far). Mouse Look Sensitivity is the one genuine exception —
|
||
retail's own `InitOptions` never calls `SetSliderLabel` for it, so it alone
|
||
should show no caption.
|
||
|
||
### Opening the tab and reading the rows
|
||
|
||
1. **Open the Options panel (F11) and click the Config tab.** Six section
|
||
headers appear top-to-bottom: **Sound Options**, **Camera Options**,
|
||
**Graphics Options**, **Rendering Quality Options**, **Input Options**,
|
||
**UI Options**. Every row has a label (a menu dropdown, a checkbox, or a
|
||
slider — three of the Sound rows are a combined checkbox+slider in one
|
||
row). If ANY row shows no label at all, that is a DAT string-resolution
|
||
miss worth reporting (the code deliberately renders nothing rather than
|
||
invented English when a string fails to resolve).
|
||
2. **Scroll the list** via the scrollbar. All 27 rows are reachable; the
|
||
list does not clip or overlap the Apply/Reset/Defaults buttons at the
|
||
bottom. **Gate-4 re-test note (#381):** see the Character-tab (OP4)
|
||
step 2 note above — the SAME opaque-footer-backing fix applies here.
|
||
|
||
### Live rows — audio
|
||
|
||
3. **Drag the "Sound Effects" slider (first Sound Options trio) while a
|
||
sound effect is audibly looping or repeating** (e.g. stand near an
|
||
ambient sound source, or trigger a combat/UI sound repeatedly). The
|
||
volume should change LIVE, continuously, as you drag — not just on
|
||
release.
|
||
4. **CORRECTED at the OP6 rework round (2026-08-11, review M2) — the
|
||
checkbox's stored value is ENABLED-sense, not "Disabled" as the
|
||
rejected slice's own gate script previously described.** With the
|
||
first Sound-trio checkbox CHECKED (its default state — see step
|
||
6), sound effects are AUDIBLE. **Uncheck it** — sound effects go SILENT
|
||
immediately, regardless of where the slider is set. Check it again —
|
||
sound effects resume at the slider's current level. **Also report the
|
||
checkbox's DAT-resolved caption VERBATIM** (OP6 re-review SF-2: the
|
||
authored `ID_Sound_DisableSound` string was never read during research,
|
||
so this script does not know retail's exact English — if the caption
|
||
reads as a "Disable ..." phrasing while CHECKED means audible, that
|
||
label-vs-behavior tension is retail's own, byte-verified; report the
|
||
text so the script can record it, and gate on the BEHAVIOUR).
|
||
5. **Repeat step 4 for the "Ambient Sound" trio** (the second row) using
|
||
an ambient loop (wind, water, torches) as your audible test — same live
|
||
drag + mute-on-uncheck behaviour.
|
||
6. **Note the retail-faithful default:** on a FRESH character (never
|
||
touched these settings), BOTH "Disable Sound Effects" and "Disable
|
||
Ambient Sound" checkboxes are CHECKED by default
|
||
(`gmConfigUI::InitOptions`'s `SetDefaultValue(1, ...)` on both trios) —
|
||
and CHECKED means AUDIBLE, matching retail's own byte-verified
|
||
`SoundManager::effect_sounds_enabled`/`ambient_sounds_enabled` statics
|
||
(both compiled `= 1`). **Sound starts ON out of the box, not muted.**
|
||
If you hear silence on a fresh character, that IS a defect — report it.
|
||
7. **Drag the third slider / check its toggle ("Disable Interface Sound",
|
||
third trio).** Expect NO audible change either way — this is retail's
|
||
OWN dead knob (register row AP-174/AP-199): retail registers this
|
||
preference and then never reads it; UI/interface sounds are always
|
||
scaled by the Sound Effects slider instead. Confirm the row is
|
||
clickable/draggable and does not crash; do not expect it to do anything.
|
||
|
||
### Live rows — display
|
||
|
||
8. **Open the Resolution menu (Graphics Options, first row) and pick a
|
||
different resolution — IN WINDOWED MODE.** The window should resize
|
||
IMMEDIATELY, live, no restart needed. **Gate-2 re-test note (#374):**
|
||
the first gate's failure here was a client-wide dropdown routing bug —
|
||
clicks on an open popup's items landed on the rows UNDERNEATH it (your
|
||
session's Full Screen and VSync flips were those stolen clicks). Fixed;
|
||
dropdown items now win while a popup is open, and a click outside an
|
||
open popup dismisses it without acting on what's below. While
|
||
FULLSCREEN a resolution pick cannot switch the display mode yet
|
||
(Silk API limit, #376) — it applies on the next return to windowed;
|
||
do not report that as a step-8 failure. **Gate-4 re-test note (#378):**
|
||
the SAME row (and every other Config-tab dropdown — Sound Features, the
|
||
four texture-detail menus, the two Chat Font menus) previously rendered
|
||
as bare text with no button well/arrow and no visible popup at all
|
||
(#374's fix only corrected click ROUTING, not the missing chrome). Every
|
||
dropdown now shows the sunken value-well face + green arrow cap and
|
||
opens a real bordered, scrollable popup on click — report if ANY
|
||
Config-tab dropdown still renders bare text.
|
||
9. **Toggle "Full Screen".** The window should switch between windowed and
|
||
fullscreen IMMEDIATELY, live.
|
||
10. **Toggle "Sync To Refresh" (VSync) and drag the "Field of View"
|
||
slider.** Both persist to `settings.json`, but — matching this
|
||
project's pre-existing (not new) behaviour for these two fields —
|
||
neither re-applies until the NEXT LAUNCH. Do not expect an immediate
|
||
visual change; do a full relaunch afterward (step 15) to confirm the
|
||
new value took effect at startup.
|
||
|
||
### Store-only rows — no observable effect is the CONTRACTED behaviour
|
||
|
||
11. **Sound Features menu (Stereo/Mono), Play Sound Only When Active
|
||
toggle.** No consumer exists (register row AP-199). Confirm they're
|
||
clickable and persist (step 14); expect no audible/behavioural change.
|
||
12. **Screen Brightness slider, Automatic Degrades toggle, Graphics
|
||
Performance slider, Degrade Distance slider, the four Rendering
|
||
Quality menus (Landscape/Environment Texture Detail, Texture
|
||
Filtering, Landscape Draw Distance), Building Detail Textures,
|
||
Multi-Pass Alpha.** No consumer exists — acdream's Vulkan renderer is
|
||
driven by one aggregate quality preset, not these per-feature knobs
|
||
(register row AP-198). Confirm every one is clickable/draggable and
|
||
persists; expect zero visual change from any of them. **Landscape
|
||
Draw Distance specifically may show NO highlighted item** even right
|
||
after Defaults — that is retail's own decompiled data being
|
||
ambiguous (AP-198's own sub-note), not a rendering bug.
|
||
13. **Mouse Look Sensitivity slider, Invert Mouselook Y Axis toggle, Use
|
||
Mouse Turning toggle (Input Options), Camera Stiffness/Adjustment
|
||
Speed sliders, Align To Slope toggle (Camera Options), Chat Font
|
||
Face/Size menus (UI Options).** No consumer exists for any of these
|
||
six Camera/Input rows (register row TS-74 — the SAME "no persistent
|
||
mouse-turning camera mode" gap the Gameplay tab's macro already
|
||
exposed) or the two Chat font rows (register row AP-200 — distinct
|
||
fields from the chat panel's own separately-live font-size control,
|
||
wherever that is currently exposed). Confirm all eight are
|
||
clickable/draggable and persist; expect no visual/audible change.
|
||
|
||
### Apply/Reset/Defaults
|
||
|
||
14. **Toggle a mix of row types (a checkbox, a slider, a menu selection),
|
||
then click Reset.** Every row you touched reverts to its
|
||
last-shown/Applied value — live audio/display rows revert their
|
||
ACTUAL effect too (sound un-mutes or re-mutes, resolution changes
|
||
back), not just the checkbox art.
|
||
15. **Click Defaults.** Every row jumps LIVE to retail's byte-verified
|
||
default (Sound/Ambient trios back to AUDIBLE+100% volume — CORRECTED
|
||
at the OP6 rework round, review M2; the checked-by-default checkbox
|
||
means enabled, not muted — Resolution to 800×600, now a highlighted
|
||
dropdown entry since it is one of `DisplaySettings.AvailableResolutions`
|
||
(review S4), Full Screen on, Camera Stiffness to 0.45, etc.) —
|
||
Apply/Reset light up afterward if anything actually changed (Defaults
|
||
never commits by itself).
|
||
16. **Switch to another tab (e.g. Gameplay) without clicking Apply after
|
||
an edit.** Uncommitted edits silently revert — switching tabs is a
|
||
Reset, not a save.
|
||
|
||
### Persistence — local-only, survives relaunch
|
||
|
||
17. **Set a distinctive combination** (e.g. unmute Sound Effects at 40%
|
||
volume, pick a non-default Resolution, drag Field of View partway) —
|
||
every row here writes to `settings.json` on change, not just on
|
||
Apply.
|
||
18. **Fully relaunch the client** (close the window, start a new
|
||
process — a relog is not required, this is all local-only state).
|
||
Reopen the Options panel's Config tab — every row (live AND
|
||
store-only) should read back at your set values. The Sync To
|
||
Refresh/Field Of View rows deferred to next-launch (step 10) should
|
||
now ALSO be visibly applied (window vsync behaviour / camera FOV).
|
||
|
||
### What to report
|
||
|
||
- Any section/row/label missing, OR (CORRECTED at the OP6 rework round,
|
||
review M1) any of the SIX labelled sliders (Camera Stiffness/Adjustment
|
||
Speed, Field Of View, Screen Brightness, Graphics Performance, Degrade
|
||
Distance) MISSING its low/high range caption — retail ships one on each
|
||
of those six (see this section's own intro). Mouse Look Sensitivity is
|
||
the one slider that should show none.
|
||
- A live row (Sound/Ambient volume+mute, Resolution, Full Screen) that
|
||
does not change live, or a store-only row that unexpectedly DOES change
|
||
something (a sign its "no consumer" premise is stale).
|
||
- Sound effects or ambient audio SILENT on a freshly-created character
|
||
before you touch any Config-tab control (see step 6) — that is the M2
|
||
regression, not expected behaviour.
|
||
- Reset/Defaults not behaving as described in 14-15.
|
||
- Any row that fails to persist across a full relaunch (step 18),
|
||
including the two next-launch-only rows (Sync To Refresh, Field of
|
||
View) not taking effect at the NEXT startup.
|
||
- Any crash, freeze, or scroll glitch anywhere in the 27-row list.
|
||
|
||
### Explicitly NOT in scope for this gate
|
||
|
||
- Any audible/visual change from the store-only rows listed in steps
|
||
11-13 — no acdream consumer exists for any of them (register rows
|
||
AP-198, AP-199, AP-200, TS-74).
|
||
- The camera actually turning from "Use Mouse Turning" (Config tab OR the
|
||
Gameplay tab's macro) — no persistent mouse-turning camera mode exists
|
||
(TS-74, already covered by OP3's gate).
|
||
- Configure Keyboard — OP8.
|
||
|
||
---
|
||
|
||
## OP7 — headless `characterOptions`
|
||
|
||
Unlike OP3-OP6, this is not a graphical-client gate: no window is launched.
|
||
The coordinator runs `acdream-headless run` against local ACE with a config
|
||
declaring a small `characterOptions` block, and inspects the ACE-side
|
||
persisted `CharacterOptions1`/`CharacterOptions2` (and, for the
|
||
`ListenTo*Chat` ids, the actual Turbine room membership) before and after.
|
||
Automated coverage (schema rejection, the diff-and-send engine, the wiring
|
||
from a real `PlayerDescription` game event through to a captured wire
|
||
action, and dedicated-update-thread affinity) already runs in
|
||
`tests/AcDream.Headless.Tests/HeadlessConfigurationLoaderTests.cs`,
|
||
`HeadlessCharacterOptionsSeederTests.cs`, and
|
||
`HeadlessCharacterOptionsSeederWiringTests.cs` — this script is for the ONE
|
||
thing those tests cannot prove: that a REAL ACE server actually accepts and
|
||
persists the sends.
|
||
|
||
### Recipe
|
||
|
||
1. **Confirm the character's starting state.** Before running the bot,
|
||
note (or reset) the target character's `IgnoreAllegianceRequests`
|
||
(0x01, auto-save, `SetSingleCharacterOption 0x0005`) and
|
||
`ListenToTradeChat` (0x24, auto-save, also `0x0005` — additionally
|
||
joins/leaves the Turbine trade room) options — e.g. via an existing
|
||
graphical login, or by inspecting ACE's stored `CharacterOptions1`/
|
||
`CharacterOptions2` for the character row directly. Pick a THIRD,
|
||
batched (non-auto-save) id for the blob path — `SalvageMultiple`
|
||
(0x22, tier 2) is a safe choice: it has no observable server-side
|
||
effect beyond the stored bit, so a mismatch is purely a persistence
|
||
question, not a behavior one.
|
||
2. **Author a headless config** with all three declared at values that
|
||
DIFFER from the character's current stored state, e.g.:
|
||
```json
|
||
{
|
||
"version": 1,
|
||
"sessions": [{
|
||
"id": "op7-bot",
|
||
"endpoint": { "host": "127.0.0.1", "port": 9000 },
|
||
"account": "testaccount",
|
||
"character": { "name": "+Acdream" },
|
||
"policy": { "id": "idle" },
|
||
"credential": { "provider": "environment", "reference": "OP7_BOT_PASSWORD" },
|
||
"characterOptions": {
|
||
"IgnoreAllegianceRequests": true,
|
||
"ListenToTradeChat": true,
|
||
"SalvageMultiple": true
|
||
}
|
||
}]
|
||
}
|
||
```
|
||
(Flip each `true` to `false` instead if the character already has that
|
||
bit set — the point is a genuine diff in both directions, not
|
||
specifically "everything ON".)
|
||
3. **Run it**: `acdream-headless run --config op7-bot.json` with
|
||
`OP7_BOT_PASSWORD` set in the environment. Let it sit in-world a few
|
||
seconds, then stop it (Ctrl+C — the process scheduler's graceful-
|
||
shutdown path already sends a real logoff).
|
||
|
||
### Expected wire sends on first connect
|
||
|
||
- One `SetSingleCharacterOption (0x0005)` for `IgnoreAllegianceRequests`
|
||
(id `0x01`) — immediately, no batching.
|
||
- One `SetSingleCharacterOption (0x0005)` for `ListenToTradeChat` (id
|
||
`0x24`) — immediately, and ACE's handler additionally joins the
|
||
Turbine trade-chat room server-side for this session (observable via
|
||
ACE's own Turbine-chat membership logging if available).
|
||
- **No** `SetSingleCharacterOption (0x0005)` for `SalvageMultiple` (id
|
||
`0x22`) — it is batched, not auto-save, so `TrySetOption` only marks
|
||
the module dirty and never sends `0x0005` for it. The single
|
||
`SetCharacterOptions (0x01A1)` blob flush is the ONLY wire evidence of
|
||
this id changing (OP7's diff-and-send calls the explicit `SaveOptions`
|
||
verb once, after every declared id has been diffed, never interleaved
|
||
— so this same `0x01A1` also carries whatever the loop already wrote
|
||
locally for `IgnoreAllegianceRequests`/`ListenToTradeChat`, even though
|
||
those two ALSO got their own immediate `0x0005`).
|
||
- **No other option bits change.** ACE's stored `CharacterOptions1`/
|
||
`CharacterOptions2` for every UNDECLARED id stay exactly what they
|
||
were before the run.
|
||
- **No 0x01A1 blob before the client's own `GameActionLoginComplete`.**
|
||
If ACE logs a refusal ("SetCharacterOptions received before
|
||
FirstEnterWorldDone" or equivalent), that is a real OP7 defect — the
|
||
diff-and-send is contracted to run only after both LoginComplete has
|
||
been sent AND a real `PlayerDescription` has seeded local state
|
||
(`HeadlessCharacterOptionsSeeder`'s own two-precondition latch).
|
||
|
||
### Expected silence on reconnect
|
||
|
||
4. **Run the SAME config a second time** (a fresh process, or the
|
||
process's own reconnect path if it fires) without changing anything
|
||
on the ACE side in between. Expect **zero** `0x0005`/`0x01A1` sends
|
||
for the three declared ids — the fresh `PlayerDescription` now echoes
|
||
back exactly what the first run persisted, so the declared-vs-actual
|
||
diff finds nothing (idempotent by construction: a bot that already
|
||
has what it wants sends nothing, matching retail's own "re-set to the
|
||
current value produces nothing" rule — set-character-options-wire.md
|
||
§3.1/§3.5).
|
||
5. **Flip one declared value** in the config (e.g. `SalvageMultiple` back
|
||
to `false`) and run again. `SalvageMultiple` is batched, so expect
|
||
exactly ONE `0x01A1` blob flush and **no** `0x0005` for it — the other
|
||
two declared ids, still matching, produce nothing at all (not even the
|
||
blob).
|
||
|
||
### What to report
|
||
|
||
- Any declared id that does NOT persist across a fresh reconnect (ACE
|
||
either rejected it silently, or the diff-and-send never actually ran).
|
||
- Any UNDECLARED id whose stored value changed — a sign the blob echoed
|
||
something it shouldn't have (wire research §5.3's "echo real values,
|
||
never zero them" rule, or a stale/incorrect snapshot read).
|
||
- Any 0x0005/0x01A1 send observed BEFORE the client's own LoginComplete
|
||
action on the wire (a WireMCP capture on loopback `127.0.0.1:9000`
|
||
settles this precisely if ACE's own log line is ambiguous).
|
||
- Whether `ListenToTradeChat` actually joined the Turbine trade room
|
||
server-side (not just the stored bit) — the one declared id in the
|
||
sample config with a real behavioral consumer.
|
||
|
||
### Explicitly NOT in scope for this gate
|
||
|
||
- Every other tier-1/tier-2 option name — the three above exercise both
|
||
wire paths (auto-save `0x0005`-only and batched `0x0005`+`0x01A1`);
|
||
the remaining 23 names share the same two code paths and are already
|
||
covered by the id-ascending completeness assertions in
|
||
`CharacterOptionTableTests` (OP1) and the schema tests here (OP7).
|
||
- Any presentation-only (tier-3) option — `HeadlessConfigurationLoader`
|
||
refuses to load a config that declares one; there is nothing to run.
|
||
- The graphical Options panel's own Character tab — OP4.
|
||
|
||
### OP7 gate result — PASSED (coordinator, 2026-08-11)
|
||
|
||
Three live runs against local ACE (`127.0.0.1:9000`, `+Acdream`,
|
||
`lifecycle-smoke` policy, temporary seeder diagnostics since reverted):
|
||
|
||
- **Run 1 (declared: IgnoreAllegianceRequests=true, ListenToTradeChat=true,
|
||
SalvageMultiple=true):** the seeder sent exactly two `SetSingleOption`
|
||
diffs (`IgnoreAllegianceRequests` false→true auto-save;
|
||
`SalvageMultiple` false→true batched) plus ONE `SaveOptions (0x01A1)`
|
||
flush; `ListenToTradeChat` was already true and sent nothing. After the
|
||
policy's own portal AND a genuine mid-run reconnect (fresh ACE
|
||
`PlayerDescription`), all three reported `already ..., no send` —
|
||
proving ACE accepted and persisted both wire paths within the session.
|
||
- **Run 3 (fresh process, SalvageMultiple flipped to false):** the fresh
|
||
seed echoed `SalvageMultiple=true` from run 1 — a value ONLY the run-1
|
||
`0x01A1` blob could have carried (batched id, never `0x0005`), which is
|
||
the cross-process ACE-persistence proof. The flip sent one
|
||
`SetSingleOption` + one `SaveOptions` and nothing for the other two
|
||
ids; post-portal and post-reconnect re-diffs were silent.
|
||
- Both runs: no pre-LoginComplete sends observed, no ACE refusals,
|
||
graceful zero-code exits, `disposed` with converged runtime and zero
|
||
leases. Pre-existing noise unrelated to OP7, noted for the record: an
|
||
unmapped `[weenie-error] code=0x051D`, and the K4 resource envelope's
|
||
`handle-count` violation (the `k4-linux-30-session` Linux profile
|
||
evaluated on Windows).
|
||
|
||
---
|
||
|
||
## OP8 — Configure Keyboard
|
||
|
||
> **Gate-2 re-test note (#375):** the first look at this screen was a
|
||
> visual mess — textless buttons/tabs, stray buttons above the window,
|
||
> overlapping text. Two root causes, both fixed: the screen's build was
|
||
> missing its string resolver (every authored caption rendered empty),
|
||
> and the row-template prototypes (a header + a three-button row) were
|
||
> being built as live widgets parked at the screen's top-left. Expect
|
||
> now: captioned tabs (Movement/Camera/Combat/UI/CharacterSettings/
|
||
> Emotes), captioned buttons (Defaults/Revert/OK/Cancel, Load File.../
|
||
> Save As...), Command + Mapping 1-3 column headers, and NOTHING
|
||
> rendered above or outside the framed panel.
|
||
|
||
> **Re-gate note (2026-08-14 fix round — #394/#395/#396):** three findings
|
||
> from the first OP8 look at this screen, all fixed:
|
||
> 1. **Row-caption font (#394):** the action labels ("Move Forward", …)
|
||
> now draw in the row template's authored 18px serif (FontDid
|
||
> `0x4000000A`), not the debug bitmap font. The gothic section headers
|
||
> and the key-button captions were already using their authored fonts.
|
||
> 2. **Key caption text (#395):** key buttons now show retail's localized
|
||
> key names — DAT overrides first ("Left Ctrl"/"Left Alt" are the only
|
||
> authored ones), then YOUR keyboard layout's own name (a Swedish
|
||
> layout shows "SKIFT" for the Shift key, exactly like your retail
|
||
> screenshot), modifiers joined with "+" (chords like "SKIFT+M"), and a
|
||
> binding to a bare modifier key shows just the key name — never
|
||
> "Shift+ShiftLeft". Mouse chords keep enum spellings (AD-96).
|
||
> 3. **Capture-instruction dialog (#396):** clicking a mapping button now
|
||
> opens retail's own instruction dialog ("The next key you press or
|
||
> mouse button that you click will be mapped to the '<action>'
|
||
> action. … Press the ESC key to cancel.") — the wait-dialog shape from
|
||
> your retail screenshot, with the row's action name interpolated. It
|
||
> closes when you press the new key OR press ESC. Step 4 below is
|
||
> REWRITTEN accordingly; the old "a pressed/active state is enough"
|
||
> contract is retired.
|
||
|
||
**Known, tracked behaviors — do NOT file as defects (read before testing):**
|
||
|
||
- **Shared combat keys prompt a false conflict (ISSUES #373).** Retail
|
||
legitimately shares Insert/Delete/End/PageUp/PageDown across the three
|
||
combat contexts (Melee/Missile/Magic) via the DAT's `ConflictingMaps`
|
||
table, which acdream does not read yet. Rebinding any of those keys — or
|
||
any chord one combat context already holds — will show a conflict
|
||
confirmation retail would not. DECLINE the prompt and move on; the
|
||
binding you declined stays untouched. Report only if declining CHANGES a
|
||
binding anyway.
|
||
- **Camera Alternate Controls rows are display/store-only (AP-203).** They
|
||
show retail's authored arrow-key defaults and accept edits, but do not
|
||
drive the camera — only the primary Camera scheme is live. Verify they
|
||
RENDER (arrow keys visible); do not expect camera behavior from them.
|
||
|
||
|
||
The screen is retail's own separate full-screen window (`gmKeyboardUI`,
|
||
LayoutDesc `0x21000009`), NOT a fifth tab of the Options panel. It opens
|
||
from the Gameplay tab's Configure Keyboard button — **the OP3 INERT
|
||
contract for that button is retired as of this slice**; OP3's own script
|
||
line calling it INERT no longer applies.
|
||
|
||
### Opening the screen
|
||
|
||
1. **Open the Options panel (F11), Gameplay tab, click "Configure
|
||
Keyboard."** A separate full-screen window opens (not layered inside
|
||
the Options panel) showing six tabs across the top: Movement, Camera,
|
||
Combat, UI, CharacterSettings, Emote — Movement is the default/first
|
||
tab. Each tab lists grouped rows: a bold-ish header naming the
|
||
sub-category (e.g. "Movement Commands"), then one row per action —
|
||
an action label on the left, up to three key-binding buttons to its
|
||
right (retail's "Mapping 1/2/3" columns) showing the currently bound
|
||
key(s) (e.g. "W", "Up").
|
||
2. **Not every group has a visible header/rows.** Debug/dialog/system
|
||
InputMaps (DialogBoxes, DebugConsole, ProfilerUI, UIDebugger,
|
||
DebugCommands, and six further unnamed contexts) are 100% non-user-
|
||
bindable in the shipped DAT and correctly show NOTHING — this is not
|
||
a bug, it is retail's own shipped data (byte-verified against the
|
||
live DAT, see `RetailActionMap.cs`'s class doc).
|
||
3. **The bottom-row buttons:** Load File…, a current-keymap-name label,
|
||
Save As…, Defaults, Revert, OK, Cancel. Load File / Save As are
|
||
**INERT** (D4 — no `.keymap` file interchange, AP-202) — clicking
|
||
them does nothing; this is correct, contracted behavior.
|
||
|
||
### Rebind a movement key live
|
||
|
||
4. **On the Movement tab, find "Move Forward"** (should show two bound
|
||
keys — "W" and your layout's name for the up-arrow key). Click the
|
||
SECOND key button. **Retail's capture-instruction dialog opens**
|
||
(#396 re-gate): "The next key you press or mouse button that you
|
||
click will be mapped to the 'Move Forward' action. …Press the ESC
|
||
key to cancel." — naming THIS row's action. Press ESC once first:
|
||
the dialog closes and nothing changes. Click the button again to
|
||
re-open it for step 5.
|
||
5. **Press a different key**, e.g. `U`. The button should immediately
|
||
update to show "U".
|
||
6. **Move your character forward using W and U** (both should now work
|
||
— this is the live-effect proof: the rebind reached the SAME
|
||
`InputDispatcher`/`KeyBindings` every other input path uses, not a
|
||
screen-local shadow copy). The original "Up" arrow should no longer
|
||
move the character forward.
|
||
7. **Right-click the "U" key button** you just set. It should clear
|
||
back to blank/empty — right-click erases just that one slot
|
||
(`EraseBinding`), distinct from a hypothetical "Clear all" (there is
|
||
no Clear-all button on this screen — the shipped 2013 row template
|
||
authors none; a real, DAT-verified fact, not a limitation of this
|
||
port).
|
||
|
||
### Conflict on a taken chord
|
||
|
||
8. **Still on Movement, click "Move Backward"'s first key button**
|
||
(currently "X"), then press **W** — the SAME key you just confirmed
|
||
is bound to "Move Forward."
|
||
9. **Expect**: a confirmation dialog opens (retail's own
|
||
`OpenOverwriteBindingDialog`, ported through the same
|
||
`RetailDialogFactory` confirm mechanism the game's other Yes/No
|
||
prompts already use) naming "Move Forward" as the row that currently
|
||
holds the key and asking whether to reassign it to "Move Backward."
|
||
**Click Yes**: "Move Backward" takes W, and "Move Forward" loses its
|
||
W slot (down to just "U" from step 5); both rows' key-button labels
|
||
update to reflect the swap. Repeat steps 8-9 once more but **click
|
||
No** this time: neither row should change at all — the capture is
|
||
simply abandoned, exactly like Escape.
|
||
|
||
### Non-bindable refusal
|
||
|
||
10. **Click any key button, then press Ctrl+M** (acdream's own
|
||
debug-only mute toggle — retail has no equivalent, so no DAT row on
|
||
this screen owns it). **Expect**: the rebind is REFUSED — the key
|
||
button you clicked keeps its previous value, and a system message
|
||
appears reading "Could not overwrite " (retail's own byte-verified
|
||
`ID_KeyMapCantOverwriteReadOnlyKeymap_Label` string, table
|
||
`0x23000004` — it may read as an odd sentence fragment on its own;
|
||
that is the literal stored string, not a truncation bug in this
|
||
port). Confirm Ctrl+M still mutes/unmutes audio afterward — the
|
||
acdream-only binding was NOT touched.
|
||
|
||
### Erase, then Reset to Defaults
|
||
|
||
11. **Erase a couple more bindings** via right-click (any tab).
|
||
12. **Click Defaults.** Every row on EVERY tab should snap back to its
|
||
retail-default key(s) LIVE — including the ones you just erased,
|
||
the "Move Forward"/"Move Backward" pair from steps 5-9 (both back
|
||
to their original W/Up and X/Down), and the Ctrl+M row from step 10
|
||
is untouched (it has no DAT row, so Defaults cannot and does not
|
||
touch it). The OK/Cancel/Revert buttons should now read as
|
||
"changed" (retail never gates Defaults itself, but the page as a
|
||
whole is dirty after it runs) — confirm by tabbing away and back:
|
||
edits should still be there (Defaults applies live without
|
||
committing, exactly like a manual edit would).
|
||
13. **Click Revert** (not Cancel) with SOME rows still showing your
|
||
Defaults-restored values. Expect every changed row to revert to
|
||
whatever was bound when you last clicked OK (or, if you haven't
|
||
clicked OK yet this session, back to what was loaded from
|
||
`keybinds.json` at startup) — Revert and Cancel run the identical
|
||
verb (`RestoreSavedValues`); Revert just doesn't also close the
|
||
window.
|
||
|
||
### Persistence across relaunch
|
||
|
||
14. **Rebind one distinctive key** (e.g. change "Jump" from Space to
|
||
some other free key) and **click OK.** The window closes.
|
||
15. **Close acdream gracefully and relaunch it** (see CLAUDE.md's
|
||
logout-before-reconnect discipline — wait for the graceful-close
|
||
session-clear window before reconnecting). Open Configure Keyboard
|
||
again: the rebind from step 14 should still be there — proof it
|
||
persisted to `keybinds.json` on disk, not just the in-memory
|
||
dispatcher.
|
||
16. **Open a plain text editor on `%LOCALAPPDATA%\acdream\keybinds.json`**
|
||
(or the platform-portable equivalent) and confirm the rebound
|
||
action shows the new key. A sibling `keybinds-unmapped.json` should
|
||
also exist if you rebound anything on the CharacterSettings or
|
||
Emote tabs during this session (AP-203's store-only rows — e.g. try
|
||
rebinding one "Bow Deep"-style Emote row and confirm it shows up in
|
||
THAT file, not `keybinds.json`).
|
||
|
||
### Cancel discards uncommitted edits
|
||
|
||
17. **Rebind another key WITHOUT clicking OK**, then click **Cancel.**
|
||
The window closes; reopen it — the rebind from this step should be
|
||
GONE (reverted to the last-committed/loaded state), matching step
|
||
13's Revert behavior plus the window closing.
|
||
|
||
### What to report
|
||
|
||
- Any row that shows a DIFFERENT key than what `keybinds.json` /
|
||
`RetailDefaults()` says it should (a sign the DAT-vs-InputAction
|
||
identity table mis-mapped a row — see
|
||
`RetailActionIdentityRoundTripTests` for the automated half of this
|
||
check).
|
||
- Any tab/header that renders EMPTY where it should show rows, or vice
|
||
versa (the page-scoped element-lookup trap this slice's controller
|
||
explicitly guards against — a regression here would mean one page's
|
||
rows leaked into another, or the six reused element ids resolved to
|
||
the wrong page's instance).
|
||
- Whether the confirm dialog's wording (step 9) or the refusal
|
||
message's odd phrasing (step 10, the literal DAT string) reads
|
||
awkwardly enough in practice to warrant a follow-up polish pass.
|
||
- Any row whose Emote/CharacterSettings binding visibly DOES something
|
||
in-game despite AP-203 saying it shouldn't (would mean acdream grew a
|
||
consumer for it since this table was written, and the identity table
|
||
should be updated to route it live).
|