feat(physics): port retail complete object frame pipeline
Restore the named-retail object update order across local, remote, static, projectile, animation, shadow, teleport, and effect lifetimes. Separate authoritative root commits from spatial rebucketing, preserve per-owner hook/FIFO ordering, and remove update-path allocations with exact lifecycle and residency gates. Add deterministic conformance, adversarial lifetime, GUID-reuse, pending-cell, quaternion, timestamp, and allocation coverage. Release build is warning-free and all 6,446 tests pass with five intentional skips; retail, architecture, and adversarial reviews are clean. Co-authored-by: OpenAI Codex <codex@openai.com>
This commit is contained in:
parent
31a0889f08
commit
f961d70023
77 changed files with 12513 additions and 1871 deletions
|
|
@ -21,9 +21,9 @@ namespace AcDream.Core.Tests.Physics.Motion;
|
|||
// the scalar) and drains pending_motions synthetically, so the two legs where
|
||||
// the live #170 residual actually lives have ZERO coverage:
|
||||
//
|
||||
// 1. the PHYSICAL turn: _DoMotion(TurnRight) → MotionTableDispatchSink.
|
||||
// TurnApplied → ObservedOmega → per-tick quaternion integration →
|
||||
// MoveToMath.GetHeading → HandleTurnToHeading's HeadingGreater test;
|
||||
// 1. the PHYSICAL turn: _DoMotion(TurnRight) → MotionTableDispatchSink →
|
||||
// DAT MotionData.Omega → CSequence's complete root Frame →
|
||||
// Frame::combine → HandleTurnToHeading's HeadingGreater test;
|
||||
// 2. the REAL drain: MotionTableManager pending_animations countdown fed by
|
||||
// CSequence AnimDone hooks (link-anim completions), popping
|
||||
// CMotionInterp.pending_motions via the MotionDoneTarget seam.
|
||||
|
|
@ -31,11 +31,9 @@ namespace AcDream.Core.Tests.Physics.Motion;
|
|||
// This harness wires a real MotionInterpreter + AnimationSequencer +
|
||||
// MotionTableDispatchSink + MoveToManager EXACTLY like GameWindow's
|
||||
// EnsureRemoteMotionBindings (GameWindow.cs:4251) and ticks them in
|
||||
// GameWindow.TickAnimations' per-entity order for the grounded branch=MOVETO
|
||||
// path (GameWindow.cs:9697 HandleTargetting → 9994 TickRemoteMoveTo →
|
||||
// 10024 get_state_velocity refresh → 10050 manual omega integration →
|
||||
// 10247 Sequencer.Advance → 10306 AnimationDone per AnimDoneHook →
|
||||
// 10309 Manager.UseTime). Wire events (aggro stance UM, mt-6 arms, attack
|
||||
// the retail per-object order: CSequence update → PositionManager adjust →
|
||||
// complete Frame combine → hook processing → Target/Movement/PartArray/
|
||||
// Position manager tail. Wire events (aggro stance UM, mt-6 arms, attack
|
||||
// UMs) replay the exact live sequence captured in launch-drainq.log
|
||||
// (2026-07-04, guid 0x80000BE5, Mite Scamp chasing the fleeing player).
|
||||
//
|
||||
|
|
@ -118,9 +116,6 @@ internal sealed class RemoteChaseHarness
|
|||
private readonly CreatureHost _creatureHost;
|
||||
private readonly TargetHost _playerHost;
|
||||
|
||||
/// <summary>GameWindow's RemoteMotion.ObservedOmega twin.</summary>
|
||||
public Vector3 ObservedOmega;
|
||||
|
||||
/// <summary>Scripted player (chase target) world position.</summary>
|
||||
public Vector3 PlayerPos;
|
||||
public Vector3 PlayerVelocity;
|
||||
|
|
@ -162,17 +157,7 @@ internal sealed class RemoteChaseHarness
|
|||
};
|
||||
|
||||
// ── EnsureRemoteMotionBindings (GameWindow.cs:4251) verbatim ──
|
||||
Sink = new MotionTableDispatchSink(Seq)
|
||||
{
|
||||
TurnApplied = (turnMotion, turnSpeed) =>
|
||||
{
|
||||
float signed = (turnMotion & 0xFFu) == 0x0E
|
||||
? -MathF.Abs(turnSpeed)
|
||||
: turnSpeed;
|
||||
ObservedOmega = new Vector3(0f, 0f, -(MathF.PI / 2f) * signed);
|
||||
},
|
||||
TurnStopped = () => ObservedOmega = Vector3.Zero,
|
||||
};
|
||||
Sink = new MotionTableDispatchSink(Seq);
|
||||
Interp.DefaultSink = Sink;
|
||||
// #174: production binds the seam to Manager.HandleEnterWorld
|
||||
// (strip + full queue drain, retail 0x0050fe20 → 0x0051bdd0) —
|
||||
|
|
@ -425,64 +410,52 @@ internal sealed class RemoteChaseHarness
|
|||
// Player (chase target) moves per its scripted velocity.
|
||||
PlayerPos += PlayerVelocity * Dt;
|
||||
|
||||
// 1. Voyeur delivery (player host HandleTargetting → this entity's
|
||||
// HandleUpdateTarget; GameWindow.cs:8094 runs before the per-remote
|
||||
// loop, 9697 in-loop).
|
||||
if (_targetArmed && Now - _lastDeliveryTime >= _quantum)
|
||||
DeliverTargetInfo();
|
||||
|
||||
// 2. MovementManager drive (TickRemoteMoveTo — the R5-V5 facade
|
||||
// relay, MovementManager::UseTime 0x005242f0).
|
||||
Movement.UseTime();
|
||||
|
||||
// 3. get_state_velocity → body velocity (the d2ccc80e refresh,
|
||||
// GameWindow.cs:10024).
|
||||
if (Body.OnWalkable)
|
||||
Body.set_local_velocity(Interp.get_state_velocity(), false);
|
||||
|
||||
// 4. Manual omega integration (GameWindow.cs:10050-10058 verbatim).
|
||||
if (ObservedOmega.LengthSquared() > 1e-8f)
|
||||
// CPartArray::Update emits one complete local Frame: authored PosFrames
|
||||
// plus MotionData velocity/omega (CSequence::apply_physics 0x00524AB0).
|
||||
var rootFrame = new Frame
|
||||
{
|
||||
float omegaMag = ObservedOmega.Length();
|
||||
var axis = ObservedOmega / omegaMag;
|
||||
float angle = omegaMag * Dt;
|
||||
var deltaRot = Quaternion.CreateFromAxisAngle(axis, angle);
|
||||
Body.Orientation = Quaternion.Normalize(
|
||||
Quaternion.Multiply(Body.Orientation, deltaRot));
|
||||
}
|
||||
Origin = Vector3.Zero,
|
||||
Orientation = Quaternion.Identity,
|
||||
};
|
||||
Seq.Advance(Dt, rootFrame);
|
||||
|
||||
// 5. R5-V3 (#171): the sticky steer — GameWindow's legacy-branch slot
|
||||
// (retail UpdatePositionInternal PositionManager::adjust_offset
|
||||
// @0x00512d0e, composed BEFORE the velocity integration). Origin is
|
||||
// mover-local; the rotation carries a RELATIVE heading (identity =
|
||||
// untouched = no turn).
|
||||
var pmDelta = new MotionDeltaFrame();
|
||||
// PositionManager receives that same Frame and may replace its motion
|
||||
// (sticky/interpolation) before Frame::combine applies translation with
|
||||
// the OLD orientation, then post-multiplies the relative orientation.
|
||||
var pmDelta = new MotionDeltaFrame
|
||||
{
|
||||
Origin = rootFrame.Origin,
|
||||
Orientation = rootFrame.Orientation,
|
||||
};
|
||||
Pm.AdjustOffset(pmDelta, Dt);
|
||||
if (pmDelta.Origin != Vector3.Zero)
|
||||
Body.Position += Vector3.Transform(pmDelta.Origin, Body.Orientation);
|
||||
if (!pmDelta.Orientation.IsIdentity)
|
||||
Body.Orientation = MoveToMath.SetHeading(
|
||||
Body.Orientation,
|
||||
MoveToMath.GetHeading(Body.Orientation) + pmDelta.GetHeading());
|
||||
Body.Orientation = Quaternion.Normalize(
|
||||
Body.Orientation * pmDelta.Orientation);
|
||||
|
||||
// 5b. Position integration (UpdatePhysicsInternal, simplified: grounded,
|
||||
// no gravity participation for this scenario).
|
||||
Body.Position += Body.Velocity * Dt;
|
||||
// The fixture has no gravity or independent physical velocity, but run
|
||||
// the same physics primitive so a future fixture cannot accidentally
|
||||
// bypass the retail slot.
|
||||
Body.calc_acceleration();
|
||||
Body.UpdatePhysicsInternal(Dt);
|
||||
|
||||
// 5c. R5-V3: PositionManager::UseTime (retail UpdateObjectInternal
|
||||
// tail @0x005159b3) — the sticky 1 s lease watchdog.
|
||||
Pm.UseTime();
|
||||
|
||||
// 6. Anim loop (GameWindow.cs:10247-10309): advance, drain AnimDone
|
||||
// hooks into the manager countdown, zero-tick sweep.
|
||||
Seq.Advance(Dt);
|
||||
// process_hooks precedes the manager tail. AnimationDone decrements the
|
||||
// exact pending-animation node that owns the transition.
|
||||
var hooks = Seq.ConsumePendingHooks();
|
||||
for (int i = 0; i < hooks.Count; i++)
|
||||
{
|
||||
if (hooks[i] is DatReaderWriter.Types.AnimationDoneHook)
|
||||
Seq.Manager.AnimationDone(success: true);
|
||||
}
|
||||
|
||||
// UpdateObjectInternal manager tail: Target → Movement → PartArray →
|
||||
// Position. A movement selected here contributes to next tick's Frame.
|
||||
if (_targetArmed && Now - _lastDeliveryTime >= _quantum)
|
||||
DeliverTargetInfo();
|
||||
Movement.UseTime();
|
||||
Seq.Manager.UseTime();
|
||||
Pm.UseTime();
|
||||
|
||||
// ── Observables ──
|
||||
uint substate = Seq.Manager.State.Substate;
|
||||
|
|
@ -509,7 +482,7 @@ internal sealed class RemoteChaseHarness
|
|||
public void Snapshot(string tag)
|
||||
{
|
||||
string line = FormattableString.Invariant(
|
||||
$"t={Now,6:F2} {tag,-14} mt={Mgr.MovementTypeState,-14} substate=0x{Seq.Manager.State.Substate:X8} heading={Heading,6:F1} dist={DistToPlayer,6:F2} pending={Interp.MotionsPending()} omega.Z={ObservedOmega.Z,6:F3}");
|
||||
$"t={Now,6:F2} {tag,-14} mt={Mgr.MovementTypeState,-14} substate=0x{Seq.Manager.State.Substate:X8} heading={Heading,6:F1} dist={DistToPlayer,6:F2} pending={Interp.MotionsPending()} omega.Z={Seq.CurrentOmega.Z,6:F3}");
|
||||
Trace.Add(line);
|
||||
_log?.WriteLine(line);
|
||||
}
|
||||
|
|
@ -627,7 +600,11 @@ internal sealed class RemoteChaseHarness
|
|||
AddLink(mt, Combat, Ready, AttackAction, MakeMd(AttackLink));
|
||||
|
||||
// Global (unstyled) TurnRight modifier — physics-only in Branch 4.
|
||||
mt.Modifiers[(int)(TurnRight & 0xFFFFFFu)] = MakeMd(TurnAnim);
|
||||
// The signed DAT omega is the rotation source, matching retail's
|
||||
// Humanoid motion table; no command-derived side channel exists.
|
||||
mt.Modifiers[(int)(TurnRight & 0xFFFFFFu)] = MakeMd(
|
||||
TurnAnim,
|
||||
omega: new Vector3(0f, 0f, -1.5f));
|
||||
|
||||
return (setup, mt, loader);
|
||||
}
|
||||
|
|
@ -646,7 +623,7 @@ public sealed class RemoteChaseEndToEndHarnessTests
|
|||
/// The core chase cycle: aggro stance change, one mt-6 arm at a target
|
||||
/// 90° off the creature's facing, 15 m away, stationary. Retail bar: the
|
||||
/// stance links play out (~1 s), the chase turn starts, completes at the
|
||||
/// turn rate (90° at π/2·2.08 rad/s ≈ 0.5 s), and BeginMoveForward
|
||||
/// authored turn rate (90° at 1.5·2.08 rad/s ≈ 0.5 s), and BeginMoveForward
|
||||
/// installs the forward cycle. Total budget: 4 s of ticks is generous.
|
||||
/// </summary>
|
||||
[Theory]
|
||||
|
|
@ -692,7 +669,7 @@ public sealed class RemoteChaseEndToEndHarnessTests
|
|||
Assert.True(installTick >= 0,
|
||||
$"forward cycle never installed within 6 s of the arm (bearing {bearingDeg}°); " +
|
||||
$"final: mt={h.Mgr.MovementTypeState} substate=0x{h.Seq.Manager.State.Substate:X8} " +
|
||||
$"heading={h.Heading:F1} pending={h.Interp.MotionsPending()} omega.Z={h.ObservedOmega.Z:F3}");
|
||||
$"heading={h.Heading:F1} pending={h.Interp.MotionsPending()} omega.Z={h.Seq.CurrentOmega.Z:F3}");
|
||||
Assert.True(installTick <= Seconds(4f),
|
||||
$"forward cycle took {installTick * Dt:F2} s to install (bearing {bearingDeg}°) — " +
|
||||
"retail installs within the turn duration (~1-2 s)");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue