fix(animation): preserve wire motion on every spawn path
Replace the door-specific static-animation seed with one retail description-then-enter-world initializer shared by normal and reactive spawns. This preserves authoritative Dead and On/Off states, prevents replacement corpses from returning to Ready, and pins the lifecycle with Core and App tests. Co-Authored-By: Codex <codex@openai.com>
This commit is contained in:
parent
9b97102c67
commit
8be933fc94
9 changed files with 335 additions and 64 deletions
|
|
@ -382,6 +382,60 @@ public sealed class AnimationSequencerTests
|
|||
$"Expected link-anim Y({transforms[0].Origin.Y}) > cycle X({transforms[0].Origin.X})");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SpawnEnterWorld_DeadState_StripsReadyToDeadTransition()
|
||||
{
|
||||
// Retail spawn order:
|
||||
// ACCObjectMaint::CreateObject -> CPhysicsObj::set_description
|
||||
// (0x00558870 -> 0x00514F40) installs Ready then applies the wire's
|
||||
// Dead state while the object is detached. SmartBox::HandleCreateObject
|
||||
// then enters the object into the world (0x00454C80 -> 0x00516170),
|
||||
// reaching CPartArray::HandleEnterWorld (0x00517D70) ->
|
||||
// MotionTableManager::HandleEnterWorld (0x0051BDD0). That last call
|
||||
// strips the Ready->Dead transition before the first rendered tick.
|
||||
const uint Style = 0x8000003Du;
|
||||
const uint ReadyMotion = 0x41000003u;
|
||||
const uint DeadMotion = 0x40000011u;
|
||||
const uint ReadyAnim = 0x03000022u;
|
||||
const uint DeadAnim = 0x03000020u;
|
||||
const uint FallAnim = 0x03000021u;
|
||||
|
||||
var setup = Fixtures.MakeSetup(1);
|
||||
var mt = Fixtures.MakeMtable(
|
||||
style: Style,
|
||||
motion: DeadMotion,
|
||||
cycleAnimId: DeadAnim,
|
||||
fromMotion: ReadyMotion,
|
||||
toMotion: DeadMotion,
|
||||
linkAnimId: FallAnim);
|
||||
mt.StyleDefaults[(DRWMotionCommand)Style] = (DRWMotionCommand)ReadyMotion;
|
||||
mt.Cycles[(int)((Style << 16) | (ReadyMotion & 0xFFFFFFu))] =
|
||||
Fixtures.MakeMotionData(ReadyAnim, framerate: 30f);
|
||||
|
||||
var loader = new FakeLoader();
|
||||
loader.Register(ReadyAnim,
|
||||
Fixtures.MakeAnim(1, 1, Vector3.Zero, Quaternion.Identity));
|
||||
loader.Register(FallAnim,
|
||||
Fixtures.MakeAnim(2, 1, new Vector3(0, 1, 0), Quaternion.Identity));
|
||||
loader.Register(DeadAnim,
|
||||
Fixtures.MakeAnim(1, 1, new Vector3(1, 0, 0), Quaternion.Identity));
|
||||
|
||||
var seq = new AnimationSequencer(setup, mt, loader);
|
||||
seq.InitializeState();
|
||||
seq.SetCycle(Style, DeadMotion);
|
||||
|
||||
var detachedPose = seq.Advance(0.001f);
|
||||
Assert.True(detachedPose[0].Origin.Y > detachedPose[0].Origin.X,
|
||||
"Before enter-world, the description-time Ready->Dead link must exist");
|
||||
|
||||
seq.Manager.HandleEnterWorld();
|
||||
|
||||
var enteredPose = seq.Advance(0.001f);
|
||||
Assert.True(enteredPose[0].Origin.X > enteredPose[0].Origin.Y,
|
||||
"Enter-world must start the corpse on the fallen Dead cycle");
|
||||
Assert.Equal(1, seq.CurrentNodeDiag.QueueCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Advance_LinkTailDoesNotBlendIntoLinkFrame0()
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue