diff --git a/tests/AcDream.Core.Tests/World/TeleportAnimSequencerTests.cs b/tests/AcDream.Core.Tests/World/TeleportAnimSequencerTests.cs index f38ba60e..3a7c039e 100644 --- a/tests/AcDream.Core.Tests/World/TeleportAnimSequencerTests.cs +++ b/tests/AcDream.Core.Tests/World/TeleportAnimSequencerTests.cs @@ -236,4 +236,109 @@ public sealed class TeleportAnimSequencerTests Assert.False(seq.IsActive); Assert.Contains(TeleportAnimEvent.FireLoginComplete, evts); } + + // --- Task 1.4: FadeAlpha endpoints and monotonicity --- + + [Fact] + public void FadeAlpha_IsZeroAtStart_OfWorldFadeOut() + { + var seq = new TeleportAnimSequencer(); + seq.Begin(TeleportEntryKind.Logout); + var (snap, _) = seq.Tick(dt: 0f, worldReady: false); + // At elapsed=0 in WorldFadeOut: smoothstep(0)=0 => alpha=0 (world fully visible). + Assert.Equal(0f, snap.FadeAlpha, precision: 4); + } + + [Fact] + public void FadeAlpha_IsOneAtEnd_OfWorldFadeOut() + { + var seq = new TeleportAnimSequencer(); + seq.Begin(TeleportEntryKind.Logout); + seq.Tick(0f, worldReady: false); // consume enter-sound tick, elapsed≈0 + + // Drive to just BEFORE the transition (so we're still in WorldFadeOut) + DriveSeconds(seq, TeleportAnimSequencer.FadeTime - 0.02f, worldReady: false); + Assert.Equal(TeleportAnimState.WorldFadeOut, seq.State); + + var (snap, _) = seq.Tick(0f, worldReady: false); + // smoothstep(clamp((1.0-0.02)/1.0,0,1)) should be close to 1 + Assert.True(snap.FadeAlpha > 0.95f, $"Expected FadeAlpha near 1, got {snap.FadeAlpha}"); + } + + [Fact] + public void FadeAlpha_IsMonotonicallyIncreasing_DuringWorldFadeOut() + { + var seq = new TeleportAnimSequencer(); + seq.Begin(TeleportEntryKind.Logout); + seq.Tick(0f, worldReady: false); + + float prev = -1f; + // Sample 50 steps within FadeTime + float step = TeleportAnimSequencer.FadeTime / 50f; + for (int i = 0; i < 48; i++) // stop before transition + { + var (snap, _) = seq.Tick(step, worldReady: false); + if (seq.State != TeleportAnimState.WorldFadeOut) break; + Assert.True(snap.FadeAlpha >= prev - 0.001f, + $"Alpha decreased: {prev} -> {snap.FadeAlpha} at step {i}"); + prev = snap.FadeAlpha; + } + } + + [Fact] + public void FadeAlpha_IsZero_DuringTunnelStates() + { + var seq = new TeleportAnimSequencer(); + seq.Begin(TeleportEntryKind.Portal); + seq.Tick(0f, worldReady: false); + + // In Tunnel state, overlay alpha should be 0 (the tunnel viewport is shown; no alpha quad needed) + var (snap, _) = seq.Tick(0.016f, worldReady: false); + Assert.Equal(TeleportAnimState.Tunnel, seq.State); + Assert.Equal(0f, snap.FadeAlpha); + } + + [Fact] + public void ShowTunnel_TrueInTunnelFamilyStates_FalseOtherwise() + { + // Logout path: WorldFadeOut -> TunnelFadeIn -> Tunnel -> TunnelContinue -> TunnelFadeOut -> WorldFadeIn -> Off + var seq = new TeleportAnimSequencer(); + seq.Begin(TeleportEntryKind.Logout); + seq.Tick(0f, worldReady: false); + + // WorldFadeOut: ShowTunnel = false + var (snap, _) = seq.Tick(0.016f, worldReady: false); + Assert.Equal(TeleportAnimState.WorldFadeOut, seq.State); + Assert.False(snap.ShowTunnel); + + // Advance to TunnelFadeIn + DriveSeconds(seq, TeleportAnimSequencer.FadeTime, worldReady: false); + Assert.Equal(TeleportAnimState.TunnelFadeIn, seq.State); + var (snap2, _) = seq.Tick(0f, worldReady: false); + Assert.True(snap2.ShowTunnel); + + // Advance to Tunnel + DriveSeconds(seq, TeleportAnimSequencer.FadeTime, worldReady: false); + Assert.Equal(TeleportAnimState.Tunnel, seq.State); + var (snap3, _) = seq.Tick(0f, worldReady: false); + Assert.True(snap3.ShowTunnel); + } + + [Fact] + public void ShowPleaseWait_TrueOnlyInTunnelContinue() + { + var seq = new TeleportAnimSequencer(); + seq.Begin(TeleportEntryKind.Portal); + seq.Tick(0f, worldReady: false); + + // Tunnel: ShowPleaseWait false + var (snap1, _) = seq.Tick(0.016f, worldReady: false); + Assert.Equal(TeleportAnimState.Tunnel, seq.State); + Assert.False(snap1.ShowPleaseWait); + + // Advance to TunnelContinue + var (snap2, _) = seq.Tick(0.016f, worldReady: true); + Assert.Equal(TeleportAnimState.TunnelContinue, seq.State); + Assert.True(snap2.ShowPleaseWait); + } }