feat(D.2b): run importer-built vitals window under ACDREAM_RETAIL_UI_IMPORTER (A/B)
Adds RuntimeOptions.RetailUiImporter (ACDREAM_RETAIL_UI_IMPORTER=1) — a new opt-in flag that runs the LayoutImporter-built vitals window ALONGSIDE the hand-authored vitals panel for pixel-for-pixel A/B comparison. The importer window is placed at x=200, y=30 so both render simultaneously within the same ACDREAM_RETAIL_UI=1 session. The hand-authored path is entirely untouched and remains the default; the importer path is the eventual switch-over target. Also adds two RuntimeOptionsRetailUiTests covering the new flag: value "1" → true, unset/other → false. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
e8ddb68801
commit
ab3ab79380
3 changed files with 57 additions and 0 deletions
|
|
@ -1794,6 +1794,36 @@ public sealed class GameWindow : IDisposable
|
||||||
_uiHost.Root.AddChild(panel);
|
_uiHost.Root.AddChild(panel);
|
||||||
Console.WriteLine("[D.2b] retail UI active — vitals panel from vitals.xml markup.");
|
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)
|
||||||
|
{
|
||||||
|
AcDream.App.UI.Layout.ImportedLayout? imported;
|
||||||
|
lock (_datLock)
|
||||||
|
imported = AcDream.App.UI.Layout.LayoutImporter.Import(
|
||||||
|
_dats!, 0x2100006Cu, ResolveChrome, vitalsDatFont);
|
||||||
|
if (imported is not null)
|
||||||
|
{
|
||||||
|
AcDream.App.UI.Layout.VitalsController.Bind(imported,
|
||||||
|
healthPct: () => _vitalsVm!.HealthPercent,
|
||||||
|
staminaPct: () => _vitalsVm!.StaminaPercent ?? 0f,
|
||||||
|
manaPct: () => _vitalsVm!.ManaPercent ?? 0f,
|
||||||
|
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).");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.WriteLine("[D.2b] importer vitals: LayoutDesc 0x2100006C not found.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Retail chat window — a draggable/resizable nine-slice frame hosting a
|
// Retail chat window — a draggable/resizable nine-slice frame hosting a
|
||||||
// scrollable transcript (UiChatView). Read-only + wheel-scroll for now;
|
// scrollable transcript (UiChatView). Read-only + wheel-scroll for now;
|
||||||
// drag-select + Ctrl+C copy land in the next D.2b sub-step. A dedicated
|
// drag-select + Ctrl+C copy land in the next D.2b sub-step. A dedicated
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ public sealed record RuntimeOptions(
|
||||||
bool DumpLiveSpawns,
|
bool DumpLiveSpawns,
|
||||||
int? LegacyStreamRadius,
|
int? LegacyStreamRadius,
|
||||||
bool RetailUi,
|
bool RetailUi,
|
||||||
|
bool RetailUiImporter,
|
||||||
string? AcDir)
|
string? AcDir)
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -85,6 +86,7 @@ public sealed record RuntimeOptions(
|
||||||
// top of the quality preset's radii. Null when unset or invalid.
|
// top of the quality preset's radii. Null when unset or invalid.
|
||||||
LegacyStreamRadius: TryParseNonNegativeInt(env("ACDREAM_STREAM_RADIUS")),
|
LegacyStreamRadius: TryParseNonNegativeInt(env("ACDREAM_STREAM_RADIUS")),
|
||||||
RetailUi: IsExactlyOne(env("ACDREAM_RETAIL_UI")),
|
RetailUi: IsExactlyOne(env("ACDREAM_RETAIL_UI")),
|
||||||
|
RetailUiImporter: IsExactlyOne(env("ACDREAM_RETAIL_UI_IMPORTER")),
|
||||||
AcDir: NullIfEmpty(env("ACDREAM_AC_DIR")));
|
AcDir: NullIfEmpty(env("ACDREAM_AC_DIR")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,4 +25,29 @@ public class RuntimeOptionsRetailUiTests
|
||||||
Assert.False(opts.RetailUi);
|
Assert.False(opts.RetailUi);
|
||||||
Assert.Null(opts.AcDir);
|
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