acdream/tests/AcDream.App.Tests/UI/Layout/FixtureLoader.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

54 lines
2.4 KiB
C#

using System.IO;
using System.Text.Json;
using AcDream.App.UI.Layout;
namespace AcDream.App.Tests.UI.Layout;
/// <summary>
/// Loads the committed vitals ElementInfo fixture and builds the widget tree —
/// no dats required. The fixture was generated from layout <c>0x2100006C</c>
/// via the real portal.dat and serialized with <see cref="System.Text.Json"/>.
/// </summary>
public static class FixtureLoader
{
private static readonly JsonSerializerOptions _opts = new()
{
IncludeFields = true,
};
/// <summary>
/// Deserializes the committed <c>vitals_2100006C.json</c> fixture (copied to
/// the test output directory via the csproj <c>CopyToOutputDirectory</c> item)
/// into an <see cref="ElementInfo"/> tree, then builds and returns the
/// <see cref="ImportedLayout"/> using a null-returning sprite resolver and no
/// dat font — sufficient for conformance checks on tree structure and slice ids.
/// </summary>
public static ImportedLayout LoadVitals()
{
var root = LoadVitalsInfos();
return LayoutImporter.Build(root, _ => (0u, 0, 0), null);
}
/// <summary>
/// Deserializes the committed <c>vitals_2100006C.json</c> fixture into a raw
/// <see cref="ElementInfo"/> tree WITHOUT calling <see cref="LayoutImporter.Build"/>.
/// Use this when the test needs to inspect the resolved <see cref="ElementInfo"/>
/// tree directly (e.g. inheritance-resolution checks) without exercising the
/// widget factory.
/// </summary>
public static AcDream.App.UI.Layout.ElementInfo LoadVitalsInfos()
{
var fixturePath = Path.Combine(AppContext.BaseDirectory, "UI", "Layout", "fixtures", "vitals_2100006C.json");
if (!File.Exists(fixturePath))
throw new FileNotFoundException($"Vitals fixture not found at: {fixturePath}");
var bytes = File.ReadAllBytes(fixturePath);
// Strip UTF-8 BOM (EF BB BF) if present so JsonSerializer.Deserialize<T>(ReadOnlySpan<byte>)
// does not reject the first byte.
ReadOnlySpan<byte> span = bytes;
if (span.Length >= 3 && span[0] == 0xEF && span[1] == 0xBB && span[2] == 0xBF)
span = span[3..];
return JsonSerializer.Deserialize<AcDream.App.UI.Layout.ElementInfo>(span, _opts)
?? throw new InvalidOperationException($"fixture deserialized to null: {fixturePath}");
}
}