else
         {
             var nextTarget = FindNextTarget();
-            _logger.LogInformation("NT → {NT}", nextTarget);
-
             if (nextTarget is { IsDead: false })
                 SetTarget(nextTarget);
         }
 
     private readonly ILoggerFactory _loggerFactory;
     private readonly IGameGui _gameGui;
     private readonly IClientState _clientState;
+    private readonly ILogger<GatheringController> _logger;
     private readonly Regex _revisitRegex;
 
     private CurrentRequest? _currentRequest;
         MovementController movementController,
         MoveTo.Factory moveFactory,
         Mount.Factory mountFactory,
+        Combat.Factory combatFactory,
         Interact.Factory interactFactory,
         GatheringPointRegistry gatheringPointRegistry,
         GameFunctions gameFunctions,
         IGameGui gameGui,
         IClientState clientState,
         IPluginLog pluginLog)
-        : base(chatGui, logger)
+        : base(chatGui, mountFactory, combatFactory, condition, logger)
     {
         _movementController = movementController;
         _moveFactory = moveFactory;
         _loggerFactory = loggerFactory;
         _gameGui = gameGui;
         _clientState = clientState;
+        _logger = logger;
 
         _revisitRegex = dataManager.GetRegex<LogMessage>(5574, x => x.Text, pluginLog)
                         ?? throw new InvalidDataException("No regex found for revisit message");
 
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using Dalamud.Game.ClientState.Conditions;
 using Dalamud.Plugin.Services;
 using Microsoft.Extensions.Logging;
 using Questionable.Controller.Steps;
+using Questionable.Controller.Steps.Common;
+using Questionable.Controller.Steps.Interactions;
 using Questionable.Controller.Steps.Shared;
+using Questionable.Model.Questing;
 
 namespace Questionable.Controller;
 
 internal abstract class MiniTaskController<T>
 {
-    protected readonly IChatGui _chatGui;
-    protected readonly ILogger<T> _logger;
     protected readonly TaskQueue _taskQueue = new();
 
-    protected MiniTaskController(IChatGui chatGui, ILogger<T> logger)
+    private readonly IChatGui _chatGui;
+    private readonly Mount.Factory _mountFactory;
+    private readonly Combat.Factory _combatFactory;
+    private readonly ICondition _condition;
+    private readonly ILogger<T> _logger;
+
+    protected MiniTaskController(IChatGui chatGui, Mount.Factory mountFactory, Combat.Factory combatFactory,
+        ICondition condition, ILogger<T> logger)
     {
         _chatGui = chatGui;
         _logger = logger;
+        _mountFactory = mountFactory;
+        _combatFactory = combatFactory;
+        _condition = condition;
     }
 
     protected virtual void UpdateCurrentTask()
         ETaskResult result;
         try
         {
+            if (_taskQueue.CurrentTask.WasInterrupted())
+            {
+                InterruptQueueWithCombat();
+                return;
+            }
+
             result = _taskQueue.CurrentTask.Update();
         }
         catch (Exception e)
 
     protected virtual void OnNextStep(ILastTask task)
     {
-
     }
 
     public abstract void Stop(string label);
 
     public virtual IList<string> GetRemainingTaskNames() =>
         _taskQueue.RemainingTasks.Select(x => x.ToString() ?? "?").ToList();
+
+    public void InterruptQueueWithCombat()
+    {
+        _logger.LogWarning("Interrupted, attempting to resolve (if in combat)");
+        if (_condition[ConditionFlag.InCombat])
+        {
+            List<ITask> tasks = [];
+            if (_condition[ConditionFlag.Mounted])
+                tasks.Add(_mountFactory.Unmount());
+
+            tasks.Add(_combatFactory.CreateTask(null, false, EEnemySpawnType.QuestInterruption, [], [], []));
+            tasks.Add(new WaitAtEnd.WaitDelay());
+            _taskQueue.InterruptWith(tasks);
+        }
+        else
+            _taskQueue.InterruptWith([new WaitAtEnd.WaitDelay()]);
+    }
 }
 
     private readonly GatheringController _gatheringController;
     private readonly QuestRegistry _questRegistry;
     private readonly IKeyState _keyState;
+    private readonly IChatGui _chatGui;
     private readonly ICondition _condition;
     private readonly IToastGui _toastGui;
     private readonly Configuration _configuration;
     private readonly YesAlreadyIpc _yesAlreadyIpc;
     private readonly TaskCreator _taskCreator;
-    private readonly Mount.Factory _mountFactory;
-    private readonly Combat.Factory _combatFactory;
+    private readonly ILogger<QuestController> _logger;
 
     private readonly string _actionCanceledText;
 
         Mount.Factory mountFactory,
         Combat.Factory combatFactory,
         IDataManager dataManager)
-        : base(chatGui, logger)
+        : base(chatGui, mountFactory, combatFactory, condition, logger)
     {
         _clientState = clientState;
         _gameFunctions = gameFunctions;
         _gatheringController = gatheringController;
         _questRegistry = questRegistry;
         _keyState = keyState;
+        _chatGui = chatGui;
         _condition = condition;
         _toastGui = toastGui;
         _configuration = configuration;
         _yesAlreadyIpc = yesAlreadyIpc;
         _taskCreator = taskCreator;
-        _mountFactory = mountFactory;
-        _combatFactory = combatFactory;
+        _logger = logger;
 
         _condition.ConditionChange += OnConditionChange;
         _toastGui.Toast += OnNormalToast;
     }
 
     public bool IsRunning => !_taskQueue.AllTasksComplete;
+    public TaskQueue TaskQueue => _taskQueue;
 
     public sealed class QuestProgress
     {
         }
     }
 
-    public void InterruptQueueWithCombat()
-    {
-        _logger.LogWarning("Interrupted with action canceled message, attempting to resolve");
-        List<ITask> tasks = [];
-        if (_condition[ConditionFlag.Mounted])
-            tasks.Add(_mountFactory.Unmount());
-
-        tasks.Add(_combatFactory.CreateTask(null, false, EEnemySpawnType.QuestInterruption, [], [], []));
-        tasks.Add(new WaitAtEnd.WaitDelay());
-        _taskQueue.InterruptWith(tasks);
-    }
-
     public void Dispose()
     {
         _toastGui.ErrorToast -= OnErrorToast;
 
     {
     }
 
+    public virtual InteractionProgressContext? ProgressContext() => null;
+
     public bool Start()
     {
         _continueAt = DateTime.Now.Add(Delay);
 
         ILogger<MountTask> logger) : ITask
     {
         private bool _mountTriggered;
+        private InteractionProgressContext? _progressContext;
         private DateTime _retryAt = DateTime.MinValue;
 
+        public InteractionProgressContext? ProgressContext() => _progressContext;
+
         public bool ShouldRedoOnInterrupt() => true;
 
         public bool Start()
                     return ETaskResult.TaskComplete;
                 }
 
-                _mountTriggered = gameFunctions.Mount();
+                _progressContext = InteractionProgressContext.FromActionUse(() => _mountTriggered = gameFunctions.Mount());
+
                 _retryAt = DateTime.Now.AddSeconds(5);
                 return ETaskResult.StillRunning;
             }
 
 
 internal interface ITask
 {
+    InteractionProgressContext? ProgressContext() => null;
+
+    bool WasInterrupted()
+    {
+        var progressContext = ProgressContext();
+        if (progressContext != null)
+        {
+            progressContext.Update();
+            return progressContext.WasInterrupted();
+        }
+
+        return false;
+    }
+
     bool ShouldRedoOnInterrupt() => false;
 
     bool Start();
 
--- /dev/null
+using System;
+using FFXIVClientStructs.FFXIV.Client.Game;
+
+namespace Questionable.Controller.Steps;
+
+internal sealed class InteractionProgressContext
+{
+    private bool _firstUpdateDone;
+    public bool CheckSequence { get; private set; }
+    public int CurrentSequence { get; private set; }
+
+    private InteractionProgressContext(bool checkSequence, int currentSequence)
+    {
+        CheckSequence = checkSequence;
+        CurrentSequence = currentSequence;
+    }
+
+    public static unsafe InteractionProgressContext Create(bool checkSequence)
+    {
+        if (!checkSequence)
+        {
+            // this is a silly hack; we assume that the previous cast was successful
+            // if not for this, we'd instantly be seen as interrupted
+            ActionManager.Instance()->CastTimeElapsed = ActionManager.Instance()->CastTimeTotal;
+        }
+
+        return new InteractionProgressContext(checkSequence, ActionManager.Instance()->LastUsedActionSequence);
+    }
+
+    private static unsafe (bool, InteractionProgressContext?) FromActionUseInternal(Func<bool> func)
+    {
+        int oldSequence = ActionManager.Instance()->LastUsedActionSequence;
+        if (!func())
+            return (false, null);
+        int newSequence = ActionManager.Instance()->LastUsedActionSequence;
+        if (oldSequence == newSequence)
+            return (true, null);
+        return (true, Create(true));
+    }
+
+    public static InteractionProgressContext? FromActionUse(Func<bool> func)
+    {
+        return FromActionUseInternal(func).Item2;
+    }
+
+    public static InteractionProgressContext? FromActionUseOrDefault(Func<bool> func)
+    {
+        var result = FromActionUseInternal(func);
+        if (!result.Item1)
+            return null;
+        return result.Item2 ?? Create(false);
+    }
+
+    public unsafe void Update()
+    {
+        if (!_firstUpdateDone)
+        {
+            int lastSequence = ActionManager.Instance()->LastUsedActionSequence;
+            if (!CheckSequence && lastSequence > CurrentSequence)
+            {
+                CheckSequence = true;
+                CurrentSequence = lastSequence;
+            }
+
+            _firstUpdateDone = true;
+        }
+    }
+
+    public unsafe bool WasSuccessful()
+    {
+        if (CheckSequence)
+        {
+            if (CurrentSequence != ActionManager.Instance()->LastUsedActionSequence ||
+                CurrentSequence != ActionManager.Instance()->LastHandledActionSequence)
+                return false;
+        }
+
+        return ActionManager.Instance()->CastTimeElapsed > 0 &&
+               Math.Abs(ActionManager.Instance()->CastTimeElapsed - ActionManager.Instance()->CastTimeTotal) < 0.001f;
+    }
+
+    public unsafe bool WasInterrupted()
+    {
+        if (CheckSequence)
+        {
+            if (CurrentSequence == ActionManager.Instance()->LastHandledActionSequence &&
+                CurrentSequence == ActionManager.Instance()->LastUsedActionSequence)
+                return false;
+        }
+
+        return ActionManager.Instance()->CastTimeElapsed == 0 &&
+               ActionManager.Instance()->CastTimeTotal > 0;
+    }
+
+    public override string ToString() =>
+        $"IPCtx({(CheckSequence ? CurrentSequence : "-")} - {WasSuccessful()}, {WasInterrupted()})";
+}
 
                 return null;
             }
 
-            return new DoAttune(step.DataId.Value, step.AetherCurrentId.Value, gameFunctions, loggerFactory.CreateLogger<DoAttune>());
+            return new DoAttune(step.DataId.Value, step.AetherCurrentId.Value, gameFunctions,
+                loggerFactory.CreateLogger<DoAttune>());
         }
     }
 
-    private sealed class DoAttune(uint dataId, uint aetherCurrentId, GameFunctions gameFunctions, ILogger<DoAttune> logger) : ITask
+    private sealed class DoAttune(
+        uint dataId,
+        uint aetherCurrentId,
+        GameFunctions gameFunctions,
+        ILogger<DoAttune> logger) : ITask
     {
+        private InteractionProgressContext? _progressContext;
+
+        public InteractionProgressContext? ProgressContext() => _progressContext;
+
         public bool Start()
         {
             if (!gameFunctions.IsAetherCurrentUnlocked(aetherCurrentId))
             {
                 logger.LogInformation("Attuning to aether current {AetherCurrentId} / {DataId}", aetherCurrentId,
                     dataId);
-                gameFunctions.InteractWith(dataId);
+                _progressContext =
+                    InteractionProgressContext.FromActionUseOrDefault(() => gameFunctions.InteractWith(dataId));
                 return true;
             }
 
 
         GameFunctions gameFunctions,
         ILogger<DoAttune> logger) : ITask
     {
+        private InteractionProgressContext? _progressContext;
+
+        public InteractionProgressContext? ProgressContext() => _progressContext;
+
         public bool Start()
         {
             if (!aetheryteFunctions.IsAetheryteUnlocked(aetheryteLocation))
             {
                 logger.LogInformation("Attuning to aethernet shard {AethernetShard}", aetheryteLocation);
-                gameFunctions.InteractWith((uint)aetheryteLocation, ObjectKind.Aetheryte);
+                _progressContext = InteractionProgressContext.FromActionUseOrDefault(() =>
+                    gameFunctions.InteractWith((uint)aetheryteLocation, ObjectKind.Aetheryte));
                 return true;
             }
 
 
         GameFunctions gameFunctions,
         ILogger<DoAttune> logger) : ITask
     {
+        private InteractionProgressContext? _progressContext;
+
+        public InteractionProgressContext? ProgressContext() => _progressContext;
+
         public bool Start()
         {
             if (!aetheryteFunctions.IsAetheryteUnlocked(aetheryteLocation))
             {
                 logger.LogInformation("Attuning to aetheryte {Aetheryte}", aetheryteLocation);
-                gameFunctions.InteractWith((uint)aetheryteLocation);
+                _progressContext =
+                    InteractionProgressContext.FromActionUseOrDefault(() =>
+                        gameFunctions.InteractWith((uint)aetheryteLocation));
                 return true;
             }
 
 
         GameFunctions gameFunctions,
         ICondition condition,
         ILogger<DoInteract> logger)
-        : ITask, IConditionChangeAware
+        : ITask
     {
         private bool _needsUnmount;
-        private EInteractionState _interactionState = EInteractionState.None;
+        private InteractionProgressContext? _progressContext;
         private DateTime _continueAt = DateTime.MinValue;
 
         public Quest? Quest => quest;
             set => interactionType = value;
         }
 
+        public InteractionProgressContext? ProgressContext() => _progressContext;
+
         public bool Start()
         {
             IGameObject? gameObject = gameFunctions.FindObjectByDataId(dataId);
 
             if (gameObject.IsTargetable && HasAnyMarker(gameObject))
             {
-                _interactionState = gameFunctions.InteractWith(gameObject)
-                    ? EInteractionState.InteractionTriggered
-                    : EInteractionState.None;
+                _progressContext =
+                    InteractionProgressContext.FromActionUseOrDefault(() => gameFunctions.InteractWith(gameObject));
                 _continueAt = DateTime.Now.AddSeconds(0.5);
                 return true;
             }
             }
             else
             {
-                if (_interactionState == EInteractionState.InteractionConfirmed)
+                if (_progressContext != null && _progressContext.WasSuccessful())
                     return ETaskResult.TaskComplete;
 
                 if (interactionType == EInteractionType.Gather && condition[ConditionFlag.Gathering])
             if (gameObject == null || !gameObject.IsTargetable || !HasAnyMarker(gameObject))
                 return ETaskResult.StillRunning;
 
-            _interactionState = gameFunctions.InteractWith(gameObject)
-                ? EInteractionState.InteractionTriggered
-                : EInteractionState.None;
+            _progressContext =
+                InteractionProgressContext.FromActionUseOrDefault(() => gameFunctions.InteractWith(gameObject));
             _continueAt = DateTime.Now.AddSeconds(0.5);
             return ETaskResult.StillRunning;
         }
         }
 
         public override string ToString() => $"Interact({dataId})";
-
-        public void OnConditionChange(ConditionFlag flag, bool value)
-        {
-            logger.LogDebug("Condition change: {Flag} = {Value}", flag, value);
-            if (_interactionState == EInteractionState.InteractionTriggered &&
-                flag is ConditionFlag.OccupiedInQuestEvent or ConditionFlag.OccupiedInEvent &&
-                value)
-            {
-                logger.LogInformation("Interaction was most likely triggered");
-                _interactionState = EInteractionState.InteractionConfirmed;
-            }
-            else if (dataId is >= 1047901 and <= 1047905 &&
-                     condition[ConditionFlag.Disguised] &&
-                     flag == ConditionFlag
-                         .Mounting71 && // why the fuck is this the flag that's used, instead of OccupiedIn[Quest]Event
-                     value)
-            {
-                logger.LogInformation("(A Knight of Alexandria) Interaction was most likely triggered");
-                _interactionState = EInteractionState.InteractionConfirmed;
-            }
-        }
-
-        private enum EInteractionState
-        {
-            None,
-            InteractionTriggered,
-            InteractionConfirmed,
-        }
     }
 }
 
         private bool _usedItem;
         private DateTime _continueAt;
         private int _itemCount;
+        private InteractionProgressContext? _progressContext;
+
+        public InteractionProgressContext? ProgressContext() => _progressContext;
 
         public ElementId? QuestId => questId;
         public uint ItemId => itemId;
             if (_itemCount == 0)
                 throw new TaskException($"Don't have any {ItemId} in inventory (checks NQ only)");
 
-            _usedItem = UseItem();
+            _progressContext = InteractionProgressContext.FromActionUseOrDefault(() => _usedItem = UseItem());
             _continueAt = DateTime.Now.Add(GetRetryDelay());
             return true;
         }
 
             if (!_usedItem)
             {
-                _usedItem = UseItem();
+                _progressContext = InteractionProgressContext.FromActionUseOrDefault(() => _usedItem = UseItem());
                 _continueAt = DateTime.Now.Add(GetRetryDelay());
                 return ETaskResult.StillRunning;
             }
 
     {
         private bool _teleported;
         private DateTime _continueAt;
+        private InteractionProgressContext? _progressContext;
+
+        public InteractionProgressContext? ProgressContext() => _progressContext;
 
         public bool Start() => !ShouldSkipTeleport();
 
                 chatGui.PrintError($"[Questionable] Aetheryte {targetAetheryte} is not unlocked.");
                 throw new TaskException("Aetheryte is not unlocked");
             }
