Wikki's Cooldown Pulse — Full Changelog
========================================
Latest version : 2.1.3 (r30)
Last updated   : 2026-06-21

Entries are listed newest first.

========================================================================
2.1.3 | community fix | 2026-06-21 | r30 | sound queue — double pulse
========================================================================

ROOT CAUSE
  PlaySound(soundId) in the WAR/RoR client uses a single audio slot for
  addon sounds.  When two abilities expire within the same rendered frame
  (or on back-to-back frames), FirePulse() calls PlaySound() twice in
  rapid succession and the client silently discards the second call.
  Both pulses were visible, but only one sound was audible.
  Settings (Start/End Alpha, Speed, mincooldown) do not influence this —
  a valid configuration was confirmed as the trigger for the report.

FIX
  A small FIFO queue (_soundQueue) and a hook-call counter (_soundCooldown)
  delay the second PlaySound() call by ~30 hook invocations (≈ 1 rendered
  frame on a typical 10-30 button setup, i.e. 33–100 ms at 30 fps):

    • PlayPulseSound() checks _soundCooldown.  If > 0, the key is pushed
      onto _soundQueue instead of calling PlaySound() immediately.
    • _soundCooldown is set to 30 by PlayPulseSoundDirect() after each
      actual PlaySound() call.
    • OnAbilityUpdate() (called once per visible button per frame) ticks
      the counter down and, when it reaches zero, pops and plays the next
      queued key via PlayPulseSoundDirect().
    • Reinitialize() resets _soundQueue and _soundCooldown on profile
      changes and /reload.

  New public helpers (for the test suite):
    WCDP.PlayPulseSoundDirect(key)  — plays immediately, resets counter
    WCDP.TickSoundQueue()           — one drain tick (also called by hook)
    WCDP.GetSoundQueueState()       — returns _soundCooldown, _soundQueue

TESTS
  3 new unit tests in tests/unit/test_soundSelection.lua:
    • soundQueue: second PlayPulseSound is queued, not played immediately
    • soundQueue: queued sound plays after cooldown drains via TickSoundQueue
    • soundQueue: Reinitialize clears queue and cooldown
  Total: 47 tests, 0 failures (Lua 5.4.6).
  Note: reset() in test_soundSelection.lua now calls WCDP.Reinitialize()
  to flush the sound queue between tests.

========================================================================
2.1.2 | community feature | 2026-06-21 | r29 | sound selection
========================================================================

FEATURE — Sound Selection
  Five confirmed GameData.Sound.* constants are selectable via a
  ComboBox in Configuration > General:

    Help Tips (default)        — GameData.Sound.HELP_TIPS_NEW              [Enemy]
    Quest Objectives Done      — GameData.Sound.QUEST_OBJECTIVES_COMPLETED [Enemy]
    Quest Completed            — GameData.Sound.QUEST_COMPLETED            [CombatLogger]
    Quest Accepted             — GameData.Sound.QUEST_ACCEPTED             [CombatLogger]
    Tome Close                 — GameData.Sound.TOME_CLOSE                 [zMailMod]
    None (disabled)            — sentinel key: silences sound without
                                 un-ticking "Enable Sound With Pulse".

  Selection saved in db.general.soundKey; older profiles receive
  "HELP_TIPS_NEW" automatically via the updateSettings migration path.

NOT IMPLEMENTED — Volume Slider
  PlaySound(soundId) takes a single argument.  No volume parameter,
  Sound.SetVolume(), or SetMasterVolume() was found in any Game\ROR
  addon.  A Lua-level volume control is not possible with the current
  RoR API.

TESTS — infrastructure added (tests/ directory, Lua 5.4, no framework)
  tests/stubs/api_stubs.lua            stubs for all WAR/RoR client globals
  tests/unit/test_updateSettings.lua   profile migration (updateSettings)
  tests/unit/test_peakTracking.lua     PerformAbilityUpdate peak-tracking
  tests/unit/test_moraleAllowed.lua    MoraleAllowed filter logic
  tests/unit/test_moraleAvailable.lua  OnPlayerMoraleUpdated edge-detect
  tests/unit/test_soundSelection.lua   PlayPulseSound all soundKey variants
  tests/run_tests.lua                  runner (44 tests, 0 failures)
  Run: lua run_tests.lua  (from the tests/ directory)

========================================================================
2.1.1 | community fix+feature | 2026-06-21 | nil-retry, sound, minimap menu
========================================================================

FIX (priority)
  PerformAbilityUpdate permanently blacklisted any ability whose
  Player.GetAbilityData() returned nil on a given frame, silencing that
  ability's pulse for the entire zone session.  The nil can be transient
  (data not yet loaded by the client).  Fix: on nil, return without
  writing to blackList or abilityTypeCache so the next frame retries.

CHANGE
  GameData.Sound.WINDOW_CLOSE was not found in any installed RoR addon
  and is therefore unconfirmed.  Replaced with GameData.Sound.HELP_TIPS_NEW,
  confirmed in Enemy/DefaultSettings.lua.  The pcall wrapper is preserved.

VERIFY (no code change)
  Player.AbilityType.* vs GameData.AbilityType.*: both namespaces coexist.
  Enemy/ClickCasting compares .abilityType against GameData.AbilityType.TACTIC;
  MoraleCircle passes Player.AbilityType.MORALE as a second parameter to
  Player.GetAbilityData().  Since the code worked in 2.0.4 and no
  contradiction was found, no change was made.

FEATURE
  The minimap button right-click now opens a context menu
  (EA_Window_ContextMenu, same pattern as Enemy) with two entries:
    • "Configuration"    — opens config window (same as left-click)
    • "Hide Minimap Icon" — hides the button and saves the state.
  A "Show Minimap Button" checkbox in the General config page lets
  players re-enable it.  Stored in db.general.showMinimapButton (default
  true); existing profiles get the key added automatically by updateSettings.

========================================================================
2.1.0 | community feature | 2026-06-21 | minimap button
========================================================================

NEW
  A minimap button (gold pulse icon) appears next to the overhead map.
  Left or right click opens the configuration window; mouseover shows a
  short tooltip.  The button can be repositioned via the Layout Editor and
  its position is saved.  Built the same way Enemy builds its minimap icon:
  DynamicImage anchored to EA_Window_OverheadMapMapDisplay, 48×48 TGA.

========================================================================
2.0.8 | community fix | 2026-06-21 | pulse sound + visible debug
========================================================================

FIX
  The pulse sound never played.  The addon used Sound.Play(), which the
  RoR client does not expose, so all calls were silent.  Sound now plays
  through PlaySound() (the function RoR actually provides), wrapped in
  pcall with a fallback so a missing API can never break the pulse.
  Controlled by the existing "Enable Sound With Pulse" option.

CHANGE
  Debug messages now print to the chat window (same channel as the init
  line) instead of the internal DEBUG() console, so "Enable Debug
  Messages" / "/wcdp debug" output is actually visible to the player.

========================================================================
2.0.7 | community feature | 2026-06-21 | morale-available pulse + debug
========================================================================

NEW
  Morale can now pulse the instant a rank becomes available as the morale
  bar fills (or its post-use cooldown ends), not only for the last morale
  cast.  Driven by the PLAYER_MORALE_UPDATED event and the morale-bar API
  (GetMoraleBarData / GetMoraleCooldown / GetMoralePercentForLevel),
  evaluated the same way the default morale UI does.  Each rank flashes
  once on the not-available → available edge.
  Toggle: "Pulse When Morale Available" in the Morale section (on by
  default).  When off, morale falls back to the 2.0.6 cooldown path.

NEW
  "Enable Debug Messages" checkbox in General + "/wcdp debug" slash command.

FIX
  The Morale and Pet "Minimum Cooldown" fields were saving into the
  Actions minimum cooldown.  They now save to their own settings correctly.

========================================================================
2.0.6 | community fix | 2026-06-21 | morale pulse regression
========================================================================

FIX
  Morale abilities never pulsed after the 2.0.5 rewrite.  Morale buttons
  do not count down; the client streams "cooldown=0 / max=60" updates for
  them, so the 2.0.5 peak tracker never recorded a peak and suppressed
  the pulse entirely.  Morale is now handled on its own path (triggered
  off m_MaxCooldown, the proven 2.0.4 behavior, with a guard against
  repeated 0/60 updates), while abilities and pets keep peak tracking.
  The pulse display was factored into a shared WCDP.FirePulse() helper.

========================================================================
2.0.5 | community fix | 2026-06-11 | peak tracking
========================================================================

FIX
  Pulses randomly failed to fire when another ability was cast (global
  cooldown overwriting the button's max cooldown) or when the bar
  refreshed while moving.  Cooldown peaks are now tracked per ability
  internally so the pulse always triggers on the actual expiry.
  Ability types are cached to avoid calling GetAbilityData() every frame.

========================================================================
2.0.4 | wikki | 2011-01-06 | original release
========================================================================

SVN r23 — Fixed an issue where the same ability wouldn't pulse back-to-back.
SVN r24 — Tagged as 2.0.4.
