From b7a417a6f085ec0e084537134a76121cf473b9d7 Mon Sep 17 00:00:00 2001 From: Mickael Cagnion Date: Tue, 19 May 2026 10:55:11 +0200 Subject: [PATCH 1/8] 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 4c07ab328805a7a95e1e3660f78d09baea31528b Mon Sep 17 00:00:00 2001 From: Mickael Cagnion Date: Thu, 21 May 2026 13:53:49 +0200 Subject: [PATCH 2/8] 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 d10d162216cdaf40fb03af19833fb4bb6ba19976 Mon Sep 17 00:00:00 2001 From: Mickael Cagnion Date: Thu, 21 May 2026 14:36:27 +0200 Subject: [PATCH 3/8] 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 5c208e10042d263d88596539a642f23fa2c4ee37 Mon Sep 17 00:00:00 2001 From: Mickael Cagnion Date: Mon, 17 Aug 2026 19:34:26 +0200 Subject: [PATCH 4/8] 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") From a50712a43d536139dbe477ee2f18cc1e6c3d9872 Mon Sep 17 00:00:00 2001 From: Mickael Cagnion Date: Fri, 21 Aug 2026 22:55:00 +0200 Subject: [PATCH 5/8] Fix Compare item tooltip cache invalidation Include Shift in the tooltip update key and clear cached update parameters before drawing the Equip comparison tooltip. --- src/Classes/CompareTab.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Classes/CompareTab.lua b/src/Classes/CompareTab.lua index d7d1576e90..8f2667d586 100644 --- a/src/Classes/CompareTab.lua +++ b/src/Classes/CompareTab.lua @@ -3963,7 +3963,7 @@ function CompareTabClass:DrawItems(vp, compareEntry, inputEvents) local maxTooltipWidth = m_min(600, m_max(260, vp.width - 24)) if hoverItem and hoverItemsTab then local hoverBuild = hoverItemsTab.build - if self.itemTooltip:CheckForUpdate(hoverItemsTab, hoverItem, hoverSlotName, maxTooltipWidth, main.slotOnlyTooltips, launch.devModeAlt, hoverBuild and hoverBuild.outputRevision) then + if self.itemTooltip:CheckForUpdate(hoverItemsTab, hoverItem, hoverSlotName, maxTooltipWidth, main.slotOnlyTooltips, launch.devModeAlt, IsKeyDown("SHIFT"), hoverBuild and hoverBuild.outputRevision) then hoverItemsTab:AddItemTooltip(self.itemTooltip, hoverItem, hoverSlotName, nil, maxTooltipWidth) end SetDrawLayer(nil, 100) @@ -3973,7 +3973,7 @@ function CompareTabClass:DrawItems(vp, compareEntry, inputEvents) -- Draw stat comparison tooltip when hovering Equip button if hoverEquipItem and hoverEquipSlotName and not hoverItem then - self.itemTooltip:Clear() + self.itemTooltip:Clear(true) self.itemTooltip.maxWidth = maxTooltipWidth local calcFunc, calcBase = self.calcs.getMiscCalculator(self.primaryBuild) if calcFunc then From a608d3bbe909a47cb7d3b3a174de8a573c26d022 Mon Sep 17 00:00:00 2001 From: Mickael Cagnion Date: Fri, 21 Aug 2026 23:48:32 +0200 Subject: [PATCH 6/8] Verify cached radius tooltip content Compare the first-pass tooltip text with the warm-cache result so the output-cache test covers semantic equivalence as well as reduced calculator calls. --- spec/System/TestRadiusJewelStatDiff_spec.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/spec/System/TestRadiusJewelStatDiff_spec.lua b/spec/System/TestRadiusJewelStatDiff_spec.lua index 13b5d256ee..72c8e2dd25 100644 --- a/spec/System/TestRadiusJewelStatDiff_spec.lua +++ b/spec/System/TestRadiusJewelStatDiff_spec.lua @@ -780,11 +780,14 @@ describe("TestRadiusJewelStatDiff", function() local tooltip = new("Tooltip"):Tooltip() build.itemsTab:AddItemTooltip(tooltip, item, slot) local firstPassCalcCalls = calcCalls + local firstPassTooltipText = tooltipText(tooltip) assert.is_true(firstPassCalcCalls > 0, "full radius jewel tooltip should calculate outputs on first pass") tooltip = new("Tooltip"):Tooltip() build.itemsTab:AddItemTooltip(tooltip, item, slot) + assert.are.equals(firstPassTooltipText, tooltipText(tooltip), + "cached radius outputs should preserve tooltip content") local secondPassCalcCalls = calcCalls - firstPassCalcCalls assert.is_true(secondPassCalcCalls < firstPassCalcCalls, "full radius jewel tooltip should reuse cached radius outputs on second pass") From cfaf06fc5bcf3d21b9c147279fc3d82b93d05f27 Mon Sep 17 00:00:00 2001 From: Mickael Cagnion Date: Sat, 22 Aug 2026 00:15:50 +0200 Subject: [PATCH 7/8] Clarify radius tooltip cache contracts Use the established slot-only vocabulary and document the cache-key sentinels and selective passive-path rebuild contract found during cumulative naming review. --- spec/System/TestRadiusJewelStatDiff_spec.lua | 8 +++---- src/Classes/ItemsTab.lua | 22 ++++++++++++-------- src/Classes/PassiveSpec.lua | 5 ++++- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/spec/System/TestRadiusJewelStatDiff_spec.lua b/spec/System/TestRadiusJewelStatDiff_spec.lua index 72c8e2dd25..3a6af166ae 100644 --- a/spec/System/TestRadiusJewelStatDiff_spec.lua +++ b/spec/System/TestRadiusJewelStatDiff_spec.lua @@ -668,7 +668,7 @@ describe("TestRadiusJewelStatDiff", function() "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() + it("AddItemTooltip reuses slot-only radius jewel comparison specs until output changes", function() local spec, sockets = setupAllocatedSockets(2) local item = newCustomLeapJewel("Cached Leap") @@ -692,13 +692,13 @@ describe("TestRadiusJewelStatDiff", function() 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") + "slot-only radius jewel hover should reuse its cached comparison spec") build.outputRevision = build.outputRevision + 1 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") + "slot-only radius jewel comparison spec cache should reset when output changes") end) specClass.BuildAllDependsAndPaths = originalBuildAllDependsAndPaths main.slotOnlyTooltips = originalSlotOnlyTooltips @@ -764,7 +764,7 @@ describe("TestRadiusJewelStatDiff", function() local originalSlotOnlyTooltips = main.slotOnlyTooltips main.slotOnlyTooltips = false build.itemsTab.jewelComparisonOutputCache = nil - build.itemsTab.targetedJewelComparisonSpecCache = nil + build.itemsTab.slotOnlyJewelComparisonSpecCache = nil local originalGetMiscCalculator = build.calcsTab.GetMiscCalculator local calcCalls = 0 diff --git a/src/Classes/ItemsTab.lua b/src/Classes/ItemsTab.lua index 43cedfb17f..8e812428b1 100644 --- a/src/Classes/ItemsTab.lua +++ b/src/Classes/ItemsTab.lua @@ -4232,7 +4232,9 @@ local function itemChangesPassiveTree(item) and (item.jewelData.intuitiveLeapLike or item.jewelData.impossibleEscapeKeystone))) end -local function getStoredItemId(itemsTab, item) +-- An empty key part represents no replacement; replacement items without a stored ID +-- return nil so their output is not cached. +local function getJewelComparisonItemCacheKeyPart(itemsTab, item) if not item then return "" end @@ -4256,11 +4258,11 @@ local function getJewelComparisonOutputCache(itemsTab) end local function getJewelComparisonOutputCacheKey(itemsTab, compareSlot, replacementItem) - local replacementItemId = getStoredItemId(itemsTab, replacementItem) - if not replacementItemId then + local replacementItemKeyPart = getJewelComparisonItemCacheKeyPart(itemsTab, replacementItem) + if not replacementItemKeyPart then return end - return tostring(compareSlot.slotName) .. ":" .. tostring(compareSlot.nodeId or "") .. ":" .. tostring(compareSlot.selItemId or "") .. ":" .. replacementItemId + return tostring(compareSlot.slotName) .. ":" .. tostring(compareSlot.nodeId or "") .. ":" .. tostring(compareSlot.selItemId or "") .. ":" .. replacementItemKeyPart end -- These jewels can replace passive nodes or disconnect allocated passives, so @@ -4339,22 +4341,22 @@ local function cloneSpecForJewelComparison(spec) return specCopy end -local function buildSpecForJewelComparison(itemsTab, compareSlot, replacementItem, useCache) +local function buildSpecForJewelComparison(itemsTab, compareSlot, replacementItem, useJewelComparisonSpecCache) 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 canCache = useJewelComparisonSpecCache 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 + cache = itemsTab.slotOnlyJewelComparisonSpecCache if not cache or cache.outputRevision ~= outputRevision then cache = { outputRevision = outputRevision, specs = { }, } - itemsTab.targetedJewelComparisonSpecCache = cache + itemsTab.slotOnlyJewelComparisonSpecCache = cache end cacheKey = tostring(compareSlot.nodeId) .. ":" .. tostring(replacementItemId or "") if cache.specs[cacheKey] then @@ -4379,7 +4381,9 @@ local function buildSpecForJewelComparison(itemsTab, compareSlot, replacementIte end local ok, err = xpcall(function() - -- Tooltip comparison specs only need calc state; node paths are UI data. + -- These temporary specs only feed the misc calculator, which does not read node.path/pathDist. + -- Jewel socket distances are still rebuilt for jewel scaling; + -- Split Personality highlight paths are also refreshed. spec:BuildAllDependsAndPaths(true) end, debug.traceback) if tempItemId then diff --git a/src/Classes/PassiveSpec.lua b/src/Classes/PassiveSpec.lua index 57f1302f87..3984e1c57c 100644 --- a/src/Classes/PassiveSpec.lua +++ b/src/Classes/PassiveSpec.lua @@ -1098,7 +1098,10 @@ function PassiveSpecClass:NodesInIntuitiveLeapLikeRadius(node) return result end --- Rebuilds dependencies and paths for all nodes +-- Rebuilds dependencies and calculation distances for all nodes. +-- When node paths are skipped, node.path/pathDist remain unset while jewel socket +-- distanceToClassStart values and Split Personality paths are still refreshed. +---@param skipNodePathRebuild? boolean function PassiveSpecClass:BuildAllDependsAndPaths(skipNodePathRebuild) local timelessJewelTypeByConqueror = { vaal = 1, From cbd668f2c59aaaad8480e064cf60299fe09e96e3 Mon Sep 17 00:00:00 2001 From: Mickael Cagnion Date: Sat, 22 Aug 2026 14:36:56 +0200 Subject: [PATCH 8/8] Unify radius jewel tooltip comparison caching Use the existing output cache for slot-only and multi-slot hovers instead of retaining temporary passive specs. Keep calculation-only specs free of UI path data while preserving socket distances. This reduces duplicate state and repeated calculations without changing tooltip content. --- spec/System/TestRadiusJewelStatDiff_spec.lua | 59 ++++++++++++++++--- src/Classes/ItemsTab.lua | 60 ++++++-------------- src/Classes/PassiveSpec.lua | 16 +++--- 3 files changed, 77 insertions(+), 58 deletions(-) diff --git a/spec/System/TestRadiusJewelStatDiff_spec.lua b/spec/System/TestRadiusJewelStatDiff_spec.lua index 3a6af166ae..36c4f7bbc5 100644 --- a/spec/System/TestRadiusJewelStatDiff_spec.lua +++ b/spec/System/TestRadiusJewelStatDiff_spec.lua @@ -668,7 +668,7 @@ describe("TestRadiusJewelStatDiff", function() "limited unique radius jewels should rebuild only the same-unique slot that will be displayed") end) - it("AddItemTooltip reuses slot-only radius jewel comparison specs until output changes", function() + it("AddItemTooltip reuses slot-only radius jewel comparison outputs until output changes", function() local spec, sockets = setupAllocatedSockets(2) local item = newCustomLeapJewel("Cached Leap") @@ -678,6 +678,7 @@ describe("TestRadiusJewelStatDiff", function() local originalSlotOnlyTooltips = main.slotOnlyTooltips main.slotOnlyTooltips = true + build.itemsTab.jewelComparisonOutputCache = nil local specClass = getmetatable(spec) local originalBuildAllDependsAndPaths = specClass.BuildAllDependsAndPaths local rebuilds = 0 @@ -685,22 +686,42 @@ describe("TestRadiusJewelStatDiff", function() rebuilds = rebuilds + 1 return originalBuildAllDependsAndPaths(self, ...) end + 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"):Tooltip() build.itemsTab:AddItemTooltip(tooltip, item, slot) + local firstPassTooltipText = tooltipText(tooltip) + local firstPassCalcCalls = calcCalls + assert.is_true(firstPassCalcCalls > 0, + "slot-only radius jewel hover should calculate its output on first pass") tooltip = new("Tooltip"):Tooltip() build.itemsTab:AddItemTooltip(tooltip, item, slot) + assert.are.equals(firstPassTooltipText, tooltipText(tooltip), + "cached slot-only radius output should preserve tooltip content") assert.are.equals(1, rebuilds, - "slot-only radius jewel hover should reuse its cached comparison spec") + "slot-only radius jewel hover should reuse its cached comparison output") + assert.are.equals(firstPassCalcCalls, calcCalls, + "slot-only radius jewel hover should not recalculate a cached output") build.outputRevision = build.outputRevision + 1 tooltip = new("Tooltip"):Tooltip() build.itemsTab:AddItemTooltip(tooltip, item, slot) assert.are.equals(2, rebuilds, - "slot-only radius jewel comparison spec cache should reset when output changes") + "slot-only radius jewel output cache should reset when output changes") + assert.is_true(calcCalls > firstPassCalcCalls, + "slot-only radius jewel comparison should recalculate after output changes") end) specClass.BuildAllDependsAndPaths = originalBuildAllDependsAndPaths + build.calcsTab.GetMiscCalculator = originalGetMiscCalculator main.slotOnlyTooltips = originalSlotOnlyTooltips if not ok then error(err) @@ -723,10 +744,13 @@ describe("TestRadiusJewelStatDiff", function() local originalSlotOnlyTooltips = main.slotOnlyTooltips main.slotOnlyTooltips = true local specClass = getmetatable(spec) + local originalBuildAllDependsAndPaths = specClass.BuildAllDependsAndPaths local originalBuildPathFromNode = specClass.BuildPathFromNode local originalSetNodeDistanceToClassStart = specClass.SetNodeDistanceToClassStart + local originalBuildSplitPersonalityPath = specClass.BuildSplitPersonalityPath local buildPathCalls = 0 local distanceCalls = 0 + local splitPersonalityPathCalls = 0 specClass.BuildPathFromNode = function(self, ...) buildPathCalls = buildPathCalls + 1 return originalBuildPathFromNode(self, ...) @@ -735,22 +759,42 @@ describe("TestRadiusJewelStatDiff", function() distanceCalls = distanceCalls + 1 return originalSetNodeDistanceToClassStart(self, ...) end + specClass.BuildSplitPersonalityPath = function(self, ...) + splitPersonalityPathCalls = splitPersonalityPathCalls + 1 + return originalBuildSplitPersonalityPath(self, ...) + end local ok, err = pcall(function() local tooltip = new("Tooltip"):Tooltip() build.itemsTab:AddItemTooltip(tooltip, radiusItem, radiusSlot) + local calculationOnlyTooltipText = tooltipText(tooltip) + assert.are.equals(0, buildPathCalls, + "calculation-only tooltip specs should not rebuild UI node paths") + assert.is_true(distanceCalls > 0, + "calculation-only tooltip specs should refresh jewel socket distances used by calc") + assert.are.equals(0, splitPersonalityPathCalls, + "calculation-only tooltip specs should not rebuild Split Personality highlight paths") + + build.itemsTab.jewelComparisonOutputCache = nil + specClass.BuildAllDependsAndPaths = function(self) + return originalBuildAllDependsAndPaths(self) + end + local fullRebuildTooltip = new("Tooltip"):Tooltip() + build.itemsTab:AddItemTooltip(fullRebuildTooltip, radiusItem, radiusSlot) + assert.are.equals(calculationOnlyTooltipText, tooltipText(fullRebuildTooltip), + "calculation-only radius jewel specs should preserve full-rebuild tooltip output") + assert.is_true(splitPersonalityPathCalls > 0, + "the full-rebuild comparison should exercise Split Personality highlight paths") end) + specClass.BuildAllDependsAndPaths = originalBuildAllDependsAndPaths specClass.BuildPathFromNode = originalBuildPathFromNode specClass.SetNodeDistanceToClassStart = originalSetNodeDistanceToClassStart + specClass.BuildSplitPersonalityPath = originalBuildSplitPersonalityPath 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) it("AddItemTooltip reuses full radius jewel comparison outputs until output changes", function() @@ -764,7 +808,6 @@ describe("TestRadiusJewelStatDiff", function() local originalSlotOnlyTooltips = main.slotOnlyTooltips main.slotOnlyTooltips = false build.itemsTab.jewelComparisonOutputCache = nil - build.itemsTab.slotOnlyJewelComparisonSpecCache = nil local originalGetMiscCalculator = build.calcsTab.GetMiscCalculator local calcCalls = 0 diff --git a/src/Classes/ItemsTab.lua b/src/Classes/ItemsTab.lua index 8e812428b1..6968a2a01c 100644 --- a/src/Classes/ItemsTab.lua +++ b/src/Classes/ItemsTab.lua @@ -4341,33 +4341,12 @@ local function cloneSpecForJewelComparison(spec) return specCopy end -local function buildSpecForJewelComparison(itemsTab, compareSlot, replacementItem, useJewelComparisonSpecCache) +local function buildSpecForJewelComparison(itemsTab, compareSlot, replacementItem) local tempItemId - local replacementItemId = replacementItem and replacementItem.id - local replacementItemIsStored = replacementItemId and itemsTab.items[replacementItemId] == replacementItem - local canCache = useJewelComparisonSpecCache and (not replacementItem or replacementItemIsStored) - local cacheKey - local cache - if canCache then - local outputRevision = itemsTab.build and itemsTab.build.outputRevision or 0 - cache = itemsTab.slotOnlyJewelComparisonSpecCache - if not cache or cache.outputRevision ~= outputRevision then - cache = { - outputRevision = outputRevision, - specs = { }, - } - itemsTab.slotOnlyJewelComparisonSpecCache = 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 replacementItemIsStored then - spec.jewels[compareSlot.nodeId] = replacementItemId + if replacementItem.id and itemsTab.items[replacementItem.id] == replacementItem then + spec.jewels[compareSlot.nodeId] = replacementItem.id else tempItemId = -1 while itemsTab.items[tempItemId] do @@ -4381,9 +4360,9 @@ local function buildSpecForJewelComparison(itemsTab, compareSlot, replacementIte end local ok, err = xpcall(function() - -- These temporary specs only feed the misc calculator, which does not read node.path/pathDist. - -- Jewel socket distances are still rebuilt for jewel scaling; - -- Split Personality highlight paths are also refreshed. + -- These temporary specs only feed the misc calculator, which does not read regular + -- node paths or Split Personality highlight paths. Jewel socket distances are still + -- rebuilt for Split Personality modifier scaling. spec:BuildAllDependsAndPaths(true) end, debug.traceback) if tempItemId then @@ -4392,9 +4371,6 @@ 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 @@ -5118,22 +5094,20 @@ 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, useJewelComparisonOutputCache) + local function getReplacedItemAndOutput(compareSlot, selItem) 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 + outputCacheKey = getJewelComparisonOutputCacheKey(self, compareSlot, override.repItem) + if outputCacheKey then + outputCache = getJewelComparisonOutputCache(self) + if outputCache.outputs[outputCacheKey] then + return selItem, outputCache.outputs[outputCacheKey] end end - override.spec = buildSpecForJewelComparison(self, compareSlot, override.repItem, useJewelComparisonSpecCache) + override.spec = buildSpecForJewelComparison(self, compareSlot, override.repItem) end local output = calcFunc(override) if outputCacheKey then @@ -5141,9 +5115,9 @@ function ItemsTabClass:AddItemTooltip(tooltip, item, slot, dbMode, maxWidth) end return selItem, output end - local function addCompareForSlot(compareSlot, selItem, output, useJewelComparisonSpecCache) + local function addCompareForSlot(compareSlot, selItem, output) if not selItem or not output then - selItem, output = getReplacedItemAndOutput(compareSlot, nil, useJewelComparisonSpecCache) + selItem, output = getReplacedItemAndOutput(compareSlot) end local header if item == selItem then @@ -5159,7 +5133,7 @@ function ItemsTabClass:AddItemTooltip(tooltip, item, slot, dbMode, maxWidth) -- one slot. local compareOnlySlot = type(slot) ~= "string" and slot or self.slots[slot] if main.slotOnlyTooltips and slot then - if compareOnlySlot then addCompareForSlot(compareOnlySlot, nil, nil, true) end + if compareOnlySlot then addCompareForSlot(compareOnlySlot) end return end @@ -5180,7 +5154,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, nil, true) + local _, output = getReplacedItemAndOutput(slotEntry.compareSlot, slotEntry.selItem) slotEntry.output = output table.insert(slots, slotEntry) end diff --git a/src/Classes/PassiveSpec.lua b/src/Classes/PassiveSpec.lua index 3984e1c57c..ba9cf5458e 100644 --- a/src/Classes/PassiveSpec.lua +++ b/src/Classes/PassiveSpec.lua @@ -1099,10 +1099,10 @@ function PassiveSpecClass:NodesInIntuitiveLeapLikeRadius(node) end -- Rebuilds dependencies and calculation distances for all nodes. --- When node paths are skipped, node.path/pathDist remain unset while jewel socket --- distanceToClassStart values and Split Personality paths are still refreshed. ----@param skipNodePathRebuild? boolean -function PassiveSpecClass:BuildAllDependsAndPaths(skipNodePathRebuild) +-- Calculation-only specs leave UI path fields unset while still refreshing jewel +-- socket distanceToClassStart values used by the calculator. +---@param calculationOnly? boolean +function PassiveSpecClass:BuildAllDependsAndPaths(calculationOnly) local timelessJewelTypeByConqueror = { vaal = 1, karui = 2, @@ -1600,7 +1600,7 @@ function PassiveSpecClass:BuildAllDependsAndPaths(skipNodePathRebuild) -- Reset and rebuild all node paths for id, node in pairs(self.nodes) do - if skipNodePathRebuild then + if calculationOnly then node.pathDist = nil node.path = nil else @@ -1613,7 +1613,7 @@ function PassiveSpecClass:BuildAllDependsAndPaths(skipNodePathRebuild) end for id, node in pairs(self.allocNodes) do if #node.intuitiveLeapLikesAffecting == 0 or node.connectedToStart then - if not skipNodePathRebuild then + if not calculationOnly then self:BuildPathFromNode(node) end if node.isJewelSocket or node.expansionJewel then @@ -1622,7 +1622,9 @@ function PassiveSpecClass:BuildAllDependsAndPaths(skipNodePathRebuild) end end - self:BuildSplitPersonalityPath() + if not calculationOnly then + self:BuildSplitPersonalityPath() + end end function PassiveSpecClass:ReplaceNode(old, newNode)