Skip to content

Talent-aware dispels: "Only Dispellable by You" follows what you can actually cleanse - #254

Open
Krathe82 wants to merge 8 commits into
DanderBot:stablefrom
Krathe82:krathe/dispel-talent-aware
Open

Talent-aware dispels: "Only Dispellable by You" follows what you can actually cleanse#254
Krathe82 wants to merge 8 commits into
DanderBot:stablefrom
Krathe82:krathe/dispel-talent-aware

Conversation

@Krathe82

@Krathe82 Krathe82 commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Against stable (the live line, per v5.3.1 shipping from it).

The problem, field-reported twice in one day

The engine's dispel flag (RAID_PLAYER_DISPELLABLE) is class/spec shaped — talents are not part of it, in both directions:

  • It misses what a talent grants: a Shaman's poison dispel is Poison Cleansing Totem. The 5.3.1 gap repair covered this, but its probe (IsSpellInSpellBook) answers "should this appear in the spellbook", which per Blizzard's own documentation returns true for talent entries the player has not taken — so every shaman saw the poison overlay, talented or not.
  • It asserts what a talent gates: a Priest without Improved Purify saw diseases they cannot cure.

What this does

DISPEL_SPELLS — a per-class curated table of dispel spells and the types each grants, all eight dispel-capable classes. Cross-checked against the addons that already solve this on retail rather than authored from memory (the check found four errors in the from-memory pass, including Hunter missing entirely), then validated in game class by class — which caught two classic-era IDs that do not resolve on retail; both dropped.

Two probes, split by what each answers. Talent entries use C_SpellBook.IsSpellKnown ("Returns true if a player knows a spell"). Base entries use IsSpellInSpellBook with includeOverrides, because a base spell replaced by an upgrade is still usable and only that call follows the override chain. ⚠ The talent flag means "the spellbook probe lies about this spell" (override-linked grants), not "this spell is talent-gated" — Remove Curse and Detox are talent-gated and track correctly as base entries, verified both ways in game.

Subtract from the engine, never replace it — fail-open by design. A wrong ID or an uncurated class can only over-show (what the overlay already does today), never hide a dispel the player has. If nothing in a class list probes known, nothing is subtracted at all. Applied to the overlay and the debuff row's by-me mode together so the two displays cannot disagree. ALL mode untouched — DISPELLABLE is capability-independent by definition.

Capability changes apply live. A talent edit fires no spec event and the spellbook grant lands late (SPELLS_CHANGED), so a watcher on TRAIT_CONFIG_UPDATED + SPELLS_CHANGED (+ UNIT_PET for the Warlock, whose only dispel belongs to the Imp) re-plans when the capability set actually flips. The dispel-type maps join the overlay's tuning signature — they were in neither signature, so a plan that differed only by map was computed and silently never applied; that staleness is also what let a Dwarf's bleed repair leak onto other players' frames as containers changed hands (field-reported on 5.3.1, first commit here).

/df debug dispelcap prints each probe, its conclusion, the resolved spell name (the check that caught both bad IDs), and — the half that can regress someone — exactly what would be subtracted for this character.

Added: the by-me base token itself was wrong (29b1c8a8)

Field-caught after this PR went up: a Priest with "Only Dispellable by You" saw the poison overlay lit raid-wide. Root cause is one level below everything above — the mode's base filter rode RAID_PLAYER_DISPELLABLE. The token names point the wrong way and Blizzard's own token table (Blizzard_FrameXMLUtil/AuraUtil.lua) is explicit:

  • RAID = "harmful auras the player can dispel" (player-scoped — what by-me means)
  • RAID_PLAYER_DISPELLABLE = "auras someone in the player's raid can dispel" (raid-scoped)

Solo and in small same-class groups the two sets are identical, which is how the wrong one survived testing. The fix moves by-me (overlay + debuff row) to HARMFUL|RAID at all four sites; the gap slot's negation follows to !RAID, which also closes a second hole — the old raid-scoped negation went dark for a Dwarf's self-bleed the moment any raid-mate could dispel that type. ⚠ Not yet confirmed in game: validation requires a mixed raid (solo, the two tokens are indistinguishable by construction).

Confirmed in game (Krathe)

Shaman poison following the totem both directions without a reload; Priest disease following Improved Purify both directions; the Dwarf bleed correctly self-only; all eight classes' dump readings, including the pet-bank path with and without an Imp.

