using System.Numerics; using AcDream.Core.Net; using AcDream.Core.Net.Messages; using AcDream.Core.Physics; using AcDream.Core.Physics.Motion; using AcDream.Runtime.Entities; using AcDream.Runtime.Physics; namespace AcDream.Runtime.Tests.Physics; /// /// Bug B (2026-08-04) — remote characters froze on steep surfaces instead of /// sliding. The per-tick remote owner forged retail's two contact transients /// (Contact | OnWalkable) before every sweep, discarded the /// authoritative velocity, decided its landing edge from the contact-derived /// ResolveResult.IsOnGround rather than the plane-derived /// OnWalkable, and cleared the persistent Gravity state bit. /// /// /// Retail derives all of it: CPhysicsObj::SetPositionInternal /// (0x00515330) writes CONTACT_TS from /// collision_info.contact_plane_valid (0x00515430) and then routes /// ON_WALKABLE_TS through set_on_walkable (0x00511310) purely on /// contact_plane.N.z < PhysicsGlobals::floor_z /// (0x00515465-0x0051548E). set_on_walkable is the SOLE source of /// MovementManager::HitGround/::LeaveGround. Gravity survives a /// steep contact because calc_acceleration (0x00510950) only /// zeroes acceleration when CONTACT and ON_WALKABLE are BOTH set, and /// calc_friction (0x0050EE70) returns at its first line when /// ON_WALKABLE is clear. /// /// /// /// Every test here runs the production /// tick over a synthetic landblock whose terrain is a single constant-gradient /// ramp, so the contact plane the sweep finds is a real geometric result, not a /// stubbed value. /// /// public sealed class RuntimeRemoteSteepContactSlideTests { /// /// Ramp gradient chosen so the terrain-plane normal's Z lands just under /// retail's walkable limit — 52.4 degrees against a 48.4-degree limit, the /// same relationship as the live house roof that produced the freeze /// (measured contact-plane Normal.Z 0.6097 versus FloorZ 0.6642). /// private const float SteepGradient = 1.30f; /// A gentle ramp that is comfortably walkable. private const float WalkableGradient = 0.10f; [Fact] public void SteepTerrainProducesANonWalkableContactPlane() { using RemoteRampHarness harness = RemoteRampHarness.OnRamp(SteepGradient); Assert.True(harness.Remote.Body.ContactPlaneValid); Assert.InRange( harness.Remote.Body.ContactPlane.Normal.Z, 0.55f, PhysicsGlobals.FloorZ - 0.001f); } /// /// The landing edge must be the sweep's plane-derived /// OnWalkable, never IsOnGround (which is /// inContact || … and is therefore TRUE on a steep contact). /// [Fact] public void SteepContactDoesNotLatchALanding() { using RemoteRampHarness harness = RemoteRampHarness.OnRamp(SteepGradient); harness.Remote.Airborne = true; int groundEdges = 0; harness.Remote.Motion.RemoveLinkAnimations = () => groundEdges++; harness.Tick(40); Assert.True(harness.Remote.Body.InContact); Assert.False(harness.Remote.Body.OnWalkable); Assert.Equal(0, groundEdges); } /// /// Gravity is a persistent object property in retail; nothing on a ground /// edge may clear it. Before the fix both landing blocks did, which is why /// calc_acceleration returned zero forever afterwards. /// [Fact] public void GravityPersistsAcrossTicksOnASteepContact() { using RemoteRampHarness harness = RemoteRampHarness.OnRamp(SteepGradient); harness.Remote.Airborne = true; harness.Tick(40); Assert.True(harness.Remote.Body.HasGravity); Assert.True(harness.Remote.Body.Acceleration.Z < -1f); } /// /// The visible consequence: a remote resting on a non-walkable face keeps /// moving. Before the fix the body reported moved=0.0000 on every /// tick, forever. /// [Fact] public void SteepContactKeepsTheBodySlidingDownhill() { using RemoteRampHarness harness = RemoteRampHarness.OnRamp(SteepGradient); Vector3 start = harness.Remote.Body.Position; harness.Tick(40); Vector3 travelled = harness.Remote.Body.Position - start; Assert.True( travelled.Length() > 0.25f, $"expected a slide, body moved {travelled.Length():F4} m"); Assert.True( travelled.Z < -0.1f, $"expected downhill travel, dz = {travelled.Z:F4} m"); } /// /// The direct statement of "stop forging inputs": with no sweep to derive /// from — no starting cell, so ResolveWithTransition is skipped /// entirely — the tick must leave both retail transients exactly as it /// found them. Retail's only writer is SetPositionInternal /// (0x00515330), which a skipped transition never reaches. /// [Fact] public void TheTickNeverAssertsContactOrWalkableWithoutASweep() { using RemoteRampHarness harness = RemoteRampHarness.OnRamp(WalkableGradient); harness.Remote.CellId = 0u; harness.Remote.Body.TransientState &= ~(TransientStateFlags.Contact | TransientStateFlags.OnWalkable); harness.Remote.Airborne = false; harness.Tick(1); Assert.False(harness.Remote.Body.InContact); Assert.False(harness.Remote.Body.OnWalkable); } /// /// The tick immediately after a body crossed from walkable ground onto a /// steep face: it enters carrying last tick's grounded transients and its /// downhill speed. The tick must NOT re-assert those transients — with /// Contact | OnWalkable forced, calc_acceleration /// (0x00510950) returns zero and calc_friction /// (0x0050EE70) engages, so the body decelerates to a stop on a face /// retail would keep accelerating it down. /// [Fact] public void AGroundedTickOnASteepFaceReleasesTheBodyInsteadOfPinningIt() { using RemoteRampHarness harness = RemoteRampHarness.OnRamp(SteepGradient); harness.Remote.Body.TransientState |= TransientStateFlags.Contact | TransientStateFlags.OnWalkable; harness.Remote.Airborne = false; harness.Remote.Body.Velocity = Vector3.Normalize(new Vector3(0f, 1f, -SteepGradient)) * 3f; Vector3 start = harness.Remote.Body.Position; harness.Tick(40); Assert.False(harness.Remote.Body.OnWalkable); float travelled = (harness.Remote.Body.Position - start).Length(); Assert.True( travelled > 2f, $"expected the steep face to release the body, travelled {travelled:F3} m"); } /// /// Retail's MoveOrTeleport (0x00516330) never reads or writes /// the wire velocity for a remote; the deleted per-tick /// Body.Velocity = Zero threw away whatever ACE delivered through /// 0xF74E as well as everything gravity had accumulated. /// [Fact] public void AuthoritativeVelocityIsNotDiscardedOnAGroundedTick() { using RemoteRampHarness harness = RemoteRampHarness.OnRamp(WalkableGradient); Assert.False(harness.Remote.Airborne); harness.Remote.Body.Velocity = new Vector3(2.146f, 2.264f, -3.549f); harness.Tick(1); Assert.NotEqual(Vector3.Zero, harness.Remote.Body.Velocity); Assert.True( harness.Remote.Body.Velocity.X > 0.5f, $"velocity X was {harness.Remote.Body.Velocity.X:F4}"); } /// /// The committed transients must be the ones the sweep's contact plane /// implies — Contact from plane validity, OnWalkable from /// Normal.Z >= floor_z — and never an independently asserted pair. /// /// /// Deliberately NOT stated as the two equalities /// ContactPlaneValid == InContact and /// IsWalkableContact(committed plane) == OnWalkable. Neither is an /// invariant of the production code, and this test asserted both until the /// 2026-08-04 review: PhysicsEngine.ResolveWithTransition publishes /// the contact plane whenever the transition returned ok, while the /// transient commit additionally requires candidateMoved /// (RuntimeRemotePhysicsUpdater's SetPositionInternal commit, /// matching retail UpdateObjectInternal pc:283657), so a zero-move /// frame can legitimately leave the two one tick apart. The same writeback /// also falls back to LastKnownContactPlane, which keeps /// ContactPlaneValid true across a contact-FREE frame by design. /// The old assertions passed only because every body in this fixture moves /// on every tick. What is asserted instead is what the commit path DOES /// guarantee — the two implications — plus each fixture's known ramp /// geometry checked on BOTH sides of retail's walkability comparison, so /// re-forging Contact | OnWalkable still fails the steep case. /// /// [Fact] public void CommittedTransientsAgreeWithTheCommittedContactPlane() { using RemoteRampHarness steep = RemoteRampHarness.OnRamp(SteepGradient); steep.Tick(20); AssertTransientsAreContactPlaneDerived( steep.Remote.Body, expectWalkable: false); using RemoteRampHarness gentle = RemoteRampHarness.OnRamp(WalkableGradient); gentle.Tick(20); AssertTransientsAreContactPlaneDerived( gentle.Remote.Body, expectWalkable: true); } private static void AssertTransientsAreContactPlaneDerived( PhysicsBody body, bool expectWalkable) { // Unconditional: OnWalkable is only ever written as // `inContact && onWalkable` // (PhysicsObjUpdate.CommitSetPositionContactPrefix), so it cannot // outlive Contact on any frame, committed or not. Assert.True( !body.OnWalkable || body.InContact, "OnWalkable without Contact — the two transients were asserted " + "independently of the contact plane"); // Contact is written from the sweep's contact-plane validity by the // same resolve that publishes the plane, so a body in contact carries // a valid plane. The CONVERSE is not guaranteed — see the summary. Assert.True( !body.InContact || body.ContactPlaneValid, "Contact without a valid contact plane — Contact was not " + "plane-derived"); // Both sides of retail's walkability comparison // (SetPositionInternal 0x00515465-0x0051548E) against this fixture's // known constant-gradient ramp: the plane the sweep found, and the // transient that plane drove. Assert.Equal( expectWalkable, body.ContactPlane.Normal.Z >= PhysicsGlobals.FloorZ); Assert.Equal(expectWalkable, body.OnWalkable); } /// /// The other half of the edge: a genuine walkable landing must still fire /// retail's set_on_walkable(1) -> MovementManager::HitGround /// exactly once and leave the body grounded. /// [Fact] public void WalkableLandingStillLandsAndFiresTheGroundEdgeOnce() { using RemoteRampHarness harness = RemoteRampHarness.Airborne(WalkableGradient, height: 3f); int groundEdges = 0; harness.Remote.Motion.RemoveLinkAnimations = () => groundEdges++; harness.Tick(60); Assert.True(harness.Remote.Body.OnWalkable); Assert.False(harness.Remote.Airborne); Assert.Equal(1, groundEdges); } /// /// GRAVITY_PS is set by the retail CPhysicsObj constructor /// (state 0x400C08 @0x00512508) and thereafter assigned wholesale from the /// wire by set_description's set_state (0x00514DD0), /// which post-processes only lighting/nodraw/hidden. No ground edge /// anywhere in retail toggles it — acdream's two landing blocks did, which /// is what left a landed remote permanently unable to fall again. /// [Fact] public void WalkableLandingDoesNotClearTheGravityStateBit() { using RemoteRampHarness harness = RemoteRampHarness.Airborne(WalkableGradient, height: 3f); harness.Tick(60); Assert.True(harness.Remote.Body.OnWalkable); Assert.True(harness.Remote.Body.HasGravity); } }