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