Skip to content

fix(cdm): a spell with no layout position no longer sorts to the front - #1211

Merged
EllesmereGaming merged 2 commits into
EllesmereGaming:mainfrom
dfrisone:cobra-shot-cdm-jump
Aug 7, 2026
Merged

fix(cdm): a spell with no layout position no longer sorts to the front#1211
EllesmereGaming merged 2 commits into
EllesmereGaming:mainfrom
dfrisone:cobra-shot-cdm-jump

Conversation

@dfrisone

@dfrisone dfrisone commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

The report

Reported by @Dexal on 8.7.5. On a BM Hunter ST preset, Cobra Shot intermittently jumps from its correct last position to first in EUI's tracked list, while Blizzard's own CDM order stays correct. No reliable repro, but it clusters around three things: switching CDM preset, updating the addon, and logging in.

Cause

Those three triggers are one trigger. Each makes Blizzard re-lay out the cooldown viewer, so frame.layoutIndex is briefly absent. The spillover interpolation substituted for that:

local L = frame.layoutIndex or 0

0 is below every real layoutIndex. With L = 0 no anchor satisfies li < L, so there is no predecessor, baseIdx falls to minAnchorIdx - 1, and the frame is placed before every anchor. A tracked spell jumps to first place, positioned from a value we never had.

Cobra Shot is the one that shows it because it is the spillover on that preset: it has no entry in the bar's stored order, so it is the only frame that takes the interpolation path at all. The AOE preset does not track it, which is why that preset never showed the bug.

This also explains the two things that made the report look strange. It is intermittent because it depends on the sort landing inside the relayout window, and Blizzard's order is never wrong because Blizzard's data was fine, we just read it too early and then invented a position.

Fix

Do not derive a position from a layoutIndex we do not have.

When it is missing, hold the frame's last known sort position, so a transient read produces no visible movement at all, and the following pass places it properly once the layout exists. A frame with no remembered position falls to 99999, matching the existing "unknown sorts last" convention rather than the old "unknown sorts first" accident.

Scoped tightly: injected presets (cooldownID nil) and the no-anchors case both take the existing 99999 path, unchanged.

Second commit

Self-review turned up the same substitution twelve lines above, in the anchor collection:

blizzLIs[#blizzKeys] = frame.layoutIndex or 0

An anchor recorded at effective index 0 is a valid predecessor for every spillover, and during a full relayout, when they all collapse to 0, the interpolation degenerates to "insert after whichever anchor was enumerated first". Same root cause, same window, quieter symptom. An anchor we cannot place is not an anchor, so it is dropped; that narrows the anchor set, and an empty set already has defined behaviour.

Also renames fc._lastSortOrder to fc.lastSortOrder, since every other field on that cache is unprefixed.

Known remaining instances

layoutIndex or 0 also appears at the sort tiebreaker and in the buff-path entry ordering (four sites). Same pattern, but they are in the buff path, none are reported, and each needs its own answer to what "unknown" should mean there. Left alone deliberately rather than changing buff ordering inside a cooldown fix. Happy to take them in a follow-up.

Testing

Confirmed in game. Both halves were checked:

  • Steady state, where the anchor change is a no-op: EUI's tracked order matches Blizzard's, unchanged from before.
  • The relayout window, driven from Blizzard's own Cooldown Settings panel (toggling tracked spells and switching presets each fire RefreshLayout on the viewer, so the window can be hit repeatedly rather than waiting for a login): nothing jumps to first, and nothing falls to the tail either, which was the failure mode the narrowed anchor set could have introduced.
safe for work puppy GIF

Reported by @Dexal on 8.7.5: on a BM Hunter ST preset, Cobra Shot
intermittently jumps from its correct last position to FIRST in EUI's tracked
list, while Blizzard's own CDM order stays correct. No reliable repro, but it
clusters around switching CDM preset, updating the addon, and logging in.

Those three share one thing: Blizzard re-lays out the cooldown viewer, so
frame.layoutIndex is briefly absent. The spillover interpolation stood in for
that with

    local L = frame.layoutIndex or 0

and 0 is below every real layoutIndex. With L = 0 no anchor satisfies
`li < L`, so there is no predecessor, baseIdx falls to minAnchorIdx - 1, and
the frame is placed before every anchor. A tracked spell silently jumps to
first place, from a value we never actually had.

Cobra Shot is the one that shows it because it is the spillover on that
preset: it has no entry in the bar's stored order, so it is the frame that
takes the interpolation path at all.

Do not derive a position from a layoutIndex we do not have. When it is
missing, hold the frame's last known sort position instead, so a transient
read produces no visible movement at all; the next pass, once the layout
exists, places it properly. A frame with no remembered position falls to
99999, matching the existing "unknown sorts last" convention rather than the
old "unknown sorts first" accident.

Scoped tightly: injected presets (cooldownID nil) and the no-anchors case both
still take the existing 99999 path, unchanged.
Self-review of the previous commit found the same substitution a few lines
above it, in the anchor collection:

    blizzLIs[#blizzKeys] = frame.layoutIndex or 0

An anchor recorded at effective index 0 sits below every true layoutIndex, so
it is a valid predecessor for every spillover. During a full relayout, when
every anchor collapses to 0, the interpolation degenerates to "insert after
whichever anchor happened to be first" -- a quieter version of exactly the bug
the previous commit fixed, and in the same window.

An anchor we cannot place is not an anchor. Dropping it narrows the anchor set,
and an empty set already has defined behaviour: spillovers fall to the tail.

Also renamed fc._lastSortOrder to fc.lastSortOrder. Every other field on that
cache is unprefixed and the odd one out reads like it means something.

Kept as its own commit because the previous one is the in-game confirmed
change and this one is not.
@EllesmereGaming
EllesmereGaming merged commit 70a8907 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