refactor(render): Phase U.1 — delete two-pipe inside-out machinery
Remove IndoorCellStencilPipeline + portal_stencil shaders, RenderInsideOutAcdream, RenderOutsideInAcdream, the A8-perf instrumentation, the cameraInsideBuilding / ACDREAM_A8_INDOOR_BRANCH branch, and the dead EntitySet partition values. Collapse the render branch to the default Draw(All) path (U.4a replaces it with the gated unified pass). Keep all audited EnvCellRenderer / BuildingLoader / CellVisibility / camera-collision fixes. Also deleted with the partition: the two test-only walk helpers (WbDrawDispatcher.WalkEntitiesForTest / WalkEntitiesForTestByCellIds) and their test files (WbDrawDispatcherEntitySetTests, WbDrawDispatcherCellIdsOverloadTests), which existed solely to exercise the removed IndoorPass/OutdoorScenery/ BuildingShells/LiveDynamic partition. EntityMatchesSet / IsShellScopedSet collapse to the All-path constants; the set: parameter is retained as a seam for the unified pass. Note: the depth-clear-if-inside default-path workaround was removed per the U.1 task list — any current indoor-wall degradation persists until a later Phase U task lands the unified pass (expected, not a regression introduced here). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
0f7b395be1
commit
3fc77be5de
8 changed files with 37 additions and 2612 deletions
|
|
@ -1,232 +0,0 @@
|
|||
// Phase A8 — portal mesh triangle-fan generation tests.
|
||||
//
|
||||
// Pure-math coverage of PortalMeshBuilder.BuildTriangles — the part of
|
||||
// IndoorCellStencilPipeline that converts a list of LoadedCell with
|
||||
// PortalPolygons + WorldTransform into a flat Vector3[] of triangles
|
||||
// in world space. The GL/upload portion is exercised at runtime only.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using AcDream.App.Rendering;
|
||||
using Xunit;
|
||||
|
||||
namespace AcDream.App.Tests.Rendering;
|
||||
|
||||
public class IndoorCellStencilPipelineTests
|
||||
{
|
||||
[Fact]
|
||||
public void BuildTriangles_NoCells_ReturnsEmpty()
|
||||
{
|
||||
var verts = PortalMeshBuilder.BuildTriangles(new List<LoadedCell>());
|
||||
Assert.Empty(verts);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BuildTriangles_SkipsInnerPortals()
|
||||
{
|
||||
// Two portals on one cell: one exit (OtherCellId=0xFFFF, should be
|
||||
// included), one inner (OtherCellId=0x0102, should be skipped).
|
||||
var cell = new LoadedCell
|
||||
{
|
||||
WorldTransform = Matrix4x4.Identity,
|
||||
Portals = new()
|
||||
{
|
||||
new CellPortalInfo(0xFFFF, 100, 0), // exit — included
|
||||
new CellPortalInfo(0x0102, 101, 0), // inner — skipped
|
||||
},
|
||||
ClipPlanes = new() { default, default },
|
||||
PortalPolygons = new()
|
||||
{
|
||||
new[]
|
||||
{
|
||||
new Vector3(0, 0, 0),
|
||||
new Vector3(1, 0, 0),
|
||||
new Vector3(1, 1, 0),
|
||||
},
|
||||
new[]
|
||||
{
|
||||
new Vector3(10, 0, 0),
|
||||
new Vector3(11, 0, 0),
|
||||
new Vector3(11, 1, 0),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
var verts = PortalMeshBuilder.BuildTriangles(new List<LoadedCell> { cell });
|
||||
|
||||
// Only the exit polygon (3 verts → 1 triangle → 3 vertices).
|
||||
Assert.Equal(3, verts.Length);
|
||||
Assert.Equal(new Vector3(0, 0, 0), verts[0]);
|
||||
Assert.Equal(new Vector3(1, 0, 0), verts[1]);
|
||||
Assert.Equal(new Vector3(1, 1, 0), verts[2]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BuildTriangles_OnlyIncludesProvidedVisibleCells()
|
||||
{
|
||||
// The render path now feeds BuildTriangles from the portal traversal's
|
||||
// visible cells, not every cell in the building. A hidden room's exit
|
||||
// portal must not punch outdoor terrain into the current view.
|
||||
var visibleInnerCell = new LoadedCell
|
||||
{
|
||||
WorldTransform = Matrix4x4.Identity,
|
||||
Portals = new() { new CellPortalInfo(0x0102, 100, 0) },
|
||||
ClipPlanes = new() { default },
|
||||
PortalPolygons = new()
|
||||
{
|
||||
new[]
|
||||
{
|
||||
new Vector3(0, 0, 0),
|
||||
new Vector3(1, 0, 0),
|
||||
new Vector3(1, 1, 0),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
var hiddenExitCell = new LoadedCell
|
||||
{
|
||||
WorldTransform = Matrix4x4.Identity,
|
||||
Portals = new() { new CellPortalInfo(0xFFFF, 101, 0) },
|
||||
ClipPlanes = new() { default },
|
||||
PortalPolygons = new()
|
||||
{
|
||||
new[]
|
||||
{
|
||||
new Vector3(10, 0, 0),
|
||||
new Vector3(11, 0, 0),
|
||||
new Vector3(11, 1, 0),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
var visibleOnly = PortalMeshBuilder.BuildTriangles(new List<LoadedCell> { visibleInnerCell });
|
||||
var allBuildingCells = PortalMeshBuilder.BuildTriangles(new List<LoadedCell> { visibleInnerCell, hiddenExitCell });
|
||||
|
||||
Assert.Empty(visibleOnly);
|
||||
Assert.Equal(3, allBuildingCells.Length);
|
||||
Assert.Equal(new Vector3(10, 0, 0), allBuildingCells[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BuildTriangles_CameraSideFilterSkipsExitPortalsBehindCamera()
|
||||
{
|
||||
var cell = new LoadedCell
|
||||
{
|
||||
WorldTransform = Matrix4x4.Identity,
|
||||
InverseWorldTransform = Matrix4x4.Identity,
|
||||
Portals = new() { new CellPortalInfo(0xFFFF, 100, 0) },
|
||||
ClipPlanes = new()
|
||||
{
|
||||
new PortalClipPlane
|
||||
{
|
||||
Normal = Vector3.UnitX,
|
||||
D = 0f,
|
||||
InsideSide = 0,
|
||||
},
|
||||
},
|
||||
PortalPolygons = new()
|
||||
{
|
||||
new[]
|
||||
{
|
||||
new Vector3(0, 0, 0),
|
||||
new Vector3(0, 1, 0),
|
||||
new Vector3(0, 0, 1),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
var visible = PortalMeshBuilder.BuildTriangles(new List<LoadedCell> { cell }, new Vector3(1, 0, 0));
|
||||
var rejected = PortalMeshBuilder.BuildTriangles(new List<LoadedCell> { cell }, new Vector3(-1, 0, 0));
|
||||
|
||||
Assert.Equal(3, visible.Length);
|
||||
Assert.Empty(rejected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BuildTriangles_TriangulatesAsFan()
|
||||
{
|
||||
// 4-vertex quad → fan = 2 triangles → 6 vertices.
|
||||
// Quad: (0,0,0), (1,0,0), (1,1,0), (0,1,0).
|
||||
// Fan from vertex 0: (0,1,2), (0,2,3).
|
||||
var cell = new LoadedCell
|
||||
{
|
||||
WorldTransform = Matrix4x4.Identity,
|
||||
Portals = new() { new CellPortalInfo(0xFFFF, 100, 0) },
|
||||
ClipPlanes = new() { default },
|
||||
PortalPolygons = new()
|
||||
{
|
||||
new[]
|
||||
{
|
||||
new Vector3(0, 0, 0),
|
||||
new Vector3(1, 0, 0),
|
||||
new Vector3(1, 1, 0),
|
||||
new Vector3(0, 1, 0),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
var verts = PortalMeshBuilder.BuildTriangles(new List<LoadedCell> { cell });
|
||||
|
||||
Assert.Equal(6, verts.Length);
|
||||
// Triangle 1: (0,1,2)
|
||||
Assert.Equal(new Vector3(0, 0, 0), verts[0]);
|
||||
Assert.Equal(new Vector3(1, 0, 0), verts[1]);
|
||||
Assert.Equal(new Vector3(1, 1, 0), verts[2]);
|
||||
// Triangle 2: (0,2,3)
|
||||
Assert.Equal(new Vector3(0, 0, 0), verts[3]);
|
||||
Assert.Equal(new Vector3(1, 1, 0), verts[4]);
|
||||
Assert.Equal(new Vector3(0, 1, 0), verts[5]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BuildTriangles_AppliesWorldTransform()
|
||||
{
|
||||
// Identity cell-local triangle, translated by WorldTransform.
|
||||
var translate = Matrix4x4.CreateTranslation(new Vector3(100, 200, 300));
|
||||
var cell = new LoadedCell
|
||||
{
|
||||
WorldTransform = translate,
|
||||
Portals = new() { new CellPortalInfo(0xFFFF, 100, 0) },
|
||||
ClipPlanes = new() { default },
|
||||
PortalPolygons = new()
|
||||
{
|
||||
new[]
|
||||
{
|
||||
new Vector3(0, 0, 0),
|
||||
new Vector3(1, 0, 0),
|
||||
new Vector3(0, 1, 0),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
var verts = PortalMeshBuilder.BuildTriangles(new List<LoadedCell> { cell });
|
||||
|
||||
Assert.Equal(3, verts.Length);
|
||||
Assert.Equal(new Vector3(100, 200, 300), verts[0]);
|
||||
Assert.Equal(new Vector3(101, 200, 300), verts[1]);
|
||||
Assert.Equal(new Vector3(100, 201, 300), verts[2]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BuildTriangles_SkipsEmptyOrDegeneratePolygons()
|
||||
{
|
||||
var cell = new LoadedCell
|
||||
{
|
||||
WorldTransform = Matrix4x4.Identity,
|
||||
Portals = new()
|
||||
{
|
||||
new CellPortalInfo(0xFFFF, 100, 0),
|
||||
new CellPortalInfo(0xFFFF, 101, 0),
|
||||
},
|
||||
ClipPlanes = new() { default, default },
|
||||
PortalPolygons = new()
|
||||
{
|
||||
System.Array.Empty<Vector3>(), // empty
|
||||
new[] { new Vector3(0, 0, 0), new Vector3(1, 0, 0) }, // degenerate (2 verts)
|
||||
},
|
||||
};
|
||||
|
||||
var verts = PortalMeshBuilder.BuildTriangles(new List<LoadedCell> { cell });
|
||||
Assert.Empty(verts);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,92 +0,0 @@
|
|||
// Phase A8 RR5 — verify WbDrawDispatcher.WalkEntitiesForTestByCellIds,
|
||||
// the pure-data companion to the new Draw(cellIds:) production overload.
|
||||
//
|
||||
// Semantics: indoor entities (ParentCellId.HasValue) are gated by explicit
|
||||
// membership in cellIds. Building shells (IsBuildingShell) pass only when their
|
||||
// BuildingShellAnchorCellId belongs to the same cell set.
|
||||
// Outdoor scenery (no ParentCellId, not a shell) is excluded by EntitySet.IndoorPass.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using AcDream.App.Rendering.Wb;
|
||||
using AcDream.Core.World;
|
||||
using Xunit;
|
||||
|
||||
namespace AcDream.Core.Tests.Rendering.Wb;
|
||||
|
||||
public class WbDrawDispatcherCellIdsOverloadTests
|
||||
{
|
||||
private static WorldEntity CellEnt(uint id, uint cellId) => new()
|
||||
{
|
||||
Id = id,
|
||||
SourceGfxObjOrSetupId = 0x01000001u,
|
||||
ParentCellId = cellId,
|
||||
MeshRefs = new List<MeshRef> { new(0x01000001u, Matrix4x4.Identity) },
|
||||
Position = Vector3.Zero,
|
||||
Rotation = Quaternion.Identity,
|
||||
};
|
||||
|
||||
private static WorldEntity OutdoorScenery(uint id) => new()
|
||||
{
|
||||
Id = id,
|
||||
SourceGfxObjOrSetupId = 0x01000001u,
|
||||
ParentCellId = null,
|
||||
IsBuildingShell = false,
|
||||
MeshRefs = new List<MeshRef> { new(0x01000001u, Matrix4x4.Identity) },
|
||||
Position = Vector3.Zero,
|
||||
Rotation = Quaternion.Identity,
|
||||
};
|
||||
|
||||
private static WorldEntity BuildingShell(uint id, uint? anchorCellId) => new()
|
||||
{
|
||||
Id = id,
|
||||
SourceGfxObjOrSetupId = 0x02000001u,
|
||||
ParentCellId = null,
|
||||
IsBuildingShell = true,
|
||||
BuildingShellAnchorCellId = anchorCellId,
|
||||
MeshRefs = new List<MeshRef> { new(0x01000001u, Matrix4x4.Identity) },
|
||||
Position = Vector3.Zero,
|
||||
Rotation = Quaternion.Identity,
|
||||
};
|
||||
|
||||
[Fact]
|
||||
public void WalkEntitiesByCellIds_IncludesOnlyEntitiesInListedCells()
|
||||
{
|
||||
var entities = new List<WorldEntity>
|
||||
{
|
||||
CellEnt(0x40000001u, 0xA9B40150u), // in listed cells
|
||||
CellEnt(0x40000002u, 0xA9B40151u), // in listed cells
|
||||
CellEnt(0x40000003u, 0xA9B40999u), // OUT — not in list
|
||||
BuildingShell(0xC0000001u, 0xA9B40150u), // in listed building cells
|
||||
BuildingShell(0xC0000003u, 0xA9B40999u), // OUT — another building shell
|
||||
OutdoorScenery(0xC0000002u), // OUT — not a shell, not in cell list
|
||||
};
|
||||
var cellIds = new HashSet<uint> { 0xA9B40150u, 0xA9B40151u };
|
||||
|
||||
var result = WbDrawDispatcher.WalkEntitiesForTestByCellIds(
|
||||
entities, cellIds, set: WbDrawDispatcher.EntitySet.IndoorPass);
|
||||
|
||||
Assert.Equal(3, result.Count);
|
||||
Assert.Contains(0x40000001u, result);
|
||||
Assert.Contains(0x40000002u, result);
|
||||
Assert.Contains(0xC0000001u, result);
|
||||
Assert.DoesNotContain(0x40000003u, result);
|
||||
Assert.DoesNotContain(0xC0000003u, result);
|
||||
Assert.DoesNotContain(0xC0000002u, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WalkEntitiesByCellIds_EmptyCellList_ExcludesBuildingShells()
|
||||
{
|
||||
var entities = new List<WorldEntity>
|
||||
{
|
||||
CellEnt(0x40000001u, 0xA9B40150u),
|
||||
BuildingShell(0xC0000001u, 0xA9B40150u),
|
||||
};
|
||||
|
||||
var result = WbDrawDispatcher.WalkEntitiesForTestByCellIds(
|
||||
entities, new HashSet<uint>(), set: WbDrawDispatcher.EntitySet.IndoorPass);
|
||||
|
||||
Assert.Empty(result);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,282 +0,0 @@
|
|||
// Phase A8 — verify the WbDrawDispatcher EntitySet partition (taxonomy-aware).
|
||||
//
|
||||
// The pure-data WalkEntitiesForTest helper iterates a flat entity list and
|
||||
// returns the IDs that survive the EntitySet filter + visibleCellIds gate.
|
||||
//
|
||||
// EntitySet.IndoorPass — ParentCellId.HasValue OR IsBuildingShell,
|
||||
// and NOT live-dynamic (ServerGuid == 0).
|
||||
// Building shells are gated by their dat anchor;
|
||||
// live-dynamic flows through LiveDynamic instead.
|
||||
// EntitySet.OutdoorScenery — ParentCellId == null AND not live-dynamic.
|
||||
// Includes building shells for exterior/depth repair passes.
|
||||
// EntitySet.BuildingShells — IsBuildingShell only, gated by dat anchor when
|
||||
// visibleCellIds are supplied.
|
||||
// EntitySet.LiveDynamic — ServerGuid != 0 (player, NPCs, dropped items,
|
||||
// idle doors after animation). Drawn last with
|
||||
// stencil disabled.
|
||||
// EntitySet.All — pre-A8 behavior (visibleCellIds gates indoor;
|
||||
// outdoor entities pass through).
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using AcDream.App.Rendering.Wb;
|
||||
using AcDream.Core.World;
|
||||
using Xunit;
|
||||
|
||||
namespace AcDream.Core.Tests.Rendering.Wb;
|
||||
|
||||
public class WbDrawDispatcherEntitySetTests
|
||||
{
|
||||
private static WorldEntity CellEnt(uint id, uint cellId) => new()
|
||||
{
|
||||
Id = id,
|
||||
SourceGfxObjOrSetupId = 0x01000001u,
|
||||
ParentCellId = cellId,
|
||||
MeshRefs = new List<MeshRef> { new() { GfxObjId = 0x01000001u } },
|
||||
Position = Vector3.Zero,
|
||||
Rotation = Quaternion.Identity,
|
||||
};
|
||||
|
||||
private static WorldEntity OutdoorScenery(uint id) => new()
|
||||
{
|
||||
Id = id,
|
||||
SourceGfxObjOrSetupId = 0x01000001u,
|
||||
ParentCellId = null,
|
||||
IsBuildingShell = false,
|
||||
MeshRefs = new List<MeshRef> { new() { GfxObjId = 0x01000001u } },
|
||||
Position = Vector3.Zero,
|
||||
Rotation = Quaternion.Identity,
|
||||
};
|
||||
|
||||
private static WorldEntity BuildingShell(uint id, uint? anchorCellId = null) => new()
|
||||
{
|
||||
Id = id,
|
||||
SourceGfxObjOrSetupId = 0x02000001u,
|
||||
ParentCellId = null,
|
||||
IsBuildingShell = true,
|
||||
BuildingShellAnchorCellId = anchorCellId,
|
||||
MeshRefs = new List<MeshRef> { new() { GfxObjId = 0x01000001u } },
|
||||
Position = Vector3.Zero,
|
||||
Rotation = Quaternion.Identity,
|
||||
};
|
||||
|
||||
private static WorldEntity LiveDynamic(uint id, uint serverGuid) => new()
|
||||
{
|
||||
Id = id,
|
||||
SourceGfxObjOrSetupId = 0x02000001u,
|
||||
ServerGuid = serverGuid,
|
||||
ParentCellId = null,
|
||||
IsBuildingShell = false,
|
||||
MeshRefs = new List<MeshRef> { new() { GfxObjId = 0x01000001u } },
|
||||
Position = Vector3.Zero,
|
||||
Rotation = Quaternion.Identity,
|
||||
};
|
||||
|
||||
[Fact]
|
||||
public void IndoorPass_IncludesCellEntities()
|
||||
{
|
||||
var entities = new List<WorldEntity>
|
||||
{
|
||||
CellEnt(0x10000001, 0xA9B40143),
|
||||
OutdoorScenery(0x10000002),
|
||||
CellEnt(0x10000003, 0xA9B40144),
|
||||
};
|
||||
|
||||
var visible = new HashSet<uint> { 0xA9B40143u, 0xA9B40144u };
|
||||
var result = WbDrawDispatcher.WalkEntitiesForTest(
|
||||
entities, visibleCellIds: visible, set: WbDrawDispatcher.EntitySet.IndoorPass);
|
||||
|
||||
Assert.Equal(2, result.Count);
|
||||
Assert.Contains(0x10000001u, result);
|
||||
Assert.Contains(0x10000003u, result);
|
||||
Assert.DoesNotContain(0x10000002u, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IndoorPass_IncludesBuildingShells_WhenAnchorCellIsVisible()
|
||||
{
|
||||
var entities = new List<WorldEntity>
|
||||
{
|
||||
BuildingShell(0xC0000001, 0xA9B40143u), // cottage wall
|
||||
OutdoorScenery(0xC0000002), // tree
|
||||
CellEnt(0x40000001, 0xA9B40143),
|
||||
};
|
||||
|
||||
var visible = new HashSet<uint> { 0xA9B40143u };
|
||||
var result = WbDrawDispatcher.WalkEntitiesForTest(
|
||||
entities, visibleCellIds: visible, set: WbDrawDispatcher.EntitySet.IndoorPass);
|
||||
|
||||
Assert.Equal(2, result.Count);
|
||||
Assert.Contains(0xC0000001u, result); // building shell included
|
||||
Assert.Contains(0x40000001u, result); // cell entity included
|
||||
Assert.DoesNotContain(0xC0000002u, result); // tree excluded
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IndoorPass_WithNullCellFilter_UsesEntitySetOnly()
|
||||
{
|
||||
var entities = new List<WorldEntity>
|
||||
{
|
||||
BuildingShell(0xC0000001, 0xA9B40143u),
|
||||
CellEnt(0x40000001, 0xA9B40143),
|
||||
CellEnt(0x40000002, 0xA9B40199),
|
||||
OutdoorScenery(0xC0000002),
|
||||
LiveDynamic(0x10000001, serverGuid: 0x50000123u),
|
||||
};
|
||||
|
||||
var result = WbDrawDispatcher.WalkEntitiesForTest(
|
||||
entities, visibleCellIds: null, set: WbDrawDispatcher.EntitySet.IndoorPass);
|
||||
|
||||
Assert.Equal(3, result.Count);
|
||||
Assert.Contains(0xC0000001u, result);
|
||||
Assert.Contains(0x40000001u, result);
|
||||
Assert.Contains(0x40000002u, result);
|
||||
Assert.DoesNotContain(0xC0000002u, result);
|
||||
Assert.DoesNotContain(0x10000001u, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IndoorPass_ExcludesBuildingShells_WhenAnchorCellIsNotVisible()
|
||||
{
|
||||
var entities = new List<WorldEntity>
|
||||
{
|
||||
BuildingShell(0xC0000001, 0xA9B40150u),
|
||||
CellEnt(0x40000001, 0xA9B40143),
|
||||
};
|
||||
|
||||
var visible = new HashSet<uint> { 0xA9B40143u };
|
||||
var result = WbDrawDispatcher.WalkEntitiesForTest(
|
||||
entities, visibleCellIds: visible, set: WbDrawDispatcher.EntitySet.IndoorPass);
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Contains(0x40000001u, result);
|
||||
Assert.DoesNotContain(0xC0000001u, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IndoorPass_ExcludesLiveDynamic()
|
||||
{
|
||||
var entities = new List<WorldEntity>
|
||||
{
|
||||
CellEnt(0x40000001, 0xA9B40143),
|
||||
LiveDynamic(0x10000001, serverGuid: 0x50000123u),
|
||||
};
|
||||
|
||||
var visible = new HashSet<uint> { 0xA9B40143u };
|
||||
var result = WbDrawDispatcher.WalkEntitiesForTest(
|
||||
entities, visibleCellIds: visible, set: WbDrawDispatcher.EntitySet.IndoorPass);
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Contains(0x40000001u, result);
|
||||
Assert.DoesNotContain(0x10000001u, result); // live-dynamic excluded
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OutdoorScenery_IncludesBuildingShells()
|
||||
{
|
||||
var entities = new List<WorldEntity>
|
||||
{
|
||||
BuildingShell(0xC0000001), // cottage wall — included
|
||||
OutdoorScenery(0xC0000002), // tree — included
|
||||
CellEnt(0x40000001, 0xA9B40143), // cell — excluded
|
||||
};
|
||||
|
||||
var result = WbDrawDispatcher.WalkEntitiesForTest(
|
||||
entities, visibleCellIds: null, set: WbDrawDispatcher.EntitySet.OutdoorScenery);
|
||||
|
||||
Assert.Equal(2, result.Count);
|
||||
Assert.Contains(0xC0000002u, result);
|
||||
Assert.Contains(0xC0000001u, result);
|
||||
Assert.DoesNotContain(0x40000001u, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OutdoorScenery_ExcludesLiveDynamic()
|
||||
{
|
||||
var entities = new List<WorldEntity>
|
||||
{
|
||||
OutdoorScenery(0xC0000001),
|
||||
LiveDynamic(0x10000001, serverGuid: 0x50000123u),
|
||||
};
|
||||
|
||||
var result = WbDrawDispatcher.WalkEntitiesForTest(
|
||||
entities, visibleCellIds: null, set: WbDrawDispatcher.EntitySet.OutdoorScenery);
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Contains(0xC0000001u, result);
|
||||
Assert.DoesNotContain(0x10000001u, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BuildingShells_IncludesOnlyAnchoredShells()
|
||||
{
|
||||
var entities = new List<WorldEntity>
|
||||
{
|
||||
BuildingShell(0xC0000001, 0xA9B40143u),
|
||||
BuildingShell(0xC0000002, 0xA9B40999u),
|
||||
OutdoorScenery(0xC0000003),
|
||||
CellEnt(0x40000001, 0xA9B40143),
|
||||
LiveDynamic(0x10000001, serverGuid: 0x50000123u),
|
||||
};
|
||||
|
||||
var visible = new HashSet<uint> { 0xA9B40143u };
|
||||
var result = WbDrawDispatcher.WalkEntitiesForTest(
|
||||
entities, visibleCellIds: visible, set: WbDrawDispatcher.EntitySet.BuildingShells);
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Contains(0xC0000001u, result);
|
||||
Assert.DoesNotContain(0xC0000002u, result);
|
||||
Assert.DoesNotContain(0xC0000003u, result);
|
||||
Assert.DoesNotContain(0x40000001u, result);
|
||||
Assert.DoesNotContain(0x10000001u, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LiveDynamic_IncludesOnlyServerSpawned()
|
||||
{
|
||||
var entities = new List<WorldEntity>
|
||||
{
|
||||
OutdoorScenery(0xC0000001),
|
||||
BuildingShell(0xC0000002),
|
||||
CellEnt(0x40000001, 0xA9B40143),
|
||||
LiveDynamic(0x10000001, serverGuid: 0x50000123u),
|
||||
LiveDynamic(0x10000002, serverGuid: 0x50000456u),
|
||||
};
|
||||
|
||||
var result = WbDrawDispatcher.WalkEntitiesForTest(
|
||||
entities, visibleCellIds: null, set: WbDrawDispatcher.EntitySet.LiveDynamic);
|
||||
|
||||
Assert.Equal(2, result.Count);
|
||||
Assert.Contains(0x10000001u, result);
|
||||
Assert.Contains(0x10000002u, result);
|
||||
Assert.DoesNotContain(0xC0000001u, result);
|
||||
Assert.DoesNotContain(0xC0000002u, result);
|
||||
Assert.DoesNotContain(0x40000001u, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void All_MatchesPreA8Behavior()
|
||||
{
|
||||
var entities = new List<WorldEntity>
|
||||
{
|
||||
CellEnt(0x40000001, 0xA9B40143),
|
||||
OutdoorScenery(0xC0000001),
|
||||
BuildingShell(0xC0000002),
|
||||
LiveDynamic(0x10000001, serverGuid: 0x50000123u),
|
||||
CellEnt(0x40000002, 0xA9B40999), // not in visibleCellIds
|
||||
};
|
||||
|
||||
var visible = new HashSet<uint> { 0xA9B40143u };
|
||||
var result = WbDrawDispatcher.WalkEntitiesForTest(
|
||||
entities, visibleCellIds: visible, set: WbDrawDispatcher.EntitySet.All);
|
||||
|
||||
// Pre-A8: visibleCellIds gates indoor entities only; outdoor entities
|
||||
// (regardless of building/scenery/live-dynamic) pass through.
|
||||
Assert.Equal(4, result.Count);
|
||||
Assert.Contains(0x40000001u, result);
|
||||
Assert.Contains(0xC0000001u, result);
|
||||
Assert.Contains(0xC0000002u, result);
|
||||
Assert.Contains(0x10000001u, result);
|
||||
Assert.DoesNotContain(0x40000002u, result);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue