fix(ui): morning gate — map town markers: green rollover highlight + the authored map-note tooltip skin/font
User finding 3 (retail screenshot): hovering a town on the Map tab turns its marker GREEN and shows the name on a special-font tooltip — clearly not our generic 0x10000395 popup skin, and we had no hover highlight at all. Re-derivation (live-DAT probe + raw ElementDesc dump + surface byte-decode; MapNoteLiveDatTests pins all of it): - m_pMap (0x100001EC)'s P0x47/P0x48 = 0x100001F0 @ 0x21000026 are the note CONSTRUCTION template (AddMapNote @0x004a1bb0's CreateChildElement args) — that part we had right. - The TEMPLATE's own DirectState authors the note's tooltip popup locator P0x47=0x10000398/P0x48=0x21000041 — the FOURTH popup skin, whose incorporated text child 0x10000396 fonts 0x40000015 where the other three skins font 0x40000002 (the user's "special font") — plus P0x50=0.0 (zero per-element tooltip delay: town tooltips fire the instant the dwell arms; UiRoot already honors it), P0x4B TooltipOn, and P0x13 RolloverEnabled. Batch C's "the template authors no locator of its own" claim was WRONG, and BuildTownMarkers' hardcoded shared-skin override was clobbering the authored values — removed. - The hover highlight: the template's Normal/Normal_rollover states are PassToChildren descriptors driving the swallowed highlight child 0x100001F1 (base 0x100002B7@0x21000042 — a four-piece frame all drawing 0x06004CC9, byte-decoded PURE GREEN A=FF R=00 G=FF B=00) via per-state P0x3B (Invisible): hidden at rest, green on rollover. Port: - UiButton.CascadeStateToChildren — retail UIElement::SetState @0x00464E70's PassToChildren cascade, keyed off the REQUESTED state id (properties commit unconditionally; only the sprite draw is art-gated, the existing #382/AP-222 distinction). - UiDatElement.TrySetRetailState honors per-state P0x3B for NAMED states (OnSetAttribute @0x00462d80 case 8: SetVisible(value==0)). The unnamed-DirectState case is explicitly excluded — honoring it would un-gate ISSUES #408 (1,083 authored-invisible elements) through BuildWidget's post-children state reapply; measured breaking the spell-favorite drag tests before the scoping (note added to #408). - MapPageController.BuildTownMarkers rebuilds the button-swallowed highlight child per marker through the AD-108 IconBuilder seam (Bindings.TemplateInfoResolver, backed by RowTemplateResolver.ResolveInfo — same cache) and arms it with the initial Normal cascade. Register TS-85's Batch C paragraph corrected; RetailTooltipPresenter's F10 shared-skin remark updated (MapPageController no longer a consumer). Tests: 3 installed-DAT pins (locator/delay/rollover; per-state P0x3B + green frame; the four-skin font sweep), UiButton cascade + UiDatElement P0x3B units, MapHousePanel marker no-clobber + hover-highlight fixture. App suite 5487 passed / 3 skips (5490 total, +11 over baseline); Runtime 1744/1744. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
ef567bfa20
commit
942a02af11
12 changed files with 682 additions and 46 deletions
135
tests/AcDream.App.Tests/UI/Layout/MapNoteLiveDatTests.cs
Normal file
135
tests/AcDream.App.Tests/UI/Layout/MapNoteLiveDatTests.cs
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using AcDream.App.UI.Layout;
|
||||
using DatReaderWriter;
|
||||
using DatReaderWriter.Options;
|
||||
|
||||
namespace AcDream.App.Tests.UI.Layout;
|
||||
|
||||
/// <summary>
|
||||
/// 2026-08-17 morning gate finding 3 installed-DAT pins: the map town-hotspot
|
||||
/// template's OWN authored tooltip locator + rollover-highlight mechanism,
|
||||
/// re-derived after the user's retail screenshot (green marker highlight on
|
||||
/// hover, parchment-banner tooltip in a special font) refuted the earlier
|
||||
/// "template authors no locator of its own" recon. Follows
|
||||
/// <see cref="TooltipLiveDatTests"/>'s <c>[InstalledDatFact]</c> pattern.
|
||||
///
|
||||
/// <para>
|
||||
/// The derivation chain (raw-DAT-dumped + <c>MapHousePanelSlotProbeTests</c>):
|
||||
/// <c>m_pMap</c> (<c>0x100001EC</c>) authors <c>P0x47=0x100001F0</c>/
|
||||
/// <c>P0x48=0x21000026</c> — the note CONSTRUCTION template
|
||||
/// (<c>gmMapUI::AddMapNote @0x004a1bb0</c>'s <c>CreateChildElement</c> args).
|
||||
/// The template's DirectState then authors the note's OWN tooltip popup
|
||||
/// locator <c>P0x47=0x10000398</c>/<c>P0x48=0x21000041</c>, a zero
|
||||
/// per-element tooltip delay (<c>P0x50=0.0</c>), <c>P0x4B</c> TooltipOn, and
|
||||
/// <c>P0x13</c> RolloverEnabled; its <c>Normal</c>/<c>Normal_rollover</c>
|
||||
/// states are PassToChildren descriptors driving the highlight child
|
||||
/// <c>0x100001F1</c> (base <c>0x100002B7</c>@<c>0x21000042</c> — a
|
||||
/// four-piece frame of pure-green <c>0x06004CC9</c>, byte-decoded
|
||||
/// A=FF R=00 G=FF B=00) via per-state <c>P0x3B</c>.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public sealed class MapNoteLiveDatTests
|
||||
{
|
||||
private static string DatDirectory =>
|
||||
System.Environment.GetEnvironmentVariable("ACDREAM_DAT_DIR")
|
||||
?? Path.Combine(
|
||||
System.Environment.GetFolderPath(System.Environment.SpecialFolder.UserProfile),
|
||||
"Documents",
|
||||
"Asheron's Call");
|
||||
|
||||
private const uint TemplateLayoutId = 0x21000026u;
|
||||
private const uint TemplateElementId = 0x100001F0u;
|
||||
private const uint HighlightChildId = 0x100001F1u;
|
||||
private const uint MapNoteSkinRootId = 0x10000398u;
|
||||
private const uint TooltipCatalogLayoutId = 0x21000041u;
|
||||
private const uint TooltipTextChildId = 0x10000396u;
|
||||
private const uint MapNoteFontDid = 0x40000015u;
|
||||
private const uint GenericSkinFontDid = 0x40000002u;
|
||||
private const uint GreenFrameSurfaceId = 0x06004CC9u;
|
||||
|
||||
[InstalledDatFact]
|
||||
public void HotspotTemplate_AuthorsItsOwnPopupLocator_ZeroDelay_AndRollover()
|
||||
{
|
||||
using var dats = new DatCollection(DatDirectory, DatAccessType.Read);
|
||||
|
||||
ElementInfo? template = LayoutImporter.ImportInfos(dats, TemplateLayoutId, TemplateElementId);
|
||||
Assert.NotNull(template);
|
||||
|
||||
// The note's own popup locator — NOT the generic shared skin the
|
||||
// pre-fix code hardcoded.
|
||||
Assert.Equal(MapNoteSkinRootId, template!.TooltipRootElementId);
|
||||
Assert.Equal(TooltipCatalogLayoutId, template.TooltipLayoutDid);
|
||||
Assert.True(template.TooltipEnabled); // P0x4B
|
||||
Assert.True(template.TooltipDelaySeconds.HasValue); // P0x50 authored
|
||||
Assert.Equal(0f, template.TooltipDelaySeconds!.Value); // = 0.0
|
||||
|
||||
// P0x13 RolloverEnabled — drives UiButtonStateMachine's
|
||||
// NormalRollover request on pointer-over.
|
||||
Assert.True(template.TryGetEffectiveBool(0x13u, out bool rollover) && rollover);
|
||||
|
||||
// The two PassToChildren cascade descriptors.
|
||||
Assert.True(template.States.TryGetValue(1u, out UiStateInfo? normal));
|
||||
Assert.True(template.States.TryGetValue(2u, out UiStateInfo? normalRollover));
|
||||
Assert.Equal("Normal", normal!.Name);
|
||||
Assert.Equal("Normal_rollover", normalRollover!.Name);
|
||||
Assert.True(normal.PassToChildren);
|
||||
Assert.True(normalRollover.PassToChildren);
|
||||
}
|
||||
|
||||
[InstalledDatFact]
|
||||
public void HotspotTemplate_HighlightChild_FlipsPerStateInvisible_WithGreenFrameMedia()
|
||||
{
|
||||
using var dats = new DatCollection(DatDirectory, DatAccessType.Read);
|
||||
|
||||
ElementInfo? template = LayoutImporter.ImportInfos(dats, TemplateLayoutId, TemplateElementId);
|
||||
Assert.NotNull(template);
|
||||
|
||||
ElementInfo highlight = Assert.Single(template!.Children, c => c.Id == HighlightChildId);
|
||||
|
||||
// Per-state P0x3B: hidden in Normal, shown in Normal_rollover.
|
||||
Assert.True(highlight.States.TryGetValue(1u, out UiStateInfo? normal));
|
||||
Assert.True(highlight.States.TryGetValue(2u, out UiStateInfo? rollover));
|
||||
Assert.True(normal!.Properties.TryGetValue(0x3Bu, out var normalInvisible));
|
||||
Assert.True(rollover!.Properties.TryGetValue(0x3Bu, out var rolloverInvisible));
|
||||
Assert.Equal(UiPropertyKind.Bool, normalInvisible.Kind);
|
||||
Assert.Equal(UiPropertyKind.Bool, rolloverInvisible.Kind);
|
||||
Assert.True(normalInvisible.BoolValue);
|
||||
Assert.False(rolloverInvisible.BoolValue);
|
||||
|
||||
// The base-chain frame (0x100002B7@0x21000042): four edge pieces,
|
||||
// every one drawing the pure-green line surface 0x06004CC9.
|
||||
Assert.Equal(4, highlight.Children.Count);
|
||||
foreach (ElementInfo edge in highlight.Children)
|
||||
{
|
||||
Assert.True(edge.StateMedia.TryGetValue("", out var media));
|
||||
Assert.Equal(GreenFrameSurfaceId, media.File);
|
||||
}
|
||||
}
|
||||
|
||||
[InstalledDatFact]
|
||||
public void MapNoteSkin_TextChild_FontsTheSpecialFont_UnlikeTheGenericSkins()
|
||||
{
|
||||
using var dats = new DatCollection(DatDirectory, DatAccessType.Read);
|
||||
|
||||
// The four popup skins in 0x21000041 share structure and the SAME
|
||||
// incorporated text child id — but each skin's child-table merge
|
||||
// gives 0x10000396 its own effective font, and the map-note skin
|
||||
// 0x10000398 is the odd one out (0x40000015 vs 0x40000002) — the
|
||||
// "special/blackletter-style font" of the user's retail screenshot.
|
||||
var fonts = new Dictionary<uint, uint>();
|
||||
foreach (uint skinId in new[] { 0x10000487u, 0x10000395u, 0x10000397u, MapNoteSkinRootId })
|
||||
{
|
||||
ElementInfo? skin = LayoutImporter.ImportInfos(dats, TooltipCatalogLayoutId, skinId);
|
||||
Assert.NotNull(skin);
|
||||
ElementInfo text = Assert.Single(skin!.Children, c => c.Id == TooltipTextChildId);
|
||||
fonts[skinId] = text.FontDid;
|
||||
}
|
||||
|
||||
Assert.Equal(MapNoteFontDid, fonts[MapNoteSkinRootId]);
|
||||
Assert.Equal(GenericSkinFontDid, fonts[0x10000487u]);
|
||||
Assert.Equal(GenericSkinFontDid, fonts[0x10000395u]);
|
||||
Assert.Equal(GenericSkinFontDid, fonts[0x10000397u]);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue