using System.IO; using System.Linq; using AcDream.App.UI; using AcDream.App.UI.Layout; using AcDream.Content; using DatReaderWriter; using DatReaderWriter.DBObjs; using DatReaderWriter.Options; namespace AcDream.App.Tests.UI.Layout; /// /// Campaign CC gate round 1 Batch C, Commit 2: client-wide blast-radius /// sweep for the UiText/UiField media-bearing-child /// un-consume fix ('s new UiText or /// UiField carve-out). Walks EVERY installed LayoutDesc /// (DatCollection.GetAllIdsOfType<LayoutDesc>) and reports /// every Type-12 (UIElement_Text) element that does NOT author /// PassToChildren on any state (the pre-fix "consumes everything" shape) /// but HAS at least one direct child carrying its own state media — the /// exact set the fix now builds instead of silently dropping. Logged via /// Console.WriteLine so the full enumeration is visible in test /// output for the commit message; the assertions pin only landmark counts/ /// elements (not a brittle exact global total) so the gate survives a /// future DAT revision without going red on an unrelated content change. /// public sealed class LayoutImporterMediaBearingChildSweepTests { private static string DatDirectory => System.Environment.GetEnvironmentVariable("ACDREAM_DAT_DIR") ?? Path.Combine( System.Environment.GetFolderPath(System.Environment.SpecialFolder.UserProfile), "Documents", "Asheron's Call"); private readonly record struct Finding(uint LayoutId, uint ElementId, uint[] MediaBearingChildIds); [InstalledDatFact] public void MediaBearingChildSweep_EnumeratesEveryAffectedType12Element() { using var dats = new DatCollection(DatDirectory, DatAccessType.Read); var findings = new List(); foreach (uint layoutId in dats.GetAllIdsOfType().OrderBy(x => x)) { ElementInfo? tree = LayoutImporter.ImportInfos(dats, layoutId); if (tree is null) continue; Walk(layoutId, tree, findings); } Console.WriteLine($"[SWEEP] {findings.Count} affected Type-12 elements across " + $"{findings.Select(f => f.LayoutId).Distinct().Count()} layouts."); foreach (Finding f in findings.OrderBy(f => f.LayoutId).ThenBy(f => f.ElementId)) { Console.WriteLine( $"[SWEEP] layout=0x{f.LayoutId:X8} element=0x{f.ElementId:X8} " + $"mediaBearingChildren=[{string.Join(",", f.MediaBearingChildIds.Select(id => $"0x{id:X8}"))}]"); } // Landmarks the investigation specifically flagged for the user's // visual check — assert they are genuinely in the affected set // (not asserting a brittle exact global count). Assert.Contains(findings, f => f.LayoutId == 0x21000005u && f.ElementId == 0x1000059Au); Assert.Contains(findings, f => f.LayoutId == 0x2100006Fu && f.ElementId == 0x10000011u); // The three chargen description boxes this campaign already fixed. Assert.Contains(findings, f => f.ElementId == 0x100003E0u); // Profession Assert.Contains(findings, f => f.ElementId == 0x10000409u); // Town Assert.Contains(findings, f => f.ElementId == 0x10000404u); // Summary how-to Assert.True(findings.Count > 0, "the sweep must find at least the known chargen landmarks."); } /// /// Regression pin (Commit 2): the two landmarks the investigation /// flagged for the user's own visual check actually BUILD their /// media-bearing children as real widgets now, on a NON-chargen /// layout — proving the fix is not accidentally chargen-only. FLAG: /// this is a structural/widget-level pin only; the user's own visual /// check of chat + the main game UI is still owed (the lead schedules /// it) — a passing test here does not stand in for that. /// [InstalledDatFact] public void MainGameUiAndChatInput_MediaBearingChildrenNowBuildAsRealWidgets() { using var dats = new DatCollection(DatDirectory, DatAccessType.Read); // MAIN GAME UI (0x21000005/0x1000059A): the same eight gold-frame // pieces the chargen boxes carry. AssertChildrenBuild( dats, layoutId: 0x21000005u, elementId: 0x1000059Au, expectedChildIds: [ 0x100002DEu, 0x100002DFu, 0x100002E0u, 0x100002E1u, 0x100000E8u, 0x100002E2u, 0x100002E3u, 0x100000EAu, ]); // CHAT INPUT (0x2100006F/0x10000011): a single media-bearing child. AssertChildrenBuild( dats, layoutId: 0x2100006Fu, elementId: 0x10000011u, expectedChildIds: [0x1000048Cu]); } private static void AssertChildrenBuild( IDatReaderWriter dats, uint layoutId, uint elementId, uint[] expectedChildIds) { ElementInfo? tree = LayoutImporter.ImportInfos(dats, layoutId); Assert.NotNull(tree); ElementInfo? target = FindInfo(tree!, elementId); Assert.NotNull(target); UiElement built = LayoutImporter.Build( target!, _ => (0u, 0, 0), null).Root; Assert.IsType(built); foreach (uint childId in expectedChildIds) { Assert.NotNull(UiElement.FindDescendant(built, childId)); } } private static ElementInfo? FindInfo(ElementInfo node, uint id) { if (node.Id == id) return node; foreach (ElementInfo child in node.Children) { ElementInfo? found = FindInfo(child, id); if (found is not null) return found; } return null; } private static void Walk(uint layoutId, ElementInfo node, List findings) { if (node.Type == 12u) { bool passToChildren = node.States.Values.Any(static s => s.PassToChildren); if (!passToChildren) { uint[] mediaBearingChildren = node.Children .Where(static c => c.StateMedia.Count > 0) .Select(static c => c.Id) .ToArray(); if (mediaBearingChildren.Length > 0) findings.Add(new Finding(layoutId, node.Id, mediaBearingChildren)); } } foreach (ElementInfo child in node.Children) Walk(layoutId, child, findings); } }