feat(rendering): compare the incremental shadow scene

Construct Slice F's non-drawing scene only for lifecycle automation, drain accepted static and live deltas at the final update boundary, and compare exact current-path fingerprints at cadence and checkpoints. Publish bounded mismatch, journal, index, digest, and memory evidence without changing normal launches or draw submission.

Release: 8,211 passed, 5 skipped.
This commit is contained in:
Erik 2026-07-24 22:24:30 +02:00
parent ff5d86175f
commit 0eb6648589
21 changed files with 937 additions and 20 deletions

View file

@ -38,12 +38,14 @@ public sealed class FrameRootCompositionTests
AssertAppearsInOrder(
source,
"new RenderFrameResourceController(",
"new RenderSceneShadowComparisonController(",
"new WorldSceneRenderer(",
"new WorldLifecycleAutomationController(",
"\"world lifecycle automation owner\"",
"\"world lifecycle automation binding\"",
"new SerialRenderFramePostDiagnosticsPhase(",
"new RenderFrameOrchestrator(",
"(IRenderFramePostDiagnosticsPhase?)lifecycleAutomation",
"postDiagnostics,",
"new RetailLiveFrameCoordinator(",
"new UpdateFrameOrchestrator(",
"d.FrameGraphs.PublishOwned(",

View file

@ -144,6 +144,15 @@ public sealed class WorldLifecycleAutomationControllerTests
AbortedSelectionFrames: 0,
SelectionPartCount: 7,
SelectionDigest: new RenderSceneHash128(15, 16)),
RenderSceneShadow =
RenderSceneShadowComparisonSnapshot.Disabled with
{
Enabled = true,
ComparisonCount = 19,
SuccessfulComparisonCount = 19,
ComparedOracleFrameSequence = 19,
MatchedProjectionCount = 73,
},
TrackedGpuBytes = 1234,
Residency = new ResidencySnapshot(
[
@ -214,6 +223,20 @@ public sealed class WorldLifecycleAutomationControllerTests
0xFEDCBA9876543210uL,
renderSceneOracle.GetProperty("digest").GetProperty("high")
.GetUInt64());
JsonElement renderSceneShadow = json.RootElement
.GetProperty("resources")
.GetProperty("renderSceneShadow");
Assert.True(renderSceneShadow.GetProperty("enabled").GetBoolean());
Assert.Equal(
19,
renderSceneShadow.GetProperty("comparisonCount").GetInt64());
Assert.Equal(
73,
renderSceneShadow.GetProperty("matchedProjectionCount")
.GetInt32());
Assert.Equal(
0,
renderSceneShadow.GetProperty("mismatchCount").GetInt64());
JsonElement residency = json.RootElement
.GetProperty("resources")
.GetProperty("residency");
@ -353,6 +376,7 @@ public sealed class WorldLifecycleAutomationControllerTests
private static WorldLifecycleResourceSnapshot EmptyResources() => new(
0, 0, 0, 0, 0, 0, 0, CurrentRenderSceneOracleSnapshot.Disabled,
RenderSceneShadowComparisonSnapshot.Disabled,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0L, 0, 0L, 0L, 0L, 0L, 0, 0, 0, 0, 0, 0, 0, 0L, 0L,
0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L,

View file

@ -269,6 +269,7 @@ public sealed class LiveSessionHostTests
InboundEventFifo = noop,
LiveLiveness = noop,
LiveRuntime = noop,
RenderSceneProjection = noop,
SessionIdentity = noop,
RemoteTeleport = noop,
NetworkEffects = noop,

View file

@ -298,6 +298,7 @@ public sealed class LiveSessionResetPlanTests
LiveRuntime = Stage(
"live runtime",
() => LiveSessionEntityRuntimeReset.ClearAndRequireConvergence(live)),
RenderSceneProjection = Stage("render scene projection"),
SessionIdentity = Stage("session identity", () =>
{
LiveSessionEntityRuntimeReset.RequireConvergence(live);
@ -378,6 +379,7 @@ public sealed class LiveSessionResetPlanTests
"inbound event fifo",
"live liveness",
"live runtime",
"render scene projection",
"session identity",
"remote teleport",
"network effects",

View file

@ -0,0 +1,224 @@
using System.Numerics;
using AcDream.App.Rendering;
using AcDream.App.Rendering.Scene;
using AcDream.Core.World;
namespace AcDream.App.Tests.Rendering;
public sealed class RenderSceneShadowRuntimeTests
{
private const uint Landblock = 0xA9B4FFFF;
private const uint Cell = 0xA9B40170;
[Fact]
public void ExactCurrentProjection_MatchesAndAcknowledgesDirtyWorkset()
{
WorldEntity entity = Entity(17, serverGuid: 0, parentCell: null);
CurrentRenderSceneOracle oracle = Capture(
entity,
visibleCells: []);
using var shadow = new RenderSceneShadowRuntime(Generation(1));
Register(shadow, entity, RenderProjectionClass.OutdoorStatic);
Assert.Equal(1, shadow.DrainUpdateBoundary().Registered);
var controller = new RenderSceneShadowComparisonController(
shadow,
oracle);
RenderSceneShadowComparisonSnapshot snapshot =
controller.CaptureCheckpointSnapshot();
Assert.True(snapshot.Enabled);
Assert.Equal(1uL, snapshot.ComparisonCount);
Assert.Equal(1uL, snapshot.SuccessfulComparisonCount);
Assert.Equal(0, snapshot.MismatchCount);
Assert.Equal(1, snapshot.MatchedProjectionCount);
Assert.Equal(0, snapshot.PendingDeltaCount);
Assert.Equal(0, snapshot.SceneIndexCounts.Dirty);
Assert.Equal(1, snapshot.SceneCounts.Total);
Assert.True(snapshot.Memory.TotalEstimatedBytes > 0);
}
[Fact]
public void FirstExactMismatch_IsNamedAndLoggedOnlyOnce()
{
WorldEntity entity = Entity(18, serverGuid: 0, parentCell: null);
CurrentRenderSceneOracle oracle = Capture(entity, []);
using var shadow = new RenderSceneShadowRuntime(Generation(1));
RenderProjectionRecord record = Project(
entity,
RenderProjectionClass.OutdoorStatic) with
{
Source = Project(
entity,
RenderProjectionClass.OutdoorStatic).Source with
{
TransformFingerprint = new RenderSceneHash128(91, 92),
},
};
shadow.Journal.Register(in record);
shadow.DrainUpdateBoundary();
var logs = new List<string>();
var controller = new RenderSceneShadowComparisonController(
shadow,
oracle,
logs.Add);
controller.CompareNow();
controller.CompareNow();
RenderSceneShadowComparisonSnapshot snapshot = controller.Snapshot;
Assert.Equal(2uL, snapshot.ComparisonCount);
Assert.Equal(0uL, snapshot.SuccessfulComparisonCount);
Assert.Equal(2, snapshot.MismatchCount);
Assert.Contains("sourceChannel=static", snapshot.FirstMismatch);
Assert.Contains("field=transform", snapshot.FirstMismatch);
Assert.Single(logs);
}
[Fact]
public void NonFloodedIndoorStatic_IsAValidRetainedSceneExtra()
{
WorldEntity entity = Entity(19, serverGuid: 0, parentCell: Cell);
CurrentRenderSceneOracle oracle = Capture(entity, []);
Assert.Empty(oracle.Projections);
using var shadow = new RenderSceneShadowRuntime(Generation(1));
Register(shadow, entity, RenderProjectionClass.IndoorCellStatic);
shadow.DrainUpdateBoundary();
var controller = new RenderSceneShadowComparisonController(
shadow,
oracle);
controller.CompareNow();
Assert.Equal(1uL, controller.Snapshot.SuccessfulComparisonCount);
Assert.Equal(0, controller.Snapshot.MismatchCount);
}
[Fact]
public void DrawnOutdoorExtra_IsReportedAsStaleProjection()
{
WorldEntity expected = Entity(20, serverGuid: 0, parentCell: null);
WorldEntity extra = Entity(21, serverGuid: 0, parentCell: null);
CurrentRenderSceneOracle oracle = Capture(expected, []);
using var shadow = new RenderSceneShadowRuntime(Generation(1));
Register(shadow, expected, RenderProjectionClass.OutdoorStatic);
Register(shadow, extra, RenderProjectionClass.OutdoorStatic);
shadow.DrainUpdateBoundary();
var controller = new RenderSceneShadowComparisonController(
shadow,
oracle);
controller.CompareNow();
Assert.Equal(1, controller.Snapshot.MismatchCount);
Assert.Contains("expected=absent actual=drawn", controller.Snapshot.FirstMismatch);
}
[Fact]
public void RejectedDelta_IsAComparisonFailureEvenWhenSceneIsEmpty()
{
CurrentRenderSceneOracle oracle = CaptureNoEntities();
using var shadow = new RenderSceneShadowRuntime(Generation(1));
WorldEntity missing = Entity(22, serverGuid: 0, parentCell: null);
RenderProjectionRecord record = Project(
missing,
RenderProjectionClass.OutdoorStatic);
shadow.Journal.Update(
RenderProjectionDeltaKind.UpdateTransform,
in record);
RenderDeltaApplyResult apply = shadow.DrainUpdateBoundary();
Assert.Equal(1, apply.RejectedMissing);
var controller = new RenderSceneShadowComparisonController(
shadow,
oracle);
controller.CompareNow();
Assert.Equal(1, controller.Snapshot.MismatchCount);
Assert.Contains("rejectedDeltas=1", controller.Snapshot.FirstMismatch);
}
private static void Register(
RenderSceneShadowRuntime shadow,
WorldEntity entity,
RenderProjectionClass projectionClass)
{
RenderProjectionRecord record = Project(entity, projectionClass);
shadow.Journal.Register(in record);
}
private static RenderProjectionRecord Project(
WorldEntity entity,
RenderProjectionClass projectionClass) =>
RenderProjectionRecordFactory.ProjectEntity(
StaticRenderProjectionJournal.StaticEntityId(
Landblock,
entity.Id),
projectionClass,
RenderOwnerIncarnation.FromRaw(entity.Id),
Landblock,
entity.ParentCellId ?? Landblock,
entity,
spatiallyVisible: true);
private static CurrentRenderSceneOracle Capture(
WorldEntity entity,
HashSet<uint> visibleCells)
{
var oracle = new CurrentRenderSceneOracle();
var result = new InteriorEntityPartition.Result();
InteriorEntityPartition.Partition(
result,
visibleCells,
[
(
Landblock,
Vector3.Zero,
Vector3.One,
(IReadOnlyList<WorldEntity>)[entity],
(IReadOnlyDictionary<uint, WorldEntity>?)null),
],
oracle);
return oracle;
}
private static CurrentRenderSceneOracle CaptureNoEntities()
{
var oracle = new CurrentRenderSceneOracle();
var result = new InteriorEntityPartition.Result();
InteriorEntityPartition.Partition(
result,
[],
Array.Empty<(
uint LandblockId,
Vector3 AabbMin,
Vector3 AabbMax,
IReadOnlyList<WorldEntity> Entities,
IReadOnlyDictionary<uint, WorldEntity>? AnimatedById)>(),
oracle);
return oracle;
}
private static RenderSceneGeneration Generation(ulong value) =>
RenderSceneGeneration.FromRaw(value);
private static WorldEntity Entity(
uint id,
uint serverGuid,
uint? parentCell) =>
new()
{
Id = id,
ServerGuid = serverGuid,
SourceGfxObjOrSetupId = 0x02000001u,
Position = new Vector3(1, 2, 3),
Rotation = Quaternion.Identity,
MeshRefs =
[
new MeshRef(
0x01000001u,
Matrix4x4.CreateTranslation(0.25f, 0.5f, 0.75f)),
],
ParentCellId = parentCell,
};
}