Carry one immutable prepared collision closure with each accepted near-tier generation and install graph plus flat views through the same retained publication receipt. Apply the same strict package-only rule to live entities, add exact sampled graph-authoritative comparison artifacts and lifecycle counters, and prove cancellation, demotion, rehydrate, revisit, teardown, reconnect, and the nine-stop route with 14,064 zero-mismatch samples. Co-authored-by: OpenAI Codex <codex@openai.com>
653 lines
18 KiB
C#
653 lines
18 KiB
C#
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 (UseFlat(cache))
|
|
{
|
|
FlatCellContainmentBsp flat = cell.FlatContainmentBsp ??
|
|
throw MissingFlat("cell containment");
|
|
return flat.RootIndex >= 0;
|
|
}
|
|
|
|
CollisionShadowVerifier? shadow = cache.CollisionShadow;
|
|
if (shadow is null || !shadow.TrySample(out long sample))
|
|
return cell.CellBSP?.Root is not null;
|
|
|
|
bool flatResult = false;
|
|
Exception? flatFault = null;
|
|
shadow.BeginFlatPass();
|
|
try
|
|
{
|
|
flatResult = (cell.FlatContainmentBsp ??
|
|
throw MissingFlat("cell containment")).RootIndex >= 0;
|
|
}
|
|
catch (Exception fault)
|
|
{
|
|
flatFault = fault;
|
|
}
|
|
finally
|
|
{
|
|
shadow.EndFlatPass();
|
|
}
|
|
|
|
bool graphResult = cell.CellBSP?.Root is not null;
|
|
if (flatFault is null)
|
|
{
|
|
shadow.RecordBoolean(
|
|
sample,
|
|
"HasCellContainment",
|
|
cell.SourceId,
|
|
graphResult,
|
|
flatResult,
|
|
string.Empty);
|
|
}
|
|
else
|
|
{
|
|
shadow.RecordFault(
|
|
sample,
|
|
"HasCellContainment",
|
|
cell.SourceId,
|
|
flatFault,
|
|
string.Empty);
|
|
}
|
|
return graphResult;
|
|
}
|
|
|
|
internal static bool HasPhysics(
|
|
PhysicsDataCache cache,
|
|
CellPhysics cell)
|
|
{
|
|
if (UseFlat(cache))
|
|
{
|
|
FlatPhysicsBsp flat = cell.FlatPhysicsBsp ??
|
|
throw MissingFlat("cell physics");
|
|
return flat.RootIndex >= 0;
|
|
}
|
|
|
|
CollisionShadowVerifier? shadow = cache.CollisionShadow;
|
|
if (shadow is null || !shadow.TrySample(out long sample))
|
|
return cell.BSP?.Root is not null;
|
|
|
|
bool flatResult = false;
|
|
Exception? flatFault = null;
|
|
shadow.BeginFlatPass();
|
|
try
|
|
{
|
|
flatResult = (cell.FlatPhysicsBsp ??
|
|
throw MissingFlat("cell physics")).RootIndex >= 0;
|
|
}
|
|
catch (Exception fault)
|
|
{
|
|
flatFault = fault;
|
|
}
|
|
finally
|
|
{
|
|
shadow.EndFlatPass();
|
|
}
|
|
|
|
bool graphResult = cell.BSP?.Root is not null;
|
|
if (flatFault is null)
|
|
{
|
|
shadow.RecordBoolean(
|
|
sample,
|
|
"HasCellPhysics",
|
|
cell.SourceId,
|
|
graphResult,
|
|
flatResult,
|
|
string.Empty);
|
|
}
|
|
else
|
|
{
|
|
shadow.RecordFault(
|
|
sample,
|
|
"HasCellPhysics",
|
|
cell.SourceId,
|
|
flatFault,
|
|
string.Empty);
|
|
}
|
|
return graphResult;
|
|
}
|
|
|
|
internal static bool HasPhysics(
|
|
PhysicsDataCache cache,
|
|
GfxObjPhysics gfxObject)
|
|
{
|
|
if (UseFlat(cache))
|
|
{
|
|
FlatPhysicsBsp flat = gfxObject.FlatPhysicsBsp ??
|
|
throw MissingFlat("GfxObj physics");
|
|
return flat.RootIndex >= 0;
|
|
}
|
|
|
|
CollisionShadowVerifier? shadow = cache.CollisionShadow;
|
|
if (shadow is null || !shadow.TrySample(out long sample))
|
|
return gfxObject.BSP.Root is not null;
|
|
|
|
bool flatResult = false;
|
|
Exception? flatFault = null;
|
|
shadow.BeginFlatPass();
|
|
try
|
|
{
|
|
flatResult = (gfxObject.FlatPhysicsBsp ??
|
|
throw MissingFlat("GfxObj physics")).RootIndex >= 0;
|
|
}
|
|
catch (Exception fault)
|
|
{
|
|
flatFault = fault;
|
|
}
|
|
finally
|
|
{
|
|
shadow.EndFlatPass();
|
|
}
|
|
|
|
bool graphResult = gfxObject.BSP.Root is not null;
|
|
if (flatFault is null)
|
|
{
|
|
shadow.RecordBoolean(
|
|
sample,
|
|
"HasGfxObjPhysics",
|
|
gfxObject.SourceId,
|
|
graphResult,
|
|
flatResult,
|
|
string.Empty);
|
|
}
|
|
else
|
|
{
|
|
shadow.RecordFault(
|
|
sample,
|
|
"HasGfxObjPhysics",
|
|
gfxObject.SourceId,
|
|
flatFault,
|
|
string.Empty);
|
|
}
|
|
return graphResult;
|
|
}
|
|
|
|
internal static FlatCollisionSphere RootBoundingSphere(
|
|
PhysicsDataCache cache,
|
|
CellPhysics cell)
|
|
{
|
|
if (UseFlat(cache))
|
|
{
|
|
FlatPhysicsBsp flat = cell.FlatPhysicsBsp ??
|
|
throw MissingFlat("cell physics");
|
|
return flat.Nodes[flat.RootIndex].BoundingSphere;
|
|
}
|
|
|
|
DatReaderWriter.Types.Sphere sphere = cell.BSP!.Root!.BoundingSphere;
|
|
var graphResult =
|
|
new FlatCollisionSphere(sphere.Origin, sphere.Radius);
|
|
CollisionShadowVerifier? shadow = cache.CollisionShadow;
|
|
if (shadow is null || !shadow.TrySample(out long sample))
|
|
return graphResult;
|
|
|
|
try
|
|
{
|
|
shadow.BeginFlatPass();
|
|
FlatPhysicsBsp flat = cell.FlatPhysicsBsp ??
|
|
throw MissingFlat("cell physics");
|
|
FlatCollisionSphere flatResult =
|
|
flat.Nodes[flat.RootIndex].BoundingSphere;
|
|
shadow.EndFlatPass();
|
|
shadow.RecordSphere(
|
|
sample,
|
|
"RootBoundingSphere",
|
|
cell.SourceId,
|
|
graphResult,
|
|
flatResult);
|
|
}
|
|
catch (Exception fault)
|
|
{
|
|
if (shadow.IsFlatPass)
|
|
shadow.EndFlatPass();
|
|
shadow.RecordFault(
|
|
sample,
|
|
"RootBoundingSphere",
|
|
cell.SourceId,
|
|
fault,
|
|
string.Empty);
|
|
}
|
|
return graphResult;
|
|
}
|
|
|
|
internal static bool PointInsideCell(
|
|
PhysicsDataCache cache,
|
|
CellPhysics cell,
|
|
Vector3 localPoint)
|
|
{
|
|
if (UseFlat(cache))
|
|
{
|
|
FlatCellContainmentBsp flat = cell.FlatContainmentBsp ??
|
|
throw MissingFlat("cell containment");
|
|
return FlatBspQuery.PointInsideCellBsp(flat, localPoint);
|
|
}
|
|
|
|
CollisionShadowVerifier? shadow = cache.CollisionShadow;
|
|
if (shadow is null || !shadow.TrySample(out long sample))
|
|
return BSPQuery.PointInsideCellBsp(
|
|
cell.CellBSP?.Root,
|
|
localPoint);
|
|
|
|
bool flatResult = false;
|
|
Exception? flatFault = null;
|
|
shadow.BeginFlatPass();
|
|
try
|
|
{
|
|
flatResult = FlatBspQuery.PointInsideCellBsp(
|
|
cell.FlatContainmentBsp ??
|
|
throw MissingFlat("cell containment"),
|
|
localPoint);
|
|
}
|
|
catch (Exception fault)
|
|
{
|
|
flatFault = fault;
|
|
}
|
|
finally
|
|
{
|
|
shadow.EndFlatPass();
|
|
}
|
|
bool graphResult = BSPQuery.PointInsideCellBsp(
|
|
cell.CellBSP?.Root,
|
|
localPoint);
|
|
string input = CollisionShadowVerifier.FormatInput(
|
|
localPoint,
|
|
0f);
|
|
if (flatFault is null)
|
|
{
|
|
shadow.RecordBoolean(
|
|
sample,
|
|
"PointInsideCell",
|
|
cell.SourceId,
|
|
graphResult,
|
|
flatResult,
|
|
input);
|
|
}
|
|
else
|
|
{
|
|
shadow.RecordFault(
|
|
sample,
|
|
"PointInsideCell",
|
|
cell.SourceId,
|
|
flatFault,
|
|
input);
|
|
}
|
|
return graphResult;
|
|
}
|
|
|
|
internal static bool SphereIntersectsCell(
|
|
PhysicsDataCache cache,
|
|
CellPhysics cell,
|
|
Vector3 localCenter,
|
|
float radius)
|
|
{
|
|
if (UseFlat(cache))
|
|
{
|
|
FlatCellContainmentBsp flat = cell.FlatContainmentBsp ??
|
|
throw MissingFlat("cell containment");
|
|
return FlatBspQuery.SphereIntersectsCellBsp(
|
|
flat,
|
|
localCenter,
|
|
radius);
|
|
}
|
|
|
|
CollisionShadowVerifier? shadow = cache.CollisionShadow;
|
|
if (shadow is null || !shadow.TrySample(out long sample))
|
|
{
|
|
return BSPQuery.SphereIntersectsCellBsp(
|
|
cell.CellBSP?.Root,
|
|
localCenter,
|
|
radius);
|
|
}
|
|
|
|
bool flatResult = false;
|
|
Exception? flatFault = null;
|
|
shadow.BeginFlatPass();
|
|
try
|
|
{
|
|
flatResult = FlatBspQuery.SphereIntersectsCellBsp(
|
|
cell.FlatContainmentBsp ??
|
|
throw MissingFlat("cell containment"),
|
|
localCenter,
|
|
radius);
|
|
}
|
|
catch (Exception fault)
|
|
{
|
|
flatFault = fault;
|
|
}
|
|
finally
|
|
{
|
|
shadow.EndFlatPass();
|
|
}
|
|
bool graphResult = BSPQuery.SphereIntersectsCellBsp(
|
|
cell.CellBSP?.Root,
|
|
localCenter,
|
|
radius);
|
|
string input = CollisionShadowVerifier.FormatInput(
|
|
localCenter,
|
|
radius);
|
|
if (flatFault is null)
|
|
{
|
|
shadow.RecordBoolean(
|
|
sample,
|
|
"SphereIntersectsCell",
|
|
cell.SourceId,
|
|
graphResult,
|
|
flatResult,
|
|
input);
|
|
}
|
|
else
|
|
{
|
|
shadow.RecordFault(
|
|
sample,
|
|
"SphereIntersectsCell",
|
|
cell.SourceId,
|
|
flatFault,
|
|
input);
|
|
}
|
|
return graphResult;
|
|
}
|
|
|
|
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 (UseFlat(cache))
|
|
{
|
|
FlatPhysicsBsp flat = cell.FlatPhysicsBsp ??
|
|
throw MissingFlat("cell physics");
|
|
return FlatBspQuery.FindCollisions(
|
|
flat,
|
|
transition,
|
|
localSphereCenter,
|
|
localSphereRadius,
|
|
hasLocalSphere1,
|
|
localSphere1Center,
|
|
localSphere1Radius,
|
|
localCurrentCenter,
|
|
localSpaceZ,
|
|
scale,
|
|
localToWorld,
|
|
engine,
|
|
worldOrigin);
|
|
}
|
|
|
|
CollisionShadowVerifier? shadow = cache.CollisionShadow;
|
|
if (shadow is null || !shadow.TrySample(out long sample))
|
|
{
|
|
return BSPQuery.FindCollisions(
|
|
cell.BSP?.Root,
|
|
cell.Resolved,
|
|
transition,
|
|
localSphereCenter,
|
|
localSphereRadius,
|
|
hasLocalSphere1,
|
|
localSphere1Center,
|
|
localSphere1Radius,
|
|
localCurrentCenter,
|
|
localSpaceZ,
|
|
scale,
|
|
localToWorld,
|
|
engine,
|
|
worldOrigin);
|
|
}
|
|
|
|
Transition flatTransition = shadow.PrepareShadow(transition);
|
|
TransitionState flatState = TransitionState.Invalid;
|
|
Exception? flatFault = null;
|
|
shadow.BeginFlatPass();
|
|
try
|
|
{
|
|
flatState = FlatBspQuery.FindCollisions(
|
|
cell.FlatPhysicsBsp ??
|
|
throw MissingFlat("cell physics"),
|
|
flatTransition,
|
|
localSphereCenter,
|
|
localSphereRadius,
|
|
hasLocalSphere1,
|
|
localSphere1Center,
|
|
localSphere1Radius,
|
|
localCurrentCenter,
|
|
localSpaceZ,
|
|
scale,
|
|
localToWorld,
|
|
engine,
|
|
worldOrigin);
|
|
}
|
|
catch (Exception fault)
|
|
{
|
|
flatFault = fault;
|
|
}
|
|
finally
|
|
{
|
|
shadow.EndFlatPass();
|
|
}
|
|
|
|
TransitionState graphState = BSPQuery.FindCollisions(
|
|
cell.BSP?.Root,
|
|
cell.Resolved,
|
|
transition,
|
|
localSphereCenter,
|
|
localSphereRadius,
|
|
hasLocalSphere1,
|
|
localSphere1Center,
|
|
localSphere1Radius,
|
|
localCurrentCenter,
|
|
localSpaceZ,
|
|
scale,
|
|
localToWorld,
|
|
engine,
|
|
worldOrigin);
|
|
string input = CollisionShadowVerifier.FormatInput(
|
|
localSphereCenter,
|
|
localSphereRadius,
|
|
hasLocalSphere1,
|
|
localSphere1Center,
|
|
localSphere1Radius,
|
|
localCurrentCenter,
|
|
localSpaceZ,
|
|
scale,
|
|
localToWorld,
|
|
worldOrigin);
|
|
if (flatFault is null)
|
|
{
|
|
shadow.RecordTransition(
|
|
sample,
|
|
"FindCellCollisions",
|
|
cell.SourceId,
|
|
graphState,
|
|
flatState,
|
|
transition,
|
|
flatTransition,
|
|
input);
|
|
}
|
|
else
|
|
{
|
|
shadow.RecordFault(
|
|
sample,
|
|
"FindCellCollisions",
|
|
cell.SourceId,
|
|
flatFault,
|
|
input);
|
|
}
|
|
return graphState;
|
|
}
|
|
|
|
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 (UseFlat(cache))
|
|
{
|
|
FlatPhysicsBsp flat = gfxObject.FlatPhysicsBsp ??
|
|
throw MissingFlat("GfxObj physics");
|
|
return FlatBspQuery.FindCollisions(
|
|
flat,
|
|
transition,
|
|
localSphereCenter,
|
|
localSphereRadius,
|
|
hasLocalSphere1,
|
|
localSphere1Center,
|
|
localSphere1Radius,
|
|
localCurrentCenter,
|
|
localSpaceZ,
|
|
scale,
|
|
localToWorld,
|
|
engine,
|
|
worldOrigin);
|
|
}
|
|
|
|
CollisionShadowVerifier? shadow = cache.CollisionShadow;
|
|
if (shadow is null || !shadow.TrySample(out long sample))
|
|
{
|
|
return BSPQuery.FindCollisions(
|
|
gfxObject.BSP.Root,
|
|
gfxObject.Resolved,
|
|
transition,
|
|
localSphereCenter,
|
|
localSphereRadius,
|
|
hasLocalSphere1,
|
|
localSphere1Center,
|
|
localSphere1Radius,
|
|
localCurrentCenter,
|
|
localSpaceZ,
|
|
scale,
|
|
localToWorld,
|
|
engine,
|
|
worldOrigin);
|
|
}
|
|
|
|
Transition flatTransition = shadow.PrepareShadow(transition);
|
|
TransitionState flatState = TransitionState.Invalid;
|
|
Exception? flatFault = null;
|
|
shadow.BeginFlatPass();
|
|
try
|
|
{
|
|
flatState = FlatBspQuery.FindCollisions(
|
|
gfxObject.FlatPhysicsBsp ??
|
|
throw MissingFlat("GfxObj physics"),
|
|
flatTransition,
|
|
localSphereCenter,
|
|
localSphereRadius,
|
|
hasLocalSphere1,
|
|
localSphere1Center,
|
|
localSphere1Radius,
|
|
localCurrentCenter,
|
|
localSpaceZ,
|
|
scale,
|
|
localToWorld,
|
|
engine,
|
|
worldOrigin);
|
|
}
|
|
catch (Exception fault)
|
|
{
|
|
flatFault = fault;
|
|
}
|
|
finally
|
|
{
|
|
shadow.EndFlatPass();
|
|
}
|
|
|
|
TransitionState graphState = BSPQuery.FindCollisions(
|
|
gfxObject.BSP.Root,
|
|
gfxObject.Resolved,
|
|
transition,
|
|
localSphereCenter,
|
|
localSphereRadius,
|
|
hasLocalSphere1,
|
|
localSphere1Center,
|
|
localSphere1Radius,
|
|
localCurrentCenter,
|
|
localSpaceZ,
|
|
scale,
|
|
localToWorld,
|
|
engine,
|
|
worldOrigin);
|
|
string input = CollisionShadowVerifier.FormatInput(
|
|
localSphereCenter,
|
|
localSphereRadius,
|
|
hasLocalSphere1,
|
|
localSphere1Center,
|
|
localSphere1Radius,
|
|
localCurrentCenter,
|
|
localSpaceZ,
|
|
scale,
|
|
localToWorld,
|
|
worldOrigin);
|
|
if (flatFault is null)
|
|
{
|
|
shadow.RecordTransition(
|
|
sample,
|
|
"FindGfxObjCollisions",
|
|
gfxObject.SourceId,
|
|
graphState,
|
|
flatState,
|
|
transition,
|
|
flatTransition,
|
|
input);
|
|
}
|
|
else
|
|
{
|
|
shadow.RecordFault(
|
|
sample,
|
|
"FindGfxObjCollisions",
|
|
gfxObject.SourceId,
|
|
flatFault,
|
|
input);
|
|
}
|
|
return graphState;
|
|
}
|
|
|
|
private static bool UseFlat(PhysicsDataCache cache) =>
|
|
cache.CollisionTraversalMode == CollisionTraversalMode.Flat ||
|
|
cache.CollisionShadow?.IsFlatPass == true;
|
|
|
|
private static InvalidOperationException MissingFlat(string kind) =>
|
|
new(
|
|
$"Flat collision traversal requires a prepared {kind} asset. " +
|
|
"Gameplay must not fall back silently.");
|
|
}
|