From c3d6eccf51b8fd9f8ab63b765227c2f291ddd99d Mon Sep 17 00:00:00 2001 From: Erik Date: Sun, 21 Jun 2026 20:00:39 +0200 Subject: [PATCH] test(core/slice-1): full portal+logout event-sequence ordering + no-duplicate-fire coverage Co-Authored-By: Claude Opus 4.8 (1M context) --- .../World/TeleportAnimSequencerTests.cs | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/tests/AcDream.Core.Tests/World/TeleportAnimSequencerTests.cs b/tests/AcDream.Core.Tests/World/TeleportAnimSequencerTests.cs index 3a7c039e..fb712d7f 100644 --- a/tests/AcDream.Core.Tests/World/TeleportAnimSequencerTests.cs +++ b/tests/AcDream.Core.Tests/World/TeleportAnimSequencerTests.cs @@ -341,4 +341,115 @@ public sealed class TeleportAnimSequencerTests Assert.Equal(TeleportAnimState.TunnelContinue, seq.State); Assert.True(snap2.ShowPleaseWait); } + + // --- Task 1.5: Full portal event sequence and logout event sequence --- + + // Helper: run the sequencer to completion collecting ordered events. + private static List RunToOff( + TeleportEntryKind kind, bool worldReadyAfterFirstTunnel = true, float step = 0.05f) + { + var seq = new TeleportAnimSequencer(); + var allEvts = new List(); + seq.Begin(kind); + + int safetyNet = 5000; + while (seq.IsActive && --safetyNet > 0) + { + // Simulate world becoming ready on first Tick inside Tunnel + bool ready = worldReadyAfterFirstTunnel && seq.State == TeleportAnimState.Tunnel; + var (_, evts) = seq.Tick(step, worldReady: ready); + allEvts.AddRange(evts); + } + return allEvts; + } + + [Fact] + public void Portal_FullSequence_EventsInOrder() + { + var evts = RunToOff(TeleportEntryKind.Portal); + + // Required events in order: PlayEnterSound, Place, PlayExitSound, FireLoginComplete. + // EnterTunnel is optional but must come before Place if present. + int enterIdx = evts.IndexOf(TeleportAnimEvent.PlayEnterSound); + int placeIdx = evts.IndexOf(TeleportAnimEvent.Place); + int exitIdx = evts.IndexOf(TeleportAnimEvent.PlayExitSound); + int loginIdx = evts.IndexOf(TeleportAnimEvent.FireLoginComplete); + + Assert.True(enterIdx >= 0, "PlayEnterSound must fire"); + Assert.True(placeIdx >= 0, "Place must fire"); + Assert.True(exitIdx >= 0, "PlayExitSound must fire"); + Assert.True(loginIdx >= 0, "FireLoginComplete must fire"); + + Assert.True(enterIdx < placeIdx, "PlayEnterSound must precede Place"); + Assert.True(placeIdx < exitIdx, "Place must precede PlayExitSound"); + Assert.True(exitIdx < loginIdx, "PlayExitSound must precede FireLoginComplete"); + } + + [Fact] + public void Logout_FullSequence_EventsInOrder_WithEnterTunnelAfterFades() + { + var evts = RunToOff(TeleportEntryKind.Logout); + + int enterIdx = evts.IndexOf(TeleportAnimEvent.PlayEnterSound); + int tunnelIdx = evts.IndexOf(TeleportAnimEvent.EnterTunnel); + int placeIdx = evts.IndexOf(TeleportAnimEvent.Place); + int exitIdx = evts.IndexOf(TeleportAnimEvent.PlayExitSound); + int loginIdx = evts.IndexOf(TeleportAnimEvent.FireLoginComplete); + + Assert.True(enterIdx >= 0, "PlayEnterSound must fire"); + Assert.True(tunnelIdx >= 0, "EnterTunnel must fire (Logout path goes through WorldFadeOut->TunnelFadeIn->Tunnel)"); + Assert.True(placeIdx >= 0, "Place must fire"); + Assert.True(exitIdx >= 0, "PlayExitSound must fire"); + Assert.True(loginIdx >= 0, "FireLoginComplete must fire"); + + Assert.True(enterIdx < tunnelIdx, "PlayEnterSound must precede EnterTunnel"); + Assert.True(tunnelIdx < placeIdx, "EnterTunnel must precede Place"); + Assert.True(placeIdx < exitIdx, "Place must precede PlayExitSound"); + Assert.True(exitIdx < loginIdx, "PlayExitSound must precede FireLoginComplete"); + } + + [Fact] + public void Portal_NoEventsFiredTwice() + { + var evts = RunToOff(TeleportEntryKind.Portal); + Assert.Equal(1, evts.Count(e => e == TeleportAnimEvent.PlayEnterSound)); + Assert.Equal(1, evts.Count(e => e == TeleportAnimEvent.Place)); + Assert.Equal(1, evts.Count(e => e == TeleportAnimEvent.PlayExitSound)); + Assert.Equal(1, evts.Count(e => e == TeleportAnimEvent.FireLoginComplete)); + } + + [Fact] + public void Death_BehavesIdenticallyToPortal_FullSequence() + { + var portalEvts = RunToOff(TeleportEntryKind.Portal); + var deathEvts = RunToOff(TeleportEntryKind.Death); + // Same event sequence (both enter at Tunnel) + Assert.Equal(portalEvts, deathEvts); + } + + [Fact] + public void Login_BehavesIdenticallyToPortal_FullSequence() + { + var portalEvts = RunToOff(TeleportEntryKind.Portal); + var loginEvts = RunToOff(TeleportEntryKind.Login); + Assert.Equal(portalEvts, loginEvts); + } + + [Fact] + public void AfterOff_IsActiveIsFalse_AndStateIsOff() + { + RunToOff(TeleportEntryKind.Portal); + var seq = new TeleportAnimSequencer(); + seq.Begin(TeleportEntryKind.Portal); + + // Drain to Off + for (int i = 0; i < 5000 && seq.IsActive; i++) + { + bool ready = seq.State == TeleportAnimState.Tunnel; + seq.Tick(0.05f, worldReady: ready); + } + + Assert.False(seq.IsActive); + Assert.Equal(TeleportAnimState.Off, seq.State); + } }