fix(ui): world tooltips never cleared, stacking dozens of popups — single-slot invariant restored on every found-object edge

RetailTooltipPresenter.UpdateWorldHoverTooltip only called RemovePopup()
on the found-object-LOST edge (found == 0u). An A->B found-object CHANGE
(walking past a run of NPCs/doors/lifestones with no intervening "nothing
found" frame) skipped straight to TryBuildAndMountPopup with the previous
popup still mounted as a child of _host -- only the _popupRoot reference
got overwritten, so every earlier popup was orphaned in the tree and never
removed. Matches the user's screenshot of 15+ stacked name boxes.

Fix: clear any showing world popup on ANY found-object edge -- change or
loss -- before evaluating whether to mount a new one, mirroring
OnTooltipShow's own unconditional RemovePopup() at its top.

Live-verified against local ACE (testaccount/+Acdream, session-config
launch): a temporary probe logged 103 mount/102 remove events across many
direct object-to-object transitions (Silver Tusker, Armored Tusker,
+Acdream); hostChildren never exceeded baseline+1 and popupSkinChildren
never exceeded 1 -- confirmed at most one tooltip ever exists. Probe
stripped before landing; two new fixture regressions
(WorldHover_FoundObjectChangesDirectly_ReplacesThePopupWithoutStacking,
WorldHover_ThenUiDwellTooltip_ReplacesRatherThanStacks) both fail pre-fix.

fix #409 (follow-on)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-17 00:23:54 +02:00
parent 63b0668fb9
commit 97a7be12ee
3 changed files with 127 additions and 4 deletions

View file

@ -353,12 +353,23 @@ public sealed class RetailTooltipPresenter : IDisposable
return; // no change -> RecvNotice_SmartBoxObjectFound never re-fires
_worldHoverGuid = found;
// #409 follow-on (2026-08-16 overnight hover/UI round, Batch A bug 1):
// every found-object edge — whether to a DIFFERENT object or to
// none at all — tears down whatever world popup is currently up
// FIRST, mirroring OnTooltipShow's own unconditional RemovePopup() at
// its top. The pre-fix code only cleared on the found==0u edge, so an
// A-found-B transition (walking past a run of NPCs/doors/lifestones
// with never a frame of "nothing found" between them) called
// TryBuildAndMountPopup again with the OLD popup still mounted as a
// child of _host — only the _popupRoot reference got overwritten, so
// every previous popup was orphaned in the tree and never removed.
// _popupRoot is a single field by design (retail's own single
// m_pTooltipElement slot); this restores that single-slot invariant.
if (_worldTooltipShowing)
RemovePopup();
if (found == 0u)
{
if (_worldTooltipShowing)
RemovePopup();
return;
}
if (WorldTooltipsEnabled?.Invoke() != true)
return;