refactor(render): delete the packed pview product
This commit is contained in:
parent
78547718c7
commit
8d8aa715ee
5 changed files with 101 additions and 2335 deletions
|
|
@ -354,7 +354,6 @@ internal sealed class FrameRootCompositionPhase
|
||||||
// contract cleanup later in this campaign.
|
// contract cleanup later in this campaign.
|
||||||
CurrentRenderSceneOracle? currentRenderSceneOracle = null;
|
CurrentRenderSceneOracle? currentRenderSceneOracle = null;
|
||||||
RenderSceneShadowComparisonController? renderSceneShadowComparison = null;
|
RenderSceneShadowComparisonController? renderSceneShadowComparison = null;
|
||||||
RenderScenePViewFrameProductController? renderFrameProduct = null;
|
|
||||||
{
|
{
|
||||||
// Campaign V slice V6j: the world scene is composed on BOTH arms.
|
// Campaign V slice V6j: the world scene is composed on BOTH arms.
|
||||||
// Every renderer below now exists on Vulkan too; what forks is one
|
// Every renderer below now exists on Vulkan too; what forks is one
|
||||||
|
|
@ -607,8 +606,7 @@ internal sealed class FrameRootCompositionPhase
|
||||||
?? throw new InvalidOperationException(
|
?? throw new InvalidOperationException(
|
||||||
"Lifecycle automation requires the canonical physics cache."),
|
"Lifecycle automation requires the canonical physics cache."),
|
||||||
currentRenderSceneOracle,
|
currentRenderSceneOracle,
|
||||||
renderSceneShadowComparison,
|
renderSceneShadowComparison);
|
||||||
renderFrameProduct);
|
|
||||||
lifecycleAutomation =
|
lifecycleAutomation =
|
||||||
new WorldLifecycleAutomationController(
|
new WorldLifecycleAutomationController(
|
||||||
() => session.WorldReveal.Snapshot,
|
() => session.WorldReveal.Snapshot,
|
||||||
|
|
|
||||||
|
|
@ -47,8 +47,6 @@ internal sealed class WorldLifecycleResourceSnapshotSource
|
||||||
_renderSceneOracle;
|
_renderSceneOracle;
|
||||||
private readonly IRenderSceneShadowSnapshotSource?
|
private readonly IRenderSceneShadowSnapshotSource?
|
||||||
_renderSceneShadow;
|
_renderSceneShadow;
|
||||||
private readonly IRenderFrameProductSnapshotSource?
|
|
||||||
_renderFrameProduct;
|
|
||||||
|
|
||||||
public WorldLifecycleResourceSnapshotSource(
|
public WorldLifecycleResourceSnapshotSource(
|
||||||
GpuWorldState world,
|
GpuWorldState world,
|
||||||
|
|
@ -69,8 +67,7 @@ internal sealed class WorldLifecycleResourceSnapshotSource
|
||||||
ResidencyManager residency,
|
ResidencyManager residency,
|
||||||
PhysicsDataCache physics,
|
PhysicsDataCache physics,
|
||||||
ICurrentRenderSceneOracleSnapshotSource? renderSceneOracle = null,
|
ICurrentRenderSceneOracleSnapshotSource? renderSceneOracle = null,
|
||||||
IRenderSceneShadowSnapshotSource? renderSceneShadow = null,
|
IRenderSceneShadowSnapshotSource? renderSceneShadow = null)
|
||||||
IRenderFrameProductSnapshotSource? renderFrameProduct = null)
|
|
||||||
{
|
{
|
||||||
_world = world ?? throw new ArgumentNullException(nameof(world));
|
_world = world ?? throw new ArgumentNullException(nameof(world));
|
||||||
_animations = animations
|
_animations = animations
|
||||||
|
|
@ -99,7 +96,6 @@ internal sealed class WorldLifecycleResourceSnapshotSource
|
||||||
_physics = physics ?? throw new ArgumentNullException(nameof(physics));
|
_physics = physics ?? throw new ArgumentNullException(nameof(physics));
|
||||||
_renderSceneOracle = renderSceneOracle;
|
_renderSceneOracle = renderSceneOracle;
|
||||||
_renderSceneShadow = renderSceneShadow;
|
_renderSceneShadow = renderSceneShadow;
|
||||||
_renderFrameProduct = renderFrameProduct;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public WorldLifecycleResourceSnapshot Capture(RenderFrameOutcome outcome)
|
public WorldLifecycleResourceSnapshot Capture(RenderFrameOutcome outcome)
|
||||||
|
|
@ -151,9 +147,7 @@ internal sealed class WorldLifecycleResourceSnapshotSource
|
||||||
RenderSceneShadow:
|
RenderSceneShadow:
|
||||||
_renderSceneShadow?.CaptureCheckpointSnapshot()
|
_renderSceneShadow?.CaptureCheckpointSnapshot()
|
||||||
?? RenderSceneShadowComparisonSnapshot.Disabled,
|
?? RenderSceneShadowComparisonSnapshot.Disabled,
|
||||||
RenderFrameProduct:
|
RenderFrameProduct: RenderFrameProductComparisonSnapshot.Disabled,
|
||||||
_renderFrameProduct?.Snapshot
|
|
||||||
?? RenderFrameProductComparisonSnapshot.Disabled,
|
|
||||||
PendingLiveTeardowns: _liveEntities.PendingTeardownCount,
|
PendingLiveTeardowns: _liveEntities.PendingTeardownCount,
|
||||||
PendingLandblockRetirements: _streaming.PendingRetirementCount,
|
PendingLandblockRetirements: _streaming.PendingRetirementCount,
|
||||||
ParticleEmitters: _particles.ActiveEmitterCount,
|
ParticleEmitters: _particles.ActiveEmitterCount,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,98 @@
|
||||||
|
namespace AcDream.App.Rendering.Scene;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retained lifecycle-automation schema for releases that still deserialize
|
||||||
|
/// the former packed-frame comparison block. Campaign FW4 removed that
|
||||||
|
/// producer; new snapshots always publish <see cref="Disabled"/>.
|
||||||
|
/// </summary>
|
||||||
|
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);
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -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<uint> 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<uint> { 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<uint> { 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<uint> { 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<string>();
|
|
||||||
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<WorldEntity> Entities,
|
|
||||||
IReadOnlyDictionary<uint, WorldEntity>? AnimatedById)
|
|
||||||
Entry(IReadOnlyList<WorldEntity> 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<uint, LoadedCell> _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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue