feat(render): Phase U.2a — portal BFS ordering + fixpoint termination
PortalVisibilityFrame gains OrderedVisibleCells (closest-first). Replace the FIFO + MaxReprocessPerCell cap with a distance-priority queue and a grow-watermark fixpoint (retail InsCellTodoList 433183 / AddViewToPortals 433446) so cyclic dungeon graphs converge without duplicate-cell blow-up. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
3fc77be5de
commit
d8807755ce
2 changed files with 165 additions and 23 deletions
|
|
@ -1,4 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using AcDream.App.Rendering;
|
||||
using Xunit;
|
||||
|
|
@ -121,6 +122,71 @@ public class PortalVisibilityBuilderTests
|
|||
Assert.True(frame.OutsideView.Polygons.Count < 256,
|
||||
$"OutsideView poly count {frame.OutsideView.Polygons.Count} — termination/dedup regression guard");
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Phase U.2a: ordered visible-cell list (closest-first) + grow-watermark
|
||||
// fixpoint termination (replaces MaxReprocessPerCell hard cap).
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
// Straight chain A -> B -> C, camera in A looking down -Z. Each onward portal
|
||||
// is progressively farther in -Z so the camera-to-portal distance is monotonic,
|
||||
// forcing the priority queue to dequeue A, then B, then C in that order.
|
||||
private static (LoadedCell[] cells, Dictionary<uint, LoadedCell> lookup) SyntheticChain()
|
||||
{
|
||||
const uint A = 0x0001, B = 0x0002, C = 0x0003;
|
||||
var a = Cell(A, new CellPortalInfo((ushort)B, 0, 0));
|
||||
a.PortalPolygons.Add(Quad(0f, 0f, 0.6f, 0.6f, -2f)); // portal A->B at z=-2 (nearer)
|
||||
var b = Cell(B, new CellPortalInfo((ushort)C, 0, 0));
|
||||
b.PortalPolygons.Add(Quad(0f, 0f, 0.6f, 0.6f, -5f)); // portal B->C at z=-5 (farther)
|
||||
var c = Cell(C, new CellPortalInfo(0xFFFF, 0, 0));
|
||||
c.PortalPolygons.Add(Quad(0f, 0f, 0.6f, 0.6f, -8f)); // exit window
|
||||
var all = new Dictionary<uint, LoadedCell> { [A] = a, [B] = b, [C] = c };
|
||||
return (new[] { a, b, c }, all);
|
||||
}
|
||||
|
||||
[Fact] // closest-first ordering
|
||||
public void Build_OrdersVisibleCells_ClosestFirst()
|
||||
{
|
||||
var (cells, lookup) = SyntheticChain();
|
||||
var f = PortalVisibilityBuilder.Build(
|
||||
cells[0], Vector3.Zero, id => lookup.TryGetValue(id, out var c) ? c : null, ViewProj());
|
||||
Assert.Equal(new uint[] { 0x0001, 0x0002, 0x0003 }, f.OrderedVisibleCells.ToArray());
|
||||
}
|
||||
|
||||
// Hub cell with 4 rooms, each room portal-linked BACK to the hub (a cycle on
|
||||
// every spoke). A naive FIFO with no real fixpoint re-enqueues the hub once per
|
||||
// returning spoke and the rooms once per hub re-process — the watermark must
|
||||
// converge instead, bounding the visible set to {hub + 4 rooms} with no dupes.
|
||||
private static (LoadedCell hub, Dictionary<uint, LoadedCell> lookup) SyntheticCyclicHub()
|
||||
{
|
||||
const uint HUB = 0x0010;
|
||||
uint[] rooms = { 0x0011, 0x0012, 0x0013, 0x0014 };
|
||||
// Hub has one portal to each room; rooms sit at distinct depths so ordering is deterministic.
|
||||
var hub = Cell(HUB,
|
||||
new CellPortalInfo((ushort)rooms[0], 0, 0), new CellPortalInfo((ushort)rooms[1], 1, 0),
|
||||
new CellPortalInfo((ushort)rooms[2], 2, 0), new CellPortalInfo((ushort)rooms[3], 3, 0));
|
||||
for (int i = 0; i < 4; i++)
|
||||
hub.PortalPolygons.Add(Quad(0f, 0f, 0.6f, 0.6f, -2f - i)); // -2,-3,-4,-5
|
||||
var all = new Dictionary<uint, LoadedCell> { [HUB] = hub };
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
var room = Cell(rooms[i], new CellPortalInfo((ushort)HUB, 0, 0)); // links back to hub → cycle
|
||||
room.PortalPolygons.Add(Quad(0f, 0f, 0.6f, 0.6f, -2f - i));
|
||||
all[rooms[i]] = room;
|
||||
}
|
||||
return (hub, all);
|
||||
}
|
||||
|
||||
[Fact] // cyclic graph terminates and bounds the visible set
|
||||
public void Build_CyclicHub_TerminatesAndBounds()
|
||||
{
|
||||
var (hub, lookup) = SyntheticCyclicHub();
|
||||
var f = PortalVisibilityBuilder.Build(
|
||||
hub, Vector3.Zero, id => lookup.TryGetValue(id, out var c) ? c : null, ViewProj());
|
||||
Assert.True(f.OrderedVisibleCells.Count <= 5,
|
||||
$"hub + 4 rooms expected, got {f.OrderedVisibleCells.Count} — fixpoint failed to converge");
|
||||
Assert.Equal(f.OrderedVisibleCells.Count, f.OrderedVisibleCells.Distinct().Count()); // no dup cells
|
||||
}
|
||||
}
|
||||
|
||||
internal static class PortalFrameTestHelper
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue