feat(overhaul): select pack shadows from retail visibility

Borrow the exact prior-completed landscape visibility transaction and S2 CELLARRAY owner for the opt-in IA-24 directional-shadow pack. Select terrain by authored 1..64 cells, ordinary casters by CELLARRAY, and buildings by outdoor EffectCellId with no fallback.

Keep retained caster/material/terrain topology stable across visibility-only frames. Publish exact arbitrary active instance runs and bounded terrain commands through separate selection sequences; preserve transform-journal, fade/retry, deferral, shader/RHI, ordinary world, and pack-off behavior. Amend IA-24 and the S5 ledger.

Pre-commit gates: Release solution build 0 warnings/0 errors; focused visibility/frame/caster/prepared/GPU/terrain/pack lane 137/137; warmed caster/prepared/terrain selectors 0 B; git diff --check clean. Official hermetic and InstalledDat evidence intentionally run post-commit from this exact clean tree.

Mutation evidence (each restored exactly): (1) CELLARRAY->Parent first failed PriorLandscapeSelection expected [201,202,203,205], actual [204,205]. (2) all resident terrain first failed Assert.Single with 3 commands. (3) building EffectCell->anchor first failed expected trailing 205, actual 206. (4) admit missing membership first failed with extra 204. (5) completed->building scratch first failed completed-view Assert.True, expected true/actual false. (6) selection advanced BuildSequence first failed expected 1/actual 2. (7) alternating->prefix first failed active command count expected 3/actual 1.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-09-04 17:52:29 +02:00
parent 5c106bcdff
commit a4de2efc4e
19 changed files with 1198 additions and 100 deletions

View file

@ -1,5 +1,7 @@
using System.Numerics;
using AcDream.App.Rendering;
using AcDream.App.Rendering.Scene;
using AcDream.App.Rendering.Vfx;
using AcDream.Core.World;
namespace AcDream.App.Tests.Rendering;
@ -106,6 +108,107 @@ public sealed class DirectionalShadowCasterFrameTests
Assert.Equal(2, frame.Stats.IndexCopies);
}
[Fact]
public void PriorLandscapeSelection_UsesExactCellArrayAndBuildingEffectCell()
{
const uint visible = 0x12340002u;
const uint outdoorAboveTerrainRange = 0x12340041u;
RenderProjectionRecord[] statics =
[
Record(201, RenderProjectionClass.OutdoorStatic),
Record(204, RenderProjectionClass.OutdoorStatic, parentCell: visible),
Record(205, RenderProjectionClass.OutdoorStatic, building: true,
effectCell: visible, buildingAnchor: 0x12340100u),
Record(206, RenderProjectionClass.OutdoorStatic, building: true,
effectCell: 0u, buildingAnchor: visible),
];
RenderProjectionRecord[] dynamics =
[
Record(202, RenderProjectionClass.LiveDynamicRoot),
Record(203, RenderProjectionClass.EquippedChild),
];
var source = new QuerySource(statics, dynamics);
var frame = new DirectionalShadowCasterFrame();
var membership = new RecordingMembership(
new Dictionary<uint, IReadOnlyList<uint>>
{
[201] = [0x12340001u, visible],
[202] = [visible],
[203] = [outdoorAboveTerrainRange],
});
var visibleCells = new HashSet<uint>
{
visible,
outdoorAboveTerrainRange,
};
var visibility = new RetailLandscapeVisibilityFrame(
visibleCells,
HasCompletedWorldView: true);
frame.Build(new RenderSceneQuery(source, Generation));
ulong topologySequence = frame.BuildSequence;
frame.Select(in visibility, membership);
ulong[] selected = frame.Casters.ToArray()
.Zip(frame.SelectedCasters.ToArray())
.Where(static pair => pair.Second)
.Select(static pair => pair.First.Projection.Id.RawValue)
.ToArray();
Assert.Equal([201ul, 202ul, 203ul, 205ul], selected);
Assert.Equal(4, frame.Stats.ActiveSelected);
Assert.Equal(topologySequence, frame.BuildSequence);
Assert.Equal(0x12340100u,
frame.Casters.ToArray().Single(c => c.Projection.Id.RawValue == 205)
.Projection.Source.BuildingShellAnchorCellId);
Assert.DoesNotContain(204ul, selected); // ParentCell is not CELLARRAY.
Assert.DoesNotContain(206ul, selected); // Anchor is not placement.
var noCompletedView = new RetailLandscapeVisibilityFrame(
visibleCells,
HasCompletedWorldView: false);
frame.Build(new RenderSceneQuery(source, Generation));
frame.Select(in noCompletedView, membership);
Assert.Equal(topologySequence, frame.BuildSequence);
Assert.Equal(0, frame.Stats.Classifications);
Assert.Equal(0, frame.Stats.ActiveSelected);
Assert.DoesNotContain(true, frame.SelectedCasters.ToArray());
}
[Fact]
public void WarmPriorLandscapeSelection_AllocatesZeroWithStreamingBoundedScratch()
{
const uint visible = 0x12340002u;
var statics = new RenderProjectionRecord[256];
var cells = new Dictionary<uint, IReadOnlyList<uint>>(statics.Length);
for (int index = 0; index < statics.Length; index++)
{
uint entityId = checked((uint)(30_000 + index));
statics[index] = Record(entityId, RenderProjectionClass.OutdoorStatic);
cells.Add(entityId, index % 2 == 0 ? [visible] : [0x12340003u]);
}
var source = new QuerySource(statics, []);
var frame = new DirectionalShadowCasterFrame();
RenderSceneQuery query = new(source, Generation);
frame.Build(in query);
frame.Build(in query);
var membership = new RecordingMembership(cells);
var visibility = new RetailLandscapeVisibilityFrame(
new HashSet<uint> { visible },
HasCompletedWorldView: true);
frame.Select(in visibility, membership);
long retainedBytes = frame.RetainedScratchBytes;
ZeroAllocationProbe.AssertAllocatesNothing(
"DirectionalShadowCasterFrame.Select prior completed landscape",
() => frame.Select(in visibility, membership),
batchSize: 256);
Assert.Equal(128, frame.Stats.ActiveSelected);
Assert.Equal(retainedBytes, frame.RetainedScratchBytes);
Assert.Equal(1ul, frame.BuildSequence);
Assert.Equal(0, frame.Stats.Classifications);
}
[Fact]
public void UnchangedSecondFrame_ReusesSortedTopologyWithoutIndexCopiesOrClassification()
{
@ -575,6 +678,8 @@ public sealed class DirectionalShadowCasterFrameTests
bool building = false,
RenderProjectionFlags extraFlags = RenderProjectionFlags.None,
uint parentCell = 0,
uint effectCell = 0,
uint buildingAnchor = 0,
ulong? sortKey = null,
RenderCasterIdentityKind casterIdentity =
RenderCasterIdentityKind.Unclassified)
@ -606,8 +711,8 @@ public sealed class DirectionalShadowCasterFrameTests
: 0,
SourceId: (uint)id,
ParentCellId: parentCell,
EffectCellId: 0,
BuildingShellAnchorCellId: 0,
EffectCellId: effectCell,
BuildingShellAnchorCellId: buildingAnchor,
TransformFingerprint: default,
GeometryFingerprint: default,
AppearanceFingerprint: default),
@ -619,6 +724,24 @@ public sealed class DirectionalShadowCasterFrameTests
};
}
private sealed class RecordingMembership(
IReadOnlyDictionary<uint, IReadOnlyList<uint>> cellsByEntity)
: IDirectionalShadowCellMembership
{
public bool TryGetRetailCellArray(
uint entityId,
out IReadOnlyList<uint> cells)
{
if (cellsByEntity.TryGetValue(entityId, out IReadOnlyList<uint>? found))
{
cells = found;
return found.Count != 0;
}
cells = Array.Empty<uint>();
return false;
}
}
private sealed class QuerySource : IRenderSceneQuerySource
{
private RenderProjectionRecord[] _statics;

View file

@ -939,6 +939,84 @@ public sealed class DirectionalShadowGpuTests
allocation => allocation.Usage == GpuRingUsage.Indirect);
}
[Fact]
public void ActiveAlternatingRuns_UploadOnlySelectedTransformAddresses()
{
using var device = new RecordingGpuDevice();
using var renderer = new DirectionalSunShadowRenderer(
device,
DirectionalShadowPreset.Medium);
var world = new DirectionalShadowPreparedDraws();
RenderSceneGeneration generation = RenderSceneGeneration.FromRaw(21);
Assert.True(world.TryBegin(generation, 44, estimatedInstances: 6));
for (int casterIndex = 0; casterIndex < 6; casterIndex++)
{
Matrix4x4 transform = Matrix4x4.CreateTranslation(casterIndex, 0f, 0f);
DirectionalShadowTransformSource source =
DirectionalShadowTransformSource.Static(casterIndex);
world.Add(
100,
7,
12,
GpuTextureSlot.Unassigned,
0,
CullMode.CounterClockwise,
DirectionalShadowCasterMaterial.Opaque,
in transform,
in source);
}
DirectionalShadowPreparationStats stats = default;
world.Complete(generation, 44, in stats);
world.ApplySelection(
[true, false, true, false, true, false],
casterSelectionSequence: 1);
var terrain = new DirectionalShadowTerrainPreparedDraws();
Assert.True(terrain.TryBegin(1, 0));
terrain.Complete(1);
using IGpuBuffer vertices = Buffer(device, "world-v", GpuBufferUsage.Vertex);
using IGpuBuffer indices = Buffer(device, "world-i", GpuBufferUsage.Index);
var geometry = new DirectionalShadowMeshGeometry(vertices, indices);
device.Clear();
using IGpuFrame frame = device.BeginFrame();
WorldTransformFrameSlice transforms = PublishSharedTransforms(frame, world.Transforms);
DirectionalSunShadowDiagnostics diagnostics = renderer.RenderPrepared(
frame,
EnabledEnvironment(),
Matrix4x4.Identity,
Matrix4x4.CreatePerspectiveFieldOfView(
MathF.PI / 3f,
16f / 9f,
0.1f,
500f),
cameraNearMeters: 0.1f,
casterDepthPaddingMeters: 48f,
world,
terrain,
geometry,
terrainGeometry: null,
transforms);
RecordingGpuBuffer commands = Assert.Single(
device.CreatedBuffers,
buffer => buffer.Name == "directional-shadow-world-commands-2");
Span<byte> bytes = stackalloc byte[3 * 20];
commands.Read(0, bytes);
ReadOnlySpan<DrawElementsIndirectCommand> uploaded =
MemoryMarshal.Cast<byte, DrawElementsIndirectCommand>(bytes);
Assert.Equal([0u, 2u, 4u],
uploaded.ToArray().Select(static command => command.BaseInstance));
Assert.All(uploaded.ToArray(),
static command => Assert.Equal(1u, command.InstanceCount));
Assert.All(
device.OfKind<GpuRecordedMultiDrawIndirect>(),
static draw => Assert.Equal(3u, draw.DrawCount));
Assert.Equal(6, diagnostics.ResidentWorldInstances);
Assert.Equal(3, diagnostics.ActiveWorldInstances);
Assert.Equal(1, diagnostics.ResidentWorldCommands);
Assert.Equal(3, diagnostics.ActiveWorldCommands);
}
[Fact]
public void TopologyRebuild_SwapsRetainedBuffersAndDisposalReleasesTheCurrentSet()
{

View file

@ -1463,6 +1463,63 @@ public sealed class AtmosphericPostProcessGraphTests
Assert.Equal(first.WindAmplitude, second.WindAmplitude);
}
[Fact]
public void BuiltInAndDeclaredShadowGraphsUseTheSameTypedPriorVisibilitySelector()
{
string renderingRoot = Path.Combine(
RepositoryRoot(),
"src",
"AcDream.App",
"Rendering",
"Packs");
string builtIn = File.ReadAllText(
Path.Combine(renderingRoot, "AtmosphericPostProcessGraph.cs"));
string declared = File.ReadAllText(
Path.Combine(renderingRoot, "DeclaredFullscreenRenderPackGraph.cs"));
AssertGraphUsesTypedSelection(builtIn, "RenderDirectionalShadows(");
AssertGraphUsesTypedSelection(declared, "RenderDeclaredDirectionalShadows(");
string register = File.ReadAllText(Path.Combine(
RepositoryRoot(),
"docs",
"architecture",
"retail-divergence-register.md"));
string ia24 = Assert.Single(register.Split('\n'), static line =>
line.StartsWith("| IA-24 |", StringComparison.Ordinal));
Assert.Contains("prior successfully completed", ia24,
StringComparison.Ordinal);
Assert.Contains("S2's retained retail CELLARRAY", ia24,
StringComparison.Ordinal);
Assert.Contains("building shells by their outdoor placement `EffectCellId`", ia24,
StringComparison.Ordinal);
Assert.Contains("pack-off does not build, select, upload, or draw shadow work", ia24,
StringComparison.Ordinal);
static void AssertGraphUsesTypedSelection(string source, string methodName)
{
int start = source.IndexOf(methodName, StringComparison.Ordinal);
Assert.True(start >= 0, $"missing {methodName}");
int end = source.IndexOf("public IGpuRenderTarget PrepareWorldTarget", start,
StringComparison.Ordinal);
Assert.True(end > start, $"could not bound {methodName}");
string method = source[start..end];
Assert.Contains(
"RetailLandscapeVisibilityFrame priorLandscapeVisibility =",
method,
StringComparison.Ordinal);
Assert.Contains("world.PriorLandscapeVisibility", method,
StringComparison.Ordinal);
Assert.Contains("_shadowCasters.Select(", method, StringComparison.Ordinal);
Assert.Contains("world.DirectionalShadowCellMembership", method,
StringComparison.Ordinal);
Assert.Contains("EmptyDirectionalShadowCellMembership.Instance", method,
StringComparison.Ordinal);
Assert.Contains("PriorLandscapeVisibility: world.PriorLandscapeVisibility", method,
StringComparison.Ordinal);
}
}
private static AtmosphericFrameUniforms RenderAndReadFrameBlock(
RecordingGpuDevice device,
AtmosphericPostProcessGraph graph,

View file

@ -96,6 +96,56 @@ public sealed class ParticleVisibilityControllerTests
Assert.True(Assert.Single(particles.EnumerateEmitters()).ViewEligible);
}
[Fact]
public void BorrowedLandscapeFrame_TracksOnlyCompletedTransactionsByReference()
{
var controller = new ParticleVisibilityController();
RetailLandscapeVisibilityFrame first =
controller.CaptureCompletedLandscapeVisibility();
Assert.False(first.HasCompletedWorldView);
Assert.Empty(first.CellIds);
// A completed null-root safety frame is authoritative and empty.
controller.BeginFrame(Vector3.Zero);
controller.UseWorldView();
controller.CompleteFrame();
RetailLandscapeVisibilityFrame completedEmpty =
controller.CaptureCompletedLandscapeVisibility();
Assert.True(completedEmpty.HasCompletedWorldView);
Assert.Empty(completedEmpty.CellIds);
Assert.Same(first.CellIds, completedEmpty.CellIds);
controller.BeginFrame(Vector3.One);
controller.UseWorldView();
controller.MarkVisibleLandscapeCells([0x12340001u]);
RetailLandscapeVisibilityFrame whileBuilding =
controller.CaptureCompletedLandscapeVisibility();
Assert.Same(completedEmpty.CellIds, whileBuilding.CellIds);
Assert.Empty(whileBuilding.CellIds);
controller.AbortFrame();
RetailLandscapeVisibilityFrame aborted =
controller.CaptureCompletedLandscapeVisibility();
Assert.Same(completedEmpty.CellIds, aborted.CellIds);
Assert.True(aborted.HasCompletedWorldView);
Assert.Empty(aborted.CellIds);
controller.BeginFrame(Vector3.One);
controller.UseWorldView();
controller.MarkVisibleLandscapeCells([0x12340001u]);
controller.CompleteFrame();
RetailLandscapeVisibilityFrame replaced =
controller.CaptureCompletedLandscapeVisibility();
Assert.Same(completedEmpty.CellIds, replaced.CellIds);
Assert.Equal([0x12340001u], replaced.CellIds);
controller.Reset();
RetailLandscapeVisibilityFrame reset =
controller.CaptureCompletedLandscapeVisibility();
Assert.Same(completedEmpty.CellIds, reset.CellIds);
Assert.False(reset.HasCompletedWorldView);
Assert.Empty(reset.CellIds);
}
[Fact]
public void CompleteAbortAndResetPublishOnlyCompleteLandscapeTransactions()
{

View file

@ -262,6 +262,63 @@ public sealed class DirectionalShadowPreparedDrawTests
Assert.Single(product.Commands.ToArray());
}
[Fact]
public void AlternatingCasterSelection_EmitsExactContiguousInstanceRuns()
{
var product = new DirectionalShadowPreparedDraws();
RenderSceneGeneration generation = RenderSceneGeneration.FromRaw(18);
Assert.True(product.TryBegin(generation, 33, estimatedInstances: 6));
for (int casterIndex = 0; casterIndex < 6; casterIndex++)
{
Matrix4x4 transform = Matrix4x4.CreateTranslation(casterIndex, 0f, 0f);
DirectionalShadowTransformSource source =
DirectionalShadowTransformSource.Static(casterIndex);
product.Add(
firstIndex: 100,
baseVertex: 7,
indexCount: 12,
GpuTextureSlot.Unassigned,
textureLayer: 0,
CullMode.CounterClockwise,
DirectionalShadowCasterMaterial.Opaque,
in transform,
in source);
}
DirectionalShadowPreparationStats stats = default;
product.Complete(generation, 33, in stats);
ulong topologySequence = product.BuildSequence;
bool[] alternating = [true, false, true, false, true, false];
product.ApplySelection(alternating, casterSelectionSequence: 1);
Assert.Single(product.Commands.ToArray());
Assert.Equal(6u, product.Commands[0].InstanceCount);
Assert.Equal(3, product.ActiveCommands.Length);
Assert.Equal([0u, 2u, 4u],
product.ActiveCommands.ToArray().Select(static command => command.BaseInstance));
Assert.All(product.ActiveCommands.ToArray(),
static command => Assert.Equal(1u, command.InstanceCount));
Assert.Equal(3, product.ActiveBatches.Length);
Assert.Equal(3, product.Stats.ActiveInstances);
Assert.Equal(3, product.Stats.ActiveCommands);
Assert.Equal(topologySequence, product.BuildSequence);
bool[] inverse = [false, true, false, true, false, true];
product.ApplySelection(inverse, casterSelectionSequence: 2);
Assert.Equal([1u, 3u, 5u],
product.ActiveCommands.ToArray().Select(static command => command.BaseInstance));
Assert.Equal(topologySequence, product.BuildSequence);
ulong selectionSequence = 2;
ZeroAllocationProbe.AssertAllocatesNothing(
"DirectionalShadowPreparedDraws.ApplySelection alternating",
() => product.ApplySelection(
(selectionSequence & 1ul) == 0ul ? alternating : inverse,
++selectionSequence),
batchSize: 256);
Assert.Equal(topologySequence, product.BuildSequence);
}
[Fact]
public void StableTopology_ComposesSlimPoseWithoutMutatingCasterOrAllocating()
{

View file

@ -1,4 +1,6 @@
using AcDream.App.Rendering;
using AcDream.App.Rendering.Vfx;
using AcDream.App.Rendering.Wb;
namespace AcDream.App.Tests.Rendering.Wb;
@ -72,4 +74,50 @@ public sealed class DirectionalShadowTerrainPreparedDrawTests
Assert.Single(product.Commands.ToArray());
Assert.Equal(20u, product.Commands[0].FirstIndex);
}
[Fact]
public void PriorLandscapeSelection_ScansExactAuthoredEightByEightCells()
{
var product = new DirectionalShadowTerrainPreparedDraws();
Assert.True(product.TryBegin(frameSequence: 1, estimatedCommands: 3));
DirectionalShadowTerrainRange[] resident =
[
new(FirstIndex: 0, IndexCount: 384, LandblockId: 0x1111FFFFu),
new(FirstIndex: 384, IndexCount: 384, LandblockId: 0x2222FFFFu),
new(FirstIndex: 768, IndexCount: 384, LandblockId: 0x3333FFFFu),
];
foreach (DirectionalShadowTerrainRange range in resident)
product.Add(in range);
product.Complete(frameSequence: 1);
ulong topologySequence = product.BuildSequence;
var visible = new HashSet<uint>
{
0x11110040u,
0x22220041u, // valid outdoor family, but not this slot's 8x8 cells.
};
var visibility = new RetailLandscapeVisibilityFrame(
visible,
HasCompletedWorldView: true);
product.ApplySelection(in visibility);
DrawElementsIndirectCommand selected = Assert.Single(product.Commands.ToArray());
Assert.Equal(0u, selected.FirstIndex);
Assert.Equal(3, product.ResidentRanges.Length);
Assert.Equal(topologySequence, product.BuildSequence);
var empty = new RetailLandscapeVisibilityFrame(
new HashSet<uint>(),
HasCompletedWorldView: true);
product.ApplySelection(in empty);
Assert.Empty(product.Commands.ToArray());
Assert.Equal(topologySequence, product.BuildSequence);
product.ApplySelection(in visibility);
ZeroAllocationProbe.AssertAllocatesNothing(
"DirectionalShadowTerrainPreparedDraws.ApplySelection",
() => product.ApplySelection(in visibility),
batchSize: 256);
Assert.Equal(topologySequence, product.BuildSequence);
}
}