-            else if (aetheryteFunctions.TeleportAetheryte(targetAetheryte))
-            {
-                logger.LogInformation("Travelling via aetheryte...");
-                return true;
-            }
             else
             {
-                chatGui.Print("[Questionable] Unable to teleport to aetheryte.");
-                throw new TaskException("Unable to teleport to aetheryte");
+                _progressContext =
+                    InteractionProgressContext.FromActionUseOrDefault(() => aetheryteFunctions.TeleportAetheryte(targetAetheryte));
+                logger.LogInformation("Ctx = {C}", _progressContext);
+                if (_progressContext != null)
+                {
+                    logger.LogInformation("Travelling via aetheryte...");
+                    return true;
+                }
+                else
+                {
+                    chatGui.Print("[Questionable] Unable to teleport to aetheryte.");
+                    throw new TaskException("Unable to teleport to aetheryte");
+                }
             }
         }
 
 
             _canRestart = moveParams.RestartNavigation;
         }
 
+        public InteractionProgressContext? ProgressContext() => _mountTask?.ProgressContext();
+
         public bool ShouldRedoOnInterrupt() => true;
 
         public bool Start()
 
 
 internal sealed class TaskQueue
 {
+    private readonly List<ITask> _completedTasks = [];
     private readonly List<ITask> _tasks = [];
-    private int _currentTaskIndex;
     public ITask? CurrentTask { get; set; }
 
-    public IEnumerable<ITask> RemainingTasks => _tasks.Skip(_currentTaskIndex);
-    public bool AllTasksComplete => CurrentTask == null && _currentTaskIndex >= _tasks.Count;
+    public IEnumerable<ITask> RemainingTasks => _tasks;
+    public bool AllTasksComplete => CurrentTask == null && _tasks.Count == 0;
 
     public void Enqueue(ITask task)
     {
 
     public bool TryDequeue([NotNullWhen(true)] out ITask? task)
     {
-        if (_currentTaskIndex >= _tasks.Count)
-        {
-            task = null;
+        task = _tasks.FirstOrDefault();
+        if (task == null)
             return false;
-        }
 
-        task = _tasks[_currentTaskIndex];
         if (task.ShouldRedoOnInterrupt())
-            _currentTaskIndex++;
-        else
-            _tasks.RemoveAt(0);
+            _completedTasks.Add(task);
+
+        _tasks.RemoveAt(0);
         return true;
     }
 
     public bool TryPeek([NotNullWhen(true)] out ITask? task)
     {
-        if (_currentTaskIndex >= _tasks.Count)
-        {
-            task = null;
-            return false;
-        }
-
-        task = _tasks[_currentTaskIndex];
-        return true;
+        task = _tasks.FirstOrDefault();
+        return task != null;
     }
 
     public void Reset()
     {
         _tasks.Clear();
-        _currentTaskIndex = 0;
+        _completedTasks.Clear();
         CurrentTask = null;
     }
 
     public void InterruptWith(List<ITask> interruptionTasks)
     {
-        if (CurrentTask != null)
-        {
-            _tasks.Insert(0, CurrentTask);
-            CurrentTask = null;
-            _currentTaskIndex = 0;
-        }
-
-        _tasks.InsertRange(0, interruptionTasks);
+        List<ITask?> newTasks =
+        [
+            ..interruptionTasks,
+            .._completedTasks.Where(x => !ReferenceEquals(x, CurrentTask)).ToList(),
+            CurrentTask,
+            .._tasks
+        ];
+        Reset();
+        _tasks.AddRange(newTasks.Where(x => x != null).Cast<ITask>());
     }
 }