fix #374: open dropdown popups get first claim on pointer routing
Campaign OP gate 2 root cause: UiElement.HitTest walks siblings front-to-back by z-order, so an OPEN UiMenu's extended button+popup hit-test union was never consulted when a LATER sibling's rect overlapped the popup area — on the Config tab every dropdown has rows below it, so Resolution-item clicks toggled the Full Screen / VSync rows underneath (the gate session's persisted fullscreen/vsync flips were exactly those stolen clicks). Latent since UiMenu existed; vendor/chat menus only worked by z-order luck. Fix: UiMenu's open/close now registers with UiRoot (SetActivePopup / ClearActivePopup); a registered popup gets FIRST claim on mouse-down, scroll, and hover routing; a press outside a live popup dismisses it and is SWALLOWED (the dismissing click must not act on what sat underneath); hidden/detached owners self-heal the registration on the next pointer event. UiMenu gains the IsOpen seam and a single SetOpen writer. Also in this commit, from the same investigation: - SilkRuntimeDisplayWindowTarget.Apply documents the fullscreen half honestly: IViewProperties.VideoMode is READ-ONLY, so a resolution pick while fullscreen cannot switch the display mode through Silk's abstract API — split out as #376 (native glfwSetWindowMonitor port) rather than half-shipping untested native interop at a gate tail. - Gate script §OP6 step 8 re-scoped: test resolution in WINDOWED mode. Regressed by tests/AcDream.App.Tests/UI/UiMenuPopupRoutingTests.cs — 4 tests driving the real UiRoot input path on a mounted overlapping tree, with an in-test overlap CONTROL click so the popup assertions cannot pass vacuously (the #372 lesson: only mount+drive-input tests catch this class; every fixture-conformance test stayed green through this bug). Full Release suite: 13,081 passed / 4 skipped / 0 failed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
07c0c2c7b9
commit
355c86a6f6
6 changed files with 388 additions and 10 deletions
|
|
@ -24,6 +24,86 @@ What does NOT go here:
|
|||
- Every session: scan OPEN issues at start; promote/close anything we touched during the session before ending.
|
||||
- Promoting to a Phase: mark as `DONE (promoted to Phase X)` + commit SHA where the Phase entry landed.
|
||||
|
||||
## #376 — Fullscreen resolution picks cannot switch the display mode (Silk API limit; needs native glfwSetWindowMonitor)
|
||||
|
||||
**Status:** OPEN — filed 2026-08-11, split from #374's investigation.
|
||||
While FULLSCREEN, the visible resolution is the display's video mode, and
|
||||
Silk's abstract windowing API cannot change it:
|
||||
`IViewProperties.VideoMode` is read-only, and Silk fullscreen is
|
||||
desktop-mode borderless. `SilkRuntimeDisplayWindowTarget.Apply`
|
||||
(`src/AcDream.App/Settings/RuntimeSettingsTargets.cs`) therefore applies a
|
||||
Config-tab resolution pick to the WINDOWED size only — visible immediately
|
||||
in windowed mode, and on the next return to windowed when picked while
|
||||
fullscreen. Retail's own fullscreen switch is a real display-mode change
|
||||
(`Device::ForceDisplayResolution`, `gmClient::Init @0x004047af`). Fix
|
||||
shape: a native GLFW port — reach the underlying handle and call
|
||||
`glfwSetWindowMonitor(window, monitor, 0, 0, width, height, refresh)`
|
||||
through `Silk.NET.GLFW` when fullscreen, keeping the abstract path for
|
||||
windowed. Needs a physical gate (mode switches can black-screen on bad
|
||||
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.
|
||||
|
||||
**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.
|
||||
|
||||
**Blocks the OP8 connected gate.**
|
||||
|
||||
## #374 — Config tab: picking a new Resolution does not resize the window (live gate failure)
|
||||
|
||||
**Status:** ROOT-CAUSED + FIXED (this commit) — pending the user's
|
||||
re-gate. Filed 2026-08-11 at Campaign OP's second connected gate ("I was
|
||||
not able to change the screen resolution").
|
||||
|
||||
**ROOT CAUSE — popup hit-test priority, a UiRoot-level routing hole.**
|
||||
`UiElement.HitTest` walks siblings front-to-back by z-order; an OPEN
|
||||
`UiMenu` extends its hit area beyond its own rect (the button+popup union
|
||||
in `UiMenu.OnHitTest`), but any sibling added AFTER the menu whose rect
|
||||
overlaps the popup area wins the walk before the menu's extended
|
||||
hit-test is ever consulted. On the Config tab every dropdown has rows
|
||||
BELOW it — so clicking a Resolution popup item actually clicked the rows
|
||||
underneath (the session's persisted `fullscreen: true` + `vsync: false`
|
||||
flips were exactly such stolen clicks toggling the Full Screen / VSync
|
||||
rows under the open popup). Vendor's and chat's menus only ever worked
|
||||
because no overlapping sibling sat in front of them — the hole was
|
||||
latent since UiMenu existed. **Fix:** an open popup registers with
|
||||
`UiRoot` (`SetActivePopup`) and gets FIRST claim on mouse-down, scroll,
|
||||
and hover routing; a press outside the popup dismisses it and is
|
||||
SWALLOWED (the standard dropdown-dismiss gesture — the dismissing click
|
||||
must not act on whatever sat underneath); hidden/detached owners
|
||||
self-heal the registration. Regressed by
|
||||
`tests/AcDream.App.Tests/UI/UiMenuPopupRoutingTests.cs` (4 tests, with
|
||||
an in-test overlap CONTROL so the assertions cannot pass vacuously).
|
||||
|
||||
The investigation also surfaced the fullscreen half — a resolution pick
|
||||
while fullscreen cannot switch the display mode through Silk's abstract
|
||||
API at all — split out as #376. And the same session's log showed 64
|
||||
full `settings.json` disk writes from slider drags (one per drag tick);
|
||||
noted here as a minor perf observation, not yet its own issue.
|
||||
|
||||
**Re-gate (§OP6 step 8): test the Resolution row in WINDOWED mode** —
|
||||
the pick should now register and resize immediately; fullscreen mode
|
||||
switching stays #376.
|
||||
|
||||
## #373 — Configure Keyboard: DAT `ActionMap.ConflictingMaps` not consulted — the combat cluster raises false conflict prompts
|
||||
|
||||
**Status:** OPEN — filed 2026-08-11 at Campaign OP slice OP8's re-review
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue