v0.3.0
Quiver
v0.3.0 (2026-05-28)
Full Changelog Previous Releases
- Bump version to 0.3.0
- Widen auto-shot bar to 102px and lower position by 4px
Bar now spans from the traps button center (210°) to the food button
center (330°) at radius 59 — both x-extents are ±51px. Offset changed
from -4 to -8 to sit a smidge lower below the sphere. - Auto-shot bar: cyan color on speed proc, gold when normal
Bar turns cyan when UNIT_ATTACK_SPEED fires with a faster speed (Rapid
Fire, haste trinkets, Bloodlust etc.) and reverts to gold when speed
returns to normal. speedBoosted flag resets on START_AUTOREPEAT_SPELL
and PLAYER_ENTERING_WORLD so the color is always in sync. - Add auto-shot swing timer bar
Gold bar below the sphere tracks time to next Auto Shot. Counts down
from weapon speed to 0, resets on each Auto Shot proc. Clips to 0.5s
window when Aimed Shot begins casting. Hides outside combat or without
a target. Movement detection uses PLAYER_STARTED/STOPPED_MOVING events
to avoid per-frame GetUnitSpeed; module reference cached as a file-local
upvalue to keep the OnUpdate path allocation-free. - Remove Stings module; fix aspect color update delay
Stings is unused and unlikely to be needed soon — removed from toc, Core,
Sphere (sting bar + UpdateStingDisplay), and deleted the module file.
Aspect delay fix: add UNIT_SPELLCAST_SUCCEEDED to the raw event frame.
Aspects are instant casts so this fires immediately when an aspect is cast,
catching the change before UNIT_AURA eventually fires for some other buff.
UPDATE_SHAPESHIFT_FORM and UNIT_AURA are kept as fallbacks. - Aspects: use raw WoW frame for event registration to avoid AceEvent collision
Quiver:RegisterEvent keys handlers by (addon_object, event). Stings registers
UNIT_AURA after Aspects, silently replacing it. Aspects now uses a dedicated
CreateFrame to register both UNIT_AURA and UPDATE_SHAPESHIFT_FORM independently,
so both events fire regardless of registration order.
Restore Aspects:Disable() in Core:Disable() to unregister the raw frame events. - Fix aspect detection: shapeshift API + correct event, fix UNIT_AURA handler collision
Two root causes:- AceEvent-3.0 keys handlers by (addon, event). Stings:Enable() registers
UNIT_AURA after Aspects:Enable(), silently replacing Aspects' handler.
Aspects now uses UPDATE_SHAPESHIFT_FORM instead, which doesn't conflict. - GetShapeshiftFormInfo returns (icon, active, castable, spellID) — no name.
Previous UnitBuff scan was looking at buff auras, which may not include
aspects (they are shapeshifts in TBC). Detection now uses GetShapeshiftForm +
GetShapeshiftFormInfo + GetSpellInfo to resolve the active aspect by spell ID,
with a UnitBuff fallback for clients where aspects surface as regular buffs.
- AceEvent-3.0 keys handlers by (addon, event). Stings:Enable() registers
- Fix aspect detection event and food orbit count not updating after feeding
Aspects: replace UPDATE_SHAPESHIFT_FORM with UNIT_AURA (aspects are regular
buffs in TBC Anniversary, not shapeshifts, so the shapeshift event never
fires). Remove the prev != current guard in DetectCurrentAspect so
UpdateColor is always called and the no-aspect pulse fires on load.
Food orbit counter: split BAG_UPDATE_DELAYED into two handlers. BAG_UPDATE
(fires per slot, cheap RefreshFoodOrbitCount) covers the orbit count when
the picker is closed; BAG_UPDATE_DELAYED (fires once, expensive GetSuitableFood
scan) covers the picker refresh when it is open. This fixes the count not
updating after quick-feeding via the orbit button if BAG_UPDATE_DELAYED was
unreliable in this scenario. - Fix aspect detection: use UNIT_AURA and always refresh sphere color
UPDATE_SHAPESHIFT_FORM does not fire for aspects since they are regular buffs
in TBC Classic Anniversary, not shapeshifts. Switch to UNIT_AURA filtered to
the player unit.
Remove the prev != current guard in DetectCurrentAspect so UpdateColor is
always called after a scan. This fixes the no-aspect pulse not appearing on
load (prev and current were both nil so the guard skipped the update). - Replace Aspects poller and Stings UNIT_AURA with updateInfo-filtered event handlers
Aspects was polling UnitBuff every second via an OnUpdate ticker, generating
15-20 AuraData table allocations/sec that outpaced Lua 5.1's incremental GC.
Stings fired a full UnitDebuff scan on every UNIT_AURA without filtering.
Both now use UNIT_AURA with updateInfo filtering: skip the scan entirely when
the update contains no aspect/sting changes. Falls back to a full scan when
updateInfo is nil (older Classic clients) or isFullUpdate is set.
Added ASPECTS_BY_NAME and STINGS_BY_NAME O(1) lookup tables to replace the
inner ipairs loops in DetectCurrentAspect and ScanTarget.
Removed Aspects:Disable() (no ticker to stop) and the Core:Disable() call. - Fix memory leaks: stop tickers on disable, reuse config panel, drop UNIT_AURA
- Mana/Aspects/Pet tickers stored as self.ticker so Core:Disable() can stop them
- Config panel built once and refreshed on open rather than recreated each time
- UNIT_AURA dropped for aspect detection (10-20 fires/sec in combat, each allocs
an updateInfo table); 1s poll already handles it - BAG_UPDATE -> BAG_UPDATE_DELAYED (fires once after bag settles, not per slot)
- UpdateTriggerReadiness guards SetScript with _lastSelected to avoid closure churn
- Aspects:DetectCurrentAspect gates UpdateColor on actual aspect change
- TENTHS_STR lookup table replaces string.format in 0.1s trap ticker hot path
- GetContainerItemID fast path in pet food scan before full GetContainerItemInfo
- Reduce trap cooldown ticker allocation rate
cdTicker: use 0.5s interval when trap menu is closed (was always 0.1s).
Menu-open path keeps 0.1s for smooth expansion-button text. This cuts
UpdateTrapCooldowns calls from 10/sec to 2/sec when menu is closed.
PopulateMenu: cache b._normalTex = b:GetNormalTexture() after SetNormalTexture.
UpdateTrapCooldowns: use b._normalTex / triggerBtn._normalTex instead of
calling GetNormalTexture() on every tick. On some WoW client builds
GetNormalTexture() allocates a fresh userdata wrapper each call; caching
eliminates that source entirely. triggerBtn._normalTex is initialised lazily
on first UpdateTrapCooldowns call so it works without a Sphere.lua change.
Display text: show tenths only for sub-1s remaining; integer (math.ceil)
above that. string.format is now only called for the last second of a
cooldown instead of the last 10 seconds, reducing string churn 10x. - Trap trigger button: keep countdown live when menu is closed
UpdateTrapCooldowns now manages cdTicker lifetime itself:- If maxRemaining > 0 and ticker is not running, start it (so the trigger
button countdown stays current without depending on SPELL_UPDATE_COOLDOWN
firing between GCDs). - If maxRemaining == 0 and menu is closed, stop it.
HideAll calls UpdateTrapCooldowns after clearing activeMenu so the ticker
restarts immediately if a trap is still cooling, eliminating the freeze-then-
resume gap that occurred when closing the menu while a cooldown was active.
Per-button visual updates remain gated behind menuOpen so the extra ticker
cycles when the menu is closed are cheap (trigger button only).
- If maxRemaining > 0 and ticker is not running, start it (so the trigger
- Fix pet ring visibility; reduce UpdateTrapCooldowns overhead
Sphere.lua: Move petRing from BACKGROUND to BORDER layer. Both petRing and
the sphere bg texture (bg) were at BACKGROUND; draw order within the same
sublayer is non-deterministic so the ring could render behind the sphere.
BORDER is guaranteed to sit above BACKGROUND, making the happiness ring
reliably visible.
Menus.lua: UpdateTrapCooldowns is called on every SPELL_UPDATE_COOLDOWN
(fires frequently in combat) AND every 0.1s when the trap menu is open.
Previously it re-called GetSpellCooldown for each of 5 buttons even though
Traps:UpdateCooldowns had just read and stored that data. Now it reads from
Traps.cooldowns directly. Also gates per-button visual updates (SetText,
SetDesaturated, cdFrame, cdDim, cdText) behind an activeMenu == traps check
so that work is skipped entirely when the trap menu is closed. - Memory: pre-allocate trap cooldown tables and pulse color constants
Traps.lua: Initialize now seeds self.cooldowns with one {start,duration}
table per trap. UpdateCooldowns updates those tables in-place instead of
replacing them on every SPELL_UPDATE_COOLDOWN event. This event fires
every GCD in combat; the old code discarded and re-created 5 tables each
time, generating continuous GC pressure.
Sphere.lua: Define PULSE_COLOR_MANA_LOW and PULSE_COLOR_NO_ASPECT as
module-level constants. UpdateColor (called every second via the aspect
ticker plus on every UNIT_AURA for the player) now assigns these constant
references instead of allocating a fresh {r,g,b} table on each call. - Fix frame memory leak: pool and reuse menu buttons instead of recreating
RebuildAll fires on PLAYER_ENTERING_WORLD, SPELLS_CHANGED, and
PLAYER_REGEN_ENABLED. The old PopulateMenu created ~52 new WoW frames
per call (26 buttons + 26 blockers) and dropped references to the
previous ones. WoW cannot destroy frames — they accumulated as hidden
C-side objects for the entire session, causing steady memory growth
visible after every zone change.
Fix: NewMenu pre-allocates a fixed pool of CreateBlankButton() frames
sized to #entries. PopulateMenu now resets and reconfigures the same
pool frames on every rebuild instead of creating new ones. Cooldown
frames (cdFrame/cdDim/cdText) are created once per pool slot on first
use via a nil-guard and reused thereafter.
Frame count is now fixed at Initialize time:
aspects 7 + pet 5 + traps 5 + tracking 9 = 26 buttons + 26 blockers - Fix ghost right-clicks on hidden menu buttons (#27)
Hidden SecureActionButtonTemplate buttons intercept right-clicks even at
alpha=0. EnableMouse is a protected operation on secure frames and cannot
be toggled during combat lockdown.
Fix: each popup menu button gets a companion non-secure blocker frame
anchored over it (same strata, one frame level higher, EnableMouse=true).
The blocker is shown when the button is invisible and hidden when the menu
opens — normal frames can be shown/hidden freely during combat lockdown,
so this works in all states without tainting the secure button. - Fix cdText nil guard; consolidate food selection into nested table
UI/Menus.lua:- UpdateTrapCooldowns: guard now checks triggerBtn.cdText as well as
cdDim before accessing either — previously cdDim guard passed but
cdText access was unconditional - Food selection keys consolidated from three sibling flat keys
(menuSelections["food"], ["foodID"], ["foodIsBuff"]) into a single
nested table (menuSelections.food = {name, itemID, isPetBuff}).
Eliminates collision risk with menu names and makes the saved state
self-contained. Existing saves with old flat keys are silently dropped
(food re-selects on next picker use — acceptable for a UI preference)
- UpdateTrapCooldowns: guard now checks triggerBtn.cdText as well as
- Remove dead CastSpellByName helpers and unused saved var keys
All casting goes through SecureActionButtonTemplate attributes; the
module-level Cast() wrappers were dead code that would taint the UI if
called during combat lockdown.
Removed:- Aspects:Cast(), Traps:Cast(), Stings:Cast(), Tracking:Cast()
- Pet:CallPet(), DismissPet(), RevivePet(), MendPet()
- Tracking:Cast() also wrote char.lastTracking which was never read back
- Core defaults: lastAspect (never written) and lastTracking (never read)
- Fix ammo low threshold crossing; gate OnUpdate tickers
Modules/Ammo.lua:- Add wasLow flag: OnAmmoLow now fires once on threshold crossing, not
on every BAG_UPDATE while below threshold - Skip warning when count == 0 (no ammo pouch equipped)
UI/Menus.lua: - selectionTicker starts hidden; shown when a deferred SetAttribute is
queued, hidden again once the write fires — no longer runs every frame - cdTicker moved to module level, starts hidden; shown when the trap
menu opens, hidden when any menu closes — previously ran unconditionally
calling GetSpellCooldown x5 every 0.1s for the entire session
- Add wasLow flag: OnAmmoLow now fires once on threshold crossing, not
- Fix food sort order; macro bindings on sphere; passive spell filter
Modules/Pet.lua:- Fix GetItemInfo destructuring: itemLevel was capturing itemQuality (slot 3)
instead of itemLevel (slot 4), causing all common food to tie and shuffle - Add name as stable tiebreaker so same-ilvl items never reorder on open
UI/Menus.lua: - Filter passive spells out of knownSpellCache so they don't appear as
castable entries in menus - Show Feed Pet spell icon on orbit button when no food is selected
UI/Sphere.lua: - HasBinding helper supports both spell and macro binding types
- Error messages updated to 'action' wording to cover macro bindings
- Fix GetItemInfo destructuring: itemLevel was capturing itemQuality (slot 3)