# Changelog

## v1.12.1

### Fixes
- **"Mute all classes except mine" now refreshes correctly when switching characters.** The snapshot used to undo the previous mute set was stored per-profile, so if you used per-character profiles, logging from one class into another could leave the previous character's mutes stacked on top of the new ones (over-muting, and potentially silencing some of your own sounds). That snapshot is now account-global, so any character cleanly undoes whatever the previous one applied before re-detecting its own class and reapplying.

## v1.12.0

### New Features
- **"Mute all classes except mine."** A new toggle at the top of the Muting tab that aggressively mutes the spell sounds of every class except your own, so you stop hearing other players' rotations while you focus on your own. It is built from a per-class spell-sound dataset (the game's spell-family data crossed with Resonance's existing mute data) and detects your class automatically. Two intentional limitations are spelled out right in the UI: because WoW mutes sounds per file and globally (it cannot tell who is casting), you will still hear other players of your own class; and where a sound file is shared between another class and yours, it gets muted too, since this is meant to be an aggressive option. Retail only. Fully localized.

## v1.11.0

### New Features
- **Mists of Pandaria Classic support.** Resonance now runs natively on MoP Classic (5.5.x), joining retail, Vanilla, and TBC Anniversary. It ships a Mists-specific data set built from MoP's own DB2 tables: modern spell-sound muting, classic-sound replacement (with the Browse search and auto-suggestions fully populated), and mount / ambient / sheathe muting all work the same as on the other flavors. Like the other Classic builds it mutes transiently (only during each cast) rather than persistently, since Classic spells share generic sound files.
- **Spell-mute backfill for MoP's unlinked classic abilities.** Mists' `SpellXSpellVisual` table doesn't connect many old player abilities — Spell Reflection, Charge, Battle Shout, Taunt, and others — to their sounds, so a pure DB2 walk leaves them unmuteable. Resonance backfills those from the verified TBC/Vanilla sound linkages, keeping only files that actually exist in Mists' own sound table. Confirmed in-game that the recovered sounds are the ones the client plays; coverage grew from ~17k to ~40k spells.

### Fixes
- **Mount sounds on Classic now resolve to the correct creature.** Mount muting read the stale spell-effect display, which on Mists pointed some mounts at the wrong creature entirely (e.g. Swift White Steed resolved to a *ghost* sound set, so muting it did nothing audible). Mount displays are now resolved through Mists' modern `MountXDisplay` table — the same mapping the live client uses — so muting a mount silences its actual sounds. (As before, the gallop is the shared creature-footstep system and is intentionally left on.)
- **"Mute skyriding wind sounds" is hidden on Classic flavors.** Skyriding is a retail-only flight system, so the toggle no longer appears on Vanilla, TBC, or MoP Classic.

## v1.10.0

### New Features
- **Class sound presets for Demon Hunter and Evoker.** The class-preset system now covers the two newest classes. Neither has classic-era sounds to swap in, so these presets *mute* the loud looping/aura layers of the frequently-cast rotation abilities while keeping the clean cast one-shots — and play a clean classic cast where one fits better than silence. Demon Hunter silences Glide's wing-flap loops (keeping the cast and landing), the Devourer spec's Consume aura hum (replaced with a clean classic frost wind-up → cast), Void Ray's sustained void loops, and Soul Immolation's precast loop. Evoker silences Disintegrate's channelled-beam loop and Augmentation's Ebon Might aura hum. Every target ability and its rotation usage was checked against the live class guides (archon.gg / icy-veins / Wowhead) and every sound was verified through the DB2 spell→sound chain (no filename guessing); each mute is scoped to the ability's own dedicated sound files so it never bleeds into other spells. Apply from the class-presets list like any other class.
- **Ambient muting now silences positional zone emitters, mapped from Blizzard's own placement data.** A zone's ambience also lives in positional `sound/emitters/` and `sound/doodad/` loops (voidspire zaps, zone tones, cave ambience, void-structure hums) that the original `sound/ambience/`-only scan missed. Two complementary passes now cover them: `tools/generate_ambient_data.lua` harvests structured emitters by their `<patch>amb-[zoneN-]<zone>` filename token, and a new `tools/generate_emitter_data.py` walks the `SoundEmitters` DB2 table **by map ID** → `SoundKit` → `FileDataID`, so a zone mutes exactly what is placed on its map regardless of how the file is named (this catches generic/shared files filename matching can't). Classification is expansion-scoped, so a stock loop reused across older expansions (e.g. a generic wind) isn't silenced game-wide by one zone toggle. Voidstorm grew from 122 → 300 muteable FIDs — now including the void-structure doodad hums, voidspire black holes, and portal/beam emitters — with zero existing FIDs dropped. Resolves NoraLiyn's CurseForge report that Voidstorm ambience kept playing despite muting.

### Fixes
- **Precast sounds now respect the multi-hit window.** `UNIT_SPELLCAST_START` previously played the precast sound with no throttle, so spamming the same spell spammed the precast sound — while "Cast complete" already debounced. Precast now runs the same debounce + multi-hit-window logic as cast-complete, using its own tracking tables so a precast never suppresses (nor is suppressed by) the matching cast-complete sound under the "Both" trigger. Sequence sounds still bypass the throttle so each hit advances the index. Resolves siderealist's CurseForge report.
- **Precast-only sounds stop when the cast is interrupted.** A spell set to play *only* a precast sound kept playing it to the end even if the cast was cancelled, interrupted, or broken by movement — only the "Both" (precast + cast) trigger stopped correctly. Precast-only now registers its sound the same way, so `UNIT_SPELLCAST_STOP` cuts it off the instant the cast bar disappears.

## v1.9.1

### Compatibility

- **Updated for WoW 12.0.7 (Midnight).** Bumped the retail TOC interface (Resonance and Resonance_Data) to `120007` so the addon loads without the out-of-date flag on patch 12.0.7. No code changes were needed — none of the patch's API changes touch Resonance's code paths.

## v1.9.0

### New Features
- **Ranged sound override (Hunter).** New "Auto Shot sound source" dropdown decouples the `classicAutoShot` engine from the equipped weapon: choose `Auto`, `Always bow`, or `Always gun`. Resolves Noshei's CurseForge request for cross-type substitution (e.g., gunfire → bow on TBC). An advanced toggle exposes `Always crossbow` for users who want to split crossbow out of the bow pool. Existing `classicAutoShot=true` users get `"auto"` injected on first load — behavior unchanged unless explicitly overridden.
- **Sheathe sound replacement.** Mute Blizzard's per-material sheathe / draw sounds and play user-chosen replacements on sheath state transitions. Resolves Arcane_Duskwhisper's CurseForge request for replaceable melee sheathe sounds. Mute and replace are independent toggles; either can be on without the other. Four simple-mode slots (melee draw, melee sheathe, ranged draw, ranged sheathe), each opt-in — fill only the transitions you want to replace, leave the rest blank to keep Blizzard's default; `Advanced...` button opens a popup with per-material overrides (5 materials × 2 states). Per-material data vendored from LibSoundIndex (MIT, credited in `LICENSES.md`).
- **Sheath module flavor guard.** If `GetSheathState` or `ToggleSheath` is missing on a flavor, the sheathe module fails closed (no event registration, no Lua errors) and logs once; ranged override remains functional. Ships on retail, TBC, and Vanilla.

### Implementation Notes
- `ToggleSheath` secure hook is installed exactly once at addon load and persists for the session — disabling the feature makes the hook callback inert rather than uninstalling (secure hooks cannot be removed).
- `C_Timer.After(0)` deferred state checks are guarded by an explicit `sheatheCheckPending` flag (Lua's `C_Timer` does not auto-coalesce) and a generation token that invalidates in-flight callbacks on disable / profile switch.
- `GetSheathState()` mapping is `1=none/sheathed, 2=melee drawn, 3=ranged drawn` (verified via `Blizzard_SharedXMLBase/ModelSceneUtil.lua`). Initial-state seeding on `PLAYER_ENTERING_WORLD` prevents the first sheathe action after login from being eaten by the no-dispatch-on-nil rule.
- Per-material routing is best-effort from item subclass; may not exactly match Blizzard's per-model classification. Falls back to simple-mode slots on unknown materials.

## v1.8.0

### New Features
- **Data-driven flavor capability table (`Resonance.Flavor`).** Replaces scattered `IS_TBC` / `IS_VANILLA` / `IS_CLASSIC` runtime checks with a single capability profile per flavor: `muteMode`, `supportsPrecastMute`, `supportsClassPresets`, `supportsNPCBrowser`, `registersCLEUForImpacts`, `registersSpellCastSent`, `unavailableProfessions`, etc. Adding a new WoW flavor (e.g. Wrath Anniversary) is now a single profile entry in `FLAVOR_PROFILES` instead of auditing dozens of branches. Legacy `IS_*` aliases retained for now; new code should query `Resonance.Flavor.X`.
- **Aggressive cast-start mute (opt-in, Classic).** General-tab toggle persistently mutes the cast-initiation FIDs (cast_start / channel_start / precast events) for every configured spell, eliminating the first-cast leak that the transient mute path can't catch — WoW plays cast-start audio synchronously inside the engine's cast-initiation C call, before any Lua hook (including `UNIT_SPELLCAST_SENT`) runs. New `Resonance_SpellEarlyFIDs` table emitted by `tools/spell_sounds.py` (filtered to `SpellVisualEvent.StartEvent in {1,3,5}`) drives this; the toggle's tooltip warns that cast-start FIDs are shared across same-school spells (enabling for Fireball also silences other fire spells' cast-start audio). Off by default — existing users see no change unless they opt in.
- **TBC Anniversary + Vanilla Anniversary support.** Resonance now ships three flavors: retail, TBC (Interface 20505, build `wow_anniversary` 2.5.5.66765), and Vanilla (Interface 11508, build `wow_classic_era` 1.15.8.66129). Each flavor has its own TOC + data set: `SpellMuteData`, `SpellSounds`, `MountSoundData`, `AmbientSoundData`. Mute-on-cast, character vox, ambient, mount, profession, weapon-impact, and FileDataID muting all work on Classic. Mount Sounds tab is available on both Classic flavors via cross-flavor extraction (vanilla uses its own AURA_MOUNTED DisplayID chain plus retail fallback for max coverage — 92 mounts on Vanilla, more on TBC).
- **Transient cast-window mute on Classic.** Classic spell visuals reuse generic sound FIDs across thousands of spells (one "magic cast" FID is shared by ~2,800 spells), so persistent global muting silences too much. Classic now mutes the spell's default FIDs only during the cast window via `UNIT_SPELLCAST_SENT`, then unmutes after 5s. Collateral mute is scoped to the cast window instead of forever. The over-shared filter is dropped on Classic — TBC `SpellMuteData` grew from 5,767 → 17,422 entries, Vanilla generates 18,350. Stale transient mutes from `/reload` mid-cast are drained at OnEnable via `db._lastTransientFIDs`. Refreshed at `UNIT_SPELLCAST_CHANNEL_START` so long channels (Drain Soul, Tranquility, Mind Flay) keep their default sounds muted across the full channel duration.
- **First-cast warm-up on Classic.** WoW's audio system silently defers `MuteSoundFile` for FIDs it hasn't yet seen, so the very first cast of a configured spell in a session would leak through. `warmUpTransientFIDs()` cycles `MuteSoundFile` / `UnmuteSoundFile` for each configured spell's FIDs at `OnEnable` to register them with the engine, so the next mute (at cast time) applies immediately.

### Fixes
- **Refcount inflation across `/reload`** (transient + precast mutes). Both layers now gate `MuteSoundFile` on a saved-variable snapshot (`db._lastTransientFIDs` / `db._lastPrecastFIDs`) instead of the in-memory ticker. WoW's `MuteSoundFile` calls survive `/reload` but the in-memory tables don't, so without the snapshot gate every `/reload` mid-cast (or every opt-in cycle) added +1 to the engine refcount with no matching unmute — eventually FIDs would get permanently stuck muted. Snapshots are drained at OnEnable and on toggle-off.
- **`/res off` no longer destroys saved muting config.** The disable path used to wipe `mutedNPCs`, `mutedMounts`, `muteCreatureVox`, `muteAmbientSounds`, `muteProfessionSounds` and reset `muteWeaponImpacts`/`muteVocalizations`/`classicAutoShot` — so a user toggling off to A/B compare audio lost their entire curated muting config. Disable now only flips the master `db.enabled` flag and unmutes live FIDs; saved preferences persist. The General-tab master toggle, `/res off`, and the LDB minimap right-click all share a single canonical `Resonance.disableEverything()` / `enableEverything()` pair (previously each had its own drifted copy with different bugs — the LDB version was missing `applyMountMutes`, `clearPrecastMutes`, `clearMountMutes`, and `clearAmbientMutes`).
- **`/res checkfid`** output is now fully localized (was a mix of localized and hardcoded English) and references to a non-existent `/res testfid` are removed.
- **Per-cast string-parse hot path** for `getSpellMuteFIDs` is now cached per spell ID. `Resonance.SpellMuteData[sid]` is parsed once via `gmatch` and the result is reused on subsequent casts; previously a Mage spamming Fireball would re-parse the same comma-separated FID string several times per second.
- **`playOneSoundWithUnmute`** respects `transientMutedFIDs` and `precastMutedFIDs` in both the muted-detection check and the post-playback re-mute restoration. Without this, playing a sound that overlapped a muted FID would over-unmute and leave the FID audible.
- **Classic stale snapshot cleanup**: with a stale `_lastAutoMutedFIDs` snapshot from a prior retail-style session, OnEnable no longer re-applies persistent mutes via `reapplyAutoMutesFromSnapshot` — it falls through to `loadDataAddon` so `rebuildAutoMutes` can drain the stale snapshot cleanly.

### UX
- **"Aggressive cast-start mute (opt-in)"** toggle (was "Mute precast sounds (Classic)") leads the description with the symptom rather than the developer-facing rationale, and color-codes the trade-off paragraph. Toggling now prints a confirmation message unconditionally.
- **Tab reorder**: General → Spell Sounds → Sound Browser → Custom Sounds → Muting → Mount Sounds → Ambient → Alerts → Presets → Profiles. Sound Browser is no longer 8th; Custom Sounds groups with file-management.
- **Class Presets and Clear Presets buttons hidden on Classic flavors** — class templates target retail spells/specs and Clear Presets is a no-op without templates. Add Spell anchors directly to Clear All on Classic, keeping the row uncluttered.
- **Spell input placeholder** ("e.g. 6343 or Mortal Strike") is now localized, plus the `handleCheckfidCommand` debug strings.
- **`tools/spell_sounds.py --build {tbc,classic}`:** build aliases for `wow_anniversary` and `wow_classic_era`. Emits flavor-suffixed `SpellMuteData_TBC.lua` / `SpellMuteData_Vanilla.lua`. New `--max-fid-sharing` CLI flag (default 100; pass a large value to disable the over-shared filter for transient-mute Classic builds). Vox-data and weapon-data extraction are wrapped in try/except so missing or differently-shaped DB2 tables on Classic flavors don't fail the whole pipeline. CSV downloader passes `&product=` alongside `?build=` for unambiguous routing on wago.tools. New `tools/generate_vanilla_data.py` builds vanilla-specific Mount/Ambient/SpellSounds files.
- **Runtime flavor detection (`Resonance.IS_RETAIL` / `IS_TBC` / `IS_VANILLA` / `IS_WRATH` / `IS_CATA` / `IS_MISTS` / `IS_CLASSIC`)** in `Core.lua`. Used to gate Mount-related UI, profession exclusions (Jewelcrafting hidden on Vanilla, Inscription hidden on TBC + Vanilla), `COMBAT_LOG_EVENT_UNFILTERED` registration for impact sounds on Classic, and the transient mute path.

## v1.7.0

### New Features
- **Impact sounds — layered delayed playback for travel-time spells:** New **Impact** tab in the spell editor. Configure a list of `{delay, sound}` entries that fire after cast-complete, staggered by delay. Intended for travel-time spells like Wrath, Frostbolt, Starsurge where the modern sound plays at cast rather than at target arrival. Per-entry delay is editable inline in each row. Since 12.0.x removed addon access to `SPELL_DAMAGE`/`CombatLogGetCurrentEventInfo`, real impact detection isn't possible — impact delays are user-tuned fixed offsets.
- **Precast auto-stops at cast end:** The precast sound now stops exactly when the cast bar ends, regardless of the sound's natural length, via `UNIT_SPELLCAST_STOP`. Covers successful completion, player cancel, enemy interrupt, and spell-fail (e.g. moving during cast). No more manual `cfg.precastDuration` tuning — and no more sound bleeding past the cast. `cfg.precastDuration` still works as a shorter ceiling if set.
- **"Add Class Presets" / "Refresh Class Presets" button:** One-click preset for the player's current class, in the Spell Sounds tab next to Clear Presets. Label and tooltip flip based on whether the class's preset is already applied:
   - Add: loads the built-in preset with recommended per-spell sound replacements
   - Refresh: re-applies to pick up new spells and updated auto-mute data from the latest template, without wiping user-customised sounds
- **Complete slash-command palette (`/res` reorganisation):** Every per-spell field the GUI edits is now reachable via chat commands with a consistent `subject → verb → args` structure. Each phase command accepts an optional `horde`/`alliance` trailing argument to write to a faction-specific override bundle.
   ```
   /res cast    add|list|remove|clear|mode|loop|duration <spellID> [...] [horde|alliance]
   /res precast add|list|remove|clear|mode|loop|duration <spellID> [...] [horde|alliance]
   /res impact  add|list|remove|clear                     <spellID> [...] [horde|alliance]
   /res trigger  <spellID> cast|precast|both
   /res channel  <spellID> Master|SFX|Dialog|Music|Ambience|off
   /res multihit <spellID> <seconds|off>
   /res unmute   add|list|remove|clear <spellID> [<fileDataID>]
   /res spell    show|remove           <spellID>
   ```
- **Faction GUI expansion — horde/alliance cast + precast:** The Advanced Options → Faction-specific sounds section now has four inputs (Horde cast, Horde precast, Alliance cast, Alliance precast) instead of two. Editor writes to the nested `cfg.horde` / `cfg.alliance` bundle (new data model); legacy `cfg.hordeSound` / `cfg.allianceSound` are lifted into the bundle on first load. Faction impact lists are still slash-only (`/res impact add <spellID> <delay> <sound> horde|alliance`); they survive GUI save round-trips intact.
- **Search-by-name input on the Impact tab:** Matching Cast/Precast tabs, the Impact tab has the same Browse (name autocomplete) and File Path / FID input modes, plus a persistent Delay field shared between both modes.
- **Patch 12.0.5 support:** TOC declares compatibility with both live (12.0.1) and 12.0.5 (`## Interface: 120001, 120005`). Spell mute data, mount/NPC/creature-vox/profession data regenerated from build `wowt 12.0.5.67088` — covers Midnight 12.0.5 spells (Battle Visage, Explosive Shot, Sanguine Vial, Tigereye Brew redesign, Voidforge / Void Assault content, etc.).
- **`/res spell show <spellID>`:** New diagnostic dump — prints the full `spell_config` entry including faction overrides, impact list, exclusions, and trigger.
- **Editor tab order now follows cast lifecycle:** Precast → Cast Complete → Impact → Muted (was Cast → Precast → Impact → Muted).

### Bug Fixes
- **Precast sound dropdown button was mislabelled (reported on CurseForge):** The "+ Mute" button with pass-skip icon in the precast Browse autocomplete was actually calling the add action — it always added the sound when clicked, the label just hid that. Fixed to `+ Add` with a plus icon.
- **Battle Shout / Rallying Cry / Intimidating Shout / Demoralizing Shout weren't auto-muting their modern roar.** The Revamp BattleRoar FIDs are shared across ~240 buff/rage spells (Bloodlust, Frenzied Regeneration, Ancient Hysteria, NPC variants, etc.) so `--max-fid-sharing=100` stripped them from SpellMuteData. Added explicit `muteFIDs` in the Warrior template so applying the preset mutes those FIDs regardless of the sharing filter. Matches the existing fix pattern for Shield Slam / Pummel / Rend / Colossus Smash from earlier versions.
- **Starfire / Starsurge Lunar precast loop wasn't muted.** Same root cause — `spell_dr_revamp_lunar_precast_loop_01` (FID 1597782) is used by 114 Balance spells, filtered from SpellMuteData. Added `muteFIDs = {1597782}` to both Starfire (194153) and Starsurge (197626 + 78674) templates.
- **Spam-cast Wrath: second impact sound didn't fire.** The multi-hit-window debounce (default 1.5s) was returning early in `UNIT_SPELLCAST_SUCCEEDED` *before* impact scheduling, so a rapid recast under heavy haste dropped its impact. Impact scheduling is now extracted into `scheduleImpactsForCast` and runs before the debounce — debounce only affects the main cast sound, as intended.
- **Precast sound list: first-added sound didn't appear in the list.** WoW 12.x added a stricter `SetPoint` check that exposed a latent bug where the empty-state row (with only a text FontString) was reused as the first data row (which needs playBtn/removeBtn too). Fixed by detecting the empty-state row and upgrading it with the missing widgets.
- **GUI save was dropping `cfg.muteFIDs`.** Pre-existing bug: opening and saving a spell in the editor wiped `cfg.muteFIDs` (populated by class templates — e.g. Warrior Battle Shout's forced-mute FIDs). Would be re-filled on next login by `refreshPresetsFromTemplates`, but mutes wouldn't apply in the interim. Now preserved verbatim across GUI save.
- **Template refresh now propagates new shape fields to existing preset entries.** Previously `refreshPresetsFromTemplates` only updated `muteExclusions` / `muteFIDs` / `multiHitWindow` on existing preset-sourced spells. New template fields like `precastSound`, `trigger`, `impacts` never reached users who already had the spell configured — the Precast/Impact tabs would show empty even after "Refresh Class Presets". Now fills in any missing (nil) shape field from the template while preserving user-customised values (nil-check, not truthy-check, so explicit `cfg.sound = false` survives).
- **Addon could fail to register silently ("/res does nothing") after growing enough slash-command helpers.** Lua's 60-upvalue-per-function cap was being approached by `Resonance:ChatCommand`. Extracted the command handlers (`handleImpactCommand`, `handleTriggerCommand`, `handleChannelCommand`, `handleOnCommand`, `handleOffCommand`, `handleDiagCommand`, etc.) into file-scope functions. Also added `tools/check_upvalues.sh` + `make check-upvalues` target so CI catches approach-to-cap at lint time — WARN at 45, FAIL at the 60 cap.
- **Options.lua stays under Lua's 200-local-per-function cap.** The faction + impact GUI expansion pushed the enclosing `buildTab2_SpellSounds` over 200; bundled editor state into `editorFaction` + `factionRows` tables so adding more fields doesn't multiply local count.
- **Snapshot invalidation on template updates:** when the template introduces new `muteFIDs` and a user is mid-session without having opened Options (i.e., `Resonance_Data` not loaded), the auto-mute snapshot would be reapplied on login without the new FIDs. `refreshPresetsFromTemplates` now invalidates the snapshot when changes are detected, forcing `loadDataAddon → rebuildAutoMutes` so template-added mutes take effect that session.
- **`cfg.hordeSound` edge case in migration:** `migrateFaction` previously could drop `cfg.hordeSound` if a cfg happened to have both the legacy flat field and a faction bundle without `.sound` set. Now merges the legacy value into the bundle when bundle lacks `.sound`.

### Data updates
- Regenerated `SpellMuteData.lua`, `MountSoundData.lua`, `NPCSoundData.lua`, `CreatureVoxData.lua`, `ProfessionSoundData.lua` from 12.0.5 build — 137K+ spells touched, covers 12.0.5 content (new spells, Void Assault zones, Ritual Sites).
- Removed dead orphan `data/MountSoundData.lua` (was duplicated between repo-root and `Resonance_Data/data/`; only the latter is loaded by the TOC).
- **Druid Wrath / Starsurge / Starfire / Starfall template updates:**
   - Wrath (5176): three-phase classic restoration — precast `lifedrainloop`, cast `naturecast`, impact (+1.0s) `lightningboltimpact`.
   - Starsurge (197626 + 78674 variant): precast `lifedrainloop`, cast `druid_starfallmissile1` (unchanged), impact (+0.5s) `starfireimpact`, `muteFIDs = {1597782}`.
   - Starfire (194153): precast `lifedrainloop`, cast `starfireimpact` (unchanged), `muteFIDs = {1597782}`.
   - Starfall (191034): sound changed from `StarFireImpact.ogg` (wrong — that's Starfire's) to a random pool of the dedicated `druid_starfallmissile1/2/3` FIDs.
- Army of the Dead (42650) removed from the Death Knight template — modern spell is a single quick cast, and the `shadowprecasthigh` replacement was noticeably too long.

### Localisation
- 141 new user-facing strings wrapped in `L[...]` and declared in the enUS/enGB block for strict validator compliance.
- 18 GUI-visible strings translated into all 9 non-English locales (ptBR, deDE, frFR, esES/esMX, itIT, ruRU, koKR, zhCN, zhTW). Spell-name references cross-checked against `wago.tools` `SpellName` DB2 for build 12.0.5.67088 (Wrath → Zorn / Colère / Ira / Ira Silvana / Cólera / Гнев / 천벌 / 愤怒 / 憤怒).
- Remaining ~123 slash-command usage/diagnostic strings get enUS entries and fall back to English via the metatable in other locales — consistent with existing behaviour for command-line output.

### Breaking changes
- `/res duration <spellID> <seconds|off>` is removed. Use `/res cast duration <spellID> <seconds|off>` (and `/res precast duration ...` for the precast phase).
- Legacy `cfg.hordeSound` / `cfg.allianceSound` / `cfg.impactSound` / `cfg.impactDelay` / `cfg.impactDuration` fields are no longer written on save — they migrate into `cfg.horde` / `cfg.alliance` bundles and `cfg.impacts[]` list respectively. Old saved-vars format still reads correctly via one-shot migration (`migrateFaction`, `migrateImpacts`), and the playback path has a legacy-field fallback for defence in depth.

## v1.6.1

### New Features
- **Addon-manager-safe vanilla sound folder:** Extracted vanilla/TBC/Wrath sound files can now live in `Interface/AddOns/Resonance_Sounds/vanilla/` instead of `Interface/AddOns/Resonance/sounds/vanilla/`. Files in the sibling `Resonance_Sounds/` addon survive updates from WoWUp, CurseForge, and other addon managers, which wipe and replace the `Resonance/` folder on every update. The legacy in-addon path still works as a fallback.
- **LibSharedMedia-3.0 integration:** Resonance now ships LibSharedMedia-3.0. Custom sounds you register in the Custom Sounds tab are automatically exported to LSM under MediaType "sound", so other LSM-aware addons (WeakAuras, BigWigs, etc.) can see them. In the other direction, sound search boxes throughout Resonance now surface sounds registered with LSM by any other addon — pick one and it works exactly like a regular sound reference.

## v1.6.0

### New Features
- **Tabbed spell editor:** The spell editor now uses a clean tabbed layout with three tabs — Cast Complete, Precast, and Muted. Each sound tab has identical controls (sound list, browse/search, duration, loop). The Muted tab shows auto-muted FileDataIDs with checkboxes.
- **Precast multi-sound support:** The Precast tab now supports multiple sounds, sound lists, and loop — matching the Cast Complete tab's capabilities. Old single-sound precast configs load and play correctly.
- **Unified spell input:** The spell ID field now accepts both numeric IDs and spell names with autocomplete search. No more separate search box for new spells vs editing.
- **Collapsible Advanced Options:** Sound mode (standard/sequence), channel override, multi-hit window, and faction-specific sounds are grouped under a collapsible "Advanced Options" toggle. Auto-expands when editing a spell that has these configured.
- **Faction-specific sounds:** Configure different replacement sounds for Horde and Alliance characters on the same spell.

### Bug Fixes
- Fix spell editor UI elements overlapping (duration box on top of sound mode, precast section colliding with other controls). The tabbed layout eliminates all anchor chain conflicts.
- Fix export/import/preset functions silently dropping `hordeSound`, `allianceSound`, `precastLoop`, and `multiHitWindow` fields. All spell config fields are now preserved across export, import, preset save, and preset apply operations.
- Fix mount sound CSD-derived folder discovery for mounts whose sounds live in numeric or cross-referenced folders (e.g. Headless Horseman's Hallowed Charger).

## v1.5.2

### Bug Fixes
- Fix mounts whose sounds live in numeric or cross-referenced sound folders (e.g. Headless Horseman's Hallowed Charger). The generator now discovers sound folders by tracing CSD FileDataIDs back to their listfile folder, catching M2-embedded animation sounds that don't match the model folder name.

## v1.5.1

### New Features
- **Faction-specific sounds:** Configure different replacement sounds for Horde and Alliance characters on the same spell. In the spell editor, check "Faction-specific sounds" to reveal Horde and Alliance sound fields. Useful for spells like Time Warp that you want to sound different per faction.

## v1.5.0

### New Features
- **Mount sound muting:** New "Mount Sounds" tab to mute individual mount sounds. Browse your collected mounts in a paginated, searchable list and check the box to mute any mount's locomotion and ambient sounds (walk, run, jump, fly, wing flap, idle fidgets, summoning).
- **Skyriding sound muting:** Dedicated toggle to mute all skyriding wind effects — dive wind, speed rush, ability sounds (Surge Forward, Skyward Ascent, Whirling Surge), and ground skimming.
- **Model-embedded sound discovery:** Mount sounds are now discovered from three sources: DB2 CreatureSoundData tables, M2 model animation sounds (via community listfile), and spell visual chains. This catches sounds from older mounts (e.g. Mimiron's Head) whose audio is baked into the 3D model rather than DB2 tables.
- **Mount data generator:** New `--generate-mount-data` flag for `tools/spell_sounds.py` walks Mount → MountXDisplay → CreatureDisplayInfo → CreatureSoundData → SoundKitEntry and scans model sound folders to produce `MountSoundData.lua` (1,580 mounts, 537 CSD profiles, 17K+ sound files).
- Mount search mode added to the Sound Browser tab for searching across all mounts (not just collected).

## v1.4.0

### New Features
- **Sequence sound mode:** Configure a list of sounds that play one per hit for multi-hit spells like Rampage. Each successive hit advances through the sound list, creating a combo-style strike sequence. Works with any number of sounds and wraps around when the list is exhausted. Select "Sequence (1 per hit)" in the spell editor's sound mode radio to enable.
- For single-hit spells, sequence mode cycles through sounds across casts (each cast plays the next sound in the list).

### Bug Fixes
- Fix `refreshPresetsFromTemplates` overwriting user-customized sounds on every login/reload. Template refresh now only updates mute data (muteExclusions, muteFIDs) and preserves user sound and multiHitWindow settings.

## v1.3.5

### New Features
- **Multi-hit sound window:** Configurable time window that prevents multi-hit spells (e.g. Rampage, Raging Blow) from playing their replacement sound on every hit. Defaults to 1.5 seconds globally, adjustable per spell in the spell editor or globally in the General tab. Resolves the behavior change from v1.2.0 where name-based config fallback caused all sub-hits to trigger sounds.

### Bug Fixes
- Fix Whirlwind not muting its modern cast sounds. The 5 caster-side FIDs (whoosh sounds) were in ExcludedFIDs as over-shared; added explicit `muteFIDs` to both Whirlwind templates (1680, 190411).
- Set Bladestorm template to a 5-second multi-hit window (non-channeled spell that fires repeated hits over ~4s).

## v1.3.4

### Bug Fixes
- Fix channeled spells (e.g. Tranquility) playing replacement sounds on every tick. Channeled spells now play on `UNIT_SPELLCAST_CHANNEL_START` and suppress all subsequent `UNIT_SPELLCAST_SUCCEEDED` tick events — including sub-spells with different IDs that resolve to the same config via name matching.
- Fix Mark of the Wild echo caused by unmuted original retail sounds playing alongside the replacement. Added `muteFIDs` for the two excluded FIDs (568735, 568917).
- Fix auto-mutes silently inactive on first install or after the Resonance_Data folder restructure — eagerly load Resonance_Data during `OnEnable` when no auto-mute snapshot exists, instead of waiting for Options to be opened.
- Fix multiple template spells across 6 classes whose original sounds were never muted because their FIDs were in ExcludedFIDs (over-shared) with no explicit `muteFIDs`. Affected spells: Shield Slam, Pummel, Rend, Shield Block, Colossus Smash (Warrior), Rapid Fire (Hunter), Greater Invisibility, Shimmer (Mage), Chain Lightning, Ascendance (Shaman), Breath of Fire (Monk), Ravager (Warrior).
- Fix empty auto-mute snapshot (`{}`) incorrectly treated as valid — now checks `next() ~= nil` instead of truthiness.
- Update Bladestorm template to WotLK-era sounds (heroicthrowmissile + FX_Lightning_Impact_Heavy03).

## v1.3.2

### New Features
- **Alerts tab:** Configure alert sounds for combat events with a dropdown + table UI. Select an event type, pick a sound (with autocomplete search), and manage alerts with enable/disable toggles.
  - **Interrupt alert:** Play a sound when your cast is interrupted by an enemy kick or counterspell. Uses `LOSS_OF_CONTROL_ADDED` + `UNIT_SPELLCAST_INTERRUPTED` dual-direction correlation — fully compatible with Midnight 12.0.1 instanced content.
  - **Loss of Control alert:** Play a sound when you are stunned, feared, silenced, or otherwise lose control of your character.
  - **Death alert:** Play a sound when you die.
- **Custom Sounds tab:** Register your own .ogg/.mp3 sound files and use them throughout the addon. Place files in `Interface/AddOns/Resonance_Sounds/`, register them with a display name, and they appear in all sound search autocomplete dropdowns (highlighted in green).
- **Per-spell sound channel:** Override the sound channel for individual spells in the spell editor. Route specific replacement sounds to Master, SFX, Music, Ambience, or Dialog — useful for adjusting relative volume of replacements vs. game sounds.
- **Cast phase triggers:** Choose when spell replacement sounds play — on cast complete, cast bar start (precast), or both with separate sounds for each phase.
- **Sound duration cutoff:** Limit how long a replacement sound plays (in seconds).
- **Sound looping:** Loop a replacement sound continuously or a set number of times.
- **Ambient sound muting:** Mute ambient sounds by zone with per-zone toggles in a new Ambient tab. Includes a search feature to find and mute individual ambient sounds, with expansion/zone info in results.
- **Per-NPC sound muting:** Mute sounds from specific NPCs.
- **Sound autocomplete:** Autocomplete dropdown when selecting replacement sounds, with a redesigned two-line layout showing expansion/zone and FID info.
- **Rogue Shadowstrike** added to class sound templates.

### UI/UX Improvements
- Options panel reorganized into Settings subcategories with dedicated panels and tab headings.
- Redesigned dropdown rows with per-row variable height and inline mute icon button with visual feedback.
- Improved WCAG contrast and color consistency across the UI.
- Sound channel dropdown now uses Blizzard's localized global strings — labels match the game's Sound settings in every language.
- Preset spells deleted by the user no longer auto-repopulate on login. Excluded spells are tracked and skipped during template refresh. Re-applying a class template clears the exclusion list.

### Performance
- **LoadOnDemand data split:** Addon split into Resonance (core) + Resonance_Data, deferring large data tables until options are first opened.
- Memory optimizations: table reuse, deferred snapshots, prefix pools, eliminated string concatenation during search.

### Bug Fixes
- Fix crash when copying AceDB profiles — nil-guard tables that may not exist after profile copy (`mute_file_data_ids`, `spell_config`, etc.).
- Fix custom panels (Spell Sounds, Presets, Ambient) showing empty on first visit — add deferred refresh after initial build to allow WoW's layout pass to complete.
- Fix Resonance_Data not found after addon update — moved to its own folder with BigWigs packager `move-folders` directive.
- Fix CI lint failures — extract only `read_globals` from the WoW API globals file instead of loading the full config.
- Fix UNIT_MODEL_CHANGED spam: cache race/gender key so vocalization mute refreshes only trigger on actual changes (barbershop, Orb of Deception), not on every model update.
- Auto-migrate stale creature vox snapshots on addon upgrade.
- Exclude profession sound FIDs from creature vocalization muting.
- Clear stale mute snapshots on startup.
- Use MAX_MUTE_DEPTH unmutes in all clear functions to properly drain refcount.
- Fixed various UI layout issues (editor overlap, duration anchoring, ambient tab positioning, preview button placement).
- Fixed mojibake caused by UTF-8 arrow character in disabled message.

### Tooling
- Added luacheck and StyLua linting/formatting with CI workflow.
- Cross-platform dev setup docs in README (macOS, Linux apt/pacman, Windows).
- `/res checkfid <id>` diagnostic command to inspect which muting systems affect a given FileDataID.
- `tools/generate_ambient_data.lua` for generating ambient sound data.

### Localization
- All new keys translated across 9 languages (ptBR, deDE, frFR, esES, itIT, ruRU, koKR, zhCN, zhTW).

## v1.2.0

### New Features
- **Mute-only mode:** Mute all sounds from a spell without selecting a replacement. Enable "Mute only" in the spell editor to silence original sounds with nothing playing in their place.
- **Profession sound muting:** Per-profession checkboxes (Alchemy, Blacksmithing, Cooking, etc.) to mute crafting, gathering, and other profession-related sounds. 13 professions, 168 unique sound files.
- **Shared-sounds disclaimer:** General tab now shows a note explaining that some sounds are shared across multiple spells/effects, so muting for one feature may silence them elsewhere.

### Tooling
- `tools/spell_sounds.py --generate-profession-data` generates `data/ProfessionSoundData.lua` from DB2 SkillLine/SkillLineAbility data.

## v1.1.0

### New Features
- **Classic auto-shot sounds (Hunter):** Replace modern bow and gun auto-shot sounds with classic ones. Automatically detects equipped weapon type (bow/crossbow or gun). Toggle in General options (Hunter only).
- **Disable state:** When "Enable Resonance" is unchecked, all General tab controls are now visually disabled and muting options revert to defaults, making it clear the addon is inactive.
- **Version display:** Minimap tooltip now shows the addon version.

### Bug Fixes
- **Revenge** sound mapping corrected — was playing DecisiveStrike.ogg instead of dedicated warrior_revenge sounds.
- **Bladestorm** sound mapping corrected — was playing WhirlwindShort.ogg instead of dedicated warrior_bladestorm sound.
- **Heart Strike** sound mapping corrected — was playing Blood Strike sounds instead of dedicated deathknight_heartstrike sounds.
- **Addon icon** in the AddOns menu now matches the minimap button icon.

### Creature Vocalization Improvements
- Added fidget (idle roars/growls), alert (pre-aggro), and jump sounds to creature vox muting — previously only combat sounds were covered.
- Narrowed spell FID exclusion to player spells only — NPC abilities no longer incorrectly protect their creature's own vox sounds from being muted.
- Total creature vox coverage increased from ~40K to ~61K sound files (+54%).

## v1.0.0

- Initial release.
