feat(#182): port handle_all_collisions as a pure Core unit (Slice 2a)
PhysicsObjUpdate.HandleAllCollisions — retail CPhysicsObj::handle_all_collisions (0x00514780, pc:282647): the velocity 'bleed on block' decision. fsf<=1 → reflect the into-surface component (v += -(v·n)(elasticity+1)·n) unless staying-on-walkable (retail's should_reflect guard, restoring the broader rule AD-25 suppressed); INELASTIC zeros instead; fsf>1 → v=0 entirely (the airborne-stuck fix). Bit round-trip owned by the Core resolve writeback (Slice 1), not re-encoded here. 7 conformance tests green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
6c3cd96b7a
commit
78df1370b6
2 changed files with 189 additions and 0 deletions
78
src/AcDream.Core/Physics/PhysicsObjUpdate.cs
Normal file
78
src/AcDream.Core/Physics/PhysicsObjUpdate.cs
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
using System.Numerics;
|
||||
|
||||
namespace AcDream.Core.Physics;
|
||||
|
||||
/// <summary>
|
||||
/// Verbatim port of the collision-response tail of retail
|
||||
/// <c>CPhysicsObj::UpdateObjectInternal</c>: the velocity decision that
|
||||
/// <c>SetPositionInternal</c> (0x00515330) drives through
|
||||
/// <c>handle_all_collisions</c> (0x00514780, pc:282647). Kept as a pure function over a
|
||||
/// <see cref="PhysicsBody"/> + the resolve outcome so the whole decision is unit-testable
|
||||
/// in Core, independent of the App per-frame loop. The transition INTERNALS
|
||||
/// (<c>ResolveWithTransition</c> and below) are untouched.
|
||||
/// </summary>
|
||||
public static class PhysicsObjUpdate
|
||||
{
|
||||
/// <summary>
|
||||
/// retail <c>handle_all_collisions</c> (0x00514780). Reflects or zeros the body's
|
||||
/// <see cref="PhysicsBody.Velocity"/> (retail m_velocityVector) based on
|
||||
/// <see cref="PhysicsBody.FramesStationaryFall"/>:
|
||||
/// <list type="bullet">
|
||||
/// <item>fsf ≤ 1 → reflect the into-surface component
|
||||
/// (<c>v += -(v·n)(elasticity+1)·n</c>, pc:282712) when we should reflect and a
|
||||
/// collision normal is valid; an INELASTIC mover zeros instead (pc:282720).</item>
|
||||
/// <item>fsf > 1 → <c>v = 0</c> entirely (pc:282729) — the "bleed on block" that
|
||||
/// lets gravity resume so a blocked jump falls/glides off (fixes the #182
|
||||
/// airborne-stuck wedge).</item>
|
||||
/// </list>
|
||||
/// The Stationary* transient-bit round-trip (pc:282737-758) is owned by the Core resolve
|
||||
/// writeback (<c>PhysicsEngine.ResolveWithTransition</c>), not re-encoded here.
|
||||
/// </summary>
|
||||
/// <param name="body">The mover.</param>
|
||||
/// <param name="collisionNormalValid">Was a wall/creature collision normal recorded this resolve.</param>
|
||||
/// <param name="collisionNormal">Outward collision normal (points away from the surface).</param>
|
||||
/// <param name="prevContact">Whether the body had Contact BEFORE this resolve committed
|
||||
/// (retail arg3 — reserved for environment-collision reporting; unused today).</param>
|
||||
/// <param name="prevOnWalkable">Whether the body was OnWalkable before this resolve (retail arg4).</param>
|
||||
/// <param name="nowOnWalkable">Whether the body is OnWalkable after this resolve.</param>
|
||||
public static void HandleAllCollisions(
|
||||
PhysicsBody body,
|
||||
bool collisionNormalValid, Vector3 collisionNormal,
|
||||
bool prevContact, bool prevOnWalkable, bool nowOnWalkable)
|
||||
{
|
||||
// var_10_1 (pc:282653-282657): reflect UNLESS the mover stays on walkable ground
|
||||
// (and is not sledding). This restores retail's broader rule — the AD-25 airborne-only
|
||||
// suppression is retired: the landing-snap fragility it guarded is gone (landing state
|
||||
// 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 shouldReflect = !(prevOnWalkable && nowOnWalkable && !sledding);
|
||||
|
||||
if (body.FramesStationaryFall <= 1)
|
||||
{
|
||||
if (shouldReflect && collisionNormalValid)
|
||||
{
|
||||
if (body.State.HasFlag(PhysicsStateFlags.Inelastic))
|
||||
{
|
||||
body.Velocity = Vector3.Zero; // pc:282720-282722
|
||||
}
|
||||
else
|
||||
{
|
||||
float dot = Vector3.Dot(body.Velocity, collisionNormal);
|
||||
if (dot < 0f) // moving INTO the surface
|
||||
{
|
||||
float k = -(dot * (body.Elasticity + 1f)); // pc:282712
|
||||
body.Velocity += collisionNormal * k;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
body.Velocity = Vector3.Zero; // fsf>1 → THE BLEED (pc:282729)
|
||||
}
|
||||
|
||||
_ = prevContact; // retail report_environment_collision(arg3) — weenie collision events, later.
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue