using System.Numerics; namespace AcDream.Core.Physics; /// /// Verbatim port of the collision-response tail of retail /// CPhysicsObj::UpdateObjectInternal: the velocity decision that /// SetPositionInternal (0x00515330) drives through /// handle_all_collisions (0x00514780, pc:282647). Kept as a pure function over a /// + the resolve outcome so the whole decision is unit-testable /// in Core, independent of the App per-frame loop. The transition INTERNALS /// (ResolveWithTransition and below) are untouched. /// public static class PhysicsObjUpdate { /// /// retail handle_all_collisions (0x00514780). Reflects or zeros the body's /// (retail m_velocityVector) based on /// : /// /// fsf ≤ 1 → reflect the into-surface component /// (v += -(v·n)(elasticity+1)·n, pc:282712) when we should reflect and a /// collision normal is valid; an INELASTIC mover zeros instead (pc:282720). /// fsf > 1 → v = 0 entirely (pc:282729) — the "bleed on block" that /// lets gravity resume so a blocked jump falls/glides off (fixes the #182 /// airborne-stuck wedge). /// /// The Stationary* transient-bit round-trip (pc:282737-758) is owned by the Core resolve /// writeback (PhysicsEngine.ResolveWithTransition), not re-encoded here. /// /// The mover. /// Was a wall/creature collision normal recorded this resolve. /// Outward collision normal (points away from the surface). /// Whether the body had Contact BEFORE this resolve committed /// (retail arg3 — reserved for environment-collision reporting; unused today). /// Whether the body was OnWalkable before this resolve (retail arg4). /// Whether the body is OnWalkable after this resolve. 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. } }