File Details
EpicDamageMeter v.2.4.28
- R
- May 26, 2026
- 447.06 KB
- 933
- 12.0.7+14
- Classic TBC + 4
File Name
EpicDamageMeter.zip
Supported Versions
- 12.0.7
- 12.0.5
- 12.0.1
- 5.5.4
- 5.5.3
- 4.4.2
- 4.4.1
- 3.80.1
- 3.80.0
- 3.4.5
- 3.4.4
- 2.5.5
- 2.5.4
- 1.15.8
- 1.15.7
EpicDamageMeter - Changelog
============================
Version 2.4.28
--------------
PvP Crash Fix: SecretValue Taint in Tooltip Cache Key
THE BUG
- 26x error spam in PvP: "attempt to compare local 'expectedKey'
(a secret string value, while execution tainted by
'EpicDamageMeter')" — fired every time a bar tooltip was shown
on a Paladin (or any player) during an arena/BG match.
- Root cause: GetTooltipSpells and SetBarData both built a
composite cache key via string.format("%s|%s|%s|%s", tostring(
actor.guid), ...). On WoW 12.0+ in PvP, actor.guid can be a
SecretValue. tostring() on a SecretValue returns a SECRET
STRING, which then made the whole expectedKey secret. The
subsequent `bar._cachedKey ~= expectedKey` comparison tainted
Lua execution, throwing the error 26 times in a row.
THE FIX
- New _plain() helper at both cache-key build sites: returns the
string form only if the value is NOT secret, otherwise returns
a fallback. Falls back to bar.plainGUID (set for the local
player) before giving up on "?".
- Comparison hardened: if bar._cachedKey itself is somehow
secret, treat as cache miss and reset rather than running the
`~=` compare. Plain == plain compares are safe; only mixed-
taint compares throw.
- Both sites fixed in one pass: SetBarData (~line 1973) and
GetTooltipSpells (~line 2934).
WHY THIS NEVER CAUGHT IT IN PvE
- In PvE, actor.guid is always plain. The bug only triggers
when CLEU is restricted (arena, BG) so the C_DamageMeter path
feeds the bar with secret GUIDs/names. SecretValues are a
12.0+ feature; older clients never had this code path.
Version 2.4.27
--------------
Heal Tooltip: Merge Absorbs
THE BUG
- Side-by-side screenshot from the user — Details! showed nine
healing entries for a Priest, EDM only five. The missing four
were: "Schnelle Prognose", "Machtwort: Schild" (Power Word:
Shield — an absorb), "Kosmische Welle" and "Gebet der
Besserung". User flagged it as "HPS zeigt net alle Werte
korrekt an".
- Root cause: when fetching the spell breakdown for the healing
tooltip we only queried `Enum.DamageMeterType.HealingDone`,
which returns only the actual-heal spells. Absorb-style
spells (Power Word: Shield, Sacred Veil, etc.) live under
`Enum.DamageMeterType.Absorbs` and never showed up.
THE FIX
- The Healing / HPS bar tooltip cache build now queries BOTH
HealingDone AND Absorbs and merges the two spell lists into
a single table (keyed by spellID).
- After merging, sort the combined list by `totalAmount`
descending so a big shield doesn't end up below a small heal
just because the API returned heals first.
- Damage / DPS mode path untouched (single enum, same as before).
NOTE
- The "Blitzheilung 238K vs 102K" amount discrepancy in the user's
screenshots is most likely because Details! was on a longer /
different combat segment when the screenshot was taken — the
per-cell percentages already diverge (43.5% vs 40.2%), so the
underlying segment totals were different snapshots, not a bug
in our number. After this fix the COMPLETENESS of the spell
list matches Details!; absolute amounts only line up when both
meters are looking at the same segment at the same moment.
Version 2.4.26
--------------
Restore working math from reference v2.4.15
User uploaded a working reference build (v2.4.15) and pointed out
that my recent "improvements" broke the math everywhere. Compared
both trees and reverted the broken logic while keeping the visual
work intact.
REVERTED TO REFERENCE
- Core/Utils.lua restored byte-for-byte. My AbbreviateNumbers-based
ResolveNumber (v2.4.21+) was over-engineered and returned 0 for
some SecretValues — that's why the tracker showed "0 (0 DPS)"
during combat. The reference's simple two-path is back:
1. Plain numbers → tonumber, done.
2. SecretValues → pcall'd string.format("%.0f", val) followed
by pcall'd #length + string.byte byte-rebuild.
- UI/Graph.lua Update reverted to the reference's Strategy 1
(Database) → Strategy 2 (tracker-fed values) order. No own
sliding-window math.
- MainFrame UpdateBarsFromDamageMeter Abbr reverted to the
reference's Details!-style two-path (SecretValue →
AbbreviateNumbers, plain → Utils.FormatNumber).
- MainFrame graph-publishing reverted: writes `total` (cumulative)
to _graphDPS like the reference, not a pre-divided rate.
KEPT FROM RECENT WORK
The following visual additions don't touch the math logic and
stay in place:
- Detail Window hero header + stat pills + ranked spell rows
- Settings hero logo + tagline banner + arena theme + version
footer + ESC menu button
- Tracker title-bar mini hero logo
- Tracker bar gloss + shadow + edge highlight overlays
- Status bar combat-time icon + warm-gold colour + accent stripes
- Empty-combatSources bail (matches reference + Details! pattern;
removed the synthesis hack)
Lesson learned: when the reference works, copy first, modify
second.
Version 2.4.25
--------------
Revert to Details! reference pattern
User feedback: stop inventing math, follow Details! exactly. I
sourced the relevant Details! code (parser_nocleu1.lua, boot.lua
in Tercioo/Details-Damage-Meter) and reverted the divergences:
PER-SPELL DPS (was 2.4.24 amount / session.durationSeconds)
- Details reads spell.amountPerSecond DIRECTLY from
C_DamageMeter (parser_nocleu1.lua:760-770). No own division.
- DetailWindow.ShowSpellList, MainFrame UpdateBars cache build,
and MainFrame GetTooltipSpells lazy path all reverted to
reading sp.amountPerSecond and feeding it through Abbr() like
before.
EMPTY combatSources (was 2.4.23 local-player synthesis)
- Details bails silently when session.combatSources is nil or
empty (parser_nocleu1.lua:1859-1866). No synthesised local
player row, no CLEU fallback.
- UpdateBarsFromDamageMeter reverted to the same behaviour. The
"tracker shows no data during the SecretValue phase" is a
Blizzard API artefact — every other meter on 12.0 has the
same brief gap, and synthesising fake data created downstream
consistency issues (math mismatches between bar and tooltip).
What this means:
- Numbers will match Details! exactly (since we use the same
API fields).
- If Blizzard's stock meter shows different numbers, that's
Blizzard's UI using a different formula — not a bug we should
paper over with our own derivation.
- Brief no-data window at combat start (under arena/BG
SecretValue restriction) is expected behaviour now.
Version 2.4.24
--------------
Per-Spell DPS Now Matches Blizzard's Damage Meter
THE PROBLEM
- Side-by-side screenshot from the user: Blizzard's stock meter
showed "Heilige Pein 40.779 (3.719)", EDM Detail Window showed
"40.8K (4.3K DPS)" — same spell, same moment, same player.
Amounts matched (40K), DPS rates differed by ~16%.
- Root cause: we read the per-spell DPS from C_DamageMeter's
spell.amountPerSecond, which is a different metric than what
Blizzard's own UI displays. Blizzard's stock meter divides the
spell's total damage by the session.durationSeconds (a stable
"since combat start" duration). The API's amountPerSecond
appears to be either an instantaneous or shorter-window rate,
which tends to read higher.
THE FIX
- Per-spell DPS in the Detail Window's spell list and in the
main-window bar tooltip is now computed as
spell.totalAmount / session.durationSeconds
exactly like Blizzard's UI does. We fetch session.durationSeconds
via CallSessionAPI / GetCombatSessionFromType right before the
spell loop and divide once per spell.
- Applies to:
DetailWindow.ShowSpellList (Damage + Healing tabs)
MainFrame UpdateBarsFromDamageMeter spell-cache build
MainFrame GetTooltipSpells lazy-fetch path
- DB segment duration is still used as a fallback for Classic /
when C_DamageMeter doesn't expose durationSeconds.
Version 2.4.23
--------------
Synthetic-Source Fallback — Bars during SecretValue Combat
THE REGRESSION
- Reported: tracker showed no data during combat and only filled
in after PLAYER_REGEN_ENABLED. Debug log captured the smoking
gun: "Synced C_DamageMeter -> segment: dmg=92872 heal=0
players=0". `dmg` had a real value, but `players` (i.e.
combatSources) was empty.
- Why: in arenas / BGs / wherever the SecretValue restriction is
active, Blizzard's session.combatSources is empty during the
fight. The API still gives you a session.totalAmount (a
SecretValue you can pass to widgets) but no per-source array.
UpdateBarsFromDamageMeter hit `if sourceCount == 0 then return
false end` and fell back to the Database path — which has no
CLEU-fed data on Retail since 2.4.8 — so the tracker rendered
empty bars.
THE FIX
- When session.combatSources is empty AND session.totalAmount
resolves to a non-zero number, synthesise a one-row source
for the local player:
name = UnitName("player")
classFilename = the player's class
sourceGUID = UnitGUID("player")
isLocalPlayer = true
totalAmount = session.totalAmount (still secret, OK —
widgets handle it)
amountPerSecond = session.amountPerSecond
- The rest of the render pipeline (which already handles
SecretValues via SetText / SetMinMaxValues) shows the player
their own bar with the live session total during the fight.
- Marked on the instance via `_syntheticSource` flag so we can
later add a small "(local view)" hint or skip features that
need a real multi-source array (left as a TODO).
Version 2.4.22
--------------
Taint-Hardening — Utils.ResolveNumber
CRITICAL FIX
- Fixed: UpdateBarsFromDamageMeter threw repeatedly with
"attempt to get length of local 'str' (a secret string value,
while execution tainted by 'EpicDamageMeter')" at Utils.lua:110.
- Root cause: 2.4.21 added an AbbreviateNumbers fast-path for
SecretValues that called `#str > 0` without wrapping in pcall.
AbbreviateNumbers returns a tainted string when fed a
SecretValue, and `#` on a tainted string throws. The error
fired ~4× per refresh tick, polluting the debug log.
- Rewrote ResolveNumber so EVERY taint-sensitive operation is
pcall-guarded. The new flow:
1. tonumber for plain values (fast path)
2. AbbreviateNumbers (preferred) OR string.format (fallback)
to get a representation of the SecretValue
3. string.byte loop (taint-safe — returns plain integers)
to rebuild a plain Lua string
4. tonumber on the plain string (handles "12345" form)
OR Utils.ParseAbbrev (handles "10.5K" / "1.2M" form)
Nothing in this function can throw past pcall now. Worst case
is returning 0 if the byte loop can't read any chars.
Version 2.4.21
--------------
Tracker / Graph / Footer Fixes
TRACKER SHOWED "0 (0 DPS)" DURING COMBAT
- Fixed: after the 2.4.11 ResolveNumber tightening, the tracker
bar displayed "0 (0 DPS)" mid-combat and only filled in once
PLAYER_REGEN_ENABLED fired. Root cause: forcing every
SecretValue through the byte-rebuild slow path returned 0
whenever string.format("%.0f", secretValue) didn't produce a
parseable string — which on Midnight 12.0 happens for any rate
that includes a fractional component.
- New strategy: ask Blizzard's AbbreviateNumbers (which handles
SecretValues correctly) for a clean string like "10.5K", parse
that back via Utils.ParseAbbrev into a plain number. The
byte-rebuild path stays as a final fallback only.
GRAPH MIRRORS THE TRACKER NOW
- Per user request: removed the 3-second rolling-window
calculation added in 2.4.20. The graph just plots whatever
per-second rate the tracker is currently displaying.
- The tracker now publishes a proper RATE (not the cumulative
total) on the Retail path too — `total / duration` when the
display mode is DAMAGE_DONE / HEALING_DONE, or `total` when
the mode is already DPS / HPS.
- A sample is pushed every Update tick while in combat so the
line never freezes — even a momentary 0 between bursts is a
real value worth plotting.
VERSION FOOTER POSITION
- Fixed: the "EpicDM v2.4.20" label sat in mid-air below the
Reset Data / Reload UI buttons with a big gap. Now anchored
to the bottom edge of the sidebar with a thin separator above
it — visually grouped with the action buttons.
Version 2.4.20
--------------
Graph Sliding-Window + Decimal Precision + Stale Tooltip Cache
GRAPH — FLAT LINE BUG
- Fixed: the DPS/HPS graph drew a flat line that never moved during
combat — every sample showed the same value as the last. Root
cause: Graph:Update computed `dps = totalDamage / segmentDuration`,
i.e. the *cumulative average* DPS since combat start. That value
trends toward a constant the longer the fight runs, so the line
flattens almost immediately.
- Replaced with a 3-second rolling-window: each tick we record
(timestamp, cumulative total) into a buffer, drop entries older
than the window, then derive the rate from the delta over the
window span. The line now visibly moves with burst/lull cycles
the way a real DPS meter should.
- Window state is cleared when combat ends so a new pull starts
with a clean rate.
TRACKER — DECIMAL PRECISION
- Fixed: bar values rendered as "10K DPS" with no decimal place.
The user asked for "10.4K DPS". The Abbr helper in MainFrame
routed SecretValues straight through Blizzard's AbbreviateNumbers,
which doesn't add a decimal for plain values < 1000 multiples.
Now it normalises through Utils.ResolveNumber first (unwraps
SecretValues to plain numbers) and formats with Utils.FormatNumber
(`%.1f%s`) so we always get one decimal place.
BAR TOOLTIP — MISMATCHED MATH
- Fixed: hovering a bar showed spell totals that summed to more
than the tracker bar value (e.g. tooltip showed 3.6M of spells
under a 1.3M bar). Root cause: cache key was
`guid|mode|segmentIndex`. A new combat keeps segmentIndex at 0
(still "current"), so the cached spells from the *previous*
combat survived into the new one — the tracker re-rendered with
fresh data but the tooltip kept the stale list.
- Cache key now includes the Blizzard session ID
(`instance._blzSessionId` or `EDM.Core.latestBlzSessionId`), so
every new combat invalidates the cache automatically.
Version 2.4.19
--------------
DPS/HPS Graph Polish
GLYPH FIX
- The bottom-right duration label was showing the literal byte
sequence "xE2x8FxB1 :43" instead of a stopwatch glyph. Root
cause: U+23F1 STOPWATCH isn't in WoW's default font glyph set,
so the renderer fell back to printing the escape bytes raw.
Same problem on the crosshair tooltip's time line.
- Replaced both with a font-safe bullet glyph (•) in muted gold:
"|cff998866●|r 0:43". Renders in every font we ship with.
GRAPH CHROME
- Added split accent stripes on the Y-axis edge: top half tinted
the DPS red, bottom half tinted the HPS green, both fading
toward the midpoint. Gives the chart a "billboard" frame
without obscuring data.
- Bottom of the canvas now has a faint white-to-transparent
horizontal accent stripe that visually separates the chart
body from the X-axis labels.
Version 2.4.18
--------------
Tracker Polish
TRACKER TITLE BAR
- New mini hero emblem in the title bar (18x18) — a compressed
version of the Settings hero logo: fire gradient (yellow→navy)
with 3 rising damage-bars (6/9/13px). Renders the addon's brand
on the tracker too instead of just plain text. Title text now
anchors to the emblem's right side.
TRACKER BARS
- Bars get the same depth treatment the Detail Window ability
bars use:
* bar.gloss — top 50% height, white→transparent vertical
gradient at 18% alpha. Lit-from-above feel.
* bar.shadow — bottom 45% height, transparent→black gradient
at 40% alpha. Adds depth at the bar's foot.
Both live on bar.statusBar at OVERLAY layers so they paint above
the colored fill (not below it, where they were invisible).
STATUS BAR (BOTTOM STRIP)
- Combat duration now has a ⏱ glyph and warm-gold text colour
instead of the previous dim grey. Two short 1px vertical accent
stripes flank the time — gives the strip a "billboard" feel.
- Total label ("Total: ...") split-coloured: dim grey label,
bright white value. Both dynamic-update paths in UpdateBars
preserve the colour tags now (label colour kept in sync after
data refreshes).
Version 2.4.17
--------------
Hero Logo
The tiny 28px "E" emblem in the Settings title was replaced with a
proper hero block, inspired by the reference image the user shared:
fire-coloured shield with rising damage bars, plus a stacked brand
mark next to it.
Because we can't ship a .png that matches Blizzard's interface rules
in this state, the whole emblem is rendered procedurally in Lua —
all done with WHITE8X8 textures and gradients. Layers, top to bottom:
outer glow additive orange/red soft halo behind the emblem
outer border LOGO_BORDER (theme-aware)
base fill vertical gradient: yellow-top → deep-navy-bottom
flame layer additive orange wash on the upper 60%
damage bars 5 vertical bars stepped 8 / 14 / 20 / 26 / 32 px
high in left-to-right order, coloured from gold
through red — mirrors the rising-damage motif in
the reference logo
top sheen short white gradient at the top for a "lit" feel
inner border 1px white at 15% alpha for a carved edge
Brand wordmark to the right is now stacked:
EPIC small ember-orange tag, 9pt outlined
DAMAGE METER 17pt outlined wordmark, two-tone
The title bar height didn't change — the hero fits in the existing
52px strip. Wizard / theme / close buttons on the right unchanged.
Version 2.4.16
--------------
ESC Menu Position Fix + ArenaCore-Style Tagline + Version Footer
ESC MENU
- Fixed: the "EpicDM" entry added in 2.4.15 was barely visible —
it landed near the top of the menu, overlapping with the
"Optionen" button. Root cause: the anchor relied on
GameMenuButtonAddons which is nil on Midnight 12.0, then fell
back to a hard-coded TOP offset that put us next to the title.
- The button now repositions itself on every ESC-menu open. It
walks the menu's children, finds the lowest-positioned visible
Button, and anchors below it — including below buttons added
by OTHER addons (SUI / Arena Core / etc.). The defer runs twice
(immediately and again 50ms later) to catch addons that inject
their buttons after the first OnShow tick.
- The GameMenuFrame grows automatically by the overhang so our
button is never clipped.
SETTINGS — ArenaCore-INSPIRED FOOTER + TAGLINE
- New 22px tagline banner directly below the title, rotating
through five EDM-themed slogans on every panel open:
* "Track every hit. Own every fight."
* "Numbers don't lie — read the meter."
* "From pull to parse, every cooldown counted."
* "Know your damage. Earn the kill."
* "Big numbers. Cleaner reads. Fewer surprises."
Theme-aware gradient: arena = orange-to-purple wash (matches
the ArenaCore tagline look), dark = accent gradient, light =
subtle accent tint.
- Version footer at the bottom of the sidebar showing the
"EpicDM v2.4.16" mark in muted text. Pulls the version from
the TOC at runtime so it stays in sync.
Version 2.4.15
--------------
Light-Mode Fix + ESC-Menu Button + Arena Theme
LIGHT MODE
- Fixed: section headers were unreadable in light mode — the dark
accent-tinted gradient overlay I added in 2.4.13 dominated the
card colour and inverted the page (light bg + dark headers with
dark text). Now adapts per theme:
light → very subtle accent-tint that fades to zero
dark → keeps the moody accent depth gradient
- Text outline + shadow disabled in light mode (was creating a
black halo around dark text on light bg).
ESC MENU BUTTON
- The in-game ESC menu now has an "Epic DM" entry, the same way
SUI and Arena Core surface their settings. Tries the modern
Menu.ModifyMenu API first (Midnight 12.0 dropdown menu) and
falls back to a GameMenuButtonTemplate button on older clients.
Clicking it opens the EDM settings panel and dismisses the
game menu.
ARENA THEME (NEW)
- New "arena" theme variant, inspired by Arena Core's deep purple
aesthetic. Cycle with the sun/moon button: dark → light → arena
→ dark. While in arena mode the icon tints purple to signal the
third state.
- Palette: near-black panels, saturated purple accent, yellow
hover on nav items (matches Arena Core's yellow glow on active
category).
- Section headers in arena mode get a thicker 2px purple border,
heavier wash gradient, and BOLD UPPERCASE titles in accent
purple instead of the standard text colour — the "section card"
feel from the reference.
Version 2.4.14
--------------
Tracker / Details Polish + Display Bug Fixes
TRACKER
- Title bar no longer duplicates the mode name. Previously the
title showed "Epic DM Verursachter Schaden" while the toolbar
button directly below also said "Verursachter Schaden" — visual
redundancy that pushed the brand off-screen on narrow windows.
Title is now just the brand (+ enemy indicator when active);
mode lives exclusively in the toolbar button.
DETAIL WINDOW
- Fixed: header pills still showed "0 DPS / 0 HPS" while spell rows
rendered correct per-second values. Root cause: the previous
fallback divided by the DB segment duration which is clamped to
1s after a segment is reset — producing absurd values OR being
short-circuited by a SecretValue rate that resolved to a tiny
non-zero number. Now reads `session.durationSeconds` from
C_DamageMeter (the duration the meter actually measured) as the
primary divisor, with a session-level fallback when the per-source
data doesn't carry it, and treats any rate <= 0 as "missing" so
the fallback always kicks in for the bad cases.
- Fixed: spell rows below 1000 DPS displayed raw IEEE-754 floats
("777.76580810547 DPS"). The Abbr helper short-circuited around
Utils.ResolveNumber for SecretValues and routed them through
Blizzard's AbbreviateNumbers, which doesn't round low values.
Both Abbr helpers in DetailWindow now go through ResolveNumber
first, then Utils.FormatNumber — guaranteed clean integers.
- Spell-row depth: the gloss / shadow overlays added in 2.4.10
weren't actually visible because they sat on the bar parent
while the StatusBar child rendered above them. Moved both to
bar.statusBar at OVERLAY layer so they paint on top of the
colored fill. Added a 2px right-edge highlight texture (hidden
by default, ready for show* code to position).
Version 2.4.13
--------------
Pill Look Goes System-Wide
TOOLBAR SPACING
- Fixed: in the meter toolbar, the mode label ("Verursachter Schaden"),
the Team/Enemy toggle ("Team"), and the segment label ("Aktuell")
visually fused into each other on long localised strings. Root
cause: mode button was a fixed 90px wide and the German label
overflows beyond that width, bleeding into the next button.
- Mode button now auto-fits its label width (clamped 60-220px) via
a new `Instance:ResizeModeBtn()` method that's called on init
and on every SetMode. Gaps between mode/enemy/divider/segment
bumped from 3-4px to 7-8px so labels stay visually separate.
PILL DESIGN — REUSABLE WIDGETS
- Extracted the Detail Window header pill into
EDM.Widgets:CreateStatPill(parent, opts) so the same dark-card +
top thin / bottom thick / left vertical accent stripes + accent-
tinted depth gradient style can be reused across the addon.
Returns a frame with :SetValue(value, sub, isZero) for live updates.
- Added EDM.Widgets:CreateSectionBar(parent, opts) — a wider
horizontal sibling for section headers (single-line title + meta).
Same accent palette, same lit-from-top feel.
SETTINGS PANEL
- Settings section headers (Config:CreateSection) adopt the pill
look: dark backdrop, horizontal accent-tinted depth gradient,
top + bottom + left-vertical accent stripes in the theme's
ACCENT colour. Section title outlined for better legibility on
the new gradient bg, chevron is now accent-coloured.
- Hover state intensifies all three accent stripes plus the border
rather than just dimming the bg — clear visual feedback that the
section is interactive/collapsible.
Version 2.4.12
--------------
Detail Window Pass 2 — Pills, Targets, DPS
PILLS
- Split the confusing "INT / DSP" combo pill into two separate pills:
"INTERRUPTS" and "DISPELS". User feedback was that "INT / DSP"
read like a DPS abbreviation. Each pill now has its own label,
accent colour, and dim-when-zero state.
- Added a second (thicker) bottom accent stripe and a subtle
accent-tinted depth gradient on each pill. Active pills feel
more "lit" while zero-valued pills clearly recede.
- Default window grew from 460x560 to 520x580 so the new 6-pill
row doesn't truncate the "INTERRUPTS" label.
HEADER DPS / HPS FALLBACK
- Fixed: header pills showed "0 DPS" and "0 HPS" while the spell
rows showed correct DPS values (e.g. "Göttlicher Hammer 7.1K
DPS"). C_DamageMeter's amountPerSecond is sometimes nil / 0 /
SecretValue even when totalAmount is populated — happens on
Overall sessions, post-combat snapshots and expired sessions.
- Header now computes rate = total / duration as a fallback when
the session-provided rate is missing, so DPS/HPS stays in sync
with the spell rows.
TARGETS TAB
- Fixed: "Ziele" tab was empty on Retail because we removed CLEU
registration in 2.4.8 (12.0 moved combat events to EventRegistry
and forbade CLEU for addons). actor.targets had no data.
- Now inverts C_DamageMeter's EnemyDamageTaken view: each enemy
session has combatSources listing the players who damaged it.
Walking those and matching the actor's GUID gives "what targets
did THIS player damage, and for how much". SecretValue-safe
matching via pcall'd equality.
- Target rows now show "Damage DPS · X.X%" in the same compact
layout the spell rows use, plus the gold/silver/bronze rank
badge on the top 3 targets.
Version 2.4.11
--------------
Bug Fixes + Debug Console + Detail Window Polish
BAR TOOLTIP — STALE SPELL CACHE
- Fixed: bar tooltip on hover showed spells from the wrong player.
Repro: hovering Toilettegodx (Monk) in the Healing window showed
Warlock spells like Drain Soul / Dark Pact / Healthstone — those
were Méuw's (a Warlock who occupied that bar slot earlier).
- Root cause: bars are pooled and recycled across actors / modes /
segments. `bar._cachedSpells` (populated by UpdateBars for the
tooltip) had no invalidation key, so a bar that previously showed
Player A and was later reassigned to Player B kept serving A's
spells. The bug also surfaced when switching segments or modes on
the same window.
- Fix: every cache write now tags the bar with a composite key
(`actor.guid | mode | segmentIndex`). Both the cache-write paths
(UpdateBars and GetTooltipSpells lazy fetch) set the key, and
every cache read checks it. Mismatch → drop and re-fetch.
- Also fixed a second SecretValue arithmetic site at MainFrame.lua:
1938 (cache build during UpdateBars) — same `tonumber(secret)`
taint leak we fixed at line 2918 last release.
CRASH / TAINT
- Fixed: 14x "attempt to perform arithmetic on local 'amt' (a secret
number value)" at MainFrame.lua:2916 (GetTooltipSpells). The bar
tooltip's spell loop called `tonumber(sp.totalAmount)` which on a
SecretValue returns the still-secret value, then `spellTotal + amt`
blew up. Now uses Utils.ResolveNumber which handles SecretValues
via the byte-rebuild slow path.
- Fixed: Utils.ResolveNumber fast-path silently passed SecretValues
through. `tonumber(secret) > 0` doesn't throw and the comparison
reports ok, so the wrapper assumed the value was clean and returned
it. Subsequent arithmetic / format calls then leaked taint or
produced raw float strings. Added an explicit issecretvalue() check
before the fast path so SecretValues always take the byte-rebuild
route.
- Fixed: 6x "attempt to call a nil value" at DebugConsole.lua:671
when running `/mem`. Midnight 12.0 moved GetNumAddOns, GetAddOnInfo,
IsAddOnLoaded, GetAddOnMemoryUsage, UpdateAddOnMemoryUsage under
C_AddOns. The command now resolves through C_AddOns first and
falls back to the legacy globals where they still exist.
DETAIL WINDOW
- Fixed: spell row DPS showed raw floats like "687.46350097656 DPS"
for values below 1000. Root cause was the same ResolveNumber leak
above — perSecond is a SecretValue on Retail and slipped past
the rounding. The Utils.ResolveNumber fix resolves this generically,
and the row layout was rewritten:
* Value (top-right, big) : amount only, e.g. "41K"
* Detail (bottom-right) : "687 DPS · 26.1%" (rate + share)
* Subtext (bottom-left) : "5 hits · 2 crits (40%)"
Eye lands on the big number, then drops to the rate+share line.
- Header pills: zero-valued pills now dim themselves (value muted,
border faded, accent strip dimmed) so the eye skips empty stats.
- Removed redundant "I / D" sub-label under Int/Dsp pill — the
pill's main label already says "INT / DSP".
DEBUG CONSOLE
- Fixed: errors captured by the in-addon console showed up as
"Runtime: table: 0x..." with no actual message. BugGrabber-format
error objects use the field name `.msg` (not `.message` which the
old extractor expected). The extractor now probes multiple field
names — msg, message, error, errorMessage, text, what, desc,
reason — and falls back to serialising primitive table fields so
you always see something concrete instead of an opaque table id.
- Stack trace shown bumped from 3 to 6 lines (3 was too short to
see where the error actually came from).
- NEW console command: `/edm console` -> `last` (also `error last`)
dumps the full last captured error: message, full stack trace,
and the first 12 lines of locals. No more BugSack required for
basic debugging — though BugSack still works on top if installed.
--------------
Detail Window Redesign
- Reworked the Player Detail window UI top-to-bottom while keeping all
existing functionality, signatures and data flow intact.
HEADER (was: cramped text rows separated by pipes)
* Default window grew from 420×520 to 460×560 for breathing room.
* Larger class icon (54px, was 48), framed with class-tinted border.
* Big soft class-color halo glow behind the icon.
* Class-color top accent band runs across the entire header.
* Player name now in 16px outlined class color (was 13).
* Class subtitle line under the name (small, muted).
* "Dmg / DPS | Heal / HPS | Deaths | Interrupts | Dispels | Absorbs"
one-line text block REPLACED by a row of five stat PILLS:
DAMAGE - big total + small "X DPS" sub
HEALING - big total + small "X HPS" sub
ABSORBS - big total
DEATHS - big number (greys out when zero)
INT/DSP - combined interrupts/dispels as "12 / 3"
Each pill has its own colored top accent (red/green/gold/purple/blue).
* Pills flex-resize on window resize.
TABS (was: text-only with thin underline)
* Now a segmented control: 30px tall, gradient bg, top accent strip,
bottom indicator. Each tab carries its own type color (Damage red,
Healing green, Targets orange, Damage Taken crimson).
* Hover state subtly tints the bg toward the tab's accent.
* Active tab gets a stronger tinted gradient + full-color top accent
bar + bottom indicator line.
ABILITY BARS (was: flat red statusbar, tiny icon)
* Layered rendering: dark base + vertical depth gradient + status bar
(skin-aware texture) + top gloss highlight + bottom shadow.
* Slightly larger icon with 1px dark border (separates from bg).
* Spell name jumped from 10px to 11px outlined; value text bolder.
* Hits/crits sub-line now in its own field (was crammed in tiny text).
* Top 3 entries get a gold/silver/bronze rank badge on the icon.
* Subtle row divider for cleaner list rhythm.
All show* functions (ShowDamageAbilities, ShowHealingAbilities,
ShowTargets, ShowDamageTaken, ShowSpellList, ShowSpellTooltip,
ShowSpellDetail) and the live C_DamageMeter integration are unchanged.
Backward-compat shims keep self.header.stats1/stats2 callable as
no-ops in case any code path still touches them.
Version 2.4.9
-------------
Bug Fix
- Fixed: hovering a player bar during combat on Retail showed 0/0/0
in the tooltip; the right values only appeared after combat ended.
Root cause: UpdateBarsFromDamageMeter writes a zero-initialised
actor stub into bar.actorData (the bars get their width directly
from C_DamageMeter session values, the stub is for spell-cache
lazy loading). The tooltip read those zeros instead of asking
C_DamageMeter itself. After combat, OnCombatEnd imports session
totals back into the database actor, which is why post-combat
worked.
- Tooltip:ShowActorTooltip now queries C_DamageMeter live for the
hovered source's damage / healing / absorbs / damage-taken /
interrupts / dispels across multiple session enums, picking the
freshest available value and falling back to the database actor
only when live data is missing. Duration is taken from the
session itself so it matches what the bars are rendering.
- SecretValue-safe throughout: GUID/name comparisons go through a
pcalled equality wrapper, formatting routes secret numbers through
AbbreviateNumbers, division skipped entirely for secret damage.
Theme Pack + Animation Framework
- NEW: 8 new skins, expanding the catalogue from 23 -> 31. Each
one targets a stylistic gap the existing themes don't cover:
* Terminal — retro CRT phosphor green on near-black, monospace
font, scanline overlay, sharp edges. For DOS nostalgia.
* Sakura — soft rose pastel on dark wine background, gentle
gradients, cherry-blossom palette. Daytime / lighter mood.
* Bloodforge — Death Knight runic aesthetic, crimson on iron,
gothic Morpheus serif, runic accent borders.
* Vaporwave — Y2K sunset: magenta/cyan/violet on twilight grid.
Dreamy gradients, neon-with-soft-edges (NOT Cyberpunk's harsh
industrial; this is Lisa Frank / Macintosh Plus).
* Newsprint — anti-color skin. Cream paper, black ink, serif.
Class colors intentionally muted so typography does the
talking. For anyone tired of the class-color rainbow.
* Tactical — military HUD: amber on dark olive, monospace,
crosshair-style accents. Reads like a cockpit display.
* Brutalist — architecture-inspired heavy minimalism. Raw
concrete grays, oversized typography, 3px black borders,
yellow alert accents. Nothing soft, nothing decorated.
* Glitch — broken-LCD aesthetic with chromatic aberration.
Red shadow on cyan text, RGB-tinted edges. Pixel statusbar
texture. For when you want the UI to feel slightly wrong.
- NEW: Procedural statusbar palettes (Skins:CreateProceduralBar) —
Lua-generated gradients registered as LSM statusbars, no .tga
artwork needed. Ships with Sunset, Ocean, Toxic, Ember, Aurora
ready-made palettes for quick custom-theming.
- NEW: UI/Animations.lua module with reusable effects:
* A:Pulse(frame, opts) — breathing opacity loop
* A:Glow(frame, opts) — one-shot glow burst with scale
* A:CountUp(fs, from, to, opts) — number-tween FontString with
ease-out / linear / ease-in
* A:Shimmer(frame, opts) — light-band slides across (good
for #1 rank highlight)
* A:RecordFlash(frame, opts) — celebratory flash for new bests
* A:Shake(frame, opts) — damped oscillation for emphasis
* A:CancelAll(frame) — cleanup all anims on a frame
* A.FormatBig(v) — formats 1234567 -> "1.23M"
All self-cleaning: starting a new anim on the same frame cancels
the previous one to avoid stacking.
- NEW: Animations wired into Bars.lua. Each bar now reacts to its
data live:
* CountUp on the value FontString when DPS changes by ≥100
(deltas below that snap to avoid micro-tween churn)
* Glow burst in class color when the bar climbs a rank
* Continuous Shimmer band sliding across the #1 bar
* Gold RecordFlash the first time a bar takes #1 in the
current (actor, mode) — celebrates breaking through
Per-bar state tracking (peak value, last rank, shimmer flag,
ever-was-first flag) makes the triggers idempotent — they
don't refire on every refresh during a single rank streak.
Anim state is reset on bar recycling (ReleaseBar) and on theme
changes (ApplySettings) so nothing leaks between segments.
- NEW: Settings hooks bars.useAnimations (master toggle) and
bars.animOptOut = { CountUp = true, Glow = true, ... } for
per-anim opt-out. Default: all on. Defaults can be wired into
Config.lua's Bars tab in a follow-up.
Version 2.4.8
-------------
Retail (Midnight 12.0) — ADDON_ACTION_FORBIDDEN fix
- Root cause: Midnight 12.0 migrated combat / unit / roster
events from the legacy Frame:RegisterEvent API to the new
global EventRegistry (CallbackRegistryMixin). The legacy path
now fires ADDON_ACTION_FORBIDDEN for these events; only
lifecycle events (ADDON_LOADED, PLAYER_LOGIN) still work,
which is why AceAddon kept loading fine while combat tracking
stayed silently broken.
- Solution: register every restricted event through
EventRegistry:RegisterFrameEventAndCallback(event, fn, owner).
Callback signature is (ownerID, ...eventArgs) — no event-name
arg, which differs from the OnEvent script style. DAMAGE_METER_*
events are custom (not real frame events) so they use
EventRegistry:RegisterCallback instead.
- COMBAT_LOG_EVENT_UNFILTERED is NO LONGER registered on Retail —
Blizzard removed addon CLEU access entirely in 12.0. Damage
data flows through C_DamageMeter exclusively (the server-combat
ticker in OnEnable polls C_DamageMeter.GetCombatSessionFromType
each tick and UpdateFromDamageMeter renders from it).
- ENCOUNTER_STATE_CHANGED dropped — it is a custom callback on
EncounterJournal, not a real frame event. ENCOUNTER_START /
ENCOUNTER_END (real events, registered via EventRegistry) cover
the encounter-tracking need.
- TOC: bumped Interface 120001 -> 120005 and Version 2.4.3 -> 2.4.8.
- Applied to Core_Retail.lua (10 frame events + 3 C_DamageMeter
callbacks) and Parser_Retail.lua (6 frame events).
- OnDisable now unregisters via EventRegistry:UnregisterFrameEventAndCallback
and UnregisterCallback so a /reload doesn't double-register.
- Core:RegisterEvents() and Parser:RegisterEvents() are now
no-op stubs (kept for API compatibility).
Known follow-up: BroadcasterTools.lua and ArenaSummary.lua still
use legacy RegisterEvent for UNIT_SPELLCAST_*, COMBAT_LOG_EVENT_*,
PVP_MATCH_COMPLETE. Those features (Action Tracker, Event Tracker,
Arena Summary) will hit the same restriction when activated.
Migration to EventRegistry planned for next release.
Note: earlier 2.4.8 attempts (pcall wrap, dedicated frame,
C_Timer.After defer, file-load registration, Interface bump,
Frame:RegisterEventCallback) all failed because they fixed the
wrong layer. RegisterEventCallback is a CallbackRegistryMixin
method whose event set is closed and mixin-local — it rejects
raw frame events like PLAYER_REGEN_DISABLED. EventRegistry is
the right entry point.
Version 2.4.7
-------------
Broadcaster Tools (Retail only)
- NEW: Action Tracker overlay — shows your spell casts in a scrolling bar
list. Cast bars fill as you cast; instant casts show a 1.2s decay. Tracks
UNIT_SPELLCAST_* events for full cast state (start, success, interrupt,
channel). Enable/Disable from Broadcaster Tools settings tab.
- NEW: Event Tracker overlay — shows nearby combat events: defensive
cooldowns, offensive cooldowns (90s+), crowd control, and spell interrupts.
Events appear as color-coded rows with a 1-second fill animation.
Built-in curated spell database covering all classes.
- NEW: Arena DPS Bar — split bar comparing your team's damage vs the enemy
team in arena. 5-second rolling window with 100ms sampling. Activates
automatically when entering an arena zone.
- Broadcaster Tools tab is now Retail-only (hidden on Classic/TBC/Wrath/MoP).
- Config UI: Enable/Disable + Options buttons for each tracker (like Details).
- "Disable Mythic+ Stuff" toggle now finds the Mythic Dungeon button
dynamically instead of hardcoding index [4].
Easter Egg
- NEW: Snake minigame in Settings sidebar! Click the Snake tab,
click the grid, and use Arrow keys or WASD to play. Space to
pause, R to restart. Speed increases as you eat. High score
saved per profile. A little something for raid downtime.
- Fixed Snake game freezing / becoming unresponsive after a while.
Root cause: EditBox keyboard capture lost focus (ESC, clicking
elsewhere, combat lockdown) with no recovery path. Now auto-pauses
on focus loss with a clear "Click to resume" overlay. Click or
press R to restart after Game Over without needing keyboard focus.
- Fixed Snake grid clipped on left side. Grid was offset by
-SIDEBAR_WIDTH/2 from the scroll child center; now centred at 0.
- Fixed Click to Play — clicking the grid now immediately starts
the game (snake moves right). Previously only keyboard worked.
- Fixed Snake 180-degree turn death: direction checks now use the
queued direction instead of the current one, preventing instant
self-collision from fast opposite-key presses.
- Snake visual overhaul:
- Animated effects: death flash (red overlay fade-out), screen
shake on death, floating "+10" score popups that rise and fade,
golden level-up flash when ranking up.
- Rank system: Worm → Snake → Serpent → Viper → Python → Wyrm
(WoW item-quality colours: green/blue/purple/orange/red/gold).
- Speed bar in header showing current game speed percentage.
- Animated score counter that smoothly counts up on eat.
- Persistent stats: games played, total food eaten, longest snake.
- Golden shimmering food with dual-frequency pulse.
- Brighter snake head, distinct neck, smoother body gradient.
- Dimmed overlay backgrounds for all states with 3 text lines.
- Red-tinted grid on death, darker grid during play.
- Outer glow border on grid, icon in title.
- Footer: high score (gold), stats, controls hint.
- Proper cleanup (OnUpdate + EditBox) when switching tabs.
Debug Console & Debug Parity
- NEW: In-game Debug Console — CMD-like window showing debug output,
errors, and system messages in real-time. Color-coded by category
(CORE=green, COMBAT=orange, ERROR=red, DATA=cyan, PVP=magenta,
PERF=teal, EVENT=blue, WARN=orange). 500-line ring buffer,
copy-pasteable text. Toggle with /edm console. All clients.
- Console toolbar: one-click buttons for Clear, Diag, Errors, Mem, GC.
- Console filter tabs: click category tabs (CORE, COMBAT, DATA, ERROR,
PVP, UI, PARSER, EVENT, SYSTEM) to show/hide specific log categories.
"All" button resets filters.
- Console Tab-completion: press Tab to autocomplete commands.
- Console commands: help, clear, diag, errors, log [n] [cat], debug
on|off, info, events, mem, gc, segments, search <text>, fps,
profile, api, lua <code>, reset, reload.
- Console "lua" command: execute arbitrary Lua in a sandboxed env with
EDM namespace access. Useful for live addon inspection.
- Console "api" command: checks availability of all relevant WoW APIs.
- Console "segments" command: shows current + stored segment details.
- Console "search" command: find matching lines in console history.
- Console "fps" command: shows framerate and network latency.
- Console "profile" command: shows active profile and available list.
- NEW: Dedicated "Debug" tab in Settings sidebar — full diagnostic
panel with inline pass/fail results, error log viewer (last 15
errors with timestamps), system info panel (addon version, TOC,
client, memory, FPS, latency, segments, profile), WoW API
availability checker (13 APIs), and quick-action buttons (Open
Console, Run Diagnostic, Show Errors).
- Debug/Record Timeline toggles moved from Advanced section into the
new Debug tab's General section.
- Debug Console now embedded directly in the Settings Debug tab:
scrollable output area, toolbar buttons (Clear, Diag, Errors, Mem,
Info), command input with > prompt, Tab-completion, and command
history (Up/Down arrows). Matches the addon's own design language.
Live-refresh: output updates automatically as new log entries arrive.
/edm console now opens Settings on the Debug tab instead of a popup.
Standalone BugSack-style window still available via DC:Toggle().
- Fixed Unicode square characters in diagnostic output and settings.
Replaced checkmark (U+2713) and cross (U+2717) with ASCII "OK"/"X"
— WoW's game fonts lack these glyphs, causing visible squares.
- Hardcoded SelectTab(6) for Profiles tab replaced with dynamic
FindTabIndex("BuildProfilesTab") lookup — tab indices no longer
break when new tabs are added.
- Classic debug parity: /edm diag, /edm errors, /edm log [n] [cat],
and /edm console now available on Classic Era, TBC Anniversary,
MoP Classic, and Wrath Titan Forged (previously Retail-only).
Arena Summary (Retail only)
- Fixed Arena Summary showing wrong/missing data compared to WoW's
default scoreboard. Enemy team was empty, friendly team showed tiny
numbers (e.g. 45K instead of 915K). Root cause: CLEU combat log data
is restricted in WoW 12.x arenas. Switched primary data source to
C_PvP.GetScoreInfo() — the same API WoW's own scoreboard uses.
Falls back to CLEU segment data on older clients.
- NEW: Arena match history — matches are saved persistently (up to 50)
in your profile. Browse past matches with Prev/Next navigation
buttons after the arena has ended. Each saved match stores team
rosters, damage, healing, deaths, kills, rating, and rating change.
- NEW: Rating change display — shows +/- rating in green/red next to
player names when available from the scoreboard API.
- NEW: Data source indicator — label shows whether data comes from
"Scoreboard" (C_PvP API), "Combat Log" (CLEU fallback), or
"No data" so you know the source at a glance.
- NEW: Date/time shown in subtitle for saved matches when browsing
history.
Spell Casts Tracking
- NEW: "Casts" column in spell tooltip — hover any player bar (Damage,
Healing, DPS, etc.) to see how many times each spell was cast. Shown
alongside the existing Total / DPS / % columns in a light blue color.
Cast data comes from SPELL_CAST_SUCCESS events via CLEU.
- NEW: "Casts" display mode — dedicated bar view ranking players by total
spell casts. Cycles alongside existing modes in the menu and rotation.
Tooltip shows per-spell cast breakdown with count and percentage.
- Pet casts are attributed to owners via GetEffectiveSource mapping.
- Works on all clients (Retail, Classic, TBC, Wrath, MoP).
Bug Fixes
- Fixed Action Tracker entries disappearing immediately after cast
completes. Entries now stay visible in their final state (green for
success, red for interrupted) with a gentle fade, and are only
removed when pushed off by newer casts. Channels and instants also
persist instead of vanishing after 1.2-2s.
- Fixed "cannot be indexed with secret keys" errors in arena/PvP.
UnitGUID() returns tainted secret values for arena/nameplate units
in WoW 12.x. Added SafeGUID/SafeName wrappers with issecretvalue()
guards in Parser_Retail (NAME_PLATE_UNIT_ADDED, AddPetForUnit,
UpdatePetMapping) and ArenaSummary (BuildRosterMaps).
- Debug Console now captures real Lua runtime errors (not just
manual LogError calls). Hooks into BugGrabber if installed,
otherwise wraps the global error handler via seterrorhandler().
Only shows errors from EpicDamageMeter files. Errors include
stack traces (first 3 frames) and occurrence count.
- Fixed DPS/HPS colored bars invisible during combat. Bar width used
amountPerSecond (rate, e.g. 8.9K) against topValue=session.maxAmount
(total, e.g. 890K), producing ~1% fill. Now always uses totalAmount
for bar width — proportions are identical since all actors share the
same combat duration.
- Fixed 5 diagnostic FAIL results (Constants, MainFrame, Config UI,
Graph, DetailWindow). RunDiagnostic was checking wrong namespace
paths (EDM.C, EDM.UI.MainFrame, EDM.UI.Config, EDM.UI.Graph,
EDM.UI.DetailWindow). Corrected to actual paths (EDM.Constants,
EDM.UI, EDM.Config, EDM.Graph, EDM.DetailWindow).
Version 2.4.6
-------------
Full Compatibility Audit — Retail + Classic/TBC
- Fixed ADDON_ACTION_FORBIDDEN error spam (6x) on TBC Anniversary Edition.
AceEvent frame was named, causing Blizzard taint tracking to block
RegisterEvent during combat. Changed to unnamed frame. Also defers event
registration if the addon loads during combat (InCombatLockdown guard).
- Fixed ADDON_ACTION_BLOCKED on mode menu open: SetPropagateKeyboardInput
is a protected function on Retail/TBC Anniversary. Replaced with
UISpecialFrames for ESCAPE-to-close (standard non-protected pattern).
- Fixed Overhealing and Threat modes showing no data on TBC Anniversary.
Root cause: ADDON_ACTION_FORBIDDEN blocked CLEU event registration →
parser never received combat log events → database stayed empty.
- Fixed DPS/HPS bar display to match Damage Done/Healing Done format.
Now shows "52.3K (476 DPS) 43.4%" instead of "476/s 43.4%".
- Fixed "table index is nil" crash at Parser_Classic.lua:506 when taking
fall damage (ENVIRONMENTAL_DAMAGE). sourceGUID is nil for environmental
events — can't be used as a table key. Now uses "Environment" fallback.
- Fixed SetGradient API crash on Classic and TBC Anniversary. The wrapper
now auto-probes at first call: tries new 2-arg CreateColor form, if it
errors falls back to old 7-arg form. Previous detection via GetBuildInfo
TOC version was unreliable on TBC Anniversary (Retail client, but
reported Classic-like version). All 60+ SetGradient calls across
MainFrame, Config, DetailWindow, Graph, Skins, Widgets, ArenaSummary,
DeathRecap now go through the wrapper.
- Fixed ColorPickerFrame:SetupColorPickerAndShow() crash on Classic/TBC.
This API is Retail 10.2.5+ only. Added compat path using legacy
ColorPickerFrame direct-property method for Classic clients.
- Fixed SecretValue arithmetic crash in Threat mode (Retail PvP). The
threat value can be wrapped by WoW 12.0+ SecretValue. Now washed
through tonumber(tostring()) before arithmetic.
- Fixed mode menu ESCAPE-to-close broken by OnShow overwrite. Two
SetScript("OnShow") handlers were registered on the same frame —
the second killed the first (which enabled keyboard propagation).
Merged into a single OnShow handler.
- Fixed Database.lua nil crash when profile.combat sub-table is nil.
All 5 occurrences of EDM.db.profile.combat.X now have full nil-guard
chain.
- Fixed C_DamageMeter session fetch crash on Retail. GetCombatSessionFrom
Type/ID calls can throw if the session is tainted. Now wrapped in pcall.
- Fixed duplicate "Max Spells" slider in tooltip settings (one with wrong
range 3-15 and default 6 vs correct 3-30 default 30). Removed duplicate.
- Fixed Skins.list nil crash in Setup Wizard Step 2 when Skins module
hasn't loaded yet.
- Improved client detection: granular per-expansion flags (isVanilla, isTBC,
isWrath, isCata, isMoP) instead of binary isRetail/isClassic. isRetail is
now strictly Midnight (12.x+). Added hasMythicPlus flag (Retail only).
- Mythic Dungeon settings tab now hidden on non-Retail clients (Classic Era,
TBC Anniversary, Wrath, MoP). Config sidebar dynamically filters categories
based on client capabilities.

