diff --git a/src/AcDream.App/UI/Layout/MapPageController.cs b/src/AcDream.App/UI/Layout/MapPageController.cs index d52f0cf1..8541522b 100644 --- a/src/AcDream.App/UI/Layout/MapPageController.cs +++ b/src/AcDream.App/UI/Layout/MapPageController.cs @@ -200,6 +200,22 @@ public sealed class MapPageController return lines; } + /// + /// Live-DAT-confirmed 2026-08-17: the SAME shared popup skin + /// hardcodes for its own runtime-text tooltips + /// (its own class doc has the full "one of the four popup skins + /// RetailTooltipPresenter already mounts" citation). The map-note + /// template (0x100001F0) authors no individual tooltip-popup + /// locator of its own (a plain 10x10 hotspot dot), so + /// RetailTooltipPresenter.OnTooltipShow's unconditional + /// AuthoredTooltipRootElementId == 0 -> return guard needs one + /// supplied — reusing the item catalog's proven-working skin is the + /// same "best-evidenced inference, not a measured retail value" shape + /// TS-85's own UpdateWorldHoverTooltip fallback already uses. + /// + private const uint MarkerTooltipRootElementId = 0x10000395u; + private const uint MarkerTooltipLayoutDid = 0x21000041u; + /// /// Instantiates the 53 static town hotspots (gmMapUI::AddMapNote) /// from m_pMap's own 0x47/0x48 template attrs. A @@ -226,12 +242,19 @@ public sealed class MapPageController marker.Width = loc.Width; marker.Height = loc.Height; // gmMapUI::AddMapNote's UIElement::SetTooltip call — a LITERAL - // string (StringInfo::SetLiteralValue), not a DAT table lookup. - // AuthoredTooltipText/Enabled is the exact seam - // RetailTooltipPresenter already serves (closes register row - // TS-85's last item, gmMapUI::AddMapNote @0x004A1C51). - marker.AuthoredTooltipText = loc.Name; - marker.AuthoredTooltipEnabled = true; + // string (StringInfo::SetLiteralValue), not a DAT table lookup — + // i.e. retail's RUNTIME m_TTText mechanism, not the authored + // P0x49 path. UiButton.TooltipText is the exact settable seam + // backing UiElement.GetTooltipText()'s override, which + // RetailTooltipPresenter.ResolveTooltipText consults BEFORE the + // authored text (closes register row TS-85's last item, + // gmMapUI::AddMapNote @0x004A1C51). AuthoredTooltipRootElementId/ + // LayoutDid still gate the popup SKIN unconditionally even on + // the runtime-text path — see MarkerTooltipRootElementId's doc. + if (marker is UiButton markerButton) + markerButton.TooltipText = loc.Name; + marker.AuthoredTooltipRootElementId = MarkerTooltipRootElementId; + marker.AuthoredTooltipLayoutDid = MarkerTooltipLayoutDid; _map!.AddChild(marker); } } diff --git a/tests/AcDream.App.Tests/UI/Layout/MapHousePanelControllerTests.cs b/tests/AcDream.App.Tests/UI/Layout/MapHousePanelControllerTests.cs index 2c91ef6f..f57a97ae 100644 --- a/tests/AcDream.App.Tests/UI/Layout/MapHousePanelControllerTests.cs +++ b/tests/AcDream.App.Tests/UI/Layout/MapHousePanelControllerTests.cs @@ -20,12 +20,22 @@ public sealed class MapHousePanelControllerTests /// Serves BOTH the town-hotspot template (any (layoutId, /// elementId) pair not the player/house icon ids) and the two icons /// re-resolves standalone - /// (m_pMap's own button-swallowed children) — a real - /// would set DatElementId the - /// same way does, so tests that need - /// to find these icons back by id after the fact need it too. + /// (m_pMap's own button-swallowed children). Returns a + /// — matching the live template's own authored + /// Type 1 (MapHousePanelSlotProbeTests: "hotspot template + /// type=1") — so 's + /// marker is UiButton tooltip-text branch is actually exercised + /// by these tests. A real would set + /// DatElementId the same way + /// does, so tests that need to find these icons back by id after the + /// fact need it too. private static UiElement? FakeHotspotTemplate(uint layoutId, uint elementId) - => new UiText { Width = 10f, Height = 10f, DatElementId = elementId }; + => new UiButton(new ElementInfo(), static _ => (0u, 0, 0)) + { + Width = 10f, + Height = 10f, + DatElementId = elementId, + }; private static MapHousePanelController.Callbacks MakeCallbacks( List? calls = null, @@ -151,8 +161,16 @@ public sealed class MapHousePanelControllerTests Assert.Equal(53, townMarkers.Count); Assert.All(townMarkers, c => Assert.Contains( MapLocations.All, loc => loc.Width == c.Width && loc.Height == c.Height)); - Assert.All(townMarkers, c => Assert.True(c.AuthoredTooltipEnabled)); - Assert.Contains(townMarkers, c => c.AuthoredTooltipText == "Holtburg"); + // Runtime tooltip text (UiButton.TooltipText, backing + // GetTooltipText()) — the retail SetTooltip/m_TTText mechanism, NOT + // the DAT-authored AuthoredTooltipText path. The popup-skin locator + // is unconditionally required even on the runtime-text path (see + // MapPageController.MarkerTooltipRootElementId's doc). + Assert.All(townMarkers, c => Assert.NotEqual(0u, c.AuthoredTooltipRootElementId)); + Assert.All(townMarkers, c => Assert.NotEqual(0u, c.AuthoredTooltipLayoutDid)); + Assert.All(townMarkers, c => Assert.IsType(c)); + Assert.All(townMarkers, c => Assert.False(string.IsNullOrEmpty(((UiButton)c).TooltipText))); + Assert.Contains(townMarkers, c => ((UiButton)c).TooltipText == "Holtburg"); } [Fact]