From 29a9e7acbf34821dfda8b63582d6b024910dbad6 Mon Sep 17 00:00:00 2001 From: Mickael Cagnion Date: Tue, 19 May 2026 10:55:11 +0200 Subject: [PATCH 1/4] Cache targeted radius jewel tooltip comparisons Avoid rebuilding radius-jewel comparison specs for repeated targeted tooltip hovers and skip limited-unique socket outputs that will not be shown. Preserve full multi-slot tooltip behavior when slot-only tooltips are disabled. Related local follow-up to PR 9746. --- spec/System/TestRadiusJewelStatDiff_spec.lua | 98 ++++++++++++++++++++ src/Classes/CompareTab.lua | 8 +- src/Classes/ItemsTab.lua | 67 +++++++++---- 3 files changed, 154 insertions(+), 19 deletions(-) diff --git a/spec/System/TestRadiusJewelStatDiff_spec.lua b/spec/System/TestRadiusJewelStatDiff_spec.lua index 0fd2fa0389..e226a99793 100644 --- a/spec/System/TestRadiusJewelStatDiff_spec.lua +++ b/spec/System/TestRadiusJewelStatDiff_spec.lua @@ -122,6 +122,35 @@ local function setupAllocatedSocket() return spec, socketNode end +local function setupAllocatedSockets(count) + local spec = build.spec + local sockets = { } + local sortedSockets = { } + for _, node in pairs(spec.nodes) do + if node.isJewelSocket then + sortedSockets[#sortedSockets + 1] = node + end + end + table.sort(sortedSockets, function(a, b) + return a.id < b.id + end) + for _, socketNode in ipairs(sortedSockets) do + if allocatePathToNode(spec, socketNode) then + sockets[#sockets + 1] = socketNode + if #sockets >= count then + break + end + end + end + if #sockets < count then + pending("Could not allocate the requested number of jewel sockets for this tree layout") + return spec, sockets + end + spec:BuildAllDependsAndPaths() + runCallback("OnFrame") + return spec, sockets +end + local function rebuildBuild() build.buildFlag = true runCallback("OnFrame") @@ -600,4 +629,73 @@ describe("TestRadiusJewelStatDiff", function() "tooltip should contain a 'Removing this item' comparison header") end) + it("AddItemTooltip avoids rebuilding unused limited-unique socket comparisons without a target slot", function() + local spec, sockets = setupAllocatedSockets(2) + + local item = newThreadOfHope() + item.limit = 1 + equipJewelInSocket(item, sockets[1]) + spec:BuildAllDependsAndPaths() + runCallback("OnFrame") + + local specClass = getmetatable(spec) + local originalBuildAllDependsAndPaths = specClass.BuildAllDependsAndPaths + local rebuilds = 0 + specClass.BuildAllDependsAndPaths = function(self, ...) + rebuilds = rebuilds + 1 + return originalBuildAllDependsAndPaths(self, ...) + end + + local ok, err = pcall(function() + local tooltip = new("Tooltip") + build.itemsTab:AddItemTooltip(tooltip, item) + end) + specClass.BuildAllDependsAndPaths = originalBuildAllDependsAndPaths + if not ok then + error(err) + end + + assert.are.equals(1, rebuilds, + "limited unique radius jewels should rebuild only the same-unique slot that will be displayed") + end) + + it("AddItemTooltip reuses targeted radius jewel comparison specs until output changes", function() + local spec, sockets = setupAllocatedSockets(2) + + local item = newCustomLeapJewel("Cached Leap") + local slot = equipJewelInSocket(item, sockets[1]) + spec:BuildAllDependsAndPaths() + runCallback("OnFrame") + + local originalSlotOnlyTooltips = main.slotOnlyTooltips + main.slotOnlyTooltips = true + local specClass = getmetatable(spec) + local originalBuildAllDependsAndPaths = specClass.BuildAllDependsAndPaths + local rebuilds = 0 + specClass.BuildAllDependsAndPaths = function(self, ...) + rebuilds = rebuilds + 1 + return originalBuildAllDependsAndPaths(self, ...) + end + + local ok, err = pcall(function() + local tooltip = new("Tooltip") + build.itemsTab:AddItemTooltip(tooltip, item, slot) + tooltip = new("Tooltip") + build.itemsTab:AddItemTooltip(tooltip, item, slot) + assert.are.equals(1, rebuilds, + "targeted radius jewel hover should reuse its cached comparison spec") + + build.outputRevision = build.outputRevision + 1 + tooltip = new("Tooltip") + build.itemsTab:AddItemTooltip(tooltip, item, slot) + assert.are.equals(2, rebuilds, + "targeted radius jewel comparison spec cache should reset when output changes") + end) + specClass.BuildAllDependsAndPaths = originalBuildAllDependsAndPaths + main.slotOnlyTooltips = originalSlotOnlyTooltips + if not ok then + error(err) + end + end) + end) diff --git a/src/Classes/CompareTab.lua b/src/Classes/CompareTab.lua index eca7261903..d7d1576e90 100644 --- a/src/Classes/CompareTab.lua +++ b/src/Classes/CompareTab.lua @@ -3777,6 +3777,7 @@ function CompareTabClass:DrawItems(vp, compareEntry, inputEvents) local hoverX, hoverY = 0, 0 local hoverW, hoverH = 0, 0 local hoverItemsTab = nil + local hoverSlotName = nil -- Track item copy button clicks local clickedCopySlot = nil @@ -3878,6 +3879,7 @@ function CompareTabClass:DrawItems(vp, compareEntry, inputEvents) if rowHoverItem then hoverItem = rowHoverItem hoverItemsTab = rowHoverItemsTab + hoverSlotName = pHover and equipSlotName or cHover and copySlotName or nil hoverX, hoverY = rowHoverX, rowHoverY hoverW, hoverH = rowHoverW, rowHoverH end @@ -3960,8 +3962,10 @@ function CompareTabClass:DrawItems(vp, compareEntry, inputEvents) SetViewport() local maxTooltipWidth = m_min(600, m_max(260, vp.width - 24)) if hoverItem and hoverItemsTab then - self.itemTooltip:Clear() - hoverItemsTab:AddItemTooltip(self.itemTooltip, hoverItem, nil, nil, maxTooltipWidth) + local hoverBuild = hoverItemsTab.build + if self.itemTooltip:CheckForUpdate(hoverItemsTab, hoverItem, hoverSlotName, maxTooltipWidth, main.slotOnlyTooltips, launch.devModeAlt, hoverBuild and hoverBuild.outputRevision) then + hoverItemsTab:AddItemTooltip(self.itemTooltip, hoverItem, hoverSlotName, nil, maxTooltipWidth) + end SetDrawLayer(nil, 100) self.itemTooltip:Draw(vp.x + hoverX, vp.y + checkboxOffset + hoverY, hoverW, hoverH, vp) SetDrawLayer(nil, 0) diff --git a/src/Classes/ItemsTab.lua b/src/Classes/ItemsTab.lua index 3ae6e046aa..f6e6aec220 100644 --- a/src/Classes/ItemsTab.lua +++ b/src/Classes/ItemsTab.lua @@ -4308,12 +4308,33 @@ local function cloneSpecForJewelComparison(spec) return specCopy end -local function buildSpecForJewelComparison(itemsTab, compareSlot, replacementItem) +local function buildSpecForJewelComparison(itemsTab, compareSlot, replacementItem, useCache) local tempItemId + local replacementItemId = replacementItem and replacementItem.id + local replacementItemIsStored = replacementItemId and itemsTab.items[replacementItemId] == replacementItem + local canCache = useCache and (not replacementItem or replacementItemIsStored) + local cacheKey + local cache + if canCache then + local outputRevision = itemsTab.build and itemsTab.build.outputRevision or 0 + cache = itemsTab.targetedJewelComparisonSpecCache + if not cache or cache.outputRevision ~= outputRevision then + cache = { + outputRevision = outputRevision, + specs = { }, + } + itemsTab.targetedJewelComparisonSpecCache = cache + end + cacheKey = tostring(compareSlot.nodeId) .. ":" .. tostring(replacementItemId or "") + if cache.specs[cacheKey] then + return cache.specs[cacheKey] + end + end + local spec = cloneSpecForJewelComparison(itemsTab.build.spec) if replacementItem then - if replacementItem.id and itemsTab.items[replacementItem.id] == replacementItem then - spec.jewels[compareSlot.nodeId] = replacementItem.id + if replacementItemIsStored then + spec.jewels[compareSlot.nodeId] = replacementItemId else tempItemId = -1 while itemsTab.items[tempItemId] do @@ -4335,6 +4356,9 @@ local function buildSpecForJewelComparison(itemsTab, compareSlot, replacementIte if not ok then error(err, 0) end + if cacheKey then + cache.specs[cacheKey] = spec + end return spec end @@ -5058,18 +5082,18 @@ function ItemsTabClass:AddItemTooltip(tooltip, item, slot, dbMode, maxWidth) tooltip:AddLine(14, colorCodes.TIP .. "Tip: Press Ctrl+D"..itemTabHint.." to disable the display of stat differences.") - local function getReplacedItemAndOutput(compareSlot) - local selItem = self.items[compareSlot.selItemId] + local function getReplacedItemAndOutput(compareSlot, selItem, useJewelComparisonSpecCache) + selItem = selItem or self.items[compareSlot.selItemId] local override = { repSlotName = compareSlot.slotName, repItem = item ~= selItem and item or nil } if compareSlot.nodeId and (itemChangesPassiveTree(selItem) or itemChangesPassiveTree(item)) then - override.spec = buildSpecForJewelComparison(self, compareSlot, override.repItem) + override.spec = buildSpecForJewelComparison(self, compareSlot, override.repItem, useJewelComparisonSpecCache) end local output = calcFunc(override) return selItem, output end - local function addCompareForSlot(compareSlot, selItem, output) + local function addCompareForSlot(compareSlot, selItem, output, useJewelComparisonSpecCache) if not selItem or not output then - selItem, output = getReplacedItemAndOutput(compareSlot) + selItem, output = getReplacedItemAndOutput(compareSlot, nil, useJewelComparisonSpecCache) end local header if item == selItem then @@ -5082,29 +5106,38 @@ function ItemsTabClass:AddItemTooltip(tooltip, item, slot, dbMode, maxWidth) -- if we have a specific slot to compare to, and the user has "Show -- tooltips only for affected slots" checked, we can just compare that - -- one slot + -- one slot. + local compareOnlySlot = type(slot) ~= "string" and slot or self.slots[slot] if main.slotOnlyTooltips and slot then - slot = type(slot) ~= "string" and slot or self.slots[slot] - if slot then addCompareForSlot(slot) end + if compareOnlySlot then addCompareForSlot(compareOnlySlot, nil, nil, true) end return end - - local slots = {} local isUnique = item.rarity == "UNIQUE" or item.rarity == "RELIC" local currentSameUniqueCount = 0 + local slotCandidates = {} for _, compareSlot in ipairs(compareSlots) do - local selItem, output = getReplacedItemAndOutput(compareSlot) + local selItem = self.items[compareSlot.selItemId] local isSameUnique = isUnique and selItem and item.name == selItem.name if isUnique and isSameUnique and item.limit then currentSameUniqueCount = currentSameUniqueCount + 1 end - table.insert(slots, - { selItem = selItem, output = output, compareSlot = compareSlot, isSameUnique = isSameUnique }) + table.insert(slotCandidates, + { selItem = selItem, compareSlot = compareSlot, isSameUnique = isSameUnique }) + end + local isLimitedUniqueAtLimit = (isUnique and item.limit and currentSameUniqueCount == item.limit) or false + + local slots = {} + for _, slotEntry in ipairs(slotCandidates) do + if not isLimitedUniqueAtLimit or slotEntry.isSameUnique then + local _, output = getReplacedItemAndOutput(slotEntry.compareSlot, slotEntry.selItem) + slotEntry.output = output + table.insert(slots, slotEntry) + end end -- limited uniques: only compare to slots with the same item if more don't fit - if currentSameUniqueCount == item.limit then + if isLimitedUniqueAtLimit then for _, slotEntry in ipairs(slots) do if slotEntry.isSameUnique then addCompareForSlot(slotEntry.compareSlot, slotEntry.selItem, slotEntry.output) From 5bf00b11ca00f02370296a32fa054bc61b0fdb91 Mon Sep 17 00:00:00 2001 From: Mickael Cagnion Date: Thu, 21 May 2026 13:53:49 +0200 Subject: [PATCH 2/4] Skip UI path rebuilds for jewel tooltip specs Tooltip comparison specs only need calc state, but radius jewel comparisons were still rebuilding passive tree UI paths for every temporary spec. Skip that path rebuild while preserving socket distance recomputation for Split Personality-style jewel scaling. --- spec/System/TestRadiusJewelStatDiff_spec.lua | 55 ++++++++++++++++++++ src/Classes/ItemsTab.lua | 5 +- src/Classes/PassiveSpec.lua | 15 ++++-- 3 files changed, 69 insertions(+), 6 deletions(-) diff --git a/spec/System/TestRadiusJewelStatDiff_spec.lua b/spec/System/TestRadiusJewelStatDiff_spec.lua index e226a99793..6faf58a72a 100644 --- a/spec/System/TestRadiusJewelStatDiff_spec.lua +++ b/spec/System/TestRadiusJewelStatDiff_spec.lua @@ -201,6 +201,15 @@ local function newPlainJewel() "Implicits: 0\n") end +local function newSplitPersonality() + return new("Item", "Rarity: UNIQUE\n" .. + "Split Personality\n" .. + "Crimson Jewel\n" .. + "Implicits: 0\n" .. + "+5 to Strength\n" .. + "This Jewel's Socket has 25% increased effect per Allocated Passive Skill between it and your Class' starting location\n") +end + -- Helper: minimal Impossible Escape item. Uses "Radius: Small" and targets -- a specific keystone. The parser populates both impossibleEscapeKeystone -- and impossibleEscapeKeystones from the "in Radius of X" mod. @@ -698,4 +707,50 @@ describe("TestRadiusJewelStatDiff", function() end end) + it("AddItemTooltip skips UI path rebuilds for temporary radius jewel specs", function() + local spec, sockets = setupAllocatedSockets(2) + + local radiusItem = newThreadOfHope() + local radiusSlot = equipJewelInSocket(radiusItem, sockets[1]) + local splitItem = newSplitPersonality() + equipJewelInSocket(splitItem, sockets[2]) + spec:BuildAllDependsAndPaths() + runCallback("OnFrame") + + assert.is_true((spec.nodes[sockets[2].id].distanceToClassStart or 0) > 0, + "Split Personality socket should have a class-start distance in the base spec") + + local originalSlotOnlyTooltips = main.slotOnlyTooltips + main.slotOnlyTooltips = true + local specClass = getmetatable(spec) + local originalBuildPathFromNode = specClass.BuildPathFromNode + local originalSetNodeDistanceToClassStart = specClass.SetNodeDistanceToClassStart + local buildPathCalls = 0 + local distanceCalls = 0 + specClass.BuildPathFromNode = function(self, ...) + buildPathCalls = buildPathCalls + 1 + return originalBuildPathFromNode(self, ...) + end + specClass.SetNodeDistanceToClassStart = function(self, ...) + distanceCalls = distanceCalls + 1 + return originalSetNodeDistanceToClassStart(self, ...) + end + + local ok, err = pcall(function() + local tooltip = new("Tooltip") + build.itemsTab:AddItemTooltip(tooltip, radiusItem, radiusSlot) + end) + specClass.BuildPathFromNode = originalBuildPathFromNode + specClass.SetNodeDistanceToClassStart = originalSetNodeDistanceToClassStart + main.slotOnlyTooltips = originalSlotOnlyTooltips + if not ok then + error(err) + end + + assert.are.equals(0, buildPathCalls, + "temporary tooltip specs should not rebuild UI node paths") + assert.is_true(distanceCalls > 0, + "temporary tooltip specs should still refresh jewel socket distances used by calc") + end) + end) diff --git a/src/Classes/ItemsTab.lua b/src/Classes/ItemsTab.lua index f6e6aec220..16ba942ab4 100644 --- a/src/Classes/ItemsTab.lua +++ b/src/Classes/ItemsTab.lua @@ -4268,7 +4268,7 @@ local function cloneSpecForJewelComparison(spec) local nodeCopy = setmetatable({ }, getmetatable(node)) for key, value in pairs(node) do if key ~= "linked" and key ~= "depends" and key ~= "intuitiveLeapLikesAffecting" - and key ~= "path" and key ~= "power" then + and key ~= "path" and key ~= "pathDist" and key ~= "distanceToClassStart" and key ~= "power" then nodeCopy[key] = value end end @@ -4348,7 +4348,8 @@ local function buildSpecForJewelComparison(itemsTab, compareSlot, replacementIte end local ok, err = xpcall(function() - spec:BuildAllDependsAndPaths() + -- Tooltip comparison specs only need calc state; node paths are UI data. + spec:BuildAllDependsAndPaths(true) end, debug.traceback) if tempItemId then itemsTab.items[tempItemId] = nil diff --git a/src/Classes/PassiveSpec.lua b/src/Classes/PassiveSpec.lua index cd16dd659f..57f1302f87 100644 --- a/src/Classes/PassiveSpec.lua +++ b/src/Classes/PassiveSpec.lua @@ -1099,7 +1099,7 @@ function PassiveSpecClass:NodesInIntuitiveLeapLikeRadius(node) end -- Rebuilds dependencies and paths for all nodes -function PassiveSpecClass:BuildAllDependsAndPaths() +function PassiveSpecClass:BuildAllDependsAndPaths(skipNodePathRebuild) local timelessJewelTypeByConqueror = { vaal = 1, karui = 2, @@ -1597,15 +1597,22 @@ function PassiveSpecClass:BuildAllDependsAndPaths() -- Reset and rebuild all node paths for id, node in pairs(self.nodes) do - node.pathDist = (node.alloc and #node.intuitiveLeapLikesAffecting == 0) and 0 or 1000 - node.path = nil + if skipNodePathRebuild then + node.pathDist = nil + node.path = nil + else + node.pathDist = (node.alloc and #node.intuitiveLeapLikesAffecting == 0) and 0 or 1000 + node.path = nil + end if node.isJewelSocket or node.expansionJewel then node.distanceToClassStart = 0 end end for id, node in pairs(self.allocNodes) do if #node.intuitiveLeapLikesAffecting == 0 or node.connectedToStart then - self:BuildPathFromNode(node) + if not skipNodePathRebuild then + self:BuildPathFromNode(node) + end if node.isJewelSocket or node.expansionJewel then self:SetNodeDistanceToClassStart(node) end From 1687753e7cb59332621ff3b4058a846cef2b05da Mon Sep 17 00:00:00 2001 From: Mickael Cagnion Date: Thu, 21 May 2026 14:36:27 +0200 Subject: [PATCH 3/4] Cache radius jewel tooltip outputs Reuse full radius jewel comparison outputs while the build output revision is unchanged. This reduces repeated Compare-tab hover work for slotOnlyTooltips=OFF without caching cloned specs or changing comparison behavior. --- spec/System/TestRadiusJewelStatDiff_spec.lua | 50 ++++++++++++++++++++ src/Classes/ItemsTab.lua | 49 ++++++++++++++++++- 2 files changed, 97 insertions(+), 2 deletions(-) diff --git a/spec/System/TestRadiusJewelStatDiff_spec.lua b/spec/System/TestRadiusJewelStatDiff_spec.lua index 6faf58a72a..47ca8792c5 100644 --- a/spec/System/TestRadiusJewelStatDiff_spec.lua +++ b/spec/System/TestRadiusJewelStatDiff_spec.lua @@ -753,4 +753,54 @@ describe("TestRadiusJewelStatDiff", function() "temporary tooltip specs should still refresh jewel socket distances used by calc") end) + it("AddItemTooltip reuses full radius jewel comparison outputs until output changes", function() + local spec, sockets = setupAllocatedSockets(2) + + local item = newCustomLeapJewel("Cached Full Leap") + local slot = equipJewelInSocket(item, sockets[1]) + spec:BuildAllDependsAndPaths() + runCallback("OnFrame") + + local originalSlotOnlyTooltips = main.slotOnlyTooltips + main.slotOnlyTooltips = false + build.itemsTab.jewelComparisonOutputCache = nil + build.itemsTab.targetedJewelComparisonSpecCache = nil + + local originalGetMiscCalculator = build.calcsTab.GetMiscCalculator + local calcCalls = 0 + build.calcsTab.GetMiscCalculator = function(self, ...) + local calcFunc, calcBase = originalGetMiscCalculator(self, ...) + return function(...) + calcCalls = calcCalls + 1 + return calcFunc(...) + end, calcBase + end + + local ok, err = pcall(function() + local tooltip = new("Tooltip") + build.itemsTab:AddItemTooltip(tooltip, item, slot) + local firstPassCalcCalls = calcCalls + assert.is_true(firstPassCalcCalls > 0, + "full radius jewel tooltip should calculate outputs on first pass") + + tooltip = new("Tooltip") + build.itemsTab:AddItemTooltip(tooltip, item, slot) + local secondPassCalcCalls = calcCalls - firstPassCalcCalls + assert.is_true(secondPassCalcCalls < firstPassCalcCalls, + "full radius jewel tooltip should reuse cached radius outputs on second pass") + + build.outputRevision = build.outputRevision + 1 + local beforeInvalidationCalcCalls = calcCalls + tooltip = new("Tooltip") + build.itemsTab:AddItemTooltip(tooltip, item, slot) + assert.is_true(calcCalls - beforeInvalidationCalcCalls > secondPassCalcCalls, + "full radius jewel output cache should reset when output changes") + end) + build.calcsTab.GetMiscCalculator = originalGetMiscCalculator + main.slotOnlyTooltips = originalSlotOnlyTooltips + if not ok then + error(err) + end + end) + end) diff --git a/src/Classes/ItemsTab.lua b/src/Classes/ItemsTab.lua index 16ba942ab4..43cedfb17f 100644 --- a/src/Classes/ItemsTab.lua +++ b/src/Classes/ItemsTab.lua @@ -4232,6 +4232,37 @@ local function itemChangesPassiveTree(item) and (item.jewelData.intuitiveLeapLike or item.jewelData.impossibleEscapeKeystone))) end +local function getStoredItemId(itemsTab, item) + if not item then + return "" + end + local itemId = item.id + if itemId and itemsTab.items[itemId] == item then + return tostring(itemId) + end +end + +local function getJewelComparisonOutputCache(itemsTab) + local outputRevision = itemsTab.build and itemsTab.build.outputRevision or 0 + local cache = itemsTab.jewelComparisonOutputCache + if not cache or cache.outputRevision ~= outputRevision then + cache = { + outputRevision = outputRevision, + outputs = { }, + } + itemsTab.jewelComparisonOutputCache = cache + end + return cache +end + +local function getJewelComparisonOutputCacheKey(itemsTab, compareSlot, replacementItem) + local replacementItemId = getStoredItemId(itemsTab, replacementItem) + if not replacementItemId then + return + end + return tostring(compareSlot.slotName) .. ":" .. tostring(compareSlot.nodeId or "") .. ":" .. tostring(compareSlot.selItemId or "") .. ":" .. replacementItemId +end + -- These jewels can replace passive nodes or disconnect allocated passives, so -- rebuild the passive tree before comparing their stats. -- Keep this list in sync with PassiveSpec's constructor, Init, and Select* @@ -5083,13 +5114,27 @@ function ItemsTabClass:AddItemTooltip(tooltip, item, slot, dbMode, maxWidth) tooltip:AddLine(14, colorCodes.TIP .. "Tip: Press Ctrl+D"..itemTabHint.." to disable the display of stat differences.") - local function getReplacedItemAndOutput(compareSlot, selItem, useJewelComparisonSpecCache) + local function getReplacedItemAndOutput(compareSlot, selItem, useJewelComparisonSpecCache, useJewelComparisonOutputCache) selItem = selItem or self.items[compareSlot.selItemId] local override = { repSlotName = compareSlot.slotName, repItem = item ~= selItem and item or nil } + local outputCache + local outputCacheKey if compareSlot.nodeId and (itemChangesPassiveTree(selItem) or itemChangesPassiveTree(item)) then + if useJewelComparisonOutputCache then + outputCacheKey = getJewelComparisonOutputCacheKey(self, compareSlot, override.repItem) + if outputCacheKey then + outputCache = getJewelComparisonOutputCache(self) + if outputCache.outputs[outputCacheKey] then + return selItem, outputCache.outputs[outputCacheKey] + end + end + end override.spec = buildSpecForJewelComparison(self, compareSlot, override.repItem, useJewelComparisonSpecCache) end local output = calcFunc(override) + if outputCacheKey then + outputCache.outputs[outputCacheKey] = output + end return selItem, output end local function addCompareForSlot(compareSlot, selItem, output, useJewelComparisonSpecCache) @@ -5131,7 +5176,7 @@ function ItemsTabClass:AddItemTooltip(tooltip, item, slot, dbMode, maxWidth) local slots = {} for _, slotEntry in ipairs(slotCandidates) do if not isLimitedUniqueAtLimit or slotEntry.isSameUnique then - local _, output = getReplacedItemAndOutput(slotEntry.compareSlot, slotEntry.selItem) + local _, output = getReplacedItemAndOutput(slotEntry.compareSlot, slotEntry.selItem, nil, true) slotEntry.output = output table.insert(slots, slotEntry) end From c52198c8b2f03b53302c1a51d65aba9d4f16f95f Mon Sep 17 00:00:00 2001 From: Mickael Cagnion Date: Mon, 17 Aug 2026 19:34:26 +0200 Subject: [PATCH 4/4] Update radius tooltip specs for explicit constructors Keep the rebased regression tests compatible with the current class constructor API. --- spec/System/TestRadiusJewelStatDiff_spec.lua | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/spec/System/TestRadiusJewelStatDiff_spec.lua b/spec/System/TestRadiusJewelStatDiff_spec.lua index 47ca8792c5..13b5d256ee 100644 --- a/spec/System/TestRadiusJewelStatDiff_spec.lua +++ b/spec/System/TestRadiusJewelStatDiff_spec.lua @@ -202,7 +202,7 @@ local function newPlainJewel() end local function newSplitPersonality() - return new("Item", "Rarity: UNIQUE\n" .. + return new("Item"):Item("Rarity: UNIQUE\n" .. "Split Personality\n" .. "Crimson Jewel\n" .. "Implicits: 0\n" .. @@ -656,7 +656,7 @@ describe("TestRadiusJewelStatDiff", function() end local ok, err = pcall(function() - local tooltip = new("Tooltip") + local tooltip = new("Tooltip"):Tooltip() build.itemsTab:AddItemTooltip(tooltip, item) end) specClass.BuildAllDependsAndPaths = originalBuildAllDependsAndPaths @@ -687,15 +687,15 @@ describe("TestRadiusJewelStatDiff", function() end local ok, err = pcall(function() - local tooltip = new("Tooltip") + local tooltip = new("Tooltip"):Tooltip() build.itemsTab:AddItemTooltip(tooltip, item, slot) - tooltip = new("Tooltip") + tooltip = new("Tooltip"):Tooltip() build.itemsTab:AddItemTooltip(tooltip, item, slot) assert.are.equals(1, rebuilds, "targeted radius jewel hover should reuse its cached comparison spec") build.outputRevision = build.outputRevision + 1 - tooltip = new("Tooltip") + tooltip = new("Tooltip"):Tooltip() build.itemsTab:AddItemTooltip(tooltip, item, slot) assert.are.equals(2, rebuilds, "targeted radius jewel comparison spec cache should reset when output changes") @@ -737,7 +737,7 @@ describe("TestRadiusJewelStatDiff", function() end local ok, err = pcall(function() - local tooltip = new("Tooltip") + local tooltip = new("Tooltip"):Tooltip() build.itemsTab:AddItemTooltip(tooltip, radiusItem, radiusSlot) end) specClass.BuildPathFromNode = originalBuildPathFromNode @@ -777,13 +777,13 @@ describe("TestRadiusJewelStatDiff", function() end local ok, err = pcall(function() - local tooltip = new("Tooltip") + local tooltip = new("Tooltip"):Tooltip() build.itemsTab:AddItemTooltip(tooltip, item, slot) local firstPassCalcCalls = calcCalls assert.is_true(firstPassCalcCalls > 0, "full radius jewel tooltip should calculate outputs on first pass") - tooltip = new("Tooltip") + tooltip = new("Tooltip"):Tooltip() build.itemsTab:AddItemTooltip(tooltip, item, slot) local secondPassCalcCalls = calcCalls - firstPassCalcCalls assert.is_true(secondPassCalcCalls < firstPassCalcCalls, @@ -791,7 +791,7 @@ describe("TestRadiusJewelStatDiff", function() build.outputRevision = build.outputRevision + 1 local beforeInvalidationCalcCalls = calcCalls - tooltip = new("Tooltip") + tooltip = new("Tooltip"):Tooltip() build.itemsTab:AddItemTooltip(tooltip, item, slot) assert.is_true(calcCalls - beforeInvalidationCalcCalls > secondPassCalcCalls, "full radius jewel output cache should reset when output changes")