diff --git a/src/AcDream.Core/World/TeleportAnimSequencer.cs b/src/AcDream.Core/World/TeleportAnimSequencer.cs
new file mode 100644
index 00000000..d7980a78
--- /dev/null
+++ b/src/AcDream.Core/World/TeleportAnimSequencer.cs
@@ -0,0 +1,37 @@
+namespace AcDream.Core.World;
+
+// acclient.h:6871 — TeleportAnimState enum, verbatim order.
+public enum TeleportAnimState
+{
+ Off = 0,
+ WorldFadeOut = 1,
+ TunnelFadeIn = 2,
+ Tunnel = 3,
+ TunnelContinue = 4,
+ TunnelFadeOut = 5,
+ WorldFadeIn = 6,
+}
+
+/// Why the teleport was triggered — drives per-entry start state (spec §2.5).
+public enum TeleportEntryKind { Portal, Login, Death, Logout }
+
+///
+/// Edge-triggered events the sequencer emits on the tick they first occur.
+/// Consumers drive audio / placement / LoginComplete from these; the sequencer
+/// has no dependency on any of those systems.
+///
+public enum TeleportAnimEvent
+{
+ PlayEnterSound, // Begin(): sound_ui_enter_portal
+ EnterTunnel, // Off/WorldFade* -> Tunnel: world is now hidden
+ Place, // Tunnel -> TunnelContinue: world loaded; place the player
+ PlayExitSound, // TunnelFadeOut -> WorldFadeIn: sound_ui_exit_portal
+ FireLoginComplete, // WorldFadeIn -> Off: send GameAction 0xA1
+}
+
+/// Immutable per-frame snapshot from the sequencer.
+public readonly record struct TeleportAnimSnapshot(
+ TeleportAnimState State,
+ float FadeAlpha, // 0 = clear world, 1 = full black
+ bool ShowTunnel, // true during TunnelFadeIn..TunnelFadeOut
+ bool ShowPleaseWait); // true during TunnelContinue only
diff --git a/tests/AcDream.Core.Tests/World/TeleportAnimSequencerTests.cs b/tests/AcDream.Core.Tests/World/TeleportAnimSequencerTests.cs
new file mode 100644
index 00000000..355b22f7
--- /dev/null
+++ b/tests/AcDream.Core.Tests/World/TeleportAnimSequencerTests.cs
@@ -0,0 +1,43 @@
+using AcDream.Core.World;
+using Xunit;
+
+namespace AcDream.Core.Tests.World;
+
+public sealed class TeleportAnimSequencerTests
+{
+ // --- Task 1.1: type presence ---
+
+ [Fact]
+ public void Enums_HaveExpectedValues()
+ {
+ Assert.Equal(0, (int)TeleportAnimState.Off);
+ Assert.Equal(1, (int)TeleportAnimState.WorldFadeOut);
+ Assert.Equal(2, (int)TeleportAnimState.TunnelFadeIn);
+ Assert.Equal(3, (int)TeleportAnimState.Tunnel);
+ Assert.Equal(4, (int)TeleportAnimState.TunnelContinue);
+ Assert.Equal(5, (int)TeleportAnimState.TunnelFadeOut);
+ Assert.Equal(6, (int)TeleportAnimState.WorldFadeIn);
+
+ _ = TeleportEntryKind.Portal;
+ _ = TeleportEntryKind.Login;
+ _ = TeleportEntryKind.Death;
+ _ = TeleportEntryKind.Logout;
+
+ _ = TeleportAnimEvent.PlayEnterSound;
+ _ = TeleportAnimEvent.PlayExitSound;
+ _ = TeleportAnimEvent.Place;
+ _ = TeleportAnimEvent.FireLoginComplete;
+ _ = TeleportAnimEvent.EnterTunnel;
+ }
+
+ [Fact]
+ public void Snapshot_DefaultsAreOff_ClearAlpha()
+ {
+ var snap = new TeleportAnimSnapshot(
+ TeleportAnimState.Off, FadeAlpha: 0f, ShowTunnel: false, ShowPleaseWait: false);
+ Assert.Equal(TeleportAnimState.Off, snap.State);
+ Assert.Equal(0f, snap.FadeAlpha);
+ Assert.False(snap.ShowTunnel);
+ Assert.False(snap.ShowPleaseWait);
+ }
+}