using System.Numerics; using AcDream.Core.Lighting; using Xunit; namespace AcDream.Core.Tests.Lighting; public sealed class LightManagerTests { private static LightSource MakePoint(Vector3 pos, float range, uint ownerId = 0, bool lit = true) => new LightSource { Kind = LightKind.Point, WorldPosition = pos, Range = range, IsLit = lit, OwnerId = ownerId, }; [Fact] public void Register_Unregister_TracksList() { var mgr = new LightManager(); var a = MakePoint(Vector3.Zero, 5f); var b = MakePoint(new Vector3(10, 0, 0), 5f); mgr.Register(a); mgr.Register(b); Assert.Equal(2, mgr.RegisteredCount); mgr.Unregister(a); Assert.Equal(1, mgr.RegisteredCount); } [Fact] public void Register_DuplicateInstance_Idempotent() { var mgr = new LightManager(); var light = MakePoint(Vector3.Zero, 5f); mgr.Register(light); mgr.Register(light); Assert.Equal(1, mgr.RegisteredCount); } [Fact] public void Tick_SelectsByDistance_Top8() { var mgr = new LightManager(); // 12 lights at varying distances, all with range 100 so none filter out. for (int i = 0; i < 12; i++) mgr.Register(MakePoint(new Vector3(i, 0, 0), 100f)); mgr.Tick(viewerWorldPos: Vector3.Zero); Assert.Equal(8, mgr.ActiveCount); // Top 8 should be the closest (i=0..7). foreach (var l in mgr.Active) { Assert.NotNull(l); Assert.True(l!.WorldPosition.X <= 7f); } } [Fact] public void Tick_DropsLightsOutsideRangeWithSlack() { var mgr = new LightManager(); mgr.Register(MakePoint(new Vector3(20, 0, 0), range: 5f)); // far outside its own range mgr.Tick(viewerWorldPos: Vector3.Zero); Assert.Equal(0, mgr.ActiveCount); } [Fact] public void Tick_IncludesLightsNearRangeEdge_WithSlack() { var mgr = new LightManager(); // Light at distance 5.0, range 5.0: distSq=25, rangeSq*1.1^2 = 25*1.21 = 30.25 → included. mgr.Register(MakePoint(new Vector3(5, 0, 0), range: 5f)); mgr.Tick(viewerWorldPos: Vector3.Zero); Assert.Equal(1, mgr.ActiveCount); } [Fact] public void Tick_SunSlot0_PreservedAcrossTicks() { var mgr = new LightManager(); var sun = new LightSource { Kind = LightKind.Directional, WorldForward = -Vector3.UnitZ }; mgr.Sun = sun; mgr.Register(MakePoint(Vector3.Zero, 100f)); mgr.Tick(Vector3.Zero); Assert.Equal(2, mgr.ActiveCount); Assert.Same(sun, mgr.Active[0]); } [Fact] public void Tick_UnlitLight_Excluded() { var mgr = new LightManager(); var light = MakePoint(Vector3.Zero, 100f, lit: false); mgr.Register(light); mgr.Tick(Vector3.Zero); Assert.Equal(0, mgr.ActiveCount); // Toggle lit: should now appear. light.IsLit = true; mgr.Tick(Vector3.Zero); Assert.Equal(1, mgr.ActiveCount); } [Fact] public void UnregisterByOwner_RemovesAttachedLights() { var mgr = new LightManager(); mgr.Register(MakePoint(Vector3.Zero, 5f, ownerId: 42)); mgr.Register(MakePoint(new Vector3(1, 0, 0), 5f, ownerId: 42)); mgr.Register(MakePoint(new Vector3(2, 0, 0), 5f, ownerId: 99)); mgr.UnregisterByOwner(42); Assert.Equal(1, mgr.RegisteredCount); } [Fact] public void DistSq_UpdatedEachTick() { var mgr = new LightManager(); var light = MakePoint(new Vector3(3, 0, 4), 10f); // dist 5 mgr.Register(light); mgr.Tick(Vector3.Zero); Assert.Equal(25f, light.DistSq, 2); mgr.Tick(new Vector3(3, 0, 0)); // same x, same y, z diff 4 Assert.Equal(16f, light.DistSq, 2); } }