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);
}
}