feat(D.2b): flip vitals to the LayoutDesc importer; retire hand-authored vitals.xml
The importer (proven pixel-identical at the 2026-06-15 A/B gate) is now the default vitals window when ACDREAM_RETAIL_UI=1 — data-driven from LayoutDesc 0x2100006C. Removed: the hand-authored vitals.xml build path, the asset file (recoverable from git history), and the now-obsolete ACDREAM_RETAIL_UI_IMPORTER flag (RuntimeOptions param + parse + 2 tests). The window is user-positioned at (10,30) and movable; resize stays off — the dat stacked-vitals layout is fixed- size (chrome edges near-pinned), faithful grip/dragbar resize is Plan 2. MarkupDocument/UiNineSlicePanel remain for the chat window + plugin panels. AcDream.App builds 0/0; AcDream.App.Tests 352 passed / 1 skipped / 0 failed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
5ac9d8c19c
commit
bf77a23ad3
4 changed files with 37 additions and 89 deletions
|
|
@ -1782,39 +1782,21 @@ public sealed class GameWindow : IDisposable
|
|||
var controls = _options.AcDir is { } acDir
|
||||
? AcDream.App.UI.ControlsIni.Load(System.IO.Path.Combine(acDir, "controls", "controls.ini"))
|
||||
: AcDream.App.UI.ControlsIni.Parse(string.Empty);
|
||||
string vitalsXml = System.IO.File.ReadAllText(
|
||||
System.IO.Path.Combine(AppContext.BaseDirectory, "UI", "assets", "vitals.xml"));
|
||||
var panel = AcDream.App.UI.MarkupDocument.Build(vitalsXml, _vitalsVm!, ResolveChrome, controls);
|
||||
|
||||
// Phase D.2b — retail dat-font for the vitals numbers. Font 0x40000000
|
||||
// (Latin-1, 16px, outline atlas). The consola TTF debug font is wrong
|
||||
// for retail look; the meter falls back to it only if the dat font fails
|
||||
// to load. Loaded under _datLock for consistency with other dat reads
|
||||
// (no streaming worker is active during OnLoad, but the lock is cheap).
|
||||
// Phase D.2b — retail dat-font for the vitals numbers (Font 0x40000000,
|
||||
// Latin-1, 16px, outline atlas). Passed into the importer so the meter
|
||||
// number overlay renders through the dat-font two-pass blit; falls back to
|
||||
// the debug font only if it fails to load. Under _datLock like other reads.
|
||||
AcDream.App.UI.UiDatFont? vitalsDatFont;
|
||||
lock (_datLock)
|
||||
vitalsDatFont = AcDream.App.UI.UiDatFont.Load(_dats!, _textureCache!);
|
||||
if (vitalsDatFont is not null)
|
||||
{
|
||||
foreach (var child in panel.Children)
|
||||
if (child is AcDream.App.UI.UiMeter meter)
|
||||
meter.DatFont = vitalsDatFont;
|
||||
Console.WriteLine("[D.2b] vitals dat-font 0x40000000 loaded for numeric overlay.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("[D.2b] vitals dat-font 0x40000000 unavailable — falling back to debug font.");
|
||||
}
|
||||
Console.WriteLine(vitalsDatFont is not null
|
||||
? "[D.2b] vitals dat-font 0x40000000 loaded for numeric overlay."
|
||||
: "[D.2b] vitals dat-font 0x40000000 unavailable — falling back to debug font.");
|
||||
|
||||
_uiHost.Root.AddChild(panel);
|
||||
Console.WriteLine("[D.2b] retail UI active — vitals panel from vitals.xml markup.");
|
||||
|
||||
// Phase D.2b — LayoutDesc importer A/B harness. When ACDREAM_RETAIL_UI_IMPORTER=1,
|
||||
// build the SAME vitals window (0x2100006C) data-driven from the dat and place it beside
|
||||
// the hand-authored one so the two can be compared pixel-for-pixel before the importer
|
||||
// becomes the default. The hand-authored path above is untouched.
|
||||
if (_options.RetailUiImporter)
|
||||
{
|
||||
// Phase D.2b — the vitals window is data-driven from the dat LayoutDesc
|
||||
// (0x2100006C) via the LayoutImporter. The former hand-authored vitals.xml
|
||||
// markup path was retired after the importer proved pixel-identical at the
|
||||
// 2026-06-15 A/B gate. MarkupDocument stays for plugin/custom panels.
|
||||
AcDream.App.UI.Layout.ImportedLayout? imported;
|
||||
lock (_datLock)
|
||||
imported = AcDream.App.UI.Layout.LayoutImporter.Import(
|
||||
|
|
@ -1828,16 +1810,22 @@ public sealed class GameWindow : IDisposable
|
|||
healthText: () => (_vitalsVm!.HealthCurrent, _vitalsVm.HealthMax) is (uint c, uint m) ? $"{c}/{m}" : "",
|
||||
staminaText: () => (_vitalsVm!.StaminaCurrent, _vitalsVm.StaminaMax) is (uint c, uint m) ? $"{c}/{m}" : "",
|
||||
manaText: () => (_vitalsVm!.ManaCurrent, _vitalsVm.ManaMax) is (uint c, uint m) ? $"{c}/{m}" : "");
|
||||
// Offset beside the hand-authored window (at x=10) for an A/B visual comparison.
|
||||
imported.Root.Left = 200;
|
||||
imported.Root.Top = 30;
|
||||
_uiHost.Root.AddChild(imported.Root);
|
||||
Console.WriteLine("[D.2b] importer vitals window active (A/B vs hand-authored).");
|
||||
// Top-level window: user-positioned (Anchors.None so the per-frame anchor
|
||||
// pass doesn't reset it) + movable, like the retired hand-authored panel.
|
||||
// Resize is left off — the dat stacked-vitals layout (0x2100006C) is
|
||||
// fixed-size (chrome edges near-pinned); faithful grip/dragbar-driven
|
||||
// resize is the Plan-2 window manager.
|
||||
var vitalsRoot = imported.Root;
|
||||
vitalsRoot.Left = 10; vitalsRoot.Top = 30;
|
||||
vitalsRoot.ClickThrough = false;
|
||||
vitalsRoot.Anchors = AcDream.App.UI.AnchorEdges.None;
|
||||
vitalsRoot.Draggable = true;
|
||||
_uiHost.Root.AddChild(vitalsRoot);
|
||||
Console.WriteLine("[D.2b] retail UI active — vitals window from LayoutDesc importer (0x2100006C).");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("[D.2b] importer vitals: LayoutDesc 0x2100006C not found.");
|
||||
}
|
||||
Console.WriteLine("[D.2b] vitals: LayoutDesc 0x2100006C not found — vitals unavailable.");
|
||||
}
|
||||
|
||||
// Retail chat window — a draggable/resizable nine-slice frame hosting a
|
||||
|
|
|
|||
|
|
@ -41,7 +41,6 @@ public sealed record RuntimeOptions(
|
|||
bool DumpLiveSpawns,
|
||||
int? LegacyStreamRadius,
|
||||
bool RetailUi,
|
||||
bool RetailUiImporter,
|
||||
string? AcDir)
|
||||
{
|
||||
/// <summary>
|
||||
|
|
@ -86,7 +85,6 @@ public sealed record RuntimeOptions(
|
|||
// top of the quality preset's radii. Null when unset or invalid.
|
||||
LegacyStreamRadius: TryParseNonNegativeInt(env("ACDREAM_STREAM_RADIUS")),
|
||||
RetailUi: IsExactlyOne(env("ACDREAM_RETAIL_UI")),
|
||||
RetailUiImporter: IsExactlyOne(env("ACDREAM_RETAIL_UI_IMPORTER")),
|
||||
AcDir: NullIfEmpty(env("ACDREAM_AC_DIR")));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +0,0 @@
|
|||
<!-- Retail stacked vitals window, geometry + sprite ids dat-verified from
|
||||
LayoutDesc 0x2100006C (160x58, 5px chrome border, three flush 150x16 bars
|
||||
at y=5/21/37). Each bar: back 3-slice (empty track) + front 3-slice (fill,
|
||||
clipped to the fraction). Sprite ids are the STACKED-window set
|
||||
(0x0600747E-0x0600748F), NOT the floaty-row set. -->
|
||||
<panel id="acdream.vitals" x="10" y="30" w="160" h="58" resize="x">
|
||||
<meter id="health" x="5" y="5" w="150" h="16" fill="{HealthPercent}" cur="{HealthCurrent}" max="{HealthMax}" color="#FFC70D0D" anchor="left,top,right"
|
||||
backleft="0x0600747E" backtile="0x0600747F" backright="0x06007480" frontleft="0x06007481" fronttile="0x06007482" frontright="0x06007483"/>
|
||||
<meter id="stamina" x="5" y="21" w="150" h="16" fill="{StaminaPercent}" cur="{StaminaCurrent}" max="{StaminaMax}" color="#FFD49E1F" anchor="left,top,right"
|
||||
backleft="0x06007484" backtile="0x06007485" backright="0x06007486" frontleft="0x06007487" fronttile="0x06007488" frontright="0x06007489"/>
|
||||
<meter id="mana" x="5" y="37" w="150" h="16" fill="{ManaPercent}" cur="{ManaCurrent}" max="{ManaMax}" color="#FF1F33D9" anchor="left,top,right"
|
||||
backleft="0x0600748A" backtile="0x0600748B" backright="0x0600748C" frontleft="0x0600748D" fronttile="0x0600748E" frontright="0x0600748F"/>
|
||||
</panel>
|
||||
|
|
@ -25,29 +25,4 @@ public class RuntimeOptionsRetailUiTests
|
|||
Assert.False(opts.RetailUi);
|
||||
Assert.Null(opts.AcDir);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_ReadsRetailUiImporter_WhenSetToOne()
|
||||
{
|
||||
var env = new Dictionary<string, string?>
|
||||
{
|
||||
["ACDREAM_RETAIL_UI_IMPORTER"] = "1",
|
||||
};
|
||||
var opts = RuntimeOptions.Parse("dats", k => env.GetValueOrDefault(k));
|
||||
Assert.True(opts.RetailUiImporter);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_DefaultsRetailUiImporterOff_WhenUnsetOrOtherValue()
|
||||
{
|
||||
// Unset → false.
|
||||
Assert.False(RuntimeOptions.Parse("dats", _ => null).RetailUiImporter);
|
||||
|
||||
// Non-"1" values → false (mirrors RetailUi / other IsExactlyOne flags).
|
||||
var envOther = new Dictionary<string, string?>
|
||||
{
|
||||
["ACDREAM_RETAIL_UI_IMPORTER"] = "true",
|
||||
};
|
||||
Assert.False(RuntimeOptions.Parse("dats", k => envOther.GetValueOrDefault(k)).RetailUiImporter);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue