using System.Collections.Generic; using System.Numerics; using AcDream.App.World; using Xunit; namespace AcDream.App.Tests.World; public class TeleportArrivalControllerTests { // Records each Place(destPos, destCell, forced) call. private sealed record PlaceCall(Vector3 Pos, uint Cell, bool Forced); private static TeleportArrivalController Make( ArrivalReadiness verdict, List placed, int maxHoldFrames = TeleportArrivalController.DefaultMaxHoldFrames) => new( readiness: (_, _) => verdict, place: (pos, cell, forced) => placed.Add(new PlaceCall(pos, cell, forced)), maxHoldFrames: maxHoldFrames); [Fact] public void BeginArrival_EntersHolding() { var placed = new List(); var c = Make(ArrivalReadiness.NotReady, placed); c.BeginArrival(new Vector3(1, 2, 3), 0x01250126u); Assert.Equal(TeleportArrivalPhase.Holding, c.Phase); Assert.Empty(placed); } [Fact] public void Tick_WhenIdle_IsNoOp() { var placed = new List(); var c = Make(ArrivalReadiness.Ready, placed); c.Tick(); // never began Assert.Equal(TeleportArrivalPhase.Idle, c.Phase); Assert.Empty(placed); } [Fact] public void Tick_NotReady_KeepsHolding_DoesNotPlace() { var placed = new List(); var c = Make(ArrivalReadiness.NotReady, placed); c.BeginArrival(new Vector3(1, 2, 3), 0x01250126u); c.Tick(); c.Tick(); Assert.Equal(TeleportArrivalPhase.Holding, c.Phase); Assert.Empty(placed); } [Fact] public void Tick_Ready_PlacesUnforced_AndIdles() { var placed = new List(); var c = Make(ArrivalReadiness.Ready, placed); c.BeginArrival(new Vector3(30, -60, 6.005f), 0x01250126u); c.Tick(); Assert.Equal(TeleportArrivalPhase.Idle, c.Phase); var call = Assert.Single(placed); Assert.False(call.Forced); Assert.Equal(0x01250126u, call.Cell); Assert.Equal(new Vector3(30, -60, 6.005f), call.Pos); } [Fact] public void Tick_Impossible_PlacesForced_AndIdles() { var placed = new List(); var c = Make(ArrivalReadiness.Impossible, placed); c.BeginArrival(new Vector3(1, 2, 3), 0x0125FF00u); c.Tick(); Assert.Equal(TeleportArrivalPhase.Idle, c.Phase); var call = Assert.Single(placed); Assert.True(call.Forced); } [Fact] public void Tick_Timeout_PlacesForced_AfterMaxHoldFrames() { var placed = new List(); var c = Make(ArrivalReadiness.NotReady, placed, maxHoldFrames: 3); c.BeginArrival(new Vector3(1, 2, 3), 0x01250126u); c.Tick(); // 1 c.Tick(); // 2 Assert.Empty(placed); Assert.Equal(TeleportArrivalPhase.Holding, c.Phase); c.Tick(); // 3 -> timeout var call = Assert.Single(placed); Assert.True(call.Forced); Assert.Equal(TeleportArrivalPhase.Idle, c.Phase); } [Fact] public void BeginArrival_AfterPlace_ReArms() { var placed = new List(); var c = Make(ArrivalReadiness.Ready, placed); c.BeginArrival(new Vector3(1, 0, 0), 0x01250126u); c.Tick(); // places #1, idle c.BeginArrival(new Vector3(2, 0, 0), 0x01250127u); c.Tick(); // places #2, idle 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); } }