merge: A7 lighting (Fix A point-light shape + Fix B per-object selection) into main
Brings the worktree branch claude/thirsty-goldberg-51bb9b into main: -aa94cedper-vertex Gouraud + faithful calc_point_light (wrap + norm) -4345e77per-OBJECT point-light selection (minimize_object_lighting) Auto-merged cleanly against the D.2b retail-UI line (only GameWindow.cs overlapped, resolved by git). Merged tree builds green; 35/35 Core lighting tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
commit
37911ed510
7 changed files with 517 additions and 42 deletions
|
|
@ -144,4 +144,116 @@ public sealed class LightManagerTests
|
|||
mgr.Tick(new Vector3(3, 0, 0)); // same x, same y, z diff 4
|
||||
Assert.Equal(16f, light.DistSq, 2);
|
||||
}
|
||||
|
||||
// ── Fix B: per-object selection (minimize_object_lighting) ────────────────
|
||||
|
||||
[Fact]
|
||||
public void BuildPointLightSnapshot_ExcludesDirectionalAndUnlit()
|
||||
{
|
||||
var mgr = new LightManager();
|
||||
mgr.Register(MakePoint(new Vector3(1, 0, 0), 5f)); // in
|
||||
mgr.Register(MakePoint(new Vector3(2, 0, 0), 5f, lit: false)); // unlit → out
|
||||
mgr.Register(new LightSource { Kind = LightKind.Directional }); // sun → out
|
||||
|
||||
mgr.BuildPointLightSnapshot(Vector3.Zero);
|
||||
|
||||
Assert.Single(mgr.PointSnapshot);
|
||||
Assert.Equal(1f, mgr.PointSnapshot[0].WorldPosition.X, 3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BuildPointLightSnapshot_IndexStable_InBudget()
|
||||
{
|
||||
var mgr = new LightManager();
|
||||
// Registration order preserved when under MaxGlobalLights (no sort).
|
||||
mgr.Register(MakePoint(new Vector3(100, 0, 0), 5f)); // far
|
||||
mgr.Register(MakePoint(new Vector3(1, 0, 0), 5f)); // near
|
||||
|
||||
mgr.BuildPointLightSnapshot(Vector3.Zero);
|
||||
|
||||
Assert.Equal(2, mgr.PointSnapshot.Count);
|
||||
Assert.Equal(100f, mgr.PointSnapshot[0].WorldPosition.X, 3); // index 0 = first registered
|
||||
Assert.Equal(1f, mgr.PointSnapshot[1].WorldPosition.X, 3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SelectForObject_EmptySnapshot_ReturnsZero()
|
||||
{
|
||||
Span<int> idx = stackalloc int[8];
|
||||
int n = LightManager.SelectForObject(System.Array.Empty<LightSource>(), Vector3.Zero, 1f, idx);
|
||||
Assert.Equal(0, n);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SelectForObject_InRange_Selected()
|
||||
{
|
||||
var snapshot = new[] { MakePoint(new Vector3(3, 0, 0), range: 5f) }; // dist 3 < range 5
|
||||
Span<int> idx = stackalloc int[8];
|
||||
int n = LightManager.SelectForObject(snapshot, Vector3.Zero, radius: 0f, idx);
|
||||
Assert.Equal(1, n);
|
||||
Assert.Equal(0, idx[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SelectForObject_OutOfRange_Excluded()
|
||||
{
|
||||
// dist 10, range 5, radius 0 → 10 >= 5 → excluded.
|
||||
var snapshot = new[] { MakePoint(new Vector3(10, 0, 0), range: 5f) };
|
||||
Span<int> idx = stackalloc int[8];
|
||||
int n = LightManager.SelectForObject(snapshot, Vector3.Zero, radius: 0f, idx);
|
||||
Assert.Equal(0, n);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SelectForObject_ObjectRadiusExtendsReach()
|
||||
{
|
||||
// dist 7, range 5: out of reach at radius 0, but a radius-3 object sphere
|
||||
// overlaps (7 < 5+3). The whole object catches the light — retail uses the
|
||||
// object's bounding sphere, not its centre point.
|
||||
var snapshot = new[] { MakePoint(new Vector3(7, 0, 0), range: 5f) };
|
||||
Span<int> idx = stackalloc int[8];
|
||||
|
||||
Assert.Equal(0, LightManager.SelectForObject(snapshot, Vector3.Zero, radius: 0f, idx));
|
||||
Assert.Equal(1, LightManager.SelectForObject(snapshot, Vector3.Zero, radius: 3f, idx));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SelectForObject_MoreThan8_KeepsNearest8()
|
||||
{
|
||||
// 10 candidate lights all in range; expect the 8 nearest the object centre,
|
||||
// ascending by distance, with the two farthest dropped.
|
||||
var snapshot = new LightSource[10];
|
||||
for (int i = 0; i < 10; i++)
|
||||
snapshot[i] = MakePoint(new Vector3(i + 1, 0, 0), range: 100f); // dist i+1, all in range
|
||||
|
||||
Span<int> idx = stackalloc int[8];
|
||||
int n = LightManager.SelectForObject(snapshot, Vector3.Zero, radius: 0f, idx);
|
||||
|
||||
Assert.Equal(8, n);
|
||||
// Nearest-first: index 0 (dist 1) … index 7 (dist 8). The two farthest
|
||||
// (indices 8,9 / dist 9,10) are evicted.
|
||||
for (int k = 0; k < 8; k++)
|
||||
Assert.Equal(k, idx[k]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SelectForObject_CameraIndependent_DependsOnlyOnObjectCentre()
|
||||
{
|
||||
// Same snapshot, same object centre → identical selection regardless of
|
||||
// where any "camera" is (the method takes no camera). This is the property
|
||||
// that kills the "lights up as I approach" popping.
|
||||
var snapshot = new[]
|
||||
{
|
||||
MakePoint(new Vector3(2, 0, 0), range: 10f),
|
||||
MakePoint(new Vector3(20, 0, 0), range: 10f), // out of reach of centre 0
|
||||
};
|
||||
Span<int> a = stackalloc int[8];
|
||||
Span<int> b = stackalloc int[8];
|
||||
int na = LightManager.SelectForObject(snapshot, Vector3.Zero, 1f, a);
|
||||
int nb = LightManager.SelectForObject(snapshot, Vector3.Zero, 1f, b);
|
||||
|
||||
Assert.Equal(1, na);
|
||||
Assert.Equal(na, nb);
|
||||
Assert.Equal(a[0], b[0]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue