fix #385: Options dropdowns — white centered text + size-to-content popup
User gate report (Campaign OP happy-testing round, 2026-08-13): every Config-tab dropdown drew its text gold + left-aligned and its popup a fixed 6 rows regardless of item count. All three were unmeasured styling divergences — the authored data (new probe menuprobe3, live DAT) says: - button label child 0x10000355: fontColor white, hJustify=Center - row template 0x1000035A: fontColor white, hJustify=Center - popup ListBox 0x10000358: edge-docked L=T=R=B=1, the authored condition arming retail UIElement_Menu::RecalculatePopupSize @0x0046caf0 — popup resizes to the ListBox's summed content height, uncapped (0x0046e5f4..0046e66c via ResizeScrollableArea's 0x32 broadcast) UiMenu gains three opt-in properties (ButtonTextCentered, ItemTextCentered, PopupSizeToContent) plus retail Open @0x0046cc42's empty-list gate; chat + vendor keep the class defaults, so their shipped behavior is untouched. ConfigOptionsPageController.ApplyMenuChrome wires all four corrections for the 8 Config menus with the probe citation. The same probe found vendor's authored popup ListBox is ALSO docked while our vendor dropdown ships G5's fixed 6-row window — filed as #386 + register row AD-88 (UNCLEAR: the G5 retail screenshot and the decomp mechanism conflict) instead of silently reworking a user-gated surface. The "resolution change resizes the window" observation from the same report is #374's designed windowed-mode behavior (display-mode switching is #376/#377) — no change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
028920420d
commit
a9b6435f55
8 changed files with 422 additions and 9 deletions
|
|
@ -293,6 +293,149 @@ public sealed class OptionsPanelLiveMountProbeTests
|
|||
return null;
|
||||
}
|
||||
|
||||
/// <summary>User gate report 2026-08-13 (Campaign OP happy-testing round):
|
||||
/// every Options-panel dropdown draws its text yellow + left-aligned (user:
|
||||
/// retail is white + centered) and its popup a fixed 6 rows tall (user:
|
||||
/// retail conforms to the item count). Retail mechanism, decomp-verified:
|
||||
/// <c>UIElement_Menu::RecalculatePopupSize @0x0046caf0</c> resizes the popup
|
||||
/// to the ListBox's content extent (<c>m_iScrollableHeight</c> = SUM of laid
|
||||
/// out row heights, uncapped — <c>0x0046e5f4..0046e66c</c>) + the authored
|
||||
/// popup-minus-ListBox border, but ONLY when the authored ListBox is docked
|
||||
/// on both edges (<c>m_leftEdge==1 && m_rightEdge==1</c> /
|
||||
/// <c>m_topEdge==1 && m_bottomEdge==1</c>); the caption is an
|
||||
/// authored <c>UIElement_Text</c> child named by menu attr 8
|
||||
/// (<c>NewSelection @0x0046cd60</c>) whose color/justify come from the
|
||||
/// LayoutDesc, and <c>Open @0x0046cc30</c> centers the POPUP over the
|
||||
/// button when bool attr 3 is authored. This probe measures all of that
|
||||
/// authored data for the Config option-menu chain (catalog 0x21000043,
|
||||
/// base 0x10000353) with vendor's dropdown (0x1000034F chain — visibly a
|
||||
/// FIXED 6-row + scrollbar popup in retail, user-gated during the vendor
|
||||
/// campaign) as the contrast control.</summary>
|
||||
[Fact]
|
||||
public void ProbeMenuPopupSizingAndTextStyle()
|
||||
{
|
||||
if (Environment.GetEnvironmentVariable("ACDREAM_PROBE_LIVE_MOUNT") != "1")
|
||||
return;
|
||||
|
||||
var datDir = Environment.GetEnvironmentVariable("ACDREAM_DAT_DIR")
|
||||
?? Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
|
||||
"Documents",
|
||||
"Asheron's Call");
|
||||
using var dats = new DatCollection(datDir, DatAccessType.Read);
|
||||
|
||||
Console.WriteLine("[menuprobe3] === Config option-menu base 0x21000043/0x10000353 ===");
|
||||
ElementInfo? menuBase = LayoutImporter.ImportInfos(dats, 0x21000043u, 0x10000353u);
|
||||
if (menuBase is null)
|
||||
{
|
||||
Console.WriteLine("[menuprobe3] 0x10000353 FAILED to import");
|
||||
}
|
||||
else
|
||||
{
|
||||
DumpMenuAttrs3(menuBase, "base 0x10000353", new[] { 2u, 3u, 5u, 6u, 7u, 8u, 9u });
|
||||
foreach (ElementInfo c in menuBase.Children)
|
||||
DumpTextStyle(c, $"base child 0x{c.Id:X8}");
|
||||
}
|
||||
|
||||
Console.WriteLine("[menuprobe3] === Config leaf 0x2100002B/0x10000224 (attrs 3+8) ===");
|
||||
ElementInfo? configRow = LayoutImporter.ImportInfos(dats, 0x2100002Bu, 0x10000222u);
|
||||
if (configRow is not null && FindInfo(configRow, 0x10000224u) is { } leaf)
|
||||
{
|
||||
DumpMenuAttrs3(leaf, "leaf 0x10000224", new[] { 3u, 8u });
|
||||
foreach (ElementInfo c in leaf.Children)
|
||||
DumpTextStyle(c, $"leaf child 0x{c.Id:X8}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("[menuprobe3] leaf 0x10000224 MISSING");
|
||||
}
|
||||
|
||||
Console.WriteLine("[menuprobe3] === Config popup root/ListBox/row template ===");
|
||||
DumpDockAndSize(dats, 0x21000043u, 0x10000357u, "Config popup root");
|
||||
DumpDockAndSize(dats, 0x21000043u, 0x10000358u, "Config popup ListBox");
|
||||
ElementInfo? rowTemplate = LayoutImporter.ImportInfos(dats, 0x21000043u, 0x1000035Au);
|
||||
if (rowTemplate is not null)
|
||||
{
|
||||
DumpTextStyle(rowTemplate, "Config row template 0x1000035A");
|
||||
foreach (ElementInfo c in rowTemplate.Children)
|
||||
DumpTextStyle(c, $"row-template child 0x{c.Id:X8}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("[menuprobe3] row template 0x1000035A FAILED to import");
|
||||
}
|
||||
|
||||
Console.WriteLine("[menuprobe3] === CONTROL: vendor chain (fixed 6-row + scrollbar in retail) ===");
|
||||
DumpDockAndSize(dats, 0x21000043u, 0x1000034Fu, "Vendor popup root");
|
||||
DumpDockAndSize(dats, 0x21000043u, 0x10000350u, "Vendor popup ListBox");
|
||||
ElementInfo? vendorBase = LayoutImporter.ImportInfos(dats, 0x21000043u, 0x1000034Bu);
|
||||
if (vendorBase is not null)
|
||||
{
|
||||
DumpMenuAttrs3(vendorBase, "vendor base 0x1000034B", new[] { 2u, 3u, 5u, 6u, 7u, 8u, 9u });
|
||||
foreach (ElementInfo c in vendorBase.Children)
|
||||
DumpTextStyle(c, $"vendor base child 0x{c.Id:X8}");
|
||||
}
|
||||
ElementInfo? vendorRowTemplate = LayoutImporter.ImportInfos(dats, 0x21000043u, 0x10000352u);
|
||||
if (vendorRowTemplate is not null)
|
||||
{
|
||||
DumpTextStyle(vendorRowTemplate, "Vendor row template 0x10000352");
|
||||
foreach (ElementInfo c in vendorRowTemplate.Children)
|
||||
DumpTextStyle(c, $"vendor row-template child 0x{c.Id:X8}");
|
||||
}
|
||||
}
|
||||
|
||||
private static void DumpMenuAttrs3(ElementInfo el, string label, uint[] attrs)
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"[menuprobe3] {label}: type=0x{el.Type:X8} ({el.X},{el.Y} {el.Width}x{el.Height}) "
|
||||
+ $"edges L={el.Left} T={el.Top} R={el.Right} B={el.Bottom} "
|
||||
+ $"children=[{string.Join(",", el.Children.ConvertAll(c => $"0x{c.Id:X8}"))}]");
|
||||
foreach (uint attr in attrs)
|
||||
{
|
||||
if (el.TryGetEffectiveProperty(attr, out UiPropertyValue value))
|
||||
{
|
||||
string rendered = value.Kind switch
|
||||
{
|
||||
UiPropertyKind.Bool => value.BoolValue.ToString(),
|
||||
UiPropertyKind.DataId or UiPropertyKind.Enum => $"0x{value.UnsignedValue:X8}",
|
||||
UiPropertyKind.Integer => value.IntegerValue.ToString(),
|
||||
_ => value.Kind.ToString(),
|
||||
};
|
||||
Console.WriteLine($"[menuprobe3] attr[{attr}] kind={value.Kind} value={rendered}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"[menuprobe3] attr[{attr}] MISSING");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void DumpDockAndSize(DatCollection dats, uint layoutId, uint elementId, string label)
|
||||
{
|
||||
ElementInfo? el = LayoutImporter.ImportInfos(dats, layoutId, elementId);
|
||||
if (el is null)
|
||||
{
|
||||
Console.WriteLine($"[menuprobe3] {label} 0x{elementId:X8}: FAILED to import");
|
||||
return;
|
||||
}
|
||||
Console.WriteLine(
|
||||
$"[menuprobe3] {label} 0x{elementId:X8}: type=0x{el.Type:X8} "
|
||||
+ $"({el.X},{el.Y} {el.Width}x{el.Height}) "
|
||||
+ $"edges L={el.Left} T={el.Top} R={el.Right} B={el.Bottom} "
|
||||
+ $"children=[{string.Join(",", el.Children.ConvertAll(c => $"0x{c.Id:X8}"))}]");
|
||||
}
|
||||
|
||||
private static void DumpTextStyle(ElementInfo el, string label)
|
||||
{
|
||||
string color = el.FontColor is { } fc
|
||||
? $"({fc.X:F2},{fc.Y:F2},{fc.Z:F2},{fc.W:F2})"
|
||||
: "null(default-white)";
|
||||
Console.WriteLine(
|
||||
$"[menuprobe3] {label}: type=0x{el.Type:X8} ({el.X},{el.Y} {el.Width}x{el.Height}) "
|
||||
+ $"hJustify={el.HJustify} vJustify={el.VJustify} fontColor={color} fontDid=0x{el.FontDid:X8} "
|
||||
+ $"edges L={el.Left} T={el.Top} R={el.Right} B={el.Bottom}");
|
||||
}
|
||||
|
||||
/// <summary>#380 (gate 4): exercises the PRODUCTION
|
||||
/// <see cref="ChatOptionsDatCaptions.TryRead"/> mechanism against the real
|
||||
/// DAT and asserts the two resolved captions match the user's own
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue