feat(ui): Campaign OP slice OP5 — the Chat tab

Binds LayoutDesc 0x2100005C through OP2's template-list mechanism and
OP3's per-page OptionPage model: the General Options header + two
DualHash-linked opacity sliders (Option_DefaultOpacity_Property
0x10000080 / Option_ActiveOpacity_Property 0x10000081, live-apply on
drag through RetailWindowOpacityController, defaults read from the
installed DAT's DBProperties collection at DID 0x78000001 via
ChatOptionsDatDefaults), and the five per-window text-filter blocks
(main window 12 rows minus Gameplay, four floaties 13 rows each — the
byte-verified authored order cross-checked against the raw
gmChatOptionsUI::InitOptions/AddCheckboxBitfield64Option pseudo-C, not
just the research doc's own table) writing AcDream.Core.Chat.
ChatWindowState directly, the same state CH6's chat windows already
read.

AP-195 retired: ported both halves left open at the OP2 re-review —
the ALL-set LED media swap (new UiButton.FaceFileOverride, driven by
the block-level P0x10000082/P0x10000083 sprites now threaded through
ElementInfo/DatWidgetFactory) and the CreateChildren self-sizing tail
(UiCheckboxBitfield64.Height grows with its stacked row content; the
enclosing ListBox reflows around the block's FINAL height via the new
UiTemplateListBox.AddPrebuiltRow, reusing the ListBox's own stacking
rather than a third stacking path). AP-187 broadened to cover the main
window's own filter (previously only the four floaties) and the new
live-editing write path.

The main chat window's filter (retail window id 8, ChatWindowState id
0) gains its own settings.json persistence (ChatSettings.
ChatWindowMainFilter) alongside the pre-existing floaty 1-4 fields;
opacity persistence is now wired on every live slider change, not only
through the old dev-scaffold Settings panel.

Fixture regeneration (ACDREAM_REGENERATE_UI_FIXTURES=1) picked up the
new ElementInfo.LedCheckedSprite/LedUncheckedSprite fields across all
19 committed layout fixtures — purely additive, confirmed against the
live installed DAT (0x10000520's own 0x82/0x83 properties resolve to
0x06004D17/0x06004D19 exactly as AP-195 documented).

Conformance: FilterRows/FilterBlocks pinned against the byte-verified
authored order and ChatWindowState's own default constants; the AP-195
LED swap and self-sizing behavior; the DAT opacity-default extraction
against the live installed DAT; live filter/opacity writes reaching
ChatWindowState/RetailWindowOpacityController; OnShown re-seed and
Reset/Defaults ghosting per the OP4 binding-pattern discipline.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-11 06:25:59 +02:00
parent 4798302226
commit e71e5a9614
52 changed files with 4540 additions and 40 deletions

File diff suppressed because one or more lines are too long

View file

@ -1,6 +1,6 @@
# Campaign OP connected-gate test script
**Status:** OP3, OP4, and OP7 sections. Later slices (OP5-6, OP8) append
**Status:** OP3, OP4, OP5, and OP7 sections. Later slices (OP6, OP8) append
their own sections here as they land; the campaign's OP9 closeout gate is
this document complete plus every slice code-complete.
@ -363,6 +363,152 @@ TrySetOption` seam every other Options-panel consumer already uses.
---
## 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).
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 whose height looks clipped or has a large empty gap below its
last row (a self-sizing regression).
- 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.
---
## OP7 — headless `characterOptions`
Unlike OP3-OP6, this is not a graphical-client gate: no window is launched.