diff --git a/tests/AcDream.Core.Tests/Lighting/LightBakeConformanceTests.cs b/tests/AcDream.Core.Tests/Lighting/LightBakeConformanceTests.cs new file mode 100644 index 00000000..ab3c0803 --- /dev/null +++ b/tests/AcDream.Core.Tests/Lighting/LightBakeConformanceTests.cs @@ -0,0 +1,78 @@ +using System.Collections.Generic; +using System.Numerics; +using AcDream.Core.Lighting; +using Xunit; + +namespace AcDream.Core.Tests.Lighting; + +/// +/// Golden conformance for the retail bake (calc_point_light + the [0,1] clamp), +/// driven by the live-cdb-captured Holtburg wall torches. Pins the contract that +/// mesh_modern.vert's pointContribution + the new pointAcc clamp (A7 Fix D, D-1) +/// must mirror line-for-line. See docs/research/2026-06-18-lighting-a7-fixABC-shipped-fixD-handoff.md. +/// +public class LightBakeConformanceTests +{ + private static LightSource OrangeTorch(Vector3 pos) => new() + { + Kind = LightKind.Point, + WorldPosition = pos, + ColorLinear = new Vector3(1.0f, 0.588f, 0.314f), // captured orange + Intensity = 100f, + Range = 4f * 1.3f, // falloff 4 × static_light_factor + IsLit = true, + }; + + [Theory] + [InlineData(1f)] + [InlineData(2f)] + [InlineData(3f)] + [InlineData(4f)] + [InlineData(5f)] + public void SingleOrangeTorch_IsWarmAndBounded_NeverWhite(float dist) + { + var vtx = Vector3.Zero; + var normal = Vector3.UnitX; + var torch = OrangeTorch(new Vector3(dist, 0f, 0f)); + + var c = LightBake.ComputeVertexColor(vtx, normal, new[] { torch }); + + Assert.InRange(c.X, 0f, 1f); + Assert.InRange(c.Y, 0f, 1f); + Assert.InRange(c.Z, 0f, 1f); + if (c.X > 0f) + { + Assert.True(c.X >= c.Y, $"R({c.X}) >= G({c.Y}) at d={dist}"); + Assert.True(c.Y >= c.Z, $"G({c.Y}) >= B({c.Z}) at d={dist}"); + } + } + + [Fact] + public void BeyondRange_ContributesNothing() + { + var torch = OrangeTorch(new Vector3(100f, 0f, 0f)); + var c = LightBake.ComputeVertexColor(Vector3.Zero, Vector3.UnitX, new[] { torch }); + Assert.Equal(Vector3.Zero, c); + } + + [Fact] + public void ManyOverlappingIntenseTorches_StillClampToOne() + { + var lights = new List(); + for (int i = 0; i < 8; i++) + lights.Add(new LightSource + { + Kind = LightKind.Point, + WorldPosition = new Vector3(1.5f, 0.1f * i, 0f), + ColorLinear = new Vector3(0.98f, 0.95f, 0.9f), + Intensity = 100f, + Range = 5.2f, + IsLit = true, + }); + + var c = LightBake.ComputeVertexColor(Vector3.Zero, Vector3.UnitX, lights); + Assert.InRange(c.X, 0f, 1f); + Assert.InRange(c.Y, 0f, 1f); + Assert.InRange(c.Z, 0f, 1f); + } +}