refactor(render): delete the packed pview product

This commit is contained in:
Erik 2026-08-31 04:37:54 +02:00
parent 78547718c7
commit 8d8aa715ee
5 changed files with 101 additions and 2335 deletions

View file

@ -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,

View file

@ -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,

View file

@ -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

View file

@ -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;
}
}