feat(D.2b): retail 3-slice vital bars + headless mockup verifier

Render each vital bar as a horizontal 3-slice from the real retail
RenderSurface sprites (authoritative ids from the vitals LayoutDesc
0x21000014 via dump-vitals-bars): a fixed-width bevelled left-cap, a
stretched glassy-gradient middle, and a fixed-width right-cap. The
empty back track draws full width; the coloured front fill grows from
the left to the value (the track owns the right end, so the fill omits
its own right-cap). Replaces the flat single-sprite Alphablend overlay
that read as the old UI - this is the bordered gradient look from the
retail screenshot (red HP / gold stamina / blue mana).

UiMeter gains the six 9-slice ids (BackLeft/Tile/Right +
FrontLeft/Tile/Right) and a DrawHBar helper; MarkupDocument parses the
backleft/backtile/backright/frontleft/fronttile/frontright attrs;
vitals.xml carries the 18 per-vital ids. The temporary
ACDREAM_BAR_PROVEOUT component grid is removed.

Adds AcDream.Cli render-vitals-mockup: a headless ImageSharp composite
that assembles the bars with the SAME DrawHBar logic, so the sprite
assembly can be verified by eye (Read the PNG) without launching the
client + server - the fast UI-iteration loop the user asked for.
export-ui-sprite dumps a single RenderSurface to PNG for HTML mockups.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-06-14 21:40:11 +02:00
parent 84630517e3
commit 1453ff7da2
7 changed files with 242 additions and 26 deletions

View file

@ -1,4 +1,5 @@
using System.Diagnostics;
using AcDream.Cli;
using DatReaderWriter;
using DatReaderWriter.DBObjs;
using DatReaderWriter.Enums;
@ -18,6 +19,31 @@ if (args.Length >= 1 && args[0] == "dump-vitals-bars")
return DumpVitalsBars(dvbDatDir);
}
if (args.Length >= 1 && args[0] == "render-vitals-mockup")
{
string? rvmDatDir = args.ElementAtOrDefault(1) ?? Env.GetEnvironmentVariable("ACDREAM_DAT_DIR");
string rvmOut = args.ElementAtOrDefault(2) ?? "vitals-mockup.png";
if (string.IsNullOrWhiteSpace(rvmDatDir))
{
Console.Error.WriteLine("usage: AcDream.Cli render-vitals-mockup <dat-directory> [out.png]");
return 2;
}
return VitalsMockup.Render(rvmDatDir, rvmOut);
}
if (args.Length >= 1 && args[0] == "export-ui-sprite")
{
string? eusDatDir = args.ElementAtOrDefault(1) ?? Env.GetEnvironmentVariable("ACDREAM_DAT_DIR");
string? eusId = args.ElementAtOrDefault(2);
string eusOut = args.ElementAtOrDefault(3) ?? "sprite.png";
if (string.IsNullOrWhiteSpace(eusDatDir) || string.IsNullOrWhiteSpace(eusId))
{
Console.Error.WriteLine("usage: AcDream.Cli export-ui-sprite <dat-directory> <0xId> [out.png]");
return 2;
}
return VitalsMockup.ExportSprite(eusDatDir, eusId, eusOut);
}
// Phase 0: open the four AC dat files and print how many of each asset type live in them.
// This proves DatReaderWriter works on our retail dats and gives us a baseline inventory
// to compare against what a future renderer needs.