fix(animation): harden presentation phase handoffs

Own every borrowed PartArray frame across callbacks, version presentation state across appearance rebinding, reject recursive phase consumption, and preserve static MotionDone when only a stale visual pose is discarded.
This commit is contained in:
Erik 2026-07-21 10:01:25 +02:00
parent a2fd61684a
commit 833520253a
10 changed files with 657 additions and 140 deletions

View file

@ -110,6 +110,31 @@ public sealed class LiveEntityAnimationSchedulerTests
Assert.True(animation.Entity.Position.X > startX + 1f);
}
[Fact]
public void ScheduleOwnsPartFramesAcrossLaterSequencerSampling()
{
var (live, _, animation) = BuildVisibleAnimatedWithPosFrames(RemoteGuid);
LiveEntityAnimationScheduler scheduler = BuildScheduler(live, LocalGuid);
LiveEntityAnimationSchedule schedule = scheduler.Tick(
0.1f,
animation.Entity.Position,
localHiddenPartPoseDirty: false,
liveCenterX: 0,
liveCenterY: 0)[animation.Entity.Id];
PartTransform retained = schedule.SequenceFrames![0];
animation.CaptureSequenceFrames(
[new PartTransform(new Vector3(77f, 76f, 75f), Quaternion.Identity)]);
foreach (AnimationFrame frame in animation.Animation.PartFrames)
frame.Frames[0].Origin = new Vector3(99f, 98f, 97f);
IReadOnlyList<PartTransform> borrowed = animation.Sequencer!.SampleCurrentPose();
Assert.Equal(new Vector3(99f, 98f, 97f), borrowed[0].Origin);
Assert.Equal(retained.Origin, schedule.SequenceFrames[0].Origin);
Assert.NotEqual(animation.SequenceFramesScratch[0].Origin, schedule.SequenceFrames[0].Origin);
Assert.NotEqual(borrowed[0].Origin, schedule.SequenceFrames[0].Origin);
}
[Fact]
public void BodylessNonWalkableAnimation_SuppressesRootOriginButPreservesOrientation()
{