diff --git a/src/AcDream.App/UI/Layout/DatWidgetFactory.cs b/src/AcDream.App/UI/Layout/DatWidgetFactory.cs index 8d6391c5..01804e44 100644 --- a/src/AcDream.App/UI/Layout/DatWidgetFactory.cs +++ b/src/AcDream.App/UI/Layout/DatWidgetFactory.cs @@ -122,6 +122,7 @@ public static class DatWidgetFactory 11 => BuildScrollbar(info, resolve), // UIElement_Scrollbar (reg :124137) 12 => BuildText(info, resolve, elementFont, stringResolve), // UIElement_Text 0x13 => new UiDialogRoot(), // ConfirmationDialog + 0x19 => new UiDialogRoot(), // WaitDialog (catalog root 0x31 — OP8 #396) 0x10000031u => new UiItemList(resolve), // UIElement_ItemList — toolbar/inventory/paperdoll slots 0x10000035u => BuildCheckbox( info, resolve, elementFont, fontResolve, stringResolve), // UIOption_Checkbox diff --git a/src/AcDream.App/UI/RetailUiRuntime.cs b/src/AcDream.App/UI/RetailUiRuntime.cs index b666940e..8c21f9c0 100644 --- a/src/AcDream.App/UI/RetailUiRuntime.cs +++ b/src/AcDream.App/UI/RetailUiRuntime.cs @@ -2651,7 +2651,23 @@ public sealed class RetailUiRuntime : IDisposable // convention DatWidgetFactory/IndicatorDetailText already // unescape for other DAT-authored strings. text = text.Replace("\\n", "\n", StringComparison.Ordinal); - return DialogFactory.MakeWait(text, queueKey: 0x10000001u); + try + { + return DialogFactory.MakeWait(text, queueKey: 0x10000001u); + } + catch (Exception failure) + { + // Retail's OpenMapWarnDialog returns 0 on failure and + // InitiateBinding then refuses to arm capture — a + // malformed catalog must land on that contracted path, + // not escape UiButton.OnClick into the render loop + // (the 2026-08-14 live crash: wait root type 0x19 was + // unmapped and the view ctor threw). + Console.WriteLine( + "[UI] keyboard config: capture-instruction dialog failed to " + + $"build — capture refused. {failure.Message}"); + return 0u; + } }, CloseCaptureInstructions: context => DialogFactory?.CloseDialog(context)), diff --git a/tests/AcDream.App.Tests/UI/Layout/DatWidgetFactoryTests.cs b/tests/AcDream.App.Tests/UI/Layout/DatWidgetFactoryTests.cs index bf560ce3..c06ae755 100644 --- a/tests/AcDream.App.Tests/UI/Layout/DatWidgetFactoryTests.cs +++ b/tests/AcDream.App.Tests/UI/Layout/DatWidgetFactoryTests.cs @@ -27,6 +27,20 @@ public class DatWidgetFactoryTests Assert.IsType(e); } + // ── Dialog roots: retail class types 0x13/0x19 → UiDialogRoot ──────────── + + [Theory] + [InlineData(0x13u)] // ConfirmationDialog (catalog root 0x15) + [InlineData(0x19u)] // WaitDialog (catalog root 0x31 — OP8 #396's live + // crash: unmapped type built a plain UiDatElement and + // RetailWaitDialogView's ctor threw out of OnClick) + public void DialogRootTypes_MakeUiDialogRoot(uint type) + { + var e = DatWidgetFactory.Create( + new ElementInfo { Type = type, Width = 800, Height = 600 }, NoTex, null); + Assert.IsType(e); + } + // ── Test 2: Unknown type → UiDatElement fallback ───────────────────────── [Fact] diff --git a/tests/AcDream.App.Tests/UI/Layout/KeyboardConfigLiveMountProbeTests.cs b/tests/AcDream.App.Tests/UI/Layout/KeyboardConfigLiveMountProbeTests.cs index 2f5cf1b8..c5ec5d80 100644 --- a/tests/AcDream.App.Tests/UI/Layout/KeyboardConfigLiveMountProbeTests.cs +++ b/tests/AcDream.App.Tests/UI/Layout/KeyboardConfigLiveMountProbeTests.cs @@ -276,7 +276,19 @@ public sealed class KeyboardConfigLiveMountProbeTests if (waitInfo is null) Console.WriteLine("[kbwait] wait root 0x31 -> IMPORT MISSING from 0x2100003C"); else + { DumpFontDids(waitInfo, 0); + // The 2026-08-14 live crash pin: the wait root (retail class + // type 0x19) must build as a UiDialogRoot with the shared + // popup/message pair — the unit suite's fixture only carries + // the confirmation subtree, so the installed-DAT shape is + // asserted here. + ImportedLayout waitBuilt = LayoutImporter.Build( + waitInfo, _ => (0u, 0, 0), null, null, strings.Resolve); + Assert.IsType(waitBuilt.Root); + Assert.NotNull(waitBuilt.FindElement(0x3Du)); + Assert.IsType(waitBuilt.FindElement(0x3Eu)); + } } }