From 1a7b0ed3d57885f618ec6220ad48fe91be749b34 Mon Sep 17 00:00:00 2001 From: Erik Date: Thu, 20 Aug 2026 18:35:37 +0200 Subject: [PATCH] fix(mosstank): read the real busy signal, not the last-requested spell A buff pass cast exactly one spell and then stalled at zero. The log timeline made it unambiguous: one "casting Concentration", then every later pass queued 86 buffs and cast none until the 25s stall fired. IsCasting was bound to RuntimeSpellCastState.LastRequestedSpellId. That property records the last spell REQUESTED and is cleared only by Reset() at session teardown -- it is honestly named, and I read a busy flag into it that was never there. So it latched true on the first successful cast and stayed true for the rest of the session, and every tick returned early at the busy check. It now reads the shared busy count: incremented by the cast path (FreeHandsAndCastSpell @0x00566EF0) and decremented by the server's UseDone, which is the actual in-flight signal. EvaluateGate reports PluginCastGate.Busy as well, so a genuinely wedged counter names itself in the status line instead of presenting as silence. Solution builds clean; 14,437 tests pass on the standard hermetic lane filter, 0 failures. Co-Authored-By: Claude Opus 5 --- .../Plugins/AppAutomationSurface.cs | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/AcDream.App/Plugins/AppAutomationSurface.cs b/src/AcDream.App/Plugins/AppAutomationSurface.cs index f000d31b..90e929ed 100644 --- a/src/AcDream.App/Plugins/AppAutomationSurface.cs +++ b/src/AcDream.App/Plugins/AppAutomationSurface.cs @@ -384,14 +384,27 @@ internal sealed class AppAutomationSurface } // ── IMagicCommands ──────────────────────────────────────────────────── + /// + /// True while an action the server has not acknowledged is in flight. + /// + /// + /// Reads the shared busy count, which the cast path increments + /// (FreeHandsAndCastSpell @0x00566EF0) and the server's UseDone + /// decrements. NOT RuntimeSpellCastState.LastRequestedSpellId: that + /// records the last spell requested and is cleared only by Reset at session + /// teardown, so using it as a busy flag latches true after the first cast + /// and never clears -- which is exactly what stalled MossTank's first buff + /// pass at one cast. + /// public bool IsCasting { get { - RuntimeSpellCastState? cast; + GameRuntime? runtime; lock (_gate) - cast = _cast; - return cast?.LastRequestedSpellId is not null; + runtime = _runtime; + return runtime is not null + && runtime.InventoryOwner.Transactions.BusyCount > 0; } } @@ -408,6 +421,8 @@ internal sealed class AppAutomationSurface return PluginCastGate.Unavailable; if (!spellbook.Knows(spellId)) return PluginCastGate.NotKnown; + if (IsCasting) + return PluginCastGate.Busy; return cast.EvaluateCastGate(spellId) switch {