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]