From ca67cfae72a017e3de980d0e268305e478bf9453 Mon Sep 17 00:00:00 2001 From: Krathe Date: Sun, 10 May 2026 21:23:47 +0100 Subject: [PATCH] (FrameSort) Fix frame ordering instability in Solo Shuffle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three issues caused incorrect or oscillating frame ordering when the FrameSort integration was enabled in arena / Solo Shuffle: 1. UnitsToNameList used type() == "string" to guard unit names, which lets through Midnight 12.0 secret values — opaque strings that pass the type check but crash table.concat. Added issecretvalue() guard. 2. ApplyArenaHeaderSorting called RequestSort() synchronously inside GROUP_ROSTER_UPDATE, before WoW had finished reassigning raid slots after a round transition. This passed stale unit data to FrameSort, producing the wrong nameList, which then persisted for the entire next round once combat locked it in. The call is removed; FrameSort's Blizzard provider already schedules a sort on the next frame. 3. The PLAYER_REGEN_ENABLED handler called ProcessRosterUpdate() when pendingSortingUpdate was set, which ran ApplyArenaHeaderSorting and reset nameList to INDEX — undoing FrameSort's own combat-end sort. Because WoW event handler order is non-deterministic, this caused intermittent wrong ordering between Solo Shuffle rounds. Added an IsFrameSortActive() guard so DF yields the sort to FrameSort entirely. --- Features/FrameSort.lua | 7 +++++-- Frames/Headers.lua | 31 +++++++++++++++++++++---------- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/Features/FrameSort.lua b/Features/FrameSort.lua index 17f403621..89938df65 100644 --- a/Features/FrameSort.lua +++ b/Features/FrameSort.lua @@ -12,6 +12,7 @@ local GetUnitName = GetUnitName local GetNumGroupMembers = GetNumGroupMembers local GetRaidRosterInfo = GetRaidRosterInfo local IsInRaid = IsInRaid +local issecretvalue = issecretvalue local wipe = wipe local tconcat = table.concat local tsort = table.sort @@ -56,8 +57,10 @@ local function UnitsToNameList(units) wipe(namesBuf) for i = 1, #units do local name = GetUnitName(units[i], true) - -- Guard against secret values (Midnight API restriction on some unit names) - if type(name) == "string" then + -- Skip nil and secret values (Midnight 12.0 returns opaque secret strings + -- for some unit names in instanced content; type() == "string" is not + -- sufficient — secret values pass that check but crash table.concat). + if name and not issecretvalue(name) then namesBuf[#namesBuf + 1] = name end end diff --git a/Frames/Headers.lua b/Frames/Headers.lua index 7ce6f8bb1..908a1500c 100755 --- a/Frames/Headers.lua +++ b/Frames/Headers.lua @@ -6199,11 +6199,17 @@ function DF:ApplyArenaHeaderSorting() if InCombatLockdown() then return end if not DF.arenaHeader then return end - -- FrameSort integration: yield sorting to FrameSort when active. - -- But first clear any stale nameList from the previous Solo Shuffle round — - -- if we just return, SecureGroupHeaderTemplate keeps filtering by the old - -- teammate's name, hiding the new one. Clearing to INDEX shows all current - -- units immediately, then RequestSort lets FrameSort rebuild nameList. + -- FrameSort integration: yield sorting entirely to FrameSort when active. + -- Clear any stale nameList from the previous Solo Shuffle round so + -- SecureGroupHeaderTemplate stops filtering by an old teammate's name and + -- shows all current units in INDEX order immediately. + -- Do NOT call RequestSort() here — that would fire synchronously in the same + -- event frame as GROUP_ROSTER_UPDATE, before WoW has finished assigning units + -- to raid slots. The stale data would produce the wrong nameList, which then + -- persists for the whole round once combat starts. + -- FrameSort's Blizzard provider already schedules a sort on every + -- GROUP_ROSTER_UPDATE; it calls our Sort() via RequestSelfManagedProvidersSort() + -- on the next OnUpdate frame, by which time the unit API is fully settled. if DF:IsFrameSortActive() then DF.arenaHeader:SetAttribute("nameList", nil) DF.arenaHeader:SetAttribute("sortMethod", "INDEX") @@ -6212,9 +6218,6 @@ function DF:ApplyArenaHeaderSorting() DF.arenaHeader:SetAttribute("groupFilter", nil) DF.arenaHeader:SetAttribute("roleFilter", nil) DF.arenaHeader:SetAttribute("strictFiltering", nil) - if DF.FrameSort and DF.FrameSort.RequestSort then - DF.FrameSort:RequestSort() - end return end @@ -7775,8 +7778,16 @@ headerEventFrame:SetScript("OnEvent", function(self, event, arg1, arg2) if DF.debugHeaders then print("|cFF00FF00[DF Headers]|r Applying queued sorting update after combat") end - -- Re-run ProcessRosterUpdate which will now apply sorting - DF:ProcessRosterUpdate() + -- When FrameSort integration is active, FrameSort already fires its own + -- combat-end sort synchronously on PLAYER_REGEN_ENABLED (via its + -- RunWhenCombatEnds callback queue). Calling ProcessRosterUpdate here + -- would run ApplyArenaHeaderSorting → clear nameList to INDEX, undoing + -- FrameSort's work. The event handler order between DF and FrameSort is + -- non-deterministic, so this caused intermittent wrong ordering between + -- Solo Shuffle rounds. Let FrameSort own the sort entirely. + if not DF:IsFrameSortActive() then + DF:ProcessRosterUpdate() + end -- BUG #4 FIX: Force refresh all frames after arena combat-end recovery. -- Health bars may show stale data because UNIT_HEALTH events were dropped -- while the arena header was hidden / events disabled.