promotional bannermobile promotional banner

Recount - Revived

Recount is a graphical damage meter written by Cryect. Recount (Preservation) is an attempt to preserve it and keep it running moving forward
Back to Files

Recount-v1.18.5

File nameRecount-Recount-v1.18.5.zip
Uploader
PmptastyPmptasty
Uploaded
Aug 3, 2026
Downloads
5.5K
Size
355.0 KB
Flavors
RetailClassicMoP ClassicClassic TBC
File ID
8572271
Type
R
Release
Supported game versions
  • 12.0.7
  • 12.0.5
  • 11.2.7
  • 5.5.4
  • 5.5.3
  • 4.4.0
  • 3.4.3
  • 2.5.5
  • 1.15.8

What's new

Recount Changelog

[v1.18.5] (2026-08-03) - MoP Classic support, TOC metadata corrections

Fixed

  • MoP Classic gets its own TOC (Recount_Mists.toc, Interface 50504/50503). Recount branches on WOW_PANDA_CLASSIC in several places and both the README and the CurseForge description claimed MoP support, but there was no _Mists TOC — so the client fell back to the base Recount.toc (Interface 11508), flagged the addon as out of date, and it only loaded at all with "Load out of date AddOns" ticked. Worse, the packager derives the game versions it publishes from the TOC files, so the v1.18.4 upload registered 1.15.8, 2.5.5, 3.4.3, 4.4.0, 11.2.7, 12.0.5 and 12.0.7 but no 5.5.x — CurseForge never offered the build to MoP players at all. The new TOC is byte-identical to the Cata one apart from the interface version.

Changed

  • Retail now requires VersionCheck-1.0, like every other flavour. Recount_Mainline.toc declared Dependencies: Ace3 while every Classic TOC declared Ace3, VersionCheck-1.0 — and had never listed it in the file's history, so retail players got no in-game notification when a new Recount shipped. Both are hard dependencies now on all six flavours. CurseForge already installs both automatically (they are listed under required-dependencies in .pkgmeta), so this changes nothing for anyone installing through the app; a manual installer needs VersionCheck-1.0 present, as Classic players already did. Tests/toc_spec.lua asserts the dependency lines stay in step from here on.

  • The bundled libraries are declared as embedded rather than as optional dependencies. Every TOC listed LibDropdown-1.0, LibSharedMedia-3.0, LibBossIDs-1.0, LibGraph-2.0 (and Classic Era alone also listed LibDataBroker-1.1, LibDBIcon-1.0) under ## OptionalDeps: — but all six of those ship inside libs/ and are loaded by embeds.xml or by explicit TOC lines. OptionalDeps only reorders standalone addons of the same name, so those entries were inert and described the addon incorrectly. Replaced across all six TOCs with ## X-Embeds: LibStub, LibDropdown-1.0, LibBossIDs-1.0, LibSharedMedia-3.0, LibGraph-2.0, LibDataBroker-1.1, LibDBIcon-1.0 — the field that actually means "bundled inside this addon", now identical on every flavour and naming all seven (the old line missed LibStub entirely and used the wrong capitalisation for LibDropdown-1.0). Tests/toc_spec.lua cross-checks the declaration against the contents of libs/, so a library cannot be bundled in future without being recorded.

Testing

  • Tests/toc_spec.lua guards the flavour TOCs against drift. They are near-identical by design, so the failure mode is one TOC gaining a file the others do not (that flavour silently loses a feature) or a flavour having no TOC at all. The spec asserts every supported flavour has a TOC, that its interface version is in that flavour's range, that every file it lists exists on disk, that the Classic flavours load exactly the same files in the same order, that retail loads that same list plus the two Midnight files and no Classic flavour loads them, that all 15 locale files are listed, and that dependencies, saved variables, title and project id match across flavours.

[v1.18.4] (2026-08-03) - Addon Language selector, nine bug fixes, offline test suite

Added

  • "Addon Language" dropdown in Settings. Recount's own interface text can now be forced to any bundled translation, independent of your WoW client language — a player on an English client can run Recount in German, a German client in English, and so on. The dropdown lists Auto (the previous behavior — follow the client locale) plus all 14 bundled languages: English, Deutsch, Español, Español (AL), Français, Italiano, Nederlands, Português (BR), Русский, 한국어, 简体中文, 繁體中文, Thai, Filipino. The choice is saved (RecountLanguage) and applies on the next /reload. In-game item / spell / unit names still come from your client and are unaffected.

  • README.md now ships with the addon. A player-facing guide sitting next to CHANGELOG.md in the installed folder and on the GitHub page: what Recount does, what it needs installed alongside it (Ace3 and VersionCheck-1.0), how to drive the window, every display mode, the detail / graph / compare / death-log views, fight history, live meter windows, reporting, the minimap button and broker feed, the settings worth knowing about, the full slash-command and keybinding surface, the retail Midnight differences, and a short troubleshooting section. It is deliberately not in .pkgmeta's ignore list — it is meant to be read after installing, not only on the web.

Fixed

  • Right-clicking the minimap button no longer throws an error when opening Recount's settings. On clients that ship C_SettingsUtil.OpenSettingsPanel (TBC and other flavors on the newer settings backend), Settings.OpenToCategory forwards its argument to C_SettingsUtil.OpenSettingsPanel, which requires a numeric category ID. GUI_Minimap.lua passed the literal string "Recount", producing bad argument #1 to 'OpenSettingsPanel' (outside of expected range …) from Blizzard_Settings.lua:144. Recount now uses the real category identifier handed back by AceConfigDialog when the panel is registered.

  • The main window is no longer hidden when "Hide window when not collecting" is off. Recount:SetZoneGroupFilter — the combined zone+group filter that every zone change actually goes through — was missing a pair of parentheses: HideCollect and IsShown() or not GlobalDataCollect binds as (HideCollect and IsShown()) or (not GlobalDataCollect), so turning global data collection off hid the window even though auto-hide was never enabled. The zone-only and group-only filters had the correct grouping; all three now agree.

  • Lowering "Max Fights" now trims the fight list even when there is no data. Fights:DeleteOverflowFights cleared Recount.db2.FoughtWho inside its per-combatant loop, so with zero combatants recorded (right after a reset, for instance) the fight names survived and the fight dropdown kept offering entries with nothing behind them. The name list and the death log are per-meter, not per-combatant, and are now trimmed once.

  • Recount no longer writes into Blizzard's class-colour table. Colors:GetColor("Class", …) stamped a = 1 onto the RAID_CLASS_COLORS / CUSTOM_CLASS_COLORS entry it was reading, mutating a table shared with the default UI and every other addon. It now returns a copy.

  • Clicking a bar on retail Midnight no longer throws. Recount:CreateMidnightDetailWindow never registered its window with Recount:AddWindow, so the frame had no Above / Below links; its own OnShow calls Recount:SetWindowTop, which then indexed window.Below — nil. Every attempt to open the spell breakdown from a bar errored. The window now joins the stacking chain like every other Recount window, and SetWindowTop falls back to UIParent's frame level when a window has nothing below it instead of erroring.

  • Other players' pets no longer resolve to your own pet. Recount:FindPetUnitFromFlags tested affiliation with bit_band(COMBATLOG_OBJECT_AFFILIATION_MINE) — no mask argument. LuaBitOp returns the value unchanged for a one-argument band, so the test was MINE ~= 0, i.e. always true, and every pet resolved to the "pet" unit token. Any unit lookup for a raid or party pet (tooltips, health, threat) pointed at the player's own pet instead. Now masked against unitFlags.

  • Disabling the addon no longer errors. Recount:OnDisable called Recount:UnregisterAllEvents(), but Recount embeds AceConsole / AceComm / AceTimer and not AceEvent, so the addon object has no such method. The client events live on the Recount.events frame, which is what is unregistered now.

  • Recount:AddGraphNameEntry works. The public registration point tracker modules use to label their own graph series called GraphName.insert(k, v) on a plain lookup table, which has no insert field — every call errored. It assigns GraphName[k] = v.

  • Recount:FlagSync no longer errors on a group member with no recorded data. It indexed the combatant record without checking it exists.

  • A pet's owner is now recorded on the pet. Recount:SetOwner registered the pet on its owner but never set Owner on the pet itself — the field was only ever written as false (the "not a pet" marker), so three separate features that read it were dead: pet damage and healing were left out of the raid data sync entirely, deleting a pet left its name behind on the owner's pet list, and the "Merge Pets" option's owner inheritance never applied, so a pet stayed on the meter even when its owner's category was filtered out.

Testing

  • Offline unit-test suite (Tests/). Recount now carries a busted-compatible spec suite built on the shared WoWAPITesting harness (added as a submodule at Tests/wowapi), runnable with nothing but a Lua 5.1 interpreter: lua Tests/wowapi/run.lua. 596 specs, all passing. The suite loads Recount's real shipped files — and the real Ace3 / LibDBIcon / LibGraph / LibDropdown libraries from the sibling AddOns folders — into an offline environment, then drives the addon through its actual entry points (combat-log events, minimap clicks, widget scripts, colour-picker callbacks, addon-to-addon sync messages) rather than testing reimplementations.
    • Tests/env_recount.lua stages what the shared harness does not yet provide: a bit library faithful to LuaBitOp (bare Lua 5.1 has none), a frame system whose unknown keys resolve to nil rather than to no-op functions, named children for FauxScrollFrameTemplate / OptionsSliderTemplate / GameTooltipTemplate, a client-style debugstack (LibGraph-2.0 parses its own stack trace to find its textures and errors without it), a WoW-compatible xpcall (stock Lua 5.1 drops the extra arguments ChatThrottleLib passes through it, so no addon message ever reached the wire), C_DamageMeter with 12.0 secret-value semantics, a UIDropDownMenu that actually runs its initializer, and selectable settings-backend / colour-picker contracts. Each is written up in Tests/HARNESS_CONTRACT.md for upstreaming; the submodule was not modified.
    • Tests/coverage.lua measures exact line coverage from Lua 5.1 bytecode debug info rather than a source-text heuristic. lua Tests/run_coverage.lua reports 16980/19275 executable lines (88.1%) across every shipped file; excluding the locale data files that is ≈83% of the code. Every file is above 63%, and 24 of 32 are above 84%: WindowOrder 98%, GUI_DeathGraph 98%, GUI_Detail_Midnight 97%, GUI_Reset 96%, Fonts 96%, PowerGains 96%, GUI_TitanPanel 95%, Resurrection 95%, Tracker_Midnight 94%, CCBreakers 94%, colors 91%, Fights 91%, zonefilters 91%, Interrupts 91%, Dispels 91%, roster 90%, deletion 90%, GUI_Config 90%, Threat 89%, Widgets 87%, GUI_Minimap 86%, Recount_Modes 84%, Recount.lua 84%, GUI_Report 82%, GUI_Graph 80%, Tracker 79%, LazySync 77%, GUI_Detail 77%, GUI_CompareGraph 74%, GUI_Main 73%, GUI_Realtime 69%. What is left is drag-to-move, drag-to-resize and code that branches on anchor-resolved geometry or real font metrics — all three need a layout/interaction model in the shared harness, written up as items 11–13 of Tests/HARNESS_CONTRACT.md.
    • Specs: smoke (loads and initializes cleanly on all six flavours, with no init warnings), core (lifecycle, events, slash commands, table pool, combat state), minimap, colors, windoworder, zonefilters, fights, roster, deletion, locale, tracker and combatlog (the combat-log pipeline and the whole dispatch table), modes (every display mode select → render → hover → click), mainwindow, interactions (dropdowns, formatting, detail tables, threat polling), guiwindows (graph, compare, death graph, reset, report, realtime, Titan Panel), config, midnight (12.0 secret values), lazysync (the sync protocol, asserted on the wire) and guidepth (graph series maths — integrate, normalize, stack, thin — plus compare-graph series and realtime window pooling).
    • Tests is excluded from the packaged zip.

Packaging

  • Fixed .pkgmeta ignore patterns that were matching nothing. The BigWigs packager matches with shell case globbing, which has no recursive ** support, so "**/*.ps1" and "**/*.bat" never matched and wow-version-replication.ps1 was shipping to players. They are now "*.ps1" / "*.bat". The dot-prefixed entries (.git, .github, .vscode, .claude, .luarc.json, …) were no-ops — the packager prunes dotfiles unconditionally — and have been removed rather than left implying coverage they never provided.

Implementation

  • Recount.lua captures the Blizzard category identifier. AceConfigDialog:AddToBlizOptions("Recount Blizz", "Recount") returns (frame, categoryID); the second value is stored as Recount.BlizOptionsCategory. This is version-agnostic by construction: on older AceConfigDialog builds (and clients without C_SettingsUtil) the library deliberately overrides category.ID to the category name, so the captured value is the string "Recount" and behaviour is unchanged; on newer builds the override is skipped and the captured value is Blizzard's generated numeric ID. GUI_Minimap.lua passes Recount.BlizOptionsCategory or "Recount" to both Settings.OpenToCategory and the legacy InterfaceOptionsFrame_OpenToCategory path, so the fallback still works if the panel registration ever fails.

  • Every locale file now registers through ns.NewLocale (locales/Recount-LocaleCore.lua). AceLocale-3.0 keeps only the locale table matching GetLocale() (plus the enUS default) and discards the rest via if not L then return end, so a UI-language override had no strings to switch to. ns.NewLocale(code, isDefault, silent) records every L[key] = value into both ns.Locales[code] (always — so the override path has every language in memory) and AceLocale's table (only when it matches the client / is default — so auto-detect is unchanged). The 9 community locales (deDE, esES, esMX, frFR, ptBR, ruRU, koKR, zhCN, zhTW) were converted from the stock LibStub("AceLocale-3.0"):NewLocale(...) + if not L then return end header to local _, ns = ... / local L = ns.NewLocale("<code>"). (itIT, nlNL, thTH, filPH already used it.)

  • English is selectable too. Recount-enUS.lua now routes through ns.NewLocale("enUS", true, debug). enUS uses AceLocale's L[key] = true convention (the value is the key); for that case ns.NewLocale stores the key string itself in ns.Locales (AceLocale still receives true), so forcing English on a non-English client overlays real English text rather than the boolean true. The existing debug flag is forwarded as AceLocale's silent argument.

  • Load-time overlay (top of Recount.lua). On load, if RecountLanguage is set and not "auto", it copies ns.Locales[override] onto the shared L table before any other file captures L[...] into its own structures (mode list, option tables). RecountLanguage is a plain SavedVariable, readable at load before those captures happen. Keys missing from the chosen language keep the client/English fallback.

  • Dropdown labels. Native-script names for Latin scripts (Deutsch, Español, Français…); CJK and Cyrillic carry an English hint in parentheses (한국어 (Korean), 简体中文 (Chinese, CN), 繁體中文 (Chinese, TW), Русский (Russian)) because WoW's default Western fonts render those scripts as boxes on a non-native client; Thai is shown as "Thai" for the same reason.

Notes

  • The community translations (deDE, esES, esMX, frFR, ptBR, ruRU, koKR, zhCN, zhTW) are older WoWAce exports and are likely partial — untranslated and newer strings (Compare Graph, Midnight, etc.) fall back to English. They are now selectable, not necessarily complete.
  • Every translation now stays resident in memory rather than only the client's locale — a small, intentional cost for the override feature.

Multi-version safety

  • Pure locale-layer + options change; no version-specific code paths. Identical on Classic Era, TBC, Wrath, Cata, MoP Classic, and retail (incl. Midnight). With the dropdown left on Auto, auto-detect is byte-for-byte unchanged.
  • The minimap settings fix carries no client check of its own — it reads whatever identifier the installed AceConfigDialog registered, so it is correct on every flavor regardless of which settings backend that client runs.

Files Changed

  • locales/Recount-LocaleCore.luans.NewLocale gains silent forwarding and key-as-value handling for the = true convention
  • locales/Recount-enUS.lua — routed through ns.NewLocale("enUS", true, debug)
  • locales/Recount-{deDE,esES,esMX,frFR,ptBR,ruRU,koKR,zhCN,zhTW}.lua — converted to the ns.NewLocale header
  • Recount.lua — "Addon Language" dropdown expanded from 5 entries to all 15 (Auto + 14 languages); captures Recount.BlizOptionsCategory from AddToBlizOptions
  • GUI_Minimap.lua — right-click opens the settings panel by category identifier instead of the hard-coded name
  • zonefilters.lua — parenthesised the auto-hide condition in SetZoneGroupFilter
  • Fights.luaDeleteOverflowFights trims FoughtWho and the death log once, outside the per-combatant loop
  • colors.luaGetColor returns a copy of the client class colour instead of mutating it
  • WindowOrder.lua — removed a dead v:GetScript("OnMouseUp") statement in ScaleWindows that fetched a handler and discarded it; SetWindowTop no longer indexes a nil Below
  • GUI_Detail_Midnight.lua — the Midnight detail window registers with AddWindow
  • Recount.lua — masked the affiliation test in FindPetUnitFromFlags; OnDisable unregisters events on Recount.events
  • GUI_Main.luaAddGraphNameEntry assigns into the label table instead of calling a non-existent insert
  • LazySync.luaFlagSync skips group members with no recorded data
  • Recount.luaSetOwner records the owner on the pet, not just the pet on the owner
  • Tests/ — new offline suite (see Testing above)
  • .pkgmeta, .luacheckrc, .busted, .luarc.json — packaging and lint configuration for the suite

[v1.18.3] (2026-06-10) - Retail Midnight (12.0) live damage meter display

Fixed

  • Recount bars now populate live during combat on retail Midnight (12.0+). This completes the work scaffolded in v1.18.1, where the addon loaded cleanly but its bars stayed empty because C_DamageMeter returns secret combat values. The v1.18.1/v1.18.2 approach — poll C_DamageMeter.GetCombatSessionSourceFromType, diff against a snapshot, feed deltas through AddAmount — could never work: it did arithmetic (total - snapshot) and comparison (delta > 0) on secret values, which is exactly what taints addon execution, so it skipped every secret and applied no data. That whole snapshot/diff path is removed.

  • New approach — display passthrough, no arithmetic. The rule (verified in-game): an addon may pass a secret value straight into a display sink — FontString:SetText, StatusBar:SetValue / SetMinMaxValues, AbbreviateNumbers — and it renders live, mid-fight, with no error and no taint. What an addon must never do is operate on a secret (arithmetic, comparison, sort, percentage, total, string op) or route a secret into a shared frame (above all GameTooltip); either silently taints the running execution, and that taint then poisons whatever shared resource it next touches (the classic symptom is an unrelated Blizzard tooltip — e.g. world-map POIs — throwing "Secret values are only allowed during untainted execution"). pcall does not help and is never used to "guard" a secret: it catches the throw but leaves the taint in place, which is worse because it's silent.

Implementation

  • Tracker_Midnight.lua rewritten as a secret-safe renderer. On the DAMAGE_METER_CURRENT_SESSION_UPDATED / DAMAGE_METER_COMBAT_SESSION_UPDATED / DAMAGE_METER_RESET events (and via the periodic refresh, see below) it calls C_DamageMeter.GetCombatSessionFromType(Enum.DamageMeterSessionType.Current, metric) for the on-screen mode's metric and writes each combatSources[i] straight into the existing Recount.MainWindow.Rows[i] widgets: LeftText:SetText(src.name), RightText:SetText(AbbreviateNumbers(amount)), StatusBar:SetMinMaxValues(0, session.maxAmount) + SetValue(src.totalAmount). No value is ever computed on.

    • Uses the session API GetCombatSessionFromType (returns the whole combatSources array, pre-ranked) rather than the per-source GetCombatSessionSourceFromType the v1.18.1 scaffolding used.
    • Field secrecy honoured per the API docs: name / totalAmount / amountPerSecond and session maxAmount are secret in combat (passed through, never computed on); classFilename, specIconID, classification, isLocalPlayer, deathRecapID, sourceGUID are NeverSecret (used directly — class colour comes from classFilename).
    • Bar order is the API's pre-ranked order (we can't sort by a secret value). No Total bar and no percentages on Midnight (both need addition). #combatSources is a plain count, safe for scrollbar maths and row indexing.
    • Each Midnight row's OnEnter / OnLeave / OnClick handlers are stripped once, so a secret value can never reach the shared GameTooltip via a bar tooltip.
    • Mode → metric map (matched by English mode label): Damage Done → DamageDone, DPS → DamageDone (per-second text), Damage Taken → DamageTaken, Healing Done → HealingDone, Absorbs → Absorbs, Deaths → Deaths. Modes with no C_DamageMeter source (Friendly Fire, Healing Taken, Overhealing, DOT/HOT Uptime, Activity, Threat) render blank.
  • GUI_Main.lua — added local WOW_RETAIL / local WOW_RETAIL_MIDNIGHT upvalues (same expression as Recount.lua) and a single guarded early-return at the top of RefreshMainWindow: if WOW_RETAIL_MIDNIGHT and Recount.RefreshMainWindow_Midnight then return Recount:RefreshMainWindow_Midnight(datarefresh) end. On every other client this if is false and RefreshMainWindow runs byte-for-byte as before. Routing through the existing periodic refresh means the bars also keep updating between events. Also cached C_PetBattles as a file-local upvalue (was referenced unqualified at line 661).

  • .luarc.json — added AbbreviateNumbers, LOCALIZED_CLASS_NAMES_MALE, canaccessvalue, and a batch of pre-existing WoW globals that were triggering undefined-global warnings across the project (BackdropTemplateMixin, C_PetBattles, UnitGUID, UnitHealth, UnitHealthMax, UnitIsPlayer, UnitIsConnected, UnitIsVisible, UnitAffectingCombat, UnitCanAttack, UnitDetailedThreatSituation, UnitIsFeignDeath, GetScreenHeight, UIFrameFade, GetMouseButtonClicked, GetFramerate, GetLocale, GetNetStats, GetNumDeclensionSets, GetChannelList, GetZonePVPInfo, BNGetNumFriends, BNGetSelectedFriend, Ambiguate, DeclineName, debugstack, strsplit, ChatThrottleLib).

  • .pkgmeta — fixed the ignore entries so docs/ is actually excluded from the CurseForge zip. The packager appends /* to directory entries, so a trailing slash produced docs//* which matched nothing and silently shipped the folder. Removed the trailing slash from docs, .git, .github, .vscode; added .claude; documented the rule in a comment.

Multi-version safety

  • Everything keys off WOW_RETAIL_MIDNIGHT (WOW_PROJECT_MAINLINE and build >= 120000). Classic Era, TBC, Wrath, Cata, MoP Classic, and pre-12.0 retail (11.x) are untouched — they never load the Midnight code path and RefreshMainWindow is unchanged for them.

Known limitations on Midnight

  • During an instance encounter / Mythic+ / PvP match the values are passed through and display live; views Blizzard exposes no data for stay blank (listed above). No Total bar, no percentages, and bars are non-interactive (no click-to-detail / graph) on Midnight, because those paths need to read the underlying numbers or open a tooltip.

Files Changed

  • Tracker_Midnight.lua — rewritten as the secret-safe display-passthrough renderer; old snapshot/diff AddAmount path removed
  • GUI_Main.luaWOW_RETAIL / WOW_RETAIL_MIDNIGHT upvalues, guarded RefreshMainWindow hook, C_PetBattles local
  • .luarc.json — added the secret-value globals and a batch of pre-existing WoW globals to diagnostics.globals
  • .pkgmeta — fixed docs/ (and other directory) ignore globs so docs are excluded from the package

[v1.18.2] (2026-05-16) - Bug fixes

Bug Fixes

  • Prayer of Mending now credits the priest, not the unit being healed. Reported by a user: PoM heals were being attributed to the target rather than the healer. Root cause: Blizzard's combat log reports the buffed unit as the source of the SPELL_HEAL proc (spell ID 33110) — srcGUID and dstGUID are both the target — so Recount was attributing every PoM tick to whoever happened to be wearing the buff. Fix landed in Tracker.lua:
    • New module-local tables PoMAuraSpellId = {[41635] = true}, PoMHealSpellId = {[33110] = true}, and PoMOrigCaster = {}. The aura ID is the bouncing PoM buff; the heal ID is the heal-on-damage proc; the table is keyed by the GUID currently wearing the buff and stores {srcGUID, srcName, srcFlags} of the original priest.
    • Recount:SpellAuraApplied and Recount:SpellAuraRefresh write PoMOrigCaster[dstGUID] whenever a PoM aura lands or refreshes. The buff naturally rewrites the table entry as it bounces between targets (fresh SPELL_AURA_APPLIED events from the priest each hop).
    • Recount:SpellAuraRemoved clears PoMOrigCaster[dstGUID] when the buff falls off.
    • Recount:SpellHeal checks PoMHealSpellId[spellId] at the top of the function; if matched and PoMOrigCaster[srcGUID] exists, it swaps srcGUID, srcName, srcFlags to the recorded priest before dispatching to the existing AddHealData path. All downstream attribution (Healing Done, HealedWho, WhoHealed, overhealing, time-heal, etc.) now records the priest correctly with zero changes to the heal-tracking code itself.
    • PoM does not exist on Classic Era (Vanilla 1.x), so the lookups are a no-op there — no version guard required. Applies on TBC Classic, Wrath Classic, Cata Classic, MoP Classic, and Retail (where PoM uses the same spell IDs 41635 / 33110).

[v1.18.1] (2026-05-05) - Retail Midnight (12.0) support via C_DamageMeter

Bug Fixes

  • Window color picker fixed on Classic Era and other Classic flavors. Reported by a user: "any chance of fixing the feature to change window colours?". Two bugs in one — both fixed:

    1. The picker code in colors.lua branched on WOW_RETAIL to choose between the new (Dragonflight-era) ColorPickerFrame.Content.ColorPicker API and the old OpacitySliderFrame / ColorPickerFrame.func API. But the new picker has since rolled out to every Classic flavor too — Vanilla Classic 11507/11508, Cata Classic, Wrath Classic, MoP Classic — leaving Classic clients in a half-migrated state where swatchFunc was set on Vanilla but the alpha read still went through the long-removed OpacitySliderFrame (which is nil). Replaced every WOW_RETAIL/WOW_VANILLA_CLASSIC/WOW_PANDA_CLASSIC flag check inside the picker with a runtime test for ColorPickerFrame.Content and ColorPickerFrame.Content.ColorPicker.

    2. Per-field assignment of swatchFunc / opacityFunc / cancelFunc on ColorPickerFrame followed by :Show() is silently dropped on the new picker — the OkayButton's OnClick reads from the info table the picker captured during setup, not from the frame's fields. Confirmed in-game: pressing OK on the new picker threw attempt to call field 'swatchFunc' (a nil value) from Blizzard's ColorPickerFrame.xml:79_OnClick. Refactored Colors:EditColor to use ColorPickerFrame:SetupColorPickerAndShow({r=, g=, b=, opacity=, hasOpacity=, swatchFunc=, opacityFunc=, cancelFunc=}) on the new picker (the documented Dragonflight+ pattern); the old per-field-then-Show path is preserved verbatim for any pre-Dragonflight client that still uses it. Position-relative-to-Attach moved to after Setup since the new picker re-anchors during its own setup.

    3. The first new-picker detector tested for ColorPickerFrame.Content.ColorPicker, but that field returned nil on at least Classic Era 11507/11508 even though the new-style OkayButton (which reads swatchFunc) was active — SetupColorPickerAndShow was never called and the second OK click reproduced the same error. Switched the detector to test for ColorPickerFrame.SetupColorPickerAndShow directly (the canonical 10.2.5+ method); if that method exists, the info-table contract is in force.

    4. Alpha reads in Color_Change and Opacity_Change previously reached into ColorPickerFrame.Content.ColorPicker:GetColorAlpha(). Switched to the documented top-level ColorPickerFrame:GetColorAlpha() so the same code works regardless of inner widget naming differences across Classic flavors.

    5. Color_Cancel no longer manually rewinds the picker's internal opacity slider — Colors:SetColor(Cur_Branch, Cur_Name, PreviousColor) already re-paints the registered visual elements via UpdateColor, and the picker frame is closing anyway. Removes the last ColorPickerFrame.Content.ColorPicker reference from the file.

    Also removed three now-unused project-flag locals from colors.lua (WOW_RETAIL, WOW_VANILLA_CLASSIC, WOW_PANDA_CLASSIC) and the dangling MoP-only swatchFunc = func post-Show fallback.

  • No more ADDON_ACTION_FORBIDDEN Lua error at login on retail Midnight (12.0). A user reported 25x [ADDON_ACTION_FORBIDDEN] AddOn 'Recount' tried to call the protected function 'Frame:RegisterEvent()' traced to Recount.lua:1868 (the Recount.events:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED") call inside OnEnable). Root cause: in patch 12.0, Blizzard removed addon access to COMBAT_LOG_EVENT_UNFILTERED unconditionally. The call now fires ADDON_ACTION_FORBIDDEN in every context, including at login outside any combat / encounter / instance. Two prior research dead-ends ruled out before landing the real fix: (1) C_RestrictedActions.IsAddOnRestrictionActive(0..3) is not a useful gate — it reports "no restrictions" at login while the underlying call is still blocked. (2) pcall does not help — ADDON_ACTION_FORBIDDEN is dispatched asynchronously by Blizzard, not raised at the call site.

Retail Midnight scaffolding (data path NOT yet functional — tracked for v1.18.2)

  • New file Tracker_Midnight.lua (loaded only on Mainline TOC, never on Classic flavors) is the intended CLEU-free data path. Subscribes to DAMAGE_METER_CURRENT_SESSION_UPDATED, DAMAGE_METER_COMBAT_SESSION_UPDATED, and DAMAGE_METER_RESET. On each session update, polls C_DamageMeter.GetCombatSessionSourceFromType(Current, metric, GUID) for every group member and would compute per-(player, datatype) deltas against a snapshot table, feeding them through the existing Recount:AddAmount(combatant, datatype, amount) so the rest of Recount (modes, fight history, bars, reports, etc.) works unchanged.
  • In-game test outcome on retail Midnight (12.0.5.67314): source.totalAmount reads always come back as secret number values — not just during combat as the wiki's SecretWhenInCombat annotation suggested, but also after combat. Tainted addon code can hold a secret value but cannot do arithmetic on it ("attempt to perform arithmetic on local 'total' (a secret number value, while execution tainted by 'Recount')"). The first iteration of Tracker_Midnight.lua threw this Lua error every combat. The current iteration uses issecretvalue(total) to skip the metric and does NOT update the snapshot when the value is secret — eliminates the Lua error, but post-combat reads also come back secret in practice, so no deltas are ever applied and the Recount bars stay empty on retail Midnight.
  • Net v1.18.1 effect on retail Midnight: addon loads cleanly, no Lua errors, settings / minimap / Titan Panel all work, but combat data does not populate. v1.18.2 will pursue the workaround — likely involving Skada's SecretValueHelper.lua pattern or finding a non-tainted code path the wiki documents elsewhere.
  • Self-help warning kept in place: if C_DamageMeter.IsDamageMeterAvailable() returns false (in-game damage meter setting is OFF), Recount emits a yellow chat message at login telling the user to enable it under Options > Gameplay Enhancements > Damage Meter. (Won't matter until the secret-value problem is solved, but the warning is harmless and the setting is needed regardless.)

Implementation

  • Recount.lua top-of-file — added local WOW_RETAIL = WOW_PROJECT_ID == WOW_PROJECT_MAINLINE (matching the convention in colors.lua) and local WOW_RETAIL_MIDNIGHT = WOW_RETAIL and ((select(4, GetBuildInfo()) or 0) >= 120000). GetBuildInfo and select cached as locals per project convention.

  • Recount.lua new dispatcher Recount:RegisterCombatLogEvent — on Midnight, calls Recount:RegisterCombatLogEvent_Midnight() (defined in Tracker_Midnight.lua). On every other client, calls Recount.events:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED") exactly as before.

  • Recount.lua OnEnable — line 1868 changed from Recount.events:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED") to Recount:RegisterCombatLogEvent(). The INSTANCE_ENCOUNTER_ENGAGE_UNIT registration on the next line is unchanged — that event is not subject to the CLEU restriction.

  • Tracker_Midnight.lua (new file) — implements Recount:RegisterCombatLogEvent_Midnight(), the Recount:DAMAGE_METER_* event handlers, and the snapshot/diff polling. Uses synthesized COMBATLOG_OBJECT_* flags (Self/Party/Raid + Player + Friendly + ControlPlayer) when calling Recount:AddCombatant since real CLEU flags aren't available on this code path. Every C_DamageMeter call is wrapped in pcall, including the field access on the returned struct (the DamageMeterCombatSessionSource is marked SecretWhenInCombat).

  • Secret-value handling — confirmed in-game on retail Midnight that source.totalAmount reads return secret number values during combat. Reading them does not throw, but arithmetic on them does ("attempt to perform arithmetic on local 'total' (a secret number value, while execution tainted by 'Recount')"). Worked around by checking issecretvalue(total) before any arithmetic and skipping the update when true. The snapshot is left alone in the secret case so the next non-secret read produces the correct delta against the last public total. Belt-and-suspenders pcall wraps the subtraction itself in case issecretvalue isn't reliable on a given build. Practical effect: bars don't tick up live during combat, but populate fully once the next post-combat session update fires.

  • Recount_Mainline.toc — added Tracker_Midnight.lua immediately after Tracker.lua. The other four TOCs (Recount.toc for Classic Era, Recount_BCC.toc, Recount_Wrath.toc, Recount_Cata.toc) do NOT reference the new file, so Classic-flavor clients literally never load it. Zero risk of accidental cross-version interference.

  • .luarc.json — added GetBuildInfo and C_DamageMeter to diagnostics.globals.

Behavior

  • Pre-12.0 retail and all Classic flavors — no behavior change. CLEU registers immediately at OnEnable exactly as before. Tracker_Midnight.lua is not loaded.
  • Midnight retail (12.0+) — login Lua error fixed; addon loads cleanly. Tracker_Midnight.lua registers the DAMAGE_METER_* events but in practice every poll returns secret values that we cannot operate on, so the Recount bars stay empty until v1.18.2 lands an actual workaround for secret values.
  • No more login Lua error in any scenario, including on retail Midnight.

What's deferred to v1.18.2

  • Get retail Midnight bars actually populating. The Tracker_Midnight.lua scaffolding is in place; the missing piece is a way to either (a) extract numeric values from secret-value returns, (b) call C_DamageMeter from a non-tainted execution path, or (c) display secret values directly via FontString widgets without the addon ever doing arithmetic. Likely path: study Skada's SecretValueHelper.lua (visible in zarnivoop/skada on GitHub) which is the only public reference for a working Midnight damage meter.
  • Tracker modules on Midnight retail. Dispels, Interrupts, CC Breakers, Power Gains, Resurrections write to per-spell table-data structures (AddTableDataStats, AddTableDataSum) that don't have a 1:1 mapping to C_DamageMeter's combatSpells array. Will need module-specific shims once the basic data path works.
  • Fight rotation alignment. Recount's fight boundaries come from PLAYER_REGEN_* (still works on Midnight), but Blizzard's session reset cadence may not match exactly. If the snapshots and Recount's CurrentFightData get out of sync at fight boundaries, follow-up tuning is required.

Files Changed

  • Recount.lua — added Midnight version guard and the RegisterCombatLogEvent dispatcher; replaced the unconditional CLEU RegisterEvent call in OnEnable
  • Tracker_Midnight.luanew file, Midnight-only data collection via C_DamageMeter
  • Recount_Mainline.toc — added Tracker_Midnight.lua to the load order
  • colors.lua — replaced WOW_RETAIL / WOW_VANILLA_CLASSIC / WOW_PANDA_CLASSIC flag checks inside the color picker with a runtime test for the new ColorPickerFrame.Content.ColorPicker API; removed the three now-unused project-flag locals and the post-Show swatchFunc = func fallback
  • .luarc.json — added GetBuildInfo, C_DamageMeter, and issecretvalue to diagnostics.globals

[v1.17.5] (2026-04-29) - Fix Bars Not Appearing on First Launch

Bug Fixes

  • Main window bars now render on first launch without needing a manual mode pick. Three users reported installing Recount, entering combat, and seeing the window appear with no bars or numbers — the workaround was to right-click the title bar and pick a display mode from the dropdown. Root cause: Recount.MainWindow.GetData is the function pointer the 1-second refresh timer in GUI_Main.lua:RefreshMainWindow uses to read combat data, and it is only ever assigned inside Recount:SetMainWindowMode. That assignment normally happens during OnInitialize via the chain SetupMainWindow → LoadMainWindowData → SetMainWindowMode(MainWindowMode or 1). If any earlier call in the OnInitialize Create chain (CreateMainWindow → CreateDetailWindow → CreateGraphWindow → CreateFilterWeights → InitOrder → SetupMainWindow) threw a Lua error, SetupMainWindow never ran, GetData stayed nil, and the timer-driven refresh silently early-returned forever. The right-click trick worked because picking a mode from the dropdown invokes SetMainWindowMode directly, bypassing the broken init path.

Implementation

Two complementary additive fixes in OnInitialize and RefreshMainWindow:

  • Recount.lua OnInitialize — the six-call init chain (CreateMainWindow, CreateDetailWindow, CreateGraphWindow, CreateFilterWeights, InitOrder, SetupMainWindow) is now individually wrapped in pcall via a local safeInit helper. A failure in any one step no longer prevents the others from running, so SetupMainWindow always gets a chance to bind GetData. Failures emit a yellow |cffff8800Recount init warning:|r <step> failed: <error> line to DEFAULT_CHAT_FRAME so users can screenshot and report the actual failing call instead of just "no numbers."

  • GUI_Main.lua RefreshMainWindow — the function-entry gate if not MainWindow.GetData or not MainWindow:IsShown() then return end was split. The visibility check still early-returns. The GetData check now self-heals: if GetData is nil but Recount.MainWindowData has been populated, RefreshMainWindow calls Recount:SetMainWindowMode(Recount.db.profile.MainWindowMode or 1) to bind it before continuing. Worst case for the regression scenario: bars appear ~1 second later than they would on a healthy init (next timer tick instead of immediately). Healthy installs see no behaviour change.

Files Changed

  • Recount.luaOnInitialize Create chain wrapped in pcall via local safeInit helper
  • GUI_Main.luaRefreshMainWindow now self-heals when GetData is nil but MainWindowData is loaded

[v1.17.2] (2026-04-15) - Compare Graph Window

New Features

  • Compare Graph window — A new multi-series time-series graph window accessible from the main toolbar. Add any number of player + metric combinations as overlaid lines and compare them side by side. Supports all 14 tracked metrics: Damage Done, DPS, Friendly Fire, Damage Taken, Healing Done, Absorbs, Healing Taken, Overhealing, Deaths, DOT Uptime, HOT Uptime, Activity, Threat (TPS), and Threat (Total).

  • Fight Filter dropdown — Restrict the compare graph to a single recorded combat encounter using the Fight dropdown. Selecting a fight clamps the X axis to that window; "All Fights" shows the full session timeline.

  • Per Fight mode — A "Per Fight" checkbox renders each recorded fight as a continuous sawtooth line on a shared session timeline. Each fight rises from 0 as the metric accumulates then snaps back to 0 at fight end, with flat-zero gaps between pulls. Useful for comparing output across an entire raid session.

  • Crosshair cursor with live tooltip — Hovering over the compare graph draws a vertical hairline at the cursor and shows a ANCHOR_CURSOR tooltip with the interpolated value of every active series at that X position. Values are linearly interpolated between 1-second samples and formatted as raw, k, or m depending on magnitude. The X header shows elapsed seconds since the fight/session start.

  • Normalize checkbox — Scales each series independently to 0–100% so metrics with vastly different magnitudes (e.g. 640k threat vs 11k damage) can be visually compared on the same axis.

  • Integrate checkbox — Converts per-second rate data (DPS, TPS) into cumulative totals over time. Metrics that are inherently cumulative (Damage Done, Healing Done, Threat Total, etc.) integrate automatically regardless of this checkbox.

Improvements

  • Threat time-series units correctedTimeData["Threat"] now stores raw threat/s (matching TimeData["Damage"] units) instead of k-threat/s. After integration, Threat (Total) now correctly reaches the same magnitude shown on the bar chart (e.g. 614,000). Requires one fresh fight after updating; existing saved data will be on the old scale.

  • Compare graph dropdown fixUIDropDownMenu_Initialize was being called inside the Fight and Metric dropdown OnEnter handlers, causing dropdown lists to close as soon as the cursor moved toward them. Removed the re-initialize call from OnEnter; init functions already read live data on every open.

  • Compare graph tooltip anchor — All three Compare window dropdowns (Player, Metric, Fight) changed from ANCHOR_LEFT to ANCHOR_TOPRIGHT so the hover tooltip no longer overlaps the dropdown list that opens to the left.

Files Changed

  • GUI_CompareGraph.lua — New file; full Compare Graph window implementation
  • TrackerModules/TrackerModule_Threat.lua — TPS units corrected to raw threat/s
  • GUI_Main.lua — Compare button added to main window toolbar

[v1.17.1] (2026-03-31) - Minimap Button Toggle Option

New Features

  • Minimap button toggle in settings — A checkbox has been added to the Recount settings panel (Options > Addons > Recount) to show or hide the minimap button. The checkbox calls Recount:ToggleMinimapButton(v) and the state is persisted in db.profile.minimapButton.hide so it survives reloads and is character-agnostic.

Files Changed

  • Recount.luatoggle entry added to consoleOptions.args for the minimap button visibility checkbox

[v1.17.0] (2026-03-30) - Minimap Button & Titan Panel Integration

New Features

  • Minimap button (LibDBIcon-1.0) — A persistent minimap button is now registered via LibDataBroker-1.1 and LibDBIcon-1.0. Left-click toggles the main Recount window; Shift+Left-click toggles the configuration window; Right-click opens the WoW addon settings panel. Cross-version compatible: uses Settings.OpenToCategory on Interface 11508+ and falls back to InterfaceOptionsFrame_OpenToCategory on older clients. Button position and visibility are persisted per-profile in AceDB.

  • Titan Panel integration (LibDataBroker data source) — A data source LDB object is registered under the name Recount_Stats. Any LDB display addon (Titan Panel, Bazooka, DockingStation, etc.) picks this up automatically. The plugin shows a live per-player stat (DPS: 842) updated every 1 second via C_Timer.NewTicker. Right-clicking opens a native UIDropDownMenu to switch between six display stats (DPS, Damage Done, HPS, Healing Done, Damage Taken, Deaths) and three datasets (Overall, Last Fight, Current Fight). Hovering shows a full tooltip with all six stats at once. The selected stat is persisted in Recount.db.profile.titanPanel.stat.

Files Changed

  • GUI_Minimap.lua — New file; LibDataBroker launcher + LibDBIcon-1.0 minimap button
  • GUI_TitanPanel.lua — New file; LibDataBroker data source for Titan Panel / LDB displays
  • libs/LibDataBroker-1.1/LibDataBroker-1.1.lua — New bundled library
  • libs/LibDBIcon-1.0/LibDBIcon-1.0.lua — New bundled library
  • Recount.toc (and all 4 flavor TOCs) — Added new lib scripts and GUI files
  • Recount.luaInitMinimapButton() and InitTitanPanel() called from OnInitialize()

[v1.16.0] (2026-03-30) - CurseForge Publishing, Multi-Flavor TOCs & Code Quality

New Features

  • CurseForge project integration — Added ## X-Curse-Project-ID: 1499579 to all TOC files and curseforge-project-id: 1499579 to .pkgmeta. Version field now uses the Recount-v1.18.5 packager token so releases are automatically versioned on CurseForge upload. Interface version updated to 11508 (Season of Discovery / Classic Era).

  • Multi-flavor TOC support — Added separate TOC files for each WoW client flavor following the standard BigWigs Packager / CurseForge naming convention:

    • Recount_BCC.toc — Burning Crusade Classic (Interface 20505)
    • Recount_Wrath.toc — Wrath of the Lich King Classic (Interface 30403)
    • Recount_Cata.toc — Cataclysm Classic (Interface 40400)
    • Recount_Mainline.toc — Retail / The War Within (Interface 110207, 120001, 120000)
  • CurseForge addon description — Created docs/Curseforge_Description.html with a full formatted addon description for the CurseForge project page, covering all display modes, tracker modules, features, slash commands, and credits.

Improvements

  • Ace3 externalized — Removed bundled Ace3 library source files from the repository. Libraries are now declared as externals in .pkgmeta and fetched by the CurseForge packager at release time, keeping the repository lean and libraries up to date.

  • VersionCheck-1.0 integrated — Added VersionCheck-1.0 as a dependency in Classic Era / BCC / Wrath / Cata TOC files for out-of-date addon notification. Omitted from the Mainline TOC where it is not applicable.

Bug Fixes

  • table.getn() and table.maxn() deprecated calls removed — Replaced all occurrences with the # length operator across GUI_Config.lua, GUI_Detail.lua, GUI_Graph.lua, and GUI_Main.lua. These calls produce errors in modern Lua 5.1 environments and generate lint warnings.

  • GUI_Graph.lua nil guard — Added Filtered and Filtered[1] and guard before #Filtered[1] access to prevent nil indexing when FilterDataByTime or DataCopy returns nil. Also applied or 0 fallback to Filtered[1][#Filtered[1]] and Filtered[1][1] arithmetic to prevent nil subtraction errors.

  • GUI_Detail.lua row type mismatch — Changed Row = ... or 0 to Row = ... or {} so the LSP correctly infers Row as a table, preventing false type errors when fields like Row.Data are accessed.

  • zonefilters.lua scenario type handling — Refactored C_Scenario.IsInScenario() assignment to use a separate scenarioType variable with or "none" fallback before assigning to instanceType, resolving a type-mismatch lint error. Added ---@diagnostic disable-next-line: deprecated suppression for the GetZonePVPInfo call which has no non-deprecated replacement in Classic Era.

  • Tracker.lua duplicate table keys — Commented out duplicate spell ID entries [1463], [6229], and [31000] in the shield absorb duration table. Duplicate keys silently overwrite earlier values in Lua and generate lint warnings.

  • TrackerModules/TrackerModule_CCBreakers.lua argument count mismatch — Added extraSpellId parameter to the AddCCBreaker function signature to match the 8-argument call site (previously declared with only 7 parameters).

Internal

  • Lua Language Server configuration (.luarc.json) — Added 40+ WoW API globals to suppress false "undefined global" warnings, including all COMBATLOG_OBJECT_* filter constants, CombatLogGetCurrentEventInfo, GetSpellInfo, BNGetFriendInfo, BNSendWhisper, LE_PARTY_CATEGORY_INSTANCE, InterfaceOptionsFrame, ColorPickerFrame, C_Scenario, RecountDeathTrack, RecountTempTooltip, and WoW project version constants. Added "duplicate-set-field" to diagnostics.disable.

  • markdownlint .pkgmeta suppression — Added **/.pkgmeta to both .markdownlintignore and .vscode/settings.json markdownlint.ignore to prevent YAML comment lines (# comment) from triggering false markdown heading warnings.