using System.Numerics; using AcDream.App.Physics; using AcDream.Core.Net.Messages; using AcDream.Core.Physics; using AcDream.Core.Physics.Motion; using AcDream.Core.World; using AcDream.Runtime; using AcDream.Runtime.Entities; using AcDream.Runtime.Physics; namespace AcDream.App.Tests.Physics; /// /// C4 route 4a: behavioural proof that the generic top-of-OnPosition /// render-pose write no longer double-writes a remote whose accepted Position /// classifies to /// or /// , and /// still writes it for every classification route 4a does not own. /// /// /// These tests call the PRODUCTION decision-and-mutation entry point /// () /// against a real and real /// output. Nothing /// is re-derived in the test body: revert the suppression inside that method /// and the two suppression tests fail; widen it to route is not null /// and four of the five legacy-write tests fail (the fifth, /// , /// passes a null route and so survives that particular widening — it /// discriminates the OTHER direction, an over-narrow guard). That is the gap /// #292 recorded for route 2's source-text pin. /// /// /// /// The suite also pins the ORDERING carve-out /// (): /// an airborne body's contact packet classifies Interpolate, so /// precedence — not classification — is the only thing keeping a landing /// creature planting instead of gliding. /// /// public sealed class LiveEntityNetworkRemoteSteadyStateIntegrationTests { private const uint OldCell = 0x01010001u; private const uint WireCell = 0x01010002u; private static readonly Vector3 OldPosition = new(5f, 5f, 5f); private static readonly Vector3 WirePosition = new(6f, 7f, 8f); [Fact] public void NoPositionOperation_LeavesTheRenderEntityPoseAndCellUntouched() { // Retail's airborne no-op (D1): MoveOrTeleport @0x00516330 returns 0 // for arg4 == 0 and writes nothing. CommittedCellId is nonzero so the // cell-less SetPosition branch cannot be what is selected here. RuntimeAuthoritativePositionRoute route = Classify(hasContact: false, playerDistance: 1f); Assert.Equal( RuntimeAuthoritativePositionDisposition.NoPositionOperation, route.Disposition); AssertRenderPoseSuppressed(route); } [Fact] public void Interpolate_LeavesTheRenderEntityPoseAndCellUntouched() { // Retail's near InterpolateTo queue @0x005163AF. The resolved body, // not the raw wire packet, is the branch tail's only writer of the // render entity — writing the wire pose here first would be the // second writer route 2's original defect consisted of. RuntimeAuthoritativePositionRoute route = Classify(hasContact: true, playerDistance: 10f); Assert.Equal( RuntimeAuthoritativePositionDisposition.Interpolate, route.Disposition); AssertRenderPoseSuppressed(route); } [Fact] public void FarSetPositionSimple_StillTakesTheLegacyRenderPoseWrite() { RuntimeAuthoritativePositionRoute route = Classify(hasContact: true, playerDistance: 200f); Assert.Equal( RuntimeAuthoritativePositionDisposition.SetPositionSimple, route.Disposition); AssertRenderPoseWritten(route); } [Fact] public void CellLessRemote_StillTakesTheLegacyRenderPoseWrite() { RuntimeAuthoritativePositionRoute route = Classify( hasContact: false, playerDistance: 1f, committedCellId: 0u); Assert.Equal( RuntimeAuthoritativePositionDisposition.SetPosition, route.Disposition); AssertRenderPoseWritten(route); } [Fact] public void RejectedAuthority_StillTakesTheLegacyRenderPoseWrite() { RuntimeAuthoritativePositionRoute route = Classify( hasContact: true, playerDistance: 10f, disposition: PositionTimestampDisposition.Rejected); Assert.Equal( RuntimeAuthoritativePositionDisposition.RejectedAuthority, route.Disposition); AssertRenderPoseWritten(route); } [Fact] public void RejectedData_StillTakesTheLegacyRenderPoseWrite() { RuntimeAuthoritativePositionRoute route = Classify(hasContact: true, playerDistance: float.NaN); Assert.Equal( RuntimeAuthoritativePositionDisposition.RejectedData, route.Disposition); AssertRenderPoseWritten(route); } [Fact] public void NoClassificationAtAll_StillTakesTheLegacyRenderPoseWrite() { // The login window (no local-player controller yet) and a canonical // record that has not claimed a local id both produce a null route. // Its doc comment promises the legacy path runs unchanged — so the // write must still happen. AssertRenderPoseWritten(null); } // ── Ordering: the airborne snap outranks route 4a's near branch ───────── [Fact] public void AirborneBodyWithAContactPacket_SnapsInsteadOfEnqueuing() { // The NPC LANDING packet: the client still believes the body airborne // and ACE reports it in contact, close by. That classifies // Interpolate — and AP-87's conditions are all benign (0.5 m, sampled, // DR-ticked) — so route 4a's branch would ENQUEUE it. It must SNAP: // a drudge knocked off a ledge plants, it does not glide to the // ground over a packet interval. RuntimeAuthoritativePositionRoute route = Classify(hasContact: true, playerDistance: 10f); Assert.Equal( RuntimeAuthoritativePositionDisposition.Interpolate, route.Disposition); using var fixture = new RemotePlacementDriveFixture(); (RuntimeEntityRecord record, RemoteMotion remote, _) = fixture.AddRemote(0x70005001u, new Vector3(12f, 14f, 7f)); remote.Body.Position = new Vector3(10f, 10f, 5f); // AP-140 (retired 2026-08-04): free flight is retail's CONTACT_TS-clear // (`InterpolationManager::adjust_offset` gates on `transient_state & 1` // @0x00555D52), not the client `Airborne` walkability flag. Both are // stated so the test reads the same before and after that change. remote.Airborne = true; remote.Body.TransientState = TransientStateFlags.Active; var landing = new Vector3(10.5f, 10f, 5f); // 0.5 m — well within 4 m. LiveEntityNetworkUpdateController.RemoteContactRouting routing = LiveEntityNetworkUpdateController.ApplyRemoteContactRouting( fixture.Drive, record, remote, route, landing, Quaternion.Identity, willBeDrTicked: true, runTeleportHook: () => true); Assert.Equal( LiveEntityNetworkUpdateController.RemoteContactArm.AirborneSnap, routing.Arm); Assert.Equal(landing, remote.Body.Position); } [Fact] public void GroundedBodyWithANearContactPacket_TakesRoute4asInterpolateBranch() { // The same packet on a body the client already considers grounded is // route 4a's own branch: queued for the per-tick catch-up, body // untouched. This is what proves the airborne test above is a // PRECEDENCE carve-out and not a blanket disable of route 4a. using var fixture = new RemotePlacementDriveFixture(); (RuntimeEntityRecord record, RemoteMotion remote, _) = fixture.AddRemote(0x70005002u, new Vector3(12f, 14f, 7f)); remote.Body.Position = new Vector3(10f, 10f, 5f); remote.Airborne = false; var target = new Vector3(10.5f, 10f, 5f); LiveEntityNetworkUpdateController.RemoteContactRouting routing = LiveEntityNetworkUpdateController.ApplyRemoteContactRouting( fixture.Drive, record, remote, Classify(hasContact: true, playerDistance: 10f), target, Quaternion.Identity, willBeDrTicked: true, runTeleportHook: () => true); Assert.Equal( LiveEntityNetworkUpdateController.RemoteContactArm .SteadyStateInterpolate, routing.Arm); Assert.Equal(new Vector3(10f, 10f, 5f), remote.Body.Position); } // ── AP-140 (retired 2026-08-04): the gate's predicate is CONTACT ──────── /// /// The divergence AP-140 recorded, as behaviour. A remote in CONTACT with /// a NON-walkable face — sliding down a steep roof — is /// Airborne == true by the client flag's own definition /// (!Body.OnWalkable), and used to be hard-snapped at /// UpdatePosition cadence. Retail interpolates it: /// InterpolationManager::adjust_offset @0x00555D30 gates its entire /// body on transient_state & 1 @0x00555D52, and bit 0 is /// CONTACT_TS (acclient.h:3690), not ON_WALKABLE_TS (0x2). /// /// /// This is the ONE state on which the two predicates disagree, so it is /// the only test that can discriminate the fix: point the gate back at /// remote.Airborne and this test alone fails, with /// AirborneSnap and a moved body. /// /// [Fact] public void SteepContactBody_InterpolatesInsteadOfSnapping() { using var fixture = new RemotePlacementDriveFixture(); (RuntimeEntityRecord record, RemoteMotion remote, _) = fixture.AddRemote(0x70005003u, new Vector3(12f, 14f, 7f)); var before = new Vector3(10f, 10f, 5f); remote.Body.Position = before; // Contact with a face too steep to stand on: CONTACT set, ON_WALKABLE // clear — exactly what the sweep's SetPositionInternal commit derives // on a 52-degree roof since Bug B (204d0ae0) deleted the per-tick // walkability forge that used to make this state unreachable. remote.Body.TransientState = TransientStateFlags.Active | TransientStateFlags.Contact; Assert.True(remote.Body.InContact); Assert.False(remote.Body.OnWalkable); // The client flag agrees with its five writers, all `!Body.OnWalkable`. remote.Airborne = !remote.Body.OnWalkable; Assert.True(remote.Airborne); LiveEntityNetworkUpdateController.RemoteContactRouting routing = LiveEntityNetworkUpdateController.ApplyRemoteContactRouting( fixture.Drive, record, remote, Classify(hasContact: true, playerDistance: 10f), before + new Vector3(0.5f, 0f, 0f), Quaternion.Identity, willBeDrTicked: true, runTeleportHook: () => true); Assert.Equal( LiveEntityNetworkUpdateController.RemoteContactArm .SteadyStateInterpolate, routing.Arm); // The interpolate arm enqueues a waypoint and leaves the body to the // per-tick catch-up; the snap arm would have written it directly. Assert.Equal(before, remote.Body.Position); } /// /// The regression guard on the other side of the same gate: a genuinely /// free-flying remote — no contact with anything — must still take the /// hard snap. Retail's adjust_offset writes nothing at all with /// CONTACT_TS clear, so the queued waypoint cannot advance the /// body and the authoritative position is the only thing that can. /// Widen the gate to "always interpolate" and this test fails. /// [Fact] public void FreeFlightBodyWithNoContact_StillSnaps() { using var fixture = new RemotePlacementDriveFixture(); (RuntimeEntityRecord record, RemoteMotion remote, _) = fixture.AddRemote(0x70005004u, new Vector3(12f, 14f, 7f)); remote.Body.Position = new Vector3(10f, 10f, 5f); remote.Body.TransientState = TransientStateFlags.Active; Assert.False(remote.Body.InContact); remote.Airborne = !remote.Body.OnWalkable; var landing = new Vector3(10.5f, 10f, 5f); LiveEntityNetworkUpdateController.RemoteContactRouting routing = LiveEntityNetworkUpdateController.ApplyRemoteContactRouting( fixture.Drive, record, remote, Classify(hasContact: true, playerDistance: 10f), landing, Quaternion.Identity, willBeDrTicked: true, runTeleportHook: () => true); Assert.Equal( LiveEntityNetworkUpdateController.RemoteContactArm.AirborneSnap, routing.Arm); Assert.Equal(landing, remote.Body.Position); } /// /// The unchanged third state: contact with WALKABLE ground. Both /// predicates agree here, and both before and after AP-140's fix the /// packet interpolates. Pinned so a future edit cannot "simplify" the /// gate into something that only satisfies the two tests above. /// [Fact] public void WalkableGroundedBody_IsUnchangedAndStillInterpolates() { using var fixture = new RemotePlacementDriveFixture(); (RuntimeEntityRecord record, RemoteMotion remote, _) = fixture.AddRemote(0x70005005u, new Vector3(12f, 14f, 7f)); var before = new Vector3(10f, 10f, 5f); remote.Body.Position = before; remote.Body.TransientState = TransientStateFlags.Active | TransientStateFlags.Contact | TransientStateFlags.OnWalkable; remote.Airborne = !remote.Body.OnWalkable; Assert.False(remote.Airborne); LiveEntityNetworkUpdateController.RemoteContactRouting routing = LiveEntityNetworkUpdateController.ApplyRemoteContactRouting( fixture.Drive, record, remote, Classify(hasContact: true, playerDistance: 10f), before + new Vector3(0.5f, 0f, 0f), Quaternion.Identity, willBeDrTicked: true, runTeleportHook: () => true); Assert.Equal( LiveEntityNetworkUpdateController.RemoteContactArm .SteadyStateInterpolate, routing.Arm); Assert.Equal(before, remote.Body.Position); } private static void AssertRenderPoseSuppressed( RuntimeAuthoritativePositionRoute? route) { WorldEntity entity = MakeEntity(); var beforeRotation = entity.Rotation; bool written = LiveEntityNetworkUpdateController .TryApplyGenericRemoteRenderPose( entity, route, WirePosition, WireCell, Quaternion.CreateFromAxisAngle(Vector3.UnitZ, 1.1f)); Assert.False(written); Assert.Equal(OldPosition, entity.Position); Assert.Equal(OldCell, entity.ParentCellId); Assert.Equal(beforeRotation, entity.Rotation); } private static void AssertRenderPoseWritten( RuntimeAuthoritativePositionRoute? route) { WorldEntity entity = MakeEntity(); var wireRotation = Quaternion.CreateFromAxisAngle(Vector3.UnitZ, 1.1f); bool written = LiveEntityNetworkUpdateController .TryApplyGenericRemoteRenderPose( entity, route, WirePosition, WireCell, wireRotation); Assert.True(written); Assert.Equal(WirePosition, entity.Position); Assert.Equal(WireCell, entity.ParentCellId); Assert.Equal(wireRotation, entity.Rotation); } private static WorldEntity MakeEntity() => new() { Id = 1u, SourceGfxObjOrSetupId = 1u, Position = OldPosition, Rotation = Quaternion.Identity, MeshRefs = Array.Empty(), ParentCellId = OldCell, }; private const uint Cell = 0x0101FFFFu; private static RuntimeAuthoritativePositionRoute Classify( bool hasContact, float playerDistance, uint committedCellId = OldCell, PositionTimestampDisposition disposition = PositionTimestampDisposition.Apply) => RuntimeAuthoritativePositionRouteClassifier.ClassifyAcceptedPosition( new RuntimeAcceptedPositionRouteRequest( new RuntimeAuthoritativePositionAuthority( new RuntimeGenerationToken(7), new RuntimeEntityKey(0x70000001u, 3), PositionAuthorityVersion: 11UL, AcceptedPositionSequence: 20, PreviousTeleportSequence: 10, AcceptedTeleportSequence: 10, disposition), RuntimePositionEntityKind.Remote, RuntimeAcceptedPositionSource.PositionEvent, new CreateObject.ServerPosition( Cell, 10f, 20f, 30f, 1f, 0f, 0f, 0f), PlacementFrame: 0u, PositionPackVelocity: Vector3.Zero, committedCellId, hasContact, playerDistance, UsePositionFromServer: false, HasAnimations: false, default)); }