fix(physics): restore retail cell availability semantics
This commit is contained in:
parent
d3c0d9ec0e
commit
7716c2ee89
14 changed files with 393 additions and 62 deletions
|
|
@ -577,10 +577,12 @@ public static class CellTransit
|
|||
else
|
||||
{
|
||||
AddAllOutsideCells(worldSpheres, sphereCount, seedCellId, blockOrigin, candidates);
|
||||
// Outdoor seeds always walk: retail's null-CLandCell case is
|
||||
// "landblock not loaded at all", where our per-cell building
|
||||
// lookups below come back null anyway (documented adaptation).
|
||||
seedLoaded = true;
|
||||
// Retail preserves the outside-cell additions above but skips
|
||||
// the complete growing-array transit walk when GetVisible cannot
|
||||
// resolve the ACTIVE seed CLandCell (0052b50e, 0052b576). A cached
|
||||
// building alone must not promote an object through an unavailable
|
||||
// landcell; the normal reflood after terrain publication retries.
|
||||
seedLoaded = cache.CellGraph.GetVisible(seedCellId) is not null;
|
||||
}
|
||||
|
||||
if (seedLoaded)
|
||||
|
|
@ -664,11 +666,9 @@ public static class CellTransit
|
|||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// acdream adaptation (matches <see cref="FindCellList"/> at line 518): a cell
|
||||
/// with no hydrated <see cref="CellPhysics.CellBSP"/> cannot run
|
||||
/// <c>point_in_cell</c>, so it is treated as NOT containing the point (skipped),
|
||||
/// rather than letting <see cref="BSPQuery.PointInsideCellBsp"/>'s null-node
|
||||
/// "inside" default make it spuriously claim every point.
|
||||
/// A missing <see cref="CellPhysics"/> record is unavailable and skipped.
|
||||
/// A loaded record whose authored containment root is null retains retail's
|
||||
/// <see cref="BSPQuery.PointInsideCellBsp"/> universal-inside base case.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public static uint FindVisibleChildCell(
|
||||
|
|
@ -705,8 +705,8 @@ public static class CellTransit
|
|||
/// <summary>
|
||||
/// <c>CEnvCell::point_in_cell</c> (cell-BSP vtable[0x84]) against a world point:
|
||||
/// transform to the cell's local frame, then <see cref="BSPQuery.PointInsideCellBsp"/>.
|
||||
/// A cell with no hydrated <see cref="CellPhysics.CellBSP"/> returns false (see
|
||||
/// <see cref="FindVisibleChildCell"/>'s adaptation note).
|
||||
/// A missing cell payload returns false; a loaded payload with a null root
|
||||
/// returns true through the retail BSP base case.
|
||||
/// </summary>
|
||||
private static bool PointInCell(
|
||||
PhysicsDataCache cache,
|
||||
|
|
|
|||
|
|
@ -25,9 +25,14 @@ internal static class CollisionTraversal
|
|||
{
|
||||
if (UseFlat(cache))
|
||||
{
|
||||
FlatCellContainmentBsp flat = cell.FlatContainmentBsp ??
|
||||
// Availability is the authored CellStruct payload, not the
|
||||
// containment root. Retail's loaded BSPTREE may have a null
|
||||
// root; BSPNODE::point_inside_cell_bsp treats that base case as
|
||||
// universally inside. A missing flat payload is still a broken
|
||||
// production publication and must fail loudly.
|
||||
_ = cell.FlatContainmentBsp ??
|
||||
throw MissingFlat("cell containment");
|
||||
bool flatAuthorityResult = flat.RootIndex >= 0;
|
||||
const bool flatAuthorityResult = true;
|
||||
CollisionShadowVerifier? flatShadow = cache.CollisionShadow;
|
||||
if (flatShadow is null ||
|
||||
!flatShadow.TrySample(out long flatAuthoritySample))
|
||||
|
|
@ -38,7 +43,7 @@ internal static class CollisionTraversal
|
|||
flatShadow.BeginGraphPass();
|
||||
try
|
||||
{
|
||||
graphRefereeResult = cell.CellBSP?.Root is not null;
|
||||
graphRefereeResult = true;
|
||||
}
|
||||
catch (Exception fault)
|
||||
{
|
||||
|
|
@ -74,15 +79,16 @@ internal static class CollisionTraversal
|
|||
|
||||
CollisionShadowVerifier? shadow = cache.CollisionShadow;
|
||||
if (shadow is null || !shadow.TrySample(out long sample))
|
||||
return cell.CellBSP?.Root is not null;
|
||||
return true;
|
||||
|
||||
bool flatResult = false;
|
||||
Exception? flatFault = null;
|
||||
shadow.BeginFlatPass();
|
||||
try
|
||||
{
|
||||
flatResult = (cell.FlatContainmentBsp ??
|
||||
throw MissingFlat("cell containment")).RootIndex >= 0;
|
||||
_ = cell.FlatContainmentBsp ??
|
||||
throw MissingFlat("cell containment");
|
||||
flatResult = true;
|
||||
}
|
||||
catch (Exception fault)
|
||||
{
|
||||
|
|
@ -93,7 +99,7 @@ internal static class CollisionTraversal
|
|||
shadow.EndFlatPass();
|
||||
}
|
||||
|
||||
bool graphResult = cell.CellBSP?.Root is not null;
|
||||
const bool graphResult = true;
|
||||
if (flatFault is null)
|
||||
{
|
||||
shadow.RecordBoolean(
|
||||
|
|
|
|||
|
|
@ -82,8 +82,8 @@ public sealed class PhysicsDataCache
|
|||
|
||||
/// <summary>
|
||||
/// The unified cell graph (UCG): the active id->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; }
|
||||
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ namespace AcDream.Core.World.Cells;
|
|||
/// <summary>
|
||||
/// The unified cell graph: the active, authoritative id->cell resolver and registry.
|
||||
/// Populated unconditionally from
|
||||
/// <see cref="AcDream.Core.Physics.PhysicsDataCache.CacheCellStruct"/> (before its
|
||||
/// idempotency + null-BSP guards, so BSP-less cells are included) and consumed across
|
||||
/// <see cref="AcDream.Core.Physics.PhysicsDataCache.CacheCellStruct"/> (including
|
||||
/// authored cells with null physics or containment roots) and consumed across
|
||||
/// the engine: <see cref="GetVisible"/> resolves any cell id, <see cref="CurrCell"/> is
|
||||
/// the player render/lighting root, <see cref="FindVisibleChildCell"/> resolves the
|
||||
/// 3rd-person camera cell, and <see cref="TryGetTerrainOrigin"/> supplies the block-local
|
||||
|
|
|
|||
|
|
@ -10,7 +10,11 @@ namespace AcDream.Core.World.Cells;
|
|||
/// <summary>Indoor room cell. Retail anchor: CEnvCell (acclient.h:32072).</summary>
|
||||
public sealed class EnvCell : ObjCell
|
||||
{
|
||||
/// <summary>Cell-containment BSP (retail CellStruct.CellBSP). Null => AABB fallback.</summary>
|
||||
/// <summary>
|
||||
/// Cell-containment BSP (retail CellStruct.CellBSP). A present tree with a
|
||||
/// null root is universally inside; an absent test/tooling payload uses the
|
||||
/// legacy AABB fallback.
|
||||
/// </summary>
|
||||
public CellBSPTree? ContainmentBsp { get; }
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -37,7 +41,7 @@ public sealed class EnvCell : ObjCell
|
|||
var local = Vector3.Transform(worldPoint, InverseWorldTransform);
|
||||
if (FlatContainmentBsp is not null)
|
||||
return FlatBspQuery.PointInsideCellBsp(FlatContainmentBsp, local);
|
||||
if (ContainmentBsp?.Root is not null)
|
||||
if (ContainmentBsp is not null)
|
||||
return BSPQuery.PointInsideCellBsp(ContainmentBsp.Root, local); // BSPQuery.cs:1034
|
||||
return local.X >= LocalBoundsMin.X && local.X <= LocalBoundsMax.X
|
||||
&& local.Y >= LocalBoundsMin.Y && local.Y <= LocalBoundsMax.Y
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue