diff --git a/src/AcDream.App/World/TeleportArrivalController.cs b/src/AcDream.App/World/TeleportArrivalController.cs index d6538533..096f0cce 100644 --- a/src/AcDream.App/World/TeleportArrivalController.cs +++ b/src/AcDream.App/World/TeleportArrivalController.cs @@ -97,7 +97,9 @@ public sealed class TeleportArrivalController private void Place(bool forced) { - _place(_destPos, _destCell, forced); + // Flip to Idle BEFORE invoking the placement delegate so the machine + // reflects "done holding" even if the delegate were to re-enter Tick. Phase = TeleportArrivalPhase.Idle; + _place(_destPos, _destCell, forced); } } diff --git a/tests/AcDream.App.Tests/World/TeleportArrivalControllerTests.cs b/tests/AcDream.App.Tests/World/TeleportArrivalControllerTests.cs index fbf8727f..54a23f2f 100644 --- a/tests/AcDream.App.Tests/World/TeleportArrivalControllerTests.cs +++ b/tests/AcDream.App.Tests/World/TeleportArrivalControllerTests.cs @@ -120,4 +120,28 @@ public class TeleportArrivalControllerTests Assert.Equal(2, placed.Count); Assert.Equal(0x01250127u, placed[1].Cell); } + + [Fact] + public void BeginArrival_DuringHold_ResetsTimeoutCounter() + { + var placed = new List(); + var c = Make(ArrivalReadiness.NotReady, placed, maxHoldFrames: 3); + + c.BeginArrival(new Vector3(1, 0, 0), 0x01250126u); + c.Tick(); // held=1 + c.Tick(); // held=2 (one short of the timeout) + + // Re-arm mid-hold with a fresh destination: the counter must restart. + c.BeginArrival(new Vector3(2, 0, 0), 0x01250199u); + c.Tick(); // held=1 again (NOT 3 -> no placement yet) + c.Tick(); // held=2 + Assert.Empty(placed); + Assert.Equal(TeleportArrivalPhase.Holding, c.Phase); + + c.Tick(); // held=3 -> timeout, forced place of the SECOND destination + var call = Assert.Single(placed); + Assert.True(call.Forced); + Assert.Equal(0x01250199u, call.Cell); + Assert.Equal(new Vector3(2, 0, 0), call.Pos); + } }