From a8c57c28bc221f1e4feebbb1660e82ba52f9dd88 Mon Sep 17 00:00:00 2001 From: Krathe Date: Mon, 8 Jun 2026 22:54:13 +0100 Subject: [PATCH] Sorting: stable tiebreak so group frames don't reshuffle mid-M+ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The sort comparators ended with `return false` for equal-key members (same role with Alphabetical off, or tied names), and Lua's table.sort is not stable. A roster-driven re-sort during an encounter (pet summon/dismiss, role/assist churn -> GROUP_ROSTER_UPDATE) could then land same-role players in a different order, changing the rebuilt nameList so the secure header reorders the frames — the "group frames rearrange mid-M+ after pulls" report. Add a deterministic final tiebreak by name (invariant per session, realm- qualified in the nameList) so every re-sort of the same roster yields an identical order: - Headers.lua SortMembers — the live nameList path; covers the player too, which is re-sorted through the same comparator in SORTED mode. - Sort.lua CompareUnits + CompareTestData — legacy/test path, same pattern. Trade-off: with Alphabetical off, members of the same role now order by name (previously undefined/shuffling) rather than an arbitrary order. --- Features/Sort.lua | 15 +++++++++++++-- Frames/Headers.lua | 12 +++++++++++- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/Features/Sort.lua b/Features/Sort.lua index bd4a2300..8f547ef6 100644 --- a/Features/Sort.lua +++ b/Features/Sort.lua @@ -194,7 +194,13 @@ function Sort:CompareUnits(unitA, unitB, db) return nameA < nameB end end - + + -- Deterministic final tiebreak: equal-key units need a stable order, or the + -- unstable Lua table.sort reshuffles them on each re-sort (group-frame shuffle + -- during M+ pulls). Mirrors Headers.lua SortMembers. + local tieA = UnitName(unitA) or "" + local tieB = UnitName(unitB) or "" + if tieA ~= tieB then return tieA < tieB end return false end @@ -267,7 +273,12 @@ function Sort:CompareTestData(dataA, dataB, db) return nameA < nameB end end - + + -- Deterministic final tiebreak (see CompareUnits) so test-mode sorting is + -- stable across re-sorts too. + local tieA = dataA.name or "" + local tieB = dataB.name or "" + if tieA ~= tieB then return tieA < tieB end return false end diff --git a/Frames/Headers.lua b/Frames/Headers.lua index ab501355..4a007753 100755 --- a/Frames/Headers.lua +++ b/Frames/Headers.lua @@ -4878,9 +4878,19 @@ function DF:BuildSortedNameList(members, db, selfPosition, includesPlayer) return a.name < b.name end end + -- Deterministic final tiebreak. Without one, members with equal sort keys + -- (same role, Alphabetical off) have no defined order, and Lua's table.sort + -- is NOT stable — so a roster-driven re-sort mid-encounter reshuffles + -- same-role players, making the group frames rearrange during M+ pulls. + -- Break ties by name (invariant for the session, and realm-qualified in the + -- nameList so cross-realm names don't collide) so every re-sort of the same + -- roster produces an identical order and the secure header never reorders. + if a.name ~= b.name then + return a.name < b.name + end return false end - + -- Sort non-player members table.sort(sortedMembers, SortMembers)