test(app): add canonical connected soak snapshots

Make scripted lifecycle checkpoints acknowledged post-diagnostics render barriers, capture the exact frame outcome beside canonical resource ownership, and harden the nine-stop route with ordered same-location cache and lifetime gates without weakening process residency thresholds.

Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-22 20:01:06 +02:00
parent 2862622ba2
commit bca4148739
17 changed files with 995 additions and 52 deletions

View file

@ -1,4 +1,5 @@
using System.Text.Json;
using AcDream.App.Rendering;
using AcDream.App.Streaming;
using AcDream.App.UI.Testing;
@ -71,14 +72,73 @@ internal sealed record WorldLifecycleCheckpoint(
DateTime TimestampUtc,
int ProcessId,
WorldRevealLifecycleSnapshot Reveal,
RenderFrameOutcome Render,
WorldLifecycleResourceSnapshot Resources);
internal sealed class WorldLifecycleCheckpointRequest :
IRetailUiAutomationCheckpoint
{
private readonly object _sync = new();
private RetailUiAutomationCheckpointStatus _status =
RetailUiAutomationCheckpointStatus.Pending;
private string? _error;
public WorldLifecycleCheckpointRequest(object owner, int sequence, string name)
{
Owner = owner ?? throw new ArgumentNullException(nameof(owner));
Sequence = sequence;
Name = name ?? throw new ArgumentNullException(nameof(name));
}
internal object Owner { get; }
public int Sequence { get; }
public string Name { get; }
public RetailUiAutomationCheckpointStatus Status
{
get
{
lock (_sync)
return _status;
}
}
public string? Error
{
get
{
lock (_sync)
return _error;
}
}
internal bool TrySetTerminal(
RetailUiAutomationCheckpointStatus status,
string? error)
{
if (status == RetailUiAutomationCheckpointStatus.Pending)
throw new ArgumentOutOfRangeException(nameof(status));
lock (_sync)
{
if (_status != RetailUiAutomationCheckpointStatus.Pending)
return false;
_status = status;
_error = error;
return true;
}
}
}
/// <summary>
/// Diagnostic-only runtime seam used by production retained-UI scripts. It
/// writes one structured checkpoint at explicit script edges and delegates GL
/// capture to the render-thread screenshot owner.
/// </summary>
internal sealed class WorldLifecycleAutomationController : IRetailUiAutomationRuntime
internal sealed class WorldLifecycleAutomationController :
IRetailUiAutomationRuntime,
IRenderFramePostDiagnosticsPhase,
IDisposable
{
private static readonly JsonSerializerOptions JsonOptions = new()
{
@ -87,16 +147,21 @@ internal sealed class WorldLifecycleAutomationController : IRetailUiAutomationRu
private readonly Func<WorldRevealLifecycleSnapshot> _getReveal;
private readonly Func<int> _getPortalMaterializationCount;
private readonly Func<WorldLifecycleResourceSnapshot> _captureResources;
private readonly Func<RenderFrameOutcome, WorldLifecycleResourceSnapshot>
_captureResources;
private readonly FrameScreenshotController _screenshots;
private readonly string _artifactDirectory;
private readonly Action<string> _log;
private readonly object _requestOwner = new();
private readonly object _sync = new();
private readonly Queue<WorldLifecycleCheckpointRequest> _requests = [];
private int _sequence;
private bool _disposed;
public WorldLifecycleAutomationController(
Func<WorldRevealLifecycleSnapshot> getReveal,
Func<int> getPortalMaterializationCount,
Func<WorldLifecycleResourceSnapshot> captureResources,
Func<RenderFrameOutcome, WorldLifecycleResourceSnapshot> captureResources,
FrameScreenshotController screenshots,
string artifactDirectory,
Action<string>? log = null)
@ -116,35 +181,127 @@ internal sealed class WorldLifecycleAutomationController : IRetailUiAutomationRu
public bool IsWorldViewportVisible => _getReveal().WorldViewportObserved;
public int PortalMaterializationCount => _getPortalMaterializationCount();
public bool TryWriteCheckpoint(string name, out string error)
public bool TryRequestCheckpoint(
string name,
out IRetailUiAutomationCheckpoint? checkpoint,
out string error)
{
checkpoint = null;
if (!AutomationArtifactName.TryValidate(name, out error))
return false;
lock (_sync)
{
if (_disposed)
{
error = $"checkpoint '{name}' was rejected because world lifecycle automation is shutting down";
return false;
}
var request = new WorldLifecycleCheckpointRequest(
_requestOwner,
checked(++_sequence),
name);
_requests.Enqueue(request);
checkpoint = request;
error = string.Empty;
return true;
}
}
public void CancelCheckpoint(IRetailUiAutomationCheckpoint checkpoint)
{
ArgumentNullException.ThrowIfNull(checkpoint);
if (checkpoint is not WorldLifecycleCheckpointRequest request
|| !ReferenceEquals(request.Owner, _requestOwner))
{
throw new ArgumentException(
"The checkpoint acknowledgement is not owned by this automation runtime.",
nameof(checkpoint));
}
lock (_sync)
{
request.TrySetTerminal(
RetailUiAutomationCheckpointStatus.Cancelled,
$"checkpoint '{request.Name}' was cancelled before render-frame capture");
}
}
public void Process(RenderFrameInput input, RenderFrameOutcome outcome)
{
lock (_sync)
{
if (_disposed)
return;
while (_requests.Count != 0)
{
WorldLifecycleCheckpointRequest request = _requests.Dequeue();
if (request.Status != RetailUiAutomationCheckpointStatus.Pending)
continue;
WriteCheckpoint(request, outcome);
}
}
}
private void WriteCheckpoint(
WorldLifecycleCheckpointRequest request,
RenderFrameOutcome outcome)
{
try
{
Directory.CreateDirectory(_artifactDirectory);
var checkpoint = new WorldLifecycleCheckpoint(
Sequence: checked(++_sequence),
Name: name,
Sequence: request.Sequence,
Name: request.Name,
TimestampUtc: DateTime.UtcNow,
ProcessId: Environment.ProcessId,
Reveal: _getReveal(),
Resources: _captureResources());
Render: outcome,
Resources: _captureResources(outcome));
string json = JsonSerializer.Serialize(checkpoint, JsonOptions);
string timelinePath = Path.Combine(_artifactDirectory, "world-lifecycle.checkpoints.jsonl");
File.AppendAllText(timelinePath, json + Environment.NewLine);
string timelinePath = Path.Combine(
_artifactDirectory,
"world-lifecycle.checkpoints.jsonl");
File.WriteAllText(
Path.Combine(_artifactDirectory, $"checkpoint-{name}.json"),
Path.Combine(
_artifactDirectory,
$"checkpoint-{request.Name}.json"),
json + Environment.NewLine);
_log($"[world-gate] checkpoint name={name} sequence={checkpoint.Sequence} path={timelinePath}");
error = string.Empty;
return true;
// Timeline publication is the commit edge: a visible row always
// has its complete named checkpoint artifact beside it.
File.AppendAllText(timelinePath, json + Environment.NewLine);
request.TrySetTerminal(
RetailUiAutomationCheckpointStatus.Succeeded,
error: null);
_log(
$"[world-gate] checkpoint name={request.Name} "
+ $"sequence={request.Sequence} path={timelinePath}");
}
catch (Exception exception)
{
error = $"checkpoint '{name}' failed: {exception.Message}";
return false;
request.TrySetTerminal(
RetailUiAutomationCheckpointStatus.Failed,
$"checkpoint '{request.Name}' sequence {request.Sequence} failed: "
+ exception.Message);
}
}
public void Dispose()
{
lock (_sync)
{
if (_disposed)
return;
_disposed = true;
while (_requests.Count != 0)
{
WorldLifecycleCheckpointRequest request = _requests.Dequeue();
request.TrySetTerminal(
RetailUiAutomationCheckpointStatus.Cancelled,
$"checkpoint '{request.Name}' was cancelled during world lifecycle automation shutdown");
}
}
}

View file

@ -70,7 +70,7 @@ internal sealed class WorldLifecycleResourceSnapshotSource
?? throw new ArgumentNullException(nameof(frameProfiler));
}
public WorldLifecycleResourceSnapshot Capture()
public WorldLifecycleResourceSnapshot Capture(RenderFrameOutcome outcome)
{
ObjectMeshManager meshManager = _meshes.MeshManager
?? throw new InvalidOperationException(
@ -82,8 +82,8 @@ internal sealed class WorldLifecycleResourceSnapshotSource
LoadedLandblocks: _world.LoadedLandblockIds.Count,
WorldEntities: _world.Entities.Count,
AnimatedEntities: _animations.Count,
VisibleLandblocks: render.VisibleLandblocks,
TotalLandblocks: render.TotalLandblocks,
VisibleLandblocks: outcome.World.VisibleLandblocks,
TotalLandblocks: outcome.World.TotalLandblocks,
LiveEntities: _liveEntities.Count,
MaterializedLiveEntities: _liveEntities.MaterializedCount,
PendingLiveTeardowns: _liveEntities.PendingTeardownCount,