feat(studio): dump LayoutSource — preview any retail window from the UI dump
Adds Task 4b: a second load path for the UI Studio that reads the committed retail UI layout dump (docs/research/2026-06-25-retail-ui-layout-dump.json) and renders any of the 26 retail windows as a static sprite hierarchy. New files: - src/AcDream.App/Studio/UiDumpModel.cs — POCOs + System.Text.Json parse of the dump (UiDump, DumpPanel, DumpNode, DumpRect, DumpStateSet, DumpImage, DumpState + UiDumpModel static helpers: Parse, ListSlugs, PickImageId). - src/AcDream.App/Studio/DumpLayout.cs — DumpLayout.Load(path, slug, resolve, out err): parses the dump, finds the panel by slug, builds a UiElement tree. Internal DumpSpriteElement draws its sprite via DrawSprite (not reusing UiDatElement — avoids the ElementInfo/StateMedia dat-import dependency for this static mockup). DumpGroupElement is a transparent container for Group nodes. Rect basis is ABSOLUTE in the dump (verified: inventory root at absolute x=500 and its children also start at x≈500 — child offset from parent is 0–50px, not 500px); DumpLayout subtracts parent rect to produce parent-relative Left/Top for each child. - tests/AcDream.App.Tests/Studio/DumpLayoutTests.cs — 5 tests covering: inventory load with 0x100001D5 check + >= 40 nodes, unknown slug → null+err, root at origin, children are parent-relative, all 26 slugs smoke-load. Modified files: - StudioOptions: adds DumpSlug + DumpFile fields; --dump <slug> and --dump-file <path> args; ResolveDumpFile() walks up to the solution root to find the default dump JSON (mirrors ConformanceDats.SolutionRoot()). --dump suppresses the default vitals layout so the two modes are exclusive. - StudioWindow.OnLoad: when DumpSlug is set, loads via DumpLayout (no FixtureProvider, no controllers — static structure only); else falls through to the existing LayoutSource + FixtureProvider path. Results: DumpLayoutTests 5/5 passed; full AcDream.App.Tests 609 passed, 2 skipped (same as before); dotnet build green, 0 warnings. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
9ed9d8dbd9
commit
e3de7f0dab
6 changed files with 117572 additions and 10 deletions
|
|
@ -5,7 +5,12 @@ namespace AcDream.App.Studio;
|
|||
/// Constructed by <see cref="Parse"/> from the command-line tokens that follow
|
||||
/// the <c>ui-studio</c> dispatch token.
|
||||
/// </summary>
|
||||
public sealed record StudioOptions(string DatDir, uint? LayoutId, string? MarkupPath)
|
||||
public sealed record StudioOptions(
|
||||
string DatDir,
|
||||
uint? LayoutId,
|
||||
string? MarkupPath,
|
||||
string? DumpSlug = null,
|
||||
string? DumpFile = null)
|
||||
{
|
||||
/// <summary>
|
||||
/// Parse studio options from the args that come AFTER the <c>ui-studio</c> token.
|
||||
|
|
@ -14,14 +19,21 @@ public sealed record StudioOptions(string DatDir, uint? LayoutId, string? Markup
|
|||
/// <c>ACDREAM_DAT_DIR</c> when omitted.</para>
|
||||
/// <para><c>--layout 0xNNNN</c>: hex LayoutDesc dat id to preview.</para>
|
||||
/// <para><c>--markup <path></c>: path to a KSML markup file (Task 6, unsupported for now).</para>
|
||||
/// <para>When neither <c>--layout</c> nor <c>--markup</c> is given the default layout
|
||||
/// <c>0x2100006C</c> (vitals) is used.</para>
|
||||
/// <para><c>--dump <slug></c>: load a panel from the retail UI dump JSON by slug
|
||||
/// (e.g. <c>inventory</c>, <c>radar</c>, <c>toolbar</c>). Static mockup — no controllers.</para>
|
||||
/// <para><c>--dump-file <path></c>: override the default dump file path
|
||||
/// (<c>docs/research/2026-06-25-retail-ui-layout-dump.json</c> from the solution root).
|
||||
/// Only meaningful when <c>--dump</c> is also given.</para>
|
||||
/// <para>When neither <c>--layout</c>, <c>--markup</c>, nor <c>--dump</c> is given the
|
||||
/// default layout <c>0x2100006C</c> (vitals) is used.</para>
|
||||
/// </summary>
|
||||
public static StudioOptions Parse(string[] args)
|
||||
{
|
||||
string? datDir = null;
|
||||
uint? layoutId = null;
|
||||
string? markupPath = null;
|
||||
string? dumpSlug = null;
|
||||
string? dumpFile = null;
|
||||
|
||||
for (int i = 0; i < args.Length; i++)
|
||||
{
|
||||
|
|
@ -38,6 +50,14 @@ public sealed record StudioOptions(string DatDir, uint? LayoutId, string? Markup
|
|||
{
|
||||
markupPath = args[++i];
|
||||
}
|
||||
else if (args[i] == "--dump" && i + 1 < args.Length)
|
||||
{
|
||||
dumpSlug = args[++i];
|
||||
}
|
||||
else if (args[i] == "--dump-file" && i + 1 < args.Length)
|
||||
{
|
||||
dumpFile = args[++i];
|
||||
}
|
||||
else if (!args[i].StartsWith('-'))
|
||||
{
|
||||
datDir ??= args[i];
|
||||
|
|
@ -51,10 +71,37 @@ public sealed record StudioOptions(string DatDir, uint? LayoutId, string? Markup
|
|||
throw new InvalidOperationException(
|
||||
"ui-studio: dat directory required — pass as first arg or set ACDREAM_DAT_DIR.");
|
||||
|
||||
// Default layout: vitals (0x2100006C).
|
||||
if (layoutId is null && markupPath is null)
|
||||
// Default layout: vitals (0x2100006C), unless a dump slug or markup is requested.
|
||||
if (layoutId is null && markupPath is null && dumpSlug is null)
|
||||
layoutId = 0x2100006Cu;
|
||||
|
||||
return new StudioOptions(datDir, layoutId, markupPath);
|
||||
return new StudioOptions(datDir, layoutId, markupPath, dumpSlug, dumpFile);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolve the dump file path for this session:
|
||||
/// <list type="bullet">
|
||||
/// <item><see cref="DumpFile"/> if explicitly set.</item>
|
||||
/// <item>Otherwise <c><solutionRoot>/docs/research/2026-06-25-retail-ui-layout-dump.json</c>.</item>
|
||||
/// </list>
|
||||
/// Returns null when neither the override nor the default file exists.
|
||||
/// </summary>
|
||||
public string? ResolveDumpFile()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(DumpFile))
|
||||
return DumpFile;
|
||||
|
||||
// Walk up from the App binary to the solution root (same approach as
|
||||
// ConformanceDats.SolutionRoot in the test project).
|
||||
var dir = AppContext.BaseDirectory;
|
||||
while (!string.IsNullOrEmpty(dir))
|
||||
{
|
||||
var candidate = Path.Combine(dir, "docs", "research",
|
||||
"2026-06-25-retail-ui-layout-dump.json");
|
||||
if (File.Exists(candidate))
|
||||
return candidate;
|
||||
dir = Path.GetDirectoryName(dir);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue