EntityPassesVisibleCellGate no longer returns true unconditionally for outdoor scenery under a cell filter (was the headline #78 bleed). Outdoor scenery now draws only via the unfiltered bucket (visibleCellIds: null) + ResolveEntitySlot's OutsideView routing. The outdoor-root global Draw passes visibleCellIds: null (no portal-cell scoping outdoors; retires VisibleCellIds as a render gate — peering into buildings is R5). Updated the EntityClipTests case that pinned the old bypass (Included -> Excluded). 174/174 App tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
103 lines
3.9 KiB
C#
103 lines
3.9 KiB
C#
// EntityClipTests.cs
|
|
//
|
|
// Phase W Stage 4/5: unit-test WbDrawDispatcher.EntityPassesVisibleCellGate.
|
|
// The gate is internal static (AcDream.App is InternalsVisibleTo AcDream.App.Tests)
|
|
// and pure — tests it without a GL context. Covers: ParentCellId in the visible
|
|
// set → included; ParentCellId NOT in the set → excluded; null visibleCellIds →
|
|
// everything included (outdoor / unconstrained root).
|
|
using System.Collections.Generic;
|
|
using System.Numerics;
|
|
using AcDream.App.Rendering.Wb;
|
|
using AcDream.Core.World;
|
|
using Xunit;
|
|
|
|
namespace AcDream.App.Tests.Rendering;
|
|
|
|
public sealed class EntityClipTests
|
|
{
|
|
// Minimal WorldEntity factory. EntityPassesVisibleCellGate only reads
|
|
// ParentCellId and IsBuildingShell/BuildingShellAnchorCellId from the entity;
|
|
// the other required fields are set to safe sentinel values.
|
|
private static WorldEntity Entity(uint? parentCellId, bool isShell = false, uint? shellAnchor = null) =>
|
|
new WorldEntity
|
|
{
|
|
Id = 1u,
|
|
SourceGfxObjOrSetupId = 0u,
|
|
Position = Vector3.Zero,
|
|
Rotation = Quaternion.Identity,
|
|
MeshRefs = System.Array.Empty<MeshRef>(),
|
|
ParentCellId = parentCellId,
|
|
IsBuildingShell = isShell,
|
|
BuildingShellAnchorCellId = shellAnchor,
|
|
};
|
|
|
|
[Fact]
|
|
public void EntityClip_ParentInVisibleSet_Included()
|
|
{
|
|
// Entity whose ParentCellId is in the visible set must pass the gate.
|
|
const uint cellId = 0xA9B40170u;
|
|
var visibleCellIds = new HashSet<uint> { cellId };
|
|
var entity = Entity(parentCellId: cellId);
|
|
|
|
bool result = WbDrawDispatcher.EntityPassesVisibleCellGate(
|
|
entity, visibleCellIds, WbDrawDispatcher.EntitySet.All);
|
|
|
|
Assert.True(result);
|
|
}
|
|
|
|
[Fact]
|
|
public void EntityClip_ParentNotInVisibleSet_Excluded()
|
|
{
|
|
// Entity whose ParentCellId is NOT in the visible set must fail the gate.
|
|
const uint visibleCell = 0xA9B40170u;
|
|
const uint entityCell = 0xA9B40172u;
|
|
var visibleCellIds = new HashSet<uint> { visibleCell };
|
|
var entity = Entity(parentCellId: entityCell);
|
|
|
|
bool result = WbDrawDispatcher.EntityPassesVisibleCellGate(
|
|
entity, visibleCellIds, WbDrawDispatcher.EntitySet.All);
|
|
|
|
Assert.False(result);
|
|
}
|
|
|
|
[Fact]
|
|
public void EntityClip_NullVisibleSet_IncludesAll()
|
|
{
|
|
// Null visibleCellIds means the outdoor root — no culling, all entities pass.
|
|
var entity = Entity(parentCellId: 0xA9B40172u);
|
|
|
|
bool result = WbDrawDispatcher.EntityPassesVisibleCellGate(
|
|
entity, visibleCellIds: null, WbDrawDispatcher.EntitySet.All);
|
|
|
|
Assert.True(result);
|
|
}
|
|
|
|
[Fact]
|
|
public void EntityClip_NullParentCell_NullVisibleSet_Included()
|
|
{
|
|
// An outdoor entity (ParentCellId == null) with null visibleCellIds passes.
|
|
var entity = Entity(parentCellId: null);
|
|
|
|
bool result = WbDrawDispatcher.EntityPassesVisibleCellGate(
|
|
entity, visibleCellIds: null, WbDrawDispatcher.EntitySet.All);
|
|
|
|
Assert.True(result);
|
|
}
|
|
|
|
[Fact]
|
|
public void EntityClip_NullParentCell_NonNullVisibleSet_Excluded()
|
|
{
|
|
// R1 (bleed fix #78): an outdoor entity (ParentCellId == null) with a non-null cell filter
|
|
// does NOT pass — it is not a member of any interior cell. (Was an unconditional return-true
|
|
// bypass, the headline outdoor-scenery bleed.) When such entities must draw through the
|
|
// doorway, the caller passes visibleCellIds: null and the OutsideView clip-slot routing
|
|
// (ResolveEntitySlot) gates them instead.
|
|
var visibleCellIds = new HashSet<uint> { 0xA9B40170u };
|
|
var entity = Entity(parentCellId: null);
|
|
|
|
bool result = WbDrawDispatcher.EntityPassesVisibleCellGate(
|
|
entity, visibleCellIds, WbDrawDispatcher.EntitySet.All);
|
|
|
|
Assert.False(result);
|
|
}
|
|
}
|