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
|
|
@ -589,6 +589,99 @@ public class UiButtonTests
|
|||
return CreateButton(info);
|
||||
}
|
||||
|
||||
// ── 2026-08-17 morning gate finding 3: the PassToChildren cascade +
|
||||
// per-state P0x3B (Invisible) honor. Fixture mirrors the live map
|
||||
// town-hotspot template (0x100001F0 in 0x21000026, raw-DAT-dumped):
|
||||
// a media-less RolloverEnabled button whose Normal/Normal_rollover
|
||||
// descriptors are PassToChildren and whose highlight child authors
|
||||
// Normal={0x3B:true} / Normal_rollover={0x3B:false}. ──
|
||||
|
||||
private static ElementInfo MapNoteShapedInfo(bool passToChildren = true)
|
||||
{
|
||||
var info = new ElementInfo { Type = 1, Width = 10, Height = 10 };
|
||||
AddBoolProperty(info, 0x13u, true); // P0x13 RolloverEnabled
|
||||
info.States[UiButtonStateMachine.Normal] = new UiStateInfo
|
||||
{
|
||||
Id = UiButtonStateMachine.Normal,
|
||||
Name = "Normal",
|
||||
PassToChildren = passToChildren,
|
||||
};
|
||||
info.States[UiButtonStateMachine.NormalRollover] = new UiStateInfo
|
||||
{
|
||||
Id = UiButtonStateMachine.NormalRollover,
|
||||
Name = "Normal_rollover",
|
||||
PassToChildren = passToChildren,
|
||||
};
|
||||
return info;
|
||||
}
|
||||
|
||||
private static UiDatElement HighlightChild()
|
||||
{
|
||||
var info = new ElementInfo { Type = 3, Width = 10, Height = 10 };
|
||||
var normal = new UiStateInfo { Id = 1, Name = "Normal" };
|
||||
normal.Properties.Values[0x3Bu] = new UiPropertyValue
|
||||
{ Kind = UiPropertyKind.Bool, BoolValue = true };
|
||||
var rollover = new UiStateInfo { Id = 2, Name = "Normal_rollover" };
|
||||
rollover.Properties.Values[0x3Bu] = new UiPropertyValue
|
||||
{ Kind = UiPropertyKind.Bool, BoolValue = false };
|
||||
info.States[1] = normal;
|
||||
info.States[2] = rollover;
|
||||
return new UiDatElement(info, NoTex);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PassToChildrenStates_CascadeToStatefulChildren_DrivingPerStateInvisible()
|
||||
{
|
||||
// Retail UIElement::SetState @0x00464E70's PassToChildren cascade +
|
||||
// OnSetAttribute @0x00462d80 case 8 (P0x3B -> SetVisible(value==0)):
|
||||
// the map marker's green rollover frame is hidden at rest and shown
|
||||
// only while the pointer is over the button.
|
||||
var b = CreateButton(MapNoteShapedInfo());
|
||||
UiDatElement kid = HighlightChild();
|
||||
b.AddChild(kid);
|
||||
Assert.True(kid.Visible); // pre-cascade construction default
|
||||
|
||||
// The initial Normal application (retail's own initial UpdateState_)
|
||||
// hides the highlight.
|
||||
Assert.True(b.TrySetRetailState(UiButtonStateMachine.Normal));
|
||||
Assert.False(kid.Visible);
|
||||
|
||||
b.OnEvent(new UiEvent(0, b, UiEventType.HoverEnter));
|
||||
Assert.True(kid.Visible); // Normal_rollover: P0x3B=false -> shown
|
||||
|
||||
b.OnEvent(new UiEvent(0, b, UiEventType.HoverLeave));
|
||||
Assert.False(kid.Visible); // back to Normal: P0x3B=true -> hidden
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StatesWithoutPassToChildren_DoNotCascade()
|
||||
{
|
||||
var b = CreateButton(MapNoteShapedInfo(passToChildren: false));
|
||||
UiDatElement kid = HighlightChild();
|
||||
b.AddChild(kid);
|
||||
|
||||
b.TrySetRetailState(UiButtonStateMachine.Normal);
|
||||
b.OnEvent(new UiEvent(0, b, UiEventType.HoverEnter));
|
||||
|
||||
Assert.True(kid.Visible); // never cascaded, never hidden/shown
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UiDatElement_TrySetRetailState_HonorsPerStateInvisible()
|
||||
{
|
||||
// The child half in isolation — the state-transition application of
|
||||
// dat property 0x3B, distinct from ElementReader.Invisible's
|
||||
// construction-time effective read (GF-13/AP-230).
|
||||
UiDatElement kid = HighlightChild();
|
||||
Assert.True(kid.Visible);
|
||||
|
||||
Assert.True(kid.TrySetRetailState(1u));
|
||||
Assert.False(kid.Visible);
|
||||
|
||||
Assert.True(kid.TrySetRetailState(2u));
|
||||
Assert.True(kid.Visible);
|
||||
}
|
||||
|
||||
private static ElementInfo ButtonInfo(params string[] states)
|
||||
{
|
||||
var info = new ElementInfo { Type = 1, Width = 20, Height = 20 };
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue