diff --git a/src/AcDream.Core/Physics/TransitionTypes.cs b/src/AcDream.Core/Physics/TransitionTypes.cs
new file mode 100644
index 0000000..7c973ff
--- /dev/null
+++ b/src/AcDream.Core/Physics/TransitionTypes.cs
@@ -0,0 +1,271 @@
+using System.Numerics;
+using DatReaderWriter.Types;
+
+namespace AcDream.Core.Physics;
+
+public enum TransitionState
+{
+ Invalid = 0,
+ OK = 1,
+ Collided = 2,
+ Adjusted = 3,
+ Slid = 4,
+}
+
+public enum InsertType
+{
+ Transition = 0,
+ Placement = 1,
+ InitialPlacement = 2,
+}
+
+[Flags]
+public enum ObjectInfoState : uint
+{
+ None = 0x000,
+ Contact = 0x001,
+ OnWalkable = 0x002,
+ IsViewer = 0x004,
+ PathClipped = 0x008,
+ FreeRotate = 0x010,
+ PerfectClip = 0x040,
+ IsImpenetrable = 0x080,
+ IsPlayer = 0x100,
+ EdgeSlide = 0x200,
+ IgnoreCreatures = 0x400,
+}
+
+///
+/// Per-object flags and properties for the transition system.
+/// ACE: ObjectInfo. Decompiled: struct at transition + various offsets.
+///
+public sealed class ObjectInfo
+{
+ public ObjectInfoState State;
+ public float StepUpHeight = 0.01f; // PhysicsGlobals.DefaultStepHeight
+ public float StepDownHeight = 0.04f;
+ public bool Ethereal;
+ public bool StepDown = true;
+ public float Scale = 1.0f;
+
+ // Convenience flag checks
+ public bool Contact => State.HasFlag(ObjectInfoState.Contact);
+ public bool OnWalkable => State.HasFlag(ObjectInfoState.OnWalkable);
+ public bool IsViewer => State.HasFlag(ObjectInfoState.IsViewer);
+ public bool IsPlayer => State.HasFlag(ObjectInfoState.IsPlayer);
+ public bool EdgeSlide => State.HasFlag(ObjectInfoState.EdgeSlide);
+ public bool PathClipped => State.HasFlag(ObjectInfoState.PathClipped);
+ public bool FreeRotate => State.HasFlag(ObjectInfoState.FreeRotate);
+}
+
+///
+/// Accumulated collision results for the current transition.
+/// ACE: CollisionInfo.
+///
+public sealed class CollisionInfo
+{
+ public bool ContactPlaneValid;
+ public Plane ContactPlane;
+ public uint ContactPlaneCellId;
+ public bool ContactPlaneIsWater;
+
+ public bool LastKnownContactPlaneValid;
+ public Plane LastKnownContactPlane;
+ public uint LastKnownContactPlaneCellId;
+ public bool LastKnownContactPlaneIsWater;
+
+ public bool SlidingNormalValid;
+ public Vector3 SlidingNormal; // XY only (Z zeroed)
+
+ public bool CollisionNormalValid;
+ public Vector3 CollisionNormal;
+
+ public bool CollidedWithEnvironment;
+ public int FramesStationaryFall;
+
+ public Vector3 AdjustOffset;
+ public List CollideObjectGuids = new();
+ public uint? LastCollidedObjectGuid;
+
+ public void SetContactPlane(Plane plane, uint cellId, bool isWater = false)
+ {
+ ContactPlaneValid = true;
+ ContactPlane = plane;
+ ContactPlaneCellId = cellId;
+ ContactPlaneIsWater = isWater;
+
+ LastKnownContactPlaneValid = true;
+ LastKnownContactPlane = plane;
+ LastKnownContactPlaneCellId = cellId;
+ LastKnownContactPlaneIsWater = isWater;
+ }
+
+ public void SetSlidingNormal(Vector3 normal)
+ {
+ SlidingNormalValid = true;
+ SlidingNormal = new Vector3(normal.X, normal.Y, 0f);
+ if (SlidingNormal.LengthSquared() > PhysicsGlobals.EpsilonSq)
+ SlidingNormal = Vector3.Normalize(SlidingNormal);
+ }
+
+ public void SetCollisionNormal(Vector3 normal)
+ {
+ CollisionNormalValid = true;
+ CollisionNormal = normal;
+ }
+}
+
+///
+/// Movement path descriptor — tracks sphere positions in multiple
+/// coordinate frames during collision resolution.
+/// ACE: SpherePath.
+///
+public sealed class SpherePath
+{
+ public int NumSphere = 1;
+
+ // Sphere arrays — index 0 = foot/body, index 1 = head (when NumSphere==2)
+ public readonly Sphere[] LocalSphere = new Sphere[2] { new(), new() };
+ public readonly Sphere[] GlobalSphere = new Sphere[2] { new(), new() };
+ public readonly Sphere[] GlobalCurrCenter = new Sphere[2] { new(), new() };
+
+ // Positions
+ public Vector3 BeginPos;
+ public Vector3 EndPos;
+ public Vector3 CurPos;
+ public Vector3 CheckPos;
+ public Quaternion BeginOrientation = Quaternion.Identity;
+ public Quaternion EndOrientation = Quaternion.Identity;
+ public Quaternion CurOrientation = Quaternion.Identity;
+ public Quaternion CheckOrientation = Quaternion.Identity;
+
+ // Cell tracking
+ public uint CurCellId;
+ public uint CheckCellId;
+
+ // Per-step offset
+ public Vector3 GlobalOffset;
+
+ // Step-up state
+ public bool StepUp;
+ public Vector3 StepUpNormal;
+ public bool Collide;
+
+ // Step-down state
+ public bool StepDown;
+ public float StepDownAmt;
+ public float WalkInterp = 1.0f;
+
+ // Walkable tracking
+ public bool WalkableValid;
+ public Plane WalkablePlane;
+ public float WalkableAllowance = PhysicsGlobals.FloorZ;
+
+ // Backup for restore
+ public Vector3 BackupCheckPos;
+ public uint BackupCheckCellId;
+
+ // Misc flags
+ public bool NegPolyHit;
+ public bool NegStepUp;
+ public Vector3 NegCollisionNormal;
+ public bool CheckWalkable;
+ public InsertType InsertType = InsertType.Transition;
+
+ public void SetCheckPos(Vector3 pos, uint cellId)
+ {
+ CheckPos = pos;
+ CheckCellId = cellId;
+ // Update global spheres to match new check position
+ for (int i = 0; i < NumSphere; i++)
+ {
+ GlobalSphere[i].Origin = LocalSphere[i].Origin + pos;
+ GlobalSphere[i].Radius = LocalSphere[i].Radius;
+ }
+ }
+
+ public void AddOffsetToCheckPos(Vector3 offset)
+ {
+ CheckPos += offset;
+ for (int i = 0; i < NumSphere; i++)
+ GlobalSphere[i].Origin += offset;
+ }
+
+ public void SaveCheckPos()
+ {
+ BackupCheckPos = CheckPos;
+ BackupCheckCellId = CheckCellId;
+ }
+
+ public void RestoreCheckPos()
+ {
+ SetCheckPos(BackupCheckPos, BackupCheckCellId);
+ }
+
+ ///
+ /// Initialize the path for a simple point-to-point movement.
+ ///
+ public void InitPath(Vector3 begin, Vector3 end, uint cellId,
+ float sphereRadius, float sphereHeight = 0f)
+ {
+ BeginPos = begin;
+ EndPos = end;
+ CurPos = begin;
+ CurCellId = cellId;
+
+ LocalSphere[0].Origin = new Vector3(0, 0, sphereRadius);
+ LocalSphere[0].Radius = sphereRadius;
+
+ if (sphereHeight > 0)
+ {
+ NumSphere = 2;
+ LocalSphere[1].Origin = new Vector3(0, 0, sphereHeight - sphereRadius);
+ LocalSphere[1].Radius = sphereRadius;
+ }
+ else
+ {
+ NumSphere = 1;
+ }
+
+ SetCheckPos(begin, cellId);
+
+ // Also init CurCenter
+ for (int i = 0; i < NumSphere; i++)
+ {
+ GlobalCurrCenter[i].Origin = LocalSphere[i].Origin + begin;
+ GlobalCurrCenter[i].Radius = LocalSphere[i].Radius;
+ }
+ }
+}
+
+///
+/// Physics constants matching the retail AC client.
+/// ACE: PhysicsGlobals. Decompiled: various DAT_ addresses.
+///
+public static class PhysicsGlobals
+{
+ public const float EPSILON = 0.0002f;
+ public const float EpsilonSq = EPSILON * EPSILON;
+ public const float LandingZ = 0.0871557f;
+ public const float FloorZ = 0.6642f;
+ public const float DefaultStepHeight = 0.01f;
+ public const float Gravity = -9.8f;
+ public const float MaxVelocity = 50.0f;
+ public const float DummySphereRadius = 0.1f;
+ public const int MaxTransitionSteps = 30; // retail uses 30, ACE uses 1000
+}
+
+///
+/// The main collision transition orchestrator.
+/// ACE: Transition. Decompiled: CTransition.
+/// Stub class — algorithm methods added in Task 6b-6d.
+///
+public sealed class Transition
+{
+ public ObjectInfo ObjectInfo = new();
+ public SpherePath SpherePath = new();
+ public CollisionInfo CollisionInfo = new();
+
+ // Will be populated in Task 6b:
+ // public TransitionState FindTransitionalPosition(PhysicsEngine engine, PhysicsDataCache cache) { ... }
+}