fix(ui): #409 live-failure round — tooltips read the RUNTIME text first
User gate on 1.0.3-tt.a: tooltips appeared NOWHERE in-world except one on
the paperdoll. Root-caused, fixed, and live-verified against a connected
client the same day. Two findings, both measured; neither is a broken
hover/hit-test.
1. DOMINANT ROOT CAUSE — RetailTooltipPresenter.OnTooltipShow gated on
widget.AuthoredTooltipText (P0x49) alone. Retail's
UIElement::StartTooltipAtMouse @0x00460D70 takes the RUNTIME m_TTText
first (@0x00460DA3 IsValid -> @0x00460DAA verbatim) and only falls back
to InqProperty(0x49) at @0x00460DDF. acdream ALREADY had the runtime
layer — UiElement.GetTooltipText(), written by the Options/Chat/Config
page controllers, KeyboardConfigController, the social pages and
UiCheckboxBitfield64 — but nothing read it.
Live-DAT measured: the Options toggle-row checkbox (0x2100002B template
root 0x10000218, leaf 0x10000219) authors P0x47=0x10000397
P0x48=0x21000041 P0x4B=true and an EMPTY P0x49 — the popup locator and
the on-bit are authored; only the text arrives at runtime, exactly as
UIOption_CheckboxBitfield64::CreateChildren @0x00485E65 stamps its
siTooltip array. Re-measured client-wide: ALL 187 no-literal-text
tooltip elements author both locator ids, i.e. the whole set is
runtime-text targets.
Fixed by ResolveTooltipText (retail's order), plus:
- the P0x4B gate now applies only to the AUTHORED-text path, because
retail's eight game-code SetTooltip sites set the on-bit themselves
(__bitfield164 |= 0x20 at @0x004E1D5E/@0x004A52F4/@0x004C63AC/
@0x004C67ED/@0x004C7000/@0x004C7218/@0x004D9617/@0x00467076);
- the P0x48-absent fallback to the element's own LayoutDesc
(@0x00460E7E, this->m_layout->m_DID) is ported via the new
UiElement.SourceLayoutDid, threaded from LayoutImporter.Build's new
sourceLayoutDid parameter and passed by Import + the four template
resolvers.
2. THE "243 SHOWABLE" NUMBER WAS NEVER AN IN-WORLD NUMBER. Grouped
re-sweep: all 243 sit in CHARACTER-CREATION layouts. The inventory
window (0x21000023) and paperdoll (0x21000024) author exactly two
between them — 0x100001D6 "Drag clothing and armor here to wear them"
(the doll drag mask) and 0x100005BE (the Slots button). The first IS
the user's single working tooltip, so the paperdoll was never a
differential against a broken mechanism. Reachability was measured and
is fine: 238/243 build as real non-ClickThrough hover targets.
LIVE VERIFICATION (connected testaccount/+Acdream, Release,
ACDREAM_RETAIL_UI=1): Options -> Character -> "Vivid Targeting Indicator"
now shows its full ID_PlayerOption_*_Help sentence; a temporary hover probe
confirmed the hover target is element 0x10000219 with runtime=True. The
paperdoll tooltip still shows. An inventory ITEM still shows nothing —
that is UIElement_UIItem::UpdateTooltip @0x004E1CB0 (retail shows the item
name, "%d %s"-prefixed when the stack is > 1), which stays deferred:
UiItemSlot is constructed programmatically at 6+ sites and carries neither
the P0x47 locator nor a name source, so it is its own slice.
Bookkeeping: register TS-85 narrowed (m_TTText READ side now ported; the
row now enumerates all 15 SetTooltip call sites split into ported vs
no-acdream-analog). #409's gate note rewritten to lead with the in-world
surfaces — the old note listed only chargen, which is why it could not
have caught this. Filed #411 for the hover-cursor scope addition: an
exhaustive raw scan of every ElementDesc found only 101 authored
MediaDescCursor entries, all on Dragbar/Resizebar with the 5 DIDs
RetailCursorCatalog already hardcodes, so retail has NO per-element cursor
for inventory items; the likely mechanism is the rollover STATE
(UIElement::MouseOverTop @0x004615D0) that UiItemSlot lacks entirely.
Gates: Release build 0 errors; App suite (live-DAT env) 5424/5421 passed/3
skips (was 5416/5413/3, +8 new tests); Runtime 1735/0; full solution (no
env) 14,631/14,561 passed/70 skipped/0 failed (was 14,623/14,554/69).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
2719782dc0
commit
5f9ca18155
8 changed files with 487 additions and 33 deletions
181
docs/ISSUES.md
181
docs/ISSUES.md
|
|
@ -24,6 +24,87 @@ 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.
|
||||
|
||||
## #411 — Hover feedback over interactive UI elements: no cursor swap, and item cells have no rollover state
|
||||
|
||||
**Status:** OPEN (filed 2026-08-16 during the #409 live-failure investigation, at the
|
||||
lead's scope addition; user report: "the cursor should light up when I hover over an
|
||||
item in inventory. it does not")
|
||||
**Severity:** LOW (cosmetic/affordance; no gameplay impact)
|
||||
**Depends on:** nothing — the hover dispatch it needs is already correct (see #409's
|
||||
live-failure round, which proved `UiRoot.UpdateHover` selects the right widget).
|
||||
|
||||
**Retail mechanism, derived from `docs/research/named-retail/acclient_2013_pseudo_c.txt`.**
|
||||
There are THREE separate hover-feedback layers, and the DAT decides which one applies:
|
||||
|
||||
1. **Per-element cursor** — `UIElementManager::CheckCursor @0x0045ABF0`, called from
|
||||
`SwitchMouseOver @0x0045B5F8` whenever the entered element changes. It takes
|
||||
`m_pElementWithMouseCapture` if it `HasCursor()`, else `m_pElementLastEntered` if it
|
||||
`HasCursor()`, and calls `SetCursor(elem->m_cursorDID, m_cursorHotX, m_cursorHotY, 0)`;
|
||||
otherwise it restores `m_defaultCursorDID` with the "is default" flag 1.
|
||||
`UIElement::HasCursor @0x00464980` is just `m_cursorDID != INVALID_DID`, and the ONLY
|
||||
writer of `m_cursorDID` in the whole binary is `UIElement::SetCursor @0x0045FF50`,
|
||||
whose ONLY caller is `MediaMachine::Update_Cursor @0x00465A80` — i.e. this layer is
|
||||
100% authored `MediaDescCursor` state media, never game code.
|
||||
**MEASURED (exhaustive raw scan of every `ElementDesc` in every `LayoutDesc`,
|
||||
template roots and nested children included): exactly 101 authored cursor media,
|
||||
on element types 9 (Resizebar) and 2 (Dragbar) only, using only 5 cursor DIDs —
|
||||
`0x06006119`, `0x06006126`, `0x06006127`, `0x06006128`, `0x06005E66`.** Those five
|
||||
are precisely what `RetailCursorCatalog.TryGetWindowControlCursor` already
|
||||
hardcodes. **So NO inventory item, item list, button, or option row authors a
|
||||
per-element cursor in retail, and acdream is not missing a cursor swap for them.**
|
||||
What acdream IS missing here is the general seam: `UiElement.StateCursors` is parsed
|
||||
(`LayoutImporter.ReadState`) and stored (`UiElement.SetStateCursors`) but has ZERO
|
||||
consumers — the window move/resize cursors reach the screen through the hardcoded
|
||||
catalog instead. Porting `CheckCursor` properly means driving the cursor off the
|
||||
hovered element's own `StateCursors` and deleting the hardcode.
|
||||
|
||||
2. **Global "found" cursor** — `ClientUISystem::UpdateCursorState @0x00564630` picks the
|
||||
`...Found` variant of whichever cursor the current combat/target/busy mode selects,
|
||||
keyed ONLY on `SmartBox::get_found_object_id() != 0`. The only writer is
|
||||
`UIElement_SmartBoxWrapper::FindObject @0x004E545D` — the 3D viewport's world pick.
|
||||
So this layer never fires over an inventory item either.
|
||||
|
||||
3. **Per-element rollover STATE** — `UIElementManager::SwitchMouseOver @0x0045B560` calls
|
||||
`m_pElementLastEntered->MouseOverTop(1)` on enter and `(0)` on leave.
|
||||
`UIElement::MouseOverTop @0x004615D0` sets `__bitfield164` bit 0 and broadcasts
|
||||
element message `0x1B`; the media machine then swaps the element to its rollover
|
||||
media. `UIElement_Button::MouseOverTop @0x004721F0` and
|
||||
`UIElement_Field::MouseOverTop @0x00472360` override it (the Field override is the
|
||||
drag-drop accept/reject state pair, states 9/10).
|
||||
|
||||
**Most likely what the user is seeing.** Because layers 1 and 2 are measurably not
|
||||
involved for an inventory item, "the cursor should light up" is most likely layer 3 —
|
||||
the item cell's own rollover highlight. acdream's `UiButton` honors rollover
|
||||
(`RolloverEnabled`, dat property `0x13`, `UiButton.cs:462` + its HoverEnter/HoverLeave
|
||||
handling at `UiButton.cs:831/835`), but **`UiItemSlot` has no hover handling at all** —
|
||||
no HoverEnter/HoverLeave, no rollover state. That is the concrete gap to close first.
|
||||
|
||||
**Open question that needs one user observation (or a retail side-by-side).** Whether
|
||||
the retail behavior the user remembers is (a) the item cell highlighting, or (b) the
|
||||
mouse pointer bitmap actually changing. If it is (b), the mechanism is NOT any of the
|
||||
three above for a UI item and needs a fresh derivation — do not guess. Ask before
|
||||
porting.
|
||||
|
||||
**Port plan (in dependency order).**
|
||||
1. Give `UiItemSlot` a HoverEnter/HoverLeave rollover state (retail
|
||||
`UIElement::MouseOverTop @0x004615D0` bit 0 + message `0x1B`), sourced from the
|
||||
cell's own authored rollover media where it has one.
|
||||
2. Replace `RetailCursorCatalog.TryGetWindowControlCursor`'s five hardcoded DIDs with a
|
||||
real `CheckCursor @0x0045ABF0` port off `UiElement.StateCursors` + a capture-wins
|
||||
precedence, driven from `UiRoot`'s existing hover-change edge (the same edge #409's
|
||||
tooltip dwell already uses). This is behavior-neutral for the 101 measured elements
|
||||
and removes a hardcode; it is the prerequisite for any future DAT that authors a
|
||||
cursor somewhere new.
|
||||
3. Only if the user confirms (b) above: derive the item-hover cursor mechanism fresh.
|
||||
|
||||
**Files:** `src/AcDream.App/UI/UiItemSlot.cs`, `src/AcDream.App/UI/UiRoot.cs`
|
||||
(`UpdateHover`), `src/AcDream.App/UI/RetailCursorCatalog.cs`,
|
||||
`src/AcDream.App/UI/CursorFeedbackController.cs`, `src/AcDream.App/UI/UiElement.cs`
|
||||
(`StateCursors`, today unconsumed), `src/AcDream.App/UI/Layout/LayoutImporter.cs`
|
||||
(`ReadState`'s `MediaDescCursor` read).
|
||||
|
||||
---
|
||||
|
||||
## #410 — Client-wide VJustify (vertical text justification) enum mapping + unauthored default are wrong (retail default is Top, not Center)
|
||||
|
||||
**Status:** OPEN
|
||||
|
|
@ -107,7 +188,7 @@ horizontal `HJustify` mapping while in this code, since it shares the
|
|||
|
||||
## #409 — Client-wide UI tooltip system is unshipped (GF-16, deferred out of Campaign CC gate round 1)
|
||||
|
||||
**Status:** CODE-COMPLETE 2026-08-16, review-fix round F1-F11 landed same day (`fix(ui): #409 tooltip review fix round`) — pending the user's connected visual gate (see the gate note at the bottom of this entry).
|
||||
**Status:** CODE-COMPLETE 2026-08-16; review-fix round F1-F11 and the LIVE-FAILURE round both landed same day. The live-failure round's own fix is LIVE-VERIFIED (Options -> Character tab tooltip observed on a real connected client, screenshot evidence); the user's full connected gate is still owed.
|
||||
**Severity:** LOW-MEDIUM (cosmetic/discoverability — no gameplay impact, but retail shows a tooltip on hover for authored elements client-wide and acdream showed none before this fix)
|
||||
|
||||
**2026-08-16 re-derivation + port.** Full re-derivation from
|
||||
|
|
@ -125,6 +206,63 @@ the other 187 have no literal text and show nothing — see register row
|
|||
TS-85 for the honest scope of what's missing there), superseding the
|
||||
original "~253" estimate.
|
||||
|
||||
**2026-08-16 LIVE-FAILURE round (user gate on build `1.0.3-tt.a`: "tooltips do not appear
|
||||
anywhere except one on the paperdoll").** Root-caused, fixed, and live-verified the same
|
||||
day. TWO findings, both measured, neither of them a broken hover/hit-test:
|
||||
|
||||
1. **The dominant root cause: the presenter read only the AUTHORED text.**
|
||||
`RetailTooltipPresenter.OnTooltipShow` gated on `widget.AuthoredTooltipText`
|
||||
(`P0x49`) alone. Retail's `UIElement::StartTooltipAtMouse @0x00460D70` takes the
|
||||
RUNTIME `m_TTText` first (`@0x00460DA3` `StringInfo::IsValid` -> `@0x00460DAA`
|
||||
verbatim copy) and only falls back to `InqProperty(0x49)` at `@0x00460DDF`.
|
||||
acdream ALREADY had the runtime layer — `UiElement.GetTooltipText()`, populated by
|
||||
`CharacterOptionsPageController:477`, `ChatOptionsPageController:502`,
|
||||
`ConfigOptionsPageController` (5 sites), `KeyboardConfigController:477`,
|
||||
`SocialAllegiancePageController:412`, `SocialFellowshipPageController:458`, and
|
||||
`UiCheckboxBitfield64:216` — but nothing consulted it. **Live-DAT measured:** the
|
||||
Options toggle-row checkbox (`0x2100002B` template root `0x10000218`, checkbox leaf
|
||||
`0x10000219`) authors `P0x47=0x10000397 P0x48=0x21000041 P0x4B=true` and an EMPTY
|
||||
`P0x49` — the popup locator and the on-bit are authored, only the text arrives at
|
||||
runtime, exactly as retail's `UIOption_CheckboxBitfield64::CreateChildren
|
||||
@0x00485E65` stamps its `siTooltip` array. Re-measured across every LayoutDesc: all
|
||||
187 no-literal-text tooltip elements author BOTH locator ids, i.e. the whole set is
|
||||
runtime-text targets. FIXED: `RetailTooltipPresenter.ResolveTooltipText` now uses
|
||||
retail's order, and the `P0x48`-absent fallback to the element's own LayoutDesc
|
||||
(`@0x00460E7E`, `this->m_layout->m_DID`) is ported through the new
|
||||
`UiElement.SourceLayoutDid` threaded from `LayoutImporter.Build`'s new
|
||||
`sourceLayoutDid` parameter. (`RowTemplateResolver`'s build delegate carries no
|
||||
layout id, so social row templates leave `SourceLayoutDid` at 0 — they author
|
||||
`P0x48` anyway, so no live case needs the fallback there.)
|
||||
|
||||
2. **The "243 showable" number was never an in-world number.** A grouped re-sweep of
|
||||
the same 243 found them concentrated in CHARACTER-CREATION layouts (`0x21000038`
|
||||
heritage/profession/skills/appearance/town/summary tabs, `0x21000047` attributes,
|
||||
`0x21000049`/`0x2100004C` appearance+skills, `0x21000005`/`0x2100000F`/`0x21000068`
|
||||
the shared appearance page, `0x21000046` heritage picks). The INVENTORY window
|
||||
(`0x21000023`) and paperdoll (`0x21000024`) author exactly TWO between them:
|
||||
`0x100001D6` "Drag clothing and armor here to wear them" (the doll drag mask,
|
||||
`PaperdollController.DollDragMaskId`) and `0x100005BE` "When this option is chosen,
|
||||
you will see explicit equipment slots instead of a portrait" (the Slots button).
|
||||
**That first one IS the user's single working tooltip** — confirmed live. So the
|
||||
paperdoll was never a differential against a broken mechanism; it was the only
|
||||
authored-text tooltip in the panel being hovered. Reachability was also measured
|
||||
and is NOT a problem: 238 of the 243 build as real, non-`ClickThrough` hover
|
||||
targets (230 `UiButton`, 6 `UiScrollbar`, 2 `UiField`; the 5 misses are Type-12
|
||||
prototypes the importer skips by design).
|
||||
|
||||
**Live verification (2026-08-16, connected `testaccount`/`+Acdream`, Release,
|
||||
`ACDREAM_RETAIL_UI=1`):** hovering Options -> Character -> "Vivid Targeting Indicator"
|
||||
now shows "Enable this option to apply a targeting indicator around selected objects
|
||||
and monsters for better visual reference"; a temporary hover probe confirmed the hover
|
||||
target is element `0x10000219` with `runtime=True`. Hovering the paperdoll still shows
|
||||
"Drag clothing and armor here to wear them". Hovering an inventory ITEM still shows
|
||||
nothing — that is `UIElement_UIItem::UpdateTooltip @0x004E1CB0` (retail shows the item
|
||||
NAME, `"%d %s"`-prefixed when the stack is > 1), which stays deferred: acdream's
|
||||
`UiItemSlot` is constructed programmatically at 6+ sites and carries neither the
|
||||
`P0x47` popup locator nor a name source, so porting it is its own slice, not a
|
||||
one-line seam. Register TS-85 is narrowed accordingly and now enumerates all 15
|
||||
`SetTooltip` call sites split into ported / no-acdream-analog.
|
||||
|
||||
**2026-08-16 review-fix round (F1-F11), same day.** An Opus review of the
|
||||
port above returned architectural PASS-with-findings / retail-fidelity FAIL
|
||||
with twelve findings; F1-F11 landed in one fix commit (F12 was info-only).
|
||||
|
|
@ -196,19 +334,34 @@ and the per-tick `BringToFront` re-raise chain now has four rungs
|
|||
`RetailDialogFactory`, `RetailTooltipPresenter`) — bounded and enumerable
|
||||
today, but a design smell worth flagging.
|
||||
|
||||
**Gate note (5-10 min, `ACDREAM_RETAIL_UI=1`):** hover the mouse over any
|
||||
of these and hold still — a small tooltip box should appear after a brief
|
||||
pause (~0.25 s, matching retail's registered default) and disappear when
|
||||
you move to a different control: (1) chargen Appearance page — the rotate
|
||||
arrows beside the preview ("Rotate left."/"Rotate right.") and any color
|
||||
swatch ("Changes color of selected clothing or body part."); (2) the same
|
||||
page's hair/eyes/nose spin arrows (longer help text — should WRAP across
|
||||
multiple lines, not run off-screen); (3) any other screen with a tooltip
|
||||
you recall from retail. Confirm: the box sits right at the cursor (not
|
||||
offset), never runs off the edge of the window even near a corner, and
|
||||
disappears on its own after ~10 s if you hold still without moving away.
|
||||
No click-to-dismiss is expected — only moving off the control, or a very
|
||||
long hold, closes it.
|
||||
**Gate note (5-10 min, `ACDREAM_RETAIL_UI=1`) — REWRITTEN at the live-failure
|
||||
round, because the original note only listed chargen surfaces and so could not
|
||||
have caught the in-world gap the user's gate found.** Hover and hold still; a
|
||||
small box should appear after ~0.25 s and vanish when you move to a different
|
||||
control.
|
||||
|
||||
*In-world (this is what the live-failure fix added — check these FIRST):*
|
||||
(1) Options (F11) -> **Character** tab, any checkbox row (e.g. "Vivid Targeting
|
||||
Indicator") — a full help sentence. Same for the **Chat** and **Config** tabs'
|
||||
rows, sliders and dropdowns. (2) Options -> Configure Keyboard, any key button.
|
||||
(3) Social panel (F3/F4) -> the Allegiance/Fellowship checkboxes. (4) Inventory
|
||||
-> hover the paperdoll figure ("Drag clothing and armor here to wear them") and
|
||||
the "Slots" button.
|
||||
|
||||
*Chargen (already worked before this round):* (5) Appearance page rotate arrows
|
||||
("Rotate left."/"Rotate right.") and any color swatch; (6) the hair/eyes/nose
|
||||
spin arrows — longer text, should WRAP rather than run off-screen; (7) the
|
||||
Heritage/Profession/Skills/Town/Summary tab buttons.
|
||||
|
||||
*Expected NOT to show anything yet (deferred, register TS-85):* hovering an
|
||||
inventory ITEM icon. Retail shows the item name there
|
||||
(`UIElement_UIItem::UpdateTooltip @0x004E1CB0`); acdream shows nothing.
|
||||
|
||||
Confirm also: the box sits offset down-right of the cursor (retail's +32px on
|
||||
both axes), never runs off the edge of the window even near a corner, and
|
||||
disappears on its own after ~10 s if you hold still without moving away. No
|
||||
click-to-dismiss is expected — only moving off the control, or a very long
|
||||
hold, closes it.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue