// 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 { 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 { 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 { 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 { new() { GfxObjId = 0x01000001u } }, Position = Vector3.Zero, Rotation = Quaternion.Identity, }; [Fact] public void IndoorPass_IncludesCellEntities() { var entities = new List { CellEnt(0x10000001, 0xA9B40143), OutdoorScenery(0x10000002), CellEnt(0x10000003, 0xA9B40144), }; var visible = new HashSet { 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 { BuildingShell(0xC0000001, 0xA9B40143u), // cottage wall OutdoorScenery(0xC0000002), // tree CellEnt(0x40000001, 0xA9B40143), }; var visible = new HashSet { 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 { 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 { BuildingShell(0xC0000001, 0xA9B40150u), CellEnt(0x40000001, 0xA9B40143), }; var visible = new HashSet { 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 { CellEnt(0x40000001, 0xA9B40143), LiveDynamic(0x10000001, serverGuid: 0x50000123u), }; var visible = new HashSet { 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 { 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 { 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 { BuildingShell(0xC0000001, 0xA9B40143u), BuildingShell(0xC0000002, 0xA9B40999u), OutdoorScenery(0xC0000003), CellEnt(0x40000001, 0xA9B40143), LiveDynamic(0x10000001, serverGuid: 0x50000123u), }; var visible = new HashSet { 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 { 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 { CellEnt(0x40000001, 0xA9B40143), OutdoorScenery(0xC0000001), BuildingShell(0xC0000002), LiveDynamic(0x10000001, serverGuid: 0x50000123u), CellEnt(0x40000002, 0xA9B40999), // not in visibleCellIds }; var visible = new HashSet { 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); } }