feat(runtime): own SetPosition collision reports

This commit is contained in:
Erik 2026-08-01 00:15:11 +02:00
parent ec627c13a2
commit 237d1184d2
19 changed files with 3744 additions and 103 deletions

View file

@ -81,14 +81,53 @@ public static class PhysicsObjUpdate
Action? leaveGround = null,
Func<bool>? isCurrent = null,
Func<bool>? isVelocityCurrent = null)
{
if (!CommitSetPositionContactTransition(
body,
inContact,
onWalkable,
previousOnWalkable,
hitGround,
leaveGround,
isCurrent))
{
return false;
}
// Position, Vector, and Movement are independently timestamped but
// can all install m_velocityVector. If a later one arrived from a
// callback above, retain its vector and finish the non-overlapping
// contact/pose commit without applying this older collision response.
if (isVelocityCurrent?.Invoke() == false)
return isCurrent?.Invoke() ?? true;
HandleAllCollisions(
body,
collisionNormalValid,
collisionNormal,
previousContact,
previousOnWalkable,
body.OnWalkable);
return isCurrent?.Invoke() ?? true;
}
/// <summary>
/// Commits retail's Contact/OnWalkable and HitGround/LeaveGround prefix,
/// stopping immediately before <c>handle_all_collisions</c>. Runtime uses
/// this seam to run the canonical collision-table reports without adding
/// another per-placement delegate allocation.
/// </summary>
public static bool CommitSetPositionContactTransition(
PhysicsBody body,
bool inContact,
bool onWalkable,
bool previousOnWalkable,
Action? hitGround = null,
Action? leaveGround = null,
Func<bool>? isCurrent = null)
{
ArgumentNullException.ThrowIfNull(body);
// SetPositionInternal replaces Contact first but retains the source
// OnWalkable bit through its first calc_acceleration call. A deferred
// teleport may have parked the live body with both bits cleared, so
// restore the captured source bit explicitly before reproducing that
// ordering.
if (previousOnWalkable)
body.TransientState |= TransientStateFlags.OnWalkable;
else
@ -106,10 +145,6 @@ public static class PhysicsObjUpdate
else
body.TransientState &= ~TransientStateFlags.OnWalkable;
// AP-10 (Campaign P Slice P4, 2026-07-30): mirror WATER_CONTACT_TS
// alongside CONTACT_TS/ON_WALKABLE_TS, same as ApplySetPositionContact.
// Callers (e.g. RemoteTeleportPlacement) already set body.ContactPlaneIsWater
// before invoking this commit.
if (body.ContactPlaneIsWater)
body.TransientState |= TransientStateFlags.WaterContact;
else
@ -128,21 +163,6 @@ public static class PhysicsObjUpdate
return false;
}
body.calc_acceleration();
// Position, Vector, and Movement are independently timestamped but
// can all install m_velocityVector. If a later one arrived from a
// callback above, retain its vector and finish the non-overlapping
// contact/pose commit without applying this older collision response.
if (isVelocityCurrent?.Invoke() == false)
return isCurrent?.Invoke() ?? true;
HandleAllCollisions(
body,
collisionNormalValid,
collisionNormal,
previousContact,
previousOnWalkable,
finalOnWalkable);
return isCurrent?.Invoke() ?? true;
}
@ -179,14 +199,14 @@ public static class PhysicsObjUpdate
// is now owned by the SetPositionInternal-derived contact flags, not a Velocity.Z<=0
// gate). A grounded corridor wall-slide keeps its tangential velocity (should_reflect
// false), exactly as retail.
bool sledding = body.State.HasFlag(PhysicsStateFlags.Sledding);
bool sledding = (body.State & PhysicsStateFlags.Sledding) != 0;
bool shouldReflect = !(prevOnWalkable && nowOnWalkable && !sledding);
if (body.FramesStationaryFall <= 1)
{
if (shouldReflect && collisionNormalValid)
{
if (body.State.HasFlag(PhysicsStateFlags.Inelastic))
if ((body.State & PhysicsStateFlags.Inelastic) != 0)
{
body.Velocity = Vector3.Zero; // pc:282720-282722
}