checkpoint(render): preserve pre-overhaul investigation state
This commit is contained in:
parent
e880860291
commit
b3b7d922f1
45 changed files with 3168 additions and 619 deletions
|
|
@ -276,8 +276,7 @@ public sealed class LightManagerTests
|
|||
|
||||
LightSource[] expected = FullSortOracle(
|
||||
registered,
|
||||
Vector3.Zero,
|
||||
visibleCells: null);
|
||||
Vector3.Zero);
|
||||
manager.BuildPointLightSnapshot(Vector3.Zero);
|
||||
|
||||
Assert.Equal(expected, manager.PointSnapshot);
|
||||
|
|
@ -324,23 +323,15 @@ public sealed class LightManagerTests
|
|||
manager.Register(light);
|
||||
}
|
||||
|
||||
IReadOnlySet<uint>? visibleCells = scenario % 2 == 0
|
||||
? new HashSet<uint>
|
||||
{
|
||||
0xAAAA0101u,
|
||||
0xAAAA0103u,
|
||||
}
|
||||
: null;
|
||||
Vector3 player = new(
|
||||
random.Next(-4, 5),
|
||||
random.Next(-4, 5),
|
||||
random.Next(-2, 3));
|
||||
LightSource[] expected = FullSortOracle(
|
||||
registered,
|
||||
player,
|
||||
visibleCells);
|
||||
player);
|
||||
|
||||
manager.BuildPointLightSnapshot(player, visibleCells);
|
||||
manager.BuildPointLightSnapshot(player);
|
||||
|
||||
Assert.Equal(expected, manager.PointSnapshot);
|
||||
}
|
||||
|
|
@ -369,15 +360,12 @@ public sealed class LightManagerTests
|
|||
registered.Add(light);
|
||||
manager.Register(light);
|
||||
}
|
||||
IReadOnlySet<uint> visibleCells =
|
||||
new HashSet<uint> { fountainRoom, corridor };
|
||||
Vector3 player = new(4.25f, -1.5f, 0.7f);
|
||||
LightSource[] expected = FullSortOracle(
|
||||
registered,
|
||||
player,
|
||||
visibleCells);
|
||||
player);
|
||||
|
||||
manager.BuildPointLightSnapshot(player, visibleCells);
|
||||
manager.BuildPointLightSnapshot(player);
|
||||
|
||||
Assert.Equal(LightManager.MaxGlobalLights, manager.PointSnapshot.Count);
|
||||
Assert.Equal(expected, manager.PointSnapshot);
|
||||
|
|
@ -415,71 +403,11 @@ public sealed class LightManagerTests
|
|||
Assert.Equal(0, allocated);
|
||||
}
|
||||
|
||||
// ── Visible-cell scoping (A7.L1, 2026-07-09 — the Town Network starvation fix) ──
|
||||
// BuildPointLightSnapshot's player-nearest cap sorts by raw Euclidean distance,
|
||||
// which is not a reliable proxy for "same room" in a dense, maze-like hub: a
|
||||
// fixture on the other side of a wall can be geometrically closer than the
|
||||
// player's own room's torches. The Town Network fountain room (463 registered
|
||||
// fixtures, cap 128) went dark because far-denser, closer-in-a-straight-line
|
||||
// corridor fixtures won the cap over the room's own lights. Filtering candidacy
|
||||
// by the frame's actual visible-cell set (the render already computes this)
|
||||
// fixes it without touching the distance-sort anchor (still the PLAYER, per the
|
||||
// #176 correction — camera anchoring is what caused the earlier flicker).
|
||||
|
||||
[Fact]
|
||||
public void BuildPointLightSnapshot_VisibleCellScoping_RoomLightsSurviveOverEuclideanCloserInvisibleCell()
|
||||
public void BuildPointLightSnapshot_UsesAllResidentLights()
|
||||
{
|
||||
var mgr = new LightManager();
|
||||
|
||||
// A different, NOT-visible cell packed with fixtures that are, in raw
|
||||
// straight-line distance, closer to the player than the room's own
|
||||
// torches (e.g. a corridor on the other side of a wall).
|
||||
const uint otherCellId = 0xAAAA0102u;
|
||||
for (int i = 0; i < LightManager.MaxGlobalLights + 50; i++)
|
||||
mgr.Register(MakePoint(new Vector3(1f + i * 0.001f, 1f, 0), range: 5f, ownerId: (uint)(i + 1), cellId: otherCellId));
|
||||
|
||||
// The player's own room: a handful of torches, each FARTHER in raw
|
||||
// distance than every "other cell" fixture above, but the only cell
|
||||
// actually visible from the player's viewpoint this frame.
|
||||
const uint roomCellId = 0xAAAA0101u;
|
||||
var roomTorches = new LightSource[5];
|
||||
for (int i = 0; i < roomTorches.Length; i++)
|
||||
{
|
||||
roomTorches[i] = MakePoint(new Vector3(50f + i, 0, 0), range: 15f, cellId: roomCellId);
|
||||
mgr.Register(roomTorches[i]);
|
||||
}
|
||||
|
||||
var visibleCells = new HashSet<uint> { roomCellId };
|
||||
mgr.BuildPointLightSnapshot(playerWorldPos: Vector3.Zero, visibleCells);
|
||||
|
||||
foreach (var torch in roomTorches)
|
||||
Assert.Contains(torch, mgr.PointSnapshot);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BuildPointLightSnapshot_VisibleCellScoping_CellLessLightAlwaysIncluded()
|
||||
{
|
||||
// The viewer fill light (CellId==0) must survive scoping unconditionally —
|
||||
// retail's per-frame add_dynamic_light(&viewer_light, ...) is unconditional
|
||||
// (LightManager.UpdateViewerLight's doc comment).
|
||||
var mgr = new LightManager();
|
||||
var viewerFill = MakePoint(new Vector3(0, 0, 2), range: 15f, cellId: 0u);
|
||||
mgr.Register(viewerFill);
|
||||
var otherRoom = MakePoint(new Vector3(2, 0, 0), range: 5f, cellId: 0xBEEFu);
|
||||
mgr.Register(otherRoom);
|
||||
|
||||
var visibleCells = new HashSet<uint> { 0xF00Du }; // neither light's cell
|
||||
mgr.BuildPointLightSnapshot(Vector3.Zero, visibleCells);
|
||||
|
||||
Assert.Contains(viewerFill, mgr.PointSnapshot);
|
||||
Assert.DoesNotContain(otherRoom, mgr.PointSnapshot);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BuildPointLightSnapshot_NoVisibleCellsArg_UnscopedLegacyBehavior()
|
||||
{
|
||||
// Outdoor / no-clipRoot callers omit visibleCells — every registered lit
|
||||
// light stays a candidate, exactly the pre-A7.L1 behavior.
|
||||
// Retail walks the resident EnvCell registry. A camera-root transition
|
||||
// cannot make either resident cell stop contributing candidates.
|
||||
var mgr = new LightManager();
|
||||
mgr.Register(MakePoint(new Vector3(1, 0, 0), 5f, cellId: 0xAAAAu));
|
||||
mgr.Register(MakePoint(new Vector3(2, 0, 0), 5f, cellId: 0xBBBBu));
|
||||
|
|
@ -685,8 +613,7 @@ public sealed class LightManagerTests
|
|||
|
||||
private static LightSource[] FullSortOracle(
|
||||
IReadOnlyList<LightSource> registered,
|
||||
Vector3 player,
|
||||
IReadOnlySet<uint>? visibleCells)
|
||||
Vector3 player)
|
||||
{
|
||||
var ranked = new List<OracleRank>();
|
||||
for (int index = 0; index < registered.Count; index++)
|
||||
|
|
@ -694,13 +621,6 @@ public sealed class LightManagerTests
|
|||
LightSource light = registered[index];
|
||||
if (!light.IsLit || light.Kind == LightKind.Directional)
|
||||
continue;
|
||||
if (visibleCells is not null
|
||||
&& light.CellId != 0
|
||||
&& !visibleCells.Contains(light.CellId))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ranked.Add(new OracleRank(
|
||||
light,
|
||||
Vector3.DistanceSquared(light.WorldPosition, player)));
|
||||
|
|
|
|||
|
|
@ -21,7 +21,9 @@ namespace AcDream.Core.Tests.Physics;
|
|||
public sealed class CellTransitFindTransitCellsBoxTests
|
||||
{
|
||||
private static CellPhysics MakeCellWithPortalAtRightWall(
|
||||
Matrix4x4 worldTransform, uint otherCellId, ushort flags)
|
||||
Matrix4x4 worldTransform,
|
||||
uint otherCellId,
|
||||
ushort flags)
|
||||
{
|
||||
// Portal poly at local x=2.5 (right wall), normal +X. Same shape as
|
||||
// CellTransitFindTransitCellsSphereTests' fixture, so the sphere-only
|
||||
|
|
@ -142,6 +144,47 @@ public sealed class CellTransitFindTransitCellsBoxTests
|
|||
Assert.DoesNotContain(0xA9B40101u, endToEnd);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StaticPartArrayCrossesExteriorPortal_KeepsOutdoorCells()
|
||||
{
|
||||
// CPhysicsObj::calc_cross_cells_static sets do_not_load_cells before
|
||||
// entering find_bbox_cell_list, but retail's bbox worklist returns
|
||||
// the CELLARRAY directly. The post-pass prune at CObjCell::
|
||||
// find_cell_list+0x18E belongs only to that separate sphere route.
|
||||
// This is the cathedral-ramp shape: an indoor static box genuinely
|
||||
// reaches an exterior portal and must remain registered in outdoor
|
||||
// shadow cells even when the seed's stab list is empty.
|
||||
var cache = new PhysicsDataCache();
|
||||
const uint seedCell = 0xF4180112u;
|
||||
cache.RegisterCellStructForTest(
|
||||
seedCell,
|
||||
MakeCellWithPortalAtRightWall(
|
||||
Matrix4x4.Identity,
|
||||
otherCellId: 0xFFFF,
|
||||
flags: 0));
|
||||
|
||||
var partWorldPos = new Vector3(2.0f, 0f, 2.5f);
|
||||
var sphere = new Sphere { Origin = partWorldPos, Radius = 0.7f };
|
||||
var box = new[]
|
||||
{
|
||||
MakeBox(
|
||||
new Vector3(-0.7f),
|
||||
new Vector3(0.7f),
|
||||
partWorldPos,
|
||||
Quaternion.Identity),
|
||||
};
|
||||
|
||||
IReadOnlyList<uint> cells = CellTransit.BuildShadowCellSetFromParts(
|
||||
cache,
|
||||
seedCell,
|
||||
box,
|
||||
new[] { sphere },
|
||||
isStatic: true);
|
||||
|
||||
Assert.Contains(seedCell, cells);
|
||||
Assert.Contains(cells, id => (id & 0xFFFFu) is >= 1u and <= 64u);
|
||||
}
|
||||
|
||||
// ── D3.2: inverse guard — a box that DOES cross admits, unchanged ──────
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue