fix(ui): #409 tooltip review fix round — F1-F11

Opus review of a377b9bf returned architectural PASS-with-findings /
retail-fidelity FAIL with F1-F12 (F12 info-only). All eleven fixed,
each re-derived against docs/research/named-retail/acclient_2013_pseudo_c.txt:

- F1 PositionAtMouse: retail offsets BOTH axes +32px before the clamp
  (StartTooltip @0x00459700, @0x00459739/@0x00459747) — was landing
  flush at the cursor.
- F2 UiRoot: the dwell timer now anchors to mouse-IDLE like retail's
  m_lastMouseMoveTime (MouseMoveHandler @0x0045E710), resetting on
  every move within the same widget while !_tooltipFired, not just on
  hover-enter.
- F3 register TS-85 rewritten: the "dynamic InqProperty(0x49) override"
  framing was false — UIElement::InqProperty @0x004638D0's base impl
  reads the same authored bags this port already reads. The real
  second text source (m_TTText/SetTooltip, headed by the P0xD0
  truncated-text auto-tooltip @0x00466F80) needs a per-line-position
  truncation model UiText doesn't have — sized disproportionate for
  this round and left honestly deferred rather than stubbed.
- F4 OnTooltipShow: null LayoutPolicy + Anchors=None on the popup root
  and text child before resizing, mirroring RetailMessageDialogView's
  sibling shape.
- F5 OnTooltipShow: return without mounting when the P0x4A text child
  doesn't resolve to a UiText (retail's DynamicCast gate,
  StartTooltip @0x0045DE90 @0x0045df65/@0x0045df6f) — was mounting an
  empty 30x30 bevel artifact.
- F6 UiRoot.Tick: the dwell-arm branch now requires Captured is null
  (CheckTooltip @0x0045B6E0 @0x0045b715) — a widget hovered before a
  drag/resize/capture began must not pop mid-gesture.
- F7 UiRoot.ReleaseCapture: no longer resets _tooltipFired
  (ReleaseMouseCapture @0x0045D2B0 touches only the idle timestamp) —
  a mouse-up while a tooltip is shown no longer tears it down and
  silently re-fires it 250ms later.
- F8 ApplyTooltipText: applies ResizeTo's own max/min width/height
  clamps (P0x3C/0x3D/0x3E/0x3F, @0x00463C30) before assigning the
  grown size; zeroes text.Padding to keep the measured size margin-
  comparable. New ElementInfo/UiElement plumbing for the four
  properties, same shape as the existing tooltip fields.
- F9 doc precision: sweep counts corrected 434->430 / 191->187 (live-
  DAT re-measured), the "243 showable" claim now measured exactly
  (not assumed) via a new Showable column in the sweep test, and the
  MiscSettings citation split into its two real mechanisms
  (RegisterPreference in Init vs. AttachPreference/SetPreferenceRange
  elsewhere).
- F10 register AD-106: the topmost guarantee is versus dialogs/screens
  only (the overlay popup layer and drag ghost still paint above
  regardless), and the per-tick BringToFront ratchet has four rungs,
  not three.
- F11 RetailUiRuntime.ResetSessionDialogs: now also calls the new
  UiRoot.ResetTooltipTracking() so a post-reset hover re-shows
  immediately instead of waiting out the stale fired-latch.

New pinning tests (RetailTooltipPresenterTests: F1/F2/F5/F6/F7/F8) each
verified to fail against the pre-fix behavior via a temporary revert-
and-rerun before being confirmed against the restored fix.
PortalProjectionTests.ProjectToClipLease_ReusesPooledWorkWithoutResultArrays
recurrence logged on issue #346 (already the tracking issue for this
load-sensitive flake) — hit twice under load this review, standalone
26/26, unrelated to #409.

Gates: Release build 0 errors; App suite (live-DAT env) 5416/5413
passed/3 skips (was 5410/5407/3, +6 new tests); Runtime 1735/0;
UI.Abstractions 926/0; full solution (no env, 69 skips expected)
14,623/14,554 passed/69 skipped/0 failed (was 14,617/14,548, +6).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-16 20:54:15 +02:00
parent a377b9bff7
commit 2719782dc0
11 changed files with 515 additions and 58 deletions

View file

@ -107,10 +107,10 @@ 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 (this commit) — 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 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).
**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 (this commit).** Full re-derivation from
**2026-08-16 re-derivation + port.** Full re-derivation from
`docs/research/named-retail/acclient_2013_pseudo_c.txt` corrected two things
the original GF-16 filing below got wrong from a shallower pass: **`P0x47`
is NOT a "tooltip behavior enum" — it is the element-desc id WITHIN the
@ -119,11 +119,40 @@ popup LayoutDesc (`P0x48`) to instantiate as the popup's root**
`LayoutDesc::AccessElementDesc`), and **`P0x4A` is read off the freshly
INSTANTIATED popup's own root element, not the hovering trigger element**
(it names that popup's text-child id). A live-DAT sweep (installed EoR
build) found **434 elements author at least one of the five trigger
build) found **430 elements author at least one of the five trigger
properties** (243 with literal `P0x49` `StringInfo` text this port shows;
the other 191 rely on retail's DYNAMIC `InqProperty(0x49)` text override,
out of scope — see register row TS-85), superseding the original "~253"
estimate. Layout `0x21000041` holds four 30x30 popup skins
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 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).
Highlights: the popup now offsets +32px from the mouse on both axes
(`StartTooltip @0x00459700`, was landing flush at the cursor); the dwell
timer now anchors to mouse-IDLE like retail's `m_lastMouseMoveTime`, not
hover-enter, and is gated off while the mouse has capture
(`CheckTooltip @0x0045B6E0`); `ReleaseCapture` no longer tears down an
already-shown tooltip's fired latch (it only restarts the idle deadline,
matching `ReleaseMouseCapture @0x0045D2B0`); the popup no longer mounts an
empty bevel artifact when its text child fails to resolve
(`StartTooltip @0x0045DE90`'s `DynamicCast` gate); the popup and its text
child now null their anchor policy before resizing, mirroring the sibling
`RetailMessageDialogView`; the auto-resize now applies retail's
`ResizeTo @0x00463C30` max/min width/height clamps; and a session reset now
also clears `UiRoot`'s own hover/tooltip-fired latch (not just the
presenter's popup element) so a post-reset hover re-shows immediately. The
review's biggest correction was to the ORIGINAL port's own framing of what
it deferred: "191 elements rely on retail's dynamic `InqProperty(0x49)`
override" was FALSE — retail's own base `InqProperty` reads the same
authored property bags this port already reads, so most no-literal-text
elements show nothing in retail too. Register row TS-85 is rewritten (not
just re-counted) with the real gap: the `m_TTText`/`SetTooltip` runtime-text
family, headed by the `P0xD0` truncated-text auto-tooltip
(`UIElement_Text::RecalculateTruncation @0x00466F80`) — sized and found
disproportionate to port in the same round (it needs a per-line-position
truncation model this port's `UiText` doesn't have), so it stays deferred,
honestly described. Layout `0x21000041` holds four 30x30 popup skins
(`0x10000487`/`0x10000395`/`0x10000397`/`0x10000398`), each a four-piece
bevel frame around one shared Type-12 text child `0x10000396` — confirmed
by `TooltipLiveDatTests`.
@ -152,11 +181,20 @@ already drives dismissal, and it no-ops when the hit-tested element hasn't
changed, so retail itself does not dismiss a tooltip by clicking its own
owner.
**Deferred (register TS-85):** dynamic per-element tooltip text
(`InqProperty(0x49)`, 191 elements) and the `P0x3D` per-element wrap-width
override (zero elements author one today). **Deferred (register AD-106):**
the topmost-z-order mechanism is a sibling-with-later-reraise adaptation,
not retail's literal separate presentation layer.
**Deferred (register TS-85, rewritten at the F3 review round):** the
`m_TTText`/`SetTooltip` runtime-text family headed by the `P0xD0`
truncated-text auto-tooltip (187 of 430 tooltip-property-authoring elements
have no literal text and show nothing; an unmeasured subset of those would
show retail's truncation tooltip instead) and the `P0x3D` per-element
wrap-width override (zero elements author one today). **Deferred (register
AD-106, two honest additions at the F10 review round):** the topmost-z-order
mechanism is a sibling-with-later-reraise adaptation, not retail's literal
separate presentation layer — the guarantee is versus dialogs/screens ONLY
(the overlay popup layer and the drag ghost still paint above regardless),
and the per-tick `BringToFront` re-raise chain now has four rungs
(`CharacterManagementUiController`, `CharacterCreationUiController`,
`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
@ -2893,6 +2931,18 @@ two subsequent full runs. Same FILE as #302 but a DIFFERENT test — filed
separately per the never-conflate rule.
**Filed:** 2026-08-08, observed during #344's suite runs.
**2026-08-16 recurrence (Campaign #409 tooltip review-fix round).** The Opus
reviewer of `a377b9bf` hit this same assertion failing TWICE under
full-solution load, unrelated to any #409 tooltip code; passes standalone
26/26. Joining the same running known-flake set the CC7 gate row already
names (`docs/plans/2026-08-15-character-creation-campaign.md`, CC7 row) for
Core.Net `NakEmissionTests.LossSoak_...` / Content `DecodedTextureCacheTests`
/ App `SocialPanelLiveMountProbeTests.ProbeLiveMountShapes` /
`RuntimeCollisionReportingStateTests.WarmedSteadyContactRefreshDoesNotAllocate`
a full-solution gate hitting exactly one of these five allocation/timing-
sensitive tests, with a clean standalone or immediate-rerun pass, is this
known class, not a new regression.
---
## #344 — Mid-teleport crash: world-frame owners disagree during a long portal into a dungeon