feat(render): implement Campaign AR and terrain fidelity

This commit is contained in:
Erik 2026-08-22 13:13:29 +02:00
parent 99cf26e00c
commit 7a5f96ede5
368 changed files with 50611 additions and 950 deletions

View file

@ -23,6 +23,29 @@ public interface IRetailUiAutomationCheckpoint
string? Error { get; }
}
public enum RetailUiAutomationRenderPackState
{
Retail,
CandidatePending,
Active,
FailedToRetail,
}
public readonly record struct RetailUiAutomationRenderPackStatus(
RetailUiAutomationRenderPackState State,
string PackId,
string PresetId,
long ActivationGeneration,
string? FailureReason)
{
public static RetailUiAutomationRenderPackStatus Retail { get; } = new(
RetailUiAutomationRenderPackState.Retail,
"retail",
"off",
ActivationGeneration: 0,
FailureReason: null);
}
/// <summary>
/// Narrow bridge from retained-UI scripts to render/world lifecycle
/// diagnostics. Implementations run on the same update/render thread as the
@ -33,6 +56,42 @@ public interface IRetailUiAutomationRuntime
bool IsWorldReady { get; }
bool IsWorldViewportVisible { get; }
int PortalMaterializationCount { get; }
int RenderPackPerformanceSampleCount => 0;
bool RenderPackFailedToRetail => false;
RetailUiAutomationRenderPackStatus RenderPackStatus =>
RetailUiAutomationRenderPackStatus.Retail;
int FramebufferWidth => 0;
int FramebufferHeight => 0;
bool TrySelectRenderPack(string presetId, out string error)
{
error = "render-pack selection automation is unavailable";
return false;
}
bool TryDisableRenderPack(out string error)
{
error = "render-pack selection automation is unavailable";
return false;
}
bool TryReenableRenderPack(out string error)
{
error = "render-pack selection automation is unavailable";
return false;
}
bool TryResizeFramebuffer(int width, int height, out string error)
{
error = "framebuffer resize automation is unavailable";
return false;
}
bool TryResetRenderPackPerformance(out string error)
{
error = "render-pack performance automation is unavailable";
return false;
}
bool TryRequestClientClose(out string error)
{
error = "client-close automation is unavailable";
return false;
}
bool TryRequestCheckpoint(
string name,
out IRetailUiAutomationCheckpoint? checkpoint,
@ -205,7 +264,10 @@ public sealed class RetailUiAutomationScriptRunner : IDisposable
"command" => DoCommand(command),
"input" => DoInput(command),
"checkpoint" => DoCheckpoint(command),
"renderpack" => DoRenderPack(command),
"resize" => DoResize(command),
"screenshot" => DoScreenshot(command),
"close-client" => DoCloseClient(command),
_ => Stop(command, $"unknown command '{p[0]}'"),
};
}
@ -333,7 +395,7 @@ public sealed class RetailUiAutomationScriptRunner : IDisposable
private bool DoWait(ScriptCommand command)
{
var p = command.Parts;
if (p.Length < 2) return Stop(command, "usage: wait item|element|ms|world-ready|world-visible|materialized ...");
if (p.Length < 2) return Stop(command, "usage: wait item|element|ms|world-ready|world-visible|materialized|render-pack|render-pack-samples|framebuffer ...");
string target = p[1].ToLowerInvariant();
if (target == "item")
@ -376,7 +438,168 @@ public sealed class RetailUiAutomationScriptRunner : IDisposable
return WaitOrTimeout(command, TimeoutMs(p, 3, 60000), $"portal materialization {occurrence}");
}
return Stop(command, "usage: wait item|element|ms|world-ready|world-visible|materialized ...");
if (target == "render-pack-samples")
{
if (_runtime is null)
return Stop(command, "render-pack performance automation is unavailable");
if (p.Length < 3
|| !TryParseInt(p[2], out int required)
|| required <= 0)
{
return Stop(
command,
"usage: wait render-pack-samples <count> [timeoutMs]");
}
if (_runtime.RenderPackPerformanceSampleCount >= required)
return true;
// An unavailable preset cannot ever fill an enhanced performance
// window. Finish this wait immediately so the following screenshot
// captures the controller's fully resource-free default fallback;
// the harness validates the exact failure class and zero-work
// metadata instead of turning every fallback into a five-minute
// timeout.
if (_runtime.RenderPackFailedToRetail)
return true;
return WaitOrTimeout(
command,
TimeoutMs(p, 3, 300000),
$"{required} render-pack performance samples");
}
if (target == "render-pack")
{
if (_runtime is null)
return Stop(command, "render-pack selection automation is unavailable");
if (p.Length < 3 || !TryNormalizeRenderPackPreset(p[2], out string preset))
{
return Stop(
command,
"usage: wait render-pack retail|low|medium|high|auto [timeoutMs]");
}
RetailUiAutomationRenderPackStatus status = _runtime.RenderPackStatus;
bool expectRetail = string.Equals(
preset,
"retail",
StringComparison.Ordinal);
if (expectRetail
&& status.State == RetailUiAutomationRenderPackState.Retail
&& string.Equals(status.PackId, "retail", StringComparison.OrdinalIgnoreCase)
&& string.Equals(status.PresetId, "off", StringComparison.OrdinalIgnoreCase))
{
return true;
}
if (!expectRetail
&& status.State == RetailUiAutomationRenderPackState.Active
&& string.Equals(
status.PackId,
"acdream.atmospheric",
StringComparison.OrdinalIgnoreCase)
&& string.Equals(status.PresetId, preset, StringComparison.OrdinalIgnoreCase))
{
return true;
}
if (status.State == RetailUiAutomationRenderPackState.FailedToRetail)
{
return Stop(
command,
$"render pack '{preset}' failed to retail: "
+ (status.FailureReason ?? "no failure reason was published"));
}
return WaitOrTimeout(
command,
TimeoutMs(p, 3, 90000),
$"render pack '{preset}' activation");
}
if (target == "framebuffer")
{
if (_runtime is null)
return Stop(command, "framebuffer resize automation is unavailable");
if (p.Length < 4
|| !TryParseInt(p[2], out int width)
|| !TryParseInt(p[3], out int height)
|| width <= 0
|| height <= 0)
{
return Stop(
command,
"usage: wait framebuffer <width> <height> [timeoutMs]");
}
if (_runtime.FramebufferWidth == width
&& _runtime.FramebufferHeight == height)
{
return true;
}
return WaitOrTimeout(
command,
TimeoutMs(p, 4, 30000),
$"framebuffer {width}x{height}");
}
return Stop(command, "usage: wait item|element|ms|world-ready|world-visible|materialized|render-pack|render-pack-samples|framebuffer ...");
}
private bool DoRenderPack(ScriptCommand command)
{
if (_runtime is null)
return Stop(command, "render-pack automation is unavailable");
var p = command.Parts;
if (p.Length == 2
&& string.Equals(p[1], "reset-performance", StringComparison.OrdinalIgnoreCase))
{
return _runtime.TryResetRenderPackPerformance(out string error)
|| Stop(command, error);
}
if (p.Length == 3
&& string.Equals(p[1], "select", StringComparison.OrdinalIgnoreCase)
&& TryNormalizeRenderPackPreset(p[2], out string preset))
{
return _runtime.TrySelectRenderPack(preset, out string error)
|| Stop(command, error);
}
if (p.Length == 2
&& string.Equals(p[1], "disable", StringComparison.OrdinalIgnoreCase))
{
return _runtime.TryDisableRenderPack(out string error)
|| Stop(command, error);
}
if (p.Length == 2
&& string.Equals(p[1], "reenable", StringComparison.OrdinalIgnoreCase))
{
return _runtime.TryReenableRenderPack(out string error)
|| Stop(command, error);
}
return Stop(
command,
"usage: renderpack reset-performance | renderpack select retail|low|medium|high|auto | renderpack disable | renderpack reenable");
}
private bool DoResize(ScriptCommand command)
{
if (_runtime is null)
return Stop(command, "framebuffer resize automation is unavailable");
var p = command.Parts;
if (p.Length != 3
|| !TryParseInt(p[1], out int width)
|| !TryParseInt(p[2], out int height)
|| width <= 0
|| height <= 0)
{
return Stop(command, "usage: resize <width> <height>");
}
return _runtime.TryResizeFramebuffer(width, height, out string error)
|| Stop(command, error);
}
private static bool TryNormalizeRenderPackPreset(
string value,
out string preset)
{
preset = value.ToLowerInvariant();
if (preset == "off")
preset = "retail";
return preset is "retail" or "low" or "medium" or "high" or "auto";
}
private bool DoSleep(ScriptCommand command)
@ -540,6 +763,16 @@ public sealed class RetailUiAutomationScriptRunner : IDisposable
return WaitOrTimeout(command, TimeoutMs(command.Parts, 2, 10000), $"screenshot '{name}'");
}
private bool DoCloseClient(ScriptCommand command)
{
if (command.Parts.Length != 1)
return Stop(command, "usage: close-client");
if (_runtime is null)
return Stop(command, "client-close automation is unavailable");
return _runtime.TryRequestClientClose(out string error)
|| Stop(command, error);
}
private bool WaitOrTimeout(ScriptCommand command, int timeoutMs, string label)
{
if (!HasExceeded(timeoutMs)) return false;