Skip to content

Fix party threat border size writing to and reading the raid value - #1257

Open
dfrisone wants to merge 2 commits into
EllesmereGaming:mainfrom
dfrisone:party-threat-border-scope
Open

Fix party threat border size writing to and reading the raid value#1257
dfrisone wants to merge 2 commits into
EllesmereGaming:mainfrom
dfrisone:party-threat-border-scope

Conversation

@dfrisone

@dfrisone dfrisone commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Found while auditing the party section maps for #1256. Same root cause, different section, plus a runtime half.

Cause

Two independent things decide how a control on the Party tab behaves.

Whether you can edit it - the party tab draws a "Synced with Raid Settings" blocking overlay per section, sized from that section's onSection(key, startY, endY) y-range. A control is editable whenever the section whose header it is drawn under is unsynced.

Which value it writes - SSet consults ns._PARTY_KEY_SECTION[key] and only writes party_<key> if that section is custom.

The "Threat Borders" slider is drawn on the Health Bar row, but threatBorderSize was filed under indicators. With Health Bar unsynced and Indicators synced, the overlay allowed the edit and the write fell through to the shared raid key, so both tabs read the same value and could never diverge.

The runtime read had the same split. UpdateButton resolves the party proxy, but the two threat event handlers read db.profile directly, and the hub one resolves party buttons via ns._partyUnitToButton immediately before reading the raid value. So even a correctly stored party override was repainted at the raid thickness on every threat update.

Change

  • threatBorderSize files under healthBar, matching where its control is drawn.
  • Both threat event handlers resolve the same proxy chain as every other per-button read. The Extra Frames tracker uses the extra-aware form its sibling reads already use, since that pool holds no party buttons.

Migration

A stored party_threatBorderSize normally implies both sections were custom (Health Bar to reach the control, Indicators to route the write), so it stays live and unchanged under the new section.

The exception is the legacy showThreat -> slider migration, which writes party_threatBorderSize directly, bypassing the UI gate. Such a value could sit dormant under a synced Health Bar and would switch on here. rf_threat_border_party_section_v1 (profile scope) neutralizes only that case, so no frame changes appearance. Its predicate is self-clearing, making a re-run a true no-op.

Testing

Both files pass luac -p. Verified in game on a merge of this branch and #1256, with Health Bar unsynced and Indicators left synced:

  • Raid 2 / party 4 stay independent across the options tabs and survive a relog.
  • The aggro border on a party frame renders at the party thickness and holds it while threat updates fire, which is the half the options test cannot cover.
  • SavedVariables after logout confirm party_threatBorderSize = 4 stored, the raid key left at its default, and the migration stamped once without error (EllesmereUI._migrationErrors empty).

Update after review

The migration was rewritten (_v1 -> _v2) to fix three defects:

  • Ordering. It ran at the parent ADDON_LOADED, before ERF:OnInitialize performs the legacy party_showThreat -> party_threatBorderSize conversion. For a profile carrying that legacy key the body saw nil, returned, and stamped its flag, and the dormant 0 written moments later went live under the now-custom Health Bar section. It now performs and consumes that conversion itself.
  • One direction only. A legacy value that was live under a custom Indicators section goes dormant under the new mapping. Both directions are now handled by keying on whether the live/dormant state flips at all.
  • Copy instead of clear. Copying the raid value pinned a permanent party override, so later raid edits stopped propagating and "Re-sync Health Bar" prompted about discarding a setting the user never made. It clears the key instead, which is identical at migration time.

The two code changes are unchanged and remain as tested. The migration path needs a re-test before merge.

Migration testing (the re-test noted above is done)

Offline: a harness loads this file, registers the full chain, and runs the real RunRegisteredMigrations() over synthetic profiles covering every gating combination, including both legacy party_showThreat variants. Both-custom keeps the override, both-synced keeps it, each single-sided flip clears it, and a legacy value that stays live under both mappings is preserved rather than discarded. Zero errors across the chain.

In game, on the exact case v1 got wrong: a profile carrying party_showThreat = false with Health Bar custom and Indicators synced, threatBorderSize = 1, and the v2 flag removed so it would run.

  • Party -> Health Bar -> Threat Borders reads 1, inherited from raid. Under v1 the conversion would have landed a live 0 after the migration had stamped, and it would read 0.
  • On disk afterwards: party_showThreat consumed, party_threatBorderSize cleared, threatBorderSize still 1, migration stamped once, EllesmereUI._migrationErrors empty.

That reading of 1 is only reachable if the migration performed the legacy conversion itself before deciding, which is the ordering fix.

Dragonball Goku Super Saiyan GIF

The "Threat Borders" slider is drawn on the Health Bar row, so the party
tab's blocking overlay lets you edit it whenever Health Bar is unsynced.
threatBorderSize was filed under the Indicators party-sync section, so the
write only produced a party value when Indicators was ALSO unsynced;
otherwise it went to the shared raid key. File it under "healthBar" to
match where its control lives.

The runtime read had the same split. UpdateButton resolved the party proxy,
but the two threat event handlers read db.profile directly, and the hub one
resolves party buttons via ns._partyUnitToButton immediately before reading
the raid value, so a party override was overwritten on every threat update.
Both now resolve the same proxy chain as every other per-button read (the
Extra Frames tracker uses the extra-aware form its sibling reads already
use, since that pool holds no party buttons).

A stored party_threatBorderSize normally implies both sections were custom,
so it stays live and unchanged under the new section. The legacy
showThreat -> slider migration could also write the key directly, leaving a
value dormant under a synced Health Bar that would switch on here; a
profile migration neutralizes only that case so no frame changes look.
… not copy

Three defects in the first version.

It ran at the parent ADDON_LOADED, before ERF:OnInitialize performs the
legacy party_showThreat -> party_threatBorderSize conversion. For a profile
carrying that legacy key the body saw nil, returned, and stamped its flag,
and the dormant 0 written moments later went live under the now-custom
Health Bar section, turning party aggro borders off. Exactly the case it
existed to prevent, and it could never re-run. It now performs and consumes
that conversion itself, so ordering cannot matter.

It only handled dormant becoming live. A legacy value that was LIVE under a
custom Indicators section goes dormant under the new mapping, and those
party frames would start showing a border the user had turned off. Both
directions are handled by keying on whether the state flips at all.

It neutralized by copying the raid value, which pins a permanent party
override: later raid slider edits stop propagating to party, and "Re-sync
Health Bar" prompts about discarding a setting the user never made.
Clearing the key is identical at migration time and avoids both.

Id bumped to v2 so profiles that ran the broken v1 while testing this branch
still get the corrected pass.
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