feat(physics): add differential flat BSP traversal

Port every current containment, overlap, walkable, and six-path moving-collision query to immutable integer-indexed assets behind a graph-authoritative referee. Exact synthetic, installed-DAT, complete-resolver, and zero-allocation gates prove bit-identical behavior before connected dual publication.

Co-authored-by: OpenAI Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-25 15:47:58 +02:00
parent 9cd42417a8
commit f7ff9f4eea
13 changed files with 3907 additions and 46 deletions

View file

@ -0,0 +1,226 @@
using System.Numerics;
namespace AcDream.Core.Physics;
/// <summary>
/// Temporary Slice I referee mode. Graph remains production-authoritative;
/// Flat is enabled only by differential tests until dual publication and the
/// connected shadow gate complete.
/// </summary>
internal enum CollisionTraversalMode
{
Graph,
Flat,
}
/// <summary>
/// Narrow representation switch used while the graph and flat collision
/// worlds coexist. I6 removes the graph branch after acceptance.
/// </summary>
internal static class CollisionTraversal
{
internal static bool HasCellContainment(
PhysicsDataCache cache,
CellPhysics cell)
{
if (cache.CollisionTraversalMode == CollisionTraversalMode.Flat)
{
FlatCellContainmentBsp flat = cell.FlatContainmentBsp ??
throw MissingFlat("cell containment");
return flat.RootIndex >= 0;
}
return cell.CellBSP?.Root is not null;
}
internal static bool HasPhysics(
PhysicsDataCache cache,
CellPhysics cell)
{
if (cache.CollisionTraversalMode == CollisionTraversalMode.Flat)
{
FlatPhysicsBsp flat = cell.FlatPhysicsBsp ??
throw MissingFlat("cell physics");
return flat.RootIndex >= 0;
}
return cell.BSP?.Root is not null;
}
internal static bool HasPhysics(
PhysicsDataCache cache,
GfxObjPhysics gfxObject)
{
if (cache.CollisionTraversalMode == CollisionTraversalMode.Flat)
{
FlatPhysicsBsp flat = gfxObject.FlatPhysicsBsp ??
throw MissingFlat("GfxObj physics");
return flat.RootIndex >= 0;
}
return gfxObject.BSP.Root is not null;
}
internal static FlatCollisionSphere RootBoundingSphere(
PhysicsDataCache cache,
CellPhysics cell)
{
if (cache.CollisionTraversalMode == CollisionTraversalMode.Flat)
{
FlatPhysicsBsp flat = cell.FlatPhysicsBsp ??
throw MissingFlat("cell physics");
return flat.Nodes[flat.RootIndex].BoundingSphere;
}
DatReaderWriter.Types.Sphere sphere = cell.BSP!.Root!.BoundingSphere;
return new FlatCollisionSphere(sphere.Origin, sphere.Radius);
}
internal static bool PointInsideCell(
PhysicsDataCache cache,
CellPhysics cell,
Vector3 localPoint)
{
if (cache.CollisionTraversalMode == CollisionTraversalMode.Flat)
{
FlatCellContainmentBsp flat = cell.FlatContainmentBsp ??
throw MissingFlat("cell containment");
return FlatBspQuery.PointInsideCellBsp(flat, localPoint);
}
return BSPQuery.PointInsideCellBsp(cell.CellBSP?.Root, localPoint);
}
internal static bool SphereIntersectsCell(
PhysicsDataCache cache,
CellPhysics cell,
Vector3 localCenter,
float radius)
{
if (cache.CollisionTraversalMode == CollisionTraversalMode.Flat)
{
FlatCellContainmentBsp flat = cell.FlatContainmentBsp ??
throw MissingFlat("cell containment");
return FlatBspQuery.SphereIntersectsCellBsp(
flat,
localCenter,
radius);
}
return BSPQuery.SphereIntersectsCellBsp(
cell.CellBSP?.Root,
localCenter,
radius);
}
internal static TransitionState FindCollisions(
PhysicsDataCache cache,
CellPhysics cell,
Transition transition,
Vector3 localSphereCenter,
float localSphereRadius,
bool hasLocalSphere1,
Vector3 localSphere1Center,
float localSphere1Radius,
Vector3 localCurrentCenter,
Vector3 localSpaceZ,
float scale,
Quaternion localToWorld,
PhysicsEngine? engine,
Vector3 worldOrigin)
{
if (cache.CollisionTraversalMode == CollisionTraversalMode.Flat)
{
FlatPhysicsBsp flat = cell.FlatPhysicsBsp ??
throw MissingFlat("cell physics");
return FlatBspQuery.FindCollisions(
flat,
transition,
localSphereCenter,
localSphereRadius,
hasLocalSphere1,
localSphere1Center,
localSphere1Radius,
localCurrentCenter,
localSpaceZ,
scale,
localToWorld,
engine,
worldOrigin);
}
return BSPQuery.FindCollisions(
cell.BSP?.Root,
cell.Resolved,
transition,
localSphereCenter,
localSphereRadius,
hasLocalSphere1,
localSphere1Center,
localSphere1Radius,
localCurrentCenter,
localSpaceZ,
scale,
localToWorld,
engine,
worldOrigin);
}
internal static TransitionState FindCollisions(
PhysicsDataCache cache,
GfxObjPhysics gfxObject,
Transition transition,
Vector3 localSphereCenter,
float localSphereRadius,
bool hasLocalSphere1,
Vector3 localSphere1Center,
float localSphere1Radius,
Vector3 localCurrentCenter,
Vector3 localSpaceZ,
float scale,
Quaternion localToWorld,
PhysicsEngine? engine,
Vector3 worldOrigin)
{
if (cache.CollisionTraversalMode == CollisionTraversalMode.Flat)
{
FlatPhysicsBsp flat = gfxObject.FlatPhysicsBsp ??
throw MissingFlat("GfxObj physics");
return FlatBspQuery.FindCollisions(
flat,
transition,
localSphereCenter,
localSphereRadius,
hasLocalSphere1,
localSphere1Center,
localSphere1Radius,
localCurrentCenter,
localSpaceZ,
scale,
localToWorld,
engine,
worldOrigin);
}
return BSPQuery.FindCollisions(
gfxObject.BSP.Root,
gfxObject.Resolved,
transition,
localSphereCenter,
localSphereRadius,
hasLocalSphere1,
localSphere1Center,
localSphere1Radius,
localCurrentCenter,
localSpaceZ,
scale,
localToWorld,
engine,
worldOrigin);
}
private static InvalidOperationException MissingFlat(string kind) =>
new(
$"Flat collision traversal requires a prepared {kind} asset. " +
"Gameplay must not fall back silently.");
}