feat(ui): Campaign OP slice OP4 — the Character tab

Binds LayoutDesc 0x21000028 (gmCharacterSettingsUI) through OP2's
template-list mechanism and OP3's OptionPage model: 6 authored group
headers + 50 toggle rows (49 from the 2013 build + D3's "Listen to PK
death messages", AP-193) in research doc §2's authored order, each row
resolved by PlayerOption id through CharacterOptionTable, seeded from
live RuntimeCharacterOptionsState, defaulted from CharacterOptionTable.
ClientDefault (byte-verified against UIOption_Checkbox::SetPlayerOption
@0x00486e80's own GetDefaultOptionValue call — AP-194 updated to confirm
the directive was followed), labels/tooltips resolved by name from
string table 0x23000003 (never hard-coded English), and registered with
OptionsPanelController.CharacterPage. Apply/Reset/Defaults
(0x100001FC/FD/FE) are now wired per-page via a scoped subtree search
(UiElement.FindDescendant, promoted from UiTabPanel) since Character/
Chat/Config each author their own physical instance under the SAME
element ids.

Consumers: Group A (29 ids) wire+store only via the existing
SetSingleCharacterOptionRuntimeCmd/TrySetOption seam. Group B: Display
Timestamps prefixes new transcript lines (RuntimeCommunicationState.
DisplayTimestampsSource); Disable Distance Fog forces FogMode.Off
(WeatherSystem.DisableDistanceFogSource, retiring half of TS-73); Run as
Default Movement inverts the walk-mode modifier's default
(RuntimeLocalPlayerMovementState.RunAsDefaultMovementSource). Group C
re-points AutoTarget/AutoRepeatAttack/ViewCombatTarget
(CharacterOptionCombatSettingsSource), VividTargetingIndicator/
CoordinatesOnRadar/LockUI/AcceptLootPermits from the client-local
GameplaySettings record to the canonical server bit — closing two
previously-unfiled divergences where AutoRepeatAttack and
AcceptCorpseLootingPermissions never reached the wire despite being
retail auto-save ids. TS-73 narrowed to its two still-open cases;
TS-75..TS-80 file the genuine gaps (no day/night force, no weather-
particle/profanity-filter/salvage/housing/pickup-preference subsystem,
fellowship-create's unaudited client-sourced field) rather than
inventing stand-ins.

Conformance: CharacterOptionsPageControllerTests pins all 50 rows
against CharacterOptionTable in both directions (an invented or dropped
row fails the build), the authored group/order row-by-row, and the
build/seed/Apply/Reset/Defaults/wire-publish behavior end-to-end against
the committed fixture. 52 new tests; full solution suite 13,008 passed /
4 skipped / 0 failed (was 12,956/4/0).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-11 04:31:34 +02:00
parent 7e9f372ce8
commit 22b86b9ff4
25 changed files with 1674 additions and 41 deletions

File diff suppressed because one or more lines are too long

View file

@ -1,8 +1,8 @@
# Campaign OP connected-gate test script
**Status:** OP3 and OP7 sections. Later slices (OP4-6, OP8) append their own
sections here as they land; the campaign's OP9 closeout gate is this
document complete plus every slice code-complete.
**Status:** OP3, OP4, and OP7 sections. Later slices (OP5-6, OP8) append
their own sections here as they land; the campaign's OP9 closeout gate is
this document complete plus every slice code-complete.
This document is the script the user runs against the live connected
client (`ACDREAM_LIVE=1` against the local ACE server) to accept each
@ -166,6 +166,171 @@ for this slice, not a bug.
---
## 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. **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).
### 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.
### 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.
---
## OP7 — headless `characterOptions`
Unlike OP3-OP6, this is not a graphical-client gate: no window is launched.