docs: hover-feedback completion round — #409/#411 bookkeeping, TS-85 narrowed

Records the round that shipped item-cell tooltips, the world-object
hover tooltip, and the #411 cursor-swap fix: #409's write-up gains a
"hover-feedback completion round" section covering all three items
with live-verification notes; #411 is closed with the corrected
decomp reading; register row TS-85 is narrowed to reflect the two
newly-ported SetTooltip call sites (UIElement_UIItem::UpdateTooltip,
UIElement_SmartBoxWrapper::RecvNotice_SmartBoxObjectFound) and the
still-open ones (spellcasting endowment/cast-button/favorite/submenu,
map notes, character-panel attribute/skill info).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-16 23:02:50 +02:00
parent aed423174b
commit 708e35f610
2 changed files with 126 additions and 9 deletions

View file

@ -26,13 +26,33 @@ What does NOT go here:
## #411 — Hover feedback over interactive UI elements: no cursor swap, and item cells have no rollover state ## #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 **Status:** CLOSED 2026-08-16 at the #409 hover-feedback completion round — the
lead's scope addition; user report: "the cursor should light up when I hover over an user's answer to the open question below ("the POINTER changes, like it already
item in inventory. it does not") does over world NPCs") is CONFIRMED by decomp, not merely a memory to trust:
`UIElement_SmartBoxWrapper::FindObject @0x004E5430` calls
`SmartBox::set_found_object(itemID)` UNCONDITIONALLY whenever the hovered UI
element casts to `UIElement_UIItem` — not gated on target mode, as the port plan
below (written before this finding) assumed it would need to be. Fixed with a
ONE-LINE widening of `CursorFeedbackController.Update(UiRoot)`'s existing
(too-narrow) item-hover special case; `ResolveGlobalKind` needed no changes,
since it already read the "found" flag unconditionally across every mode. See
docs/ISSUES.md #409's own "hover-feedback completion round" write-up (item 3)
for the full derivation and `CursorFeedbackControllerTests.
UpdateFromRoot_HoveringAnItemSlot_ShowsFoundCursor_In{OrdinaryPeaceMode,CombatMode}`
for the pin. The rollover-STATE half of this investigation (item 3 below,
`UiItemSlot` has no `HoverEnter`/`HoverLeave`) was NOT in scope for the pointer
question and remains unaddressed if the user separately wants the highlight —
file a fresh issue if so; this closure covers only the pointer-swap question the
lead's scope addition asked about.
**Severity:** LOW (cosmetic/affordance; no gameplay impact) **Severity:** LOW (cosmetic/affordance; no gameplay impact)
**Depends on:** nothing — the hover dispatch it needs is already correct (see #409's **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). live-failure round, which proved `UiRoot.UpdateHover` selects the right widget).
**Original investigation (below), kept for its still-valid layer 1/2/3 breakdown —
only the "never fires over an inventory item" conclusion for layer 2 was
incomplete; see the closure note above for the corrected reading.**
**Retail mechanism, derived from `docs/research/named-retail/acclient_2013_pseudo_c.txt`.** **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: There are THREE separate hover-feedback layers, and the DAT decides which one applies:
@ -188,7 +208,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) ## #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 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. **Status:** CODE-COMPLETE 2026-08-16; review-fix round F1-F11, the LIVE-FAILURE round, and the hover-feedback completion round (item-cell tooltips + world-object hover tooltip) all 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 hover-feedback completion round's three items are automated-gate-verified (unit + live-DAT) but the user's connected gate for THOSE items specifically is still owed — see that round's own "Live-verify all three" note.
**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) **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 **2026-08-16 re-derivation + port.** Full re-derivation from
@ -353,16 +373,113 @@ the "Slots" button.
spin arrows — longer text, should WRAP rather than run off-screen; (7) the spin arrows — longer text, should WRAP rather than run off-screen; (7) the
Heritage/Profession/Skills/Town/Summary tab buttons. 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 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 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 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 click-to-dismiss is expected — only moving off the control, or a very long
hold, closes it. hold, closes it.
**2026-08-16 hover-feedback completion round.** Closes the two items the
live-failure round explicitly deferred (item-cell tooltips, and the #411
pointer question), plus the world-object hover tooltip the user's gate notes
called out separately.
1. **Inventory/shortcut/paperdoll item-name tooltips — SHIPPED.**
`UIElement_UIItem::UpdateTooltip @0x004E1CB0` is called from
`UIItem_Update` (an item-DATA-CHANGE refresh, not a hover handler — the
trigger that actually SHOWS it is the generic `CheckTooltip` dwell timer,
same as any other tooltip-bearing element). Re-derived and closed the gap
the live-failure round left open ("acdream's `UiItemSlot` carries neither
the `P0x47` popup locator nor a name source"): live-DAT sweep of the
shared UIItem cell-template catalog (`ItemListCellTemplate.CatalogLayoutId`,
`0x21000037`) found ALL 47 UIItem-type (class `0x10000032`) prototypes —
inventory's cell, every toolbar slot, every paperdoll/armor slot skin —
resolve the IDENTICAL popup locator (`P0x47=0x10000395`/`P0x48=0x21000041`)
through catalog inheritance, with no literal text authored on any of them
(`TooltipLiveDatTests.UiItemCatalog_EveryPrototype_SharesTheSamePopupLocator`).
`UiItemSlot` now hardcodes that pair and exposes `GetTooltipText()` via a
new per-instance `TooltipTextResolve` delegate, wired at every physical-
item construction site — `InventoryController` (main-pack cell + grid
cells), `ExternalContainerController`, `PaperdollController` (closes the
`gmPaperDollUI::UpdateItemSlotTooltip @0x004A52EF` row too — same cell
class, same fix), `VendorUiController` (shop/buying/selling lists),
`SecureTradeUiController`, `ToolbarController`. Text is
`ClientObject.GetTooltipDisplayName()` (new Core method): `GetAppropriateName()`
prefixed with the stack count via `"{count} {name}"` when `StackSize > 1`,
matching `UpdateTooltip`'s exact `NAME_APPROPRIATE` + `"%d %s"` sprintf.
`UiCatalogSlot` (spell/component catalog cells — a DIFFERENT `UiItemSlot`
subclass) is unaffected; it already overrides `GetTooltipText()` with its
own `Label`.
2. **World-object hover tooltip (NPCs, players, signs, chests, portals) —
SHIPPED.** NOT the UI-element dwell-timer path — retail's mechanism is
`UIElement_SmartBoxWrapper::RecvNotice_SmartBoxObjectFound @0x004E5AD0`,
fed every frame by `FindObject @0x004E5430`/`Global_Loop @0x004E5620`
using the current mouse position regardless of input focus. It fires
IMMEDIATELY (no dwell wait) on the found-object id CHANGING, gated by the
`PlayerModule::ShowTooltips` character option (`CharacterOptionId.ShowTooltips`
— already modeled in `CharacterOptionTable`, default true), with text
`ACCWeenieObject::GetObjectName(id, NAME_APPROPRIATE, 0)` — the SAME name
call as item tooltips, but WITHOUT the item-cell's separate stack-count
prefix (a real, decomp-confirmed asymmetry: a ground pile of arrows shows
"Arrows", not "20 Arrows"). Ported as `RetailTooltipPresenter.
UpdateWorldHoverTooltip`, driven by the SAME world-hover pick
`CursorFeedbackController`'s own found-cursor already uses
(`WorldSelectionQuery.PickAtCursor`, `includeSelf: true`) and the SAME
`ClientObjectTable`-backed name resolver `SocialAllegiancePageController`'s
`ResolveWorldObjectName` already established as this codebase's pattern.
Queried only when no UI element is hovered (this port's reading of
`FindObject`'s `m_pElementLastOver` check, narrowed from retail's literal
"raycast even under non-item UI chrome" — see the class's own doc note).
**Own player is included** (`includeSelf: true`, the same precedent the
cursor feedback wiring already set) — no decomp evidence was found either
confirming or excluding self from the found-object pipeline, so this
follows the established local precedent rather than guessing fresh; flag
if that reads wrong in the live gate. **The exact popup skin is an
inference, not a measured value** — an exhaustive live-DAT sweep found
`UIElement_SmartBoxWrapper` (class `0x10000030`) has NO authored
`ElementDesc` anywhere installed (unlike every other tooltip trigger, it
is evidently constructed directly by `gmGamePlayUI`'s own mode setup, not
from a walkable LayoutDesc — `TooltipLiveDatTests.
SmartBoxWrapper_HasNoAuthoredElementDesc_AnywhereInstalled`), so its real
`P0x47`/`P0x48` cannot be read off the DAT the way the item catalog's can.
This port reuses the SAME pair every other game-code `SetTooltip` caller
in this family resolves to — the best-evidenced choice, called out in
register row TS-85 rather than silently assumed exact.
3. **#411 resolved: retail DOES swap the pointer over inventory items,
unconditionally — the earlier investigation's "never fires over an
inventory item" finding was INCOMPLETE, not wrong about what it checked.**
The original #411 scan (below) correctly found no PER-ELEMENT authored
cursor on item cells and correctly found `SmartBox::get_found_object_id()`
is written only by `UIElement_SmartBoxWrapper` — but it had not yet traced
`FindObject @0x004E5430` far enough: when the currently-hovered UI element
(`m_pElementLastOver`) casts to `UIElement_UIItem` (class `0x10000032`),
`FindObject` calls `SmartBox::set_found_object(itemID, 0xFFFFFFFF)`
directly and returns WITHOUT running the 3D raycast — UNCONDITIONALLY, not
gated on target mode. `ClientUISystem::UpdateCursorState @0x00564630`
computes its "found" flag ONCE at the top of the function
(`ebx = SmartBox::get_found_object_id() != 0`, `@0x00564642`) and every
later branch (default/melee-missile/magic/use/examine/use-target/busy)
reads that SAME flag — so hovering an occupied item cell shows the
cursor's "...Found" variant in EVERY mode, not only during an active
`UseTarget` selection. `CursorFeedbackController.Update(UiRoot)` already
had the item-hover special case wired (from an earlier round) but
incorrectly gated it to `TargetMode.UseTarget` only; that one-line gate is
now removed — `ResolveGlobalKind`'s existing found/not-found branching
needed no changes at all, since it already read the snapshot's
`HoverTargetGuid` unconditionally across every mode. Live-DAT-independent
(pure decomp + unit fixture), so no DAT sweep was needed for this part;
two new `CursorFeedbackControllerTests` pin the widened behavior in
ordinary peace mode and in combat mode.
**Live-verify all three on the connected client** (session-config launch,
graceful close per the usual rules): hover an inventory item — a name
tooltip should appear (with a count prefix for a stack) AND the mouse
pointer should swap to its "found" variant; hover an NPC/creature — a name
tooltip should appear immediately (no perceptible delay) if "Show Tooltips"
is on; hover a sign/chest/portal similarly.
--- ---
**Original GF-16 filing (superseded by the re-derivation above; kept for **Original GF-16 filing (superseded by the re-derivation above; kept for

File diff suppressed because one or more lines are too long