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:
parent
ff5d86175f
commit
0eb6648589
21 changed files with 937 additions and 20 deletions
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue