Make ClassicAPI the source of truth for non-player aura timing

ValidateAura resolved remote aura timers from libdebuff's allBuffAuras and then
from AllCasterAuraTracking via FindAllCasterAuraByName. Both existed for one
reason: vanilla cannot report a timer for an aura on a unit other than the
player. ClassicAPI's Aura::Source cache does, reconstructing duration and
expirationTime for any unit from the observed SMSG_SPELL_GO -- caster-modified,
so talent extensions and combo-point finisher scaling are already applied, and
Carnage's roll-gated Rip/Rake refresh is handled in the DLL rather than mirrored
in Lua.

Replaced that ~70-line cascade with ResolveUnitAuraViaClassicAPI, a positional
C_UnitAuras.UnitAura scan over the relevant range. Matching stays ours -- by
spellID when the conditional supplied a number, else by lowercased name, since
C_UnitAuras' own by-name lookup is case-sensitive and exact and would miss
[debuff:thunder_clap]. It runs after the existing scans and overrides them, which
is what "source of truth" means here.

Also drops the libdebuff UnitBuff timeleft bug the old comments worked around
(timeleft reporting ~1000s), since nothing reads it on this path any more.

Presence and stacks from C_UnitAuras are reliable. Timing is best-effort by
design: an aura cast before login, or one refreshed at max stacks, yields no
expirationTime, and remaining is left nil so the caller keeps its found-and-0
default. A permanent aura (duration 0) reports -1, matching the existing
convention.

Still on the old machinery and not yet migrated: the non-player overflow-slot
buff path (AllCasterAuraTracking, guarded by a debuff-slot check) and the player
branches. AllCasterAuraTracking still has 56 references across 5 files, mostly
OverflowBuffFrame and Conditionals.
This commit is contained in:
Brues
2026-09-10 14:40:16 -05:00
parent daaab2fd11
commit 2a562e0550
2 changed files with 123 additions and 72 deletions
+57 -9
View File
@@ -25,10 +25,23 @@ API references are line numbers into `C:\Git\ClassicAPI\docs\API.md`.
`applications` (stacks), `duration`.
- **Unlocks:** `[dispellable]` / `[curse]` / `[magic]` target conditionals and
spellID-based (rank/locale-proof) aura matching.
- **Caveat:** `expirationTime` is only populated for `unit=="player"`; it's `0`
for target/focus (vanilla server limitation). Target debuff *timers* still
need the existing libdebuff tracking — only presence/stacks/school/spellId
are reliable cross-unit.
- **~~Caveat: `expirationTime` is player-only~~ — NO LONGER TRUE.** This note said
target/focus `expirationTime` was always `0`, so debuff *timers* had to stay on
libdebuff. ClassicAPI has since added the `Aura::Source` cache: for a non-player
unit `expirationTime` is reconstructed from the observed `SMSG_SPELL_GO`, and
`duration` is the **caster-modified** value (talent extensions like Improved
Shadow Word: Pain included). It also handles combo-point finisher scaling
automatically (Rupture at 4 CP reads 14s, no registration) and ships Carnage's
roll-gated Rip/Rake refresh in the DLL (`src/turtle/Carnage.cpp`, exposed as
`RegisterAuraDurationModifierByTrigger`).
- **Real remaining caveats** (all best-effort, from `docs/API.md`):
- Only auras observed *after login* carry caster/timing; older ones report
`expirationTime` 0 and `sourceUnit` nil.
- Max-stack refresh is a blind spot: re-applying at max stacks (Shadow Weaving
5→5) emits no client-visible change, so the entry elapses and evicts.
- Out-of-range group members are spell-ID only, with `applications` always 1.
- **Consequence:** most of libdebuff is now redundant — see
"libdebuff retirement" below.
- **DONE (slice 1 — dispel-type conditionals):** added `ClassicAPI.lua`
detection module (`CleveRoids.ClassicAPI`, mirrors NampowerAPI's
@@ -150,11 +163,13 @@ Findings from the full Conditionals.lua audit. These look like ClassicAPI
candidates but are **worse** than the current implementation — recorded so we
don't re-investigate.
- **Auras / `ValidateAura`** — `C_UnitAuras.expirationTime` is **player-only**
(`0` for every other unit). It cannot replace the nampower `GetUnitField`
batch read or the remote-duration tracking (libdebuff / overflow slots). The
dispel-*type* path already uses `C_UnitAuras` (that's the one thing it's good
for); aura *timing/stacks* on non-player units must stay on nampower/libdebuff.
- ~~**Auras / `ValidateAura`** — `C_UnitAuras.expirationTime` is player-only~~ —
**STALE, do not trust this entry.** It was written before the `Aura::Source`
cache landed. Non-player `expirationTime`/`duration` now work (caster-modified,
talent extensions included), so this is no longer a reason to keep libdebuff's
remote-duration tracking. See Tier 1 §1 and "libdebuff retirement" for the
current picture and the real caveats. The nampower `GetUnitField` batch read is
a separate question and has not been re-examined.
- **`GetCurrentShapeshiftIndex` form loop / `[stance]`·`[form]`** —
`GetShapeshiftFormID()` returns the **DBC form id** (Cat=1, Bear=5,
Shadowform=28…), NOT the 1-based **bar index** these conditionals compare
@@ -183,6 +198,39 @@ don't re-investigate.
added `[focus]`/`[nofocus]`. `/focus` is provided by ClassicAPI's companion
addon.
## libdebuff retirement
libdebuff exists because vanilla cannot report debuff durations on units other
than the player. ClassicAPI's `Aura::Source` cache now does exactly that, so most
of the library is redundant. This is a staged replacement, not a delete: measure
first, then remove per group.
Current footprint: 28 public `lib:` methods, ~726 internal references in
`Utility.lua`, consumers in 8 files (`Conditionals.lua` 45, `Compatibility/pfUI.lua`
39, `Core.lua` 9). Note pfUI did **not** delete its own libdebuff — v9.0.25 still
ships ~1695 lines of it, re-based on `C_UnitAuras.GetAuraDataByIndex` /
`GetAuraDataBySpellName`. "Re-base on C_UnitAuras", not "remove", is the precedent.
**Group A — replaceable by `C_UnitAuras` (do these first):**
`GetDuration`, `GetDebuffCaster`, `IsOurDebuff`, `UnitBuff`/`UnitDebuff`,
`FindPlayerDebuff`/`FindPlayerBuff`, `GetAllDebuffsOnTarget`, `GetCachedIcon`,
`ApplyCarnageRefresh`, and the Dark Harvest trio (`ApplyDarkHarvestStart`/`End`,
`GetDarkHarvestReduction`, `GetTimeRemainingWithDarkHarvest`) — ClassicAPI ships
Carnage refresh and Dark Harvest tick compression in the DLL.
**Group B — no `C_UnitAuras` equivalent, keep:**
`ShouldApplyDebuffRank`, `DidSpellFail`, `WasSpellReflected`, `DidTargetEvade`,
`ProcessMissReason`, `IsPersonalDebuff`, `GetSpellRank`/`GetSpellBaseName`,
`HasPendingCast`. These are miss/rank/learning logic, not aura state.
**Gate before removing Group A:** confirm parity in-game on one spell where
ClassicAPI does the hard part — Rip under Carnage. Compare
`CleveRoids.libdebuff:GetDuration(spellID)` against
`C_UnitAuras.GetUnitAuraBySpellID(unit, spellID).duration` and the derived
remaining (`expirationTime - GetTime()`), on a target you have debuffed. If those
agree across a Carnage proc, Group A can go. Watch the documented best-effort
gaps: an aura cast before you logged in, and refresh-at-max-stacks.
## Marginal / optional follow-ups
- **`[swimming]`** → `IsSwimming()` would drop the nampower-2.36 version gate +