using System.Collections.Generic; using System.Numerics; using AcDream.App.Rendering; using AcDream.Core.World; using Xunit; namespace AcDream.App.Tests.Rendering; /// /// T1 (fused BR-2/3) partition contract — retail draw order: static world /// first, every server-spawned DYNAMIC in the frame's single LAST pass. /// /// ALL ServerGuid != 0 entities land in Dynamics regardless of /// cell (indoor, outdoor, unresolved, even non-visible cells — retail never /// drops a live entity for visibility-set reasons; culling is the /// viewcone's job, T3). /// ByCell carries only dat-baked indoor statics of VISIBLE /// cells (drawn with their cell). /// OutdoorStatic carries shells/scenery (the world pass). /// /// public class InteriorEntityPartitionTests { private const uint CellA = 0xA9B40170; private const uint CellB = 0xA9B40171; private const uint HiddenCell = 0xA9B40199; private const uint OutdoorCell = 0xA9B40020; private static WorldEntity Ent(uint id, uint serverGuid, uint? parentCell) => new() { Id = id, ServerGuid = serverGuid, SourceGfxObjOrSetupId = 0x01000001, Position = Vector3.Zero, Rotation = Quaternion.Identity, MeshRefs = new[] { new MeshRef(0x01000001, Matrix4x4.Identity) }, ParentCellId = parentCell, }; private static IEnumerable<(uint, Vector3, Vector3, IReadOnlyList, IReadOnlyDictionary?)> OneLb(uint lbId, params WorldEntity[] ents) => new[] { (lbId, Vector3.Zero, Vector3.Zero, (IReadOnlyList)ents, (IReadOnlyDictionary?)null) }; [Fact] public void AllServerSpawned_GoToDynamics_StaticsSplitByCellAndOutdoor() { var unresolvedLive = Ent(1, serverGuid: 0x5000000A, parentCell: null); var liveNpcInCell = Ent(2, serverGuid: 0x80001234, parentCell: CellA); var staticA = Ent(3, serverGuid: 0, parentCell: CellA); var staticB = Ent(4, serverGuid: 0, parentCell: CellB); var scenery = Ent(5, serverGuid: 0, parentCell: null); var liveOutdoor = Ent(6, serverGuid: 0x80005678, parentCell: OutdoorCell); var visible = new HashSet { CellA, CellB }; var result = InteriorEntityPartition.Partition( visible, OneLb(0xA9B4FFFF, unresolvedLive, liveNpcInCell, staticA, staticB, scenery, liveOutdoor)); // Every server-spawned entity is a dynamic — drawn in the last pass. Assert.Equal(3, result.Dynamics.Count); Assert.Contains(unresolvedLive, result.Dynamics); Assert.Contains(liveNpcInCell, result.Dynamics); Assert.Contains(liveOutdoor, result.Dynamics); // Indoor statics ride with their (visible) cell. Assert.Single(result.ByCell[CellA]); Assert.Contains(staticA, result.ByCell[CellA]); Assert.Single(result.ByCell[CellB]); Assert.Contains(staticB, result.ByCell[CellB]); // Outdoor statics (shells/scenery) ride with the world pass. Assert.Single(result.OutdoorStatic); Assert.Contains(scenery, result.OutdoorStatic); } [Fact] public void HiddenCell_DropsStatics_ButNeverDynamics() { var staticHidden = Ent(3, serverGuid: 0, parentCell: HiddenCell); var liveHidden = Ent(4, serverGuid: 0x80001234, parentCell: HiddenCell); var visible = new HashSet { CellA }; var result = InteriorEntityPartition.Partition( visible, OneLb(0xA9B4FFFF, staticHidden, liveHidden)); // A static in a non-flooded cell is not drawn this frame… Assert.False(result.ByCell.ContainsKey(HiddenCell)); Assert.Empty(result.OutdoorStatic); // …but a LIVE entity is never dropped by the visibility set (the old // contract dropped it — the audit's livedynamic-invisible divergence). Assert.Single(result.Dynamics); Assert.Contains(liveHidden, result.Dynamics); } [Fact] public void EntityWithNoMeshRefs_IsSkipped() { var noMesh = new WorldEntity { Id = 9, ServerGuid = 0, SourceGfxObjOrSetupId = 0x01000001, Position = Vector3.Zero, Rotation = Quaternion.Identity, MeshRefs = System.Array.Empty(), ParentCellId = CellA, }; var result = InteriorEntityPartition.Partition( new HashSet { CellA }, OneLb(0xA9B4FFFF, noMesh)); Assert.False(result.ByCell.ContainsKey(CellA)); Assert.Empty(result.Dynamics); Assert.Empty(result.OutdoorStatic); } }