acdream/tests/AcDream.App.Tests/UI/Layout/OptionsPanelLiveMountProbeTests.cs
Erik c3ed32fb5a docs+test: #372 — Options tabs blank + Gameplay buttons dead at first connected gate
The user's first Campaign OP connected gate (ACDREAM_RETAIL_UI=1, live
ACE) failed: Character/Chat/Config tabs render blank, only Exit Game of
the seven Gameplay buttons visibly acted. A live-DAT mount probe
(committed, env-gated) proves the panel BUILDS completely — root
UiTabPanel with a 4-entry tab table, all four page slots, all three page
ListBoxes as UiTemplateListBox with row templates, all seven buttons as
UiButton — and the three page controllers' Bind() run at mount. So the
defect is in the live render/input path the whole fixture suite never
exercises (mount -> ActivateTabs -> tab-click SwitchTo -> row draw /
button hit-test): the OP2-blast structural-false-negative class. Blocks
the OP3-OP6 gates; needs a dedicated debug slice + a gate-representative
test, not a guess.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 09:48:58 +02:00

99 lines
4.4 KiB
C#

using System.IO;
using AcDream.App.UI;
using AcDream.App.UI.Layout;
using DatReaderWriter;
using DatReaderWriter.Options;
namespace AcDream.App.Tests.UI.Layout;
/// <summary>
/// TEMPORARY gate-failure probe (Campaign OP connected gate, 2026-08-11):
/// the user's first connected gate found Character/Chat/Config tabs BLANK
/// and six of seven Gameplay buttons dead, while every fixture-driven
/// conformance test passes — the structural-false-negative class the OP2
/// blast review named. This probe runs the PRODUCTION mount path (live
/// DATs, the host-slot import the composition uses) and dumps what each
/// page controller actually resolves. Env-gated like the fixture
/// generator so CI/dat-less runs skip it.
/// </summary>
public sealed class OptionsPanelLiveMountProbeTests
{
[Fact]
public void ProbeLiveMountShapes()
{
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);
// The production mount: host 0x2100006E slot 0x1000018D (the shape
// OptionsPanelController documents; RetailUiRuntime.MountOptionsPanel
// resolves the SAME way — keep in sync with it).
ElementInfo root = LayoutImporter.ImportInfos(dats, 0x2100006Eu, 0x1000018Du);
ImportedLayout layout = LayoutImporter.Build(root, _ => (0u, 0, 0), null);
Console.WriteLine($"[probe] root id=0x{root.Id:X8} type={root.Type} children={root.Children.Count}");
DumpTree(root, 0, maxDepth: 3);
// What does the flat index hold for the load-bearing ids?
foreach (uint id in new[]
{
0x10000208u, // tab control
0x10000212u, 0x10000211u, 0x1000050Cu, 0x10000213u, // page slots
0x100001FAu, // Character ListBox
0x1000050Du, // Chat ListBox
0x10000200u, // Config ListBox
0x10000203u, 0x10000617u, 0x100005CCu, // gameplay buttons: exit-char-sel, exit game, mouse turning
0x10000206u, 0x10000207u, // UA / RA
})
{
UiElement? el = layout.FindElement(id);
Console.WriteLine($"[probe] flat 0x{id:X8} -> {(el is null ? "MISSING" : el.GetType().Name)}");
}
// The tab panel + its authored table, as production sees it.
if (layout.FindElement(0x10000208u) is UiTabPanel tabs)
{
Console.WriteLine($"[probe] tab table entries={tabs.Tabs.Count}");
foreach (UiTabTableEntry t in tabs.Tabs)
Console.WriteLine($"[probe] button=0x{t.ButtonElementId:X8} page=0x{t.PageElementId:X8} default={t.IsDefault}");
}
// Scoped lookups per page slot — what each page controller's Bind does.
foreach ((uint slot, uint listBox, string name) in new[]
{
(0x10000211u, 0x100001FAu, "Character"),
(0x1000050Cu, 0x1000050Du, "Chat"),
(0x10000213u, 0x10000200u, "Config"),
})
{
UiElement? slotEl = layout.FindElement(slot);
if (slotEl is null)
{
Console.WriteLine($"[probe] {name}: SLOT 0x{slot:X8} MISSING from flat index");
continue;
}
UiElement? scoped = UiElement.FindDescendant(slotEl, listBox);
Console.WriteLine(
$"[probe] {name}: slot=0x{slot:X8}({slotEl.GetType().Name}, children={slotEl.Children.Count}) "
+ $"scoped-listbox 0x{listBox:X8} -> {(scoped is null ? "MISSING" : scoped.GetType().Name)}");
if (scoped is UiTemplateListBox tlb)
Console.WriteLine($"[probe] templates={tlb.Templates.Count} scrollbarId=0x{tlb.ScrollbarElementId:X8}");
}
}
private static void DumpTree(ElementInfo node, int depth, int maxDepth)
{
if (depth > maxDepth) return;
Console.WriteLine(
$"[probe] {new string(' ', depth * 2)}0x{node.Id:X8} T={node.Type} "
+ $"kids={node.Children.Count} tabTable={node.TabTable.Count} tmpl={node.TemplateList.Count}");
foreach (ElementInfo child in node.Children)
DumpTree(child, depth + 1, maxDepth);
}
}