From 61f46a4477ccd7629b0787e3237e767711544ee3 Mon Sep 17 00:00:00 2001 From: vaisest <4550061+vaisest@users.noreply.github.com> Date: Sat, 22 Aug 2026 14:01:36 +0300 Subject: [PATCH 1/2] Fix keystone clusters not importing and unallocated subgraphs existing --- src/Classes/PassiveSpec.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Classes/PassiveSpec.lua b/src/Classes/PassiveSpec.lua index cd16dd659f7..ceaa97fdbbc 100644 --- a/src/Classes/PassiveSpec.lua +++ b/src/Classes/PassiveSpec.lua @@ -1765,7 +1765,7 @@ function PassiveSpecClass:BuildClusterJewelGraphs() for nodeId in pairs(self.tree.sockets) do local node = self.tree.nodes[nodeId] local jewel = self:GetSocketedJewel(nodeId) - if node and node.expansionJewel and node.expansionJewel.size == 2 and jewel and jewel.jewelData.clusterJewelValid then + if node and self.allocNodes[node.id] and node.expansionJewel and node.expansionJewel.size == 2 and jewel and jewel.jewelData.clusterJewelValid then -- This is a Large Jewel Socket, and it has a cluster jewel in it self:BuildSubgraph(jewel, self.nodes[nodeId], nil, nil, importedNodes, importedGroups) end @@ -1971,7 +1971,9 @@ function PassiveSpecClass:BuildSubgraph(jewel, parentSocket, id, upSize, importe if proxyGroup then for id, data in pairs(importedNodes) do if proxyGroup == data.group then - if node.oidx == data.orbitIndex and not data.isMastery then + local matches = node.type == "Keystone" and data.isKeystone + or (node.oidx == data.orbitIndex and not data.isMastery) + if matches then for _, extendedId in ipairs(importedGroups[proxyGroup].nodes) do if id == extendedId and inExtendedHashes(tonumber(id)) then return true From f54b997abd6e0606f5b9e38544f90da673c18f22 Mon Sep 17 00:00:00 2001 From: LocalIdentity Date: Mon, 24 Aug 2026 00:19:05 +1000 Subject: [PATCH 2/2] Fix cluster behaviour There were issues with nester cluster sockets and also reallocating sockets not making the cluster nodes appear / disappear --- spec/System/TestClusterJewelGraphs_spec.lua | 115 ++++++++++++++++++++ src/Classes/PassiveSpec.lua | 24 +++- 2 files changed, 134 insertions(+), 5 deletions(-) create mode 100644 spec/System/TestClusterJewelGraphs_spec.lua diff --git a/spec/System/TestClusterJewelGraphs_spec.lua b/spec/System/TestClusterJewelGraphs_spec.lua new file mode 100644 index 00000000000..304d17b4be9 --- /dev/null +++ b/spec/System/TestClusterJewelGraphs_spec.lua @@ -0,0 +1,115 @@ +describe("Cluster jewel subgraphs", function() + local function countSubgraphs(spec) + local count = 0 + for _ in pairs(spec.subGraphs) do + count = count + 1 + end + return count + end + + local function getOuterSocket(spec) + for nodeId in pairs(spec.tree.sockets) do + local node = spec.nodes[nodeId] + if node and node.expansionJewel and node.expansionJewel.size == 2 then + return node + end + end + end + + local function addItem(raw) + local item = new("Item"):Item(raw) + build.itemsTab:AddItem(item, true) + return item + end + + local function addKeystoneCluster() + return addItem([[Rarity: UNIQUE +One With Nothing +Small Cluster Jewel +Implicits: 0 +Adds Hollow Palm Technique]]) + end + + before_each(function() + newBuild() + end) + + it("updates the graph when an outer socket allocation changes", function() + local spec = build.spec + local socket = getOuterSocket(spec) + local jewel = addKeystoneCluster() + spec.jewels[socket.id] = jewel.id + spec.extended_hashes = { 123 } + spec.jewel_data = { + [socket.id] = { + subgraph = { + groups = { + keystone = { proxy = socket.expansionJewel.proxy, nodes = { "123" } }, + }, + nodes = { + ["123"] = { group = "keystone", isKeystone = true, orbitIndex = 0 }, + }, + }, + }, + } + + spec:BuildClusterJewelGraphs() + assert.are.equal(0, countSubgraphs(spec)) + + spec:AllocNode(socket) + assert.are.equal(1, countSubgraphs(spec)) + local _, subgraph = next(spec.subGraphs) + assert.is_true(subgraph.nodes[1].alloc) + + spec:DeallocNode(socket) + assert.are.equal(0, countSubgraphs(spec)) + end) + + it("only builds nested graphs for allocated cluster sockets", function() + local spec = build.spec + local outerSocket = getOuterSocket(spec) + spec:AllocNode(outerSocket) + + local largeCluster = addItem([[Rarity: RARE +New Item +Large Cluster Jewel +Cluster Jewel Skill: affliction_chaos_damage +Cluster Jewel Node Count: 8 +Implicits: 3 +Adds 8 Passive Skills +2 Added Passive Skills are Jewel Sockets +Added Small Passive Skills grant: 12% increased Chaos Damage]]) + spec.jewels[outerSocket.id] = largeCluster.id + spec:BuildClusterJewelGraphs() + + local nestedSocket + for _, subgraph in pairs(spec.subGraphs) do + for _, node in ipairs(subgraph.nodes) do + if node.type == "Socket" then + nestedSocket = node + break + end + end + if nestedSocket then + break + end + end + assert.is_truthy(nestedSocket) + + local nestedJewel = addKeystoneCluster() + spec.jewels[nestedSocket.id] = nestedJewel.id + spec:BuildClusterJewelGraphs() + assert.are.equal(1, countSubgraphs(spec)) + + nestedSocket = spec.nodes[nestedSocket.id] + spec:AllocNode(nestedSocket) + assert.are.equal(2, countSubgraphs(spec)) + + spec:BuildClusterJewelGraphs() + assert.are.equal(2, countSubgraphs(spec)) + + nestedSocket = spec.nodes[nestedSocket.id] + spec:DeallocNode(nestedSocket) + assert.are.equal(1, countSubgraphs(spec)) + end) +end) diff --git a/src/Classes/PassiveSpec.lua b/src/Classes/PassiveSpec.lua index ceaa97fdbbc..1a31f414fd4 100644 --- a/src/Classes/PassiveSpec.lua +++ b/src/Classes/PassiveSpec.lua @@ -778,11 +778,14 @@ function PassiveSpecClass:AllocNode(node, altPath) end -- Allocate all nodes along the path + local rebuildClusterJewelGraphs = false if #node.intuitiveLeapLikesAffecting > 0 then + rebuildClusterJewelGraphs = not node.alloc and node.expansionJewel ~= nil node.alloc = true self.allocNodes[node.id] = node else for _, pathNode in ipairs(altPath or node.path) do + rebuildClusterJewelGraphs = rebuildClusterJewelGraphs or not pathNode.alloc and pathNode.expansionJewel ~= nil pathNode.alloc = true self.allocNodes[pathNode.id] = pathNode end @@ -799,8 +802,12 @@ function PassiveSpecClass:AllocNode(node, altPath) end end - -- Rebuild all dependencies and paths for all allocated nodes - self:BuildAllDependsAndPaths() + if rebuildClusterJewelGraphs then + self:BuildClusterJewelGraphs() + else + -- Rebuild all dependencies and paths for all allocated nodes + self:BuildAllDependsAndPaths() + end end function PassiveSpecClass:DeallocSingleNode(node) @@ -814,12 +821,18 @@ end -- Deallocate the given node, and all nodes which depend on it (i.e. which are only connected to the tree through this node) function PassiveSpecClass:DeallocNode(node) + local rebuildClusterJewelGraphs = false for _, depNode in ipairs(node.depends) do + rebuildClusterJewelGraphs = rebuildClusterJewelGraphs or depNode.alloc and depNode.expansionJewel ~= nil self:DeallocSingleNode(depNode) end - -- Rebuild all paths and dependencies for all allocated nodes - self:BuildAllDependsAndPaths() + if rebuildClusterJewelGraphs then + self:BuildClusterJewelGraphs() + else + -- Rebuild all paths and dependencies for all allocated nodes + self:BuildAllDependsAndPaths() + end end -- Count the number of allocated nodes and allocated ascendancy nodes @@ -2267,7 +2280,8 @@ function PassiveSpecClass:BuildSubgraph(jewel, parentSocket, id, upSize, importe if node.type == "Socket" then -- Recurse to smaller jewels local jewel = self:GetSocketedJewel(node.id) - if jewel and jewel.jewelData.clusterJewelValid then + -- Allocated subgraph nodes are temporarily preserved outside allocNodes while rebuilding. + if (self.allocNodes[node.id] or isValueInArray(self.allocSubgraphNodes, node.id)) and jewel and jewel.jewelData.clusterJewelValid then self:BuildSubgraph(jewel, node, id, upSize, importedNodes, importedGroups) end end