Checkpoint of the unified retail-faithful indoor render. The two-week HANG/grey is fixed and the interior seals (live-verified by the user). Commits the session render-rewrite foundation together with the fixes that made it functional. - HANG fix: PortalVisibilityBuilder.Build portal flood did not terminate (the faithful ProjectToClip near-side clip drifts per round, defeating the CellView dedup; the BFS had no bound after U.2a removed MaxReprocessPerCell). Fix = drift-tolerant snapped/canonical CellView.Add dedup (PortalView.cs) plus restored MaxReprocessPerCell=16 bounded re-enqueue (PortalVisibilityBuilder.cs). Re-enqueue is kept (load-bearing for late-slice propagation, Build_ViewGrowthAfterDoneCell_PropagatesNewSlicesToExit); only its count is capped. CellViewDedupTests added. - Seal (DrawCells Task 2): RetailPViewRenderer.DrawEnvCellShells draws EVERY visible cell via IndoorDrawPlan.ShellPass (was gated on the ClipFrameAssembler slot filter, leaving slot-less cells grey). - Look-in FPS: GameWindow exterior look-in candidates limited to the player landblock +-1 (was all ~81 loaded LBs iterated every outdoor frame). No behaviour change (far cells were >48m, already culled). Remaining dominant issue = the FLAP at transitions: viewer-cell metastability (render roots at the camera-eye cell, which oscillates outdoor-indoor as the 3rd-person boom drifts across the doorway, confirmed in render-sig). SEPARATE fix, NOT the DrawCells port. Full handoff + flap fix plan + tracked follow-ups (#78 terrain, look-in-from-inside, look-in FPS, L-spotlight): docs/research/2026-06-07-indoor-render-session-handoff.md. Baselines: build 0 err; App.Tests 210/210; Core.Tests 1331 pass / 4 fail (pre-existing) / 1 skip. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
185 lines
6.4 KiB
C#
185 lines
6.4 KiB
C#
using System.Collections.Generic;
|
|
using AcDream.App.Rendering.Wb;
|
|
using Xunit;
|
|
|
|
namespace AcDream.App.Tests.Rendering.Wb;
|
|
|
|
public sealed class WbDrawDispatcherClipSlotTests
|
|
{
|
|
private const uint VisibleCellA = 0xA9B4_0164u;
|
|
private const uint VisibleCellB = 0xA9B4_0165u;
|
|
private const uint NotVisibleCell = 0xA9B4_0999u;
|
|
private const uint OutdoorCell = 0xA9B4_0020u;
|
|
|
|
private const int SlotA = 3;
|
|
private const int SlotB = 7;
|
|
private const int OutsideViewSlot = 11;
|
|
|
|
private static IReadOnlyDictionary<uint, int> Routing() => new Dictionary<uint, int>
|
|
{
|
|
[VisibleCellA] = SlotA,
|
|
[VisibleCellB] = SlotB,
|
|
};
|
|
|
|
[Fact]
|
|
public void RawResolve_LiveEntity_WithVisibleIndoorParent_GetsThatCellSlot()
|
|
{
|
|
int slot = WbDrawDispatcher.ResolveEntitySlot(
|
|
serverGuid: 0x5000_000Au, parentCellId: VisibleCellA,
|
|
cellIdToSlot: Routing(), outdoorSlot: OutsideViewSlot, outdoorVisible: true);
|
|
|
|
Assert.Equal(SlotA, slot);
|
|
}
|
|
|
|
[Fact]
|
|
public void RawResolve_LiveEntity_WithHiddenIndoorParent_IsCulled()
|
|
{
|
|
int slot = WbDrawDispatcher.ResolveEntitySlot(
|
|
serverGuid: 0x5000_000Au, parentCellId: NotVisibleCell,
|
|
cellIdToSlot: Routing(), outdoorSlot: OutsideViewSlot, outdoorVisible: true);
|
|
|
|
Assert.Equal(WbDrawDispatcher.ClipSlotCull, slot);
|
|
}
|
|
|
|
[Fact]
|
|
public void RawResolve_LiveEntity_WithOutdoorParent_UsesOutsideViewWhenVisible()
|
|
{
|
|
int slot = WbDrawDispatcher.ResolveEntitySlot(
|
|
serverGuid: 0x5000_000Au, parentCellId: OutdoorCell,
|
|
cellIdToSlot: Routing(), outdoorSlot: OutsideViewSlot, outdoorVisible: true);
|
|
|
|
Assert.Equal(OutsideViewSlot, slot);
|
|
}
|
|
|
|
[Fact]
|
|
public void RawResolve_LiveEntity_WithParentNull_IsCulledWhenRoutingActive()
|
|
{
|
|
int slot = WbDrawDispatcher.ResolveEntitySlot(
|
|
serverGuid: 0x5000_000Au, parentCellId: null,
|
|
cellIdToSlot: Routing(), outdoorSlot: OutsideViewSlot, outdoorVisible: true);
|
|
|
|
Assert.Equal(WbDrawDispatcher.ClipSlotCull, slot);
|
|
}
|
|
|
|
[Fact]
|
|
public void RawResolve_CellStatic_InVisibleSet_GetsThatCellSlot()
|
|
{
|
|
int slot = WbDrawDispatcher.ResolveEntitySlot(
|
|
serverGuid: 0u, parentCellId: VisibleCellB,
|
|
cellIdToSlot: Routing(), outdoorSlot: OutsideViewSlot, outdoorVisible: true);
|
|
|
|
Assert.Equal(SlotB, slot);
|
|
}
|
|
|
|
[Fact]
|
|
public void RawResolve_CellStatic_NotInVisibleSet_IsCulled()
|
|
{
|
|
int slot = WbDrawDispatcher.ResolveEntitySlot(
|
|
serverGuid: 0u, parentCellId: NotVisibleCell,
|
|
cellIdToSlot: Routing(), outdoorSlot: OutsideViewSlot, outdoorVisible: true);
|
|
|
|
Assert.Equal(WbDrawDispatcher.ClipSlotCull, slot);
|
|
}
|
|
|
|
[Fact]
|
|
public void RawResolve_OutdoorStab_OutdoorsVisible_GetsOutsideViewSlot()
|
|
{
|
|
int slot = WbDrawDispatcher.ResolveEntitySlot(
|
|
serverGuid: 0u, parentCellId: null,
|
|
cellIdToSlot: Routing(), outdoorSlot: OutsideViewSlot, outdoorVisible: true);
|
|
|
|
Assert.Equal(OutsideViewSlot, slot);
|
|
}
|
|
|
|
[Fact]
|
|
public void RawResolve_OutdoorStab_OutdoorsNotVisible_IsCulled()
|
|
{
|
|
int slot = WbDrawDispatcher.ResolveEntitySlot(
|
|
serverGuid: 0u, parentCellId: null,
|
|
cellIdToSlot: Routing(), outdoorSlot: OutsideViewSlot, outdoorVisible: false);
|
|
|
|
Assert.Equal(WbDrawDispatcher.ClipSlotCull, slot);
|
|
}
|
|
|
|
[Fact]
|
|
public void ForFrame_RoutingInactive_EveryEntityIsSlot0AndNotCulled()
|
|
{
|
|
var live = WbDrawDispatcher.ResolveSlotForFrame(
|
|
clipRoutingActive: false, serverGuid: 0x5000_000Au, parentCellId: null,
|
|
cellIdToSlot: null, outdoorSlot: OutsideViewSlot, outdoorVisible: true);
|
|
Assert.Equal(0u, live.Slot);
|
|
Assert.False(live.Culled);
|
|
|
|
var wouldCull = WbDrawDispatcher.ResolveSlotForFrame(
|
|
clipRoutingActive: false, serverGuid: 0u, parentCellId: NotVisibleCell,
|
|
cellIdToSlot: null, outdoorSlot: OutsideViewSlot, outdoorVisible: false);
|
|
Assert.Equal(0u, wouldCull.Slot);
|
|
Assert.False(wouldCull.Culled);
|
|
}
|
|
|
|
[Fact]
|
|
public void ForFrame_RoutingActive_LiveEntityVisible_GetsCellSlotNotCulled()
|
|
{
|
|
var r = WbDrawDispatcher.ResolveSlotForFrame(
|
|
clipRoutingActive: true, serverGuid: 0x5000_000Au, parentCellId: VisibleCellA,
|
|
cellIdToSlot: Routing(), outdoorSlot: OutsideViewSlot, outdoorVisible: true);
|
|
|
|
Assert.Equal((uint)SlotA, r.Slot);
|
|
Assert.False(r.Culled);
|
|
}
|
|
|
|
[Fact]
|
|
public void ForFrame_RoutingActive_LiveEntityParentNull_Culled()
|
|
{
|
|
var r = WbDrawDispatcher.ResolveSlotForFrame(
|
|
clipRoutingActive: true, serverGuid: 0x5000_000Au, parentCellId: null,
|
|
cellIdToSlot: Routing(), outdoorSlot: OutsideViewSlot, outdoorVisible: true);
|
|
|
|
Assert.True(r.Culled);
|
|
Assert.Equal(0u, r.Slot);
|
|
}
|
|
|
|
[Fact]
|
|
public void ForFrame_RoutingActive_CellStaticVisible_GetsCellSlotNotCulled()
|
|
{
|
|
var r = WbDrawDispatcher.ResolveSlotForFrame(
|
|
clipRoutingActive: true, serverGuid: 0u, parentCellId: VisibleCellA,
|
|
cellIdToSlot: Routing(), outdoorSlot: OutsideViewSlot, outdoorVisible: true);
|
|
|
|
Assert.Equal((uint)SlotA, r.Slot);
|
|
Assert.False(r.Culled);
|
|
}
|
|
|
|
[Fact]
|
|
public void ForFrame_RoutingActive_CellStaticNotVisible_Culled()
|
|
{
|
|
var r = WbDrawDispatcher.ResolveSlotForFrame(
|
|
clipRoutingActive: true, serverGuid: 0u, parentCellId: NotVisibleCell,
|
|
cellIdToSlot: Routing(), outdoorSlot: OutsideViewSlot, outdoorVisible: true);
|
|
|
|
Assert.True(r.Culled);
|
|
Assert.Equal(0u, r.Slot);
|
|
}
|
|
|
|
[Fact]
|
|
public void ForFrame_RoutingActive_OutdoorStabVisible_GetsOutsideViewSlot()
|
|
{
|
|
var r = WbDrawDispatcher.ResolveSlotForFrame(
|
|
clipRoutingActive: true, serverGuid: 0u, parentCellId: null,
|
|
cellIdToSlot: Routing(), outdoorSlot: OutsideViewSlot, outdoorVisible: true);
|
|
|
|
Assert.Equal((uint)OutsideViewSlot, r.Slot);
|
|
Assert.False(r.Culled);
|
|
}
|
|
|
|
[Fact]
|
|
public void ForFrame_RoutingActive_OutdoorStabNotVisible_Culled()
|
|
{
|
|
var r = WbDrawDispatcher.ResolveSlotForFrame(
|
|
clipRoutingActive: true, serverGuid: 0u, parentCellId: null,
|
|
cellIdToSlot: Routing(), outdoorSlot: OutsideViewSlot, outdoorVisible: false);
|
|
|
|
Assert.True(r.Culled);
|
|
Assert.Equal(0u, r.Slot);
|
|
}
|
|
}
|