using System;
using System.Numerics;
using AcDream.Core.Physics;
using Xunit;
using Xunit.Abstractions;
namespace AcDream.Core.Tests.Physics;
///
/// S6 (Campaign S, 2026-08-07) — reachability proof + containment guard for
/// the AP-83/AP-91 PerfectClip time-of-impact tails
/// (TransitionTypes.CylCollideWithPoint / SphereCollideWithPoint).
/// Contract: docs/research/2026-08-07-s6-perfectclip-containment-contract.md.
///
///
/// FINDING (not a failure — the contract explicitly anticipates this outcome):
/// BOTH scoping-doc candidate arguments for "the camera can never reach these
/// tails" fail. CollisionExemption.ShouldSkip only exempts a viewer
/// mover against a CREATURE target (CollisionExemption.cs:91-94) — a
/// non-creature Cyl/Sphere shadow entry passes straight through. And
/// FindObjCollisionsInCell walks every cell's shadow list
/// unconditionally, with no viewer skip-all gate before the per-target loop
/// (TransitionTypes.cs:3806, reached from FindPrimaryCellCollisions with
/// no mover-flag check, TransitionTypes.cs:2477). So the camera
/// (PhysicsCameraCollisionProbe.SweepEye — the ONLY production
/// PerfectClip setter, grep-confirmed against the whole src tree) DOES reach
/// both TOI tails whenever its PathClipped, never-grounded foot sphere
/// (moverFlags carry PathClipped; body=null + isOnGround=false means neither
/// Contact nor OnWalkable is ever seeded) overlaps a non-creature Cyl/Sphere
/// shadow entry. That population is real production content: static
/// landblock scenery with a CylSphere/Sphere and no physics BSP registers
/// with EntityCollisionFlags.None
/// (LandblockPhysicsPublisher.PublishStaticEntity, tracked live via
/// publication.CylinderOwnerCount).
///
///
///
/// These tests drive the REAL production resolve
/// (PhysicsEngine.ResolveWithTransition) with the camera's exact call
/// shape (IsViewer|PathClipped|FreeRotate|PerfectClip, single 0.3 m sphere,
/// body=null, isOnGround=false — mirrors PhysicsCameraCollisionProbe
/// .SweepEye and the harness already used by
/// CameraCornerSealReplayTests / Issue180CorridorSweepHysteresis
/// ReplayTests) against a synthetic Cyl/Sphere shadow entry, and assert
/// the guard (PhysicsDiagnostics.RecordCylPerfectClipTailReach /
/// RecordSpherePerfectClipTailReach) records the reach as camera-live,
/// never as unverified.
///
///
///
/// Sabotage (deliverable 3): the contract's literal wording is "disable
/// the exemption that cuts the chain and watch the reachability flip" — but
/// the proof found NOTHING currently cuts the chain for a non-creature
/// target, so there is no existing cut to disable. The faithful adaptation
/// flips the SAME one axis CollisionExemption.ShouldSkip actually
/// reads (EntityCollisionFlags.IsCreature) from off to on and asserts
/// reachability inverts: the guard goes silent and the camera walks straight
/// through, exactly the retail "camera ray ignores creatures" rule
/// (acclient_2013_pseudo_c.txt:276787-276790). This proves the guard is wired
/// to the actual condition the reachability proof depends on, not merely
/// "always fires regardless of input".
///
///
public class S6PerfectClipTailContainmentTests
{
private readonly ITestOutputHelper _out;
public S6PerfectClipTailContainmentTests(ITestOutputHelper output) => _out = output;
private const uint TestLandblockId = 0xA9B40000u;
private const uint TestCellId = TestLandblockId | 0x0001u; // landcell (0,0), matches CylSphereFamilyTests/SphereCollisionFamilyTests
private const float ViewerSphereRadius = 0.3f; // retail viewer_sphere (acclient :93314)
private static readonly ObjectInfoState CameraMoverFlags =
ObjectInfoState.IsViewer | ObjectInfoState.PathClipped
| ObjectInfoState.FreeRotate | ObjectInfoState.PerfectClip;
// ───────────────────────────────────────────────────────────────
// Sphere tail (AP-91)
// ───────────────────────────────────────────────────────────────
[Fact]
public void CameraSweep_HitsNonCreatureSphereProp_ReachesTail_RecordsCameraLive()
{
PhysicsDiagnostics.ResetPerfectClipTailGuardForTest();
var engine = BuildEngine();
RegisterSphere(engine, 0x00005001u, new Vector3(12f, 14f, 1.0f), radius: 1.0f,
flags: EntityCollisionFlags.None); // static prop, not a creature
Vector3 pivot = new(12f, 10f, 1.0f);
Vector3 eye = new(12f, 20f, 1.0f); // straight through the prop's center
var r = SweepViewer(engine, pivot, eye, TestCellId);
_out.WriteLine(FormattableString.Invariant($"ok={r.Ok} pos=({r.Position.X:F3},{r.Position.Y:F3},{r.Position.Z:F3}) collNorm={r.CollisionNormalValid} normal=({r.CollisionNormal.X:F3},{r.CollisionNormal.Y:F3},{r.CollisionNormal.Z:F3}) cameraLive={PhysicsDiagnostics.SphereToiCameraLiveCount} unverified={PhysicsDiagnostics.SphereToiUnverifiedCount}"));
Assert.True(PhysicsDiagnostics.SphereToiCameraLiveCount > 0,
"The camera's PathClipped, never-grounded sweep must reach the Sphere PerfectClip TOI tail (AP-91) for a non-creature target.");
Assert.Equal(0, PhysicsDiagnostics.SphereToiUnverifiedCount);
// Golden: the sphere prop must have stopped the camera's forward sweep
// (PathClipped hard-stops at first contact) well short of the far side.
Assert.True(r.Position.Y < 14f,
$"camera must be stopped by the prop, not pass through it; got Y={r.Position.Y:F3}");
Assert.True(r.Position.Y > 11f,
$"camera must actually reach the prop's surface, not stop early; got Y={r.Position.Y:F3}");
}
[Fact]
public void CameraSweep_HitsCreatureFlaggedSphere_ExemptionCutsChain_GuardStaysSilent()
{
PhysicsDiagnostics.ResetPerfectClipTailGuardForTest();
var engine = BuildEngine();
// SABOTAGE: flip the one axis CollisionExemption.ShouldSkip reads for a
// viewer mover — everything else about the geometry is identical to the
// reached case above.
RegisterSphere(engine, 0x00005002u, new Vector3(12f, 14f, 1.0f), radius: 1.0f,
flags: EntityCollisionFlags.IsCreature);
Vector3 pivot = new(12f, 10f, 1.0f);
Vector3 eye = new(12f, 20f, 1.0f);
var r = SweepViewer(engine, pivot, eye, TestCellId);
_out.WriteLine(FormattableString.Invariant($"ok={r.Ok} pos=({r.Position.X:F3},{r.Position.Y:F3},{r.Position.Z:F3}) cameraLive={PhysicsDiagnostics.SphereToiCameraLiveCount}"));
Assert.Equal(0, PhysicsDiagnostics.SphereToiCameraLiveCount);
Assert.Equal(0, PhysicsDiagnostics.SphereToiUnverifiedCount);
// Reachability flips: CollisionExemption now exempts the whole target
// for the viewer mover before shape dispatch, so the camera sails
// straight through to the far side (retail: camera ray ignores
// creatures, acclient_2013_pseudo_c.txt:276787-276790).
Assert.True(r.Position.Y > 19f,
$"a creature-flagged prop must be fully exempt for a viewer mover; got Y={r.Position.Y:F3}");
}
// ───────────────────────────────────────────────────────────────
// Cyl tail (AP-83)
// ───────────────────────────────────────────────────────────────
[Fact]
public void CameraSweep_HitsNonCreatureCylinderProp_ReachesTail_RecordsCameraLive()
{
PhysicsDiagnostics.ResetPerfectClipTailGuardForTest();
var engine = BuildEngine();
RegisterCylinder(engine, 0x00006001u, new Vector3(12f, 14f, 0f), radius: 1.0f, height: 2.0f,
flags: EntityCollisionFlags.None);
Vector3 pivot = new(12f, 10f, 1.0f);
Vector3 eye = new(12f, 20f, 1.0f);
var r = SweepViewer(engine, pivot, eye, TestCellId);
_out.WriteLine(FormattableString.Invariant($"ok={r.Ok} pos=({r.Position.X:F3},{r.Position.Y:F3},{r.Position.Z:F3}) collNorm={r.CollisionNormalValid} normal=({r.CollisionNormal.X:F3},{r.CollisionNormal.Y:F3},{r.CollisionNormal.Z:F3}) cameraLive={PhysicsDiagnostics.CylToiCameraLiveCount} unverified={PhysicsDiagnostics.CylToiUnverifiedCount}"));
Assert.True(PhysicsDiagnostics.CylToiCameraLiveCount > 0,
"The camera's PathClipped, never-grounded sweep must reach the Cyl PerfectClip TOI tail (AP-83) for a non-creature target.");
Assert.Equal(0, PhysicsDiagnostics.CylToiUnverifiedCount);
Assert.True(r.Position.Y < 14f,
$"camera must be stopped by the prop, not pass through it; got Y={r.Position.Y:F3}");
Assert.True(r.Position.Y > 11f,
$"camera must actually reach the prop's surface, not stop early; got Y={r.Position.Y:F3}");
}
[Fact]
public void CameraSweep_HitsCreatureFlaggedCylinder_ExemptionCutsChain_GuardStaysSilent()
{
PhysicsDiagnostics.ResetPerfectClipTailGuardForTest();
var engine = BuildEngine();
RegisterCylinder(engine, 0x00006002u, new Vector3(12f, 14f, 0f), radius: 1.0f, height: 2.0f,
flags: EntityCollisionFlags.IsCreature);
Vector3 pivot = new(12f, 10f, 1.0f);
Vector3 eye = new(12f, 20f, 1.0f);
var r = SweepViewer(engine, pivot, eye, TestCellId);
_out.WriteLine(FormattableString.Invariant($"ok={r.Ok} pos=({r.Position.X:F3},{r.Position.Y:F3},{r.Position.Z:F3}) cameraLive={PhysicsDiagnostics.CylToiCameraLiveCount}"));
Assert.Equal(0, PhysicsDiagnostics.CylToiCameraLiveCount);
Assert.Equal(0, PhysicsDiagnostics.CylToiUnverifiedCount);
Assert.True(r.Position.Y > 19f,
$"a creature-flagged prop must be fully exempt for a viewer mover; got Y={r.Position.Y:F3}");
}
// ───────────────────────────────────────────────────────────────
// Harness (mirrors CylSphereFamilyTests / SphereCollisionFamilyTests)
// ───────────────────────────────────────────────────────────────
private static PhysicsEngine BuildEngine()
{
var cache = new PhysicsDataCache();
var engine = new PhysicsEngine { DataCache = cache };
// Flat terrain at Z=0 across the whole landblock — irrelevant to these
// sweeps (they travel at Z≈1.0, well above ground) but required so the
// outdoor cell resolves.
var heights = new byte[81];
var heightTable = new float[256]; // all zero → terrain Z = 0
engine.AddLandblock(
landblockId: TestLandblockId,
terrain: new TerrainSurface(heights, heightTable),
cells: Array.Empty(),
portals: Array.Empty(),
worldOffsetX: 0f,
worldOffsetY: 0f);
return engine;
}
///
/// Mirror of PhysicsCameraCollisionProbe.SweepEye's transition call — same
/// shape already used by CameraCornerSealReplayTests,
/// Issue180CorridorSweepHysteresisReplayTests, and CylSphereFamilyTests'
/// landblock (0x0100 low word stays below the AdjustPosition threshold, so
/// the outdoor cellId is used as-is).
///
private static ResolveResult SweepViewer(PhysicsEngine engine, Vector3 pivot, Vector3 desiredEye, uint cellId)
{
Vector3 begin = pivot - new Vector3(0f, 0f, ViewerSphereRadius);
Vector3 end = desiredEye - new Vector3(0f, 0f, ViewerSphereRadius);
return engine.ResolveWithTransition(
currentPos: begin,
targetPos: end,
cellId: cellId,
sphereRadius: ViewerSphereRadius,
sphereHeight: 0f,
stepUpHeight: 0f,
stepDownHeight: 0f,
isOnGround: false,
body: null,
moverFlags: CameraMoverFlags,
movingEntityId: 0);
}
private static void RegisterSphere(PhysicsEngine engine, uint entityId, Vector3 worldPos,
float radius, EntityCollisionFlags flags)
{
engine.ShadowObjects.Register(
entityId, gfxObjId: 0u,
worldPos, Quaternion.Identity, radius,
worldOffsetX: 0f, worldOffsetY: 0f, landblockId: TestLandblockId,
collisionType: ShadowCollisionType.Sphere,
cylHeight: 0f, scale: 1f,
state: 0u,
flags: flags,
isStatic: true);
}
private static void RegisterCylinder(PhysicsEngine engine, uint entityId, Vector3 worldPos,
float radius, float height, EntityCollisionFlags flags)
{
engine.ShadowObjects.Register(
entityId, gfxObjId: 0u,
worldPos, Quaternion.Identity, radius,
worldOffsetX: 0f, worldOffsetY: 0f, landblockId: TestLandblockId,
collisionType: ShadowCollisionType.Cylinder,
cylHeight: height, scale: 1f,
state: 0u,
flags: flags,
isStatic: true);
}
}