using System; using System.Collections.Generic; using System.Numerics; using System.Text; using AcDream.Core.Physics; using DatReaderWriter.Types; using Xunit; namespace AcDream.Core.Tests.Physics; /// /// Pins the branch order in retail CTransition::transitional_insert, /// CTransition::edge_slide, and CTransition::cliff_slide. /// These cases distinguish the retail implementation from the four former /// acdream compensations tracked as AP-3, AP-4, AD-53, and AD-54. /// public sealed class RetailEdgeResponseOrderingTests { private const uint Cell = 0xA9B40001u; [Fact] public void TransitionalInsert_ValidSteepContact_ReturnsBeforeOrdinaryStepDownTail() { Vector3 current = new(2f, 3f, 4f); Vector3 target = current + new Vector3(0.1f, 0f, 0f); var transition = BSPStepUpFixtures.MakeGroundedTransition(current, target, cellId: Cell); transition.ObjectInfo.State |= ObjectInfoState.EdgeSlide; transition.ObjectInfo.StepDown = true; transition.ObjectInfo.StepDownHeight = 2f; Vector3 untouchedBackup = new(97f, 98f, 99f); const uint untouchedBackupCell = 0xA9B40044u; transition.SpherePath.BackupCheckPos = untouchedBackup; transition.SpherePath.BackupCheckCellId = untouchedBackupCell; var steep = new Plane(Vector3.Normalize(new Vector3(1f, 0f, 0.25f)), 0f); var engine = new PhysicsEngine { TransitionCellCollisionTestHook = (candidate, phase, _, actual) => { if (phase == TransitionCellCollisionPhase.Objects) candidate.CollisionInfo.SetContactPlane(steep, Cell, isWater: true); return actual; }, }; TransitionState result = transition.TransitionalInsertForTest(1, engine); Assert.Equal(TransitionState.OK, result); Assert.True(transition.CollisionInfo.ContactPlaneValid); Assert.True(transition.CollisionInfo.ContactPlaneIsWater); Assert.Equal(steep, transition.CollisionInfo.ContactPlane); Assert.Equal(untouchedBackup, transition.SpherePath.BackupCheckPos); Assert.Equal(untouchedBackupCell, transition.SpherePath.BackupCheckCellId); } [Theory] [InlineData(1, 0.5f, 2.0f, 0.25f, 1)] [InlineData(2, 0.5f, 2.0f, 1.00f, 2)] [InlineData(1, 0.5f, 0.75f, 0.75f, 1)] [InlineData(2, 0.5f, 0.75f, 0.75f, 1)] public void StepDownProbePlan_PreservesRetailOneVersusTwoSphereSplit( int sphereCount, float radius, float requestedHeight, float expectedProbeHeight, int expectedProbeCount) { (float probeHeight, int probeCount) = Transition.GetStepDownProbePlan( sphereCount, radius, requestedHeight); Assert.Equal(expectedProbeHeight, probeHeight); Assert.Equal(expectedProbeCount, probeCount); } [Fact] public void EdgeSlide_NotOnWalkableSteepContact_RestoresBeforeCliffSlide() { var transition = MakeFailedStepDownTransition(); transition.ObjectInfo.State = ObjectInfoState.EdgeSlide; transition.CollisionInfo.ContactPlaneValid = true; transition.CollisionInfo.ContactPlane = new Plane(Vector3.Normalize(new Vector3(1f, 0f, 0.25f)), 0f); transition.CollisionInfo.ContactPlaneIsWater = true; transition.CollisionInfo.LastKnownContactPlaneValid = true; transition.CollisionInfo.LastKnownContactPlane = new Plane(Vector3.UnitZ, 0f); Vector3 failedCandidate = transition.SpherePath.BackupCheckPos; bool stop = transition.EdgeSlideAfterStepDownFailedForTest( new PhysicsEngine(), stepDownHeight: 0.04f, zVal: PhysicsGlobals.FloorZ, out TransitionState result); Assert.True(stop); Assert.Equal(TransitionState.OK, result); Assert.Equal(failedCandidate, transition.SpherePath.CheckPos); Assert.False(transition.CollisionInfo.ContactPlaneValid); Assert.False(transition.CollisionInfo.ContactPlaneIsWater); Assert.False(transition.CollisionInfo.CollisionNormalValid); } [Fact] public void CliffSlide_UsesOnlyLastKnownContactPlaneNormal() { var transition = MakeFailedStepDownTransition(); // A qualifying remembered walkable normal deliberately points along Y. // The former AD-53 fallback consumed it; retail consumes the explicit // last-known contact normal below and therefore resolves along -X. Plane rememberedWalkable = new(Vector3.Normalize(new Vector3(0f, 1f, 1f)), 0f); transition.SpherePath.SetWalkable( rememberedWalkable, SquareOnPlaneZ0(), Vector3.UnitZ); transition.SpherePath.ClearWalkable(); transition.CollisionInfo.LastKnownContactPlaneValid = true; transition.CollisionInfo.LastKnownContactPlane = new Plane(Vector3.UnitZ, 0f); Plane steepContact = new(Vector3.Normalize(new Vector3(1f, 0f, 0.5f)), 0f); TransitionState result = transition.CliffSlideForTest(steepContact); Assert.Equal(TransitionState.Adjusted, result); Assert.True(transition.CollisionInfo.CollisionNormalValid); Assert.True(Vector3.Distance(-Vector3.UnitX, transition.CollisionInfo.CollisionNormal) < 0.0001f); } [Fact] public void CliffSlide_InvalidDefaultLastKnownPlane_TakesDegenerateOkReturn() { var transition = MakeFailedStepDownTransition(); transition.CollisionInfo.LastKnownContactPlaneValid = false; transition.CollisionInfo.LastKnownContactPlane = default; Plane steepContact = new(Vector3.Normalize(new Vector3(1f, 0f, 0.5f)), 0f); TransitionState result = transition.CliffSlideForTest(steepContact); Assert.Equal(TransitionState.OK, result); Assert.False(transition.CollisionInfo.CollisionNormalValid); } [Fact] public void EdgeSlide_StoredSteepWalkable_AlwaysRoutesToPrecipiceSlide() { var transition = MakeFailedStepDownTransition(); transition.ObjectInfo.State = ObjectInfoState.Contact | ObjectInfoState.OnWalkable | ObjectInfoState.EdgeSlide; transition.CollisionInfo.ContactPlaneValid = false; transition.CollisionInfo.LastKnownContactPlaneValid = true; transition.CollisionInfo.LastKnownContactPlane = new Plane(Vector3.UnitZ, 0f); Vector3 steepNormal = Vector3.Normalize(new Vector3(-2f, 0f, 1f)); var steepPlane = new Plane(steepNormal, 0f); Vector3[] steepQuad = [ new(0f, -1f, 0f), new(1f, -1f, 2f), new(1f, 1f, 2f), new(0f, 1f, 0f), ]; transition.SpherePath.SetWalkable(steepPlane, steepQuad, Vector3.UnitZ); Vector3 failedCandidate = new(0.5f, 0f, 1f); transition.SpherePath.SetCheckPos(failedCandidate, Cell); transition.SpherePath.SaveCheckPos(); transition.SpherePath.AddOffsetToCheckPos(new Vector3(0f, 0f, -0.25f)); bool stop = transition.EdgeSlideAfterStepDownFailedForTest( new PhysicsEngine(), stepDownHeight: 0.04f, zVal: PhysicsGlobals.FloorZ, out TransitionState result); // The restored point is inside the remembered polygon, so retail's // unconditional PrecipiceSlide returns Collided. AD-54's steep-plane // reroute instead returned Adjusted through CliffSlide. Assert.True(stop); Assert.Equal(TransitionState.Collided, result); Assert.Equal(failedCandidate, transition.SpherePath.CheckPos); Assert.False(transition.SpherePath.HasWalkablePolygon); Assert.False(transition.CollisionInfo.CollisionNormalValid); } [Fact] public void TransitionalInsert_DegenerateCliffSlideOk_ContinuesOuterRetry() { Vector3 current = new(2f, 3f, 4f); Vector3 target = current + new Vector3(0.1f, 0f, 0f); var transition = BSPStepUpFixtures.MakeGroundedTransition(current, target, cellId: Cell); transition.ObjectInfo.State |= ObjectInfoState.EdgeSlide; transition.ObjectInfo.StepDown = true; transition.ObjectInfo.StepDownHeight = 0.04f; Plane steep = new(Vector3.Normalize(new Vector3(1f, 0f, 0.25f)), 0f); int outerObjectPasses = 0; var engine = new PhysicsEngine { TransitionCellCollisionTestHook = (candidate, phase, _, actual) => { if (phase != TransitionCellCollisionPhase.Objects) return actual; if (candidate.SpherePath.StepDown) { // The nested downward probe finds a steep contact. It is // rejected as walkable, and CliffSlide takes its degenerate // OK_TS return with stop=false. // // #32 (2026-08-07) — WHY THIS STILL PASSES, because the // reason CHANGED and the old comment here was describing // deleted behaviour. It used to read "because // SetContactPlane also latches the same last-known plane, // CliffSlide's cross is parallel/degenerate". That latch is // gone. This fixture never seeds a last-known plane, so // last-known is now simply ABSENT, and CliffSlide's // degenerate return covers that case too — retail genuinely // returns OK_TS when last-known is missing (see // CliffSlide_InvalidDefaultLastKnownPlane_TakesDegenerateOkReturn). // Same outcome, different cause. This test therefore does // NOT discriminate the #32 fix; Issue32LastKnownContactPlaneTests // does, and is sabotage-verified. candidate.CollisionInfo.SetContactPlane(steep, Cell); } else { outerObjectPasses++; if (outerObjectPasses == 2) candidate.CollisionInfo.SetContactPlane(new Plane(Vector3.UnitZ, 0f), Cell); } return actual; }, }; TransitionState result = transition.TransitionalInsertForTest(2, engine); Assert.Equal(TransitionState.OK, result); Assert.Equal(2, outerObjectPasses); Assert.True(transition.CollisionInfo.ContactPlaneValid); Assert.Equal(Vector3.UnitZ, transition.CollisionInfo.ContactPlane.Normal); } [Fact] public void MultiFrameSteepRoof_PureVertical_GraphAndFlatSlideDownhillWithoutWedge() { TraceRun graph = RunSteepRoofTrace(preparedFlat: false, Vector2.Zero); TraceRun flat = RunSteepRoofTrace(preparedFlat: true, Vector2.Zero); AssertTraceParity(graph, flat); Assert.Contains(graph.Frames, frame => frame.Result.InContact); AssertNoLongFrozenStreak(graph.Frames, maximumTicks: 15); Assert.True(graph.Frames[^1].Result.Position.X < graph.Frames[0].Result.Position.X - 0.20f, $"The vertical trace did not descend the roof: " + $"{graph.Frames[0].Result.Position} -> {graph.Frames[^1].Result.Position}."); Plane slope = BSPStepUpFixtures.SlopedUnwalkable().Resolved[ BSPStepUpFixtures.SlopedUnwalkable_SlopeId].Plane; float radius = BSPStepUpFixtures.SphereRadius; for (int i = 0; i < graph.Frames.Count; i++) { Vector3 position = graph.Frames[i].Result.Position; AssertFinite(position, $"steep-roof frame {i}"); if (i > 0) { float distance = Vector3.Distance( graph.Frames[i - 1].Result.Position, position); Assert.InRange(distance, 0f, 1.1f); } // While the foot center remains over the authored slope's XY // footprint, it must stay on/outside the plane by at least its // radius. Once X leaves [0,1], the body has exited the polygon and // may fall toward the separate flat reference floor. if (position.X is >= 0f and <= 1f && MathF.Abs(position.Y) <= 1f) { Vector3 footCenter = position + new Vector3(0f, 0f, radius); float signedDistance = Vector3.Dot(slope.Normal, footCenter) + slope.D; Assert.True(signedDistance >= radius - 0.015f, $"Steep-roof penetration at frame {i}: distance={signedDistance}, " + $"radius={radius}, position={position}."); } } } [Theory] [InlineData(-0.30f, 0f, "downhill")] [InlineData( 0.30f, 0f, "uphill")] [InlineData( 0f, 0.30f, "tangential")] public void MultiFrameSteepRoof_DirectionalMotion_RemainsExactAndPreservesRetailResponse( float velocityX, float velocityY, string direction) { Vector2 horizontalVelocity = new(velocityX, velocityY); TraceRun graph = RunSteepRoofTrace(preparedFlat: false, horizontalVelocity); TraceRun flat = RunSteepRoofTrace(preparedFlat: true, horizontalVelocity); AssertTraceParity(graph, flat); Assert.Contains(graph.Frames, frame => frame.Result.InContact); AssertNoLongFrozenStreak(graph.Frames, maximumTicks: 15); Vector3 first = graph.Frames[0].Result.Position; Vector3 last = graph.Frames[^1].Result.Position; Vector2 progress = new(last.X - first.X, last.Y - first.Y); if (direction == "uphill") { int contactFrame = graph.Frames.FindIndex(frame => frame.Result.InContact); Assert.True(contactFrame >= 0); float peakAfterContact = graph.Frames .GetRange(contactFrame, graph.Frames.Count - contactFrame) .Max(frame => frame.Result.Position.Z); Assert.True(peakAfterContact <= graph.Frames[contactFrame].Result.Position.Z + 0.001f, $"The uphill trace launched/bounced from the roof: " + $"contactZ={graph.Frames[contactFrame].Result.Position.Z}, peak={peakAfterContact}."); } else { Assert.True(Vector2.Dot(progress, Vector2.Normalize(horizontalVelocity)) > 0.10f, $"The {direction} trace lost requested progress: {first} -> {last}."); } Plane slope = BSPStepUpFixtures.SlopedUnwalkable().Resolved[ BSPStepUpFixtures.SlopedUnwalkable_SlopeId].Plane; float radius = BSPStepUpFixtures.SphereRadius; for (int i = 0; i < graph.Frames.Count; i++) { Vector3 position = graph.Frames[i].Result.Position; AssertFinite(position, $"steep-roof {direction} frame {i}"); if (i > 0) { float distance = Vector3.Distance( graph.Frames[i - 1].Result.Position, position); Assert.InRange(distance, 0f, 1.1f); } if (position.X is >= 0f and <= 1f && MathF.Abs(position.Y) <= 1f) { Vector3 footCenter = position + new Vector3(0f, 0f, radius); float signedDistance = Vector3.Dot(slope.Normal, footCenter) + slope.D; Assert.True(signedDistance >= radius - 0.015f, $"Steep-roof {direction} penetration at frame {i}: " + $"distance={signedDistance}, radius={radius}, position={position}."); } } } [Fact] public void MultiFrameFlatRoofLedge_GraphAndFlatTraversalRemainExactAndSlideAlongEdge() { TraceRun graph = RunFlatRoofLedgeTrace(preparedFlat: false, includeRoof: true); TraceRun flat = RunFlatRoofLedgeTrace(preparedFlat: true, includeRoof: true); TraceRun noRoof = RunFlatRoofLedgeTrace(preparedFlat: false, includeRoof: false); AssertTraceParity(graph, flat); Assert.True(graph.LandedFrame > 0, "The collision-backed control never landed on the roof."); Assert.Equal(-1, noRoof.LandedFrame); Assert.All(graph.Frames.GetRange(0, graph.LandedFrame), frame => Assert.False(frame.Result.OnWalkable)); TraceFrame landing = graph.Frames[graph.LandedFrame]; Assert.True(landing.Result.Ok); Assert.True(landing.Result.IsOnGround); Assert.True(landing.Result.InContact); Assert.True(landing.Result.OnWalkable); Assert.True(landing.BodyContactPlaneValid); Assert.True(landing.BodyWalkablePolygonValid); Assert.Equal(Vector3.UnitZ, landing.BodyContactPlane.Normal); Assert.Equal( TransientStateFlags.Contact | TransientStateFlags.OnWalkable, landing.BodyTransientState & (TransientStateFlags.Contact | TransientStateFlags.OnWalkable)); List ledge = graph.Frames.GetRange( graph.LedgeStartFrame, graph.Frames.Count - graph.LedgeStartFrame); AssertNoLongFrozenStreak(ledge, maximumTicks: 15); int outwardRejectionFrame = ledge.FindIndex(frame => frame.Result.CollisionNormalValid && frame.Result.CollisionNormal.X < -0.9f); Assert.True(outwardRejectionFrame >= 0, "The roof-edge control never produced the expected outward-X rejection normal."); float rejectedX = ledge[outwardRejectionFrame].Result.Position.X; Assert.All(ledge.GetRange( outwardRejectionFrame, ledge.Count - outwardRejectionFrame), frame => Assert.True(frame.Result.Position.X <= rejectedX + 0.001f, $"Outward X resumed after rejection: {frame.Result.Position.X} > {rejectedX}.")); float unobstructedFinalX = ledge[0].Result.Position.X + 0.12f * (ledge.Count - 1); Assert.True(ledge[^1].Result.Position.X < unobstructedFinalX - 0.25f, $"Roof edge failed to remove outward travel: final={ledge[^1].Result.Position.X}, " + $"unobstructed={unobstructedFinalX}."); Assert.True(ledge[^1].Result.Position.Y > ledge[outwardRejectionFrame].Result.Position.Y + 0.25f, $"The roof edge rejected all tangency: {ledge[outwardRejectionFrame].Result.Position} -> " + $"{ledge[^1].Result.Position}."); } [Theory] [InlineData(false)] [InlineData(true)] public void MultiFrameGroundedFloorWallSlide_InwardTangentialMotionIsGraphFlatExact( bool twoSpheres) { WallMaintenanceTrace graph = RunGroundedFloorWallSlide( preparedFlat: false, twoSpheres); WallMaintenanceTrace flat = RunGroundedFloorWallSlide( preparedFlat: true, twoSpheres); Assert.Equal(graph.SupportPasses, flat.SupportPasses); Assert.Equal(graph.PlacementPasses, flat.PlacementPasses); Assert.True(graph.SupportPasses > 0, "The full-engine replay never entered grounded support maintenance."); Assert.Equal(graph.Frames.Count, graph.SupportPasses); Assert.Equal(graph.SupportPasses, graph.PlacementPasses); Assert.Equal(graph.Frames.Count, flat.Frames.Count); for (int i = 0; i < graph.Frames.Count; i++) { Assert.Equal(graph.Frames[i].ResolveBits, flat.Frames[i].ResolveBits); Assert.Equal(graph.Frames[i].BodyBits, flat.Frames[i].BodyBits); Assert.True(graph.Frames[i].Result.Ok, $"Grounded wall slide failed at frame {i}."); Assert.True(graph.Frames[i].Result.InContact, $"Ground contact was lost at frame {i}."); Assert.True(graph.Frames[i].Result.OnWalkable, $"Walkable state was lost at frame {i}."); float wallLimit = 0.5f - BSPStepUpFixtures.SphereRadius + PhysicsGlobals.EPSILON * 10f; Assert.True(graph.Frames[i].Result.Position.X <= wallLimit, $"Wall penetration at frame {i}: X={graph.Frames[i].Result.Position.X}, " + $"limit={wallLimit}."); } AssertNoLongFrozenStreak(graph.Frames, maximumTicks: 1); Assert.True( graph.Frames[^1].Result.Position.Y > graph.Frames[0].Result.Position.Y + 0.25f, $"Wall response removed tangential progress: " + $"{graph.Frames[0].Result.Position} -> {graph.Frames[^1].Result.Position}."); } private static Transition MakeFailedStepDownTransition() { Vector3 current = Vector3.Zero; Vector3 failedCandidate = new(1f, 0f, 0f); var transition = BSPStepUpFixtures.MakeGroundedTransition( current, failedCandidate, cellId: Cell); transition.ObjectInfo.State |= ObjectInfoState.EdgeSlide; transition.SpherePath.SetCheckPos(failedCandidate, Cell); transition.SpherePath.SaveCheckPos(); transition.SpherePath.AddOffsetToCheckPos(new Vector3(0f, 0f, -0.25f)); return transition; } private static Vector3[] SquareOnPlaneZ0() => [ new(-2f, -2f, 0f), new( 2f, -2f, 0f), new( 2f, 2f, 0f), new(-2f, 2f, 0f), ]; private static TraceRun RunSteepRoofTrace( bool preparedFlat, Vector2 horizontalVelocity) { var fixture = BSPStepUpFixtures.SlopedUnwalkable(); PhysicsEngine engine = BuildCollisionEngine(fixture, preparedFlat, 0x0100E101u); float radius = BSPStepUpFixtures.SphereRadius; const float dt = 1f / 30f; const float gravity = -9.8f; var body = new PhysicsBody { Position = new Vector3(0.5f, 0f, 3f), Orientation = Quaternion.Identity, State = PhysicsStateFlags.Gravity | PhysicsStateFlags.ReportCollisions, TransientState = TransientStateFlags.Active, }; Vector3 position = new(0.5f, 0f, 3f); float velocityZ = 0f; var trace = new List(90); for (int tick = 0; tick < 90; tick++) { velocityZ += gravity * dt; body.Velocity = new Vector3( horizontalVelocity.X, horizontalVelocity.Y, velocityZ); ResolveResult result = engine.ResolveWithTransition( position, position + body.Velocity * dt, Cell, radius, radius * 2f, stepUpHeight: 0.30f, stepDownHeight: 0.04f, isOnGround: body.OnWalkable, body, ObjectInfoState.IsPlayer | ObjectInfoState.EdgeSlide, movingEntityId: 0x01000000u); position = result.Position; body.Position = position; if (result.IsOnGround) { velocityZ = 0f; body.Velocity = new Vector3( horizontalVelocity.X, horizontalVelocity.Y, 0f); } ApplyContactResult(body, result); trace.Add(CaptureFrame(result, body)); if (position.X < 0f && position.Z <= radius + 0.05f) break; } return new TraceRun(trace, LandedFrame: -1, LedgeStartFrame: -1); } private static TraceRun RunFlatRoofLedgeTrace(bool preparedFlat, bool includeRoof) { var fixture = BSPStepUpFixtures.FlatRoof(); PhysicsEngine engine = BuildCollisionEngine( fixture, preparedFlat, 0x0100E102u, includeGeometry: includeRoof); float radius = BSPStepUpFixtures.SphereRadius; const float dt = 1f / 30f; Vector3 position = new(1.55f, -0.75f, 3.6f); float velocityZ = 0f; var body = new PhysicsBody { Position = position, Orientation = Quaternion.Identity, State = PhysicsStateFlags.Gravity | PhysicsStateFlags.ReportCollisions, TransientState = TransientStateFlags.Active, }; var trace = new List(72); int landedFrame = -1; for (int tick = 0; tick < 60; tick++) { velocityZ += PhysicsBody.Gravity * dt; body.Velocity = new Vector3(0f, 0f, velocityZ); ResolveResult result = engine.ResolveWithTransition( position, position + new Vector3(0f, 0f, velocityZ * dt), Cell, radius, radius * 2f, stepUpHeight: 0.30f, stepDownHeight: 0.04f, isOnGround: body.OnWalkable, body, ObjectInfoState.IsPlayer | ObjectInfoState.EdgeSlide, movingEntityId: 0x01000001u); position = result.Position; body.Position = position; ApplyContactResult(body, result); trace.Add(CaptureFrame(result, body)); if (result.InContact && result.OnWalkable) { landedFrame = trace.Count - 1; velocityZ = 0f; body.Velocity = Vector3.Zero; break; } } int ledgeStartFrame = trace.Count; if (landedFrame >= 0) { for (int tick = 0; tick < 12; tick++) { ResolveResult result = engine.ResolveWithTransition( position, position + new Vector3(0.12f, 0.08f, 0f), Cell, radius, radius * 2f, stepUpHeight: 0.30f, stepDownHeight: 0.04f, isOnGround: body.OnWalkable, body, ObjectInfoState.IsPlayer | ObjectInfoState.EdgeSlide, movingEntityId: 0x01000001u); position = result.Position; body.Position = position; ApplyContactResult(body, result); trace.Add(CaptureFrame(result, body)); } } return new TraceRun(trace, landedFrame, ledgeStartFrame); } private static WallMaintenanceTrace RunGroundedFloorWallSlide( bool preparedFlat, bool twoSpheres) { var fixture = BSPStepUpFixtures.TallWall(); PhysicsEngine engine = BuildCollisionEngine( fixture, preparedFlat, 0x0100E103u); int supportPasses = 0; int placementPasses = 0; engine.TransitionCellCollisionTestHook = (candidate, phase, _, actual) => { if (phase == TransitionCellCollisionPhase.Environment) { if (candidate.SpherePath.StepDown && actual == TransitionState.OK && candidate.CollisionInfo.ContactPlaneValid && candidate.CollisionInfo.ContactPlane.Normal.Z >= candidate.SpherePath.WalkableAllowance) supportPasses++; else if (candidate.SpherePath.InsertType == InsertType.Placement) placementPasses++; } return actual; }; float radius = BSPStepUpFixtures.SphereRadius; Vector3 position = new(0.5f - radius, -0.55f, 0f); var floor = fixture.Resolved[BSPStepUpFixtures.TallWall_FloorId]; var body = new PhysicsBody { Position = position, Orientation = Quaternion.Identity, GroundNormal = Vector3.UnitZ, ContactPlaneValid = true, ContactPlane = floor.Plane, ContactPlaneCellId = Cell, WalkablePolygonValid = true, WalkablePlane = floor.Plane, WalkableVertices = floor.Vertices, WalkableUp = Vector3.UnitZ, TransientState = TransientStateFlags.Active | TransientStateFlags.Contact | TransientStateFlags.OnWalkable, }; var trace = new List(10); for (int tick = 0; tick < 10; tick++) { body.Velocity = new Vector3(1.8f, 2.1f, 0f); ResolveResult result = engine.ResolveWithTransition( position, position + new Vector3(0.06f, 0.07f, 0f), Cell, radius, sphereHeight: twoSpheres ? 1.835f : 0f, stepUpHeight: 0.04f, stepDownHeight: 0.04f, isOnGround: true, body, ObjectInfoState.IsPlayer | ObjectInfoState.EdgeSlide, movingEntityId: 0x01000002u); position = result.Position; body.Position = position; ApplyContactResult(body, result); trace.Add(CaptureFrame(result, body)); } return new WallMaintenanceTrace(trace, supportPasses, placementPasses); } private static PhysicsEngine BuildCollisionEngine( (PhysicsBSPNode Root, Dictionary Resolved) fixture, bool preparedFlat, uint gfxObjId, bool includeGeometry = true) { var normalized = new Dictionary(fixture.Resolved.Count); foreach ((ushort id, ResolvedPolygon polygon) in fixture.Resolved) { normalized.Add(id, new ResolvedPolygon { Id = id, Vertices = polygon.Vertices, Plane = polygon.Plane, NumPoints = polygon.NumPoints, SidesType = polygon.SidesType, }); } var physics = new GfxObjPhysics { SourceId = gfxObjId, BSP = new PhysicsBSPTree { Root = fixture.Root }, Resolved = normalized, BoundingSphere = fixture.Root.BoundingSphere, }; var cache = new PhysicsDataCache(); if (includeGeometry && preparedFlat) { cache.CollisionTraversalMode = CollisionTraversalMode.Flat; cache.CacheGfxObj(gfxObjId, FlatCollisionAssetBuilder.FlattenGfxObj(physics)); } else if (includeGeometry) { cache.RegisterGfxObjForTest(gfxObjId, physics); } var heights = new byte[81]; var heightTable = new float[256]; Array.Fill(heightTable, -1000f); var engine = new PhysicsEngine { DataCache = cache }; engine.AddLandblock( 0xA9B40000u, new TerrainSurface(heights, heightTable), Array.Empty(), Array.Empty(), 0f, 0f); if (includeGeometry) { engine.ShadowObjects.Register( gfxObjId, gfxObjId, Vector3.Zero, Quaternion.Identity, fixture.Root.BoundingSphere.Radius, 0f, 0f, 0xA9B4FFFFu, ShadowCollisionType.BSP, 1f); } return engine; } private static void ApplyContactResult(PhysicsBody body, ResolveResult result) { body.TransientState &= ~(TransientStateFlags.Contact | TransientStateFlags.OnWalkable); if (result.InContact) body.TransientState |= TransientStateFlags.Contact; if (result.OnWalkable) body.TransientState |= TransientStateFlags.OnWalkable; } private static void AssertTraceParity(TraceRun expected, TraceRun actual) { Assert.Equal(expected.LandedFrame, actual.LandedFrame); Assert.Equal(expected.LedgeStartFrame, actual.LedgeStartFrame); Assert.Equal(expected.Frames.Count, actual.Frames.Count); for (int i = 0; i < expected.Frames.Count; i++) { Assert.Equal(expected.Frames[i].ResolveBits, actual.Frames[i].ResolveBits); Assert.Equal(expected.Frames[i].BodyBits, actual.Frames[i].BodyBits); } } private static void AssertNoLongFrozenStreak( IReadOnlyList trace, int maximumTicks) { int streak = 0; for (int i = 1; i < trace.Count; i++) { streak = Vector3.Distance( trace[i - 1].Result.Position, trace[i].Result.Position) < 0.001f ? streak + 1 : 0; Assert.True(streak <= maximumTicks, $"Trace froze for {streak} ticks at {trace[i].Result.Position}."); } } private static TraceFrame CaptureFrame(ResolveResult result, PhysicsBody body) => new( result, ResolveSignature(result), BodySignature(body), body.ContactPlaneValid, body.ContactPlane, body.WalkablePolygonValid, body.TransientState); private static string ResolveSignature(ResolveResult result) { var signature = new StringBuilder(256); Append(signature, result.Position); Append(signature, result.CellId); Append(signature, result.IsOnGround); Append(signature, result.CollisionNormalValid); Append(signature, result.CollisionNormal); Append(signature, result.Ok); Append(signature, result.Orientation); Append(signature, result.InContact); Append(signature, result.OnWalkable); Append(signature, result.ContactPlane); Append(signature, result.ContactPlaneCellId); Append(signature, result.ContactPlaneIsWater); return signature.ToString(); } private static string BodySignature(PhysicsBody body) { var signature = new StringBuilder(512); Append(signature, body.Position); Append(signature, body.CellPosition.ObjCellId); Append(signature, body.CellPosition.Frame.Origin); Append(signature, body.CellPosition.Frame.Orientation); Append(signature, body.InWorld); Append(signature, body.Orientation); Append(signature, body.Velocity); Append(signature, body.CachedVelocity); Append(signature, body.FramesStationaryFall); Append(signature, body.Acceleration); Append(signature, body.Omega); Append(signature, body.GroundNormal); Append(signature, body.SlidingNormal); Append(signature, body.ContactPlaneValid); Append(signature, body.ContactPlane); Append(signature, body.ContactPlaneCellId); Append(signature, body.ContactPlaneIsWater); Append(signature, body.WalkablePolygonValid); Append(signature, body.WalkablePlane); if (body.WalkableVertices is null) { Append(signature, -1); } else { Append(signature, body.WalkableVertices.Length); foreach (Vector3 vertex in body.WalkableVertices) Append(signature, vertex); } Append(signature, body.WalkableUp); Append(signature, body.Elasticity); Append(signature, body.Friction); Append(signature, (uint)body.State); Append(signature, (uint)body.TransientState); Append(signature, BitConverter.DoubleToUInt64Bits(body.LastUpdateTime)); Append(signature, body.IsFullyConstrained); Append(signature, body.LastMoveWasAutonomous); return signature.ToString(); } private static void Append(StringBuilder target, bool value) => target.Append(value ? "1|" : "0|"); private static void Append(StringBuilder target, int value) => target.Append(value).Append('|'); private static void Append(StringBuilder target, uint value) => target.Append(value.ToString("X8")).Append('|'); private static void Append(StringBuilder target, ulong value) => target.Append(value.ToString("X16")).Append('|'); private static void Append(StringBuilder target, float value) => Append(target, BitConverter.SingleToUInt32Bits(value)); private static void Append(StringBuilder target, Vector3 value) { Append(target, value.X); Append(target, value.Y); Append(target, value.Z); } private static void Append(StringBuilder target, Quaternion value) { Append(target, value.X); Append(target, value.Y); Append(target, value.Z); Append(target, value.W); } private static void Append(StringBuilder target, Plane value) { Append(target, value.Normal); Append(target, value.D); } private static void AssertFinite(Vector3 value, string context) { Assert.True( float.IsFinite(value.X) && float.IsFinite(value.Y) && float.IsFinite(value.Z), $"Non-finite position in {context}: {value}."); } private sealed record TraceRun( List Frames, int LandedFrame, int LedgeStartFrame); private sealed record WallMaintenanceTrace( List Frames, int SupportPasses, int PlacementPasses); private sealed record TraceFrame( ResolveResult Result, string ResolveBits, string BodyBits, bool BodyContactPlaneValid, Plane BodyContactPlane, bool BodyWalkablePolygonValid, TransientStateFlags BodyTransientState); }