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); } }