Skip to content

fix(unitframes): stop reading the cast tint's alpha back, it can be secret - #1217

Merged
EllesmereGaming merged 1 commit into
EllesmereGaming:mainfrom
dfrisone:Opacitty-Casbar-Bug
Aug 7, 2026
Merged

fix(unitframes): stop reading the cast tint's alpha back, it can be secret#1217
EllesmereGaming merged 1 commit into
EllesmereGaming:mainfrom
dfrisone:Opacitty-Casbar-Bug

Conversation

@dfrisone

@dfrisone dfrisone commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

The report

Reported by @zoon on 8.7.5. Two symptoms that turned out to be one bug:

  • The target's cast bar detaches and sits in the centre of the screen.
  • 50x attempt to compare a secret number value (execution tainted by 'EllesmereUIUnitFrames') at ApplyCastFillOpacity.

His repro is the part that decodes it: no error at login, but it appears after flying to a field boss, killing it, and taking a return stone back. Changing the cast bar's background transparency also triggers it.

Cause

ApplyCastFillOpacity describes itself as value-blind in its own header comment, then does the one value-dependent thing in the function:

if castbar.castTintLayer and castbar.castTintLayer:GetAlpha() > 0 then

That read cannot be trusted to return a plain number. A safe secret sink is not a safe secret source. The same castbar drives _shieldedTint's alpha from the secret notInterruptible flag via SetAlphaFromBoolean, which is the sanctioned way to consume a secret. But it puts secrecy into that castbar's render state, and the alpha then reads back secret. Comparing it inside our own tainted execution throws.

That is exactly why the field boss matters. notInterruptible is secret for any non-player unit, so the boss's cast is what seeds it. Before that the tint has never been active, the alpha is still a plain 0, and 0 > 0 is harmless. Afterwards, every styling pass throws, which is why it surfaces on the next thing that re-runs one: a transparency change, or zoning back to a city.

Why the cast bar also detaches

Same bug, not a second one. The throw is at line 10202 of the per-unit styling pass. The cast bar background's anchoring lives at 10975 in that same pass:

castbarBg:ClearAllPoints()
castbarBg:SetPoint("TOP", frame, "BOTTOM", settings.castbarOffsetX or 0, settings.castbarOffsetY or 0)

Aborting at 10202 means that never runs, so castbarBg keeps its creation-time anchor instead of the configured one.

Fix

Own the state instead of interrogating the widget. castbar._castTintOn mirrors "the last alpha we wrote was above zero", is set at all four write sites, and the comparison is deleted. We write every one of those alphas ourselves, so this removes the hazard rather than trying to reason about when the read happens to be safe.

Behaviour is identical to the old readback, including the case where the flag stays true after a cast ends, which is what GetAlpha() reported before.

Notes for review

  • _castTintOn is written on castbar, which is CreateFrame("StatusBar", nil, castbarBg), one of ours. No field is written to a Blizzard frame, so the fix cannot itself introduce the class of problem it repairs. The name also matches the castbar's existing _fillOp / _fillOpApplied / _durSide fields.
  • Verified against v8.7.5 with git show v8.7.5: rather than trusting line numbers across versions: the failing line is byte-identical to current.
  • No GetAlpha() comparisons remain in the module.
  • luac -p clean.

Testing

Confirmed in game by the reporter on a build cut from this branch: the errors are gone and the cast bar stays attached across his original repro.

Safe For Work Cat GIF

…ecret

Reported on 8.7.5 by @zoon: the target cast bar detaches to the centre of the
screen, and 50x "attempt to compare a secret number value (execution tainted
by 'EllesmereUIUnitFrames')" at ApplyCastFillOpacity.

ApplyCastFillOpacity documents itself as value-blind, then does the one
value-dependent thing in the function:

    if castbar.castTintLayer and castbar.castTintLayer:GetAlpha() > 0 then

That read cannot be trusted to return a plain number. The same castbar drives
_shieldedTint's alpha from the SECRET notInterruptible flag via
SetAlphaFromBoolean, and once secrecy is in the castbar's render state an
alpha read comes back secret. Comparing it inside our own tainted execution
throws.

It explains the reporter's exact repro. Nothing is wrong at login, because the
tint has never been active and the alpha is still a plain 0. Fighting a FIELD
BOSS is what seeds it: notInterruptible is secret for any non-player unit, so
the boss's cast puts a secret into the castbar. Every later styling pass then
throws, which is why it surfaces on the next thing that re-runs one, changing
background transparency or zoning back to a city.

That also accounts for the detached cast bar, which is the same bug and not a
second one. The throw is at line 10202 of the per-unit styling pass, and the
cast bar background's anchoring lives at 10975 in the same pass. Aborting at
10202 means castbarBg never gets its ClearAllPoints + SetPoint, so it keeps
the creation-time anchor instead of the configured one.

We write every one of these alphas ourselves, so own the state in a plain
boolean and drop the comparison entirely. _castTintOn mirrors "the last alpha
we wrote was above zero" and is set at all four write sites, so behaviour is
identical to the old readback minus the secret hazard.
@EllesmereGaming
EllesmereGaming merged commit 6dca8ad into EllesmereGaming:main Aug 7, 2026
1 check passed
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.

2 participants