refactor(physics): remove parsed collision graphs

Publish prepared GfxObj, Setup, CellStruct, and EnvCell collision records without retaining their parsed DAT BSP, polygon, vertex, or shape graphs. Strip temporary physics bundles at stable world commit, keep graph traversal only as an explicit test/tooling oracle, and report graph residency from actual retained fields.

Validated by 115 focused collision/streaming tests, a zero-warning Release build, and 8,413 passing Release tests with five pre-existing skips.

Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-25 18:21:19 +02:00
parent feb80a67d9
commit 82f8d4f82e
18 changed files with 923 additions and 104 deletions

View file

@ -402,16 +402,19 @@ public sealed class LandblockPhysicsPublisher
{
long cacheStarted = Stopwatch.GetTimestamp();
uint gfxObjectId = publication.GfxObjectIds[publication.GfxCursor];
if (datBundle.GfxObjs.TryGetValue(gfxObjectId, out var gfx))
if (publication.Build.Collisions?.GfxObjs.TryGetValue(
gfxObjectId,
out FlatGfxObjCollisionAsset? prepared) == true)
{
FlatGfxObjCollisionAsset? prepared = null;
publication.Build.Collisions?.GfxObjs.TryGetValue(
gfxObjectId,
out prepared);
_physicsDataCache.CacheGfxObj(
gfxObjectId,
gfx,
prepared);
_physicsDataCache.CacheGfxObj(gfxObjectId, prepared);
}
else if (datBundle.GfxObjs.TryGetValue(
gfxObjectId,
out var source))
{
// Graph-oracle fixture seam. Production near builds always
// carry the strict prepared closure.
_physicsDataCache.CacheGfxObj(gfxObjectId, source);
}
publication.GfxCursor++;
_gfxCacheTicks += Stopwatch.GetTimestamp() - cacheStarted;
@ -420,13 +423,16 @@ public sealed class LandblockPhysicsPublisher
{
uint setupId =
publication.SetupObjectIds[publication.SetupCursor];
if (datBundle.Setups.TryGetValue(setupId, out var setup))
{
FlatSetupCollision? prepared = null;
publication.Build.Collisions?.Setups.TryGetValue(
if (publication.Build.Collisions?.Setups.TryGetValue(
setupId,
out prepared);
_physicsDataCache.CacheSetup(setupId, setup, prepared);
out FlatSetupCollision? prepared) == true)
{
_physicsDataCache.CacheSetup(setupId, prepared);
}
else if (datBundle.Setups.TryGetValue(setupId, out var source))
{
// Graph-oracle fixture seam only.
_physicsDataCache.CacheSetup(setupId, source);
}
publication.SetupCursor++;
}
@ -505,8 +511,97 @@ public sealed class LandblockPhysicsPublisher
LoadedLandblock landblock = publication.Build.Landblock;
uint envCellId =
(landblock.LandblockId & 0xFFFF0000u) | (0x0100u + offset);
if (!datBundle.EnvCells.TryGetValue(envCellId, out var envCell)
|| envCell.EnvironmentId == 0
if (!datBundle.EnvCells.TryGetValue(envCellId, out var envCell))
return;
if (publication.Build.Collisions is not { } collisions)
{
PublishGraphFixtureCell(
publication,
datBundle,
envCellId,
envCell);
return;
}
if (!collisions.CellStructures.TryGetValue(
envCellId,
out FlatCellStructureCollisionAsset? preparedStructure)
|| !collisions.EnvCells.TryGetValue(
envCellId,
out FlatEnvCellTopology? preparedTopology))
{
return;
}
Quaternion rotation = envCell.Position.Orientation;
Vector3 cellOriginWorld = envCell.Position.Origin + publication.Origin;
Matrix4x4 physicsCellTransform =
Matrix4x4.CreateFromQuaternion(rotation)
* Matrix4x4.CreateTranslation(cellOriginWorld);
_physicsDataCache.CacheCellStruct(
envCellId,
envCell,
physicsCellTransform,
preparedStructure,
preparedTopology);
FlatPolygonTable physicsPolygons =
preparedStructure.PhysicsBsp.PolygonTable;
var portalPlanes = new List<PortalPlane>();
FlatPolygonTable portalPolygons = preparedStructure.PortalPolygons;
for (int portalIndex = 0;
portalIndex < preparedTopology.Portals.Length;
portalIndex++)
{
FlatEnvCellPortal portal = preparedTopology.Portals[portalIndex];
if ((uint)portal.PolygonIndex
>= (uint)portalPolygons.Polygons.Length)
{
continue;
}
FlatCollisionPolygon polygon =
portalPolygons.Polygons[portal.PolygonIndex];
if (polygon.VertexRange.Count < 3)
continue;
var portalVertices = new Vector3[polygon.VertexRange.Count];
for (int index = 0; index < portalVertices.Length; index++)
{
Vector3 local = portalPolygons.Vertices[
polygon.VertexRange.Start + index];
portalVertices[index] =
Vector3.Transform(local, rotation) + cellOriginWorld;
}
portalPlanes.Add(PortalPlane.FromVertices(
portalVertices.AsSpan(),
portal.OtherCellId,
envCellId & 0xFFFFu,
(ushort)portal.Flags));
}
publication.CellSurfaces.Add(new CellSurface(
envCellId,
physicsPolygons,
rotation,
cellOriginWorld));
publication.PortalPlanes.AddRange(portalPlanes);
}
/// <summary>
/// Parsed-graph publication is retained only for unit/conformance fixtures
/// constructed without a prepared collision closure. Production cannot
/// reach this branch because near-build admission requires that closure.
/// </summary>
private void PublishGraphFixtureCell(
LandblockPhysicsPublication publication,
PhysicsDatBundle datBundle,
uint envCellId,
DatReaderWriter.DBObjs.EnvCell envCell)
{
if (envCell.EnvironmentId == 0
|| !datBundle.Environments.TryGetValue(
0x0D000000u | envCell.EnvironmentId,
out var environment)
@ -518,51 +613,33 @@ public sealed class LandblockPhysicsPublisher
}
Quaternion rotation = envCell.Position.Orientation;
Vector3 cellOriginWorld = envCell.Position.Origin + publication.Origin;
Vector3 cellOriginWorld =
envCell.Position.Origin + publication.Origin;
Matrix4x4 physicsCellTransform =
Matrix4x4.CreateFromQuaternion(rotation)
* Matrix4x4.CreateTranslation(cellOriginWorld);
FlatCellStructureCollisionAsset? preparedStructure = null;
FlatEnvCellTopology? preparedTopology = null;
publication.Build.Collisions?.CellStructures.TryGetValue(
envCellId,
out preparedStructure);
publication.Build.Collisions?.EnvCells.TryGetValue(
envCellId,
out preparedTopology);
_physicsDataCache.CacheCellStruct(
envCellId,
envCell,
cellStruct,
physicsCellTransform,
preparedStructure,
preparedTopology);
physicsCellTransform);
var worldVertices = new Dictionary<ushort, Vector3>(
cellStruct.VertexArray.Vertices.Count);
foreach ((ushort vertexId, var vertex) in
cellStruct.VertexArray.Vertices)
{
Vector3 worldPosition =
worldVertices[vertexId] =
Vector3.Transform(vertex.Origin, rotation)
+ cellOriginWorld;
worldVertices[(ushort)vertexId] = worldPosition;
}
var polygonVertexIds = new List<List<short>>(
cellStruct.PhysicsPolygons.Count);
foreach (var polygon in cellStruct.PhysicsPolygons.Values)
{
var vertexIds = new List<short>(polygon.VertexIds.Count);
foreach (short vertexId in polygon.VertexIds)
vertexIds.Add(vertexId);
polygonVertexIds.Add(vertexIds);
}
polygonVertexIds.Add([.. polygon.VertexIds]);
var portalPlanes = new List<PortalPlane>();
// CellPortal.PolygonId indexes rendering Polygons, not
// PhysicsPolygons (ACViewer EnvCell.find_transit_cells).
foreach (var portal in envCell.CellPortals)
{
if (!cellStruct.Polygons.TryGetValue(
@ -589,7 +666,7 @@ public sealed class LandblockPhysicsPublisher
continue;
portalPlanes.Add(PortalPlane.FromVertices(
portalVertices.AsSpan(),
portalVertices,
portal.OtherCellId,
envCellId & 0xFFFFu,
(ushort)portal.Flags));
@ -689,17 +766,26 @@ public sealed class LandblockPhysicsPublisher
LogMultipartRegistration(landblock, entity, bspShapes);
}
SetupPhysics? setup =
_physicsDataCache.GetSetup(entity.SourceGfxObjOrSetupId);
FlatSetupCollision? setup =
_physicsDataCache.GetFlatSetup(entity.SourceGfxObjOrSetupId);
if (setup is null
&& _physicsDataCache.GetSetup(
entity.SourceGfxObjOrSetupId) is { } graphSetup)
{
// Graph-oracle fixture seam only. Production Setup publication is
// already package-flat and never allocates this conversion.
setup = FlatCollisionAssetBuilder.FlattenSetup(graphSetup);
}
if (setup is not null && entityBspCount == 0)
{
float scale = entity.Scale > 0f ? entity.Scale : 1f;
var setupShapes = new List<ShadowShape>();
for (int cylinderIndex = 0;
cylinderIndex < setup.CylSpheres.Count;
cylinderIndex < setup.Cylinders.Length;
cylinderIndex++)
{
CylSphere cylinder = setup.CylSpheres[cylinderIndex];
FlatCollisionCylinder cylinder =
setup.Cylinders[cylinderIndex];
float radius = cylinder.Radius * scale;
float baseHeight = cylinder.Height > 0f
? cylinder.Height
@ -719,13 +805,14 @@ public sealed class LandblockPhysicsPublisher
CylHeight: height));
}
if (setup.CylSpheres.Count == 0)
if (setup.Cylinders.Length == 0)
{
for (int sphereIndex = 0;
sphereIndex < setup.Spheres.Count;
sphereIndex < setup.Spheres.Length;
sphereIndex++)
{
Sphere sphere = setup.Spheres[sphereIndex];
FlatCollisionSphere sphere =
setup.Spheres[sphereIndex];
if (sphere.Radius <= 0f)
continue;
@ -746,8 +833,8 @@ public sealed class LandblockPhysicsPublisher
}
}
if (setup.CylSpheres.Count == 0
&& setup.Spheres.Count == 0
if (setup.Cylinders.Length == 0
&& setup.Spheres.Length == 0
&& setup.Radius > 0f)
{
float radius = setup.Radius * scale;