feat(lighting): SelectForCell (all dynamic lights per EnvCell) + #176 handoff — residual is a runtime z-fight
cdb trace of LIVE retail (tools/cdb/issue176-floor-light.cdb, binary<->PDB MATCH) PROVED retail applies ALL its dynamic lights — 4 intensity-100 magenta portal lights (d3dIdx 3-6, falloff 6) + the viewer fill (d3dIdx 1, 2.25) — as D3D hardware lights to EVERY Facility Hub cell, every frame, stable. So the faceted purple wedges on the floor are retail-FAITHFUL. acdream did a per-cell SelectForObject sphere-overlap 8-cap for cells, so the portal set could differ/flip per cell. - LightManager.SelectForCell (retail minimize_envcell_lighting 0x0054c170): ALL dynamic lights applied unconditionally (shader range cutoff zeroes non-reaching = D3D hardware range), then nearest static torches fill remaining slots. Wired into EnvCellRenderer.GetCellLightSet. Objects keep SelectForObject (minimize_object_lighting). Pins: SelectForCell_AppliesAllDynamicLights_EvenOutOfReach + _SameDynamicSet_ForCellsFarApart_NoFlap. - Apparatus: [light-detail] gains owner/cell/dyn (pinned the culprit = 2 portal weenies 0x000F4247/48 in 0x8A020118/19, intensity=100 magenta); CellVertexNormals_SmoothOrFaceted_Dump (corridor floor uses SMOOTH per-vertex dat normals, not flat); tools/cdb/issue176-floor-light.cdb. #176 RESIDUAL is NOT this fix. It's a RUNTIME draw z-fight in the seam floor. Eliminated (evidence): NOT lighting (per-light cap + this both no-change), NOT membership (render cell 0x8A020164 stable 100% of 188k frames / 526 angles, res=None), NOT dat geometry (coplanar sweep empty at z=-6 floor incl. cell 0164). NEXT = RenderDoc pixel-history. Full handoff + DO-NOT-RETRY: docs/research/2026-07-06-176-seam-floor-zfight-handoff.md. Suites green: Core 2599 + 2 skip. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
92bb27c03b
commit
8cb3176daa
8 changed files with 366 additions and 11 deletions
|
|
@ -17,6 +17,17 @@ public sealed class LightManagerTests
|
|||
CellId = cellId,
|
||||
};
|
||||
|
||||
private static LightSource MakeDynamic(Vector3 pos, float range, uint cellId = 0)
|
||||
=> new LightSource
|
||||
{
|
||||
Kind = LightKind.Point,
|
||||
WorldPosition = pos,
|
||||
Range = range,
|
||||
IsLit = true,
|
||||
IsDynamic = true,
|
||||
CellId = cellId,
|
||||
};
|
||||
|
||||
[Fact]
|
||||
public void Register_Unregister_TracksList()
|
||||
{
|
||||
|
|
@ -307,6 +318,58 @@ public sealed class LightManagerTests
|
|||
Assert.Equal(a[0], b[0]);
|
||||
}
|
||||
|
||||
// ── SelectForCell — retail minimize_envcell_lighting (all dynamics on every cell) ──
|
||||
|
||||
[Fact]
|
||||
public void SelectForCell_AppliesAllDynamicLights_EvenOutOfReach()
|
||||
{
|
||||
// Retail enables the WHOLE dynamic subset for every cell (cdb-verified: the same
|
||||
// portal lights on every Facility Hub cell) — including ones that don't reach it,
|
||||
// since the shader's range cutoff zeroes those. Static lights still cull by reach.
|
||||
var snapshot = new[]
|
||||
{
|
||||
MakePoint(new Vector3(1, 0, 0), range: 5f), // 0: static, reaches
|
||||
MakeDynamic(new Vector3(100, 0, 0), range: 5f), // 1: dynamic, FAR (out of reach)
|
||||
MakeDynamic(new Vector3(2, 0, 0), range: 5f), // 2: dynamic, near
|
||||
MakePoint(new Vector3(50, 0, 0), range: 5f), // 3: static, far (out of reach)
|
||||
};
|
||||
Span<int> sel = stackalloc int[LightManager.MaxLightsPerObject];
|
||||
int n = LightManager.SelectForCell(snapshot, Vector3.Zero, radius: 1f, sel);
|
||||
|
||||
bool d1 = false, d2 = false, s0 = false, s3 = false;
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
if (sel[i] == 1) d1 = true;
|
||||
if (sel[i] == 2) d2 = true;
|
||||
if (sel[i] == 0) s0 = true;
|
||||
if (sel[i] == 3) s3 = true;
|
||||
}
|
||||
Assert.True(d1, "the FAR dynamic light must still be applied — retail enables all dynamics");
|
||||
Assert.True(d2, "the near dynamic light is applied");
|
||||
Assert.True(s0, "the near static light reaches the cell → selected");
|
||||
Assert.False(s3, "the far static light doesn't reach → not selected");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SelectForCell_SameDynamicSet_ForCellsFarApart_NoFlap()
|
||||
{
|
||||
// The stability retail has and we lacked: two cells far apart get the SAME dynamic
|
||||
// set. A per-cell sphere-overlap cull of dynamics (the old SelectForObject path) let
|
||||
// that set differ/flip as the flood shifted → the floor lighting FLAPPED (#176).
|
||||
var snapshot = new[]
|
||||
{
|
||||
MakeDynamic(new Vector3(0, 0, 0), range: 5f),
|
||||
MakeDynamic(new Vector3(100, 0, 0), range: 5f),
|
||||
};
|
||||
Span<int> a = stackalloc int[8];
|
||||
Span<int> b = stackalloc int[8];
|
||||
int na = LightManager.SelectForCell(snapshot, new Vector3(0, 0, 0), 1f, a);
|
||||
int nb = LightManager.SelectForCell(snapshot, new Vector3(500, 0, 0), 1f, b);
|
||||
|
||||
Assert.Equal(2, na); // both dynamics on the near cell
|
||||
Assert.Equal(2, nb); // both dynamics on the far cell too — identical, no flap
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// #176/#177 (2026-07-06) — the end-state pin, via the SHIPPED fix (visible-cell
|
||||
/// scoping, not "uncap"). Before: <c>BuildPointLightSnapshot</c> kept only the
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue