refactor(render): remove the production portal frame carrier

This commit is contained in:
Erik 2026-08-31 05:34:46 +02:00
parent 69e69408e8
commit cb22691beb
10 changed files with 128 additions and 443 deletions

View file

@ -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);
/// <summary>
/// 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.
/// </summary>
public readonly record struct LookInClipCell(int FrameIndex, uint CellId);
/// <summary>
/// Result of <see cref="ClipFrameAssembler.Assemble"/>: populated clip buffers
/// plus routing data consumed by the render orchestration.
/// Populated clip buffers plus routing data consumed by the render walk.
/// </summary>
public sealed class ClipFrameAssembly
{
@ -64,12 +56,6 @@ public sealed class ClipFrameAssembly
/// <summary>Full retail portal_view slices per visible cell.</summary>
public Dictionary<uint, ClipViewSlice[]> CellIdToViewSlices { get; } = new();
/// <summary>First drawable slice slot per nested look-in cell.</summary>
public Dictionary<LookInClipCell, int> LookInCellToSlot { get; } = new();
/// <summary>All retail portal_view slices per nested look-in cell.</summary>
public Dictionary<LookInClipCell, ClipViewSlice[]> LookInCellToViewSlices { get; } = new();
/// <summary>Full retail outside_view slices.</summary>
public ClipViewSlice[] OutsideViewSlices { get; private set; } = System.Array.Empty<ClipViewSlice>();
@ -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<ClipViewSlice>();
SliceScratch.Clear();
@ -272,6 +254,53 @@ public sealed class ClipFrameAssembly
public static class ClipFrameAssembler
{
/// <summary>
/// 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
/// <see cref="Walk.RetailFrameWalk.InteriorOutsideView"/> after Collect.
/// </summary>
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<ClipViewSlice> slices = assembly.SliceScratch;
slices.Clear();
var fullScreen = new Vector4(-1f, -1f, 1f, 1f);
slices.Add(new ClipViewSlice(0, fullScreen, System.Array.Empty<Vector4>()));
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;
}
/// <summary>
/// Appends the cell views used by nested <c>DrawBuilding -&gt; DrawPortal</c>
/// PViews to the already assembled frame. Retail installs each nested
/// cell's own <c>portal_view</c> before drawing its shell and object list;
/// these slots must therefore be published with the main frame before the
/// first draw is recorded.
/// </summary>
public static void AppendLookInFrames(
ClipFrame frame,
IReadOnlyList<PortalVisibilityFrame> 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<ClipViewSlice> 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<Vector4>();
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);

View file

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

View file

@ -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<uint> 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
/// </summary>
public sealed class RetailPViewFrameResult
{
public PortalVisibilityFrame PortalFrame { get; private set; } = null!;
public ClipFrameAssembly ClipAssembly { get; private set; } = null!;
public HashSet<uint> DrawableCells { get; private set; } = null!;
@ -712,7 +697,6 @@ public sealed class RetailPViewFrameResult
{ get; private set; }
internal RetailPViewFrameResult Reset(
PortalVisibilityFrame portalFrame,
ClipFrameAssembly clipAssembly,
HashSet<uint> drawableCells,
HashSet<uint> visibleCells,
@ -720,7 +704,6 @@ public sealed class RetailPViewFrameResult
RenderProjectionCounts sourceCounts,
InteriorEntityPartition.Result? diagnosticPartition)
{
PortalFrame = portalFrame;
ClipAssembly = clipAssembly;
DrawableCells = drawableCells;
VisibleCells = visibleCells;

View file

@ -275,7 +275,8 @@ internal sealed class WorldRenderDiagnostics
public void EmitPViewInput(
bool enabled,
PortalVisibilityFrame portalFrame,
IReadOnlySet<uint> 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<uint>? visibleCells,
ClipFrameAssembly? clipAssembly,
IReadOnlySet<uint>? drawableCells,
InteriorEntityPartition.Result? partition,
PortalVisibilityFrame? exteriorPortalFrame,
ClipFrameAssembly? exteriorClipAssembly,
IReadOnlySet<uint>? 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<uint>? visibleCells,
IReadOnlySet<uint>? 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(",...");

View file

@ -14,7 +14,8 @@ internal interface IWorldSceneDiagnostics
CameraCellResolution CameraCellResolution { get; }
void EmitPViewInput(
PortalVisibilityFrame portalFrame,
IReadOnlySet<uint> 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<uint> 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);
}

View file

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

View file

@ -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()
{

View file

@ -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;
/// <summary>
/// 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.
/// </summary>
[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<LoadedCell> Cells(
Dictionary<uint, LoadedCell> cells,
uint firstLow,
uint lastLow)
{
var result = new List<LoadedCell>();
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<LandBlockInfo>(dats.Get<LandBlockInfo>(Landblock | 0xfffeu));
Dictionary<uint, LoadedCell> cells =
Issue120ReciprocalPingPongTests.LoadAllInteriorCells(dats, Landblock);
LoadedCell? Lookup(uint id) => cells.TryGetValue(id, out LoadedCell? cell) ? cell : null;
IReadOnlyList<LoadedCell> building3 = Cells(cells, 0x0103u, 0x0105u);
IReadOnlyList<LoadedCell> 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<uint, LoadedCell> 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<LoadedCell> opposite,
Func<uint, LoadedCell?> 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<uint, LoadedCell> 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);
}
}

View file

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

View file

@ -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<uint> visibleCells,
int outsideViewCount,
Matrix4x4 viewProjection,
LoadedCell clipRoot,
Vector3 cameraPosition,