-- 3.26: Unified buff application block in runBuffUpkeepTick. Worn Totem and invis are now both fixed for navigation: compute needMovement + needInvis upfront, then one combined nav-pause block (/nav pause + 200ms) when either is needed, apply movement first then invis (both with allowSpellCast=true since nav is paused), single /nav pause off afterward. invisApplyingNow renamed buffsApplyingNow to reflect it guards both. INVIS_REFRESH_MIN_TICKS_REMAINING default changed from 0 to 8 (48s proactive refresh) — script now proactively recasts invis before it expires rather than only reacting after it drops.
-- 3.25: Fix invis-not-recast and no-50%-med during navigation. Root cause: allowSpellCast=false during navMoving blocked pppokerApplyInvisClassBuff entirely — so pppokerEnsureInvisBuff returned without casting, and meditateToManaPpp (inside the spell path) was never reached. Fix: upkeep invis section now pauses nav (/nav pause + 200ms), applies invis with allowSpellCast=true so spell+med path is reachable, then resumes nav (/nav pause off). invisApplyingNow guard prevents upkeep re-entry while the cast is in progress (cast can take several seconds). meditateToManaPpp MaxMana read wrapped in pcall for safety. removeLevitationBuffsIfPresent() called in both needInvis and !needInvis paths.
-- 3.24: Med safety buffer + death respawn. meditateToManaPpp now meds to max(requiredMana, 50% of MaxMana) instead of just requiredMana — avoids repeated sit/stand for each subsequent cast (invis refresh, re-gate) during a run leg; log shows need/have/target. handleDeathIfNeeded(): detects Me.State=="DEAD", waits for RespawnWnd, clicks RSPB_STANDARD (bind point), waits until no longer dead + 2s settle, returns true. Wired into runBuffUpkeepTick (fires every 300ms during navigation and objective waits); returns early after respawn so buff checks run clean on the next tick. shouldStop() honored inside all wait loops.
-- 3.23: Upkeep invis-before-movement guard. runBuffUpkeepTick now reads invis state before the movement buff check. If invis is currently up and movement buff expired, movement re-application is skipped — clicking a movement item (Worn Totem, etc.) or casting a movement spell always drops invis in EQ. Character stays hidden; movement is re-applied on the next tick after invis naturally drops (which then fires the normal movement-first, invis-last sequence). Zone-entry helpers (ensureSpeedAndInvisInNeriak, Qeynos, Highpass) are unaffected — they explicitly apply movement before invis and are not in the upkeep path.
-- 3.22: Two latent fixes from code review. (1) warn forward decl — loadTextures referenced warn before its local definition; added local warn to forward-decl block and changed local function warn to assignment so it shares the same upvalue. Without this, any atlas/texture load failure would call nil and crash (never triggered because textures load cleanly, but the bug was real). (2) paintingsTaskSlot cache — getPaintingsTaskSlotNumber scanned up to 30 tasks on every objectiveCompleteFromParse call, which is called 16x per progress refresh (up to 480 TLO reads). Cache set on first successful scan, reset at runQuest start in case task was dropped/re-acquired between runs.
-- 3.21: Gate spell mana check — meditateToManaPpp(gateSpellMana) added before each /cast attempt in both tryGateToPoK_AAorSpellOnly() and tryGateToPoK(). Mana cost read once via mq.TLO.Spell(spellName).Mana() before the loop (math.max 40 floor, same pattern as movement/invis buff functions). Gate AA path unchanged — AAs do not use mana. Fixes low-level casters silently failing Gate casts after spending mana on invis.
-- 3.20: Gate retry — gem-ready polling. Added waitZonedOrGateReady(checkReadyFn, zoneId, maxMs): polls every GATE_READY_POLL_MS (250ms) and exits early when the gate ability/gem/item becomes ready again (fizzle/collapse resolved) instead of sitting out the full GATE_ZONE_WAIT_MS (90s cap). Applied to all four gate-cast paths: AA and spell in tryGateToPoK_AAorSpellOnly(), AA and spell in tryGateToPoK(), Drunkard's Stein in tryGateSteinToPoK(), and philters/potions in tryGatePotionsClickiesToPoK(). Typical collapse/fizzle wait drops from ~90s to ~3-5s. Updated warn messages in tryGateToPoK() AA/spell loops (was "waiting for AA again" — now "collapse/fizzle; retrying"). GATE_ZONE_WAIT_MS (90s) remains as safety cap; no config changes needed.
-- 3.19: Low-level Highpass safe gate (Option A). HIGHPASS_SAFE_GATE_LEVEL (default 40) — if Me.Level < threshold, nav to LOC.HIGHPASS_SAFE_GATE (outside Tiger room, no NPC clusters) before gate ladder runs in leaveHighpassTowardNorthQeynos. Character is still invis during nav; gate drops invis away from guards. Set HIGHPASS_SAFE_GATE_LEVEL=0 to disable. LOC.HIGHPASS_SAFE_GATE = { -78.13, 625.50, -18.68 } (verified in-game, heading WSW from Tiger room).
-- 3.18: Objective timeout no longer kills the run. waitObjectiveDone timeout replaced fail() with warn() — loop retries the same objective automatically on next iteration. runObjectiveStep wrapped in pcall — travel/gate fail() errors (e.g. "Could not reach PoK") warn and retry instead of crashing; stop requests re-thrown so Stop/close still works.
-- 3.17: Fix mana med threshold — meditateToManaPpp was hardcoded to 40 in both pppokerApplyMovementClassBuff and pppokerApplyInvisClassBuff. Low-level casters with >40 mana but less than spell cost would skip sitting entirely and fail the cast. Now passes math.max(40, spellData.Mana()) so the script sits until actually able to cast the spell. Added shouldStop() inside the med loop so Stop request exits immediately instead of waiting up to 2 min for timeout. Med log now shows have/need values.
-- 3.16: Shutdown cleanup — add cleanupAfterRun(): unpause RGMercs, unpause CWTN, unload MQ2AutoSize only if PPPoker loaded it (autosizePreloaded checked at script start via IIFE). cleanupAfterRun() now called on: normal quest completion, mid-run early exits (task unavailable / objectives gone), user stop, and any Lua error (pcall handler in main loop). Pre-preflight early exits (no task, no objectives, title mismatch) keep plain unpause pairs — AutoSize not yet loaded at those points.
-- 3.15: Fix RGMercs pause — solo script, no group: /rgl pauseall -> /rgl pause, /rgl unpauseall -> /rgl unpause. AutoSize: load plugin then /autosize self 3 (consistent run size; mount size left to user's AutoSize config). Clean stale prepBeforeTasselLeg doc comment (was still referencing removed Guise shrink).
-- 3.14: DRU/RNG camouflage fixes. DRU INVIS_CLASS_BUFFS: remove bogus "Invisibility" (DRU can't mem it — was triggering failed /memspell + 8s wait), add full camo ladder best→worst: "Improved Superior Camouflage" (Lv 48, Improved Invis), "Superior Camouflage" (Lv 18), "Camouflage" (Lv 4). RNG: add "Superior Camouflage" (Lv 47) before base "Camouflage" (Lv 14). INVIS_SELF_AA_NAMES: add "Innate Camouflage" (DRU/RNG AA, Alt Act ID 80 per EQResource). INVIS_GROUP_AA_NAMES: add "Shared Camouflage" (DRU/RNG group camo AA, Alt Act ID 518). Both AA names exit cleanly for non-DRU/RNG (AltAbility returns 0 → skipped).
-- 3.13: Remove Guise of the Deceiver shrink system entirely (maybeGuiseShrink, resetGuiseShrinkSession, GUISE_SHRINK_ITEM, GUISE_SHRINK_HEIGHT_MIN, TRAVEL_SHRINK_IN_QEYNOS, TRAVEL_SHRINK_IN_HIGHPASS, gui.shrinkUsed, gui.shrinkPopupShown). Add MQ2AutoSize plugin check/load in runPreflightAfterQuestChecks. Rename ensureSpeedShrinkInvisInHighpass -> ensureSpeedInvisInHighpass (all 5 call sites). Hoist bardSeloActive to module scope (safe now: 14 free slots); remove inner copies from pppokerMovementBuffPresent + pppokerApplyMovementClassBuff. Make syncJournalAfterKeywordTry + tryAcquireQuestFromBigSlick local (previously blocked by 200-local limit). Local count: 187.
-- 3.12: Phase 1 — move 8 state vars into gui table (shrinkUsed, shrinkPopupShown, lastBuffUpkeepTick, rgmercPaused, journalOpenedOnce, barLiveOpts, shimmerState, commIconTex); freed 8 locals. Phase 2 — merge waitUntilGateAltReady+waitUntilGateSpellReady into waitUntilGateReady(checkFn,label,maxMs); inline gatePotionReady alias into waitUntilGatePotionReady; freed 2 locals. Phase 3 — collapse navigationIsActive+navigationIsPaused from ~80 lines to ~36 by removing duplicate inner helper functions (no locals changed). Phase 4 — waitForZoneOrFalse uses mq.gettime() consistently; hasAnyGateItemPath collapsed to single return. Total: 196 -> 186 module locals (14 slots freed).