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,