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:
parent
63b0668fb9
commit
97a7be12ee
3 changed files with 127 additions and 4 deletions
|
|
@ -689,6 +689,76 @@ public sealed class RetailTooltipPresenterTests
|
|||
Assert.Empty(requests);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WorldHover_FoundObjectChangesDirectly_ReplacesThePopupWithoutStacking()
|
||||
{
|
||||
// #409 follow-on (2026-08-16 overnight hover/UI round, Batch A bug 1):
|
||||
// the regression that filled the user's screen with dozens of
|
||||
// stacked tooltips. Walking past a run of NPCs/doors/lifestones never
|
||||
// produces a frame where the found guid is 0 — it goes straight from
|
||||
// A to B to C. RecvNotice_SmartBoxObjectFound-equivalent must still
|
||||
// only ever have ONE popup mounted: found A, then found B (no
|
||||
// intervening "nothing found" tick) must swap the popup, not add a
|
||||
// second one on top of the first.
|
||||
const uint otherGuid = 0x80000456u;
|
||||
var (root, presenter, requests) = CreateHarness();
|
||||
uint current = WorldFoundGuid;
|
||||
presenter.WorldHoverGuidProvider = () => current;
|
||||
presenter.WorldHoverNameResolver = guid =>
|
||||
guid == WorldFoundGuid ? "A Drudge" : "A Door";
|
||||
presenter.WorldTooltipsEnabled = () => true;
|
||||
int childrenBefore = root.Children.Count;
|
||||
|
||||
presenter.Tick();
|
||||
Assert.Equal(childrenBefore + 1, root.Children.Count);
|
||||
|
||||
current = otherGuid;
|
||||
presenter.Tick();
|
||||
|
||||
// Exactly one popup, not two stacked.
|
||||
Assert.Equal(childrenBefore + 1, root.Children.Count);
|
||||
Assert.Equal(2, requests.Count);
|
||||
|
||||
current = WorldFoundGuid;
|
||||
presenter.Tick();
|
||||
current = otherGuid;
|
||||
presenter.Tick();
|
||||
current = WorldFoundGuid;
|
||||
presenter.Tick();
|
||||
|
||||
// Several more A/B/A swaps still leave exactly one popup mounted —
|
||||
// this is the "dozens of stacked name boxes" scenario, minus the bug.
|
||||
Assert.Equal(childrenBefore + 1, root.Children.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WorldHover_ThenUiDwellTooltip_ReplacesRatherThanStacks()
|
||||
{
|
||||
// The other half of the "no stacking" contract: a world tooltip
|
||||
// showing, then the mouse settles on a real UI element (dwell path)
|
||||
// — OnTooltipShow's own unconditional RemovePopup() must clear the
|
||||
// world popup, leaving exactly one popup (the UI one), not two.
|
||||
var (root, presenter, _) = CreateHarness();
|
||||
presenter.WorldHoverGuidProvider = () => WorldFoundGuid;
|
||||
presenter.WorldHoverNameResolver = _ => "A Drudge";
|
||||
presenter.WorldTooltipsEnabled = () => true;
|
||||
int childrenBefore = root.Children.Count;
|
||||
|
||||
presenter.Tick();
|
||||
Assert.Equal(childrenBefore + 1, root.Children.Count); // world tooltip up
|
||||
|
||||
var target = AddFullyAuthoredTarget(root);
|
||||
root.OnMouseMove(110, 110);
|
||||
root.Tick(0.016, 0);
|
||||
root.Tick(0.016, root.TooltipDelayMs);
|
||||
|
||||
UiElement popup = root.Children.Single(c => !ReferenceEquals(c, target));
|
||||
Assert.NotNull(popup);
|
||||
// childrenBefore world-target(none) + target(1) + popup(1) == +2 total,
|
||||
// never +3 (world popup replaced, not stacked).
|
||||
Assert.Equal(childrenBefore + 2, root.Children.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WorldHover_ReEvaluatesGateAndTextOnlyOnTheFoundGuidEdge()
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue