Skip to content

Fix radius jewel functions accumulating across mod list rebuilds - #2542

Merged
LocalIdentity merged 1 commit into
PathOfBuildingCommunity:devfrom
Rhaki:fix-radius-jewel-func-accumulation
Sep 29, 2026
Merged

LocalIdentity merged 1 commit into
PathOfBuildingCommunity:devfrom
Rhaki:fix-radius-jewel-func-accumulation

Conversation

@Rhaki

@Rhaki Rhaki commented Sep 20, 2026 •

Copy link
Copy Markdown

Description of the problem being solved:

A jewel's radius functions are appended to self.jewelData.funcList in ItemClass:BuildModListForSlotNum (src/Classes/Item.lua), and the list is never cleared — the or { } only creates the table when it is missing:

local jewelData = self.jewelData
for _, func in ipairs(modList:List(nil, "JewelFunc")) do
    jewelData.funcList = jewelData.funcList or { }
    t_insert(jewelData.funcList, func)
end

jewelData is only reset in ItemClass:BuildModList, which runs when the item is parsed. The append, however, also runs by way of ItemClass:GetActiveModListForSlotNum, which rebuilds the slot mod lists during calculation whenever the bonded rune state differs from the cached one. Every one of those rebuilds leaves another copy of the jewel's functions behind.

CalcSetup then creates one radiusJewelList entry per function, and refreshJewelStatCache appends each entry's modifiers into the same per-socket cached list. A duplicated function therefore applies the jewel's "Passive Skills in Radius also grant" stats one extra time to every node in radius.

Building the passive tree power report runs a full calculation pass per node, so it multiplies the stats several times over in one go, and the inflated values survive every later recalculation — only reimporting the tree (which reparses the items) clears them.

When this was introduced

The append itself is long-standing and is present in the released code too, but it is harmless there. On master and in v0.23.1, BuildModListForSlotNum is only ever reached from BuildModList, which clears self.jewelData immediately beforehand, so each rebuild leaves exactly one copy of the functions behind.

The second call path arrived on dev with 1bcdb48 ("Add Martial Artist Rune slots", #1991, 2026-08-31), which added ItemClass:GetActiveModListForSlotNum and has it call BuildModListsForSlots during calculation without resetting jewelData. master contains no activeBondedState at all, and the commit postdates the v0.23.1 release (2026-07-28).

The regression is therefore unreleased: it affects dev only, no released version is impacted, and this does not need backporting.

Reproduction

  1. Socket a jewel with "Passive Skills in Radius also grant" modifiers, e.g. a Time-Lost Sapphire
  2. Set the tree's node power stat to a stat those modifiers affect, and open the Power Report
  3. Wait for it to reach 100%
  4. Allocate or unallocate any passive
  5. The stats granted by the jewel are now applied several times over

The build this was found on uses a Spirit Glimmer, Time-Lost Sapphire granting notables in radius 8% increased Ignite Magnitude, 7% increased Magnitude of Ailments you inflict and Damaging Ailments deal damage 3% faster. After step 4 the Calcs tab listed each of those roughly three times per notable in radius: Ignite Magnitude Effect went from x 53.36 to x 97.46 and the burn rate modifier from 1.92 to 2.52, inflating Ignite DPS from 6,784,381 to 12,391,309 — an 83% increase from allocating a passive that grants none of those stats. Hit damage was untouched throughout, since this particular jewel only grants ailment modifiers; a jewel granting hit stats inflates those the same way.

The fix rebuilds the list from the modifiers found in the current pass instead of appending to whatever was left over. It also stops a jewel from keeping functions it no longer grants after a variant change. Only jewels reach this branch, and BuildModListsForSlots makes a single BuildModListForSlotNum call for them (the multi-call loop is for weapons and rings), so rebuilding is safe.

Steps taken to verify a working solution:

  • Ran the reproduction above before the change: the jewel's stats multiply and stay inflated until the tree is reimported.
  • Ran it after the change: Magnitude Effect and the burn rate modifier stay at their correct values through a completed Power Report followed by tree edits.
  • Confirmed against the Ignite Duration breakdown that each notable in radius contributes its 3% increased Ignite Faster exactly once, summing to the expected burn rate modifier.
  • Checked src/Classes/Item.lua at master and at the v0.23.1 tag: neither reaches the append outside BuildModList, and the reproduction does not trigger on a release build.

Link to a build that showcases this PR:

Any build with a jewel granting "Passive Skills in Radius also grant" modifiers; the trigger is the Power Report, not the build.

Screenshots:

Time-lost used in the example (10 notables selected)

image

Ignite magnitude before generating power report

image

Ignite magnitude after generating power report

image

ItemClass:BuildModListForSlotNum appends every JewelFunc it finds to
self.jewelData.funcList but never clears it, as the `or { }` only creates
the table when it is missing. jewelData is reset in BuildModList, which
runs when the item is parsed, while the append also runs by way of
GetActiveModListForSlotNum, which rebuilds the slot mod lists during
calculation whenever the bonded rune state differs from the cached one.
Every such rebuild leaves another copy of the jewel's radius functions
behind.

CalcSetup then creates one radiusJewelList entry per function, and
refreshJewelStatCache appends each entry's modifiers into the same
per-socket cached list, so a duplicated function applies the jewel's
"Passive Skills in Radius also grant" stats one extra time to every node
in radius. Building the passive tree power report runs a full calculation
pass per node, which multiplies those stats several times over, and the
inflated values then survive until the item is reparsed.

Rebuild the list from the modifiers found in the current pass instead of
appending to whatever was left over. This also stops a jewel from keeping
functions it no longer grants after a variant change.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug: calculation Numerical differences

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants