fix(physics): restore retail cell availability semantics

This commit is contained in:
Erik 2026-07-31 14:22:45 +02:00
parent d3c0d9ec0e
commit 7716c2ee89
14 changed files with 393 additions and 62 deletions

View file

@ -82,8 +82,8 @@ public sealed class PhysicsDataCache
/// <summary>
/// The unified cell graph (UCG): the active id-&gt;cell resolver and registry.
/// Populated unconditionally in <see cref="CacheCellStruct"/> — BEFORE the
/// idempotency + null-BSP guards, so BSP-less cells are registered too — and
/// Populated unconditionally in <see cref="CacheCellStruct"/> so BSP-less
/// authored cells are registered too, and
/// consumed across the engine: the player render/lighting root
/// (<c>CellGraph.CurrCell</c>, written at the player chokepoint
/// <c>PhysicsEngine.UpdatePlayerCurrCell</c> and read by the renderer), the
@ -364,9 +364,11 @@ public sealed class PhysicsDataCache
}
/// <summary>
/// Extract and cache the physics BSP + polygon data from a CellStruct
/// (indoor room geometry). No-ops if the id is already cached or the
/// CellStruct has no physics BSP.
/// Extract and cache the authored CellStruct payload (indoor room
/// geometry), including cells whose physics or containment BSP has a null
/// root. Retail keeps those loaded cells distinct from an unavailable
/// visible-cell lookup; the null containment root is universally inside.
/// No-ops only when the id is already cached.
/// </summary>
public void CacheCellStruct(
uint envCellId,
@ -414,8 +416,7 @@ public sealed class PhysicsDataCache
return;
}
// UCG Stage 1: register in the unified graph for ALL cells — before the
// idempotency + null-BSP guards below, so BSP-less cells are still included.
// UCG Stage 1: register in the unified graph for every authored cell.
if (!CellGraph.Contains(envCellId))
{
CellGraph.Add(UcgEnvCell.FromDat(
@ -427,11 +428,12 @@ public sealed class PhysicsDataCache
}
if (_cellStruct.ContainsKey(envCellId)) return;
if (cellStruct.PhysicsBSP?.Root is null) return;
Matrix4x4.Invert(worldTransform, out var inverseTransform);
var resolved = ResolvePolygons(cellStruct.PhysicsPolygons, cellStruct.VertexArray);
var resolved = cellStruct.PhysicsPolygons is null
? new Dictionary<ushort, ResolvedPolygon>()
: ResolvePolygons(cellStruct.PhysicsPolygons, cellStruct.VertexArray);
// Visible polygons — portals reference these (NOT PhysicsPolygons).
var portalPolygons = ResolvePolygons(cellStruct.Polygons, cellStruct.VertexArray);
@ -628,11 +630,9 @@ public sealed class PhysicsDataCache
preparedTopology));
}
// Preserve CacheCellStruct's existing distinction: BSP-less cells
// participate in the cell graph but do not masquerade as hydrated
// collision cells.
if (preparedStructure.PhysicsBsp.RootIndex < 0)
return;
// The prepared structure itself is the loaded CellStruct payload.
// Empty physics and containment roots remain meaningful authored
// values; neither means that the cell is unavailable.
if (_cellStruct.ContainsKey(envCellId))
return;
@ -1023,8 +1023,9 @@ public sealed class CellPhysics
/// (point-in-cell tests). Separate tree from <see cref="BSP"/>
/// (collision) and from the renderer's drawing-BSP.
/// Source: <c>cellStruct.CellBSP</c> at cache time.
/// Nullable: cells without a CellBSP cannot participate in portal
/// containment and are skipped by <see cref="CellTransit"/>.
/// A nullable root is an authored, universally-inside containment tree.
/// Cell availability is represented by presence of this
/// <see cref="CellPhysics"/> record, not by root presence.
/// </summary>
public DatReaderWriter.Types.CellBSPTree? CellBSP { get; init; }