diff --git a/src/AcDream.App/Composition/FrameRootComposition.cs b/src/AcDream.App/Composition/FrameRootComposition.cs index 1bdb3105..07cba262 100644 --- a/src/AcDream.App/Composition/FrameRootComposition.cs +++ b/src/AcDream.App/Composition/FrameRootComposition.cs @@ -354,7 +354,6 @@ internal sealed class FrameRootCompositionPhase // contract cleanup later in this campaign. CurrentRenderSceneOracle? currentRenderSceneOracle = null; RenderSceneShadowComparisonController? renderSceneShadowComparison = null; - RenderScenePViewFrameProductController? renderFrameProduct = null; { // Campaign V slice V6j: the world scene is composed on BOTH arms. // Every renderer below now exists on Vulkan too; what forks is one @@ -607,8 +606,7 @@ internal sealed class FrameRootCompositionPhase ?? throw new InvalidOperationException( "Lifecycle automation requires the canonical physics cache."), currentRenderSceneOracle, - renderSceneShadowComparison, - renderFrameProduct); + renderSceneShadowComparison); lifecycleAutomation = new WorldLifecycleAutomationController( () => session.WorldReveal.Snapshot, diff --git a/src/AcDream.App/Diagnostics/WorldLifecycleResourceSnapshotSource.cs b/src/AcDream.App/Diagnostics/WorldLifecycleResourceSnapshotSource.cs index 7af0f706..b38145c3 100644 --- a/src/AcDream.App/Diagnostics/WorldLifecycleResourceSnapshotSource.cs +++ b/src/AcDream.App/Diagnostics/WorldLifecycleResourceSnapshotSource.cs @@ -47,8 +47,6 @@ internal sealed class WorldLifecycleResourceSnapshotSource _renderSceneOracle; private readonly IRenderSceneShadowSnapshotSource? _renderSceneShadow; - private readonly IRenderFrameProductSnapshotSource? - _renderFrameProduct; public WorldLifecycleResourceSnapshotSource( GpuWorldState world, @@ -69,8 +67,7 @@ internal sealed class WorldLifecycleResourceSnapshotSource ResidencyManager residency, PhysicsDataCache physics, ICurrentRenderSceneOracleSnapshotSource? renderSceneOracle = null, - IRenderSceneShadowSnapshotSource? renderSceneShadow = null, - IRenderFrameProductSnapshotSource? renderFrameProduct = null) + IRenderSceneShadowSnapshotSource? renderSceneShadow = null) { _world = world ?? throw new ArgumentNullException(nameof(world)); _animations = animations @@ -99,7 +96,6 @@ internal sealed class WorldLifecycleResourceSnapshotSource _physics = physics ?? throw new ArgumentNullException(nameof(physics)); _renderSceneOracle = renderSceneOracle; _renderSceneShadow = renderSceneShadow; - _renderFrameProduct = renderFrameProduct; } public WorldLifecycleResourceSnapshot Capture(RenderFrameOutcome outcome) @@ -151,9 +147,7 @@ internal sealed class WorldLifecycleResourceSnapshotSource RenderSceneShadow: _renderSceneShadow?.CaptureCheckpointSnapshot() ?? RenderSceneShadowComparisonSnapshot.Disabled, - RenderFrameProduct: - _renderFrameProduct?.Snapshot - ?? RenderFrameProductComparisonSnapshot.Disabled, + RenderFrameProduct: RenderFrameProductComparisonSnapshot.Disabled, PendingLiveTeardowns: _liveEntities.PendingTeardownCount, PendingLandblockRetirements: _streaming.PendingRetirementCount, ParticleEmitters: _particles.ActiveEmitterCount, diff --git a/src/AcDream.App/Rendering/Scene/RenderFrameProductComparisonSnapshot.cs b/src/AcDream.App/Rendering/Scene/RenderFrameProductComparisonSnapshot.cs new file mode 100644 index 00000000..0179ba2d --- /dev/null +++ b/src/AcDream.App/Rendering/Scene/RenderFrameProductComparisonSnapshot.cs @@ -0,0 +1,98 @@ +namespace AcDream.App.Rendering.Scene; + +/// +/// Retained lifecycle-automation schema for releases that still deserialize +/// the former packed-frame comparison block. Campaign FW4 removed that +/// producer; new snapshots always publish . +/// +internal readonly record struct RenderFrameProductComparisonSnapshot( + bool Enabled, + ulong ProductFrameSequence, + ulong CurrentPViewFrameSequence, + ulong ComparisonCount, + ulong SuccessfulComparisonCount, + int ExpectedCandidateCount, + int ActualCandidateCount, + long MismatchCount, + string? FirstMismatch, + RenderSceneHash128 ExpectedDigest, + RenderSceneHash128 ActualDigest, + ulong PackedInputComparisonCount, + ulong PackedInputSuccessfulComparisonCount, + int PackedInputExpectedCount, + int PackedInputActualCount, + long PackedInputMismatchCount, + string? PackedInputFirstMismatch, + RenderSceneHash128 PackedInputExpectedDigest, + RenderSceneHash128 PackedInputActualDigest, + ulong ClassifiedOutputComparisonCount, + ulong ClassifiedOutputSuccessfulComparisonCount, + int ClassifiedOutputExpectedDrawCount, + int ClassifiedOutputActualDrawCount, + long ClassifiedOutputMismatchCount, + string? ClassifiedOutputFirstMismatch, + RenderSceneHash128 ClassifiedOutputExpectedDigest, + RenderSceneHash128 ClassifiedOutputActualDigest, + ulong SelectionOutputComparisonCount, + ulong SelectionOutputSuccessfulComparisonCount, + int SelectionOutputExpectedPartCount, + int SelectionOutputActualPartCount, + long SelectionOutputMismatchCount, + string? SelectionOutputFirstMismatch, + RenderSceneHash128 SelectionOutputExpectedDigest, + RenderSceneHash128 SelectionOutputActualDigest, + int PackedClassificationProjectionCount, + int PackedClassificationStaticRebuildCount, + int PackedClassificationSameFrameReuseCount, + int PackedClassificationCrossFrameReuseCount, + int PackedClassificationAnimatedCount, + int PackedClassificationRetiredCount, + long PackedClassificationRetainedPayloadBytes, + RenderFrameDiagnosticCounts ProductCounts) +{ + public static RenderFrameProductComparisonSnapshot Disabled { get; } = + new( + Enabled: false, + ProductFrameSequence: 0, + CurrentPViewFrameSequence: 0, + ComparisonCount: 0, + SuccessfulComparisonCount: 0, + ExpectedCandidateCount: 0, + ActualCandidateCount: 0, + MismatchCount: 0, + FirstMismatch: null, + ExpectedDigest: RenderSceneHash128.Empty, + ActualDigest: RenderSceneHash128.Empty, + PackedInputComparisonCount: 0, + PackedInputSuccessfulComparisonCount: 0, + PackedInputExpectedCount: 0, + PackedInputActualCount: 0, + PackedInputMismatchCount: 0, + PackedInputFirstMismatch: null, + PackedInputExpectedDigest: RenderSceneHash128.Empty, + PackedInputActualDigest: RenderSceneHash128.Empty, + ClassifiedOutputComparisonCount: 0, + ClassifiedOutputSuccessfulComparisonCount: 0, + ClassifiedOutputExpectedDrawCount: 0, + ClassifiedOutputActualDrawCount: 0, + ClassifiedOutputMismatchCount: 0, + ClassifiedOutputFirstMismatch: null, + ClassifiedOutputExpectedDigest: RenderSceneHash128.Empty, + ClassifiedOutputActualDigest: RenderSceneHash128.Empty, + SelectionOutputComparisonCount: 0, + SelectionOutputSuccessfulComparisonCount: 0, + SelectionOutputExpectedPartCount: 0, + SelectionOutputActualPartCount: 0, + SelectionOutputMismatchCount: 0, + SelectionOutputFirstMismatch: null, + SelectionOutputExpectedDigest: RenderSceneHash128.Empty, + SelectionOutputActualDigest: RenderSceneHash128.Empty, + PackedClassificationProjectionCount: 0, + PackedClassificationStaticRebuildCount: 0, + PackedClassificationSameFrameReuseCount: 0, + PackedClassificationCrossFrameReuseCount: 0, + PackedClassificationAnimatedCount: 0, + PackedClassificationRetiredCount: 0, + PackedClassificationRetainedPayloadBytes: 0, + ProductCounts: default); +} diff --git a/src/AcDream.App/Rendering/Scene/RenderScenePViewFrameProduct.cs b/src/AcDream.App/Rendering/Scene/RenderScenePViewFrameProduct.cs deleted file mode 100644 index 1a27aa51..00000000 --- a/src/AcDream.App/Rendering/Scene/RenderScenePViewFrameProduct.cs +++ /dev/null @@ -1,1645 +0,0 @@ -using System.Numerics; -using AcDream.App.Rendering.Wb; -using AcDream.App.Rendering.Walk; - -namespace AcDream.App.Rendering.Scene; - -internal readonly record struct RenderScenePViewBuildInput( - RenderSceneQuery Scene, - RenderSceneDigest SourceDigest, - PortalVisibilityFrame PortalFrame, - ClipFrameAssembly ClipAssembly, - ViewconeCuller Viewcone, - IReadOnlyList LookInCellTurns, - IWalkLookInViewSource? WalkLookInViews, - HashSet DrawableCells, - IRetailPViewCellSource Cells, - HashSet? AnimatedEntityIds, - bool RootIsOutdoor, - IReadOnlySet RootFloodCells); - -internal readonly record struct RenderFrameProductComparisonSnapshot( - bool Enabled, - ulong ProductFrameSequence, - ulong CurrentPViewFrameSequence, - ulong ComparisonCount, - ulong SuccessfulComparisonCount, - int ExpectedCandidateCount, - int ActualCandidateCount, - long MismatchCount, - string? FirstMismatch, - RenderSceneHash128 ExpectedDigest, - RenderSceneHash128 ActualDigest, - ulong PackedInputComparisonCount, - ulong PackedInputSuccessfulComparisonCount, - int PackedInputExpectedCount, - int PackedInputActualCount, - long PackedInputMismatchCount, - string? PackedInputFirstMismatch, - RenderSceneHash128 PackedInputExpectedDigest, - RenderSceneHash128 PackedInputActualDigest, - ulong ClassifiedOutputComparisonCount, - ulong ClassifiedOutputSuccessfulComparisonCount, - int ClassifiedOutputExpectedDrawCount, - int ClassifiedOutputActualDrawCount, - long ClassifiedOutputMismatchCount, - string? ClassifiedOutputFirstMismatch, - RenderSceneHash128 ClassifiedOutputExpectedDigest, - RenderSceneHash128 ClassifiedOutputActualDigest, - ulong SelectionOutputComparisonCount, - ulong SelectionOutputSuccessfulComparisonCount, - int SelectionOutputExpectedPartCount, - int SelectionOutputActualPartCount, - long SelectionOutputMismatchCount, - string? SelectionOutputFirstMismatch, - RenderSceneHash128 SelectionOutputExpectedDigest, - RenderSceneHash128 SelectionOutputActualDigest, - int PackedClassificationProjectionCount, - int PackedClassificationStaticRebuildCount, - int PackedClassificationSameFrameReuseCount, - int PackedClassificationCrossFrameReuseCount, - int PackedClassificationAnimatedCount, - int PackedClassificationRetiredCount, - long PackedClassificationRetainedPayloadBytes, - RenderFrameDiagnosticCounts ProductCounts) -{ - public static RenderFrameProductComparisonSnapshot Disabled { get; } = - new( - Enabled: false, - ProductFrameSequence: 0, - CurrentPViewFrameSequence: 0, - ComparisonCount: 0, - SuccessfulComparisonCount: 0, - ExpectedCandidateCount: 0, - ActualCandidateCount: 0, - MismatchCount: 0, - FirstMismatch: null, - ExpectedDigest: RenderSceneHash128.Empty, - ActualDigest: RenderSceneHash128.Empty, - PackedInputComparisonCount: 0, - PackedInputSuccessfulComparisonCount: 0, - PackedInputExpectedCount: 0, - PackedInputActualCount: 0, - PackedInputMismatchCount: 0, - PackedInputFirstMismatch: null, - PackedInputExpectedDigest: RenderSceneHash128.Empty, - PackedInputActualDigest: RenderSceneHash128.Empty, - ClassifiedOutputComparisonCount: 0, - ClassifiedOutputSuccessfulComparisonCount: 0, - ClassifiedOutputExpectedDrawCount: 0, - ClassifiedOutputActualDrawCount: 0, - ClassifiedOutputMismatchCount: 0, - ClassifiedOutputFirstMismatch: null, - ClassifiedOutputExpectedDigest: RenderSceneHash128.Empty, - ClassifiedOutputActualDigest: RenderSceneHash128.Empty, - SelectionOutputComparisonCount: 0, - SelectionOutputSuccessfulComparisonCount: 0, - SelectionOutputExpectedPartCount: 0, - SelectionOutputActualPartCount: 0, - SelectionOutputMismatchCount: 0, - SelectionOutputFirstMismatch: null, - SelectionOutputExpectedDigest: RenderSceneHash128.Empty, - SelectionOutputActualDigest: RenderSceneHash128.Empty, - PackedClassificationProjectionCount: 0, - PackedClassificationStaticRebuildCount: 0, - PackedClassificationSameFrameReuseCount: 0, - PackedClassificationCrossFrameReuseCount: 0, - PackedClassificationAnimatedCount: 0, - PackedClassificationRetiredCount: 0, - PackedClassificationRetainedPayloadBytes: 0, - ProductCounts: default); -} - -internal interface IRenderFrameProductSnapshotSource -{ - RenderFrameProductComparisonSnapshot Snapshot { get; } -} - -/// -/// Owns the same-frame PView product. Diagnostic composition may compare it -/// with the retired current-path observer; production borrows the exact same -/// arena and supplies its ordered ranges directly to the dispatcher. -/// -internal sealed class RenderScenePViewFrameProductController : - IRenderFrameProductSnapshotSource -{ - private readonly RenderSceneShadowRuntime _shadow; - private readonly CurrentRenderSceneOracle? _current; - - /// Campaign FW3.2b-2: the frame's retained-scene read view for - /// the walk's world-data provider (the same query - /// reads its routes from). - internal RenderSceneQuery SceneQuery => _shadow.Query; - private readonly WbDrawDispatcher? _dispatcher; - private readonly RenderScenePViewFrameBuilder _builder = new(); - private readonly RenderFrameExchange _exchange = new(); - private readonly List _expected = []; - private readonly List _actual = []; - private readonly List _packedExpected = []; - private readonly List _packedActual = []; - private readonly List _selectionExpected = []; - private readonly List _selectionActual = []; - private readonly Action? _log; - private ulong _productFrameSequence; - private ulong _comparisonCount; - private ulong _successfulComparisonCount; - private long _mismatchCount; - private string? _firstMismatch; - private ulong _packedInputComparisonCount; - private ulong _packedInputSuccessfulComparisonCount; - private long _packedInputMismatchCount; - private string? _packedInputFirstMismatch; - private ulong _classifiedOutputComparisonCount; - private ulong _classifiedOutputSuccessfulComparisonCount; - private long _classifiedOutputMismatchCount; - private string? _classifiedOutputFirstMismatch; - private ulong _selectionOutputComparisonCount; - private ulong _selectionOutputSuccessfulComparisonCount; - private long _selectionOutputMismatchCount; - private string? _selectionOutputFirstMismatch; - private PackedClassificationCacheSnapshot - _packedClassification; - - public RenderScenePViewFrameProductController( - RenderSceneShadowRuntime shadow, - CurrentRenderSceneOracle? current = null, - Action? log = null, - WbDrawDispatcher? dispatcher = null) - { - _shadow = shadow ?? throw new ArgumentNullException(nameof(shadow)); - _current = current; - _log = log; - _dispatcher = dispatcher; - Snapshot = RenderFrameProductComparisonSnapshot.Disabled with - { - Enabled = true, - }; - } - - public RenderFrameProductComparisonSnapshot Snapshot { get; private set; } - - private CurrentRenderSceneOracle Current => - _current - ?? throw new InvalidOperationException( - "Current-path comparison was requested without a referee."); - - public RenderFrameView BuildAndBorrow( - PortalVisibilityFrame portalFrame, - ClipFrameAssembly clipAssembly, - ViewconeCuller viewcone, - IReadOnlyList lookInCellTurns, - IWalkLookInViewSource? walkLookInViews, - HashSet drawableCells, - IRetailPViewCellSource cells, - HashSet? animatedEntityIds, - bool rootIsOutdoor, - IReadOnlySet rootFloodCells) - { - ArgumentNullException.ThrowIfNull(portalFrame); - ArgumentNullException.ThrowIfNull(clipAssembly); - ArgumentNullException.ThrowIfNull(viewcone); - ArgumentNullException.ThrowIfNull(lookInCellTurns); - ArgumentNullException.ThrowIfNull(drawableCells); - ArgumentNullException.ThrowIfNull(cells); - - ulong frameSequence = checked(++_productFrameSequence); - RenderSceneQuery scene = _shadow.Query; - RenderSceneDigest sourceDigest = _current is null - ? new RenderSceneDigest( - scene.Generation, - scene.Counts, - default) - : _shadow.BuildDigest(); - var input = new RenderScenePViewBuildInput( - scene, - sourceDigest, - portalFrame, - clipAssembly, - viewcone, - lookInCellTurns, - walkLookInViews, - drawableCells, - cells, - animatedEntityIds, - rootIsOutdoor, - rootFloodCells); - _builder.Build(_exchange, frameSequence, in input); - return _exchange.BorrowLatest( - scene.Generation, - frameSequence); - } - - public void Release(in RenderFrameView view) => - _exchange.Release(in view); - - public void BuildAndCompare( - PortalVisibilityFrame portalFrame, - ClipFrameAssembly clipAssembly, - ViewconeCuller viewcone, - IReadOnlyList lookInFrames, - HashSet drawableCells, - IRetailPViewCellSource cells, - HashSet? animatedEntityIds, - uint tupleLandblockId, - bool rootIsOutdoor, - Vector3 cameraWorldPosition = default, - IReadOnlySet? rootFloodCells = null) - { - // Comparison/diagnostic path: absent an explicit walk root flood, - // drawableCells keeps the pre-stage-split admission (drawableCells - // WAS the flood in the legacy meaning these fixtures pin). - RenderFrameView view = BuildAndBorrow( - portalFrame, - clipAssembly, - viewcone, - FlattenLegacyLookInCells(lookInFrames), - walkLookInViews: null, - drawableCells, - cells, - animatedEntityIds, - rootIsOutdoor, - rootFloodCells ?? drawableCells); - try - { - Compare( - in view, - tupleLandblockId, - cameraWorldPosition); - } - finally - { - Release(in view); - } - } - - private static uint[] FlattenLegacyLookInCells( - IReadOnlyList lookInFrames) - { - var cells = new List(); - for (int frameIndex = 0; frameIndex < lookInFrames.Count; frameIndex++) - { - PortalVisibilityFrame frame = lookInFrames[frameIndex]; - for (int index = frame.OrderedVisibleCells.Count - 1; index >= 0; index--) - cells.Add(frame.OrderedVisibleCells[index]); - } - return cells.ToArray(); - } - - public void CompleteProduction(in RenderFrameView view) - { - _packedClassification = - _dispatcher?.PackedClassificationSnapshot ?? default; - if (_current is null) - { - _shadow.ClearDirty(); - Snapshot = Snapshot with - { - Enabled = true, - ProductFrameSequence = view.FrameSequence, - ActualCandidateCount = view.RouteCandidates.Length, - PackedClassificationProjectionCount = - _packedClassification.ProjectionCount, - PackedClassificationStaticRebuildCount = - _packedClassification.StaticRebuildCount, - PackedClassificationSameFrameReuseCount = - _packedClassification.SameFrameReuseCount, - PackedClassificationCrossFrameReuseCount = - _packedClassification.CrossFrameReuseCount, - PackedClassificationAnimatedCount = - _packedClassification.AnimatedClassificationCount, - PackedClassificationRetiredCount = - _packedClassification.RetiredProjectionCount, - PackedClassificationRetainedPayloadBytes = - _packedClassification.RetainedPayloadBytes, - ProductCounts = view.DiagnosticCounts, - }; - return; - } - - CandidateComparison candidates = - CompareCandidateMembership(in view); - if (candidates.Successful) - _shadow.ClearDirty(); - Snapshot = Snapshot with - { - Enabled = true, - ProductFrameSequence = view.FrameSequence, - CurrentPViewFrameSequence = - Current.Snapshot.CompletedPViewFrameSequence, - ComparisonCount = _comparisonCount, - SuccessfulComparisonCount = _successfulComparisonCount, - ExpectedCandidateCount = _expected.Count, - ActualCandidateCount = _actual.Count, - MismatchCount = _mismatchCount, - FirstMismatch = _firstMismatch, - ExpectedDigest = candidates.ExpectedDigest, - ActualDigest = candidates.ActualDigest, - PackedClassificationProjectionCount = - _packedClassification.ProjectionCount, - PackedClassificationStaticRebuildCount = - _packedClassification.StaticRebuildCount, - PackedClassificationSameFrameReuseCount = - _packedClassification.SameFrameReuseCount, - PackedClassificationCrossFrameReuseCount = - _packedClassification.CrossFrameReuseCount, - PackedClassificationAnimatedCount = - _packedClassification.AnimatedClassificationCount, - PackedClassificationRetiredCount = - _packedClassification.RetiredProjectionCount, - PackedClassificationRetainedPayloadBytes = - _packedClassification.RetainedPayloadBytes, - ProductCounts = view.DiagnosticCounts, - }; - } - - private CandidateComparison CompareCandidateMembership( - in RenderFrameView view) - { - _comparisonCount = checked(_comparisonCount + 1); - _expected.Clear(); - _actual.Clear(); - - IReadOnlyList expected = - Current.PViewCandidates; - if (_expected.Capacity < expected.Count) - _expected.Capacity = expected.Count; - for (int index = 0; index < expected.Count; index++) - { - CurrentRenderPViewCandidateFingerprint candidate = - expected[index]; - CurrentRenderProjectionFingerprint projection = - candidate.Projection; - _expected.Add(new CandidateKey( - Map(candidate.Route), - candidate.RouteIndex, - candidate.CellId, - ExpectedId(in projection))); - } - - ReadOnlySpan ranges = - view.RouteRanges; - ReadOnlySpan records = - view.RouteCandidates; - if (_actual.Capacity < records.Length) - _actual.Capacity = records.Length; - for (int rangeIndex = 0; - rangeIndex < ranges.Length; - rangeIndex++) - { - RenderFrameCandidateRange range = ranges[rangeIndex]; - int end = checked(range.Offset + range.Count); - if ((uint)range.Offset > (uint)records.Length - || (uint)end > (uint)records.Length) - { - throw new InvalidOperationException( - $"Render-frame route range {rangeIndex} exceeds its candidate storage."); - } - - for (int index = range.Offset; index < end; index++) - { - _actual.Add(new CandidateKey( - range.Route, - range.RouteIndex, - range.CellId, - records[index].Id)); - } - } - - RenderSceneHash128 expectedDigest = BuildDigest(_expected); - RenderSceneHash128 actualDigest = BuildDigest(_actual); - string? mismatch = CompareKeys(); - if (mismatch is null) - { - _successfulComparisonCount = - checked(_successfulComparisonCount + 1); - } - else - { - _mismatchCount = checked(_mismatchCount + 1); - if (_firstMismatch is null) - { - _firstMismatch = mismatch; - _log?.Invoke( - "[render-frame-product] first mismatch: " - + mismatch); - } - } - - return new CandidateComparison( - mismatch is null, - expectedDigest, - actualDigest); - } - - private void Compare( - in RenderFrameView view, - uint tupleLandblockId, - Vector3 cameraWorldPosition) - { - CandidateComparison candidates = - CompareCandidateMembership(in view); - RenderSceneHash128 expectedDigest = - candidates.ExpectedDigest; - RenderSceneHash128 actualDigest = - candidates.ActualDigest; - string? mismatch = candidates.Successful - ? null - : _firstMismatch; - - PackedInputComparison packed = - ComparePackedInput(in view, tupleLandblockId); - ClassifiedOutputComparison classified = - CompareClassifiedOutput( - in view, - tupleLandblockId, - cameraWorldPosition); - if (_dispatcher is not null - && mismatch is null - && packed.Successful - && classified.Successful) - { - // The frame product and retained classifier have consumed every - // dirty fact and matched the production oracle. A later update - // starts the next dirty epoch; a mismatch or exception above - // deliberately leaves dirtiness intact for the next rebuild. - _shadow.ClearDirty(); - } - - Snapshot = new RenderFrameProductComparisonSnapshot( - Enabled: true, - ProductFrameSequence: view.FrameSequence, - CurrentPViewFrameSequence: - Current.Snapshot.CompletedPViewFrameSequence, - ComparisonCount: _comparisonCount, - SuccessfulComparisonCount: _successfulComparisonCount, - ExpectedCandidateCount: _expected.Count, - ActualCandidateCount: _actual.Count, - MismatchCount: _mismatchCount, - FirstMismatch: _firstMismatch, - ExpectedDigest: expectedDigest, - ActualDigest: actualDigest, - PackedInputComparisonCount: _packedInputComparisonCount, - PackedInputSuccessfulComparisonCount: - _packedInputSuccessfulComparisonCount, - PackedInputExpectedCount: _packedExpected.Count, - PackedInputActualCount: _packedActual.Count, - PackedInputMismatchCount: _packedInputMismatchCount, - PackedInputFirstMismatch: _packedInputFirstMismatch, - PackedInputExpectedDigest: packed.ExpectedDigest, - PackedInputActualDigest: packed.ActualDigest, - ClassifiedOutputComparisonCount: - _classifiedOutputComparisonCount, - ClassifiedOutputSuccessfulComparisonCount: - _classifiedOutputSuccessfulComparisonCount, - ClassifiedOutputExpectedDrawCount: - classified.ExpectedDrawCount, - ClassifiedOutputActualDrawCount: - classified.ActualDrawCount, - ClassifiedOutputMismatchCount: - _classifiedOutputMismatchCount, - ClassifiedOutputFirstMismatch: - _classifiedOutputFirstMismatch, - ClassifiedOutputExpectedDigest: - classified.ExpectedDigest, - ClassifiedOutputActualDigest: - classified.ActualDigest, - SelectionOutputComparisonCount: - _selectionOutputComparisonCount, - SelectionOutputSuccessfulComparisonCount: - _selectionOutputSuccessfulComparisonCount, - SelectionOutputExpectedPartCount: - classified.ExpectedSelectionCount, - SelectionOutputActualPartCount: - classified.ActualSelectionCount, - SelectionOutputMismatchCount: - _selectionOutputMismatchCount, - SelectionOutputFirstMismatch: - _selectionOutputFirstMismatch, - SelectionOutputExpectedDigest: - classified.ExpectedSelectionDigest, - SelectionOutputActualDigest: - classified.ActualSelectionDigest, - PackedClassificationProjectionCount: - _packedClassification.ProjectionCount, - PackedClassificationStaticRebuildCount: - _packedClassification.StaticRebuildCount, - PackedClassificationSameFrameReuseCount: - _packedClassification.SameFrameReuseCount, - PackedClassificationCrossFrameReuseCount: - _packedClassification.CrossFrameReuseCount, - PackedClassificationAnimatedCount: - _packedClassification.AnimatedClassificationCount, - PackedClassificationRetiredCount: - _packedClassification.RetiredProjectionCount, - PackedClassificationRetainedPayloadBytes: - _packedClassification.RetainedPayloadBytes, - ProductCounts: view.DiagnosticCounts); - } - - private ClassifiedOutputComparison CompareClassifiedOutput( - in RenderFrameView view, - uint tupleLandblockId, - Vector3 cameraWorldPosition) - { - WbDrawDispatcher? dispatcher = _dispatcher; - if (dispatcher is null) - return default; - - dispatcher.BuildPackedDispatcherOracle( - in view, - tupleLandblockId, - cameraWorldPosition); - _packedClassification = - dispatcher.PackedClassificationSnapshot; - IReadOnlyList expected = - Current.DispatcherSubmissions; - IReadOnlyList actual = - dispatcher.PackedDispatcherSubmissions; - RenderSceneHash128 expectedDigest = - BuildSubmissionDigest(expected); - RenderSceneHash128 actualDigest = - BuildSubmissionDigest(actual); - _classifiedOutputComparisonCount = - checked(_classifiedOutputComparisonCount + 1); - string? mismatch = CompareSubmissions(expected, actual); - if (mismatch is null) - { - _classifiedOutputSuccessfulComparisonCount = - checked( - _classifiedOutputSuccessfulComparisonCount + 1); - } - else - { - _classifiedOutputMismatchCount = - checked(_classifiedOutputMismatchCount + 1); - if (_classifiedOutputFirstMismatch is null) - { - _classifiedOutputFirstMismatch = mismatch; - _log?.Invoke( - "[render-classified-output] first mismatch: " - + mismatch - + $" classification={_packedClassification}"); - } - } - - IReadOnlyList - expectedSelection = Current.SelectionParts; - IReadOnlyList - actualSelection = dispatcher.PackedSelectionParts; - BuildSelectionKeys( - expectedSelection, - _selectionExpected); - BuildSelectionKeys( - actualSelection, - _selectionActual); - RenderSceneHash128 expectedSelectionDigest = - BuildSelectionDigest(_selectionExpected); - RenderSceneHash128 actualSelectionDigest = - BuildSelectionDigest(_selectionActual); - _selectionOutputComparisonCount = - checked(_selectionOutputComparisonCount + 1); - string? selectionMismatch = CompareSelection( - _selectionExpected, - _selectionActual); - if (selectionMismatch is null) - { - _selectionOutputSuccessfulComparisonCount = - checked( - _selectionOutputSuccessfulComparisonCount + 1); - } - else - { - _selectionOutputMismatchCount = - checked(_selectionOutputMismatchCount + 1); - if (_selectionOutputFirstMismatch is null) - { - _selectionOutputFirstMismatch = selectionMismatch; - _log?.Invoke( - "[render-selection-output] first mismatch: " - + selectionMismatch - + $" classification={_packedClassification}"); - } - } - - return new ClassifiedOutputComparison( - Successful: mismatch is null && selectionMismatch is null, - expected.Count, - actual.Count, - expectedDigest, - actualDigest, - expectedSelection.Count, - actualSelection.Count, - expectedSelectionDigest, - actualSelectionDigest); - } - - private static string? CompareSubmissions( - IReadOnlyList expected, - IReadOnlyList actual) - { - if (expected.Count != actual.Count) - { - int shared = Math.Min(expected.Count, actual.Count); - for (int index = 0; index < shared; index++) - { - if (expected[index] != actual[index]) - { - return $"field=draw index={index} " - + $"expected={expected[index]} " - + $"actual={actual[index]}"; - } - } - - string extra = expected.Count > actual.Count - ? $"firstExtraExpected={expected[shared]}" - : $"firstExtraActual={actual[shared]}"; - return $"field=drawCount expected={expected.Count} " - + $"actual={actual.Count} {extra}"; - } - - for (int index = 0; index < expected.Count; index++) - { - if (expected[index] != actual[index]) - { - return $"field=draw index={index} " - + $"expected={expected[index]} " - + $"actual={actual[index]}"; - } - } - - return null; - } - - private static string? CompareSelection( - IReadOnlyList expected, - IReadOnlyList actual) - { - if (expected.Count != actual.Count) - { - int shared = Math.Min(expected.Count, actual.Count); - for (int index = 0; index < shared; index++) - { - if (expected[index] != actual[index]) - { - return $"field=part index={index} " - + $"expected={expected[index]} " - + $"actual={actual[index]}"; - } - } - - string extra = expected.Count > actual.Count - ? $"firstExtraExpected={expected[shared]}" - : $"firstExtraActual={actual[shared]}"; - return $"field=partCount expected={expected.Count} " - + $"actual={actual.Count} {extra}"; - } - - for (int index = 0; index < expected.Count; index++) - { - if (expected[index] != actual[index]) - { - return $"field=part index={index} " - + $"expected={expected[index]} " - + $"actual={actual[index]}"; - } - } - - return null; - } - - private static RenderSceneHash128 BuildSubmissionDigest( - IReadOnlyList submissions) - { - StableRenderHash128 hash = StableRenderHash128.Create(); - hash.Add(submissions.Count); - for (int index = 0; index < submissions.Count; index++) - { - CurrentRenderDispatcherSubmission submission = - submissions[index]; - hash.Add(submission.VisibleInstanceCount); - hash.Add(submission.ImmediateInstanceCount); - hash.Add(submission.OpaqueGroupCount); - hash.Add(submission.TransparentGroupCount); - hash.Add(submission.TransparentDeferred); - hash.Add(submission.OpaqueDigest.Low); - hash.Add(submission.OpaqueDigest.High); - hash.Add(submission.TransparentDigest.Low); - hash.Add(submission.TransparentDigest.High); - hash.Add(submission.TransparentSetDigest.Low); - hash.Add(submission.TransparentSetDigest.High); - hash.Add(submission.Digest.Low); - hash.Add(submission.Digest.High); - } - - return hash.Finish(); - } - - private static void BuildSelectionKeys( - IReadOnlyList source, - List destination) - { - destination.Clear(); - if (destination.Capacity < source.Count) - destination.Capacity = source.Count; - for (int index = 0; index < source.Count; index++) - { - CurrentRenderSelectionFingerprint part = source[index]; - destination.Add(new SelectionKey( - part.ServerGuid, - part.LocalEntityId, - part.PartIndex, - part.GfxObjId, - part.LocalToWorld, - part.Geometry)); - } - destination.Sort(SelectionKeyComparer.Instance); - } - - private static RenderSceneHash128 BuildSelectionDigest( - IReadOnlyList parts) - { - StableRenderHash128 hash = StableRenderHash128.Create(); - hash.Add(parts.Count); - for (int index = 0; index < parts.Count; index++) - { - SelectionKey part = parts[index]; - hash.Add(part.ServerGuid); - hash.Add(part.LocalEntityId); - hash.Add(part.PartIndex); - hash.Add(part.GfxObjId); - hash.Add(part.LocalToWorld); - hash.Add(part.Geometry.Low); - hash.Add(part.Geometry.High); - } - - return hash.Finish(); - } - - private PackedInputComparison ComparePackedInput( - in RenderFrameView view, - uint tupleLandblockId) - { - _packedInputComparisonCount = - checked(_packedInputComparisonCount + 1); - _packedExpected.Clear(); - _packedActual.Clear(); - - IReadOnlyList expected = - Current.DispatcherCandidates; - if (_packedExpected.Capacity < expected.Count) - _packedExpected.Capacity = expected.Count; - for (int index = 0; index < expected.Count; index++) - { - CurrentRenderDispatcherFingerprint candidate = expected[index]; - CurrentRenderProjectionFingerprint projection = - candidate.Projection; - _packedExpected.Add(new PackedInputKey( - candidate.DrawSequence, - candidate.Set, - ExpectedId(in projection), - candidate.MeshRefIndex, - candidate.TupleLandblockId, - candidate.CacheLandblockId, - projection.EntityId, - projection.ServerGuid, - projection.SourceId, - projection.ParentCellId, - projection.EffectCellId, - projection.Flags, - projection.Transform, - projection.Geometry, - projection.Appearance, - candidate.GfxObjId, - candidate.PartTransform, - candidate.SurfaceOverrides)); - } - - ReadOnlySpan ranges = view.RouteRanges; - ReadOnlySpan routeCandidates = - view.RouteCandidates; - if (_packedActual.Capacity < view.MeshParts.Length) - _packedActual.Capacity = view.MeshParts.Length; - for (int drawSequence = 0; - drawSequence < ranges.Length; - drawSequence++) - { - RenderFrameCandidateRange range = ranges[drawSequence]; - int end = checked(range.Offset + range.Count); - for (int candidateIndex = range.Offset; - candidateIndex < end; - candidateIndex++) - { - RenderProjectionRecord projection = - routeCandidates[candidateIndex]; - if ((projection.Flags & RenderProjectionFlags.Draw) == 0) - continue; - - IReadOnlyList? meshRefs = - projection.EntityPayload.MeshRefs; - int meshCount = meshRefs?.Count ?? 0; - for (int partIndex = 0; - partIndex < meshCount; - partIndex++) - { - AcDream.Core.World.MeshRef meshRef = - meshRefs![partIndex]; - _packedActual.Add(new PackedInputKey( - drawSequence, - WbDrawDispatcher.EntitySet.All, - projection.Id, - partIndex, - tupleLandblockId, - DispatcherCacheLandblock( - in projection, - tupleLandblockId), - projection.Source.LocalEntityId, - projection.Source.ServerGuid, - projection.Source.SourceId, - projection.Source.ParentCellId, - projection.Source.EffectCellId, - projection.Source.CurrentProjectionFlags, - projection.Source.TransformFingerprint, - projection.Source.GeometryFingerprint, - projection.Source.AppearanceFingerprint, - meshRef.GfxObjId, - meshRef.PartTransform, - CurrentRenderSceneOracle - .CreateSurfaceOverrideFingerprint( - meshRef.SurfaceOverrides))); - } - } - } - - RenderSceneHash128 expectedDigest = - BuildPackedInputDigest(_packedExpected); - RenderSceneHash128 actualDigest = - BuildPackedInputDigest(_packedActual); - string? mismatch = ComparePackedInputKeys(ranges.Length); - if (mismatch is null) - { - _packedInputSuccessfulComparisonCount = - checked(_packedInputSuccessfulComparisonCount + 1); - } - else - { - _packedInputMismatchCount = - checked(_packedInputMismatchCount + 1); - if (_packedInputFirstMismatch is null) - { - _packedInputFirstMismatch = mismatch; - _log?.Invoke( - "[render-packed-input] first mismatch: " + mismatch); - } - } - - return new PackedInputComparison( - Successful: mismatch is null, - expectedDigest, - actualDigest); - } - - private static uint DispatcherCacheLandblock( - in RenderProjectionRecord projection, - uint tupleLandblockId) => - projection.Source.ParentCellId != 0 - ? (projection.Source.ParentCellId & 0xFFFF0000u) | 0xFFFFu - : tupleLandblockId; - - private string? ComparePackedInputKeys(int actualDrawCount) - { - if (Current.Snapshot.DispatcherDrawCount != actualDrawCount) - { - return $"field=drawCount expected=" - + $"{Current.Snapshot.DispatcherDrawCount} " - + $"actual={actualDrawCount}"; - } - if (_packedExpected.Count != _packedActual.Count) - { - int shared = Math.Min( - _packedExpected.Count, - _packedActual.Count); - for (int index = 0; index < shared; index++) - { - if (_packedExpected[index] != _packedActual[index]) - { - return $"field=meshPart index={index} " - + $"expected={_packedExpected[index]} " - + $"actual={_packedActual[index]}"; - } - } - - string extra = _packedExpected.Count > _packedActual.Count - ? $"firstExtraExpected={_packedExpected[shared]}" - : $"firstExtraActual={_packedActual[shared]}"; - return $"field=count expected={_packedExpected.Count} " - + $"actual={_packedActual.Count} {extra}"; - } - - for (int index = 0; index < _packedExpected.Count; index++) - { - if (_packedExpected[index] != _packedActual[index]) - { - return $"field=meshPart index={index} " - + $"expected={_packedExpected[index]} " - + $"actual={_packedActual[index]}"; - } - } - - return null; - } - - private static RenderSceneHash128 BuildPackedInputDigest( - List candidates) - { - StableRenderHash128 hash = StableRenderHash128.Create(); - hash.Add(candidates.Count); - for (int index = 0; index < candidates.Count; index++) - candidates[index].AddTo(ref hash); - return hash.Finish(); - } - - private string? CompareKeys() - { - if (_expected.Count != _actual.Count) - { - int shared = Math.Min(_expected.Count, _actual.Count); - for (int index = 0; index < shared; index++) - { - if (_expected[index] != _actual[index]) - { - return $"field=candidate index={index} " - + $"expected={_expected[index]} " - + $"actual={_actual[index]}"; - } - } - - string extra = _expected.Count > _actual.Count - ? $"firstExtraExpected={_expected[shared]}" - : $"firstExtraActual={_actual[shared]}"; - return $"field=count expected={_expected.Count} " - + $"actual={_actual.Count} {extra}"; - } - - for (int i = 0; i < _expected.Count; i++) - { - if (_expected[i] != _actual[i]) - { - return $"field=candidate index={i} " - + $"expected={_expected[i]} actual={_actual[i]}"; - } - } - - return null; - } - - private static RenderSceneHash128 BuildDigest( - List candidates) - { - StableRenderHash128 hash = StableRenderHash128.Create(); - hash.Add(candidates.Count); - for (int i = 0; i < candidates.Count; i++) - { - CandidateKey candidate = candidates[i]; - hash.Add((byte)candidate.Route); - hash.Add(candidate.RouteIndex); - hash.Add(candidate.CellId); - candidate.ProjectionId.AddTo(ref hash); - } - return hash.Finish(); - } - - private static RenderProjectionId ExpectedId( - in CurrentRenderProjectionFingerprint projection) => - projection.ServerGuid == 0 - ? StaticRenderProjectionJournal.StaticEntityId( - projection.LandblockId, - projection.EntityId) - : LiveRenderProjectionJournal.ProjectionId( - projection.EntityId); - - private static RenderFrameCandidateRoute Map( - CurrentRenderPViewRoute route) => - route switch - { - CurrentRenderPViewRoute.LandscapeOutdoorStatic => - RenderFrameCandidateRoute.LandscapeOutdoorStatic, - CurrentRenderPViewRoute.LandscapeBuildingShell => - RenderFrameCandidateRoute.LandscapeBuildingShell, - CurrentRenderPViewRoute.LandscapeOutsideDynamic => - RenderFrameCandidateRoute.LandscapeOutsideDynamic, - CurrentRenderPViewRoute.LookInObject => - RenderFrameCandidateRoute.LookInObject, - CurrentRenderPViewRoute.CellStatic => - RenderFrameCandidateRoute.CellStatic, - CurrentRenderPViewRoute.DynamicLast => - RenderFrameCandidateRoute.DynamicLast, - _ => throw new ArgumentOutOfRangeException( - nameof(route), - route, - null), - }; - - private readonly record struct CandidateKey( - RenderFrameCandidateRoute Route, - int RouteIndex, - uint CellId, - RenderProjectionId ProjectionId); - - private readonly record struct PackedInputComparison( - bool Successful, - RenderSceneHash128 ExpectedDigest, - RenderSceneHash128 ActualDigest); - - private readonly record struct CandidateComparison( - bool Successful, - RenderSceneHash128 ExpectedDigest, - RenderSceneHash128 ActualDigest); - - private readonly record struct ClassifiedOutputComparison( - bool Successful, - int ExpectedDrawCount, - int ActualDrawCount, - RenderSceneHash128 ExpectedDigest, - RenderSceneHash128 ActualDigest, - int ExpectedSelectionCount, - int ActualSelectionCount, - RenderSceneHash128 ExpectedSelectionDigest, - RenderSceneHash128 ActualSelectionDigest); - - private readonly record struct SelectionKey( - uint ServerGuid, - uint LocalEntityId, - int PartIndex, - uint GfxObjId, - Matrix4x4 LocalToWorld, - RenderSceneHash128 Geometry); - - private sealed class SelectionKeyComparer : IComparer - { - public static SelectionKeyComparer Instance { get; } = new(); - - private SelectionKeyComparer() - { - } - - public int Compare(SelectionKey left, SelectionKey right) - { - int value = left.LocalEntityId.CompareTo( - right.LocalEntityId); - if (value != 0) return value; - value = left.PartIndex.CompareTo(right.PartIndex); - if (value != 0) return value; - value = left.GfxObjId.CompareTo(right.GfxObjId); - if (value != 0) return value; - return left.ServerGuid.CompareTo(right.ServerGuid); - } - } - - private readonly record struct PackedInputKey( - int DrawSequence, - WbDrawDispatcher.EntitySet Set, - RenderProjectionId ProjectionId, - int MeshRefIndex, - uint TupleLandblockId, - uint CacheLandblockId, - uint LocalEntityId, - uint ServerGuid, - uint SourceId, - uint ParentCellId, - uint EffectCellId, - uint SourceFlags, - RenderSceneHash128 Transform, - RenderSceneHash128 Geometry, - RenderSceneHash128 Appearance, - uint GfxObjId, - Matrix4x4 PartTransform, - RenderSceneHash128 SurfaceOverrides) - { - public void AddTo(ref StableRenderHash128 hash) - { - hash.Add(DrawSequence); - hash.Add((int)Set); - ProjectionId.AddTo(ref hash); - hash.Add(MeshRefIndex); - hash.Add(TupleLandblockId); - hash.Add(CacheLandblockId); - hash.Add(LocalEntityId); - hash.Add(ServerGuid); - hash.Add(SourceId); - hash.Add(ParentCellId); - hash.Add(EffectCellId); - hash.Add(SourceFlags); - hash.Add(Transform.Low); - hash.Add(Transform.High); - hash.Add(Geometry.Low); - hash.Add(Geometry.High); - hash.Add(Appearance.Low); - hash.Add(Appearance.High); - hash.Add(GfxObjId); - hash.Add(PartTransform); - hash.Add(SurfaceOverrides.Low); - hash.Add(SurfaceOverrides.High); - } - } - -} - -/// -/// Allocation-free scene-query projection for one accepted PView frame. -/// It applies the existing viewcone and outside-stage predicates verbatim, -/// changing only the source of candidate records. -/// -internal sealed class RenderScenePViewFrameBuilder -{ - private const byte EnvCellProjectionDomain = 2; - - private readonly HashSet _projectionIds = []; - private RenderProjectionRecord[] _dynamics = []; - private RenderProjectionRecord[] _cell = []; - private RenderProjectionRecord[] _dirty = []; - private RenderProjectionRecord[] _survivors = []; - - // Cells drawn as building look-ins this frame — the DynamicLast route - // honors retail's drawn-once contract (DrawMeshInternal @0x0059F360 - // marks every non-player part): an object whose cell drew with a look-in - // must not enter the final dynamics route again. - private readonly HashSet _lookInCellScratch = new(); - private readonly Dictionary - _dynamicPositions = []; - private int _dynamicCount; - - // ACDREAM_PROBE_WALK_ROOT companion (throwaway): rate limiter for the - // [walk-dyn] outside-stage classification dump, plus the [dyn-route] - // per-entity on-change admission cache. - private uint _probeDynFrameCounter; - private readonly Dictionary _probeDynRouteStates = new(); - private int _dirtyCount; - private RenderSceneGeneration _indexGeneration; - private ulong _indexRevision; - - public void Build( - RenderFrameExchange exchange, - ulong frameSequence, - in RenderScenePViewBuildInput input) - { - ArgumentNullException.ThrowIfNull(exchange); - RenderFrameWriter writer = - exchange.BeginBuild(input.Scene.Generation, frameSequence); - try - { - writer.SetBorrowedProducts( - input.PortalFrame, - input.ClipAssembly, - input.WalkLookInViews); - RenderSceneDigest digest = input.SourceDigest; - writer.SetSourceDigest(in digest); - _projectionIds.Clear(); - LoadSceneIndices(input.Scene); - - // The walk owns every look-in cell's complete object-list turn. - // Keep one set for both downstream dynamic routes: those objects - // belong to LookInObject exactly once, never to the generic - // landscape-outside route and never to DynamicLast. The former - // outside-route duplicate was especially damaging across an - // interior root's depth clear: its color survived the clear even - // when the later look-in draw was correctly occluded by a wall. - _lookInCellScratch.Clear(); - for (int index = 0; index < input.LookInCellTurns.Count; index++) - _lookInCellScratch.Add(input.LookInCellTurns[index]); - - // FW4: the walk now owns the complete look-in object-list turn — - // statics, live dynamics, fades, picking, and particles. Do not - // advertise a packed LookInObject range the dispatcher cannot and - // must not consume a second time. - // FW4 cutover: once a walk view source is present, every live - // object is emitted at its retail cell turn (landscape, root - // flood, or building look-in). The packed dynamic ranges exist - // only for non-walk diagnostic callers during this atomic slice; - // advertising either range in production would draw the same - // object a second time after its owner cell. - if (input.WalkLookInViews is null) - { - BuildOutsideDynamicRoutes(writer, in input); - BuildDynamicLastRoute(writer, in input); - } - writer.Publish(); - AcknowledgeCachedDirtyRecords(); - } - catch - { - writer.Abort(); - throw; - } - } - - private void LoadSceneIndices(RenderSceneQuery scene) - { - RenderSceneIndexCounts counts = scene.IndexCounts; - ulong revision = scene.IndexRevision; - if (scene.Generation != _indexGeneration - || revision != _indexRevision) - { - EnsureCapacity(ref _dynamics, counts.Dynamic); - _dynamicCount = scene.CopyIndexTo( - RenderSceneIndex.Dynamic, - _dynamics); - _dynamicCount = CompactAndSort( - _dynamics, - _dynamicCount); - BuildPositionIndex( - _dynamics, - _dynamicCount, - _dynamicPositions); - - _indexGeneration = scene.Generation; - _indexRevision = revision; - } - - EnsureCapacity(ref _dirty, counts.Dirty); - _dirtyCount = scene.CopyIndexTo( - RenderSceneIndex.Dirty, - _dirty); - for (int index = 0; index < _dirtyCount; index++) - { - RenderProjectionRecord record = _dirty[index]; - if (_dynamicPositions.TryGetValue( - record.Id, - out int dynamicPosition)) - { - _dynamics[dynamicPosition] = record; - } - } - } - - private void AcknowledgeCachedDirtyRecords() - { - for (int index = 0; index < _dirtyCount; index++) - { - RenderProjectionId id = _dirty[index].Id; - if (_dynamicPositions.TryGetValue( - id, - out int dynamicPosition)) - { - _dynamics[dynamicPosition] = - _dynamics[dynamicPosition] with - { - DirtyMask = RenderDirtyMask.None, - }; - } - } - } - - private static void BuildPositionIndex( - RenderProjectionRecord[] records, - int count, - Dictionary positions) - { - positions.Clear(); - positions.EnsureCapacity(count); - for (int index = 0; index < count; index++) - positions.Add(records[index].Id, index); - } - - /// Campaign FW3.2b-2: LookInObject now carries DYNAMIC - /// candidates only — the walk draws every look-in cell's STATIC content - /// directly (WalkFrameDriver's LookInStatic turn, via - /// WalkProductionWorldData.GetCellStatics), so loading this - /// route's cell contents with includeStatics: false is what keeps - /// the two draws from doubling a look-in room's furniture (plan §FW3 - /// item 3). - private void BuildLookInRoutes( - RenderFrameWriter writer, - in RenderScenePViewBuildInput input) - { - for (int routeIndex = 0; routeIndex < input.LookInCellTurns.Count; routeIndex++) - { - uint cellId = input.LookInCellTurns[routeIndex]; - int count = LoadCell( - input.Scene, - cellId, - includeStatics: false, - includeDynamics: true); - if (count == 0) - continue; - - int survivorCount = 0; - EnsureCapacity(ref _survivors, count); - for (int index = 0; index < count; index++) - { - RenderProjectionRecord record = _cell[index]; - _survivors[survivorCount++] = record; - AddProjection( - writer, - in record, - input.AnimatedEntityIds); - } - if (survivorCount == 0) - continue; - writer.AddRouteRange( - RenderFrameCandidateRoute.LookInObject, - routeIndex, - cellId, - _survivors.AsSpan(0, survivorCount)); - } - } - - private void BuildOutsideDynamicRoutes( - RenderFrameWriter writer, - in RenderScenePViewBuildInput input) - { - if (input.RootIsOutdoor) - return; - - int sliceCount = input.ClipAssembly.OutsideViewSlices.Length; - bool probeDynamics = - AcDream.Core.Rendering.RenderingDiagnostics.ProbeWalkRootEnabled - && ++_probeDynFrameCounter % 120 == 0; - for (int sliceIndex = 0; sliceIndex < sliceCount; sliceIndex++) - { - int count = 0; - EnsureCapacity(ref _survivors, _dynamicCount); - for (int i = 0; i < _dynamicCount; i++) - { - RenderProjectionRecord record = _dynamics[i]; - uint? parentCellId = ParentCell(in record); - if (parentCellId is uint parentCell - && (_lookInCellScratch.Contains(parentCell) - || (input.WalkLookInViews is not null - && input.RootFloodCells.Contains(parentCell)))) - continue; - Sphere(in record, out Vector3 center, out float radius); - bool outside = RetailPViewRenderer.DynamicDrawsInOutsideStage( - ParentCell(in record), - center, - radius, - input.DrawableCells, - input.Cells); - bool cone = input.Viewcone.SphereVisibleInOutsideSlice( - sliceIndex, in center, radius); - if (probeDynamics) - { - Console.WriteLine( - $"[walk-dyn] slice={sliceIndex} id={record.Source.LocalEntityId:x} " - + $"parent={ParentCell(in record)?.ToString("x8") ?? "null"} " - + $"r={radius:F1} outside={(outside ? 1 : 0)} cone={(cone ? 1 : 0)}"); - } - if (!outside || !cone) - continue; - - _survivors[count++] = record; - writer.AddDynamic(in record); - AddProjection( - writer, - in record, - input.AnimatedEntityIds); - } - - writer.AddRouteRange( - RenderFrameCandidateRoute.LandscapeOutsideDynamic, - sliceIndex, - 0, - _survivors.AsSpan(0, count)); - } - } - - private void BuildDynamicLastRoute( - RenderFrameWriter writer, - in RenderScenePViewBuildInput input) - { - int count = 0; - EnsureCapacity(ref _survivors, _dynamicCount); - for (int i = 0; i < _dynamicCount; i++) - { - RenderProjectionRecord record = _dynamics[i]; - uint? parentCellId = ParentCell(in record); - bool indoor = InteriorEntityPartition.IsIndoorCellId( - parentCellId); - if (!input.RootIsOutdoor && !indoor) - continue; - - // Drawn-once (retail DrawMeshInternal @0x0059F360): a dynamic - // whose cell drew as a building look-in already rendered with - // that cell in the landscape stage; re-entering the final route - // would draw it after the boundary alpha drain and overpaint - // nearer flames (the Holtburg candle-behind-door class). - if (indoor && _lookInCellScratch.Contains(parentCellId!.Value)) - { - if (AcDream.Core.Rendering.RenderingDiagnostics.ProbeWalkRootEnabled) - { - string state = $"parent={parentCellId.Value:x8} lookin-excluded"; - if (!_probeDynRouteStates.TryGetValue(record.Source.LocalEntityId, out string? prev) - || prev != state) - { - _probeDynRouteStates[record.Source.LocalEntityId] = state; - Console.WriteLine( - $"[dyn-route] id={record.Source.LocalEntityId:x} {state}"); - } - } - continue; - } - - // FW4: an active walk draws every root-flood cell's complete - // object list at that cell's own PView::DrawCells turn. The - // packed last pass must not duplicate those live objects after - // the walk has already established their correct wall depth. - if (indoor - && input.WalkLookInViews is not null - && input.RootFloodCells.Contains(parentCellId!.Value)) - { - continue; - } - - // The stage-set split (synthesis plan step 4, [dyn-route]-pinned - // 2026-08-30): an interior-parented dynamic rides the LAST pass - // ONLY when its parent cell is in THE WALK'S OWN ROOT FLOOD. - // Retail draws a look-in cell's occupants inside the landscape - // stage through the composed portal chain, and an unreachable - // cell's occupants not at all — the legacy visibility builder - // invents cross-building cell views at the cathedral (real - // 3-5-plane cones for 0xF4180112 from roots with no sightline), - // which let a remote player draw post-clear/post-world through - // opaque walls. The walk's flood is oracle-trace-conformant; - // key the route on it. - if (indoor && !input.RootFloodCells.Contains(parentCellId!.Value)) - { - if (AcDream.Core.Rendering.RenderingDiagnostics.ProbeWalkRootEnabled) - { - string state = $"parent={parentCellId.Value:x8} rootflood-excluded"; - if (!_probeDynRouteStates.TryGetValue(record.Source.LocalEntityId, out string? prev) - || prev != state) - { - _probeDynRouteStates[record.Source.LocalEntityId] = state; - Console.WriteLine( - $"[dyn-route] id={record.Source.LocalEntityId:x} {state}"); - } - } - continue; - } - - Sphere(in record, out Vector3 center, out float radius); - bool visible = indoor - ? input.Viewcone.SphereVisibleInCell( - parentCellId!.Value, - in center, - radius) - : input.Viewcone.SphereVisibleOutside( - in center, - radius); - // ACDREAM_PROBE_WALK_ROOT companion (throwaway): the DynamicLast - // admission trace for the through-wall remote-player report — - // per entity, ON CHANGE: parent cell, look-in exclusion, the - // viewcone verdict, and the cell's lifted-plane shape ("0" = a - // pass-all zero-plane slice — the scissor-fallback trapdoor). - if (AcDream.Core.Rendering.RenderingDiagnostics.ProbeWalkRootEnabled - && indoor) - { - string state = - $"parent={parentCellId!.Value:x8} vis={(visible ? 1 : 0)} " - + $"cone={input.Viewcone.ProbeDescribeCell(parentCellId.Value)}"; - if (!_probeDynRouteStates.TryGetValue(record.Source.LocalEntityId, out string? prev) - || prev != state) - { - _probeDynRouteStates[record.Source.LocalEntityId] = state; - Console.WriteLine( - $"[dyn-route] id={record.Source.LocalEntityId:x} {state} route=DynamicLast"); - } - } - if (!visible) - continue; - - _survivors[count++] = record; - writer.AddDynamic(in record); - AddProjection( - writer, - in record, - input.AnimatedEntityIds); - } - - writer.AddRouteRange( - RenderFrameCandidateRoute.DynamicLast, - 0, - 0, - _survivors.AsSpan(0, count)); - } - - private int LoadCell( - RenderSceneQuery scene, - uint cellId, - bool includeDynamics, - bool includeStatics = true) - { - int staticCount = includeStatics - ? scene.GetCellStaticCount(cellId) - : 0; - int dynamicCount = includeDynamics - ? scene.GetCellDynamicCount(cellId) - : 0; - EnsureCapacity( - ref _cell, - checked(staticCount + dynamicCount)); - int count = 0; - if (staticCount > 0) - { - count = scene.CopyCellStaticsTo( - cellId, - _cell.AsSpan(0, staticCount)); - } - if (dynamicCount > 0) - { - count += scene.CopyCellDynamicsTo( - cellId, - _cell.AsSpan(count, dynamicCount)); - } - - return CompactAndSort(_cell, count); - } - - private void AddProjection( - RenderFrameWriter writer, - in RenderProjectionRecord record, - HashSet? animatedEntityIds) - { - if (!_projectionIds.Add(record.Id)) - return; - writer.AddTransform(new RenderFrameTransformRecord( - record.Id, - -1, - record.Transform.LocalToWorld)); - writer.AddEntityCandidate( - in record, - animatedEntityIds?.Contains(record.Source.LocalEntityId) == true); - } - - private static int CompactAndSort( - RenderProjectionRecord[] records, - int count) - { - int write = 0; - for (int read = 0; read < count; read++) - { - RenderProjectionRecord record = records[read]; - if (record.Id.Domain == EnvCellProjectionDomain - || record.MeshSet.MeshCount <= 0 - || (record.Flags - & RenderProjectionFlags.SpatiallyResident) == 0) - { - continue; - } - - records[write++] = record; - } - - Array.Sort( - records, - 0, - write, - RenderProjectionRecordSortComparer.Instance); - return write; - } - - private static uint? ParentCell(in RenderProjectionRecord record) => - record.Source.ParentCellId == 0 - ? null - : record.Source.ParentCellId; - - private static void Sphere( - in RenderProjectionRecord record, - out Vector3 center, - out float radius) - { - center = (record.Bounds.Minimum + record.Bounds.Maximum) * 0.5f; - radius = - (record.Bounds.Maximum - record.Bounds.Minimum).Length() * 0.5f; - } - - private static void EnsureCapacity(ref T[] values, int required) - { - if (values.Length >= required) - return; - int capacity = values.Length == 0 ? 4 : values.Length; - while (capacity < required) - capacity = checked(capacity * 2); - Array.Resize(ref values, capacity); - } - - private sealed class RenderProjectionRecordSortComparer : - IComparer - { - public static RenderProjectionRecordSortComparer Instance { get; } = - new(); - - private RenderProjectionRecordSortComparer() - { - } - - public int Compare( - RenderProjectionRecord left, - RenderProjectionRecord right) - { - int value = left.SortKey.Value.CompareTo(right.SortKey.Value); - return value != 0 - ? value - : left.Id.CompareTo(right.Id); - } - } -} diff --git a/tests/AcDream.App.Tests/Rendering/RenderScenePViewFrameProductTests.cs b/tests/AcDream.App.Tests/Rendering/RenderScenePViewFrameProductTests.cs deleted file mode 100644 index 64392224..00000000 --- a/tests/AcDream.App.Tests/Rendering/RenderScenePViewFrameProductTests.cs +++ /dev/null @@ -1,679 +0,0 @@ -using System.Numerics; -using AcDream.App.Rendering; -using AcDream.App.Rendering.Scene; -using AcDream.App.Rendering.Scene.Arch; -using AcDream.App.Rendering.Wb; -using AcDream.App.Rendering.Walk; -using AcDream.Core.World; - -namespace AcDream.App.Tests.Rendering; - -public sealed class RenderScenePViewFrameProductTests -{ - private sealed class RejectAllLookInViews(uint cellId) : IWalkLookInViewSource - { - public IReadOnlyList LookInCellTurns { get; } = [cellId]; - - public bool SphereVisibleInLookInTurn( - int routeIndex, - in Vector3 center, - float radius) => false; - } - - private const uint Landblock = 0xA9B4FFFF; - private const uint Cell = 0xA9B40170; - private static readonly RenderSceneGeneration Generation = - RenderSceneGeneration.FromRaw(1); - - // Campaign FW3.2b-2 (adjudicated 2026-08-30): Builder_PreservesPViewRoutesAndExcludesShellsAndWithdrawnOwners, - // Builder_OrdersMultiSliceBuildingShellsAfterLookIns, and - // Builder_InterleavesEachLookInWithItsOwnPackedBuildingShellRoutes are - // DELETED — all three asserted route-range interleaving for - // LandscapeOutdoorStatic/LandscapeBuildingShell/CellStatic, which - // RenderScenePViewFrameBuilder no longer emits at all (WalkFrameDriver - // draws every outdoor static, building shell, and cell static — look-in - // included — directly through OrderedDrawStream; plan §FW3 - // "FW3.2b-2 — the production rooting", item 3). There is no successor - // assertion to write: the routes themselves are retired, not relocated. - - [Fact] - public void Builder_ReusesOrderedIndicesWhileRefreshingDirtyRecords() - { - // Campaign FW3.2b-2 (adjudicated 2026-08-30): re-expressed against - // the DYNAMIC index (LiveDynamicRoot / DynamicCandidates) instead of - // the now-dead OutdoorStatic index — LoadSceneIndices' dirty-record - // refresh + sort-key reorder mechanism this test proves is SHARED - // between the two indices; the OutdoorStatic side was deleted along - // with BuildOutdoorRoutes (its only reader). - using var scene = new ArchRenderScene(Generation); - RenderProjectionRecord later = Record( - 0x0100_0000_0000_0012, - RenderProjectionClass.LiveDynamicRoot); - RenderProjectionRecord earlier = Record( - 0x0100_0000_0000_0011, - RenderProjectionClass.LiveDynamicRoot); - scene.Apply( - [ - RenderProjectionDelta.Register(Generation, 1, later), - RenderProjectionDelta.Register(Generation, 2, earlier), - ]); - - PortalVisibilityFrame portal = Portal(Cell); - ClipFrameAssembly clip = FullScreenClip(Cell); - ViewconeCuller viewcone = - ViewconeCuller.Build(clip, Matrix4x4.Identity); - var exchange = new RenderFrameExchange(); - var builder = new RenderScenePViewFrameBuilder(); - - Build(frameSequence: 1); - RenderFrameView first = exchange.BorrowLatest(Generation, 1); - try - { - Assert.Equal( - [earlier.Id, later.Id], - first.DynamicCandidates.ToArray() - .Select(static record => record.Id)); - } - finally - { - exchange.Release(in first); - } - - scene.ClearDirty(); - ulong retainedRevision = scene.OpenQuery().IndexRevision; - Matrix4x4 movedTransform = - Matrix4x4.CreateTranslation(0.25f, 0, 0); - RenderProjectionRecord moved = later with - { - Transform = new RenderTransform(movedTransform), - PreviousTransform = - new PreviousRenderTransform( - later.Transform.LocalToWorld), - Bounds = new RenderWorldBounds( - new Vector3(-0.75f, -1, -1), - new Vector3(1.25f, 1, 1)), - }; - scene.Apply( - [ - RenderProjectionDelta.Update( - RenderProjectionDeltaKind.UpdateTransform, - Generation, - 3, - moved), - ]); - Assert.Equal( - retainedRevision, - scene.OpenQuery().IndexRevision); - - Build(frameSequence: 2); - RenderFrameView second = exchange.BorrowLatest(Generation, 2); - try - { - RenderProjectionRecord projected = - second.EntityCandidates.ToArray() - .Single(candidate => - candidate.Projection.Id == later.Id) - .Projection; - Assert.Equal( - movedTransform, - projected.Transform.LocalToWorld); - Assert.Equal( - [earlier.Id, later.Id], - second.DynamicCandidates.ToArray() - .Select(static record => record.Id)); - } - finally - { - exchange.Release(in second); - } - - scene.ClearDirty(); - RenderProjectionRecord reordered = moved with - { - SortKey = new RenderSortKey(0), - }; - scene.Apply( - [ - RenderProjectionDelta.Update( - RenderProjectionDeltaKind.UpdateTransform, - Generation, - 4, - reordered), - ]); - Assert.True( - scene.OpenQuery().IndexRevision > retainedRevision); - - Build(frameSequence: 3); - RenderFrameView third = exchange.BorrowLatest(Generation, 3); - try - { - Assert.Equal( - [later.Id, earlier.Id], - third.DynamicCandidates.ToArray() - .Select(static record => record.Id)); - } - finally - { - exchange.Release(in third); - } - - void Build(ulong frameSequence) - { - RenderSceneQuery query = scene.OpenQuery(); - var input = new RenderScenePViewBuildInput( - query, - new RenderSceneDigest( - query.Generation, - query.Counts, - default), - portal, - clip, - viewcone, - [], - null, - [Cell], - EmptyCellSource.Instance, - [], - RootIsOutdoor: true, - RootFloodCells: new HashSet { Cell }); - builder.Build(exchange, frameSequence, in input); - } - } - - [Fact] - public void Builder_KeysLookInDynamicsToWalkTurnsAndExcludesThemFromDynamicLast() - { - const uint lookInCell = 0xA9B4_0171; - using var scene = new ArchRenderScene(Generation); - RenderProjectionRecord rootDynamic = Record( - 0x0100_0000_0000_0021, - RenderProjectionClass.LiveDynamicRoot, - parentCell: Cell); - RenderProjectionRecord lookInDynamic = Record( - 0x0100_0000_0000_0022, - RenderProjectionClass.LiveDynamicRoot, - parentCell: lookInCell); - RenderProjectionRecord outdoorDynamic = Record( - 0x0100_0000_0000_0023, - RenderProjectionClass.LiveDynamicRoot); - scene.Apply( - [ - RenderProjectionDelta.Register(Generation, 1, rootDynamic), - RenderProjectionDelta.Register(Generation, 2, lookInDynamic), - RenderProjectionDelta.Register(Generation, 3, outdoorDynamic), - ]); - - PortalVisibilityFrame portal = Portal(Cell); - ClipFrameAssembly clip = FullScreenClip(Cell); - ViewconeCuller viewcone = ViewconeCuller.Build(clip, Matrix4x4.Identity); - RenderSceneQuery query = scene.OpenQuery(); - var input = new RenderScenePViewBuildInput( - query, - new RenderSceneDigest(query.Generation, query.Counts, default), - portal, - clip, - viewcone, - LookInCellTurns: [lookInCell], - WalkLookInViews: null, - DrawableCells: [Cell, lookInCell], - Cells: EmptyCellSource.Instance, - AnimatedEntityIds: [], - RootIsOutdoor: false, - RootFloodCells: new HashSet { Cell }); - var exchange = new RenderFrameExchange(); - var builder = new RenderScenePViewFrameBuilder(); - - builder.Build(exchange, frameSequence: 1, in input); - RenderFrameView frame = exchange.BorrowLatest(Generation, 1); - try - { - Assert.DoesNotContain( - frame.RouteRanges.ToArray(), - range => range.Route == RenderFrameCandidateRoute.LookInObject); - - RenderFrameCandidateRange outside = Assert.Single( - frame.RouteRanges.ToArray(), - range => range.Route - == RenderFrameCandidateRoute.LandscapeOutsideDynamic); - RenderProjectionId[] outsideIds = frame.RouteCandidates - .Slice(outside.Offset, outside.Count) - .ToArray() - .Select(static record => record.Id) - .ToArray(); - Assert.Contains(outdoorDynamic.Id, outsideIds); - Assert.DoesNotContain(lookInDynamic.Id, outsideIds); - - RenderFrameCandidateRange dynamicLast = Assert.Single( - frame.RouteRanges.ToArray(), - range => range.Route == RenderFrameCandidateRoute.DynamicLast); - RenderProjectionId[] lastIds = frame.RouteCandidates - .Slice(dynamicLast.Offset, dynamicLast.Count) - .ToArray() - .Select(static record => record.Id) - .ToArray(); - Assert.Contains(rootDynamic.Id, lastIds); - Assert.DoesNotContain(lookInDynamic.Id, lastIds); - } - finally - { - exchange.Release(in frame); - } - } - - [Fact] - public void Builder_CarriesExactWalkConeForPerPartDispatcherAdmission() - { - const uint lookInCell = 0xA9B4_0171; - using var scene = new ArchRenderScene(Generation); - RenderProjectionRecord lookInDynamic = Record( - 0x0100_0000_0000_0022, - RenderProjectionClass.LiveDynamicRoot, - parentCell: lookInCell); - RenderProjectionRecord rootDynamic = Record( - 0x0100_0000_0000_0023, - RenderProjectionClass.LiveDynamicRoot, - parentCell: Cell); - RenderProjectionRecord outdoorDynamic = Record( - 0x0100_0000_0000_0024, - RenderProjectionClass.LiveDynamicRoot); - scene.Apply( - [ - RenderProjectionDelta.Register(Generation, 1, lookInDynamic), - RenderProjectionDelta.Register(Generation, 2, rootDynamic), - RenderProjectionDelta.Register(Generation, 3, outdoorDynamic), - ]); - - PortalVisibilityFrame portal = Portal(Cell); - ClipFrameAssembly clip = FullScreenClip(Cell); - ViewconeCuller viewcone = ViewconeCuller.Build(clip, Matrix4x4.Identity); - RenderSceneQuery query = scene.OpenQuery(); - var input = new RenderScenePViewBuildInput( - query, - new RenderSceneDigest(query.Generation, query.Counts, default), - portal, - clip, - viewcone, - LookInCellTurns: [lookInCell], - WalkLookInViews: new RejectAllLookInViews(lookInCell), - DrawableCells: [Cell, lookInCell], - Cells: EmptyCellSource.Instance, - AnimatedEntityIds: [], - RootIsOutdoor: false, - RootFloodCells: new HashSet { Cell }); - var exchange = new RenderFrameExchange(); - var builder = new RenderScenePViewFrameBuilder(); - - builder.Build(exchange, frameSequence: 1, in input); - RenderFrameView frame = exchange.BorrowLatest(Generation, 1); - try - { - Assert.DoesNotContain( - frame.RouteRanges.ToArray(), - candidate => candidate.Route == RenderFrameCandidateRoute.LookInObject); - Assert.DoesNotContain( - frame.RouteCandidates.ToArray(), - candidate => candidate.Id == rootDynamic.Id); - Assert.DoesNotContain( - frame.RouteCandidates.ToArray(), - candidate => candidate.Id == outdoorDynamic.Id); - Assert.NotNull(frame.WalkLookInViews); - Assert.False(frame.WalkLookInViews!.SphereVisibleInLookInTurn( - routeIndex: 0, - center: Vector3.Zero, - radius: 1f)); - } - finally - { - exchange.Release(in frame); - } - } - - [Fact] - public void Controller_MatchesCurrentPViewThenNamesAWithdrawnCandidate() - { - // Campaign FW3.2b-2 (adjudicated 2026-08-30): no static entities here - // — LandscapeOutdoorStatic/CellStatic no longer flow through the - // packed product at all (WalkFrameDriver owns every static now), so - // a static observed on the LEGACY oracle side with nothing on the - // packed side would report a permanent mismatch unrelated to what - // this test is proving (withdrawal detection on the SURVIVING - // DynamicLast route). - WorldEntity outdoorDynamic = - Entity(1_000_012, 0x8000_0012, parentCell: null); - WorldEntity cellDynamic = - Entity(1_000_013, 0x8000_0013, parentCell: Cell); - WorldEntity[] entities = [outdoorDynamic, cellDynamic]; - var oracle = new CurrentRenderSceneOracle(); - var partition = new InteriorEntityPartition.Result(); - InteriorEntityPartition.Partition( - partition, - [Cell], - [Entry(entities)], - oracle); - oracle.BeginPViewFrame(); - oracle.ObservePViewBucket( - CurrentRenderPViewRoute.DynamicLast, - 0, - 0, - [outdoorDynamic, cellDynamic]); - oracle.CompletePViewFrame(); - oracle.BeginDispatcherFrame(); - oracle.ObserveDispatcherDraw( - WbDrawDispatcher.EntitySet.All, - entitiesWalked: 2, - [ - (outdoorDynamic, 0, Landblock), - (cellDynamic, 0, Landblock), - ]); - - using var shadow = new RenderSceneShadowRuntime(Generation); - foreach (WorldEntity entity in entities) - { - RenderProjectionRecord record = Project(entity); - shadow.Journal.Register(in record); - } - shadow.DrainUpdateBoundary(); - var logs = new List(); - var controller = new RenderScenePViewFrameProductController( - shadow, - oracle, - logs.Add); - PortalVisibilityFrame portal = Portal(Cell); - ClipFrameAssembly clip = FullScreenClip(Cell); - ViewconeCuller viewcone = - ViewconeCuller.Build(clip, Matrix4x4.Identity); - - controller.BuildAndCompare( - portal, - clip, - viewcone, - [], - [Cell], - EmptyCellSource.Instance, - [], - Landblock, - rootIsOutdoor: true); - - RenderFrameProductComparisonSnapshot matched = controller.Snapshot; - Assert.True(matched.Enabled); - Assert.Equal(1uL, matched.ComparisonCount); - Assert.Equal(1uL, matched.SuccessfulComparisonCount); - Assert.Equal(2, matched.ExpectedCandidateCount); - Assert.Equal(2, matched.ActualCandidateCount); - Assert.Equal(0, matched.MismatchCount); - Assert.Null(matched.FirstMismatch); - Assert.Equal(matched.ExpectedDigest, matched.ActualDigest); - Assert.Equal(1uL, matched.PackedInputComparisonCount); - Assert.Equal(1uL, matched.PackedInputSuccessfulComparisonCount); - Assert.Equal(2, matched.PackedInputExpectedCount); - Assert.Equal(2, matched.PackedInputActualCount); - Assert.Equal(0, matched.PackedInputMismatchCount); - Assert.Null(matched.PackedInputFirstMismatch); - Assert.Equal( - matched.PackedInputExpectedDigest, - matched.PackedInputActualDigest); - Assert.Empty(logs); - - RenderProjectionId withdrawnId = - LiveRenderProjectionJournal.ProjectionId(cellDynamic.Id); - shadow.Journal.Unregister( - withdrawnId, - RenderOwnerIncarnation.FromRaw(cellDynamic.Id)); - shadow.DrainUpdateBoundary(); - controller.BuildAndCompare( - portal, - clip, - viewcone, - [], - [Cell], - EmptyCellSource.Instance, - [], - Landblock, - rootIsOutdoor: true); - - RenderFrameProductComparisonSnapshot mismatch = controller.Snapshot; - Assert.Equal(2uL, mismatch.ComparisonCount); - Assert.Equal(1uL, mismatch.SuccessfulComparisonCount); - Assert.Equal(1, mismatch.MismatchCount); - Assert.Contains("field=count expected=2 actual=1", mismatch.FirstMismatch); - Assert.Equal(1, mismatch.PackedInputMismatchCount); - Assert.Contains( - "field=count expected=2 actual=1", - mismatch.PackedInputFirstMismatch); - Assert.Equal(2, logs.Count); - } - - [Fact] - public void Controller_RejectsEqualMembershipInDifferentTraversalOrder() - { - WorldEntity first = - Entity(1_000_001, 0x8000_0001, parentCell: null); - WorldEntity second = - Entity(1_000_002, 0x8000_0002, parentCell: null); - var oracle = new CurrentRenderSceneOracle(); - var partition = new InteriorEntityPartition.Result(); - InteriorEntityPartition.Partition( - partition, - [], - [Entry([first, second])], - oracle); - oracle.BeginPViewFrame(); - oracle.ObservePViewBucket( - CurrentRenderPViewRoute.DynamicLast, - 0, - 0, - [first, second]); - oracle.CompletePViewFrame(); - oracle.BeginDispatcherFrame(); - oracle.ObserveDispatcherDraw( - WbDrawDispatcher.EntitySet.All, - entitiesWalked: 2, - [ - (first, 0, Landblock), - (second, 0, Landblock), - ]); - - using var shadow = new RenderSceneShadowRuntime(Generation); - RenderProjectionRecord firstRecord = Project(first) with - { - SortKey = new RenderSortKey(2), - }; - RenderProjectionRecord secondRecord = Project(second) with - { - SortKey = new RenderSortKey(1), - }; - shadow.Journal.Register(in firstRecord); - shadow.Journal.Register(in secondRecord); - shadow.DrainUpdateBoundary(); - var controller = new RenderScenePViewFrameProductController( - shadow, - oracle); - PortalVisibilityFrame portal = Portal(Cell); - ClipFrameAssembly clip = FullScreenClip(Cell); - ViewconeCuller viewcone = - ViewconeCuller.Build(clip, Matrix4x4.Identity); - - controller.BuildAndCompare( - portal, - clip, - viewcone, - [], - [], - EmptyCellSource.Instance, - [], - Landblock, - rootIsOutdoor: true); - - RenderFrameProductComparisonSnapshot snapshot = - controller.Snapshot; - Assert.Equal(1, snapshot.MismatchCount); - Assert.Contains( - $"ProjectionId = {LiveRenderProjectionJournal.ProjectionId(first.Id)}", - snapshot.FirstMismatch); - Assert.Contains( - $"ProjectionId = {LiveRenderProjectionJournal.ProjectionId(second.Id)}", - snapshot.FirstMismatch); - Assert.Equal(1, snapshot.PackedInputMismatchCount); - } - - private static RenderProjectionRecord Project(WorldEntity entity) - { - bool dynamic = entity.ServerGuid != 0; - RenderProjectionId id = dynamic - ? LiveRenderProjectionJournal.ProjectionId(entity.Id) - : StaticRenderProjectionJournal.StaticEntityId( - Landblock, - entity.Id); - RenderProjectionClass projectionClass = dynamic - ? RenderProjectionClass.LiveDynamicRoot - : entity.ParentCellId is uint parentCell - && InteriorEntityPartition.IsIndoorCellId(parentCell) - ? RenderProjectionClass.IndoorCellStatic - : RenderProjectionClass.OutdoorStatic; - return RenderProjectionRecordFactory.ProjectEntity( - id, - projectionClass, - RenderOwnerIncarnation.FromRaw(entity.Id), - Landblock, - entity.ParentCellId ?? Landblock, - entity, - spatiallyVisible: true); - } - - private static RenderProjectionRecord Record( - ulong id, - RenderProjectionClass projectionClass, - uint? parentCell = null, - RenderProjectionFlags flags = - RenderProjectionFlags.Draw - | RenderProjectionFlags.SpatiallyResident, - bool isBuildingShell = false, - uint buildingShellAnchorCellId = 0) - { - uint fullCellId = parentCell ?? Landblock; - Matrix4x4 transform = Matrix4x4.Identity; - return new RenderProjectionRecord( - RenderProjectionId.FromRaw(id), - projectionClass, - RenderOwnerIncarnation.FromRaw(id), - new RenderTransform(transform), - new PreviousRenderTransform(transform), - new RenderMeshSet(RenderAssetHandle.FromRaw(id), 1, 1), - new RenderMaterialVariant(0, 0, 1), - new RenderSpatialResidency( - RenderSpatialBucket.FromRaw(fullCellId), - Landblock, - fullCellId), - new RenderWorldBounds( - new Vector3(-1), - new Vector3(1)), - flags, - default, - new RenderSortKey(id), - RenderDirtyMask.All, - new RenderSourceMetadata( - LocalEntityId: (uint)id, - ServerGuid: projectionClass - is RenderProjectionClass.LiveDynamicRoot ? (uint)id : 0, - SourceId: 1, - ParentCellId: parentCell ?? 0, - EffectCellId: 0, - BuildingShellAnchorCellId: buildingShellAnchorCellId, - TransformFingerprint: default, - GeometryFingerprint: default, - AppearanceFingerprint: default), - new RenderEntityPayload( - [new MeshRef((uint)id, Matrix4x4.Identity)], - PaletteOverride: null, - IsBuildingShell: isBuildingShell)); - } - - private static WorldEntity Entity( - uint id, - uint serverGuid, - uint? parentCell) => - new() - { - Id = id, - ServerGuid = serverGuid, - SourceGfxObjOrSetupId = 0x0200_0001, - Position = Vector3.Zero, - Rotation = Quaternion.Identity, - MeshRefs = - [ - new MeshRef(0x0100_0001, Matrix4x4.Identity), - ], - ParentCellId = parentCell, - }; - - private static PortalVisibilityFrame Portal(uint cellId) - { - var frame = new PortalVisibilityFrame(); - frame.OrderedVisibleCells.Add(cellId); - return frame; - } - - private static ClipFrameAssembly FullScreenClip(uint cellId) - { - var slice = new ClipViewSlice( - Slot: 0, - NdcAabb: new Vector4(-1, -1, 1, 1), - Planes: []); - var assembly = new ClipFrameAssembly(); - assembly.SetOutsideViewSlices([slice]); - assembly.CellIdToViewSlices[cellId] = [slice]; - return assembly; - } - - private static ClipFrameAssembly TwoSliceClip(uint cellId) - { - var first = new ClipViewSlice( - Slot: 0, - NdcAabb: new Vector4(-1, -1, 0, 1), - Planes: []); - var second = new ClipViewSlice( - Slot: 1, - NdcAabb: new Vector4(0, -1, 1, 1), - Planes: []); - var assembly = new ClipFrameAssembly(); - assembly.SetOutsideViewSlices([first, second]); - assembly.CellIdToViewSlices[cellId] = [first]; - return assembly; - } - - private static ( - uint LandblockId, - Vector3 AabbMin, - Vector3 AabbMax, - IReadOnlyList Entities, - IReadOnlyDictionary? AnimatedById) - Entry(IReadOnlyList entities) => - ( - Landblock, - Vector3.Zero, - Vector3.One, - entities, - null); - - private sealed class EmptyCellSource : IRetailPViewCellSource - { - public static EmptyCellSource Instance { get; } = new(); - - private EmptyCellSource() - { - } - - public LoadedCell? Find(uint cellId) => null; - } - - private sealed class DictionaryCellSource : IRetailPViewCellSource - { - private readonly Dictionary _cells; - - public DictionaryCellSource(params LoadedCell[] cells) => - _cells = cells.ToDictionary(static cell => cell.CellId); - - public LoadedCell? Find(uint cellId) => - _cells.TryGetValue(cellId, out LoadedCell? cell) ? cell : null; - } -}