acdream/tests/AcDream.App.Tests/UI/Layout/LayoutConformanceTests.cs
Erik 4dcc90cb51 docs(D.2b): register AP-32 + IA-15 amend for importer; doc/test review fixes (N1/N4)
Process/quality items from the LayoutDesc-importer final review — no runtime
behavior change.

I1a — amend IA-15: the 8-piece chrome edge/corner→position mapping is no longer
a guess.  The LayoutImporter (ACDREAM_RETAIL_UI_IMPORTER) reads real LayoutDesc
dat data and resolves positions + sprite ids directly; locked by the conformance
fixture vitals_2100006C.json.  Residual risk trimmed to anchor resolution at
non-800×600 + controls.ini cascade.  Pointers added to LayoutImporter.cs and the
format-doc.

I1b — add AP-32: the importer collapses the dat's nested meter structure
(Type-7 → two Type-3 containers → three image-slice grandchildren each) into
UiMeter's programmatic 3-slice fields instead of building those nodes generically
and porting UIElement_Meter::DrawChildren.  Standalone Type-0 text elements are
also skipped (Plan 2).  Retail oracles: UIElement_Meter::DrawChildren @0x46fbd0,
UIElement_Text::DrawSelf @0x467aa0.

I1c — AP section header 31 → 32.

N1 — ElementReader.cs: comment at the Type-merge line explaining that a derived
Type 0 (text element) inherits the base's Type 12 (style prototype), which
DatWidgetFactory skips; safe for Plan 1 because vitals numbers render via
UiMeter.Label.  Format-doc §10: correct the "render as UiDatElement" sentence to
"skipped entirely" (Type-0 → inherits Type-12 via Merge → factory returns null).

N4 — new conformance test VitalsTree_TextLabel_InheritsFontDidFromBaseLayout:
walks the raw ElementInfo tree from the fixture and asserts at least one element
carries FontDid==0x40000000, proving Resolve()'s inheritance merge fired against
real dat data.  FixtureLoader gains LoadVitalsInfos() that returns the raw tree
without calling Build.

Tests: 36 pass (was 35), 0 errors, 0 warnings.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 14:55:01 +02:00

141 lines
6 KiB
C#

using AcDream.App.UI;
using AcDream.App.UI.Layout;
namespace AcDream.App.Tests.UI.Layout;
/// <summary>
/// Golden conformance tests for the vitals LayoutDesc importer.
/// Uses the committed JSON fixture (<c>vitals_2100006C.json</c>) — no dats, no GL.
///
/// These tests lock the importer's tree-building (factory dispatch, meter slice
/// extraction, rects) against the real portal.dat values captured when the
/// fixture was generated. Any regression in <see cref="LayoutImporter"/>,
/// <see cref="DatWidgetFactory"/>, or <see cref="ElementReader"/> will surface here.
///
/// Sprite ids sourced from <c>docs/research/2026-06-15-layoutdesc-format.md §11</c>.
/// </summary>
[Trait("Category", "Conformance")]
public class LayoutConformanceTests
{
// ── Test 1: Three meters at expected rects ────────────────────────────────
/// <summary>
/// The three vital bars must be UiMeters positioned at x=5, width=150, height=16,
/// at y=5 (health), y=21 (stamina), y=37 (mana).
/// </summary>
[Fact]
public void VitalsTree_HasThreeMetersAtExpectedRects()
{
var layout = FixtureLoader.LoadVitals();
(uint Id, float Y)[] expected =
[
(0x100000E6u, 5f), // health
(0x100000ECu, 21f), // stamina
(0x100000EEu, 37f), // mana
];
foreach (var (id, y) in expected)
{
var elem = layout.FindElement(id);
Assert.NotNull(elem);
var meter = Assert.IsType<UiMeter>(elem);
Assert.Equal(5f, meter.Left);
Assert.Equal(y, meter.Top);
Assert.Equal(150f, meter.Width);
Assert.Equal(16f, meter.Height);
}
}
// ── Test 2: All 18 slice ids ──────────────────────────────────────────────
/// <summary>
/// The six back+front 3-slice sprite ids for each of the three meters must
/// match the values confirmed from the dat dump (format doc §11).
/// This proves the factory's grandchild slice extraction against committed data.
/// </summary>
[Fact]
public void VitalsTree_MetersHaveExpectedSliceIds()
{
var layout = FixtureLoader.LoadVitals();
// Columns: MeterId, then 6 slice ids in order:
// BackLeft, BackTile, BackRight, FrontLeft, FrontTile, FrontRight
(uint MeterId, uint[] Slices)[] cases =
[
(0x100000E6u, [0x0600747Eu, 0x0600747Fu, 0x06007480u, 0x06007481u, 0x06007482u, 0x06007483u]), // health
(0x100000ECu, [0x06007484u, 0x06007485u, 0x06007486u, 0x06007487u, 0x06007488u, 0x06007489u]), // stamina
(0x100000EEu, [0x0600748Au, 0x0600748Bu, 0x0600748Cu, 0x0600748Du, 0x0600748Eu, 0x0600748Fu]), // mana
];
foreach (var (meterId, s) in cases)
{
var m = Assert.IsType<UiMeter>(layout.FindElement(meterId));
Assert.Equal(s[0], m.BackLeft); Assert.Equal(s[1], m.BackTile); Assert.Equal(s[2], m.BackRight);
Assert.Equal(s[3], m.FrontLeft); Assert.Equal(s[4], m.FrontTile); Assert.Equal(s[5], m.FrontRight);
}
}
// ── Test 3: Chrome TL corner sprite ───────────────────────────────────────
/// <summary>
/// The top-left chrome corner element (id <c>0x10000633</c>) must be a
/// <see cref="UiDatElement"/> whose active media file id is <c>0x060074C3</c>.
/// </summary>
[Fact]
public void VitalsTree_ChromeCornerHasExpectedSprite()
{
var layout = FixtureLoader.LoadVitals();
var elem = layout.FindElement(0x10000633u);
Assert.NotNull(elem);
var datElem = Assert.IsType<UiDatElement>(elem);
var (file, _) = datElem.ActiveMedia();
Assert.Equal(0x060074C3u, file);
}
// ── Test 4 (N4): Inheritance resolution — FontDid propagated from base ───
/// <summary>
/// Proves that <c>Resolve()</c>'s inheritance merge fired against real dat data:
/// at least one element in the fixture tree must have <c>FontDid == 0x40000000</c>
/// (the vitals font), inherited from the base-layout prototype <c>0x10000376</c>
/// in <c>0x2100003F</c> via the <c>BaseElement</c> / <c>BaseLayoutId</c> chain.
///
/// <para>
/// The three text labels (<c>0x100000EB</c> health, <c>0x100000ED</c> stamina,
/// <c>0x100000EF</c> mana) are Type=0 derived elements with no own font property.
/// The base element <c>0x10000376</c> carries <c>Properties[0x1A]</c> →
/// <c>ArrayBaseProperty[ DataIdBaseProperty{Value=0x40000000} ]</c>.
/// <see cref="ElementReader.Merge"/> propagates this via the "FontDid: derived wins
/// if non-zero, otherwise inherit" rule.
/// </para>
///
/// <para>
/// This test verifies end-to-end inheritance resolution against the committed fixture
/// (format doc §10, <c>docs/research/2026-06-15-layoutdesc-format.md</c>).
/// It operates on the raw <see cref="ElementInfo"/> tree, NOT the widget tree,
/// so the factory dispatch (Type 12 → skip) does not interfere.
/// </para>
/// </summary>
[Fact]
public void VitalsTree_TextLabel_InheritsFontDidFromBaseLayout()
{
var root = FixtureLoader.LoadVitalsInfos();
// Walk the full ElementInfo tree and collect all FontDid values.
var fontDids = new System.Collections.Generic.List<uint>();
CollectFontDids(root, fontDids);
// At least one element must carry FontDid == 0x40000000 (the vitals font).
// In practice, the three text labels (health/stamina/mana) all inherit it.
Assert.Contains(0x40000000u, fontDids);
}
private static void CollectFontDids(ElementInfo node, System.Collections.Generic.List<uint> acc)
{
if (node.FontDid != 0) acc.Add(node.FontDid);
foreach (var child in node.Children)
CollectFontDids(child, acc);
}
}