using System.IO; using AcDream.App.UI; using AcDream.App.UI.Layout; using DatReaderWriter; using DatReaderWriter.Options; namespace AcDream.App.Tests.UI.Layout; /// /// 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. /// 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}"); } } /// #372 minor half: the 13 ID_ChatOption_TextFilter_* labels fail /// to resolve in table 0x23000003 — sweep the plausible tables and key /// spellings against the live DAT to find their real home. [Fact] public void ProbeFilterLabelHome() { 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); var strings = new DatStringResolver(dats); string[] keys = { "ID_ChatOption_TextFilter_Combat", "ID_ChatOption_TextFilter_Gameplay", "ID_ChatOptions_TextFilter_Combat", // plural-Options variant "ID_Chat_TextFilter_Combat", "ID_TextFilter_Combat", "TextFilter_Combat", }; for (uint table = 0x23000001u; table <= 0x2300000Au; table++) { foreach (string key in keys) { string? hit = strings.Resolve(table, DatStringResolver.ComputeHash(key)); if (hit is not null) Console.WriteLine($"[probe] table 0x{table:X8} key '{key}' -> '{hit}'"); } } // Also: does the KNOWN-good Character-tab key family resolve where // documented, as a control? Console.WriteLine( "[probe] control ID_PlayerOption_AutoTarget in 0x23000003 -> " + $"'{strings.Resolve(0x23000003u, DatStringResolver.ComputeHash("ID_PlayerOption_AutoTarget"))}'"); // Exhaustive: sweep EVERY string table in the local dat for the key. uint targetHash = DatStringResolver.ComputeHash("ID_ChatOption_TextFilter_Combat"); foreach (uint tableId in dats.Local.GetAllIdsOfType()) { string? hit = strings.Resolve(tableId, targetHash); if (hit is not null) Console.WriteLine($"[probe] EXHAUSTIVE hit: table 0x{tableId:X8} -> '{hit}'"); } Console.WriteLine("[probe] exhaustive sweep complete"); } 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); } }