feat(R2-Q5): RemoteMotionSink DELETED — funnel dispatches straight into PerformMovement; AP-73 retired
The funnel's retail-ordered dispatches now go directly into the entity's motion-table stack via Motion/MotionTableDispatchSink (Core): ApplyMotion → PerformMovement(InterpretedCommand), StopMotion → PerformMovement(StopInterpretedCommand). No axis collection, no single-cycle priority pick, no Commit pass, no HasCycle probe, no Run→Walk→Ready fallback chain — GetObjectSequence 0x00522860 + is_allowed decide (closes H4, H5-callers, H11-callers, H15, H17-carry). Run-while-turning now blends for real: the turn is a Branch-4 modifier combined over the untouched run substate (AP-73 DELETED from the register). AnimationSequencer gains the public PerformMovement passthrough (lazy initialize_state + locomotion velocity synthesis on success — AP-75; remote body translation via PositionManager.ComputeOffset depends on CurrentVelocity) and InitializeState(); HasCycle DELETED (the miss hazard is structurally gone — GetObjectSequence checks the cycle before any surgery; new conformance test pins sequence+state untouched on a miss). GameWindow: sink swap with the TurnApplied/TurnStopped ObservedOmega callbacks (H17 carried verbatim — register AP-76, retire R6); spawn + door sites run retail's enter-world order (initialize_state installs the table default, the wire's initial motion dispatches unguarded — the L.1c fallback chains deleted); despawn drains the pending queue via HandleExitWorld (MotionDone success:false per entry, 0x0051bda0). 5 new MotionTableDispatchSink conformance tests (lazy init, Branch-4 turn blend + callback, Case-B stop unwind, Case-A stop re-drive, miss no-op). Full suite green: 3,462 passed. Live smoke vs ACE: in-world, NPC emote/Ready UMs dispatching through the new path, [MOTIONDONE] completions firing across entities — first live proof of the Q3→Q4→Q5 chain. Zero exceptions. Registers: AP-73 deleted; AP-76 added. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
c072b73686
commit
d82f07d4e5
8 changed files with 399 additions and 395 deletions
|
|
@ -0,0 +1,167 @@
|
|||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using AcDream.Core.Physics;
|
||||
using AcDream.Core.Physics.Motion;
|
||||
using DatReaderWriter.DBObjs;
|
||||
using DatReaderWriter.Types;
|
||||
|
||||
using DRWMotionCommand = DatReaderWriter.Enums.MotionCommand;
|
||||
|
||||
namespace AcDream.Core.Tests.Physics.Motion;
|
||||
|
||||
/// <summary>
|
||||
/// R2-Q5 — <see cref="MotionTableDispatchSink"/>: the funnel's dispatches go
|
||||
/// straight into <see cref="AnimationSequencer.PerformMovement"/> (no axis
|
||||
/// collection, no priority pick, no fallback chain — GetObjectSequence
|
||||
/// 0x00522860 + is_allowed decide) with the TurnApplied/TurnStopped
|
||||
/// ObservedOmega seam.
|
||||
/// </summary>
|
||||
public class MotionTableDispatchSinkTests
|
||||
{
|
||||
private const uint NC = 0x8000003Du;
|
||||
private const uint Ready = 0x41000003u;
|
||||
private const uint Walk = 0x45000005u;
|
||||
private const uint TurnRight = 0x6500000Du;
|
||||
|
||||
private const uint ReadyAnim = 0x200u;
|
||||
private const uint WalkAnim = 0x201u;
|
||||
|
||||
private sealed class Loader : IAnimationLoader
|
||||
{
|
||||
private readonly System.Collections.Generic.Dictionary<uint, Animation> _anims = new();
|
||||
public void Register(uint id, Animation anim) => _anims[id] = anim;
|
||||
public Animation? LoadAnimation(uint id) => _anims.TryGetValue(id, out var a) ? a : null;
|
||||
}
|
||||
|
||||
private static Animation MakeAnim(int frames)
|
||||
{
|
||||
var anim = new Animation();
|
||||
for (int f = 0; f < frames; f++)
|
||||
{
|
||||
var pf = new AnimationFrame(1);
|
||||
pf.Frames.Add(new Frame { Origin = Vector3.Zero, Orientation = Quaternion.Identity });
|
||||
anim.PartFrames.Add(pf);
|
||||
}
|
||||
return anim;
|
||||
}
|
||||
|
||||
private static MotionData MakeMd(uint animId, Vector3? omega = null)
|
||||
{
|
||||
var md = new MotionData();
|
||||
QualifiedDataId<Animation> qid = animId;
|
||||
md.Anims.Add(new AnimData { AnimId = qid, LowFrame = 0, HighFrame = -1, Framerate = 30f });
|
||||
if (omega is { } o)
|
||||
{
|
||||
md.Omega = o;
|
||||
md.Flags |= DatReaderWriter.Enums.MotionDataFlags.HasOmega;
|
||||
}
|
||||
return md;
|
||||
}
|
||||
|
||||
private static AnimationSequencer MakeSequencer(bool withTurnModifier = true)
|
||||
{
|
||||
var setup = new Setup();
|
||||
setup.Parts.Add(0x01000000u);
|
||||
setup.DefaultScale.Add(Vector3.One);
|
||||
|
||||
var loader = new Loader();
|
||||
loader.Register(ReadyAnim, MakeAnim(4));
|
||||
loader.Register(WalkAnim, MakeAnim(6));
|
||||
|
||||
var mt = new MotionTable { DefaultStyle = (DRWMotionCommand)NC };
|
||||
mt.StyleDefaults[(DRWMotionCommand)NC] = (DRWMotionCommand)Ready;
|
||||
mt.Cycles[(int)((NC << 16) | (Ready & 0xFFFFFFu))] = MakeMd(ReadyAnim);
|
||||
mt.Cycles[(int)((NC << 16) | (Walk & 0xFFFFFFu))] = MakeMd(WalkAnim);
|
||||
if (withTurnModifier)
|
||||
{
|
||||
mt.Modifiers[(int)((NC << 16) | (TurnRight & 0xFFFFFFu))] =
|
||||
MakeMd(ReadyAnim, omega: new Vector3(0f, 0f, 1.2f));
|
||||
}
|
||||
|
||||
return new AnimationSequencer(setup, mt, loader);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ApplyMotion_CycleClass_InstallsSubstate_LazyInitialized()
|
||||
{
|
||||
var seq = MakeSequencer();
|
||||
var sink = new MotionTableDispatchSink(seq);
|
||||
|
||||
// Fresh sequencer: the first dispatch lazily runs initialize_state
|
||||
// (retail lazy-create, r3-motioninterp-decomp §6g) then installs
|
||||
// the requested substate.
|
||||
sink.ApplyMotion(Walk, 2.0f);
|
||||
|
||||
Assert.Equal(Walk, seq.CurrentMotion);
|
||||
Assert.Equal(2.0f, seq.CurrentSpeedMod);
|
||||
// Locomotion velocity synthesis ran in the passthrough (AP-75).
|
||||
Assert.Equal(MotionInterpreter.WalkAnimSpeed * 2.0f, seq.CurrentVelocity.Y, 3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ApplyMotion_Turn_Branch4Modifier_FiresTurnApplied_NoCycleChange()
|
||||
{
|
||||
var seq = MakeSequencer();
|
||||
uint? turnMotion = null;
|
||||
float turnSpeed = 0f;
|
||||
var sink = new MotionTableDispatchSink(seq)
|
||||
{
|
||||
TurnApplied = (m, s) => { turnMotion = m; turnSpeed = s; },
|
||||
};
|
||||
|
||||
sink.ApplyMotion(Walk, 1.0f);
|
||||
sink.ApplyMotion(TurnRight, 1.5f);
|
||||
|
||||
// The AP-73 mechanism for real: substate cycle untouched, the turn
|
||||
// is a MotionState modifier with its dat omega combined.
|
||||
Assert.Equal(Walk, seq.CurrentMotion);
|
||||
Assert.Equal((TurnRight, 1.5f), (turnMotion!.Value, turnSpeed));
|
||||
Assert.Equal(1.2f * 1.5f, seq.CurrentOmega.Z, 3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StopMotion_Turn_FiresTurnStopped_UnwindsModifierPhysics()
|
||||
{
|
||||
var seq = MakeSequencer();
|
||||
bool stopped = false;
|
||||
var sink = new MotionTableDispatchSink(seq) { TurnStopped = () => stopped = true };
|
||||
|
||||
sink.ApplyMotion(Walk, 1.0f);
|
||||
sink.ApplyMotion(TurnRight, 1.5f);
|
||||
sink.StopMotion(TurnRight);
|
||||
|
||||
Assert.True(stopped);
|
||||
// StopSequenceMotion Case B (0x00522fc0): subtract_motion of the dat
|
||||
// omega + modifier unlinked.
|
||||
Assert.Equal(0f, seq.CurrentOmega.Z, 3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StopMotion_CurrentSubstate_ReDrivesToStyleDefault()
|
||||
{
|
||||
var seq = MakeSequencer();
|
||||
var sink = new MotionTableDispatchSink(seq);
|
||||
|
||||
sink.ApplyMotion(Walk, 1.0f);
|
||||
sink.StopMotion(Walk);
|
||||
|
||||
// StopSequenceMotion Case A: stopping the active cycle re-drives
|
||||
// GetObjectSequence toward the style default (Ready).
|
||||
Assert.Equal(Ready, seq.CurrentMotion);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ApplyMotion_MissingEverywhere_NoOp_DefaultKeepsPlaying()
|
||||
{
|
||||
var seq = MakeSequencer(withTurnModifier: false);
|
||||
var sink = new MotionTableDispatchSink(seq);
|
||||
|
||||
sink.ApplyMotion(Walk, 1.0f);
|
||||
// 0x44000007 RunForward: no cycle, no modifier -> dispatch fails,
|
||||
// sequence + state untouched (no fallback chain — H4/H5).
|
||||
sink.ApplyMotion(0x44000007u, 2.0f);
|
||||
|
||||
Assert.Equal(Walk, seq.CurrentMotion);
|
||||
Assert.Equal(1.0f, seq.CurrentSpeedMod);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue