diff --git a/src/AcDream.App/Rendering/ClipFrameAssembler.cs b/src/AcDream.App/Rendering/ClipFrameAssembler.cs
index 4b12fda6..026dd683 100644
--- a/src/AcDream.App/Rendering/ClipFrameAssembler.cs
+++ b/src/AcDream.App/Rendering/ClipFrameAssembler.cs
@@ -1,9 +1,9 @@
// ClipFrameAssembler.cs
//
-// Retail PView assembly policy. PortalVisibilityBuilder produces a retail-like
-// view graph: one portal_view list per visible cell plus an outside_view list.
-// This assembler packs each visible polygon as an individual GPU clip slot so
-// the renderer can draw the exact PView order:
+// Retail view assembly policy. The production frame walk supplies its
+// outside_view directly; the legacy PortalVisibilityBuilder overload remains
+// only for isolated research/replay tests. Each visible polygon is packed as
+// an individual GPU clip slot.
//
// outside_view landscape slices
// reverse cell_draw_list exit masks
@@ -40,15 +40,7 @@ public enum TerrainClipMode
public readonly record struct ClipViewSlice(int Slot, Vector4 NdcAabb, Vector4[] Planes);
///
-/// Identifies one cell inside one nested building look-in. The same EnvCell can
-/// be reached by more than one building PView, so a cell id alone is not a
-/// sufficient routing key.
-///
-public readonly record struct LookInClipCell(int FrameIndex, uint CellId);
-
-///
-/// Result of : populated clip buffers
-/// plus routing data consumed by the render orchestration.
+/// Populated clip buffers plus routing data consumed by the render walk.
///
public sealed class ClipFrameAssembly
{
@@ -64,12 +56,6 @@ public sealed class ClipFrameAssembly
/// Full retail portal_view slices per visible cell.
public Dictionary CellIdToViewSlices { get; } = new();
- /// First drawable slice slot per nested look-in cell.
- public Dictionary LookInCellToSlot { get; } = new();
-
- /// All retail portal_view slices per nested look-in cell.
- public Dictionary LookInCellToViewSlices { get; } = new();
-
/// Full retail outside_view slices.
public ClipViewSlice[] OutsideViewSlices { get; private set; } = System.Array.Empty();
@@ -106,8 +92,6 @@ public sealed class ClipFrameAssembly
Frame = frame;
foreach (ClipViewSlice[] slices in CellIdToViewSlices.Values)
ReturnSlices(slices);
- foreach (ClipViewSlice[] slices in LookInCellToViewSlices.Values)
- ReturnSlices(slices);
foreach (int[] slots in CellIdToViewSlots.Values)
ReturnSlots(slots);
if (OutsideViewSlices.Length != 0)
@@ -116,8 +100,6 @@ public sealed class ClipFrameAssembly
CellIdToSlot.Clear();
CellIdToViewSlots.Clear();
CellIdToViewSlices.Clear();
- LookInCellToSlot.Clear();
- LookInCellToViewSlices.Clear();
PerCellPlaneCounts.Clear();
OutsideViewSlices = System.Array.Empty();
SliceScratch.Clear();
@@ -272,6 +254,53 @@ public sealed class ClipFrameAssembly
public static class ClipFrameAssembler
{
+ ///
+ /// Starts one production walk assembly without constructing a parallel
+ /// PortalVisibilityFrame. Outdoor roots begin with retail's full-screen
+ /// default view; interior roots begin empty and are populated from
+ /// after Collect.
+ ///
+ public static ClipFrameAssembly BeginWalkFrame(
+ ClipFrame frame,
+ bool outdoorRoot,
+ ClipFrameAssembly? reuseAssembly = null)
+ {
+ System.ArgumentNullException.ThrowIfNull(frame);
+ frame.Reset();
+ ClipFrameAssembly assembly = reuseAssembly ?? new ClipFrameAssembly();
+ assembly.Reset(frame);
+
+ if (outdoorRoot)
+ {
+ List slices = assembly.SliceScratch;
+ slices.Clear();
+ var fullScreen = new Vector4(-1f, -1f, 1f, 1f);
+ slices.Add(new ClipViewSlice(0, fullScreen, System.Array.Empty()));
+ assembly.SetOutsideViewSlices(assembly.CopySlices(slices));
+ assembly.OutdoorSlot = 0;
+ assembly.OutdoorVisible = true;
+ assembly.TerrainMode = TerrainClipMode.Scissor;
+ assembly.TerrainScissorNdcAabb = fullScreen;
+ assembly.HasOutsideView = true;
+ assembly.OutsideViewNdcAabb = fullScreen;
+ assembly.OutsidePlaneCount = 0;
+ assembly.ScissorFallbacks = 1;
+ }
+ else
+ {
+ assembly.OutdoorSlot = 0;
+ assembly.OutdoorVisible = false;
+ assembly.TerrainMode = TerrainClipMode.Skip;
+ assembly.TerrainScissorNdcAabb = Vector4.Zero;
+ assembly.HasOutsideView = false;
+ assembly.OutsideViewNdcAabb = Vector4.Zero;
+ assembly.OutsidePlaneCount = 0;
+ assembly.ScissorFallbacks = 0;
+ }
+
+ return assembly;
+ }
+
public static ClipFrameAssembly Assemble(
ClipFrame frame,
PortalVisibilityFrame pvFrame,
@@ -529,70 +558,6 @@ public static class ClipFrameAssembler
assembly.ScissorFallbacks = scissorFallbacks;
}
- ///
- /// Appends the cell views used by nested DrawBuilding -> DrawPortal
- /// PViews to the already assembled frame. Retail installs each nested
- /// cell's own portal_view before drawing its shell and object list;
- /// these slots must therefore be published with the main frame before the
- /// first draw is recorded.
- ///
- public static void AppendLookInFrames(
- ClipFrame frame,
- IReadOnlyList lookInFrames,
- ClipFrameAssembly assembly)
- {
- System.ArgumentNullException.ThrowIfNull(frame);
- System.ArgumentNullException.ThrowIfNull(lookInFrames);
- System.ArgumentNullException.ThrowIfNull(assembly);
- if (!ReferenceEquals(frame, assembly.Frame))
- throw new System.ArgumentException(
- "The look-in slots must be appended to the assembly's clip frame.",
- nameof(frame));
-
- for (int frameIndex = 0; frameIndex < lookInFrames.Count; frameIndex++)
- {
- PortalVisibilityFrame lookIn = lookInFrames[frameIndex];
- foreach (uint cellId in lookIn.OrderedVisibleCells)
- {
- if (!lookIn.CellViews.TryGetValue(cellId, out CellView? view))
- continue;
-
- List slices = assembly.SliceScratch;
- slices.Clear();
- foreach (ViewPolygon poly in view.Polygons)
- {
- ClipPlaneSet cps = ClipPlaneSet.From(poly);
- if (cps.IsNothingVisible)
- continue;
-
- int slot;
- Vector4[] planes;
- if (cps.Count > 0)
- {
- planes = cps.PlaneArray;
- slot = frame.AppendSlot(planes);
- }
- else
- {
- planes = System.Array.Empty();
- slot = 0;
- assembly.ScissorFallbacks++;
- }
-
- slices.Add(new ClipViewSlice(slot, AabbOf(poly), planes));
- }
-
- if (slices.Count == 0)
- continue;
-
- ClipViewSlice[] packed = assembly.CopySlices(slices);
- var key = new LookInClipCell(frameIndex, cellId);
- assembly.LookInCellToViewSlices.Add(key, packed);
- assembly.LookInCellToSlot.Add(key, packed[0].Slot);
- }
- }
- }
-
private static Vector4 AabbOf(ViewPolygon poly) =>
new(poly.MinX, poly.MinY, poly.MaxX, poly.MaxY);
diff --git a/src/AcDream.App/Rendering/RetailPViewPassExecutor.cs b/src/AcDream.App/Rendering/RetailPViewPassExecutor.cs
index 17f70b50..b2b246c0 100644
--- a/src/AcDream.App/Rendering/RetailPViewPassExecutor.cs
+++ b/src/AcDream.App/Rendering/RetailPViewPassExecutor.cs
@@ -205,10 +205,10 @@ internal sealed partial class RetailPViewPassExecutor :
}
}
- public ClipFrameAssembly AssembleClipFrame(
- PortalVisibilityFrame portalFrame,
+ public ClipFrameAssembly BeginWalkClipFrame(
+ bool outdoorRoot,
ClipFrameAssembly reuseAssembly) =>
- ClipFrameAssembler.Assemble(_clipFrame, portalFrame, reuseAssembly);
+ ClipFrameAssembler.BeginWalkFrame(_clipFrame, outdoorRoot, reuseAssembly);
public void PrepareClipFrame(int terrainUploadCount) =>
_surface.PrepareClipFrame(terrainUploadCount);
diff --git a/src/AcDream.App/Rendering/RetailPViewRenderer.cs b/src/AcDream.App/Rendering/RetailPViewRenderer.cs
index d1bdba89..6ff79d4a 100644
--- a/src/AcDream.App/Rendering/RetailPViewRenderer.cs
+++ b/src/AcDream.App/Rendering/RetailPViewRenderer.cs
@@ -14,7 +14,6 @@ namespace AcDream.App.Rendering;
internal sealed class RetailPViewRenderer
{
private readonly RenderSceneShadowRuntime _renderSceneShadow;
- private readonly PortalVisibilityFrame _mainPortalFrameScratch = new();
private readonly ClipFrameAssembly _clipAssemblyScratch = new();
private readonly RetailPViewFrameResult _frameResultScratch = new();
@@ -98,19 +97,8 @@ internal sealed class RetailPViewRenderer
RetailPViewPassExecutor walkExecutor = passes as RetailPViewPassExecutor
?? throw new InvalidOperationException(
"The retail frame walk requires RetailPViewPassExecutor.");
- // Compatibility carrier only. Production visibility is populated
- // exclusively by RetailFrameWalk below; this frame supplies the clip
- // assembler's outdoor full-screen seed until that carrier is removed.
- PortalVisibilityFrame pvFrame = _mainPortalFrameScratch;
- pvFrame.ResetForBuild();
- if (ctx.RootCell.IsOutdoorNode)
- {
- pvFrame.OutsideView.SetFullScreen();
- pvFrame.OrderedVisibleCells.Add(ctx.RootCell.CellId);
- }
-
- var clipAssembly = passes.AssembleClipFrame(
- pvFrame,
+ ClipFrameAssembly clipAssembly = passes.BeginWalkClipFrame(
+ ctx.RootCell.IsOutdoorNode,
_clipAssemblyScratch);
// FW4 slice 1: PrepareClipFrame (the one clip-region publication)
// moved BELOW the walk block — an interior-rooted walk frame
@@ -123,8 +111,7 @@ internal sealed class RetailPViewRenderer
// (the old clipAssembly.CellIdToSlot.Keys filter silently dropped slot-less cells).
// Per-slice trim still applies in DrawEnvCellShells (Task 4 makes it self-contained).
_drawableCellsScratch.Clear();
- _drawableCellsScratch.UnionWith(pvFrame.OrderedVisibleCells);
- var drawableCells = _drawableCellsScratch;
+ HashSet drawableCells = _drawableCellsScratch;
passes.UseIndoorMembershipOnlyRouting();
// Campaign FW3.4a: THE ONE WALK. Builds walkContext/walkLandscape/
@@ -336,7 +323,6 @@ internal sealed class RetailPViewRenderer
RenderFrameDiagnosticCounts counts = WalkDiagnosticCounts(retainedCounts);
RenderProjectionCounts sourceCounts = retainedCounts;
RetailPViewFrameResult result = _frameResultScratch.Reset(
- pvFrame,
clipAssembly,
drawableCells,
prepareCells,
@@ -695,7 +681,6 @@ public sealed class RetailPViewFrameInput
///
public sealed class RetailPViewFrameResult
{
- public PortalVisibilityFrame PortalFrame { get; private set; } = null!;
public ClipFrameAssembly ClipAssembly { get; private set; } = null!;
public HashSet DrawableCells { get; private set; } = null!;
@@ -712,7 +697,6 @@ public sealed class RetailPViewFrameResult
{ get; private set; }
internal RetailPViewFrameResult Reset(
- PortalVisibilityFrame portalFrame,
ClipFrameAssembly clipAssembly,
HashSet drawableCells,
HashSet visibleCells,
@@ -720,7 +704,6 @@ public sealed class RetailPViewFrameResult
RenderProjectionCounts sourceCounts,
InteriorEntityPartition.Result? diagnosticPartition)
{
- PortalFrame = portalFrame;
ClipAssembly = clipAssembly;
DrawableCells = drawableCells;
VisibleCells = visibleCells;
diff --git a/src/AcDream.App/Rendering/WorldRenderDiagnostics.cs b/src/AcDream.App/Rendering/WorldRenderDiagnostics.cs
index dede5851..9cda7cb2 100644
--- a/src/AcDream.App/Rendering/WorldRenderDiagnostics.cs
+++ b/src/AcDream.App/Rendering/WorldRenderDiagnostics.cs
@@ -275,7 +275,8 @@ internal sealed class WorldRenderDiagnostics
public void EmitPViewInput(
bool enabled,
- PortalVisibilityFrame portalFrame,
+ IReadOnlySet visibleCells,
+ int outsideViewCount,
Matrix4x4 viewProjection,
bool outdoorRoot,
Vector3 eye,
@@ -294,7 +295,7 @@ internal sealed class WorldRenderDiagnostics
char root = outdoorRoot ? 'Y' : 'n';
Matrix4x4 vp = viewProjection;
_log.WriteLine(FormattableString.Invariant(
- $"[pv-input] outRoot={root} flood={portalFrame.OrderedVisibleCells.Count} eye=({eye.X:F6},{eye.Y:F6},{eye.Z:F6}) player=({player.X:F6},{player.Y:F6},{player.Z:F6}) rawPlayer=({rawPlayer.X:F6},{rawPlayer.Y:F6},{rawPlayer.Z:F6}) yaw={yaw:F8} {terrain} vp=[{vp.M11:F6} {vp.M13:F6} {vp.M22:F6} {vp.M31:F6} {vp.M33:F6} {vp.M41:F6} {vp.M42:F6} {vp.M43:F6}]"));
+ $"[pv-input] outRoot={root} visible={visibleCells.Count} outsideViews={outsideViewCount} eye=({eye.X:F6},{eye.Y:F6},{eye.Z:F6}) player=({player.X:F6},{player.Y:F6},{player.Z:F6}) rawPlayer=({rawPlayer.X:F6},{rawPlayer.Y:F6},{rawPlayer.Z:F6}) yaw={yaw:F8} {terrain} vp=[{vp.M11:F6} {vp.M13:F6} {vp.M22:F6} {vp.M31:F6} {vp.M33:F6} {vp.M41:F6} {vp.M42:F6} {vp.M43:F6}]"));
}
public void EmitRetailPViewDiagnostics(
@@ -312,8 +313,8 @@ internal sealed class WorldRenderDiagnostics
{
AcDream.Core.Rendering.RenderingDiagnostics.EmitVis(
clipRoot.CellId,
- result.PortalFrame.OrderedVisibleCells,
- result.PortalFrame.OutsideView.Polygons.Count,
+ result.VisibleCells.OrderBy(static id => id).ToArray(),
+ result.ClipAssembly.OutsideViewSlices.Length,
result.ClipAssembly.OutsidePlaneCount,
result.ClipAssembly.PerCellPlaneCounts,
result.ClipAssembly.ScissorFallbacks);
@@ -353,18 +354,12 @@ internal sealed class WorldRenderDiagnostics
bool skyDrawn,
bool depthClear,
bool outdoorSceneryDrawn,
- bool outdoorPortalDrawn,
- int outdoorRootObjectCount,
int liveDynamicDrawnCount,
string sceneParticles,
- PortalVisibilityFrame? portalFrame,
+ IReadOnlySet? visibleCells,
ClipFrameAssembly? clipAssembly,
IReadOnlySet? drawableCells,
InteriorEntityPartition.Result? partition,
- PortalVisibilityFrame? exteriorPortalFrame,
- ClipFrameAssembly? exteriorClipAssembly,
- IReadOnlySet? exteriorDrawableCells,
- InteriorEntityPartition.Result? exteriorPartition,
Vector3 cameraPosition,
Vector3 playerPosition)
{
@@ -400,7 +395,7 @@ internal sealed class WorldRenderDiagnostics
if (clipAssembly is not null)
{
text.Append(" outSlices=").Append(clipAssembly.OutsideViewSlices.Length);
- text.Append(" outPolys=").Append(portalFrame?.OutsideView.Polygons.Count ?? 0);
+ text.Append(" outPolys=").Append(clipAssembly.OutsideViewSlices.Length);
text.Append(" outMode=").Append(clipAssembly.TerrainMode);
}
else
@@ -408,12 +403,11 @@ internal sealed class WorldRenderDiagnostics
text.Append(" outSlices=0 outPolys=0 outMode=none");
}
- text.Append(" ids=").Append(FormatIds(portalFrame?.OrderedVisibleCells, true));
+ text.Append(" ids=").Append(FormatIds(visibleCells, false));
text.Append(" draw=").Append(FormatIds(drawableCells, false));
- text.Append(" miss=").Append(FormatMissingDrawableCells(portalFrame, drawableCells));
+ text.Append(" miss=").Append(FormatMissingDrawableCells(visibleCells, drawableCells));
text.Append(" obj=").Append(FormatPartitionCounts(partition));
text.Append(" outdoorDoor=").Append(outdoorSceneryDrawn ? 'Y' : 'n');
- text.Append(" outdoorRootObjs=").Append(outdoorRootObjectCount);
text.Append(" liveDynDraw=").Append(liveDynamicDrawnCount);
text.Append(" outRoot=").Append(clipRoot is { IsOutdoorNode: true } ? 'Y' : 'n');
if (partition is not null)
@@ -431,21 +425,6 @@ internal sealed class WorldRenderDiagnostics
text.Append(" bshell=").Append(totalShells).Append('/').Append(shellsWithMeshes);
}
- if (outdoorPortalDrawn || exteriorPortalFrame is not null
- || exteriorClipAssembly is not null)
- {
- text.Append(" extPortal=").Append(outdoorPortalDrawn ? 'Y' : 'n');
- text.Append(" extSlices=").Append(exteriorClipAssembly?.OutsideViewSlices.Length ?? 0);
- text.Append(" extIds=").Append(FormatIds(
- exteriorPortalFrame?.OrderedVisibleCells,
- true));
- text.Append(" extDraw=").Append(FormatIds(exteriorDrawableCells, false));
- text.Append(" extMiss=").Append(FormatMissingDrawableCells(
- exteriorPortalFrame,
- exteriorDrawableCells));
- text.Append(" extObj=").Append(FormatPartitionCounts(exteriorPartition));
- }
-
string signature = text.ToString();
if (signature == _lastRenderSignature)
{
@@ -506,32 +485,22 @@ internal sealed class WorldRenderDiagnostics
}
private static string FormatMissingDrawableCells(
- PortalVisibilityFrame? portalFrame,
+ IReadOnlySet? visibleCells,
IReadOnlySet? drawableCells)
{
- if (portalFrame is null || drawableCells is null)
+ if (visibleCells is null || drawableCells is null)
return "[]";
var text = new StringBuilder(96).Append('[');
int written = 0;
const int MaximumCells = 8;
- foreach (uint id in portalFrame.OrderedVisibleCells)
+ foreach (uint id in visibleCells.OrderBy(static id => id))
{
if (drawableCells.Contains(id))
continue;
if (written > 0)
text.Append(',');
text.Append("0x").Append(id.ToString("X8"));
- if (portalFrame.CellViews.TryGetValue(id, out var view))
- {
- text.Append(":p").Append(view.Polygons.Count);
- if (view.IsEmpty)
- text.Append(":empty");
- }
- else
- {
- text.Append(":noView");
- }
if (++written >= MaximumCells)
{
text.Append(",...");
diff --git a/src/AcDream.App/Rendering/WorldSceneDiagnosticsController.cs b/src/AcDream.App/Rendering/WorldSceneDiagnosticsController.cs
index e36f8a0c..41fb229c 100644
--- a/src/AcDream.App/Rendering/WorldSceneDiagnosticsController.cs
+++ b/src/AcDream.App/Rendering/WorldSceneDiagnosticsController.cs
@@ -14,7 +14,8 @@ internal interface IWorldSceneDiagnostics
CameraCellResolution CameraCellResolution { get; }
void EmitPViewInput(
- PortalVisibilityFrame portalFrame,
+ IReadOnlySet visibleCells,
+ int outsideViewCount,
Matrix4x4 viewProjection,
LoadedCell clipRoot,
Vector3 cameraPosition,
@@ -90,7 +91,8 @@ internal sealed class WorldSceneDiagnosticsController : IWorldSceneDiagnostics
public CameraCellResolution CameraCellResolution => _pview.CameraCellResolution;
public void EmitPViewInput(
- PortalVisibilityFrame portalFrame,
+ IReadOnlySet visibleCells,
+ int outsideViewCount,
Matrix4x4 viewProjection,
LoadedCell clipRoot,
Vector3 cameraPosition,
@@ -101,7 +103,8 @@ internal sealed class WorldSceneDiagnosticsController : IWorldSceneDiagnostics
_diagnostics.EmitPViewInput(
enabled: true,
- portalFrame,
+ visibleCells,
+ outsideViewCount,
viewProjection,
clipRoot.IsOutdoorNode,
cameraPosition,
@@ -150,18 +153,12 @@ internal sealed class WorldSceneDiagnosticsController : IWorldSceneDiagnostics
skyDrawn,
depthClear,
outdoorSceneryDrawn,
- outdoorPortalDrawn: false,
- outdoorRootObjectCount: 0,
liveDynamicDrawnCount,
sceneParticles,
- pviewResult?.PortalFrame,
+ pviewResult?.VisibleCells,
pviewResult?.ClipAssembly,
pviewResult?.DrawableCells,
pviewResult?.DiagnosticPartition,
- exteriorPortalFrame: null,
- exteriorClipAssembly: null,
- exteriorDrawableCells: null,
- exteriorPartition: null,
cameraPosition,
playerPosition);
}
diff --git a/src/AcDream.App/Rendering/WorldSceneRenderer.cs b/src/AcDream.App/Rendering/WorldSceneRenderer.cs
index ac81298f..30a002d0 100644
--- a/src/AcDream.App/Rendering/WorldSceneRenderer.cs
+++ b/src/AcDream.App/Rendering/WorldSceneRenderer.cs
@@ -262,7 +262,8 @@ internal sealed class WorldSceneRenderer : IPreparedWorldSceneFramePhase
_particleVisibility.MarkVisibleCells(pviewResult.VisibleCells);
_frames.ObserveDrawableCells(pviewResult.VisibleCells);
_diagnostics.EmitPViewInput(
- pviewResult.PortalFrame,
+ pviewResult.VisibleCells,
+ pviewResult.ClipAssembly.OutsideViewSlices.Length,
camera.ViewProjection,
clipRoot,
camera.Position,
diff --git a/tests/AcDream.App.Tests/Rendering/ClipFrameAssemblerTests.cs b/tests/AcDream.App.Tests/Rendering/ClipFrameAssemblerTests.cs
index d8c624ee..900fa1ac 100644
--- a/tests/AcDream.App.Tests/Rendering/ClipFrameAssemblerTests.cs
+++ b/tests/AcDream.App.Tests/Rendering/ClipFrameAssemblerTests.cs
@@ -23,6 +23,48 @@ public class ClipFrameAssemblerTests
return view;
}
+ [Fact]
+ public void BeginWalkFrame_OutdoorRoot_SeedsOneFullScreenDefaultView()
+ {
+ using ClipFrame frame = ClipFrame.NoClip();
+
+ ClipFrameAssembly assembly = ClipFrameAssembler.BeginWalkFrame(
+ frame,
+ outdoorRoot: true);
+
+ ClipViewSlice slice = Assert.Single(assembly.OutsideViewSlices);
+ Assert.Equal(0, slice.Slot);
+ Assert.Empty(slice.Planes);
+ Assert.Equal(new Vector4(-1f, -1f, 1f, 1f), slice.NdcAabb);
+ Assert.True(assembly.OutdoorVisible);
+ Assert.True(assembly.HasOutsideView);
+ Assert.Equal(TerrainClipMode.Scissor, assembly.TerrainMode);
+ Assert.Equal(1, frame.SlotCount);
+ }
+
+ [Fact]
+ public void BeginWalkFrame_InteriorRoot_ResetsReusedOutdoorAssemblyToEmpty()
+ {
+ using ClipFrame frame = ClipFrame.NoClip();
+ ClipFrameAssembly reuse = ClipFrameAssembler.BeginWalkFrame(
+ frame,
+ outdoorRoot: true);
+
+ ClipFrameAssembly assembly = ClipFrameAssembler.BeginWalkFrame(
+ frame,
+ outdoorRoot: false,
+ reuse);
+
+ Assert.Same(reuse, assembly);
+ Assert.Empty(assembly.OutsideViewSlices);
+ Assert.False(assembly.OutdoorVisible);
+ Assert.False(assembly.HasOutsideView);
+ Assert.Equal(TerrainClipMode.Skip, assembly.TerrainMode);
+ Assert.Equal(Vector4.Zero, assembly.OutsideViewNdcAabb);
+ Assert.Equal(0, assembly.ScissorFallbacks);
+ Assert.Equal(1, frame.SlotCount);
+ }
+
[Fact]
public void TwoVisibleCells_PlusOutsideView_ProducesCorrectSlotMapAndCounts()
{
@@ -128,39 +170,6 @@ public class ClipFrameAssemblerTests
Assert.Equal(TerrainClipMode.Planes, asm.TerrainMode);
}
- [Fact]
- public void AppendLookInFrames_PacksEveryNestedCellViewWithoutReplacingMainRoutes()
- {
- const uint mainCell = 0xA9B40100;
- const uint lookInCell = 0xA9B40106;
- var main = new PortalVisibilityFrame();
- main.CellViews[mainCell] = ViewOf(Square(0f, 0f, 0.8f));
- main.OrderedVisibleCells.Add(mainCell);
- main.OutsideView.Add(Square(0f, 0f, 0.7f));
-
- var nested = new PortalVisibilityFrame();
- nested.CellViews[lookInCell] = ViewOf(
- Square(-0.35f, 0f, 0.15f),
- Square(0.35f, 0f, 0.15f));
- nested.OrderedVisibleCells.Add(lookInCell);
-
- using ClipFrame frame = ClipFrame.NoClip();
- ClipFrameAssembly assembly = ClipFrameAssembler.Assemble(frame, main);
- int mainSlot = assembly.CellIdToSlot[mainCell];
- int slotsBeforeLookIn = frame.SlotCount;
-
- ClipFrameAssembler.AppendLookInFrames(frame, [nested], assembly);
-
- var key = new LookInClipCell(0, lookInCell);
- ClipViewSlice[] slices = assembly.LookInCellToViewSlices[key];
- Assert.Equal(2, slices.Length);
- Assert.All(slices, slice => Assert.True(slice.Slot >= slotsBeforeLookIn));
- Assert.NotEqual(slices[0].Slot, slices[1].Slot);
- Assert.Equal(slices[0].Slot, assembly.LookInCellToSlot[key]);
- Assert.Equal(mainSlot, assembly.CellIdToSlot[mainCell]);
- Assert.DoesNotContain(lookInCell, assembly.CellIdToSlot.Keys);
- }
-
[Fact]
public void Assemble_OutsideViewWithExitPortal_HasOutsideViewTrue_AabbMatchesBounds()
{
diff --git a/tests/AcDream.App.Tests/Rendering/SanctuaryPortalSeamTests.cs b/tests/AcDream.App.Tests/Rendering/SanctuaryPortalSeamTests.cs
deleted file mode 100644
index 4152cd92..00000000
--- a/tests/AcDream.App.Tests/Rendering/SanctuaryPortalSeamTests.cs
+++ /dev/null
@@ -1,226 +0,0 @@
-using System.Collections.Generic;
-using System.Numerics;
-using AcDream.App.Rendering;
-using DatReaderWriter;
-using DatReaderWriter.DBObjs;
-using DatReaderWriter.Options;
-using Xunit;
-
-namespace AcDream.App.Tests.Rendering;
-
-///
-/// Sanctuary cathedral seam captured 2026-08-27. The stationary player is in
-/// F4180104 while chase-camera zoom crosses the coincident outside portals at
-/// world Y=48 and the viewer root switches between two separate buildings:
-/// B3={0103,0104,0105} and B4={0106..0111}. The opposite cathedral half must
-/// remain available through the interior-root building look-in on both sides.
-///
-[Trait("Lane", "InstalledDat")]
-public sealed class SanctuaryPortalSeamTests
-{
- private const uint Landblock = 0xF4180000u;
- private const uint Cell0104 = Landblock | 0x0104u;
- private const uint Cell0106 = Landblock | 0x0106u;
-
- private static Matrix4x4 ViewProjection(Vector3 eye)
- {
- // Derived from the two exact flap-sweep points. Extending their zoom
- // ray reaches the stable chase target at the player's head.
- var target = new Vector3(36.033f, 49.638f, 171.353f);
- var view = Matrix4x4.CreateLookAt(eye, target, Vector3.UnitZ);
- var projection = Matrix4x4.CreatePerspectiveFieldOfView(
- 1.2f, 893f / 522f, 1f, 5000f);
- return view * projection;
- }
-
- private static IReadOnlyList Cells(
- Dictionary cells,
- uint firstLow,
- uint lastLow)
- {
- var result = new List();
- for (uint low = firstLow; low <= lastLow; low++)
- result.Add(cells[Landblock | low]);
- return result;
- }
-
- [Fact]
- public void CapturedZoomHandoff_BothRootsBuildTheExactOppositeCathedralSeed()
- {
- string? datDir = CornerFloodReplayTests.ResolveDatDir();
- if (datDir is null)
- {
- Assert.Fail("Lane=InstalledDat requires an installed retail DAT directory; see docs/release-gate.md.");
- }
-
- using var dats = new DatCollection(datDir, DatAccessType.Read);
- LandBlockInfo info = Assert.IsType(dats.Get(Landblock | 0xfffeu));
- Dictionary cells =
- Issue120ReciprocalPingPongTests.LoadAllInteriorCells(dats, Landblock);
- LoadedCell? Lookup(uint id) => cells.TryGetValue(id, out LoadedCell? cell) ? cell : null;
-
- IReadOnlyList building3 = Cells(cells, 0x0103u, 0x0105u);
- IReadOnlyList building4 = Cells(cells, 0x0106u, 0x0111u);
- Assert.Equal(0x01001FB2u, info.Buildings[3].ModelId);
- Assert.Equal(0x01001FB3u, info.Buildings[4].ModelId);
-
- var captures = new[]
- {
- new
- {
- Name = "near/root0104",
- Eye = new Vector3(32.742317f, 48.034306f, 172.447845f),
- Root = cells[Cell0104],
- Opposite = building4,
- ExpectedOppositeCell = Cell0106,
- ExpectedSeedPortal = 2,
- OppositeBuildingIndex = 4,
- },
- new
- {
- Name = "far/root0106",
- Eye = new Vector3(32.308865f, 47.823200f, 172.592255f),
- Root = cells[Cell0106],
- Opposite = building3,
- ExpectedOppositeCell = Cell0104,
- ExpectedSeedPortal = 0,
- OppositeBuildingIndex = 3,
- },
- };
-
- foreach (var capture in captures)
- {
- Matrix4x4 viewProjection = ViewProjection(capture.Eye);
- PortalVisibilityFrame main = PortalVisibilityBuilder.Build(
- capture.Root, capture.Eye, Lookup, viewProjection);
- PortalVisibilityFrame lookIn = PortalVisibilityBuilder.ConstructViewBuilding(
- capture.Opposite,
- capture.Eye,
- Lookup,
- viewProjection,
- maxSeedDistance: float.PositiveInfinity,
- seedRegion: main.OutsideView.Polygons);
-
- Assert.Contains(capture.ExpectedOppositeCell, lookIn.OrderedVisibleCells);
- Assert.True(
- lookIn.CellViews.TryGetValue(capture.ExpectedOppositeCell, out CellView? view)
- && view.Polygons.Count > 0,
- $"{capture.Name} must retain a clipped aperture for the opposite cathedral half");
- ExteriorPortalSeed seed = Assert.Single(lookIn.ExteriorSeedPortals);
- Assert.Equal(capture.ExpectedOppositeCell, seed.CellId);
- Assert.Equal(capture.ExpectedSeedPortal, seed.PortalIndex);
- Assert.NotEmpty(seed.View.Polygons);
-
- using ClipFrame clipFrame = ClipFrame.NoClip();
- ClipFrameAssembly assembly = ClipFrameAssembler.Assemble(
- clipFrame,
- main);
- ClipFrameAssembler.AppendLookInFrames(
- clipFrame,
- [lookIn],
- assembly);
- ClipViewSlice[] nestedSlices =
- assembly.LookInCellToViewSlices[
- new LookInClipCell(0, capture.ExpectedOppositeCell)];
- Assert.Equal(view.Polygons.Count, nestedSlices.Length);
- Assert.All(nestedSlices, slice => Assert.True(slice.Slot > 0));
-
- Assert.Contains(
- info.Buildings[capture.OppositeBuildingIndex].Portals,
- portal => portal.OtherCellId == (capture.ExpectedOppositeCell & 0xffffu)
- && portal.OtherPortalId == capture.ExpectedSeedPortal);
- }
- }
-
- [Fact]
- public void ReportedLateralTransition_BothRootsKeepTheOppositeFacadePortal()
- {
- string? datDir = CornerFloodReplayTests.ResolveDatDir();
- if (datDir is null)
- Assert.Fail("Lane=InstalledDat requires an installed retail DAT directory.");
-
- using var dats = new DatCollection(datDir, DatAccessType.Read);
- Dictionary cells =
- Issue120ReciprocalPingPongTests.LoadAllInteriorCells(dats, Landblock);
- LoadedCell? Lookup(uint id) => cells.TryGetValue(id, out LoadedCell? cell) ? cell : null;
-
- static (PortalVisibilityFrame Main, PortalVisibilityFrame LookIn) Build(
- Vector3 eye,
- Vector3 target,
- LoadedCell root,
- IReadOnlyList opposite,
- Func lookup)
- {
- Matrix4x4 viewProjection = Matrix4x4.CreateLookAt(eye, target, Vector3.UnitZ)
- * Matrix4x4.CreatePerspectiveFieldOfView(MathF.PI / 3f, 1.6f, 0.1f, 5000f);
- PortalVisibilityFrame main = PortalVisibilityBuilder.Build(
- root, eye, lookup, viewProjection);
- PortalVisibilityFrame lookIn = PortalVisibilityBuilder.ConstructViewBuilding(
- opposite, eye, lookup, viewProjection, seedRegion: main.OutsideView.Polygons);
- return (main, lookIn);
- }
-
- (PortalVisibilityFrame Main, PortalVisibilityFrame LookIn) from0106 = Build(
- new Vector3(31.189594f, 46.280170f, 171.783646f),
- new Vector3(32.787731f, 46.275948f, 171.354993f),
- cells[Cell0106],
- Cells(cells, 0x0103u, 0x0105u),
- Lookup);
- (PortalVisibilityFrame Main, PortalVisibilityFrame LookIn) from0104 = Build(
- new Vector3(31.198704f, 49.733287f, 171.783646f),
- new Vector3(32.796841f, 49.729065f, 171.354993f),
- cells[Cell0104],
- Cells(cells, 0x0106u, 0x0111u),
- Lookup);
-
- Assert.Equal([Cell0106, Landblock | 0x010Fu], from0106.Main.OrderedVisibleCells);
- Assert.Equal([Cell0104], from0106.LookIn.OrderedVisibleCells);
- ExteriorPortalSeed seed0104 = Assert.Single(from0106.LookIn.ExteriorSeedPortals);
- Assert.Equal(Cell0104, seed0104.CellId);
- Assert.Equal(0, seed0104.PortalIndex);
- Assert.NotEmpty(seed0104.View.Polygons);
-
- Assert.Equal([Cell0104], from0104.Main.OrderedVisibleCells);
- Assert.Equal([Cell0106, Landblock | 0x010Fu], from0104.LookIn.OrderedVisibleCells);
- ExteriorPortalSeed seed0106 = Assert.Single(from0104.LookIn.ExteriorSeedPortals);
- Assert.Equal(Cell0106, seed0106.CellId);
- Assert.Equal(2, seed0106.PortalIndex);
- Assert.NotEmpty(seed0106.View.Polygons);
- }
-
- [Fact]
- public void ReportedExactSteamSeam_Root0104KeepsTheOppositeCathedralPortal()
- {
- string? datDir = CornerFloodReplayTests.ResolveDatDir();
- if (datDir is null)
- Assert.Fail("Lane=InstalledDat requires an installed retail DAT directory.");
-
- using var dats = new DatCollection(datDir, DatAccessType.Read);
- Dictionary cells =
- Issue120ReciprocalPingPongTests.LoadAllInteriorCells(dats, Landblock);
- LoadedCell? Lookup(uint id) => cells.TryGetValue(id, out LoadedCell? cell) ? cell : null;
-
- // Live ACDREAM_PROBE_CELL capture for the user-reported stationary
- // frame at player [32.792290, 48.000618, 169.804993]. The chase eye is
- // only ~4 mm across the coincident 0104/0106 exterior portal plane.
- var eye = new Vector3(31.189665f, 48.004852f, 171.784988f);
- var target = new Vector3(32.792290f, 48.000618f, 171.354993f);
- Matrix4x4 viewProjection = Matrix4x4.CreateLookAt(eye, target, Vector3.UnitZ)
- * Matrix4x4.CreatePerspectiveFieldOfView(1.2f, 1555f / 1019f, 1f, 5000f);
-
- PortalVisibilityFrame main = PortalVisibilityBuilder.Build(
- cells[Cell0104], eye, Lookup, viewProjection);
- PortalVisibilityFrame lookIn = PortalVisibilityBuilder.ConstructViewBuilding(
- Cells(cells, 0x0106u, 0x0111u),
- eye,
- Lookup,
- viewProjection,
- seedRegion: main.OutsideView.Polygons);
-
- Assert.Contains(Cell0106, lookIn.OrderedVisibleCells);
- ExteriorPortalSeed seed = Assert.Single(lookIn.ExteriorSeedPortals);
- Assert.Equal(Cell0106, seed.CellId);
- Assert.Equal(2, seed.PortalIndex);
- Assert.NotEmpty(seed.View.Polygons);
- }
-}
diff --git a/tests/AcDream.App.Tests/Rendering/WorldRenderDiagnosticsTests.cs b/tests/AcDream.App.Tests/Rendering/WorldRenderDiagnosticsTests.cs
index 298d6c47..e49bf0ab 100644
--- a/tests/AcDream.App.Tests/Rendering/WorldRenderDiagnosticsTests.cs
+++ b/tests/AcDream.App.Tests/Rendering/WorldRenderDiagnosticsTests.cs
@@ -228,18 +228,12 @@ public sealed class WorldRenderDiagnosticsTests
skyDrawn: false,
depthClear: false,
outdoorSceneryDrawn: false,
- outdoorPortalDrawn: false,
- outdoorRootObjectCount: 0,
liveDynamicDrawnCount: 0,
sceneParticles: "none",
- portalFrame: null,
+ visibleCells: null,
clipAssembly: null,
drawableCells: null,
partition: null,
- exteriorPortalFrame: null,
- exteriorClipAssembly: null,
- exteriorDrawableCells: null,
- exteriorPartition: null,
cameraPosition: Vector3.Zero,
playerPosition: Vector3.Zero);
}
diff --git a/tests/AcDream.App.Tests/Rendering/WorldSceneRendererTests.cs b/tests/AcDream.App.Tests/Rendering/WorldSceneRendererTests.cs
index e4825f7a..89ab7d2f 100644
--- a/tests/AcDream.App.Tests/Rendering/WorldSceneRendererTests.cs
+++ b/tests/AcDream.App.Tests/Rendering/WorldSceneRendererTests.cs
@@ -739,24 +739,16 @@ public sealed class WorldSceneRendererTests
// Distinct flood-only vs in-view sets: 0x01010003 is a look-in
// cell that is drawn but never part of the main flood.
_interiorResult = new RetailPViewFrameResult().Reset(
- new PortalVisibilityFrame(),
- new ClipFrameAssembly(),
+ ClipFrameAssembler.BeginWalkFrame(
+ ClipFrame.NoClip(), outdoorRoot: false),
[0x01010001u],
[0x01010001u, 0x01010003u],
default,
default,
diagnosticPartition: null);
- var outdoorPortalFrame = new PortalVisibilityFrame();
- outdoorPortalFrame.OutsideView.Add(new ViewPolygon(
- [
- new Vector2(-1f, -1f),
- new Vector2(1f, -1f),
- new Vector2(1f, 1f),
- new Vector2(-1f, 1f),
- ]));
_outdoorResult = new RetailPViewFrameResult().Reset(
- outdoorPortalFrame,
- ClipFrameAssembler.Assemble(ClipFrame.NoClip(), outdoorPortalFrame),
+ ClipFrameAssembler.BeginWalkFrame(
+ ClipFrame.NoClip(), outdoorRoot: true),
[],
[],
default,
@@ -886,7 +878,8 @@ public sealed class WorldSceneRendererTests
public CameraCellResolution CameraCellResolution => CameraCellResolution.None;
public void EmitPViewInput(
- PortalVisibilityFrame portalFrame,
+ IReadOnlySet visibleCells,
+ int outsideViewCount,
Matrix4x4 viewProjection,
LoadedCell clipRoot,
Vector3 cameraPosition,