fix(physics): port retail slope landing stop
This commit is contained in:
parent
1d8371dbe5
commit
5a0f9868a6
13 changed files with 870 additions and 103 deletions
|
|
@ -5530,6 +5530,30 @@ public sealed class Transition
|
|||
else if (transitionState != TransitionState.Invalid)
|
||||
{
|
||||
// Collision/slide/adjusted: revert to current position.
|
||||
// Retail CTransition::validate_transition 0x0050AA70 consumes a
|
||||
// remembered contact plane only on this non-OK recovery path. It
|
||||
// first calls OBJECTINFO::kill_velocity, then restores the plane
|
||||
// when the current sphere remains within radius + EPSILON. Omitting
|
||||
// the kill preserved a landing reflection while repeatedly
|
||||
// re-grounding the mover, producing Campaign P #269's long slide.
|
||||
if (ci.LastKnownContactPlaneValid)
|
||||
{
|
||||
oi.StopVelocity();
|
||||
|
||||
var sphereCenter = sp.GlobalCurrCenter[0].Origin;
|
||||
var radius = sp.GlobalSphere[0].Radius;
|
||||
float angle = Vector3.Dot(ci.LastKnownContactPlane.Normal, sphereCenter)
|
||||
+ ci.LastKnownContactPlane.D;
|
||||
|
||||
if (radius + PhysicsGlobals.EPSILON > MathF.Abs(angle))
|
||||
{
|
||||
ci.SetContactPlane(
|
||||
ci.LastKnownContactPlane,
|
||||
ci.LastKnownContactPlaneCellId,
|
||||
ci.LastKnownContactPlaneIsWater);
|
||||
}
|
||||
}
|
||||
|
||||
if (!ci.CollisionNormalValid)
|
||||
ci.SetCollisionNormal(Vector3.UnitZ); // default: push up
|
||||
|
||||
|
|
@ -5541,14 +5565,11 @@ public sealed class Transition
|
|||
if (ci.CollisionNormalValid)
|
||||
ci.SetSlidingNormal(ci.CollisionNormal);
|
||||
|
||||
// Preserve contact plane for next step.
|
||||
// L.2.3c (2026-04-29): only OVERWRITE LastKnown when current is valid.
|
||||
// Previously: `LastKnownValid = ContactPlaneValid` cleared
|
||||
// LastKnown whenever current was invalid — destroying the prior frame's
|
||||
// contact memory. After StepUpSlide cleared ContactPlane mid-step
|
||||
// (failed step-up against a too-tall wall), this propagated to
|
||||
// LastKnown and the player went airborne for a frame, flickering the
|
||||
// falling animation. Now LastKnown survives transient losses.
|
||||
// Retail 0x0050ACFF-0x0050AD9A overwrites last-known validity with
|
||||
// current contact validity, copies the plane only when valid, and then
|
||||
// derives Contact/OnWalkable from that final plane. A remembered plane
|
||||
// can have been restored only in the non-OK recovery branch above; it
|
||||
// must never re-ground a clean move away from the surface.
|
||||
if (ci.ContactPlaneValid)
|
||||
{
|
||||
ci.LastKnownContactPlaneValid = true;
|
||||
|
|
@ -5569,79 +5590,9 @@ public sealed class Transition
|
|||
else
|
||||
oi.State &= ~ObjectInfoState.OnWalkable;
|
||||
}
|
||||
else if (ci.LastKnownContactPlaneValid)
|
||||
{
|
||||
// L.2.3c: current contact lost transiently (e.g. StepUpSlide
|
||||
// cleared it during a failed step-up) but the prior frame's
|
||||
// contact is still valid — keep the mover grounded via the
|
||||
// last-known plane. Without this, every wall bump dropped the
|
||||
// player into the falling animation for one frame.
|
||||
//
|
||||
// L.2.4 (2026-04-30): PROXIMITY GUARD. Only trust the
|
||||
// last-known plane if the sphere is still actually near it.
|
||||
// Geometrically: `angle` is the signed distance from the
|
||||
// sphere center to the plane. If |angle| exceeds the sphere
|
||||
// radius (plus a tiny epsilon), the sphere has SEPARATED
|
||||
// from the plane — typically because we fell off an edge or
|
||||
// the body dropped vertically while the resolver bounced
|
||||
// through edge-slide attempts. Without this guard the player
|
||||
// gets stuck mid-fall in a falling animation forever (live
|
||||
// bug 2026-04-30: cur.Z=96.6, check.Z=95.1 — 1.5 m below the
|
||||
// remembered floor, but still being marked Contact + OnWalkable).
|
||||
//
|
||||
// Matches ACE PhysicsObj's pre-reuse check on the last-known
|
||||
// plane and retail's CPhysicsObj::get_object_info logic.
|
||||
// A6.P3 slice 1 (2026-05-21). Retail uses global_curr_center (NOT
|
||||
// global_sphere->center) for this proximity check — see
|
||||
// acclient_2013_pseudo_c.txt:272568. global_sphere is the START
|
||||
// sphere of the transition; global_curr_center is the CURRENT center
|
||||
// after sub-step accumulation. Using the wrong one made the proximity
|
||||
// guard fire on the wrong reference point.
|
||||
var sphereCenter = sp.GlobalCurrCenter[0].Origin;
|
||||
var radius = sp.GlobalSphere[0].Radius;
|
||||
float angle = Vector3.Dot(ci.LastKnownContactPlane.Normal, sphereCenter)
|
||||
+ ci.LastKnownContactPlane.D;
|
||||
|
||||
if (radius + PhysicsGlobals.EPSILON > MathF.Abs(angle))
|
||||
{
|
||||
// ── Mechanism B — restore CP from LKCP per retail ────────────────
|
||||
// A6.P3 slice 1 (2026-05-21). Retail oracle:
|
||||
// acclient_2013_pseudo_c.txt:272577 (inside CTransition::validate_transition
|
||||
// at line 272547). When the sphere is geometrically close to the
|
||||
// LastKnownContactPlane, retail restores CP from LKCP via
|
||||
// set_contact_plane(&collision_info, &last_known_contact_plane,
|
||||
// last_known_contact_plane_is_water). This closes the gap that the
|
||||
// stripped TryFindIndoorWalkablePlane synthesis path used to fill —
|
||||
// when no fresh Path-6 CP write lands in this transition, CP is
|
||||
// retained from the previous frame instead of being re-synthesized.
|
||||
//
|
||||
// NOTE: SetContactPlane also re-latches LKCP fields
|
||||
// (TransitionTypes.cs:258-261), which is a no-op here since we
|
||||
// pass LKCP as the source.
|
||||
ci.SetContactPlane(
|
||||
ci.LastKnownContactPlane,
|
||||
ci.LastKnownContactPlaneCellId,
|
||||
ci.LastKnownContactPlaneIsWater);
|
||||
|
||||
// Still close enough to the last-known plane — preserve
|
||||
// grounded state. L.2.3i FloorZ test for OnWalkable.
|
||||
oi.State |= ObjectInfoState.Contact;
|
||||
if (ci.LastKnownContactPlane.Normal.Z >= PhysicsGlobals.FloorZ)
|
||||
oi.State |= ObjectInfoState.OnWalkable;
|
||||
else
|
||||
oi.State &= ~ObjectInfoState.OnWalkable;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Sphere has separated from the last-known plane.
|
||||
// Drop the memory and let the body resolve normally
|
||||
// (gravity → next-frame terrain probe → real contact).
|
||||
ci.LastKnownContactPlaneValid = false;
|
||||
oi.State &= ~(ObjectInfoState.Contact | ObjectInfoState.OnWalkable);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ci.LastKnownContactPlaneValid = false;
|
||||
oi.State &= ~(ObjectInfoState.Contact | ObjectInfoState.OnWalkable);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue