acdream/src/AcDream.Core/Physics/CollisionTraversal.cs
Erik b3e43d22c9
Some checks are pending
Headless portability / portable-headless (ubuntu-latest) (push) Waiting to run
Headless portability / portable-headless (windows-latest) (push) Waiting to run
Headless portability / linux-graphical (push) Waiting to run
Headless portability / linux-vulkan (push) Waiting to run
fix(physics): S1B — indoor cell membership admits on the part BOX, as retail does (#335, AP-159 narrowed)
CellTransit.FindTransitCellsBox ports CEnvCell::find_transit_cells'
part-array overload @0x0052cae0 line-for-line: per-portal x per-part
order, the sphere cheap-reject at F_EPSILON+radius, the box admit whose
"Straddle or crossing-side" rule is exactly retail's `eax != side` under
the PDB Sidedness enum, leads-outside placed AFTER the admit, the
unconditional unloaded-neighbour hint without the sphere overload's
re-test, the destination box_intersects_cell gate with its deliberate
no-break, and add_all_outside_cells after the loop. The box-vs-cell BSP
traversal lands in BOTH representations behind the flat-authoritative
dispatcher with a graph referee whose 20,000 installed comparisons are
pinned by assertion (review F5), zero mismatch.

Dual Opus review: PASS on both lenses. The mandatory D0 pseudocode pass
caught that the contract's own supplementary note misattributed the box
block to the sphere overload — it belongs to a SECOND
check_building_transit overload @0x0052c680, whose portal-side
convention is INVERTED and whose admit differs; the pseudocode doc now
records that trap plus two byte confirmations made at review:
which_side @0x00444720 is strictly > eps for POSITIVE, and
intersect_box's in-plane early exit returns CROSSING(3)
(jp @0x005aa1bc -> mov eax,3), settling review items b1/b2 for the
future bridge porter. The bridge itself stays unported as AP-159's
explicit remainder.

The review also retired #335's severity premise honestly: "over-
inclusive only, never a missed one" is wrong at production shape ratios,
where the box (whole-vertex AABB) legitimately exceeds the sphere
(physics-polygon root sphere). Measured, both populations: rigged
(box << sphere) — 1,520 placements, 978 cells removed, 0 added;
production-ratio (box >= sphere) — 950 placements, 20 removed, 1 ADDED
through the loaded-neighbour gate, which is retail's direction, not a
defect. The no-op guard (review F4) asserts removal is nonzero so an
unwired admit cannot pass silently.

Process note: the implementer authored against this session's worktree
at bec5c69d, 25 commits stale — the recorded worktree-base class. All
six files were byte-identical between bases, the diff transplanted
losslessly, and every verdict-bearing run (referee, direction sweeps,
this clean-room) was re-executed on current main. S2's uncommitted
phase-1 edits were stashed for this landing so the suite verdicts
exactly one changeset.

Also untracks 341-slope-capture.jsonl (an accidental add) and
gitignores it.

Clean-room suite: 11,248 passed / 6 skipped / 0 failed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 07:46:57 +02:00

1193 lines
36 KiB
C#

using System.Numerics;
namespace AcDream.Core.Physics;
/// <summary>
/// Temporary Slice I6 authority/referee selector. Gameplay uses
/// <see cref="Flat"/>; <see cref="Graph"/> remains only for the old-oracle
/// differential fixtures until the accepted cutover deletes it.
/// </summary>
internal enum CollisionTraversalMode
{
Graph,
Flat,
}
/// <summary>
/// Narrow representation switch used while flat production and the graph
/// referee 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");
bool flatAuthorityResult = flat.RootIndex >= 0;
CollisionShadowVerifier? flatShadow = cache.CollisionShadow;
if (flatShadow is null ||
!flatShadow.TrySample(out long flatAuthoritySample))
return flatAuthorityResult;
bool graphRefereeResult = false;
Exception? graphRefereeFault = null;
flatShadow.BeginGraphPass();
try
{
graphRefereeResult = cell.CellBSP?.Root is not null;
}
catch (Exception fault)
{
graphRefereeFault = fault;
}
finally
{
flatShadow.EndGraphPass();
}
if (graphRefereeFault is null)
{
flatShadow.RecordBoolean(
flatAuthoritySample,
"HasCellContainment",
cell.SourceId,
graphRefereeResult,
flatAuthorityResult,
string.Empty);
}
else
{
flatShadow.RecordFault(
flatAuthoritySample,
"HasCellContainment",
cell.SourceId,
graphRefereeFault,
string.Empty,
"flat");
}
return flatAuthorityResult;
}
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");
bool flatAuthorityResult = flat.RootIndex >= 0;
CollisionShadowVerifier? flatShadow = cache.CollisionShadow;
if (flatShadow is null ||
!flatShadow.TrySample(out long flatAuthoritySample))
return flatAuthorityResult;
bool graphRefereeResult = false;
Exception? graphRefereeFault = null;
flatShadow.BeginGraphPass();
try
{
graphRefereeResult = cell.BSP?.Root is not null;
}
catch (Exception fault)
{
graphRefereeFault = fault;
}
finally
{
flatShadow.EndGraphPass();
}
if (graphRefereeFault is null)
{
flatShadow.RecordBoolean(
flatAuthoritySample,
"HasCellPhysics",
cell.SourceId,
graphRefereeResult,
flatAuthorityResult,
string.Empty);
}
else
{
flatShadow.RecordFault(
flatAuthoritySample,
"HasCellPhysics",
cell.SourceId,
graphRefereeFault,
string.Empty,
"flat");
}
return flatAuthorityResult;
}
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");
bool flatAuthorityResult = flat.RootIndex >= 0;
CollisionShadowVerifier? flatShadow = cache.CollisionShadow;
if (flatShadow is null ||
!flatShadow.TrySample(out long flatAuthoritySample))
return flatAuthorityResult;
bool graphRefereeResult = false;
Exception? graphRefereeFault = null;
flatShadow.BeginGraphPass();
try
{
graphRefereeResult = gfxObject.BSP?.Root is not null;
}
catch (Exception fault)
{
graphRefereeFault = fault;
}
finally
{
flatShadow.EndGraphPass();
}
if (graphRefereeFault is null)
{
flatShadow.RecordBoolean(
flatAuthoritySample,
"HasGfxObjPhysics",
gfxObject.SourceId,
graphRefereeResult,
flatAuthorityResult,
string.Empty);
}
else
{
flatShadow.RecordFault(
flatAuthoritySample,
"HasGfxObjPhysics",
gfxObject.SourceId,
graphRefereeFault,
string.Empty,
"flat");
}
return flatAuthorityResult;
}
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");
FlatCollisionSphere flatAuthorityResult =
flat.Nodes[flat.RootIndex].BoundingSphere;
CollisionShadowVerifier? flatShadow = cache.CollisionShadow;
if (flatShadow is null ||
!flatShadow.TrySample(out long flatAuthoritySample))
return flatAuthorityResult;
try
{
flatShadow.BeginGraphPass();
DatReaderWriter.Types.Sphere graphSphere =
cell.BSP!.Root!.BoundingSphere;
var graphRefereeResult =
new FlatCollisionSphere(
graphSphere.Origin,
graphSphere.Radius);
flatShadow.EndGraphPass();
flatShadow.RecordSphere(
flatAuthoritySample,
"RootBoundingSphere",
cell.SourceId,
graphRefereeResult,
flatAuthorityResult);
}
catch (Exception fault)
{
if (flatShadow.IsGraphPass)
flatShadow.EndGraphPass();
flatShadow.RecordFault(
flatAuthoritySample,
"RootBoundingSphere",
cell.SourceId,
fault,
string.Empty,
"flat");
}
return flatAuthorityResult;
}
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");
bool flatAuthorityResult =
FlatBspQuery.PointInsideCellBsp(flat, localPoint);
CollisionShadowVerifier? flatShadow = cache.CollisionShadow;
if (flatShadow is null ||
!flatShadow.TrySample(out long flatAuthoritySample))
return flatAuthorityResult;
bool graphRefereeResult = false;
Exception? graphRefereeFault = null;
flatShadow.BeginGraphPass();
try
{
graphRefereeResult = BSPQuery.PointInsideCellBsp(
cell.CellBSP?.Root,
localPoint);
}
catch (Exception fault)
{
graphRefereeFault = fault;
}
finally
{
flatShadow.EndGraphPass();
}
string flatAuthorityInput = CollisionShadowVerifier.FormatInput(
localPoint,
0f);
if (graphRefereeFault is null)
{
flatShadow.RecordBoolean(
flatAuthoritySample,
"PointInsideCell",
cell.SourceId,
graphRefereeResult,
flatAuthorityResult,
flatAuthorityInput);
}
else
{
flatShadow.RecordFault(
flatAuthoritySample,
"PointInsideCell",
cell.SourceId,
graphRefereeFault,
flatAuthorityInput,
"flat");
}
return flatAuthorityResult;
}
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");
bool flatAuthorityResult = FlatBspQuery.SphereIntersectsCellBsp(
flat,
localCenter,
radius);
CollisionShadowVerifier? flatShadow = cache.CollisionShadow;
if (flatShadow is null ||
!flatShadow.TrySample(out long flatAuthoritySample))
return flatAuthorityResult;
bool graphRefereeResult = false;
Exception? graphRefereeFault = null;
flatShadow.BeginGraphPass();
try
{
graphRefereeResult = BSPQuery.SphereIntersectsCellBsp(
cell.CellBSP?.Root,
localCenter,
radius);
}
catch (Exception fault)
{
graphRefereeFault = fault;
}
finally
{
flatShadow.EndGraphPass();
}
string flatAuthorityInput = CollisionShadowVerifier.FormatInput(
localCenter,
radius);
if (graphRefereeFault is null)
{
flatShadow.RecordBoolean(
flatAuthoritySample,
"SphereIntersectsCell",
cell.SourceId,
graphRefereeResult,
flatAuthorityResult,
flatAuthorityInput);
}
else
{
flatShadow.RecordFault(
flatAuthoritySample,
"SphereIntersectsCell",
cell.SourceId,
graphRefereeFault,
flatAuthorityInput,
"flat");
}
return flatAuthorityResult;
}
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;
}
/// <summary>
/// AP-159 / #335 (2026-08-07). Box-shaped sibling of
/// <see cref="SphereIntersectsCell"/> — same flat-authority /
/// graph-referee shadow-sample dispatch shape, for the new
/// <c>CCellStruct::box_intersects_cell</c> port
/// (<see cref="BSPQuery.BoxIntersectsCellBsp"/> /
/// <see cref="FlatBspQuery.BoxIntersectsCellBsp"/>) that
/// <c>CellTransit.FindTransitCellsBox</c> uses for the destination-cell
/// gate in retail's part-array <c>find_transit_cells</c>.
/// </summary>
internal static bool BoxIntersectsCell(
PhysicsDataCache cache,
CellPhysics cell,
Vector3 localMin,
Vector3 localMax)
{
if (UseFlat(cache))
{
FlatCellContainmentBsp flat = cell.FlatContainmentBsp ??
throw MissingFlat("cell containment");
bool flatAuthorityResult = FlatBspQuery.BoxIntersectsCellBsp(
flat,
localMin,
localMax);
CollisionShadowVerifier? flatShadow = cache.CollisionShadow;
if (flatShadow is null ||
!flatShadow.TrySample(out long flatAuthoritySample))
return flatAuthorityResult;
bool graphRefereeResult = false;
Exception? graphRefereeFault = null;
flatShadow.BeginGraphPass();
try
{
graphRefereeResult = BSPQuery.BoxIntersectsCellBsp(
cell.CellBSP?.Root,
localMin,
localMax);
}
catch (Exception fault)
{
graphRefereeFault = fault;
}
finally
{
flatShadow.EndGraphPass();
}
string flatAuthorityInput = CollisionShadowVerifier.FormatInput(
localMin,
localMax);
if (graphRefereeFault is null)
{
flatShadow.RecordBoolean(
flatAuthoritySample,
"BoxIntersectsCell",
cell.SourceId,
graphRefereeResult,
flatAuthorityResult,
flatAuthorityInput);
}
else
{
flatShadow.RecordFault(
flatAuthoritySample,
"BoxIntersectsCell",
cell.SourceId,
graphRefereeFault,
flatAuthorityInput,
"flat");
}
return flatAuthorityResult;
}
CollisionShadowVerifier? shadow = cache.CollisionShadow;
if (shadow is null || !shadow.TrySample(out long sample))
{
return BSPQuery.BoxIntersectsCellBsp(
cell.CellBSP?.Root,
localMin,
localMax);
}
bool flatResult = false;
Exception? flatFault = null;
shadow.BeginFlatPass();
try
{
flatResult = FlatBspQuery.BoxIntersectsCellBsp(
cell.FlatContainmentBsp ??
throw MissingFlat("cell containment"),
localMin,
localMax);
}
catch (Exception fault)
{
flatFault = fault;
}
finally
{
shadow.EndFlatPass();
}
bool graphResult = BSPQuery.BoxIntersectsCellBsp(
cell.CellBSP?.Root,
localMin,
localMax);
string input = CollisionShadowVerifier.FormatInput(localMin, localMax);
if (flatFault is null)
{
shadow.RecordBoolean(
sample,
"BoxIntersectsCell",
cell.SourceId,
graphResult,
flatResult,
input);
}
else
{
shadow.RecordFault(
sample,
"BoxIntersectsCell",
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))
{
CollisionShadowVerifier? flatShadow = cache.CollisionShadow;
long flatAuthoritySample = 0;
bool sampled =
flatShadow is not null &&
flatShadow.TrySample(out flatAuthoritySample);
Transition? graphTransition = sampled
? flatShadow!.PrepareShadow(transition)
: null;
FlatPhysicsBsp flat = cell.FlatPhysicsBsp ??
throw MissingFlat("cell physics");
TransitionState flatAuthorityState = FlatBspQuery.FindCollisions(
flat,
transition,
localSphereCenter,
localSphereRadius,
hasLocalSphere1,
localSphere1Center,
localSphere1Radius,
localCurrentCenter,
localSpaceZ,
scale,
localToWorld,
engine,
worldOrigin);
if (!sampled)
return flatAuthorityState;
TransitionState graphRefereeState = TransitionState.Invalid;
Exception? graphRefereeFault = null;
flatShadow!.BeginGraphPass();
try
{
graphRefereeState = BSPQuery.FindCollisions(
cell.BSP?.Root,
cell.Resolved,
graphTransition!,
localSphereCenter,
localSphereRadius,
hasLocalSphere1,
localSphere1Center,
localSphere1Radius,
localCurrentCenter,
localSpaceZ,
scale,
localToWorld,
engine,
worldOrigin);
}
catch (Exception fault)
{
graphRefereeFault = fault;
}
finally
{
flatShadow.EndGraphPass();
}
string flatAuthorityInput = CollisionShadowVerifier.FormatInput(
localSphereCenter,
localSphereRadius,
hasLocalSphere1,
localSphere1Center,
localSphere1Radius,
localCurrentCenter,
localSpaceZ,
scale,
localToWorld,
worldOrigin);
if (graphRefereeFault is null)
{
flatShadow.RecordTransition(
flatAuthoritySample,
"FindCellCollisions",
cell.SourceId,
graphRefereeState,
flatAuthorityState,
graphTransition!,
transition,
flatAuthorityInput);
}
else
{
flatShadow.RecordFault(
flatAuthoritySample,
"FindCellCollisions",
cell.SourceId,
graphRefereeFault,
flatAuthorityInput,
"flat");
}
return flatAuthorityState;
}
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))
{
CollisionShadowVerifier? flatShadow = cache.CollisionShadow;
long flatAuthoritySample = 0;
bool sampled =
flatShadow is not null &&
flatShadow.TrySample(out flatAuthoritySample);
Transition? graphTransition = sampled
? flatShadow!.PrepareShadow(transition)
: null;
FlatPhysicsBsp flat = gfxObject.FlatPhysicsBsp ??
throw MissingFlat("GfxObj physics");
TransitionState flatAuthorityState = FlatBspQuery.FindCollisions(
flat,
transition,
localSphereCenter,
localSphereRadius,
hasLocalSphere1,
localSphere1Center,
localSphere1Radius,
localCurrentCenter,
localSpaceZ,
scale,
localToWorld,
engine,
worldOrigin);
if (!sampled)
return flatAuthorityState;
TransitionState graphRefereeState = TransitionState.Invalid;
Exception? graphRefereeFault = null;
flatShadow!.BeginGraphPass();
try
{
graphRefereeState = BSPQuery.FindCollisions(
gfxObject.BSP!.Root!,
gfxObject.Resolved,
graphTransition!,
localSphereCenter,
localSphereRadius,
hasLocalSphere1,
localSphere1Center,
localSphere1Radius,
localCurrentCenter,
localSpaceZ,
scale,
localToWorld,
engine,
worldOrigin);
}
catch (Exception fault)
{
graphRefereeFault = fault;
}
finally
{
flatShadow.EndGraphPass();
}
string flatAuthorityInput = CollisionShadowVerifier.FormatInput(
localSphereCenter,
localSphereRadius,
hasLocalSphere1,
localSphere1Center,
localSphere1Radius,
localCurrentCenter,
localSpaceZ,
scale,
localToWorld,
worldOrigin);
if (graphRefereeFault is null)
{
flatShadow.RecordTransition(
flatAuthoritySample,
"FindGfxObjCollisions",
gfxObject.SourceId,
graphRefereeState,
flatAuthorityState,
graphTransition!,
transition,
flatAuthorityInput);
}
else
{
flatShadow.RecordFault(
flatAuthoritySample,
"FindGfxObjCollisions",
gfxObject.SourceId,
graphRefereeFault,
flatAuthorityInput,
"flat");
}
return flatAuthorityState;
}
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)
{
CollisionShadowVerifier? shadow = cache.CollisionShadow;
if (shadow?.IsGraphPass == true)
return false;
if (shadow?.IsFlatPass == true)
return true;
return cache.CollisionTraversalMode == CollisionTraversalMode.Flat;
}
private static InvalidOperationException MissingFlat(string kind) =>
new(
$"Flat collision traversal requires a prepared {kind} asset. " +
"Gameplay must not fall back silently.");
}