using AcDream.App.UI; using AcDream.App.UI.Layout; using AcDream.Core.Net.Messages; using AcDream.Core.Physics; using AcDream.Core.Ui; using AcDream.Core.World; namespace AcDream.App.Tests.UI.Layout; /// /// Unit coverage for 's pure math: the /// calendar formatter ("Date: %s\nTime: %s", /// gmMapUI::Update @0x004a1eb0) and the 53-town static table /// (verbatim port of s_rgLocations). Marker-placement math itself is /// / — both /// already unit-tested elsewhere; this file only proves the wiring /// reproduces their output through the real fixture (no re-derivation). /// public sealed class MapPageControllerTests { // ── Calendar formatter ─────────────────────────────────────────────── [Fact] public void FormatDateTime_OrdinaryHour_NoAndHalfSuffix() { var calendar = new DerethDateTime.Calendar( 119, DerethDateTime.MonthName.Frostfell, 27, DerethDateTime.HourName.Dawnsong); string text = MapPageController.FormatDateTime(calendar); Assert.Equal("Date: Frostfell 27, 119 P.Y.\nTime: Dawnsong", text); } [Fact] public void FormatDateTime_AndHalfHour_RewritesSuffixWithHyphens() { var calendar = new DerethDateTime.Calendar( 10, DerethDateTime.MonthName.Morningthaw, 1, DerethDateTime.HourName.MorntideAndHalf); string text = MapPageController.FormatDateTime(calendar); Assert.Equal("Date: Morningthaw 1, 10 P.Y.\nTime: Morntide-and-Half", text); } [Theory] [InlineData(DerethDateTime.HourName.Darktide, "Darktide")] [InlineData(DerethDateTime.HourName.DarktideAndHalf, "Darktide-and-Half")] [InlineData(DerethDateTime.HourName.Gloaming, "Gloaming")] [InlineData(DerethDateTime.HourName.GloamingAndHalf, "Gloaming-and-Half")] [InlineData(DerethDateTime.HourName.WarmtideAndHalf, "Warmtide-and-Half")] public void FormatDateTime_EveryHourName_MatchesExpectedDisplayText( DerethDateTime.HourName hour, string expectedHourText) { var calendar = new DerethDateTime.Calendar( 10, DerethDateTime.MonthName.Morningthaw, 1, hour); string text = MapPageController.FormatDateTime(calendar); Assert.EndsWith($"Time: {expectedHourText}", text); } // ── Town table ──────────────────────────────────────────────────────── [Fact] public void MapLocations_Has53Entries() { Assert.Equal(53, MapLocations.All.Length); } [Fact] public void MapLocations_AllNamesAreUnique() { var names = new HashSet(StringComparer.Ordinal); foreach (MapLocation loc in MapLocations.All) Assert.True(names.Add(loc.Name), $"duplicate town name: {loc.Name}"); } [Fact] public void MapLocations_Holtburg_MatchesDecompiledByteValues() { // s_rgLocations[0x13] (pc:977379): X=0xa4 Y=0x4d W=9 H=8. MapLocation holtburg = Assert.Single(MapLocations.All, l => l.Name == "Holtburg"); Assert.Equal(0xa4, holtburg.X); Assert.Equal(0x4d, holtburg.Y); Assert.Equal(9, holtburg.Width); Assert.Equal(8, holtburg.Height); } [Fact] public void MapLocations_EveryRectIsWithinTheMapWidgetsAuthoredExtent() { // m_pMap's own authored size (MapHousePanelSlotProbeTests: markerArea // (6,8)-(247,258) — the widest observed extent). Town rects are // independent of the marker-area rect but should still land inside // a sane 0..300 canvas — a coarse sanity check that the verbatim // port didn't transpose a digit. foreach (MapLocation loc in MapLocations.All) { Assert.InRange(loc.X, 0, 260); Assert.InRange(loc.Y, 0, 260); Assert.InRange(loc.Width, 1, 20); Assert.InRange(loc.Height, 1, 20); } } // ── Marker placement wiring (real fixture, no re-derivation) ──────────── [Fact] public void Bind_PlayerMarker_OutdoorCell_ReproducesRadarCoordinatesPlacement() { // Arwic's landblock cell id (0x11CE0001 — an arbitrary real outdoor // cell, picked only because RadarCoordinates.TryFromCell already // proves gid-to-lcoord conformance elsewhere; this test proves the // WIRING, not the formula). const uint cellId = 0x11CE0001u; Assert.True(RadarCoordinates.TryFromCell(cellId, out RadarCoordinates expected)); ElementInfo rootInfo = FixtureLoader.LoadMapHouseHostInfos(); ImportedLayout layout = FixtureLoader.LoadMapHouseHost(); var callbacks = new MapHousePanelController.Callbacks( Toggle: () => { }, Map: new MapPageController.Bindings( CurrentCalendar: static () => default, PlayerCellId: () => cellId, HousePosition: static () => (CreateObject.ServerPosition?)null, TemplateResolver: (_, e) => new UiText { Width = 10f, Height = 10f, DatElementId = e }), House: new HousePageController.Bindings(Lines: static () => Array.Empty())); MapHousePanelController? controller = MapHousePanelController.Bind(rootInfo, layout, callbacks); Assert.NotNull(controller); UiElement? playerIcon = UiElement.FindDescendant(controller!.Root, MapPageController.PlayerIconId); Assert.NotNull(playerIcon); Assert.True(playerIcon!.Visible); // markerArea from the live fixture (MapHousePanelSlotProbeTests): // (6,8)-(247,258) -> m_x0=6, m_y0=8. const int markerX0 = 6, markerY0 = 8; Assert.Equal(markerX0 + (float)expected.X - playerIcon.Width / 2f, playerIcon.Left, precision: 3); Assert.Equal(markerY0 + (float)expected.Y - playerIcon.Height / 2f, playerIcon.Top, precision: 3); } [Fact] public void Bind_PlayerMarker_IndoorCell_HidesIconAndClearsCoordinateText() { // Envcell low word (>= 0x100) fails RadarCoordinates.TryFromCell — // the indoor branch (gmMapUI::Update's else: SetVisible(0)). const uint indoorCellId = 0x0012_0100u; Assert.False(RadarCoordinates.TryFromCell(indoorCellId, out _)); ElementInfo rootInfo = FixtureLoader.LoadMapHouseHostInfos(); ImportedLayout layout = FixtureLoader.LoadMapHouseHost(); var callbacks = new MapHousePanelController.Callbacks( Toggle: () => { }, Map: new MapPageController.Bindings( CurrentCalendar: static () => default, PlayerCellId: () => indoorCellId, HousePosition: static () => (CreateObject.ServerPosition?)null, TemplateResolver: (_, e) => new UiText { Width = 10f, Height = 10f, DatElementId = e }), House: new HousePageController.Bindings(Lines: static () => Array.Empty())); MapHousePanelController? controller = MapHousePanelController.Bind(rootInfo, layout, callbacks); Assert.NotNull(controller); UiElement? playerIcon = UiElement.FindDescendant(controller!.Root, MapPageController.PlayerIconId); Assert.NotNull(playerIcon); Assert.False(playerIcon!.Visible); } [Fact] public void Bind_HouseMarker_NullPosition_StaysHidden() { ElementInfo rootInfo = FixtureLoader.LoadMapHouseHostInfos(); ImportedLayout layout = FixtureLoader.LoadMapHouseHost(); var callbacks = new MapHousePanelController.Callbacks( Toggle: () => { }, Map: new MapPageController.Bindings( CurrentCalendar: static () => default, PlayerCellId: static () => 0u, HousePosition: static () => (CreateObject.ServerPosition?)null, TemplateResolver: (_, e) => new UiText { Width = 10f, Height = 10f, DatElementId = e }), House: new HousePageController.Bindings(Lines: static () => Array.Empty())); MapHousePanelController? controller = MapHousePanelController.Bind(rootInfo, layout, callbacks); Assert.NotNull(controller); UiElement? houseIcon = UiElement.FindDescendant(controller!.Root, MapPageController.HouseIconId); Assert.NotNull(houseIcon); Assert.False(houseIcon!.Visible); } }