fix #375: Configure Keyboard live mount — string resolver + parked template prototypes

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>
This commit is contained in:
Erik 2026-08-11 15:27:30 +02:00
parent 355c86a6f6
commit 8bd7e3b88d
5 changed files with 302 additions and 23 deletions

View file

@ -44,29 +44,50 @@ modes; validate against the monitor's mode list first).
## #375 — Configure Keyboard screen renders as a visual mess at the live mount (missing button/tab captions, buttons outside the window, overlapping text)
**Status:** OPEN — filed 2026-08-11 at Campaign OP's second connected gate
(user report, verbatim observations): "all buttons lacked descriptive
text", "some buttons were outside of the window", "lacked text in the
tabs", "text next to the buttons was overlapping. Looked like a mess."
The screen OPENED (the `[options] gameplay button 0x10000204 clicked`
log line fired and the user saw the window), so the OP8 mount/wiring is
alive — the defect family is presentation at the LIVE mount.
**Status:** ROOT-CAUSED + FIXED (this commit) — pending the user's
re-gate. Filed 2026-08-11 at Campaign OP's second connected gate (user
report, verbatim): "all buttons lacked descriptive text", "some buttons
were outside of the window", "lacked text in the tabs", "text next to
the buttons was overlapping. Looked like a mess."
**Same false-negative class as #372:** `KeyboardConfigControllerTests`
runs green against the committed `keyboard_config_21000009.json` fixture,
so the structural conformance suite cannot see whatever the live
DAT mount does differently (string resolution, template sizing,
anchor baselines, window extent). Diagnosis must start from live-mount
evidence, not the fixture: extend the `ACDREAM_PROBE_LIVE_MOUNT=1` probe
(`OptionsPanelLiveMountProbeTests` pattern) to mount `0x21000009` against
the real DATs and dump per-element rect + resolved caption, then compare
against the user's four observations. Candidate families (to CONFIRM, not
assume): caption lookups missing their real string table (the exact
`0x2300000D` class from #372's session), row-template text elements
sized/positioned from degenerate baselines, and the screen's authored
extent vs where children actually land.
**TWO root causes, both proven by the live-DAT probe**
(`tests/AcDream.App.Tests/UI/Layout/KeyboardConfigLiveMountProbeTests.cs`,
`ACDREAM_PROBE_LIVE_MOUNT=1` — the #372-class fixture suite was green
throughout, again):
**Blocks the OP8 connected gate.**
1. **The missing string resolver** ("buttons lacked text" + "no text in
tabs"): `RetailUiRuntime.MountKeyboardConfig`'s main
`LayoutImporter.Build` call was the ONE mount in that class not
passing `strings.Resolve` — every AUTHORED caption (OK / Cancel /
Defaults / Revert / Load File... / Save As..., the six ActionClass
tab labels, the Command / Mapping 1-3 column headers) built empty,
while the controller's own `resolveString` lookups (row captions)
worked, which is why the screen was recognizable but textless. The
probe builds the same layout both ways: resolver-less = every caption
`''`; with resolver = `Movement/Camera/Combat/UI/CharacterSettings/
Emotes`, `Command`, `Mapping 1/2/3`, `OK`, `Cancel`, ... Fix: pass
the resolver, like every sibling mount.
2. **Parked row-template prototypes** ("buttons outside the window" +
"overlapping text"): `gmKeyboardUI` (`0x21000009`) authors its
ListBox row templates — the header text `0x1000002E` and the action
row `0x1000002F` carrying the three 100x32 key buttons — as ordinary
TOP-LEVEL siblings of the screen, referenced by dat property `0x64`
(the template list). Retail never instantiates template-list elements
as live widgets (`AddItemFromTemplateList` clones from the desc — the
same re-import our `UiTemplateListBox.TemplateResolver` performs), but
`LayoutImporter.ImportInfos` built them as live elements parked at the
screen's (0,0): three key buttons at screen top y=0..32, ABOVE the
framed panel (which starts at y=62) — the "outside the window"
buttons — with the 570x40 header text overlapping them and the
window's top chrome. Fix: `ImportInfos(dats, layoutId)` 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).
Post-fix the import collapses to the framed 600x476 panel with every
screen button inside its bounds.
**Blocks the OP8 connected gate** — re-gate §OP8 after this commit.
## #374 — Config tab: picking a new Resolution does not resize the window (live gate failure)

View file

@ -869,6 +869,17 @@ Three live runs against local ACE (`127.0.0.1:9000`, `+Acdream`,
## 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