View file

@ -4,6 +4,7 @@ using AcDream.App.Composition;
using AcDream.App.Input;
using AcDream.App.Rendering;
using AcDream.App.Rendering.Wb;
using AcDream.App.Rendering.Vfx;
using AcDream.App.Streaming;
using AcDream.App.Tests.Architecture;
using AcDream.App.World;
@ -46,7 +47,8 @@ public sealed class WorldRenderFrameBuilderTests
new RecordingRoots(calls, roots),
environment,
new RecordingAnimated(calls, animated),
new RecordingBuildings(calls, new WorldBuildingFrame(null, buildings)));
new RecordingBuildings(calls, new WorldBuildingFrame(null, buildings)),
new RecordingMembership());
WorldRenderFrame result = builder.Build(
in foundation,
@ -55,6 +57,7 @@ public sealed class WorldRenderFrameBuilderTests
Assert.Equal(
[
"visibility:capture",
"camera",
"visibility:begin",
"settings",
@ -83,21 +86,24 @@ public sealed class WorldRenderFrameBuilderTests
var environment = new RecordingEnvironment([]);
var animated = new RecordingAnimated([], []);
var buildings = new RecordingBuildings([], default);
var membership = new RecordingMembership();
Assert.Throws<ArgumentNullException>(() =>
new WorldRenderFrameBuilder(null!, visibility, settings, roots, environment, animated, buildings));
new WorldRenderFrameBuilder(null!, visibility, settings, roots, environment, animated, buildings, membership));
Assert.Throws<ArgumentNullException>(() =>
new WorldRenderFrameBuilder(camera, null!, settings, roots, environment, animated, buildings));
new WorldRenderFrameBuilder(camera, null!, settings, roots, environment, animated, buildings, membership));
Assert.Throws<ArgumentNullException>(() =>
new WorldRenderFrameBuilder(camera, visibility, null!, roots, environment, animated, buildings));
new WorldRenderFrameBuilder(camera, visibility, null!, roots, environment, animated, buildings, membership));
Assert.Throws<ArgumentNullException>(() =>
new WorldRenderFrameBuilder(camera, visibility, settings, null!, environment, animated, buildings));
new WorldRenderFrameBuilder(camera, visibility, settings, null!, environment, animated, buildings, membership));
Assert.Throws<ArgumentNullException>(() =>
new WorldRenderFrameBuilder(camera, visibility, settings, roots, null!, animated, buildings));
new WorldRenderFrameBuilder(camera, visibility, settings, roots, null!, animated, buildings, membership));
Assert.Throws<ArgumentNullException>(() =>
new WorldRenderFrameBuilder(camera, visibility, settings, roots, environment, null!, buildings));
new WorldRenderFrameBuilder(camera, visibility, settings, roots, environment, null!, buildings, membership));
Assert.Throws<ArgumentNullException>(() =>
new WorldRenderFrameBuilder(camera, visibility, settings, roots, environment, animated, null!));
new WorldRenderFrameBuilder(camera, visibility, settings, roots, environment, animated, null!, membership));
Assert.Throws<ArgumentNullException>(() =>
new WorldRenderFrameBuilder(camera, visibility, settings, roots, environment, animated, buildings, null!));
}
[Fact]
@ -502,6 +508,12 @@ public sealed class WorldRenderFrameBuilderTests
{
public bool WaitingForLogin { get; private set; }
public RetailLandscapeVisibilityFrame CaptureCompletedLandscapeVisibility()
{
calls.Add("visibility:capture");
return RetailLandscapeVisibilityFrame.None;
}
public void Begin(in WorldCameraFrame camera, bool waitingForLogin)
{
calls.Add("visibility:begin");
@ -512,6 +524,66 @@ public sealed class WorldRenderFrameBuilderTests
calls.Add("visibility:projection");
}
[Fact]
public void Build_BorrowsExactPriorCompletedLandscapeBeforeCurrentBegin()
{
var particles = new ParticleVisibilityController();
particles.BeginFrame(Vector3.Zero);
particles.UseWorldView();
particles.MarkVisibleLandscapeCells([0x12340002u]);
particles.CompleteFrame();
RetailLandscapeVisibilityFrame ownerBefore =
particles.CaptureCompletedLandscapeVisibility();
var visibility = new RuntimeWorldFrameVisibilityPreparation(
selection: null,
particles,
terrain: null,
reveal: null,
environmentFrustum: null);
var camera = new FlyCamera();
var builder = new WorldRenderFrameBuilder(
new RecordingCamera([], CameraFrame(camera)),
visibility,
new RecordingSettings([]),
new RecordingRoots([], default),
new RecordingEnvironment([]),
new RecordingAnimated([], []),
new RecordingBuildings([], default),
new RecordingMembership());
RenderFrameFoundation foundation = default;
WorldRenderFrame world = builder.Build(
in foundation,
waitingForLogin: false,
activeDayGroup: null);
Assert.Same(ownerBefore.CellIds, world.PriorLandscapeVisibility.CellIds);
Assert.True(world.PriorLandscapeVisibility.HasCompletedWorldView);
Assert.Contains(0x12340002u, world.PriorLandscapeVisibility.CellIds);
Assert.Same(
ownerBefore.CellIds,
particles.CaptureCompletedLandscapeVisibility().CellIds);
WorldRenderFrame login = builder.Build(
in foundation,
waitingForLogin: true,
activeDayGroup: null);
Assert.False(login.PriorLandscapeVisibility.HasCompletedWorldView);
Assert.Empty(login.PriorLandscapeVisibility.CellIds);
}
private sealed class RecordingMembership : IDirectionalShadowCellMembership
{
public bool TryGetRetailCellArray(
uint entityId,
out IReadOnlyList<uint> cells)
{
_ = entityId;
cells = Array.Empty<uint>();
return false;
}
}
private sealed class RecordingSettings(List<string> calls)
: IWorldFrameSettingsPreview
{