Campaign OP gate 2: the screen opened as a visual mess (textless buttons/tabs, buttons above the window, overlapping text) while the fixture conformance suite stayed green — the #372 class again. Two root causes, both proven by the new env-gated live-DAT probe before fixing: 1. MountKeyboardConfig's main LayoutImporter.Build was the ONE mount in RetailUiRuntime not passing strings.Resolve — every AUTHORED caption (OK/Cancel/Defaults/Revert/Load/Save, the six ActionClass tab labels, the Command/Mapping column headers) built empty, while the controller's own resolveString row captions worked, which is why the screen was recognizable but textless. Fixed by passing the resolver like every sibling mount. 2. gmKeyboardUI authors its ListBox row templates (header 0x1000002E, action row 0x1000002F with the three 100x32 key buttons) as TOP-LEVEL siblings referenced by dat property 0x64. Retail never instantiates template-list elements as live widgets (AddItemFromTemplateList clones from the desc — the same re-import UiTemplateListBox's TemplateResolver performs), but ImportInfos built them parked at the screen's (0,0): three key buttons at y=0..32 ABOVE the framed panel (top y=62) — the 'outside the window' buttons — under a 570x40 header text overlapping them and the top chrome. ImportInfos now skips top-level elements referenced by a SAME-LAYOUT template list (the same skip class as the existing BaseElement-prototype filter; same-layout only because element ids collide across layouts — 0x10000211 is a page in BOTH the options and keyboard layouts). The probe (ACDREAM_PROBE_LIVE_MOUNT=1) pins both against the real DATs: prototypes absent from the built tree, and Defaults/Revert/OK/Cancel resolving on the resolver-passing build. Post-fix the import collapses to the framed 600x476 panel with every screen button inside its bounds. Full Release suite: 13,082 passed / 4 skipped / 0 failed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1042 lines
57 KiB
Markdown
1042 lines
57 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.
|
||
|
||
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.
|
||
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 clipping and no dead space below the
|
||
last row (AP-195's self-sizing). **Known, registered behavior (AP-201,
|
||
OP5 review fix S2): the row viewport culls whole rows rather than
|
||
clipping them (no scissor stack yet — `UiScrollablePanel.cs`), so a
|
||
240-260px filter block that straddles the visible edge at a given scroll
|
||
position can vanish ENTIRELY at that exact offset rather than showing a
|
||
partially-clipped view. Keep scrolling a little further and the block
|
||
reappears whole. This is the registered cull-vs-clip divergence, not a
|
||
self-sizing regression — do not report a block's disappear-then-
|
||
reappear-whole behavior as a bug.**
|
||
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
|
||
|
||
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 registered window, e.g. the toolbar or a floating
|
||
chat window, at its UNFOCUSED opacity). Watch that OTHER 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
|
||
|
||
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). **Not a regression:** a block that disappears entirely at
|
||
some intermediate scroll position and reappears whole a little further —
|
||
that is the registered whole-row-cull behavior (AP-201, see step 2), not
|
||
a clipping bug.
|
||
- 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.
|
||
|
||
### 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.
|
||
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.
|
||
|
||
**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 "Up"). Click the SECOND key button (currently "Up").
|
||
The button should visually indicate it is listening for input
|
||
(retail's own capture-prompt text is not wired to a tooltip in this
|
||
port — a simple pressed/active state is enough to confirm capture
|
||
started).
|
||
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).
|