using System.IO; using System.Linq; using System.Numerics; using AcDream.App.UI; using AcDream.App.UI.Layout; using AcDream.Content; using DatReaderWriter; using DatReaderWriter.Options; namespace AcDream.App.Tests.UI.Layout; /// /// Installed-retail-DAT acceptance gate for Campaign CC slice CC4. Opt in /// with ACDREAM_PROBE_LIVE_MOUNT=1; ACDREAM_DAT_DIR can /// override the ordinary Documents/Asheron's Call location. Mirrors /// 's pattern: sweeps the /// authored master-shell and page ids the campaign plan and CC4's own /// decomp research cite, pinning them against the real installed layout. /// public sealed class CharacterCreationLiveDatTests { private static string DatDirectory => Environment.GetEnvironmentVariable("ACDREAM_DAT_DIR") ?? Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Documents", "Asheron's Call"); [InstalledDatFact] public void EnumTable5_ResolvesTheMasterShellRootAndChildren() { using var dats = new DatCollection(DatDirectory, DatAccessType.Read); uint layoutId = RetailDataIdResolver.Resolve( dats, CharacterCreationUiController.RootEnum, 5u); Assert.NotEqual(0u, layoutId); Console.WriteLine( $"[CC4-DAT] category=5 enum=0x10000039 -> DID=0x{layoutId:X8}"); ImportedLayout screen = BuildSelected( dats, layoutId, CharacterCreationUiController.RootElementId); Assert.Equal( CharacterCreationUiController.RootElementId, screen.Root.DatElementId); // CC4 re-review R5: the fixed-canvas arbiter THROWS if two concurrent // screens declare different sizes, and char-management's root is // DAT-pinned at 800x600 (CharacterManagementLiveDatTests). Chargen // declares on top of it on the exact user-gate path, so its authored // extent must be pinned too — an unequal extent is now a crash at // Open(), not a cosmetic drift. Observe, don't infer (C4 closeout). ElementInfo rootInfo = Assert.IsType( LayoutImporter.ImportInfos( dats, layoutId, CharacterCreationUiController.RootElementId)); Assert.Equal(800f, rootInfo.Width); Assert.Equal(600f, rootInfo.Height); Assert.IsAssignableFrom( screen.FindElement(CharacterCreationUiController.ProgressBarElementId)); AssertButton(screen, CharacterCreationUiController.BackElementId); AssertButton(screen, CharacterCreationUiController.NextElementId); AssertButton(screen, CharacterCreationUiController.FinishElementId); AssertButton(screen, CharacterCreationUiController.HelpElementId); AssertButton(screen, CharacterCreationUiController.ExitElementId); AssertButton(screen, CharacterCreationUiController.RandomElementId); Assert.IsAssignableFrom( screen.FindElement(CharacterCreationUiController.MasterPageElementId)); foreach (uint pageId in new[] { CharacterCreationUiController.HeritagePageElementId, CharacterCreationUiController.ProfessionPageElementId, CharacterCreationUiController.SkillsPageElementId, CharacterCreationUiController.AppearancePageElementId, CharacterCreationUiController.TownPageElementId, CharacterCreationUiController.SummaryPageElementId, }) { Assert.IsAssignableFrom(screen.FindElement(pageId)); } AssertButton(screen, CharacterCreationUiController.HeritageTabElementId); AssertButton(screen, CharacterCreationUiController.ProfessionTabElementId); AssertButton(screen, CharacterCreationUiController.SkillsTabElementId); AssertButton(screen, CharacterCreationUiController.AppearanceTabElementId); AssertButton(screen, CharacterCreationUiController.TownTabElementId); AssertButton(screen, CharacterCreationUiController.SummaryTabElementId); } [InstalledDatFact] public void MountsThroughTheControllerAgainstLiveResources() { using var dats = new DatCollection(DatDirectory, DatAccessType.Read); uint layoutId = RetailDataIdResolver.Resolve( dats, CharacterCreationUiController.RootEnum, 5u); ImportedLayout screen = BuildSelected( dats, layoutId, CharacterCreationUiController.RootElementId); var host = new UiRoot(); var dialogs = MakeDialogFactory(dats, host); var bindings = new CharacterCreationRuntimeBindings( () => null, _ => default, _ => default, _ => default, (_, _) => default, (_, _) => default, _ => default, _ => default, _ => default, _ => default, _ => default, () => { }); UiElement? ResolveTemplate(uint templateLayoutId, uint templateElementId) => LayoutImporter.Import( dats, templateLayoutId, templateElementId, _ => (0u, 0, 0), null)?.Root; CharacterCreationUiController? controller = CharacterCreationUiController.CreateDetached( host, screen, ResolveTemplate, dialogs, bindings, new CharacterCreationUiController.DialogStrings( "Are you sure?", "No name", "Unspent credits", "Randomize?", "Name too long")); Assert.NotNull(controller); controller!.AttachAndTick(); controller.Dispose(); dialogs.Dispose(); } /// 13 heritage buttons, the description text, all present as /// authored (Heritage page — gmCGHeritagePage::InitializePage @ /// 0x00483a10). [InstalledDatFact] public void HeritagePage_HasAllThirteenRaceButtonsAndDescriptionText() { using var dats = new DatCollection(DatDirectory, DatAccessType.Read); uint layoutId = RetailDataIdResolver.Resolve( dats, CharacterCreationUiController.RootEnum, 5u); ImportedLayout screen = BuildSelected( dats, layoutId, CharacterCreationUiController.RootElementId); UiElement heritageRoot = Assert.IsAssignableFrom( screen.FindElement(CharacterCreationUiController.HeritagePageElementId)); uint[] heritageButtonIds = [ 0x100003BFu, 0x100003C1u, 0x100003C2u, 0x100003C3u, 0x10000590u, 0x100005A9u, 0x100005E8u, 0x100005F1u, 0x100005C4u, 0x10000591u, 0x100005BFu, 0x100005C7u, 0x100005C8u, ]; foreach (uint buttonId in heritageButtonIds) { Assert.IsType( UiElement.FindDescendant(heritageRoot, buttonId)); } Assert.IsType( UiElement.FindDescendant(heritageRoot, 0x100003C4u)); } /// Seven template buttons, six attribute sliders (each with a /// lock button + scrollbar + value text), and the four derived /// displays (Profession page — /// gmCGProfessionPage::InitializePage @ 0x00482d50). [InstalledDatFact] public void ProfessionPage_HasTemplateButtonsSlidersAndDisplays() { using var dats = new DatCollection(DatDirectory, DatAccessType.Read); uint layoutId = RetailDataIdResolver.Resolve( dats, CharacterCreationUiController.RootEnum, 5u); ImportedLayout screen = BuildSelected( dats, layoutId, CharacterCreationUiController.RootElementId); UiElement professionRoot = Assert.IsAssignableFrom( screen.FindElement(CharacterCreationUiController.ProfessionPageElementId)); uint[] templateButtonIds = [ 0x100003D9u, 0x100003DAu, 0x100003DBu, 0x100003DCu, 0x100003DDu, 0x100003DEu, 0x100003DFu, ]; foreach (uint buttonId in templateButtonIds) { Assert.IsType( UiElement.FindDescendant(professionRoot, buttonId)); } uint[] sliderContainerIds = [ 0x100003E6u, 0x100003E7u, 0x100003E8u, 0x100003E9u, 0x100003EAu, 0x100003EBu, ]; foreach (uint containerId in sliderContainerIds) { UiElement container = Assert.IsAssignableFrom( UiElement.FindDescendant(professionRoot, containerId)); Assert.IsType( UiElement.FindDescendant(container, 0x100002EEu)); // The value display authors as an editable Type-12 (retail's // NumberInputFilter) — DatWidgetFactory maps that to UiField, // not UiText. See CharacterCreationProfessionPage's ctor comment. Assert.IsType( UiElement.FindDescendant(container, 0x100002EFu)); } // Avail/health/stamina/mana each author as a Button whose Type-12 // value child is swallowed by UiButton.ConsumesDatChildren — the // same substitution the Skills page's credits meter needed. See // CharacterCreationProfessionPage's ctor comment. foreach (uint containerId in new[] { 0x100003E2u, 0x100003E3u, 0x100003E4u, 0x100003E5u }) { Assert.IsType( UiElement.FindDescendant(professionRoot, containerId)); } } /// Skills listbox, credits meter, info panes (Skills page — /// gmCGSkillsPage::InitializePage @ 0x00481dd0). [InstalledDatFact] public void SkillsPage_HasListboxCreditsAndInfoPanes() { using var dats = new DatCollection(DatDirectory, DatAccessType.Read); uint layoutId = RetailDataIdResolver.Resolve( dats, CharacterCreationUiController.RootEnum, 5u); ImportedLayout screen = BuildSelected( dats, layoutId, CharacterCreationUiController.RootElementId); UiElement skillsRoot = Assert.IsAssignableFrom( screen.FindElement(CharacterCreationUiController.SkillsPageElementId)); Assert.IsType( UiElement.FindDescendant(skillsRoot, 0x100003F7u)); // The credits meter (decomp id 0x100002f3) authors as a raw dat // child of button 0x100003f9; UiButton.ConsumesDatChildren swallows // it before it becomes an addressable widget, so the faithful // acdream substitute is the button's own Label — see // CharacterCreationSkillsPage's ctor comment. Assert.IsType( UiElement.FindDescendant(skillsRoot, 0x100003F9u)); Assert.IsType( UiElement.FindDescendant(skillsRoot, 0x100003FBu)); Assert.IsType( UiElement.FindDescendant(skillsRoot, 0x100003FCu)); } /// Four town buttons + description text (Town page — /// gmCGTownPage::InitializePage @ 0x0047c6d0). [InstalledDatFact] public void TownPage_HasFourStarterAreaButtons() { using var dats = new DatCollection(DatDirectory, DatAccessType.Read); uint layoutId = RetailDataIdResolver.Resolve( dats, CharacterCreationUiController.RootEnum, 5u); ImportedLayout screen = BuildSelected( dats, layoutId, CharacterCreationUiController.RootElementId); UiElement townRoot = Assert.IsAssignableFrom( screen.FindElement(CharacterCreationUiController.TownPageElementId)); foreach (uint buttonId in new[] { 0x1000040Bu, 0x1000040Du, 0x1000040Eu, 0x1000040Fu }) { Assert.IsType( UiElement.FindDescendant(townRoot, buttonId)); } Assert.IsType( UiElement.FindDescendant(townRoot, 0x10000409u)); } /// /// GF-11b/GF-11c (Campaign CC gate round 1 Batch B). Live-DAT-measured: /// each town button's marker (0x1000040D's own child /// 0x1000040C) and its per-button name caption both carry the /// SAME numeric id 0x10000409 as the page-level description /// panel found by the sibling test above — a genuine id collision in /// the installed dat between two DIFFERENT elements in DIFFERENT /// subtrees (harmless for DatWidgetFactory.BuildButton's lift, /// which walks the button's OWN ElementInfo.Children list rather /// than resolving by a global id lookup — but it means this test /// verifies the BUILT BUTTON's own / /// /, /// not a second FindDescendant call, which would ambiguously /// return the unrelated page-level panel). Pins: the caption's own /// authored rect (0,4,100,37) survives instead of being overwritten by /// the marker-face-relative offset (GF-11c), and its color swaps /// Normal (218,167,85) -> Highlight/white (255,255,255) on selection /// (GF-11b). /// [InstalledDatFact] public void TownPage_HoltburgButton_CaptionHonorsOwnRectAndRecolorsWhiteOnSelection() { using var dats = new DatCollection(DatDirectory, DatAccessType.Read); uint layoutId = RetailDataIdResolver.Resolve( dats, CharacterCreationUiController.RootEnum, 5u); ImportedLayout screen = BuildSelected( dats, layoutId, CharacterCreationUiController.RootElementId); UiElement townRoot = Assert.IsAssignableFrom( screen.FindElement(CharacterCreationUiController.TownPageElementId)); UiButton holtburg = AssertButton(townRoot, 0x1000040Du); Assert.Equal("Holtburg", holtburg.Label); Assert.Equal(UiButton.LabelAlignment.Center, holtburg.LabelAlign); Assert.Equal((0f, 4f, 100f, 37f), holtburg.LabelBox); holtburg.Selected = false; Assert.Equal(new Vector4(218f / 255f, 167f / 255f, 85f / 255f, 1f), holtburg.LabelColor); holtburg.Selected = true; Assert.Equal(new Vector4(1f, 1f, 1f, 1f), holtburg.LabelColor); } /// /// GF-1 (Campaign CC gate round 1 Batch B). Live-DAT-measured: the /// Heritage row (0x100003BFu, Aluvian) authors retail's custom /// "Unselected"/"Selected" radio-pair state DESCRIPTORS directly on the /// row (property-only, no media), with the actual per-state art on its /// single stateful dot child (0x100003C0). Before this fix, /// committed nothing here. /// [InstalledDatFact] public void HeritagePage_Row_SelectedTogglesTheAuthoredRadioDotState() { using var dats = new DatCollection(DatDirectory, DatAccessType.Read); uint layoutId = RetailDataIdResolver.Resolve( dats, CharacterCreationUiController.RootEnum, 5u); ImportedLayout screen = BuildSelected( dats, layoutId, CharacterCreationUiController.RootElementId); UiElement heritageRoot = Assert.IsAssignableFrom( screen.FindElement(CharacterCreationUiController.HeritagePageElementId)); UiButton aluvian = AssertButton(heritageRoot, 0x100003BFu); Assert.Equal("Unselected", aluvian.ActiveState); aluvian.Selected = true; Assert.Equal("Selected", aluvian.ActiveState); Assert.Equal(RetailUiStateIds.Selected, aluvian.ActiveRetailStateId); aluvian.Selected = false; Assert.Equal("Unselected", aluvian.ActiveState); } /// /// GF-1 counterpart: the Profession template row (0x100003D9u, /// Custom/Adventurer) authors the identical custom radio-pair shape on /// its own icon child (0x100002E9). /// [InstalledDatFact] public void ProfessionPage_TemplateButton_SelectedTogglesTheAuthoredIconState() { using var dats = new DatCollection(DatDirectory, DatAccessType.Read); uint layoutId = RetailDataIdResolver.Resolve( dats, CharacterCreationUiController.RootEnum, 5u); ImportedLayout screen = BuildSelected( dats, layoutId, CharacterCreationUiController.RootElementId); UiElement professionRoot = Assert.IsAssignableFrom( screen.FindElement(CharacterCreationUiController.ProfessionPageElementId)); UiButton template = AssertButton(professionRoot, 0x100003D9u); Assert.Equal("Unselected", template.ActiveState); template.Selected = true; Assert.Equal("Selected", template.ActiveState); template.Selected = false; Assert.Equal("Unselected", template.ActiveState); } /// /// GF-8: the Appearance page's Face/Clothes sub-tab buttons /// (0x100003A9u/0x100003AAu) author the SAME custom /// radio-pair shape on their own icon child (0x100002E9) — same /// mechanism as the heritage/template rows, different page. /// [InstalledDatFact] public void AppearancePage_FaceClothesSubTabButtons_SelectedTogglesTheAuthoredState() { using var dats = new DatCollection(DatDirectory, DatAccessType.Read); uint layoutId = RetailDataIdResolver.Resolve( dats, CharacterCreationUiController.RootEnum, 5u); ImportedLayout screen = BuildSelected( dats, layoutId, CharacterCreationUiController.RootElementId); UiElement appearanceRoot = Assert.IsAssignableFrom( screen.FindElement(CharacterCreationUiController.AppearancePageElementId)); foreach (uint buttonId in new[] { CharacterCreationAppearancePage.FaceButtonId, CharacterCreationAppearancePage.ClothesButtonId, }) { UiButton button = AssertButton(appearanceRoot, buttonId); Assert.Equal("Unselected", button.ActiveState); button.Selected = true; Assert.Equal("Selected", button.ActiveState); button.Selected = false; Assert.Equal("Unselected", button.ActiveState); } } /// /// GF-1 family, gender-button shape: 0x100003A7u/ /// 0x100003A8u author the custom Unselected/Selected media /// DIRECTLY on the button's own StateMedia (no separate face-segment /// child) — live-DAT-measured, distinct from the heritage/template/ /// sub-tab family above. Exercises 's OTHER /// custom-selection-pair code path (media on the button itself, so /// info.StateMedia.Count != 0 and no face child is ever /// computed). /// [InstalledDatFact] public void AppearancePage_GenderButtons_SelectedTogglesTheAuthoredState() { using var dats = new DatCollection(DatDirectory, DatAccessType.Read); uint layoutId = RetailDataIdResolver.Resolve( dats, CharacterCreationUiController.RootEnum, 5u); ImportedLayout screen = BuildSelected( dats, layoutId, CharacterCreationUiController.RootElementId); UiElement appearanceRoot = Assert.IsAssignableFrom( screen.FindElement(CharacterCreationUiController.AppearancePageElementId)); foreach (uint buttonId in new[] { CharacterCreationAppearancePage.FemaleButtonId, CharacterCreationAppearancePage.MaleButtonId, }) { UiButton button = AssertButton(appearanceRoot, buttonId); Assert.Equal("Unselected", button.ActiveState); button.Selected = true; Assert.Equal("Selected", button.ActiveState); button.Selected = false; Assert.Equal("Unselected", button.ActiveState); } } /// /// GF-9 (Campaign CC gate round 1 Batch B). Live-DAT-measured: all nine /// companion overlay elements (SwatchOverlayIds) resolve as /// siblings of the swatches under the color-wheel container — retail's /// ACTUAL click-feedback mechanism (SetColor's /// m_tColorWheel[...][0x10][iCurColor*7]->SetVisible), not a /// state swap on the swatch buttons themselves (which author only an /// unnamed DirectState sprite — no Normal/Highlight media at all). /// [InstalledDatFact] public void AppearancePage_SwatchOverlays_AllNinePresent() { using var dats = new DatCollection(DatDirectory, DatAccessType.Read); uint layoutId = RetailDataIdResolver.Resolve( dats, CharacterCreationUiController.RootEnum, 5u); ImportedLayout screen = BuildSelected( dats, layoutId, CharacterCreationUiController.RootElementId); UiElement appearanceRoot = Assert.IsAssignableFrom( screen.FindElement(CharacterCreationUiController.AppearancePageElementId)); foreach (uint overlayId in CharacterCreationAppearancePage.SwatchOverlayIds) { UiElement overlay = Assert.IsAssignableFrom( UiElement.FindDescendant(appearanceRoot, overlayId)); // Retail's overlay ring starts hidden — SetColor only shows the // one at the current color index; nothing is selected before // any color choice runs. Assert.True(overlay.Visible); } } /// /// GF-10 (Campaign CC gate round 1 Batch B). Live-DAT-measured: both /// zoom buttons author a STANDARD Normal/Highlight(/rollover) pair — /// unlike the custom radio-pair family above, this is pure wiring /// ('s click handlers), not /// a new UiButton mechanism. /// [InstalledDatFact] public void AppearancePage_ZoomButtons_AuthorStandardNormalHighlightPair() { using var dats = new DatCollection(DatDirectory, DatAccessType.Read); uint layoutId = RetailDataIdResolver.Resolve( dats, CharacterCreationUiController.RootEnum, 5u); ImportedLayout screen = BuildSelected( dats, layoutId, CharacterCreationUiController.RootElementId); UiElement appearanceRoot = Assert.IsAssignableFrom( screen.FindElement(CharacterCreationUiController.AppearancePageElementId)); foreach (uint buttonId in new[] { CharacterCreationAppearancePage.ZoomInId, CharacterCreationAppearancePage.ZoomOutId, }) { UiButton button = AssertButton(appearanceRoot, buttonId); Assert.Equal("Normal", button.ActiveState); Assert.True(button.TrySetRetailState(UiButtonStateMachine.Highlight)); Assert.Equal("Highlight", button.ActiveState); Assert.True(button.TrySetRetailState(UiButtonStateMachine.Normal)); Assert.Equal("Normal", button.ActiveState); } } /// The exit-warning + all per-heritage/per-town DAT string /// keys this slice cites actually resolve in the installed table. /// [InstalledDatFact] public void RequiredChargenStringsResolve() { using var dats = new DatCollection(DatDirectory, DatAccessType.Read); var strings = new DatStringResolver(dats); const uint table = 0x23000002u; string[] keys = [ "ID_CharGen_ExitWarning", "ID_CharGen_Heritage_StartingSkills_Header", "ID_CharGen_Heritage_StartingSkills", "ID_CharGen_Heritage_BonusSkills_Trained_Header", "ID_CharGen_AluvianText_BonusSkills_Trained", "ID_CharGen_GaruText_BonusSkills_Trained", "ID_CharGen_ShoText_BonusSkills_Trained", "ID_CharGen_ViaText_BonusSkills_Trained", "ID_CharGen_ShadText_BonusSkills_Trained", "ID_CharGen_GearText_BonusSkills_Trained", "ID_CharGen_AunTText_BonusSkills_Trained", "ID_CharGen_EmpText_BonusSkills_Trained", "ID_CharGen_UndText_BonusSkills_Trained", "ID_CharGen_TownHowTo", "ID_CharGen_HoltText", "ID_CharGen_ShoushiText", "ID_CharGen_YaraqText", "ID_CharGen_SanamarText", ]; foreach (string key in keys) { string? resolved = strings.Resolve(table, DatStringResolver.ComputeHash(key)); Assert.True(resolved is not null, $"missing string: {key}"); } } /// /// Campaign CC slice CC6b-MOUNT — the Appearance page's full authored /// widget catalog. Pins the campaign plan's risk item 4 finding (the /// color-wheel/gradient family resolves through EXISTING /// DatWidgetFactory mappings; no new widget type was needed — /// see 's own class doc) /// against the real installed DAT: gender/Face/Clothes buttons, all /// nine spins, all nine color swatches, the shade scrollbar, and the /// viewport. /// [InstalledDatFact] public void AppearancePage_HasGenderChoiceSpinsSwatchesShadeAndViewport() { using var dats = new DatCollection(DatDirectory, DatAccessType.Read); uint layoutId = RetailDataIdResolver.Resolve( dats, CharacterCreationUiController.RootEnum, 5u); ImportedLayout screen = BuildSelected( dats, layoutId, CharacterCreationUiController.RootElementId); UiElement appearanceRoot = Assert.IsAssignableFrom( screen.FindElement(CharacterCreationUiController.AppearancePageElementId)); AssertButton(appearanceRoot, CharacterCreationAppearancePage.FemaleButtonId); AssertButton(appearanceRoot, CharacterCreationAppearancePage.MaleButtonId); AssertButton(appearanceRoot, CharacterCreationAppearancePage.FaceButtonId); AssertButton(appearanceRoot, CharacterCreationAppearancePage.ClothesButtonId); Assert.IsAssignableFrom( UiElement.FindDescendant(appearanceRoot, CharacterCreationAppearancePage.FaceChoicesId)); Assert.IsAssignableFrom( UiElement.FindDescendant(appearanceRoot, CharacterCreationAppearancePage.ClothesChoicesId)); foreach (uint spinId in new[] { CharacterCreationAppearancePage.HairSpinId, CharacterCreationAppearancePage.EyesSpinId, CharacterCreationAppearancePage.NoseSpinId, CharacterCreationAppearancePage.MouthSpinId, CharacterCreationAppearancePage.SkinSpinId, CharacterCreationAppearancePage.HeadgearSpinId, CharacterCreationAppearancePage.ShirtSpinId, CharacterCreationAppearancePage.TrousersSpinId, CharacterCreationAppearancePage.FootwearSpinId, }) { UiButton spin = AssertButton(appearanceRoot, spinId); // Fix round F2 item 2: the current-part highlight // (TrySetRetailState(Normal/Highlight)) only has a visible // effect through UiButton's ToggleBehavior branch when the // authored spin actually sets DAT property 0x0B — measured // (not assumed, matching this file's own discipline for the // arrow geometry above) True for all nine spins against the // installed EoR dat. Pinned so a future DAT revision that // drops it shows up here instead of as a silently-dead // highlight. Assert.True(spin.ToggleBehavior, $"spin 0x{spinId:X8} must author ToggleBehavior for the current-part highlight to work."); // Re-review nit N2 (2026-08-15): ToggleBehavior alone is necessary // but not sufficient — UiButton.UpdateVisualState only COMMITS the // requested state when _availableStates actually contains it // (UiButton.cs's TrySetRetailState -> Selected setter -> // UpdateVisualState chain). MEASURED (not assumed) against the // installed EoR dat: TrySetRetailState(Highlight) itself always // reports success (the ToggleBehavior branch commits // unconditionally, matching TrySetRetailState's own contract), // but NONE of the nine spins actually carries Highlight / // Highlight_rollover / Highlight_pressed media on either of // their two consumed arrow face segments — every one of them // authors only Normal / Normal_rollover / Ghosted. So F2 item 2's // current-part highlight is CURRENTLY A NO-OP for every spin: // ActiveState silently stays at its prior value ("Normal") // instead of ever becoming "Highlight". The pre-existing // ToggleBehavior pin above never caught this because it only // checks the PROPERTY that gates the state-machine branch, not // whether that branch has anything to actually draw. Filed as // AP-222 (retail-vs-acdream status unresolved — retail's own // gmCGAppearancePage::SetSelection call sites are cited for // the SetState(1)/SetState(6) calls, not for whether retail's // OWN spin art authors Highlight media either). Pinned to // "Normal" so a future DAT revision that adds real Highlight // media is what makes this assertion start failing — the // correct trigger to update it to "Highlight" instead of a // silently-reintroduced dead highlight going unnoticed either way. Assert.True( spin.TrySetRetailState(UiButtonStateMachine.Highlight), $"spin 0x{spinId:X8} must accept a Highlight state request."); Assert.Equal("Normal", spin.ActiveState); // AP-222 CORRECTED + RESOLVED (Campaign CC gate round 1 Batch B): // the art half of the "no-op" stays a genuine no-op (ActiveState // pinned to "Normal" above, unchanged) — retail's own spin art // authors no Highlight media either, matching acdream. But the // current-part highlight is NOT presentation-dead: retail's // SetState(6) also recolors the spin's caption text (dat // property 0x1B), live-DAT-measured 218,167,85 (Normal) -> // 255,221,131 (Highlight), plus outline off -> on (property // 0x21). DatWidgetFactory.BuildButton wires this per-state style // unconditionally, so it is already active on `spin` from // TrySetRetailState(Highlight) above, independent of whether the // page has assigned a Label string yet. Assert.Equal(new Vector4(255f / 255f, 221f / 255f, 131f / 255f, 1f), spin.LabelColor); Assert.True(spin.Outline, $"spin 0x{spinId:X8} must outline its label in the Highlight state."); Assert.True(spin.TrySetRetailState(UiButtonStateMachine.Normal)); Assert.Equal(new Vector4(218f / 255f, 167f / 255f, 85f / 255f, 1f), spin.LabelColor); Assert.False(spin.Outline, $"spin 0x{spinId:X8} must not outline its label in the Normal state."); } // Every color-wheel-family id resolves through EXISTING // DatWidgetFactory mappings (Button=1, Scrollbar=0xB, the generic // Type-3 fallback) — the risk-item-4 scouting result, pinned. foreach (uint swatchId in CharacterCreationAppearancePage.SwatchIds) AssertButton(appearanceRoot, swatchId); UiScrollbar shadeScroll = Assert.IsType( UiElement.FindDescendant(appearanceRoot, CharacterCreationAppearancePage.ShadeScrollId)); // Fix round F11: measured (not assumed) against the installed EoR // dat — the shade scrollbar (0x10000321) is authored VERTICAL // (33x85, taller than wide). Before this fix, UiScrollbar.OnEvent // only routed to ScalarChanged when Horizontal was true, so mouse // input on this control never reached SetShadeFromScalar in // production. Pinned so a future DAT revision that flips this // orientation is caught here rather than silently reintroducing the // dead-input bug (UiScrollbar's OnVerticalScalarEvent handles this // orientation now, but ONLY this orientation gets exercised in // production). Assert.False(shadeScroll.Horizontal); Assert.True(shadeScroll.Height > shadeScroll.Width); Assert.IsType( UiElement.FindDescendant(appearanceRoot, CharacterCreationAppearancePage.GradCircleId)); AssertButton(appearanceRoot, CharacterCreationAppearancePage.RotateClockwiseId); AssertButton(appearanceRoot, CharacterCreationAppearancePage.RotateCounterClockwiseId); AssertButton(appearanceRoot, CharacterCreationAppearancePage.ZoomInId); AssertButton(appearanceRoot, CharacterCreationAppearancePage.ZoomOutId); Assert.IsType( UiElement.FindDescendant(appearanceRoot, CharacterCreationAppearancePage.ViewportId)); } /// /// Live-DAT-measured arrow geometry the page's spin OnClickAt zones are /// built from — every one of the nine spins is uniformly 200px wide /// with the two locally-reused arrow child ids /// (0x1000030a decrement / 0x1000030b increment) at the /// SAME local positions. If a future DAT revision changes this shared /// template's geometry, this test (not a silent behavior change) is /// where it shows up. /// [InstalledDatFact] public void AppearancePage_SpinArrowGeometryIsUniformAcrossAllNineSpins() { using var dats = new DatCollection(DatDirectory, DatAccessType.Read); uint layoutId = RetailDataIdResolver.Resolve( dats, CharacterCreationUiController.RootEnum, 5u); ElementInfo rootInfo = Assert.IsType( LayoutImporter.ImportInfos( dats, layoutId, CharacterCreationUiController.RootElementId)); ElementInfo? appearanceInfo = FindInfo( rootInfo, CharacterCreationUiController.AppearancePageElementId); Assert.NotNull(appearanceInfo); foreach (uint spinId in new[] { CharacterCreationAppearancePage.HairSpinId, CharacterCreationAppearancePage.EyesSpinId, CharacterCreationAppearancePage.NoseSpinId, CharacterCreationAppearancePage.MouthSpinId, CharacterCreationAppearancePage.SkinSpinId, CharacterCreationAppearancePage.HeadgearSpinId, CharacterCreationAppearancePage.ShirtSpinId, CharacterCreationAppearancePage.TrousersSpinId, CharacterCreationAppearancePage.FootwearSpinId, }) { ElementInfo? spin = FindInfo(appearanceInfo!, spinId); Assert.NotNull(spin); Assert.Equal(200f, spin!.Width); ElementInfo? decrement = spin.Children.FirstOrDefault(c => c.Id == 0x1000030Au); ElementInfo? increment = spin.Children.FirstOrDefault(c => c.Id == 0x1000030Bu); Assert.NotNull(decrement); Assert.NotNull(increment); Assert.Equal(80f, decrement!.X); Assert.Equal(127f, increment!.X); // Fix round F10: the arrow WIDTHS are what actually derive // CharacterCreationAppearancePage.IncrementZoneEnd (174 = // IncrementZoneStart 127 + this measured 47px width) — X alone // pins the LEFT edge of each zone, not where the increment zone // ends and the select-as-current-part body zone begins. Measured // against the installed EoR dat: both arrows are 47px wide, // uniformly, across all nine spins. Assert.Equal(47f, decrement.Width); Assert.Equal(47f, increment.Width); } } 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 RetailDialogFactory MakeDialogFactory(IDatReaderWriter dats, UiRoot host) { uint dialogDid = RetailDataIdResolver.Resolve(dats, 2u, 5u); ImportedLayout? CreateLayout(RetailDialogType type) { uint rootElementId = RetailDialogFactory.RootElementId(type); return rootElementId == 0u ? null : LayoutImporter.Import( dats, dialogDid, rootElementId, _ => (0u, 0, 0), null); } return new RetailDialogFactory(host, CreateLayout); } /// /// Campaign CC slice CC5 — the Summary page's full authored widget /// catalog: the name field (with NameInputFilter), the how-to /// text, the viewport (Summary's OWN gmCG3DView), and the /// listbox's THREE row templates (single-line, category-header, /// key/value pair) confirmed against the installed EoR dat — see /// 's own class doc for the /// decomp citation (SetSummaryText @ 0x0047b1d0) each template /// maps to. /// [InstalledDatFact] public void SummaryPage_HasNameFieldListboxTemplatesAndViewport() { using var dats = new DatCollection(DatDirectory, DatAccessType.Read); uint layoutId = RetailDataIdResolver.Resolve( dats, CharacterCreationUiController.RootEnum, 5u); ImportedLayout screen = BuildSelected( dats, layoutId, CharacterCreationUiController.RootElementId); UiElement summaryRoot = Assert.IsAssignableFrom( screen.FindElement(CharacterCreationUiController.SummaryPageElementId)); Assert.IsType( UiElement.FindDescendant(summaryRoot, CharacterCreationSummaryPage.ScrollId)); Assert.IsType( UiElement.FindDescendant(summaryRoot, CharacterCreationSummaryPage.NameTextId)); Assert.IsType( UiElement.FindDescendant(summaryRoot, CharacterCreationSummaryPage.HowToTextId)); Assert.IsType( UiElement.FindDescendant(summaryRoot, CharacterCreationSummaryPage.ViewportId)); UiTemplateListBox list = Assert.IsType( UiElement.FindDescendant(summaryRoot, CharacterCreationSummaryPage.ListBoxId)); Assert.Equal(3, list.Templates.Count); UiElement? ResolveRow(int index) => LayoutImporter.Import( dats, list.Templates[index].TemplateLayoutId, list.Templates[index].TemplateElementId, _ => (0u, 0, 0), null)?.Root; UiElement lineRow = Assert.IsAssignableFrom(ResolveRow(0)); Assert.IsType(UiElement.FindDescendant(lineRow, 0x100002F9u)); UiElement headerRow = Assert.IsAssignableFrom(ResolveRow(1)); Assert.IsType(UiElement.FindDescendant(headerRow, 0x100000FEu)); UiElement pairRow = Assert.IsAssignableFrom(ResolveRow(2)); Assert.IsType(UiElement.FindDescendant(pairRow, 0x100002FCu)); Assert.IsType(UiElement.FindDescendant(pairRow, 0x100002FDu)); } /// /// CC5 re-review residual round, R3 (2026-08-16): MEASURES the /// installed global SkillTable's (portal.dat 0x0E000004) /// MinLevel distribution instead of inferring it — the C4 /// closeout's "observe, don't infer" lesson /// (docs/research/2026-08-05-c4-closeout-handoff.md). /// 's /// own doc comment used to claim "no retail-authored skill sets /// MinLevel above Untrained=1" without ever reading the field; ACE's own /// SkillBase.cs annotates the same field // 1-2? (a hedge /// that observed 2s exist). This is that measurement — see the method's /// own real-DAT finding recorded in RetailSkillFormula.cs's doc /// comment, corrected from this test's result. /// [InstalledDatFact] public void SkillTable_MinLevelDistribution_NeverExceedsTrained() { using var dats = new DatCollection(DatDirectory, DatAccessType.Read); DatReaderWriter.DBObjs.SkillTable? skillTable = dats.Get(0x0E000004u); Assert.NotNull(skillTable); var byMinLevel = skillTable!.Skills.Values .GroupBy(skill => skill.MinLevel) .OrderBy(g => g.Key) .ToDictionary(g => g.Key, g => g.Count()); Console.WriteLine( "[CC5-R3-DAT] SkillTable MinLevel distribution (level=count): " + string.Join(", ", byMinLevel.Select(p => $"{p.Key}={p.Value}")) + $" (total skills: {skillTable.Skills.Count})"); // RetailSkillFormula.CalculateChargenScore's own gate argument ("the // decomp's own `if (edi_1 >= MinLevel)` gate passes for both callers, // Trained=2 and Specialized=3") only holds while every skill's // MinLevel stays at or below Trained(2) — pin that as a real // assertion instead of leaving it as prose, so a future DAT // revision that adds a MinLevel=3+ skill fails HERE, not silently // under-credits that skill's chargen score. Assert.True( byMinLevel.Keys.All(minLevel => minLevel <= 2), "A skill's MinLevel exceeded 2 (Trained) in the installed DAT — " + "RetailSkillFormula.CalculateChargenScore's Trained/Specialized " + "gate argument needs re-verification for this skill."); } /// /// GF-13 (Campaign CC gate round 1, Batch A). Live-DAT-probe-confirmed: /// the GM-only labels 0x10000403 ("Non-Admin") and /// 0x10000494 ("Non-Envoy") both live under the Summary page /// (0x100003D6, path 0x100003CC > 0x100003D0 > /// 0x100003D6 > {0x10000403,0x10000494}) and both author dat /// property 0x3B (Invisible) = — the exact /// mechanism retail's UIElement::OnSetAttribute @0x00462d80 case 8 /// hides them by. Pins the DATA half () /// against the installed EoR dat. /// [InstalledDatFact] public void SummaryPage_NonAdminNonEnvoyLabels_AuthorInvisibleTrue() { using var dats = new DatCollection(DatDirectory, DatAccessType.Read); uint layoutId = RetailDataIdResolver.Resolve( dats, CharacterCreationUiController.RootEnum, 5u); ElementInfo rootInfo = Assert.IsType( LayoutImporter.ImportInfos( dats, layoutId, CharacterCreationUiController.RootElementId)); foreach (uint targetId in new[] { 0x10000403u, 0x10000494u }) { ElementInfo? found = FindInfo(rootInfo, targetId); Assert.NotNull(found); Assert.True( found!.Invisible, $"element 0x{targetId:X8} must author dat property 0x3B (Invisible) = true."); } } /// /// GF-13: the BEHAVIOR half — after the real controller mounts through /// (not a raw /// call), the two authored-invisible /// elements are not . Exercises /// HideAuthoredInvisibleElements's real chargen-scoped honor path, /// not just the data plumbing the sibling test above pins. /// [InstalledDatFact] public void SummaryPage_NonAdminNonEnvoyLabels_HiddenAfterControllerMount() { using var dats = new DatCollection(DatDirectory, DatAccessType.Read); uint layoutId = RetailDataIdResolver.Resolve( dats, CharacterCreationUiController.RootEnum, 5u); ImportedLayout screen = BuildSelected( dats, layoutId, CharacterCreationUiController.RootElementId); var host = new UiRoot(); var dialogs = MakeDialogFactory(dats, host); var bindings = new CharacterCreationRuntimeBindings( () => null, _ => default, _ => default, _ => default, (_, _) => default, (_, _) => default, _ => default, _ => default, _ => default, _ => default, _ => default, () => { }); UiElement? ResolveTemplate(uint templateLayoutId, uint templateElementId) => LayoutImporter.Import( dats, templateLayoutId, templateElementId, _ => (0u, 0, 0), null)?.Root; CharacterCreationUiController? controller = CharacterCreationUiController.CreateDetached( host, screen, ResolveTemplate, dialogs, bindings, new CharacterCreationUiController.DialogStrings( "Are you sure?", "No name", "Unspent credits", "Randomize?", "Name too long")); Assert.NotNull(controller); foreach (uint targetId in new[] { 0x10000403u, 0x10000494u }) { UiElement found = Assert.IsAssignableFrom( UiElement.FindDescendant(controller!.Root, targetId)); Assert.True(found.AuthoredInvisible, $"0x{targetId:X8} must carry AuthoredInvisible."); Assert.False(found.Visible, $"0x{targetId:X8} must be hidden after mount."); } controller!.Dispose(); dialogs.Dispose(); } /// /// GF-5 (Campaign CC gate round 1, Batch A). Live-DAT-probe-confirmed /// (raw ElementDesc.Type — the same id space as retail's /// DynamicCast tags, 1=Button, 12=Text): the Skills listbox /// authors exactly two templates. Templates[0] /// (0x100002F4) is retail's own 3-child bucket-HEADER row /// (unused by this port's flat-list simplification, AP-213). /// Templates[1] (0x100002FF) is the REAL skill row — a /// plain container root (rawType 3, NOT a Button), 7 children, byte- /// traced against gmCGSkillsPage::DoSkillRecords @ 0x004817e0 + /// tagSkillRecord's copy-constructor field order /// (acclient.h): name (0x10000301, Text), pSkillLevelText /// (0x10000302, Text), pUpCostText (0x10000303, Text), /// pSkillUpButton (0x10000304, Button), pSkillDownButton /// (0x10000305, Button), pDownCostText (0x10000306, Text). /// See 's own class doc for the /// full trace. /// [InstalledDatFact] public void SkillsPage_RealRowTemplate_HasNameLevelCostTextAndArrowButtons() { using var dats = new DatCollection(DatDirectory, DatAccessType.Read); uint layoutId = RetailDataIdResolver.Resolve( dats, CharacterCreationUiController.RootEnum, 5u); ImportedLayout screen = BuildSelected( dats, layoutId, CharacterCreationUiController.RootElementId); UiElement skillsRoot = Assert.IsAssignableFrom( screen.FindElement(CharacterCreationUiController.SkillsPageElementId)); UiTemplateListBox list = Assert.IsType( UiElement.FindDescendant(skillsRoot, 0x100003F7u)); Assert.Equal(2, list.Templates.Count); UiTemplateListEntry realRowTemplate = list.Templates[1]; Assert.Equal(0x100002FFu, realRowTemplate.TemplateElementId); UiElement? row = LayoutImporter.Import( dats, realRowTemplate.TemplateLayoutId, realRowTemplate.TemplateElementId, _ => (0u, 0, 0), null)?.Root; UiElement realRow = Assert.IsAssignableFrom(row); Assert.IsNotType(realRow); Assert.IsType(UiElement.FindDescendant(realRow, 0x10000301u)); Assert.IsType(UiElement.FindDescendant(realRow, 0x10000302u)); Assert.IsType(UiElement.FindDescendant(realRow, 0x10000303u)); Assert.IsType(UiElement.FindDescendant(realRow, 0x10000306u)); Assert.IsType(UiElement.FindDescendant(realRow, 0x10000304u)); Assert.IsType(UiElement.FindDescendant(realRow, 0x10000305u)); // Templates[0] (0x100002F4) is retail's bucket-header row — unused // by RebuildRows, still confirmed present so a future revision that // drops it or changes its shape shows up here. Assert.Equal(0x100002F4u, list.Templates[0].TemplateElementId); } /// /// GF-15 (Campaign CC gate round 1, Batch A). Live-DAT-probe-confirmed /// during the investigation: the Message dialog catalog's popup /// (0x3D), message text (0x3E), and OK button /// (0x26) all author REAL, nonzero geometry (popup 400x95, /// centered by ) — /// ruling out a zero-size/collapsed-layout explanation for the dialog /// rendering nothing. The actual root cause was a Z-ORDER bug (the /// chargen screen's own per-tick BringToFront burying the dialog /// behind its opaque backdrop while the dialog kept exclusive /// input priority — fixed in /// ). This test pins the geometry /// half so a future DAT revision that collapses the popup/message/button /// to zero size is caught here instead of silently reintroducing an /// invisible dialog. /// [InstalledDatFact] public void MessageDialogCatalog_PopupMessageAndOkButton_AuthorNonzeroGeometry() { using var dats = new DatCollection(DatDirectory, DatAccessType.Read); uint dialogDid = RetailDataIdResolver.Resolve(dats, 2u, 5u); ElementInfo rootInfo = Assert.IsType( LayoutImporter.ImportInfos(dats, dialogDid, 0x24u)); Assert.Equal(800f, rootInfo.Width); Assert.Equal(600f, rootInfo.Height); ElementInfo popup = Assert.IsType(FindInfo(rootInfo, 0x3Du)); Assert.True(popup.Width > 0f && popup.Height > 0f); ElementInfo message = Assert.IsType(FindInfo(rootInfo, 0x3Eu)); Assert.True(message.Width > 0f && message.Height > 0f); ElementInfo okButton = Assert.IsType(FindInfo(rootInfo, 0x26u)); Assert.True(okButton.Width > 0f && okButton.Height > 0f); } private static void AssertButton(ImportedLayout layout, uint elementId) => Assert.IsType(layout.FindElement(elementId)); private static UiButton AssertButton(UiElement root, uint elementId) => Assert.IsType(UiElement.FindDescendant(root, elementId)); private static ImportedLayout BuildSelected( IDatReaderWriter dats, uint layoutDid, uint rootId) { ElementInfo info = Assert.IsType( LayoutImporter.ImportInfos(dats, layoutDid, rootId)); return LayoutImporter.Build( info, _ => (0u, 0, 0), null, null, new DatStringResolver(dats).Resolve); } }