perf(physics): reuse reset-complete transition scratch

Mirror retail's ten-deep LIFO transition lifetime, retain all query scratch with complete reset contracts, and remove Tier-0 enum boxing without changing collision decisions. Fresh and retained engines are bit-identical across the expanded oracle, while measured transition profiles now allocate 0 bytes per resolve.
This commit is contained in:
Erik 2026-07-25 14:37:02 +02:00
parent 2effba5127
commit 16d182c2f0
19 changed files with 3037 additions and 1228 deletions

View file

@ -25,21 +25,21 @@ namespace AcDream.Core.Physics;
[Flags]
public enum PhysicsStateFlags : uint
{
None = 0x00000000,
Static = 0x00000001,
Ethereal = 0x00000004,
ReportCollisions = 0x00000008,
IgnoreCollisions = 0x00000010,
NoDraw = 0x00000020,
Missile = 0x00000040,
Pushable = 0x00000080,
AlignPath = 0x00000100,
PathClipped = 0x00000200,
Gravity = 0x00000400,
Lighting = 0x00000800,
ParticleEmitter = 0x00001000,
Hidden = 0x00004000,
ScriptedCollision = 0x00008000,
None = 0x00000000,
Static = 0x00000001,
Ethereal = 0x00000004,
ReportCollisions = 0x00000008,
IgnoreCollisions = 0x00000010,
NoDraw = 0x00000020,
Missile = 0x00000040,
Pushable = 0x00000080,
AlignPath = 0x00000100,
PathClipped = 0x00000200,
Gravity = 0x00000400,
Lighting = 0x00000800,
ParticleEmitter = 0x00001000,
Hidden = 0x00004000,
ScriptedCollision = 0x00008000,
/// <summary>
/// A6.P7 (2026-05-25): retail HAS_PHYSICS_BSP_PS bit
/// (acclient.h:2833). When set, the entity exposes a per-Setup
@ -52,7 +52,7 @@ public enum PhysicsStateFlags : uint
/// state 0x10008 (STATIC | REPORT_COLLISIONS | HAS_PHYSICS_BSP).
/// ACE name: <c>PhysicsState.HasPhysicsBSP</c>.
/// </summary>
HasPhysicsBsp = 0x00010000,
HasPhysicsBsp = 0x00010000,
/// <summary>
/// L.3a (2026-04-30): retail INELASTIC_PS bit (acclient.h:2834).
/// When set, wall-collisions zero the velocity instead of reflecting.
@ -60,14 +60,14 @@ public enum PhysicsStateFlags : uint
/// impact rather than bounce. The player NEVER has this flag set —
/// player wall-hits use the reflection path with elasticity ~0.05.
/// </summary>
Inelastic = 0x00020000,
HasDefaultAnim = 0x00040000,
HasDefaultScript = 0x00080000,
Cloaked = 0x00100000,
Inelastic = 0x00020000,
HasDefaultAnim = 0x00040000,
HasDefaultScript = 0x00080000,
Cloaked = 0x00100000,
ReportAsEnvironment = 0x00200000,
EdgeSlide = 0x00400000,
Sledding = 0x00800000,
Frozen = 0x01000000,
EdgeSlide = 0x00400000,
Sledding = 0x00800000,
Frozen = 0x01000000,
}
/// <summary>
@ -77,17 +77,17 @@ public enum PhysicsStateFlags : uint
[Flags]
public enum TransientStateFlags : uint
{
None = 0,
Contact = 0x00000001, // bit 0 — touching any surface
None = 0,
Contact = 0x00000001, // bit 0 — touching any surface
OnWalkable = 0x00000002, // bit 1 — standing on a walkable surface
Sliding = 0x00000004, // bit 2 — carry sliding normal into next transition
Sliding = 0x00000004, // bit 2 — carry sliding normal into next transition
// retail frames_stationary_fall carried across frames: transition() seeds fsf from
// these bits before the sweep (pc:280940-947); handle_all_collisions re-encodes fsf
// into them at the end of the frame (pc:282743/282749/282753).
StationaryFall = 0x00000010, // bit 4 — fsf == 1
StationaryStop = 0x00000020, // bit 5 — fsf == 2
StationaryFall = 0x00000010, // bit 4 — fsf == 1
StationaryStop = 0x00000020, // bit 5 — fsf == 2
StationaryStuck = 0x00000040, // bit 6 — fsf == 3
Active = 0x00000080, // bit 7 — object needs per-frame update
Active = 0x00000080, // bit 7 — object needs per-frame update
}
/// <summary>
@ -99,17 +99,17 @@ public sealed class PhysicsBody
{
// ── constants ──────────────────────────────────────────────────────────
// From PhysicsGlobals.cs / confirmed by DAT_007c78a4 reference in decompiled code.
public const float MaxVelocity = 50.0f;
public const float MaxVelocity = 50.0f;
public const float MaxVelocitySquared = MaxVelocity * MaxVelocity;
public const float Gravity = -9.8f; // DAT_0082223c in FUN_00511420
public const float SmallVelocity = 0.25f;
public const float Gravity = -9.8f; // DAT_0082223c in FUN_00511420
public const float SmallVelocity = 0.25f;
public const float SmallVelocitySquared = SmallVelocity * SmallVelocity;
public const float DefaultFriction = 0.95f;
public const float MinQuantum = 1.0f / 30.0f; // ~0.0333 s
public const float DefaultFriction = 0.95f;
public const float MinQuantum = 1.0f / 30.0f; // ~0.0333 s
// Matching-client disassembly resolves the named lift's stripped global to
// 0.2 seconds. update_object consumes larger gaps as repeated 0.2 s quanta.
public const float MaxQuantum = 0.2f;
public const float HugeQuantum = 2.0f; // discard stale dt
public const float MaxQuantum = 0.2f;
public const float HugeQuantum = 2.0f; // discard stale dt
// ── struct fields ──────────────────────────────────────────────────────
// Offsets from acclient_function_map.md §PhysicsObj Struct Layout.
@ -336,6 +336,28 @@ public sealed class PhysicsBody
/// <summary>Most recent walkable polygon vertices (world-space).</summary>
public Vector3[]? WalkableVertices { get; set; }
// Transition publication retains one exact-length backing array. The
// public property remains assignable for snapshot restoration and tests;
// engine writeback uses this separate storage so clearing the logical
// walkable reference does not force a new array on the next grounded
// resolve.
private Vector3[]? _walkableVertexStorage;
internal void SetWalkableVerticesExact(ReadOnlySpan<Vector3> source)
{
if (_walkableVertexStorage is null
|| _walkableVertexStorage.Length != source.Length)
{
_walkableVertexStorage = new Vector3[source.Length];
}
source.CopyTo(_walkableVertexStorage);
WalkableVertices = _walkableVertexStorage;
}
internal Vector3[]? RetainedWalkableVertexStorage
=> _walkableVertexStorage;
/// <summary>Up vector used by the most recent walkable polygon probe.</summary>
public Vector3 WalkableUp { get; set; } = Vector3.UnitZ;
@ -389,10 +411,10 @@ public sealed class PhysicsBody
// ── convenience helpers ────────────────────────────────────────────────
public bool HasGravity => State.HasFlag(PhysicsStateFlags.Gravity);
public bool OnWalkable => TransientState.HasFlag(TransientStateFlags.OnWalkable);
public bool IsActive => TransientState.HasFlag(TransientStateFlags.Active);
public bool InContact => TransientState.HasFlag(TransientStateFlags.Contact);
public bool HasGravity => (State & PhysicsStateFlags.Gravity) != 0;
public bool OnWalkable => (TransientState & TransientStateFlags.OnWalkable) != 0;
public bool IsActive => (TransientState & TransientStateFlags.Active) != 0;
public bool InContact => (TransientState & TransientStateFlags.Contact) != 0;
// ── FUN_00511420 ───────────────────────────────────────────────────────
@ -411,16 +433,16 @@ public sealed class PhysicsBody
/// </summary>
public void calc_acceleration()
{
if (TransientState.HasFlag(TransientStateFlags.Contact) &&
TransientState.HasFlag(TransientStateFlags.OnWalkable) &&
!State.HasFlag(PhysicsStateFlags.Sledding))
if ((TransientState & TransientStateFlags.Contact) != 0 &&
(TransientState & TransientStateFlags.OnWalkable) != 0 &&
(State & PhysicsStateFlags.Sledding) == 0)
{
Acceleration = Vector3.Zero;
Omega = Vector3.Zero;
return;
}
if (State.HasFlag(PhysicsStateFlags.Gravity))
if ((State & PhysicsStateFlags.Gravity) != 0)
Acceleration = new Vector3(0f, 0f, Gravity);
else
Acceleration = Vector3.Zero;
@ -540,7 +562,7 @@ public sealed class PhysicsBody
/// </summary>
public void calc_friction(float dt, float velocityMag2)
{
if (!TransientState.HasFlag(TransientStateFlags.OnWalkable))
if ((TransientState & TransientStateFlags.OnWalkable) == 0)
return;
float dot = Vector3.Dot(GroundNormal, Velocity);
@ -553,7 +575,7 @@ public sealed class PhysicsBody
float friction = Friction;
// Sledding modifies friction thresholds (from ACE cross-check).
if (State.HasFlag(PhysicsStateFlags.Sledding))
if ((State & PhysicsStateFlags.Sledding) != 0)
{
if (velocityMag2 < 1.5625f) // 1.25² — slow sled
friction = 1.0f;
@ -590,7 +612,7 @@ public sealed class PhysicsBody
if (velocityMag2 <= 0f)
{
// No movement manager equivalent here; just clear Active if grounded.
if (TransientState.HasFlag(TransientStateFlags.OnWalkable))
if ((TransientState & TransientStateFlags.OnWalkable) != 0)
TransientState &= ~TransientStateFlags.Active;
}
else