Verification

luac -p clean on every changed file, CRLF intact, _ENV globals diffed. Features/Dispel.lua and Features/Auras.lua differ from the tested tree only by locally-held debug tooling, deliberately excluded. Changelog entries included for the two confirmed user-visible fixes.

Field report (tRp, resto shaman DWARF, v5.3.1): "bleeds are now shown with dispel
overlay on other players (which are not dwarf) when being a dwarf".

The bleed repair is scoped to the player's own frame, and that scoping is correct
-- but it could never take effect on a frame that changed hands. For a dwarf
shaman the gap slot exists in BOTH plans, own frame and everyone else's, with the
same key and the same filter string ("HARMFUL|!RAID_PLAYER_DISPELLABLE"). The only
difference is includeDispelTypes: {Poison,Bleed} on self, {Poison} elsewhere. That
map was in NEITHER signature, so the struct sig matched (no rebuild) and the
tuning sig matched (no re-tune), and the container kept whatever map it was first
built with. A frame built while driving the player carried Bleed from then on --
and header children get reassigned to other units by roster churn, which is how a
self-only repair ended up lighting bleeds across the raid.

The dfDispelSelf latch shipped with that repair was necessary but not sufficient:
it forces a re-plan on the self/non-self edge, and the re-plan then produced
identical signatures, so nothing was applied. Recomputing is not applying.

Serialise the slot's includeDispelTypes into the TUNING half, sorted so the same
set always serialises identically -- an unordered pairs() walk would move the
signature at random and re-tune on every drive. Tuning rather than structural
because SetAuraSlotCandidateFilters is a live mutator, so this re-pushes in place
with no teardown and no stranded buttons.
The row had no line at all for which signature moved, which is the first thing
anyone asks when a setting appears not to apply. It is also the proof for the
dispel-type map fix: on a frame whose self-ness flips, the includeDispelTypes map
differs while the key set and the filter strings do not, so before that fix
neither signature moved and this branch was never reached.
The probe asked the wrong question. Blizzard documents the two calls as answering
different things:

  IsSpellInSpellBook -- "Returns true if a spell should be found in the spellbook.
    This function can also return true for spells that aren't known, such as
    override spells granted by an aura linked to class talents."
  IsSpellKnown       -- "Returns true if a player knows a spell."

Poison Cleansing Totem is exactly a talent-linked entry, so the spellbook probe
answered true for every shaman and the poison gap slot was added whether or not
the totem was talented. Field-reported: "it's still showing when they don't have
the totem talent. It's a half fix."

C_SpellBook.IsSpellKnown is now the primary probe. ⚠ It is NOT nonexistent -- it
is in SpellBookDocumentation.lua beside IsSpellInSpellBook, and an earlier review
removed it as a bogus fallback on the strength of "as far as I know". Restored as
the call that should have been there first, with IsSpellInSpellBook kept below it
as a last resort that over-reports rather than leaving the feature dead.

Also: the talent handler now bumps the aura layout version itself. The drives are
version-gated, so a talent change could only re-plan because ForceRefreshAllFrames
ends in InvalidateAuraLayout -- the dispel overlay depending on the Aura Designer's
refresh to notice a talent, which breaks silently the moment that call is guarded
on AD being enabled.

Plus a debug line on the overlay's TUNE decision: the row had none, and "which
signature moved" is the first question when a setting appears not to apply.
Re-talenting Poison Cleansing Totem never brought the poison gap back, while
untalenting removed it — one-way, the same shape as every other missing trigger
this week.

PLAYER_TALENT_UPDATE is not the signal. A talent edit does not necessarily raise
it, and even when something fires, the spell is not KNOWN yet at probe time: the
spellbook grant arrives afterwards with SPELLS_CHANGED. Removal appeared to work
only because the spell goes away promptly.

Watch TRAIT_CONFIG_UPDATED and SPELLS_CHANGED, for the two different lags, and
bump the layout version only when the capability actually flips — SPELLS_CHANGED
fires constantly. The flip cache exists solely for that gate; the probe itself
stays uncached so every real read is live. Shamans only.

Also logs the overlay's structural BUILD/REBUILD decision. Adding or removing the
gap slot changes the slot key set, so a talent change is structural and never
reached the tune branch's line — which is why the log was silent through exactly
the transition being tested.
It is not 'is this the player's frame' -- it is 'does the racial gap apply here',
which is that AND being a dwarf. Printed as 'self' it produced the line
'unit=player self=false', which reads as the unit test being broken rather than a
non-dwarf short-circuiting before it runs. A diagnostic that reads as a fault is
worse than no diagnostic.
The engine's dispel flag is class/spec shaped and talents are not part of it, in
BOTH directions: it misses what a talent GRANTS (a shaman's Poison Cleansing
Totem) and asserts what a talent GATES (a priest's disease cure, which needs
Improved Purify). Both field-reported.

DISPEL_SPELLS is a per-class curated list of dispel spells and the types each
grants, cross-checked against the addons that already do this on retail rather
than authored from memory -- that check found four errors in the from-memory pass
(Monk's talent types exactly backwards, Mage and Paladin each missing a spell,
Hunter absent entirely, which would have treated every hunter as dispelling
nothing) and the field validation then caught two classic-era IDs that do not
resolve on retail and were dropped. All eight dispel-capable classes are
validated against live characters, including Warlock's pet path -- the one entry
probed through the PET spellbook bank, re-checked on UNIT_PET because summoning
or swapping a pet changes what the player can cleanse with no spellbook event.

Two probes, split by what each answers:
  talent entries -> C_SpellBook.IsSpellKnown. IsSpellInSpellBook returns true for
    UNKNOWN talent entries by Blizzard's own documentation -- the original
    over-report. ⚠ The flag means "the spellbook probe LIES about this spell"
    (override-linked grants), NOT "this spell is talent-gated": Remove Curse and
    Detox are talent-gated and track correctly as base entries, verified both
    ways in game.
  base entries -> IsSpellInSpellBook with includeOverrides, because a base spell
    replaced by an upgrade is still usable and only that call follows the
    override chain.

The correction SUBTRACTS from the engine's answer rather than replacing it --
fail-open by design: a wrong ID or an uncurated class can only over-show (what
the overlay does today), never hide a dispel the player has. If nothing in a
class list probes known, nothing is subtracted at all: negatives from a
non-functioning list are worthless. Applied to the dispel overlay and the debuff
row's by-me mode together, so the two displays cannot disagree about the same
debuff. ALL mode is untouched: it rides DISPELLABLE, which is
capability-independent by definition.

/df debug dispelcap prints each probe, its conclusion, the resolved spell name
(the check that caught both bad IDs), and -- the half that can regress someone --
exactly what would be subtracted for this character.
Field-caught (priest, full raid, 2026-08-27): with "Dispellable By Me" on, the
POISON overlay lit raid-wide for a priest who cannot dispel poison. The DISPEL
debug log ruled out everything downstream -- racialSelf=false on every build
(no dwarf gap slot), e=none (empty exclude, correct for a priest knowing both
Purify halves). The filter token itself was wrong.

Blizzard's token table (Blizzard_FrameXMLUtil/AuraUtil.lua) is explicit and the
names point the wrong way:
  RAID                    = harmful auras THE PLAYER can dispel
  RAID_PLAYER_DISPELLABLE = auras SOMEONE IN THE PLAYER'S RAID can dispel

By-me shipped on the raid-scoped token, justified by an "observed player-scoped
in practice" note -- an observation made solo and in tiny test groups, the one
setting where the two sets are identical. In a real raid with poison-dispellers
present, the token behaved exactly as documented. Race, range, and the shaman
gap work were all coincidental.

Changed in lockstep, per the sibling rule this system already carries:
- overlay by-me main slot: HARMFUL|RAID
- overlay gap slot: HARMFUL|!RAID -- the old raid-scoped negation had its own
  hole: any raid-mate able to dispel a gap type (an Evoker covers bleeds) made
  the aura pass the raid-scoped token, fail the negation, and silently darken
  the dwarf's self-bleed marker whenever the roster composition changed.
- debuff row by-me record + its gap-repair sibling: same token swap. The by-me
  record and the "raid" category record are now the same set by construction,
  which is not new overlap -- player-dispellable was already a subset of
  raid-dispellable, so the raid record was already emptied by its dispel
  negation whenever both were enabled. neg() just no longer emits the identical
  component twice.

Filter strings live in the TUNING half of the split signatures on both
surfaces, so existing sessions re-tune in place on reload -- no stranded slots.

⚠ Unverified in the field yet, and the test HAS to be a mixed raid: solo, the
wrong token is indistinguishable from the right one -- which is how it shipped.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant