feat(render): implement Campaign AR and terrain fidelity
This commit is contained in:
parent
99cf26e00c
commit
7a5f96ede5
368 changed files with 50611 additions and 950 deletions
|
|
@ -4,6 +4,7 @@ using System.Linq;
|
|||
using System.Numerics;
|
||||
using AcDream.App.UI;
|
||||
using AcDream.App.UI.Layout;
|
||||
using AcDream.Plugin.Abstractions.Rendering;
|
||||
using AcDream.UI.Abstractions.Panels.Settings;
|
||||
|
||||
namespace AcDream.App.Tests.UI.Layout;
|
||||
|
|
@ -285,7 +286,8 @@ public sealed class ConfigOptionsPageControllerTests
|
|||
public List<CameraTurningSettings> CameraTurningSaves { get; } = new();
|
||||
public List<ChatSettings> ChatSaves { get; } = new();
|
||||
|
||||
public ConfigOptionsPageController.Bindings ToBindings() => new(
|
||||
public ConfigOptionsPageController.Bindings ToBindings(
|
||||
ConfigOptionsPageController.RenderPackBindings? renderPacks = null) => new(
|
||||
LoadDisplay: () => Display,
|
||||
SaveDisplay: value => { Display = value; DisplaySaves.Add(value); },
|
||||
LoadAudio: () => Audio,
|
||||
|
|
@ -293,7 +295,10 @@ public sealed class ConfigOptionsPageControllerTests
|
|||
LoadCameraTurning: () => CameraTurning,
|
||||
SaveCameraTurning: value => { CameraTurning = value; CameraTurningSaves.Add(value); },
|
||||
LoadChat: () => Chat,
|
||||
SaveChat: value => { Chat = value; ChatSaves.Add(value); });
|
||||
SaveChat: value => { Chat = value; ChatSaves.Add(value); })
|
||||
{
|
||||
RenderPacks = renderPacks,
|
||||
};
|
||||
}
|
||||
|
||||
private static (OptionsPanelController Panel, FakeBindings Bindings, bool Bound) BindReal(
|
||||
|
|
@ -430,6 +435,377 @@ public sealed class ConfigOptionsPageControllerTests
|
|||
Assert.Equal(39, viewport.Children.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OptInRenderPackBindings_append_two_menus_without_changing_retail_only_fixture()
|
||||
{
|
||||
ImportedLayout layout = FixtureLoader.LoadOptionsPanelHost();
|
||||
OptionsPanelController controller = OptionsPanelController.Bind(
|
||||
layout,
|
||||
new OptionsPanelController.Callbacks(
|
||||
Toggle: () => { },
|
||||
RequestExitToCharacterSelection: () => { },
|
||||
ExitGame: () => { },
|
||||
UseMouseTurningSettings: () => { },
|
||||
DisplaySystemMessage: _ => { }))!;
|
||||
var fake = new FakeBindings();
|
||||
string? activationFailure = "Last activation failed: shader interface mismatch.";
|
||||
var renderPacks = new ConfigOptionsPageController.RenderPackBindings(() =>
|
||||
[
|
||||
new ConfigOptionsPageController.RenderPackChoice(
|
||||
"acdream.atmospheric",
|
||||
"Atmospheric Rendering",
|
||||
"1.0.0",
|
||||
true,
|
||||
null,
|
||||
[
|
||||
new ConfigOptionsPageController.RenderPackPresetChoice(
|
||||
"low", "Low", true, null)
|
||||
{
|
||||
MaxResidentGpuBytes = 64L * 1024 * 1024,
|
||||
MaxIncrementalGpuMillisecondsP50 = 2.0,
|
||||
MaxIncrementalGpuMillisecondsP99 = 3.0,
|
||||
MaxIncrementalCpuMillisecondsP50 = 0.15,
|
||||
MaxIncrementalCpuMillisecondsP99 = 0.50,
|
||||
},
|
||||
new("medium", "Medium", true, null),
|
||||
new("high", "High", false, "High needs more GPU memory."),
|
||||
])
|
||||
{
|
||||
FeatureSummary = "Filmic atmosphere and moving-sun shadows.",
|
||||
},
|
||||
new ConfigOptionsPageController.RenderPackChoice(
|
||||
"test.unsupported",
|
||||
"Unsupported Test Pack",
|
||||
"2.0.0",
|
||||
false,
|
||||
"Directional depth sampling is unavailable.",
|
||||
[new("low", "Low", false, "Directional depth sampling is unavailable.")]),
|
||||
])
|
||||
{
|
||||
LoadFailureNotice = () => activationFailure,
|
||||
};
|
||||
|
||||
bool bound = ConfigOptionsPageController.Bind(
|
||||
layout,
|
||||
controller.ConfigPage,
|
||||
MakeTemplateResolver(),
|
||||
(_, _) => null,
|
||||
fake.ToBindings(renderPacks));
|
||||
|
||||
Assert.True(bound);
|
||||
var configSlot = UiElement.FindDescendant(controller.TabPanel, ConfigPageSlotId)!;
|
||||
var listBox = Assert.IsType<UiTemplateListBox>(
|
||||
UiElement.FindDescendant(configSlot, ConfigOptionsPageController.ListBoxElementId));
|
||||
Assert.Equal(43, Assert.Single(listBox.Children).Children.Count);
|
||||
Assert.Equal(32, controller.ConfigPage.Rows.Count);
|
||||
|
||||
List<UiMenu> menus = CollectMenus(configSlot);
|
||||
UiMenu packMenu = menus[^2];
|
||||
UiMenu presetMenu = menus[^1];
|
||||
Assert.Equal("acdream default (retail-faithful)", packMenu.Items[0].Label);
|
||||
Assert.Contains(packMenu.Items, value => Equals(value.Payload, "acdream.atmospheric"));
|
||||
Assert.False(packMenu.EnabledProvider!("test.unsupported"));
|
||||
Assert.Equal(
|
||||
"Last activation failed: shader interface mismatch.",
|
||||
packMenu.GetTooltipText()!.Split(Environment.NewLine)[0]);
|
||||
packMenu.OnSelect!("test.unsupported");
|
||||
Assert.True(fake.Display.RenderPack.IsRetail);
|
||||
|
||||
packMenu.OnSelect!("acdream.atmospheric");
|
||||
Assert.Equal("acdream.atmospheric", fake.Display.RenderPack.PackId);
|
||||
Assert.Equal("1.0.0", fake.Display.RenderPack.PackVersion);
|
||||
Assert.Equal("low", fake.Display.RenderPack.PresetId);
|
||||
activationFailure = null;
|
||||
Assert.Equal(
|
||||
"Filmic atmosphere and moving-sun shadows.",
|
||||
packMenu.GetTooltipText());
|
||||
presetMenu = CollectMenus(configSlot)[^1];
|
||||
Assert.Equal(3, presetMenu.Items.Count);
|
||||
Assert.False(presetMenu.EnabledProvider!("high"));
|
||||
Assert.Contains(
|
||||
"GPU p50/p99 ≤ 2/3 ms",
|
||||
presetMenu.GetTooltipText(),
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains("pack VRAM ≤ 64 MiB", presetMenu.GetTooltipText(), StringComparison.Ordinal);
|
||||
|
||||
presetMenu.OnSelect!("medium");
|
||||
Assert.Equal("medium", fake.Display.RenderPack.PresetId);
|
||||
Assert.True(fake.DisplaySaves.Count >= 2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RenderPackMenu_revision_refresh_removes_withdrawn_schema_and_discovers_reregistration()
|
||||
{
|
||||
ImportedLayout layout = FixtureLoader.LoadOptionsPanelHost();
|
||||
OptionsPanelController controller = OptionsPanelController.Bind(
|
||||
layout,
|
||||
new OptionsPanelController.Callbacks(
|
||||
Toggle: () => { },
|
||||
RequestExitToCharacterSelection: () => { },
|
||||
ExitGame: () => { },
|
||||
UseMouseTurningSettings: () => { },
|
||||
DisplaySystemMessage: _ => { }))!;
|
||||
var alpha = new ConfigOptionsPageController.RenderPackChoice(
|
||||
"pack.alpha", "Alpha", "1.0.0", true, null,
|
||||
[new("low", "Low", true, null)])
|
||||
{
|
||||
Settings =
|
||||
[
|
||||
new RenderSettingDeclaration(
|
||||
"alpha-toggle", "Alpha toggle", RenderSettingKind.Boolean,
|
||||
"true", null, null, null, []),
|
||||
],
|
||||
};
|
||||
var beta = new ConfigOptionsPageController.RenderPackChoice(
|
||||
"pack.beta", "Beta", "2.0.0", true, null,
|
||||
[new("medium", "Medium", true, null)]);
|
||||
IReadOnlyList<ConfigOptionsPageController.RenderPackChoice> discovered = [alpha];
|
||||
long revision = 1;
|
||||
var fake = new FakeBindings
|
||||
{
|
||||
Display = DisplaySettings.Default with
|
||||
{
|
||||
RenderPack = new RenderPackSelectionSettings(
|
||||
"pack.alpha", "1.0.0", "low"),
|
||||
},
|
||||
};
|
||||
var renderPacks = new ConfigOptionsPageController.RenderPackBindings(
|
||||
() => discovered)
|
||||
{
|
||||
LoadRevision = () => revision,
|
||||
};
|
||||
|
||||
Assert.True(ConfigOptionsPageController.Bind(
|
||||
layout,
|
||||
controller.ConfigPage,
|
||||
MakeTemplateResolver(),
|
||||
(_, _) => null,
|
||||
fake.ToBindings(renderPacks)));
|
||||
|
||||
var configSlot = UiElement.FindDescendant(controller.TabPanel, ConfigPageSlotId)!;
|
||||
var listBox = Assert.IsType<UiTemplateListBox>(
|
||||
UiElement.FindDescendant(configSlot, ConfigOptionsPageController.ListBoxElementId));
|
||||
UiMenu packMenu = CollectMenus(configSlot)[^2];
|
||||
Assert.Equal(44, listBox.ItemCount);
|
||||
Assert.Contains(packMenu.Items, item => Equals(item.Payload, "pack.alpha"));
|
||||
|
||||
discovered = [beta];
|
||||
revision++;
|
||||
packMenu.BeforeOpen!();
|
||||
|
||||
Assert.DoesNotContain(packMenu.Items, item => Equals(item.Payload, "pack.alpha"));
|
||||
Assert.Contains(packMenu.Items, item => Equals(item.Payload, "pack.beta"));
|
||||
Assert.Equal(RenderPackSelectionSettings.RetailPackId, packMenu.Selected);
|
||||
Assert.Equal(43, listBox.ItemCount);
|
||||
Assert.Equal(32, controller.ConfigPage.Rows.Count);
|
||||
controller.ConfigPage.Reset();
|
||||
Assert.Equal(RenderPackSelectionSettings.RetailPackId, packMenu.Selected);
|
||||
Assert.DoesNotContain(packMenu.Items, item => Equals(item.Payload, "pack.alpha"));
|
||||
|
||||
fake.Display = fake.Display with
|
||||
{
|
||||
RenderPack = new RenderPackSelectionSettings(
|
||||
"pack.beta", "2.0.0", "medium"),
|
||||
};
|
||||
revision++;
|
||||
packMenu.BeforeOpen!();
|
||||
|
||||
Assert.Equal("pack.beta", packMenu.Selected);
|
||||
Assert.Equal(43, listBox.ItemCount);
|
||||
Assert.Equal("Medium", CollectMenus(configSlot)[^1].Items.Single().Label);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RenderPackSettings_live_schema_swap_replaces_only_the_optional_tail()
|
||||
{
|
||||
ImportedLayout layout = FixtureLoader.LoadOptionsPanelHost();
|
||||
OptionsPanelController controller = OptionsPanelController.Bind(
|
||||
layout,
|
||||
new OptionsPanelController.Callbacks(
|
||||
Toggle: () => { },
|
||||
RequestExitToCharacterSelection: () => { },
|
||||
ExitGame: () => { },
|
||||
UseMouseTurningSettings: () => { },
|
||||
DisplaySystemMessage: _ => { }))!;
|
||||
var fake = new FakeBindings
|
||||
{
|
||||
Display = DisplaySettings.Default with
|
||||
{
|
||||
RenderPack = new RenderPackSelectionSettings("pack.alpha", "1.0.0", "low"),
|
||||
},
|
||||
};
|
||||
RenderSettingDeclaration[] alphaSettings =
|
||||
[
|
||||
new("enabled", "Enabled", RenderSettingKind.Boolean, "false", null, null, null, []),
|
||||
new("strength", "Strength", RenderSettingKind.Float, "0.5", 0, 1, 0.25, []),
|
||||
new("samples", "Samples", RenderSettingKind.Integer, "2", 0, 10, 2, []),
|
||||
new("mode", "Mode", RenderSettingKind.Choice, "low", null, null, null,
|
||||
["low", "high"]),
|
||||
];
|
||||
var alpha = new ConfigOptionsPageController.RenderPackChoice(
|
||||
"pack.alpha", "Alpha", "1.0.0", true, null,
|
||||
[
|
||||
new ConfigOptionsPageController.RenderPackPresetChoice(
|
||||
"low", "Low", true, null)
|
||||
{
|
||||
SettingOverrides =
|
||||
[
|
||||
new RenderQualitySettingOverride("strength", "0.75"),
|
||||
],
|
||||
},
|
||||
new ConfigOptionsPageController.RenderPackPresetChoice(
|
||||
"high", "High", true, null),
|
||||
])
|
||||
{
|
||||
Settings = alphaSettings,
|
||||
};
|
||||
var beta = new ConfigOptionsPageController.RenderPackChoice(
|
||||
"pack.beta", "Beta", "2.0.0", true, null,
|
||||
[new("default", "Default", true, null)])
|
||||
{
|
||||
Settings =
|
||||
[
|
||||
new RenderSettingDeclaration(
|
||||
"beta-enabled", "Beta enabled", RenderSettingKind.Boolean,
|
||||
"true", null, null, null, []),
|
||||
],
|
||||
};
|
||||
var renderPacks = new ConfigOptionsPageController.RenderPackBindings(() =>
|
||||
[alpha, beta]);
|
||||
|
||||
Assert.True(ConfigOptionsPageController.Bind(
|
||||
layout,
|
||||
controller.ConfigPage,
|
||||
MakeTemplateResolver(),
|
||||
(_, _) => null,
|
||||
fake.ToBindings(renderPacks)));
|
||||
|
||||
var configSlot = UiElement.FindDescendant(controller.TabPanel, ConfigPageSlotId)!;
|
||||
var listBox = Assert.IsType<UiTemplateListBox>(
|
||||
UiElement.FindDescendant(configSlot, ConfigOptionsPageController.ListBoxElementId));
|
||||
Assert.Equal(47, listBox.ItemCount); // exact retail 39 + 8-row optional tail
|
||||
Assert.Equal(36, controller.ConfigPage.Rows.Count); // retail 30 + pack/preset + 4 settings
|
||||
|
||||
IReadOnlyList<UiElement> items = listBox.ViewportForTest!.Children;
|
||||
var enabled = Assert.IsType<UiButton>(
|
||||
UiElement.FindDescendant(items[42], 0x10000219u));
|
||||
var strength = Assert.IsType<UiScrollbar>(
|
||||
UiElement.FindDescendant(items[43], 0x1000021Cu));
|
||||
var samples = Assert.IsType<UiScrollbar>(
|
||||
UiElement.FindDescendant(items[44], 0x1000021Cu));
|
||||
List<UiMenu> menus = CollectMenus(configSlot);
|
||||
UiMenu packMenu = menus[^3];
|
||||
UiMenu presetMenu = menus[^2];
|
||||
UiMenu oldModeMenu = menus[^1];
|
||||
Assert.Equal(0.75f, strength.ScalarPosition, 3); // preset wins declaration default
|
||||
Assert.Equal(["low", "high"], oldModeMenu.Items.Select(value => value.Label));
|
||||
|
||||
enabled.Selected = true;
|
||||
enabled.OnClick!();
|
||||
strength.ScalarChanged!(0.62f); // 0.62 snaps to 0.5 on the declared 0.25 step
|
||||
samples.ScalarChanged!(0.33f); // 3.3 snaps to integer step 4
|
||||
oldModeMenu.OnSelect!("high");
|
||||
|
||||
Assert.Equal("true", fake.Display.RenderPack.SettingOverrides["enabled"]);
|
||||
Assert.Equal("0.5", fake.Display.RenderPack.SettingOverrides["strength"]);
|
||||
Assert.Equal("4", fake.Display.RenderPack.SettingOverrides["samples"]);
|
||||
Assert.Equal("high", fake.Display.RenderPack.SettingOverrides["mode"]);
|
||||
|
||||
presetMenu.OnSelect!("high");
|
||||
Assert.Equal("high", fake.Display.RenderPack.PresetId);
|
||||
Assert.Equal(4, fake.Display.RenderPack.SettingOverrides.Count);
|
||||
Assert.Equal(47, listBox.ItemCount);
|
||||
Assert.Equal(36, controller.ConfigPage.Rows.Count);
|
||||
int savesBeforeStaleWidget = fake.DisplaySaves.Count;
|
||||
oldModeMenu.OnSelect!("low");
|
||||
Assert.Equal(savesBeforeStaleWidget, fake.DisplaySaves.Count);
|
||||
|
||||
packMenu.OnSelect!("pack.beta");
|
||||
Assert.Equal("pack.beta", fake.Display.RenderPack.PackId);
|
||||
Assert.Equal("2.0.0", fake.Display.RenderPack.PackVersion);
|
||||
Assert.Equal("default", fake.Display.RenderPack.PresetId);
|
||||
Assert.Empty(fake.Display.RenderPack.SettingOverrides);
|
||||
Assert.Equal(44, listBox.ItemCount); // header + pack + preset + one setting + separator
|
||||
Assert.Equal(33, controller.ConfigPage.Rows.Count);
|
||||
|
||||
oldModeMenu.OnSelect!("high");
|
||||
Assert.Empty(fake.Display.RenderPack.SettingOverrides);
|
||||
|
||||
controller.ConfigPage.Reset();
|
||||
Assert.Equal("pack.alpha", fake.Display.RenderPack.PackId);
|
||||
Assert.Equal(47, listBox.ItemCount);
|
||||
Assert.Equal(36, controller.ConfigPage.Rows.Count);
|
||||
|
||||
controller.ConfigPage.Defaults();
|
||||
Assert.True(fake.Display.RenderPack.IsRetail);
|
||||
Assert.Equal(43, listBox.ItemCount); // header + pack + retail preset + separator
|
||||
Assert.Equal(32, controller.ConfigPage.Rows.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RenderPackSettingEdit_sanitizes_unknown_and_invalid_persisted_values()
|
||||
{
|
||||
ImportedLayout layout = FixtureLoader.LoadOptionsPanelHost();
|
||||
OptionsPanelController controller = OptionsPanelController.Bind(
|
||||
layout,
|
||||
new OptionsPanelController.Callbacks(
|
||||
Toggle: () => { },
|
||||
RequestExitToCharacterSelection: () => { },
|
||||
ExitGame: () => { },
|
||||
UseMouseTurningSettings: () => { },
|
||||
DisplaySystemMessage: _ => { }))!;
|
||||
var fake = new FakeBindings
|
||||
{
|
||||
Display = DisplaySettings.Default with
|
||||
{
|
||||
RenderPack = new RenderPackSelectionSettings("pack.alpha", "1.0.0", "low")
|
||||
{
|
||||
SettingOverrides = new RenderPackSettingOverrides(
|
||||
new Dictionary<string, string>
|
||||
{
|
||||
["removed"] = "1",
|
||||
["strength"] = "0.6", // not aligned to 0.25
|
||||
}),
|
||||
},
|
||||
},
|
||||
};
|
||||
var pack = new ConfigOptionsPageController.RenderPackChoice(
|
||||
"pack.alpha", "Alpha", "1.0.0", true, null,
|
||||
[new("low", "Low", true, null)])
|
||||
{
|
||||
Settings =
|
||||
[
|
||||
new RenderSettingDeclaration(
|
||||
"enabled", "Enabled", RenderSettingKind.Boolean,
|
||||
"false", null, null, null, []),
|
||||
new RenderSettingDeclaration(
|
||||
"strength", "Strength", RenderSettingKind.Float,
|
||||
"0.5", 0, 1, 0.25, []),
|
||||
],
|
||||
};
|
||||
|
||||
Assert.True(ConfigOptionsPageController.Bind(
|
||||
layout,
|
||||
controller.ConfigPage,
|
||||
MakeTemplateResolver(),
|
||||
(_, _) => null,
|
||||
fake.ToBindings(new ConfigOptionsPageController.RenderPackBindings(() => [pack]))));
|
||||
|
||||
var configSlot = UiElement.FindDescendant(controller.TabPanel, ConfigPageSlotId)!;
|
||||
var listBox = Assert.IsType<UiTemplateListBox>(
|
||||
UiElement.FindDescendant(configSlot, ConfigOptionsPageController.ListBoxElementId));
|
||||
UiElement enabledRow = listBox.ViewportForTest!.Children[42];
|
||||
var enabled = Assert.IsType<UiButton>(
|
||||
UiElement.FindDescendant(enabledRow, 0x10000219u));
|
||||
enabled.Selected = true;
|
||||
enabled.OnClick!();
|
||||
|
||||
Assert.Single(fake.Display.RenderPack.SettingOverrides);
|
||||
Assert.Equal("true", fake.Display.RenderPack.SettingOverrides["enabled"]);
|
||||
Assert.False(fake.Display.RenderPack.SettingOverrides.ContainsKey("removed"));
|
||||
Assert.False(fake.Display.RenderPack.SettingOverrides.ContainsKey("strength"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToggleRow_SfxEnabled_WritesThroughAudioBindings()
|
||||
{
|
||||
|
|
@ -1043,7 +1419,7 @@ public sealed class ConfigOptionsPageControllerTests
|
|||
(24, RowKind.Menu, true, "Environment Texture Detail"), // AP-198
|
||||
(25, RowKind.Menu, true, "Texture Filtering"), // AP-198
|
||||
(26, RowKind.Menu, true, "Landscape Draw Distance"), // AP-198
|
||||
(27, RowKind.Toggle, true, "Building Detail Textures"), // AP-198
|
||||
(27, RowKind.Toggle, false, "Building Detail Textures"), // LIVE — #226
|
||||
(28, RowKind.Toggle, true, "Multi-Pass Alpha"), // AP-198
|
||||
(31, RowKind.Slider, true, "Mouse Look Sensitivity"), // TS-74
|
||||
(32, RowKind.Toggle, true, "Invert Mouselook Y Axis"), // TS-74
|
||||
|
|
|
|||
|
|
@ -431,4 +431,47 @@ public sealed class OptionPageModelTests
|
|||
Assert.Equal(0, flushCount); // NO AfterApply publication on a seed
|
||||
Assert.Equal(1, gatingCount); // Apply/Reset ghosting re-evaluated
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RemoveTail_DetachesRowsAndPageNotifyOwnership()
|
||||
{
|
||||
var page = new OptionPage();
|
||||
var retained = new BoolOptionRow(false, false);
|
||||
var removed = new BoolOptionRow(false, false);
|
||||
int notifications = 0;
|
||||
page.OnOptionChanged = () => notifications++;
|
||||
page.Register(retained);
|
||||
page.Register(removed);
|
||||
|
||||
page.RemoveTail(1);
|
||||
notifications = 0;
|
||||
removed.SetCurrentValue(true);
|
||||
|
||||
Assert.Single(page.Rows);
|
||||
Assert.Same(retained, page.Rows[0]);
|
||||
Assert.Equal(0, notifications);
|
||||
Assert.False(page.Changed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Defaults_IsSafeWhenAnEarlierRowReplacesTheDynamicTail()
|
||||
{
|
||||
var page = new OptionPage();
|
||||
int removedApplyCount = 0;
|
||||
var pack = new BoolOptionRow(
|
||||
initial: true,
|
||||
defaultValue: false,
|
||||
apply: _ => page.RemoveTail(1));
|
||||
var dynamic = new BoolOptionRow(
|
||||
initial: false,
|
||||
defaultValue: true,
|
||||
apply: _ => removedApplyCount++);
|
||||
page.Register(pack);
|
||||
page.Register(dynamic);
|
||||
|
||||
page.Defaults();
|
||||
|
||||
Assert.Single(page.Rows);
|
||||
Assert.Equal(0, removedApplyCount);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,10 +41,65 @@ public sealed class RetailUiAutomationProbeTests
|
|||
public bool IsWorldReady { get; set; }
|
||||
public bool IsWorldViewportVisible { get; set; }
|
||||
public int PortalMaterializationCount { get; set; }
|
||||
public int RenderPackPerformanceSampleCount { get; set; }
|
||||
public bool RenderPackFailedToRetail { get; set; }
|
||||
public RetailUiAutomationRenderPackStatus RenderPackStatus { get; set; } =
|
||||
RetailUiAutomationRenderPackStatus.Retail;
|
||||
public int FramebufferWidth { get; set; } = 1280;
|
||||
public int FramebufferHeight { get; set; } = 720;
|
||||
public int RenderPackPerformanceResetCount { get; private set; }
|
||||
public List<string> RenderPackSelections { get; } = [];
|
||||
public int RenderPackDisableCount { get; private set; }
|
||||
public int RenderPackReenableCount { get; private set; }
|
||||
public List<(int Width, int Height)> FramebufferResizes { get; } = [];
|
||||
public int ClientCloseRequestCount { get; private set; }
|
||||
public List<string> Checkpoints { get; } = new();
|
||||
public HashSet<string> ScreenshotRequests { get; } = new();
|
||||
public HashSet<string> CompletedScreenshots { get; } = new();
|
||||
|
||||
public bool TryResetRenderPackPerformance(out string error)
|
||||
{
|
||||
RenderPackPerformanceResetCount++;
|
||||
RenderPackPerformanceSampleCount = 0;
|
||||
error = string.Empty;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TrySelectRenderPack(string presetId, out string error)
|
||||
{
|
||||
RenderPackSelections.Add(presetId);
|
||||
error = string.Empty;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryDisableRenderPack(out string error)
|
||||
{
|
||||
RenderPackDisableCount++;
|
||||
error = string.Empty;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryReenableRenderPack(out string error)
|
||||
{
|
||||
RenderPackReenableCount++;
|
||||
error = string.Empty;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryResizeFramebuffer(int width, int height, out string error)
|
||||
{
|
||||
FramebufferResizes.Add((width, height));
|
||||
error = string.Empty;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryRequestClientClose(out string error)
|
||||
{
|
||||
ClientCloseRequestCount++;
|
||||
error = string.Empty;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryRequestCheckpoint(
|
||||
string name,
|
||||
out IRetailUiAutomationCheckpoint? checkpoint,
|
||||
|
|
@ -654,6 +709,199 @@ public sealed class RetailUiAutomationProbeTests
|
|||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScriptRunner_resetsThenWaitsForCompleteRenderPackEvidenceWindow()
|
||||
{
|
||||
var (root, _, _, _, objects) = RootWithTwoItemLists();
|
||||
var runtime = new FakeRuntime
|
||||
{
|
||||
RenderPackPerformanceSampleCount = 1200,
|
||||
};
|
||||
var probe = new RetailUiAutomationProbe(root, objects);
|
||||
string path = Path.Combine(
|
||||
Path.GetTempPath(),
|
||||
Path.GetRandomFileName() + ".ui-probe.txt");
|
||||
File.WriteAllLines(path,
|
||||
[
|
||||
"renderpack reset-performance",
|
||||
"wait render-pack-samples 2048 1000",
|
||||
"screenshot complete-window 1000",
|
||||
]);
|
||||
|
||||
try
|
||||
{
|
||||
using var runner = new RetailUiAutomationScriptRunner(
|
||||
probe,
|
||||
path,
|
||||
dumpOnStart: false,
|
||||
runtime: runtime);
|
||||
|
||||
runner.Tick(0d);
|
||||
Assert.Equal(1, runtime.RenderPackPerformanceResetCount);
|
||||
Assert.Equal(0, runtime.RenderPackPerformanceSampleCount);
|
||||
Assert.Empty(runtime.ScreenshotRequests);
|
||||
|
||||
runtime.RenderPackPerformanceSampleCount = 2047;
|
||||
runner.Tick(0.5d);
|
||||
Assert.Empty(runtime.ScreenshotRequests);
|
||||
|
||||
runtime.RenderPackPerformanceSampleCount = 2048;
|
||||
runner.Tick(0.001d);
|
||||
Assert.Contains("complete-window", runtime.ScreenshotRequests);
|
||||
Assert.False(runner.Completed);
|
||||
|
||||
runtime.CompletedScreenshots.Add("complete-window");
|
||||
runner.Tick(0.001d);
|
||||
Assert.True(runner.Completed);
|
||||
}
|
||||
finally
|
||||
{
|
||||
File.Delete(path);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScriptRunner_renderPackWaitTerminatesEarlyForFailedToRetailFallback()
|
||||
{
|
||||
var (root, _, _, _, objects) = RootWithTwoItemLists();
|
||||
var runtime = new FakeRuntime
|
||||
{
|
||||
RenderPackFailedToRetail = true,
|
||||
};
|
||||
var probe = new RetailUiAutomationProbe(root, objects);
|
||||
string path = Path.Combine(
|
||||
Path.GetTempPath(),
|
||||
Path.GetRandomFileName() + ".ui-probe.txt");
|
||||
File.WriteAllLines(path,
|
||||
[
|
||||
"renderpack reset-performance",
|
||||
"wait render-pack-samples 2048 300000",
|
||||
"screenshot safe-fallback 1000",
|
||||
]);
|
||||
|
||||
try
|
||||
{
|
||||
using var runner = new RetailUiAutomationScriptRunner(
|
||||
probe,
|
||||
path,
|
||||
dumpOnStart: false,
|
||||
runtime: runtime);
|
||||
|
||||
runner.Tick(0d);
|
||||
|
||||
Assert.Equal(1, runtime.RenderPackPerformanceResetCount);
|
||||
Assert.Equal(0, runtime.RenderPackPerformanceSampleCount);
|
||||
Assert.Contains("safe-fallback", runtime.ScreenshotRequests);
|
||||
Assert.False(runner.Completed);
|
||||
|
||||
runtime.CompletedScreenshots.Add("safe-fallback");
|
||||
runner.Tick(0.001d);
|
||||
Assert.True(runner.Completed);
|
||||
}
|
||||
finally
|
||||
{
|
||||
File.Delete(path);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScriptRunner_renderPackTransitionsAndResizeWaitForPublishedRuntimeState()
|
||||
{
|
||||
var (root, _, _, _, objects) = RootWithTwoItemLists();
|
||||
var runtime = new FakeRuntime();
|
||||
var probe = new RetailUiAutomationProbe(root, objects);
|
||||
string path = Path.Combine(
|
||||
Path.GetTempPath(),
|
||||
Path.GetRandomFileName() + ".ui-probe.txt");
|
||||
File.WriteAllLines(path,
|
||||
[
|
||||
"renderpack select high",
|
||||
"wait render-pack high 1000",
|
||||
"renderpack disable",
|
||||
"wait render-pack retail 1000",
|
||||
"renderpack reenable",
|
||||
"wait render-pack high 1000",
|
||||
"resize 1024 768",
|
||||
"wait framebuffer 1024 768 1000",
|
||||
]);
|
||||
|
||||
try
|
||||
{
|
||||
using var runner = new RetailUiAutomationScriptRunner(
|
||||
probe,
|
||||
path,
|
||||
dumpOnStart: false,
|
||||
runtime: runtime);
|
||||
|
||||
runner.Tick(0d);
|
||||
Assert.Equal(["high"], runtime.RenderPackSelections);
|
||||
Assert.False(runner.Completed);
|
||||
|
||||
runtime.RenderPackStatus = new RetailUiAutomationRenderPackStatus(
|
||||
RetailUiAutomationRenderPackState.Active,
|
||||
"acdream.atmospheric",
|
||||
"high",
|
||||
ActivationGeneration: 1,
|
||||
FailureReason: null);
|
||||
runner.Tick(0.001d);
|
||||
Assert.Equal(1, runtime.RenderPackDisableCount);
|
||||
|
||||
runtime.RenderPackStatus = RetailUiAutomationRenderPackStatus.Retail;
|
||||
runner.Tick(0.001d);
|
||||
Assert.Equal(1, runtime.RenderPackReenableCount);
|
||||
|
||||
runtime.RenderPackStatus = new RetailUiAutomationRenderPackStatus(
|
||||
RetailUiAutomationRenderPackState.Active,
|
||||
"acdream.atmospheric",
|
||||
"high",
|
||||
ActivationGeneration: 3,
|
||||
FailureReason: null);
|
||||
runner.Tick(0.001d);
|
||||
Assert.Equal([(1024, 768)], runtime.FramebufferResizes);
|
||||
Assert.False(runner.Completed);
|
||||
|
||||
runtime.FramebufferWidth = 1024;
|
||||
runtime.FramebufferHeight = 768;
|
||||
runner.Tick(0.001d);
|
||||
Assert.True(runner.Completed);
|
||||
}
|
||||
finally
|
||||
{
|
||||
File.Delete(path);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScriptRunner_closeClientRequestsNormalRuntimeShutdownExactlyOnce()
|
||||
{
|
||||
var (root, _, _, _, objects) = RootWithTwoItemLists();
|
||||
var runtime = new FakeRuntime();
|
||||
var probe = new RetailUiAutomationProbe(root, objects);
|
||||
string path = Path.Combine(
|
||||
Path.GetTempPath(),
|
||||
Path.GetRandomFileName() + ".ui-probe.txt");
|
||||
File.WriteAllText(path, "close-client");
|
||||
|
||||
try
|
||||
{
|
||||
using var runner = new RetailUiAutomationScriptRunner(
|
||||
probe,
|
||||
path,
|
||||
dumpOnStart: false,
|
||||
runtime: runtime);
|
||||
|
||||
runner.Tick(0d);
|
||||
runner.Tick(0d);
|
||||
|
||||
Assert.True(runner.Completed);
|
||||
Assert.Equal(1, runtime.ClientCloseRequestCount);
|
||||
}
|
||||
finally
|
||||
{
|
||||
File.Delete(path);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(RetailUiAutomationCheckpointStatus.Failed, "deferred write failed")]
|
||||
[InlineData(RetailUiAutomationCheckpointStatus.Cancelled, "shutdown cancelled")]
|
||||
|
|
|
|||
|
|
@ -77,6 +77,29 @@ public sealed class UiTemplateListBoxViewportTests
|
|||
Assert.True(row2.Visible, "row 2 culled — the #372 blank-tab bug");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RemoveTail_PreservesPrefixAndRestacksFutureRows()
|
||||
{
|
||||
var box = MakeListBox(276f, 560f);
|
||||
var prefix = new UiText { Width = 260f, Height = 20f };
|
||||
var removedA = new UiText { Width = 260f, Height = 30f };
|
||||
var removedB = new UiText { Width = 260f, Height = 40f };
|
||||
box.AddPrebuiltRow(prefix);
|
||||
box.AddPrebuiltRow(removedA);
|
||||
box.AddPrebuiltRow(removedB);
|
||||
|
||||
box.RemoveTail(1);
|
||||
var replacement = new UiText { Width = 260f, Height = 25f };
|
||||
box.AddPrebuiltRow(replacement);
|
||||
|
||||
Assert.Equal(2, box.ItemCount);
|
||||
Assert.Same(prefix, box.ViewportForTest!.Children[0]);
|
||||
Assert.Same(replacement, box.ViewportForTest.Children[1]);
|
||||
Assert.Equal(20f, replacement.Top);
|
||||
Assert.Null(removedA.Parent);
|
||||
Assert.Null(removedB.Parent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// #412-class regression (2026-08-16, overnight hover/UI round, Batch A bug
|
||||
/// 2): the Options panel's Config tab escaped past the window frame — the
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue