From 87d6d7cfcb5c38369b696be67ef6072f006c7ade Mon Sep 17 00:00:00 2001 From: unrealdreamz <132005717+unrealdreamz@users.noreply.github.com> Date: Tue, 19 May 2026 13:26:15 -0400 Subject: [PATCH 01/11] Fix stateful EHP block speedup --- spec/Fixtures/issue1754_block_ehp.xml | 795 ++++++++++++++++++++++++++ spec/System/TestDefence_spec.lua | 57 +- src/Modules/CalcDefence.lua | 7 +- 3 files changed, 857 insertions(+), 2 deletions(-) create mode 100644 spec/Fixtures/issue1754_block_ehp.xml diff --git a/spec/Fixtures/issue1754_block_ehp.xml b/spec/Fixtures/issue1754_block_ehp.xml new file mode 100644 index 0000000000..4c6da1c865 --- /dev/null +++ b/spec/Fixtures/issue1754_block_ehp.xml @@ -0,0 +1,795 @@ + + + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + https://www.pathofexile.com/passive-skill-tree/AAAABgIBmLOoS-B_NJGKbCdclipOP9vFgWhlumpKQ4WadaMPYOFndqMZgWxOGXJISbq0Jhb710aZbpD4W2rtFgrJ6lMmjBfMleSg7Nq7hs48U4uSjxwyHiT562uU01NjHsudsTO7jpqu11u3nmdF0J0_KE9-axrZ-s1MlmUEsLAix7koetQKECBG7QsfgxSMu2_FO48BbNNvmSf31deWjCRhMA12mKu2W4f9CzAeKZGcBJEPlG4PJaZorn6ggqzrKw5MD232igVIcwdIxW9-r5hpyqR_wIVyi8p4aqAJuy4KlPLfbNPYZdZGT2nkKhbJabT1ZBOopqic1iLpgSEt-xzWG9ixI8v0sDtO9CIWTti2z8pBVYJZ6YnoxUKFgu7R12fIMILKFHqNgNAu4EKV9f1g5xTUs5EAAA== + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Rarity: RARE +Chimeric Sliver +Ruby +Unique ID: 3f2a054bb6d41d7fe0d14122cc3d55f5b9055285249b5bf81d740ad1d59b6136 +Item Level: 80 +LevelReq: 0 +Implicits: 0 +18% increased Armour +14% increased Attack Damage +19% increased Warcry Speed +{desecrated}2% increased Strength + + + + + + + Rarity: RARE +Loath Wound +Time-Lost Ruby +Unique ID: 79ae21e9e5c5904ecda572dff79ab1d719eac798e1d9cfb35975a9116d915bc5 +Item Level: 78 +LevelReq: 0 +Radius: Medium +Implicits: 0 +Upgrades Radius to Medium +25% increased Effect of Notable Passive Skills in Radius +25% increased Effect of Small Passive Skills in Radius + + + + + + Rarity: UNIQUE +From Nothing +Diamond +Unique ID: 60219c236ed8e1330ab16b8919afb429b870bc28c23e445c4da72a8d0b22b76c +Item Level: 84 +LevelReq: 0 +Radius: Small +Limited to: 1 +Implicits: 0 +Passives in Radius of Resolute Technique can be Allocated +without being connected to your tree +Corrupted + + + + Rarity: RARE +Ghoul Sole +Tasalian Greaves +Armour: 432 +Unique ID: 816f7b3213e27a3f552b294e0066bcb695b3c7a2075c16a000c1cd456ab4311c +Item Level: 82 +Quality: 20 +Sockets: S S +Rune: Soul Core of Tacati +Rune: Soul Core of Tacati +LevelReq: 80 +Implicits: 1 +{enchant}{rune}+22% to Chaos Resistance ++123 to Armour ++110 to maximum Life ++31% to Fire Resistance ++30% to Lightning Resistance ++32% of Armour also applies to Elemental Damage +{desecrated}35% increased Movement Speed +Corrupted + + + + + + + + + + Rarity: RARE +Torment Touch +Vaal Mitts +Armour: 485 +Unique ID: ec3d3e1408f66406a34cc183c6ffb0a87025355c3295bc90ab8be9f8d32c539e +Item Level: 79 +Quality: 20 +Sockets: S +Rune: Idol of Grold +LevelReq: 75 +Implicits: 2 +{enchant}{rune}32% increased total Power counted by Warcries +{enchant}{rune}Bonded: 32% increased Glory generation +36% increased Armour ++156 to maximum Life ++2 to Level of all Melee Skills ++42% of Armour also applies to Elemental Damage +60% increased effect of Socketed Items +{desecrated}+151 to Armour + + + + + + + + + + + Rarity: NORMAL +Evasive Leg +Unique ID: ef550b724c26f2472f48418e92bafea5c8c4a804a960cc7b09066163fba3e44e +Item Level: 99 +LevelReq: 0 +Implicits: 1 +20% increased Evasion Rating + + + + Rarity: NORMAL +Combat Arm +Unique ID: f36295e737b0e7d2e531713f8153dd41e79d029fb6591afd25302dd9f630b099 +Item Level: 99 +LevelReq: 0 +Implicits: 1 +10% increased Attack Speed + + + + Rarity: NORMAL +Guarding Arm +Unique ID: 8a1cb54bc4dfaba7a8cbdc7046e0726011a7a0424d8db8d9e9560e694079f61f +Item Level: 99 +LevelReq: 0 +Implicits: 1 +10% increased Block chance + + + + Rarity: MAGIC +Experimenter's Staunching Charm of the Bountiful +Unique ID: 7b7a2820b483555344ef9156b5dffceebb5dc44774c9109af7f472a6d9a518f8 +Item Level: 81 +Quality: 17 +LevelReq: 18 +Implicits: 1 +Used when you start Bleeding +36% increased Duration +53% increased Charges + + + + + Rarity: UNIQUE +Rite of Passage +Golden Charm +Unique ID: 897c5364a769621daab36f5fcee7e3a3264b236f7cceac264eb9f37546caf810 +Item Level: 81 +Quality: 0 +LevelReq: 50 +Implicits: 1 +Used when you kill a Rare or Unique enemy +Possessed by Spirit Of The Ox for 20 seconds on use + + + Rarity: RARE +Phoenix Bind +Plate Belt +Charm Slots: 2 +Unique ID: 430b1c0f872adfb91474657f630483dbeb06359ccc10a3de78d59f9dc469c3b4 +Item Level: 79 +LevelReq: 24 +Implicits: 2 ++121 to Armour +Has 2 Charm Slots ++142 to Armour ++169 to maximum Life ++123 to maximum Mana ++26 to Strength ++39% to Cold Resistance +{desecrated}+17% to Fire and Chaos Resistances + + + + + + + + + + + Rarity: RARE +Phoenix Pendant +Pearlescent Amulet +Unique ID: f3ab9699d86ffae4a2943e4aab99607bce30d6ae9bdb0ef28b51e1d9eab928ca +Item Level: 78 +LevelReq: 30 +Implicits: 2 +{enchant}Allocates Reinforced Barrier ++10% to all Elemental Resistances ++175 to maximum Life +9% increased maximum Life ++33 to Strength ++32% to Fire Resistance ++29% to Cold Resistance +{desecrated}34% increased Armour + + + + + + + + + + + Rarity: MAGIC +Substantial Ultimate Mana Flask of the Abundant +Unique ID: 2f3e029b0a0e34fc09b8f199f1617aab732678e3f4058ac2b4ab858a14607528 +Item Level: 80 +Quality: 10 +LevelReq: 60 +Implicits: 0 +62% increased Amount Recovered +57% increased Charges + + + + + Rarity: RARE +Onslaught Crest +Imperial Greathelm +Armour: 1269 +Unique ID: 26849265030f23ddccb3e1ddd940f30695811b039ce53ac28c6086af7ca9e2a4 +Item Level: 83 +Quality: 30 +Sockets: S +Rune: Soul Core of Tacati +LevelReq: 80 +Implicits: 1 +{enchant}{rune}+11% to Chaos Resistance ++195 to Armour +91% increased Armour ++174 to maximum Life ++29 to Strength ++29% to Cold Resistance +{desecrated}+29% of Armour also applies to Chaos Damage +Corrupted + + + + + + + + + + Rarity: MAGIC +Seething Ultimate Life Flask of the Brewer +Unique ID: 2375dec92a0147d9ea02dc37bc7f94fb4a993d0886a056c1507e2e836eea3934 +Item Level: 79 +Quality: 22 +LevelReq: 60 +Implicits: 0 +50% reduced Amount Recovered +Instant Recovery +29% reduced Charges per use + + + + + + Rarity: RARE +Armageddon Shatter +Structured Hammer +Unique ID: 0ad6ea8224a123ac010f9c13bee9c7ae415c70fd127db1c79aa110449e0eb4e5 +Item Level: 82 +Quality: 20 +Sockets: S +Rune: Farrul's Rune of the Hunt +LevelReq: 62 +Implicits: 3 +{enchant}{rune}50% increased Attack Damage against Rare or Unique Enemies +{enchant}{rune}Bonded: +1 to Level of all Attack Skills +40% chance to Daze on Hit +Adds 11 to 19 Physical Damage +96% increased Elemental Damage with Attacks ++5 to Level of all Melee Skills ++26 to Strength +Gain 20% of Damage as Extra Physical Damage +{desecrated}Break Armour equal to 4% of Physical Damage dealt + + + + + + + + + + + Rarity: RARE +Carrion Span +Tawhoan Tower Shield +Armour: 1215 +Unique ID: 2957b34aa214fbdc6d0b652646718c039598d898641ffb48e728d0266aa3343f +Item Level: 80 +Quality: 20 +Sockets: S S +Rune: Greater Iron Rune +Rune: Greater Iron Rune +LevelReq: 80 +Implicits: 4 +{enchant}{rune}36% increased Armour, Evasion and Energy Shield +{enchant}{rune}Bonded: +20 to maximum Life +{enchant}{rune}Bonded: +20 to maximum Mana +Grants Skill: Raise Shield ++253 to Armour +89% increased Armour ++185 to maximum Life ++31 to Strength +8% additional Physical Damage Reduction ++28% to Cold Resistance +Corrupted + + + + + + + + + + + + + Rarity: MAGIC +Experimenter's Grounding Charm of the Ample +Unique ID: 4a9b1924da5c16f078735bbea50effa0b1a2ed71596e0ddf13ead3596c58fc35 +Item Level: 81 +Quality: 0 +LevelReq: 32 +Implicits: 1 +Used when you become Shocked +38% increased Duration +69% increased Charges + + + + + Rarity: RARE +Demon Grip +Amethyst Ring +Unique ID: 3a9a4bc4c6c7bf6c851d9e3465e170235570e40f8d701c4bbd68eb17f54bc0e1 +Item Level: 80 +LevelReq: 20 +Implicits: 1 ++11% to Chaos Resistance ++199 to Evasion Rating ++172 to maximum Mana ++29 to Strength ++34% to Lightning Resistance +Leech 6.34% of Physical Attack Damage as Mana +{desecrated}+115 to maximum Life + + + + + + + + + + Rarity: RARE +Pain Shelter +Soldier Cuirass +Armour: 2031 +Unique ID: c1272d9098b999d72429ccdf1fe25c25781e51c8b2d9392ca792f7a41c8ab87e +Item Level: 79 +Quality: 24 +Sockets: S S +Rune: The Greatwolf's Rune of Willpower +Rune: Craiceann's Rune of Warding +LevelReq: 65 +Implicits: 4 +{enchant}{rune}50% reduced effect of Curses on you +{enchant}{rune}15% of Damage is taken from Mana before Life +{enchant}{rune}Bonded: 8% increased Curse Magnitudes +{enchant}{rune}Bonded: 8% of Maximum Life Converted to Energy Shield ++265 to Armour +105% increased Armour +10% increased maximum Life +Hits against you have 47% reduced Critical Damage Bonus ++30% to Lightning Resistance +{desecrated}+43% of Armour also applies to Elemental Damage +Corrupted + + + + + + + + + + + + + Rarity: NORMAL +Evasive Leg +Unique ID: 1170f441dfecf0cfa4c4f7b19833710477ff62ac3fb66c2c1c658daa1f097ccf +Item Level: 99 +LevelReq: 0 +Implicits: 1 +30% increased Evasion Rating + + + + Rarity: RARE +Kraken Hold +Prismatic Ring +Unique ID: 5942bb3b406d8bb45907c9efb803bc7c1e516064294f1739977e1d4f5fcb112c +Item Level: 80 +LevelReq: 35 +Implicits: 1 ++12% to all Elemental Resistances ++164 to Evasion Rating ++116 to maximum Life ++156 to maximum Mana ++35% to Fire Resistance +Leech 7.39% of Physical Attack Damage as Life +{desecrated}+20% to Lightning and Chaos Resistances + + + + + + + + + + Rarity: UNIQUE +Carrion Span +Tawhoan Tower Shield +Armour: 1215 +Unique ID: 2957b34aa214fbdc6d0b652646718c039598d898641ffb48e728d0266aa3343f +Item Level: 80 +Quality: 20 +Sockets: S S +Rune: Greater Iron Rune +Rune: Greater Iron Rune +LevelReq: 80 +Implicits: 4 +{enchant}{rune}36% increased Armour, Evasion and Energy Shield +{enchant}{rune}Bonded: +20 to maximum Life +{enchant}{rune}Bonded: +20 to maximum Mana +Grants Skill: Raise Shield ++253 to Armour +89% increased Armour ++185 to maximum Life ++31 to Strength ++28% to Cold Resistance +Corrupted + + + + + + + + + + + + Rarity: UNIQUE +Undying Hate +Timeless Jewel +Variant: Amanamu +Variant: Kulemak +Variant: Kurgal +Variant: Tecrod +Variant: Ulaman +Selected Variant: 1 +LevelReq: 0 +Radius: Very Large +Limited to: 1 +Implicits: 0 +{variant:1}{range:0.5}Glorifying the defilement of (100-8000) souls in tribute to Amanamu +{variant:2}{range:0.5}Glorifying the defilement of (100-8000) souls in tribute to Kulemak +{variant:3}{range:0.5}Glorifying the defilement of (100-8000) souls in tribute to Kurgal +{variant:4}{range:0.5}Glorifying the defilement of (100-8000) souls in tribute to Tecrod +{variant:5}{range:0.5}Glorifying the defilement of (100-8000) souls in tribute to Ulaman +Passives in radius are Conquered by the Abyssals +Historic + + + + + + + + + + Rarity: UNIQUE +Pain Shelter +Soldier Cuirass +Armour: 2031 +Unique ID: c1272d9098b999d72429ccdf1fe25c25781e51c8b2d9392ca792f7a41c8ab87e +Item Level: 79 +Quality: 24 +Sockets: S S +Rune: The Greatwolf's Rune of Willpower +Rune: Craiceann's Rune of Warding +LevelReq: 65 +Implicits: 4 +{enchant}{rune}50% reduced effect of Curses on you +{enchant}{rune}Bonded: 8% increased Curse Magnitudes +{enchant}{rune}Bonded: 8% of Maximum Life Converted to Energy Shield ++265 to Armour +105% increased Armour +10% increased maximum Life +Hits against you have 47% reduced Critical Damage Bonus ++30% to Lightning Resistance +{desecrated}+43% of Armour also applies to Elemental Damage +Corrupted + + + + + + + + + + + + Rarity: UNIQUE +Torment Touch +Vaal Mitts +Armour: 485 +Unique ID: ec3d3e1408f66406a34cc183c6ffb0a87025355c3295bc90ab8be9f8d32c539e +Item Level: 79 +Quality: 20 +Sockets: S +Rune: Idol of Grold +LevelReq: 75 +Implicits: 2 +{enchant}{rune}32% increased total Power counted by Warcries +{enchant}{rune}Bonded: 32% increased Glory generation +36% increased Armour ++156 to maximum Life ++2 to Level of all Melee Skills ++42% of Armour also applies to Elemental Damage ++170 to mana +{desecrated}+151 to Armour + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spec/System/TestDefence_spec.lua b/spec/System/TestDefence_spec.lua index 0f8aa0bc74..73da8283a8 100644 --- a/spec/System/TestDefence_spec.lua +++ b/spec/System/TestDefence_spec.lua @@ -527,4 +527,59 @@ describe("TestDefence", function() assert.are.equals(0, floor(poolsRemaining.Life)) assert.are.equals(0, floor(poolsRemaining.OverkillDamage)) end) -end) \ No newline at end of file + + it("does not reduce stateful EHP when block chance increases", function() + -- Regression fixture from #1754. The build uses life-loss prevention and MoM, + -- so the EHP speedup path must preserve monotonic block improvements. + local defendersResolveNodeId = 64327 + local adjacentBlockNodeId = 39517 + + local function readFixture(path) + local file = assert(io.open(path, "rb")) + local contents = file:read("*a") + file:close() + return contents + end + + local function loadIssue1754Build(extraNodeIds) + local xmlText = readFixture("../spec/Fixtures/issue1754_block_ehp.xml") + xmlText = xmlText:gsub('nodes="([^"]*)"', function(nodes) + local allocated = { } + for nodeId in nodes:gmatch("%d+") do + allocated[nodeId] = true + end + for _, nodeId in ipairs(extraNodeIds) do + if not allocated[tostring(nodeId)] then + nodes = nodes .. "," .. tostring(nodeId) + end + end + return 'nodes="' .. nodes .. '"' + end, 1) + loadBuildFromXML(xmlText, "issue 1754") + runCallback("OnFrame") + runCallback("OnFrame") + return build.calcsTab.calcsOutput + end + + local base = loadIssue1754Build({ }) + local baseEHP = base.TotalEHP + local baseBlock = base.EffectiveBlockChance + local defendersResolve = loadIssue1754Build({ defendersResolveNodeId }) + local defendersResolveEHP = defendersResolve.TotalEHP + local defendersResolveBlock = defendersResolve.EffectiveBlockChance + local adjacentBlock = loadIssue1754Build({ defendersResolveNodeId, adjacentBlockNodeId }) + local adjacentBlockEHP = adjacentBlock.TotalEHP + local adjacentBlockChance = adjacentBlock.EffectiveBlockChance + local defendersResolveAddsBlock = defendersResolveBlock > baseBlock + local defendersResolveAddsEHP = defendersResolveEHP > baseEHP + local adjacentNodeAddsBlock = adjacentBlockChance > defendersResolveBlock + local adjacentNodeAddsEHP = adjacentBlockEHP > defendersResolveEHP + + newBuild() + + assert.is_true(defendersResolveAddsBlock) + assert.is_true(defendersResolveAddsEHP) + assert.is_true(adjacentNodeAddsBlock) + assert.is_true(adjacentNodeAddsEHP) + end) +end) diff --git a/src/Modules/CalcDefence.lua b/src/Modules/CalcDefence.lua index 6c9ff685f7..8211b3e81b 100644 --- a/src/Modules/CalcDefence.lua +++ b/src/Modules/CalcDefence.lua @@ -3077,13 +3077,16 @@ function calcs.buildDefenceEstimations(env, actor) end iterationMultiplier = 1 -- to speed it up, run recursively but accelerated - local speedUp = data.misc.ehpCalcSpeedUp + -- Stateful pool mechanics are not associative: collapsing too many hits into one + -- can cross MoM/life-loss-prevention boundaries differently from repeated hits. + local speedUp = DamageIn["StatefulEHP"] and m_min(data.misc.ehpCalcSpeedUp, 4) or data.misc.ehpCalcSpeedUp DamageIn["cyclesRan"] = DamageIn["cyclesRan"] or false if not DamageIn["cyclesRan"] and poolTable.Life > 0 and DamageIn["iterations"] < maxIterations then Damage = { } for _, damageType in ipairs(dmgTypeList) do Damage[damageType] = DamageIn[damageType] * speedUp end + Damage["StatefulEHP"] = DamageIn["StatefulEHP"] if DamageIn.GainWhenHit then Damage.GainWhenHit = true Damage.LifeWhenHit = DamageIn.LifeWhenHit @@ -3135,6 +3138,7 @@ function calcs.buildDefenceEstimations(env, actor) for _, damageType in ipairs(dmgTypeList) do DamageIn[damageType] = output[damageType.."TakenHit"] end + DamageIn["StatefulEHP"] = output["preventedLifeLossTotal"] > 0 output["NumberOfDamagingHits"] = numberOfHitsToDie(DamageIn) end @@ -3226,6 +3230,7 @@ function calcs.buildDefenceEstimations(env, actor) output["LifeLossLostOverTime"] = 0 output["LifeBelowHalfLossLostOverTime"] = 0 end + DamageIn["StatefulEHP"] = DamageIn["TrackRecoupable"] or DamageIn["TrackLifeLossOverTime"] or DamageIn.GainWhenHit averageAvoidChance = averageAvoidChance / 5 output["ConfiguredDamageChance"] = 100 * (blockEffect * suppressionEffect * effectiveDeflectMulti * (1 - averageAvoidChance / 100)) output["NumberOfMitigatedDamagingHits"] = (output["ConfiguredDamageChance"] ~= 100 or DamageIn["TrackRecoupable"] or DamageIn["TrackLifeLossOverTime"] or DamageIn.GainWhenHit) and numberOfHitsToDie(DamageIn) or output["NumberOfDamagingHits"] From 6d61c547a77850370caf5dc73b2c1c2eb76e25da Mon Sep 17 00:00:00 2001 From: unrealdreamz <132005717+unrealdreamz@users.noreply.github.com> Date: Tue, 19 May 2026 14:53:05 -0400 Subject: [PATCH 02/11] Stabilize regression fixtures and total price order --- spec/Fixtures/issue1754_block_ehp.xml | 1 + spec/System/TestDefence_spec.lua | 9 +++++- spec/System/TestTradeQueryCurrency_spec.lua | 10 +++++++ src/Classes/TradeQuery.lua | 31 ++++++++++++++++----- src/HeadlessWrapper.lua | 3 ++ 5 files changed, 46 insertions(+), 8 deletions(-) diff --git a/spec/Fixtures/issue1754_block_ehp.xml b/spec/Fixtures/issue1754_block_ehp.xml index 4c6da1c865..25f4298c63 100644 --- a/spec/Fixtures/issue1754_block_ehp.xml +++ b/spec/Fixtures/issue1754_block_ehp.xml @@ -1,4 +1,5 @@ + diff --git a/spec/System/TestDefence_spec.lua b/spec/System/TestDefence_spec.lua index 73da8283a8..820c978ff7 100644 --- a/spec/System/TestDefence_spec.lua +++ b/spec/System/TestDefence_spec.lua @@ -542,6 +542,7 @@ describe("TestDefence", function() end local function loadIssue1754Build(extraNodeIds) + newBuild() local xmlText = readFixture("../spec/Fixtures/issue1754_block_ehp.xml") xmlText = xmlText:gsub('nodes="([^"]*)"', function(nodes) local allocated = { } @@ -558,7 +559,13 @@ describe("TestDefence", function() loadBuildFromXML(xmlText, "issue 1754") runCallback("OnFrame") runCallback("OnFrame") - return build.calcsTab.calcsOutput + local calcsOutput = build.calcsTab.calcsOutput + local result = { + TotalEHP = calcsOutput.TotalEHP, + EffectiveBlockChance = calcsOutput.EffectiveBlockChance, + } + newBuild() + return result end local base = loadIssue1754Build({ }) diff --git a/spec/System/TestTradeQueryCurrency_spec.lua b/spec/System/TestTradeQueryCurrency_spec.lua index d3cccb5298..1430e83e8e 100644 --- a/spec/System/TestTradeQueryCurrency_spec.lua +++ b/spec/System/TestTradeQueryCurrency_spec.lua @@ -61,5 +61,15 @@ describe("TradeQuery Currency Conversion", function() local result = mock_tradeQuery:GetTotalPriceString() assert.are.equal(result, "5 chaos, 10 div") end) + + it("keeps sparse row order while aggregating prices", function() + mock_tradeQuery.totalPrice = { + [2] = { currency = "chaos", amount = 3 }, + [4] = { currency = "div", amount = 1 }, + [5] = { currency = "chaos", amount = 2 }, + } + local result = mock_tradeQuery:GetTotalPriceString() + assert.are.equal(result, "5 chaos, 1 div") + end) end) end) diff --git a/src/Classes/TradeQuery.lua b/src/Classes/TradeQuery.lua index 95d62dd7db..625b576123 100644 --- a/src/Classes/TradeQuery.lua +++ b/src/Classes/TradeQuery.lua @@ -1021,16 +1021,33 @@ end -- Method to update the Total Price string sum of all items function TradeQueryClass:GetTotalPriceString() local text = "" - local sorted_price = { } - for _, entry in pairs(self.totalPrice) do - if sorted_price[entry.currency] then - sorted_price[entry.currency] = sorted_price[entry.currency] + entry.amount + local sortedPrice = { } + local currencyOrder = { } + local priceIndexes = { } + for index in pairs(self.totalPrice) do + t_insert(priceIndexes, index) + end + t_sort(priceIndexes, function(a, b) + if type(a) == "number" and type(b) == "number" then + return a < b + elseif type(a) == "number" then + return true + elseif type(b) == "number" then + return false + end + return tostring(a) < tostring(b) + end) + for _, index in ipairs(priceIndexes) do + local entry = self.totalPrice[index] + if sortedPrice[entry.currency] then + sortedPrice[entry.currency] = sortedPrice[entry.currency] + entry.amount else - sorted_price[entry.currency] = entry.amount + sortedPrice[entry.currency] = entry.amount + t_insert(currencyOrder, entry.currency) end end - for currency, value in pairs(sorted_price) do - text = text .. tostring(value) .. " " .. currency .. ", " + for _, currency in ipairs(currencyOrder) do + text = text .. tostring(sortedPrice[currency]) .. " " .. currency .. ", " end if text ~= "" then text = text:sub(1, -3) diff --git a/src/HeadlessWrapper.lua b/src/HeadlessWrapper.lua index 2b391a68b9..958c163a31 100644 --- a/src/HeadlessWrapper.lua +++ b/src/HeadlessWrapper.lua @@ -207,6 +207,9 @@ build = mainObject.main.modes["BUILD"] function newBuild() mainObject.main:SetMode("BUILD", false, "Help, I'm stuck in Path of Building!") runCallback("OnFrame") + if build and build.skillsTab then + build.skillsTab:UpdateGlobalGemCountAssignments() + end end function loadBuildFromXML(xmlText, name) mainObject.main:SetMode("BUILD", false, name or "", xmlText) From 81ba44210df7f090b335caf9d2dd045739745bd5 Mon Sep 17 00:00:00 2001 From: unrealdreamz <132005717+unrealdreamz@users.noreply.github.com> Date: Tue, 19 May 2026 15:00:15 -0400 Subject: [PATCH 03/11] Regenerate ModCache --- src/Data/ModCache.lua | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/src/Data/ModCache.lua b/src/Data/ModCache.lua index ad786db07a..59ed51fa81 100644 --- a/src/Data/ModCache.lua +++ b/src/Data/ModCache.lua @@ -5146,20 +5146,13 @@ c["Grants Skill: Kelari's Malediction"]={{[1]={flags=0,keywordFlags=0,name="Extr c["Grants Skill: Kelari, the Tainted Sands"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=1,skillId="SummonSandDjinnPlayer"}}},nil} c["Grants Skill: Level 11 Black Powder Blitz"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="BlackPowderBlitzReservationPlayer"}}},nil} c["Grants Skill: Level 11 Blink"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="BlinkReservationPlayer"}}},nil} -c["Grants Skill: Level 11 Bone Blast"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="BoneBlastPlayer"}}},nil} c["Grants Skill: Level 11 Bursting Fen Toad"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="ExplodingPoisonToadPlayer"}}},nil} -c["Grants Skill: Level 11 Chaos Bolt"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="WeaponGrantedChaosboltPlayer"}}},nil} c["Grants Skill: Level 11 Compose Requiem"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="CrossbowRequiemAmmoPlayer"}}},nil} c["Grants Skill: Level 11 Crackling Palm"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="CracklingPalmPlayer"}}},nil} c["Grants Skill: Level 11 Decompose"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="CorpseCloudPlayer"}}},nil} -c["Grants Skill: Level 11 Discipline"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="DisciplinePlayer"}}},nil} c["Grants Skill: Level 11 Ember Fusillade"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="EmberFusilladePlayer"}}},nil} -c["Grants Skill: Level 11 Feast of Flesh"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="FeastOfFleshPlayer"}}},nil} -c["Grants Skill: Level 11 Firebolt"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="FireboltPlayer"}}},nil} -c["Grants Skill: Level 11 Freezing Shards"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="FreezingShardsPlayer"}}},nil} c["Grants Skill: Level 11 Future-Past"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="FuturePastPlayer"}}},nil} c["Grants Skill: Level 11 Gemini Surge"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="GeminiSurgePlayer"}}},nil} -c["Grants Skill: Level 11 Heart of Ice"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="HeartOfIcePlayer"}}},nil} c["Grants Skill: Level 11 Herald of Ash"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="HeraldOfAshPlayer"}}},nil} c["Grants Skill: Level 11 Herald of Ice"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="HeraldOfIcePlayer"}}},nil} c["Grants Skill: Level 11 Herald of Thunder"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="HeraldOfThunderPlayer"}}},nil} @@ -5167,42 +5160,26 @@ c["Grants Skill: Level 11 Icestorm"]={{[1]={flags=0,keywordFlags=0,name="ExtraSk c["Grants Skill: Level 11 Impurity"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="ImpurityPlayer"}}},nil} c["Grants Skill: Level 11 Lightning Bolt"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="UniqueBreachLightningBoltPlayer"}}},nil} c["Grants Skill: Level 11 Living Bomb"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="LivingBombPlayer"}}},nil} -c["Grants Skill: Level 11 Malice"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="MalicePlayer"}}},nil} -c["Grants Skill: Level 11 Mana Drain"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="ManaDrainPlayer"}}},nil} -c["Grants Skill: Level 11 Parry"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="ParryPlayer"}}},nil} c["Grants Skill: Level 11 Phantasmal Arrow"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="PhantasmalArrowPlayer"}}},nil} -c["Grants Skill: Level 11 Power Siphon"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="PowerSiphonPlayer"}}},nil} c["Grants Skill: Level 11 Purity of Fire"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="PurityOfFirePlayer"}}},nil} c["Grants Skill: Level 11 Purity of Ice"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="PurityOfIcePlayer"}}},nil} c["Grants Skill: Level 11 Purity of Lightning"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="PurityOfLightningPlayer"}}},nil} -c["Grants Skill: Level 11 Raise Shield"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="ShieldBlockPlayer"}}},nil} -c["Grants Skill: Level 11 Sigil of Power"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="SigilOfPowerPlayer"}}},nil} -c["Grants Skill: Level 11 Skeletal Warrior Minion"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="SummonSkeletalWarriorsPlayer"}}},nil} -c["Grants Skill: Level 11 Solar Orb"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="SolarOrbPlayer"}}},nil} c["Grants Skill: Level 11 Spark"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="SparkPlayer"}}},nil} c["Grants Skill: Level 11 Thundergod's Wrath"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="MetaCastLightningSpellOnHitPlayer"}}},nil} c["Grants Skill: Level 11 Valako's Charge"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="ValakosChargePlayer"}}},nil} -c["Grants Skill: Level 11 Volatile Dead"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="VolatileDeadPlayer"}}},nil} c["Grants Skill: Level 11 Withering Presence"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="WitheringPresencePlayer"}}},nil} c["Grants Skill: Level 14 Life Remnants"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=14,skillId="LifeRemnantsPlayer"}}},nil} c["Grants Skill: Level 20 Black Powder Blitz"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="BlackPowderBlitzReservationPlayer"}}},nil} c["Grants Skill: Level 20 Blink"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="BlinkReservationPlayer"}}},nil} -c["Grants Skill: Level 20 Bone Blast"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="BoneBlastPlayer"}}},nil} c["Grants Skill: Level 20 Bursting Fen Toad"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="ExplodingPoisonToadPlayer"}}},nil} -c["Grants Skill: Level 20 Chaos Bolt"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="WeaponGrantedChaosboltPlayer"}}},nil} c["Grants Skill: Level 20 Chaotic Infusion"]={nil,nil} c["Grants Skill: Level 20 Chaotic Infusion 20% increased Attack Speed"]={nil,nil} c["Grants Skill: Level 20 Compose Requiem"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="CrossbowRequiemAmmoPlayer"}}},nil} c["Grants Skill: Level 20 Crackling Palm"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="CracklingPalmPlayer"}}},nil} c["Grants Skill: Level 20 Decompose"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="CorpseCloudPlayer"}}},nil} -c["Grants Skill: Level 20 Discipline"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="DisciplinePlayer"}}},nil} c["Grants Skill: Level 20 Ember Fusillade"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="EmberFusilladePlayer"}}},nil} -c["Grants Skill: Level 20 Feast of Flesh"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="FeastOfFleshPlayer"}}},nil} -c["Grants Skill: Level 20 Firebolt"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="FireboltPlayer"}}},nil} -c["Grants Skill: Level 20 Freezing Shards"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="FreezingShardsPlayer"}}},nil} c["Grants Skill: Level 20 Future-Past"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="FuturePastPlayer"}}},nil} c["Grants Skill: Level 20 Gemini Surge"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="GeminiSurgePlayer"}}},nil} -c["Grants Skill: Level 20 Heart of Ice"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="HeartOfIcePlayer"}}},nil} c["Grants Skill: Level 20 Herald of Ash"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="HeraldOfAshPlayer"}}},nil} c["Grants Skill: Level 20 Herald of Ice"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="HeraldOfIcePlayer"}}},nil} c["Grants Skill: Level 20 Herald of Thunder"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="HeraldOfThunderPlayer"}}},nil} @@ -5218,23 +5195,14 @@ c["Grants Skill: Level 20 Icestorm"]={{[1]={flags=0,keywordFlags=0,name="ExtraSk c["Grants Skill: Level 20 Impurity"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="ImpurityPlayer"}}},nil} c["Grants Skill: Level 20 Lightning Bolt"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="UniqueBreachLightningBoltPlayer"}}},nil} c["Grants Skill: Level 20 Living Bomb"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="LivingBombPlayer"}}},nil} -c["Grants Skill: Level 20 Malice"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="MalicePlayer"}}},nil} -c["Grants Skill: Level 20 Mana Drain"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="ManaDrainPlayer"}}},nil} -c["Grants Skill: Level 20 Parry"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="ParryPlayer"}}},nil} c["Grants Skill: Level 20 Phantasmal Arrow"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="PhantasmalArrowPlayer"}}},nil} c["Grants Skill: Level 20 Pinnacle of Power"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="PinnacleOfPowerPlayer"}}},nil} -c["Grants Skill: Level 20 Power Siphon"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="PowerSiphonPlayer"}}},nil} c["Grants Skill: Level 20 Purity of Fire"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="PurityOfFirePlayer"}}},nil} c["Grants Skill: Level 20 Purity of Ice"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="PurityOfIcePlayer"}}},nil} c["Grants Skill: Level 20 Purity of Lightning"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="PurityOfLightningPlayer"}}},nil} -c["Grants Skill: Level 20 Raise Shield"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="ShieldBlockPlayer"}}},nil} -c["Grants Skill: Level 20 Sigil of Power"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="SigilOfPowerPlayer"}}},nil} -c["Grants Skill: Level 20 Skeletal Warrior Minion"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="SummonSkeletalWarriorsPlayer"}}},nil} -c["Grants Skill: Level 20 Solar Orb"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="SolarOrbPlayer"}}},nil} c["Grants Skill: Level 20 Spark"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="SparkPlayer"}}},nil} c["Grants Skill: Level 20 Thundergod's Wrath"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="MetaCastLightningSpellOnHitPlayer"}}},nil} c["Grants Skill: Level 20 Valako's Charge"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="ValakosChargePlayer"}}},nil} -c["Grants Skill: Level 20 Volatile Dead"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="VolatileDeadPlayer"}}},nil} c["Grants Skill: Level 20 Withering Presence"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="WitheringPresencePlayer"}}},nil} c["Grants Skill: Life Remnants"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=1,skillId="LifeRemnantsPlayer"}}},nil} c["Grants Skill: Manifest Weapon"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=1,skillId="ManifestWeaponPlayer"}}},nil} From 53e244b0c7ee32cd00e373a47cf58d5100232fa4 Mon Sep 17 00:00:00 2001 From: unrealdreamz <132005717+unrealdreamz@users.noreply.github.com> Date: Tue, 19 May 2026 16:36:28 -0400 Subject: [PATCH 04/11] Strengthen regression coverage --- spec/System/TestDefence_spec.lua | 14 ++++++++++++++ spec/System/TestItemParse_spec.lua | 16 ++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/spec/System/TestDefence_spec.lua b/spec/System/TestDefence_spec.lua index 820c978ff7..9e40f45509 100644 --- a/spec/System/TestDefence_spec.lua +++ b/spec/System/TestDefence_spec.lua @@ -534,6 +534,10 @@ describe("TestDefence", function() local defendersResolveNodeId = 64327 local adjacentBlockNodeId = 39517 + local function assertClose(actual, expected) + assert.is_true(math.abs(actual - expected) < 0.01) + end + local function readFixture(path) local file = assert(io.open(path, "rb")) local contents = file:read("*a") @@ -563,6 +567,7 @@ describe("TestDefence", function() local result = { TotalEHP = calcsOutput.TotalEHP, EffectiveBlockChance = calcsOutput.EffectiveBlockChance, + NumberOfMitigatedDamagingHits = calcsOutput.NumberOfMitigatedDamagingHits, } newBuild() return result @@ -584,6 +589,15 @@ describe("TestDefence", function() newBuild() + assertClose(baseEHP, 220458.8068172) + assertClose(baseBlock, 40.04) + assertClose(base.NumberOfMitigatedDamagingHits, 48.806236082942) + assertClose(defendersResolveEHP, 224844.36839123) + assertClose(defendersResolveBlock, 43.16) + assertClose(defendersResolve.NumberOfMitigatedDamagingHits, 49.77713289867) + assertClose(adjacentBlockEHP, 227484.13584363) + assertClose(adjacentBlockChance, 44.98) + assertClose(adjacentBlock.NumberOfMitigatedDamagingHits, 50.361537374709) assert.is_true(defendersResolveAddsBlock) assert.is_true(defendersResolveAddsEHP) assert.is_true(adjacentNodeAddsBlock) diff --git a/spec/System/TestItemParse_spec.lua b/spec/System/TestItemParse_spec.lua index 27f7f234a3..d0b07260d4 100644 --- a/spec/System/TestItemParse_spec.lua +++ b/spec/System/TestItemParse_spec.lua @@ -142,6 +142,22 @@ describe("TestItemParse", function() assert.are.equals("Adds 39 to 62 Fire Damage", item.explicitModLines[1].line) end) + it("Pasted variable-level base granted skills parse at exact exported levels", function() + local cases = { + { base = "Bone Wand", line = "Grants Skill: Level 11 Bone Blast", skillId = "BoneBlastPlayer", level = 11 }, + { base = "Withered Wand", line = "Grants Skill: Level 20 Chaos Bolt", skillId = "WeaponGrantedChaosboltPlayer", level = 20 }, + { base = "Stoic Sceptre", line = "Grants Skill: Level 20 Discipline", skillId = "DisciplinePlayer", level = 20 }, + } + + for _, case in ipairs(cases) do + local item = new("Item", "Rarity: Normal\n" .. case.base .. "\nImplicits: 1\n" .. case.line) + + assert.are.equals(1, #item.grantedSkills) + assert.are.equals(case.skillId, item.grantedSkills[1].skillId) + assert.are.equals(case.level, item.grantedSkills[1].level) + end + end) + --TODO: POB2 Leagues? --it("League", function() --end) From 08e5fe829301c0e3d1a6205344a48db127a7ca1f Mon Sep 17 00:00:00 2001 From: LocalIdentity Date: Sat, 23 May 2026 00:15:18 +1000 Subject: [PATCH 05/11] Revert --- spec/System/TestTradeQueryCurrency_spec.lua | 10 ------- src/Classes/TradeQuery.lua | 31 +++++---------------- src/HeadlessWrapper.lua | 3 -- 3 files changed, 7 insertions(+), 37 deletions(-) diff --git a/spec/System/TestTradeQueryCurrency_spec.lua b/spec/System/TestTradeQueryCurrency_spec.lua index 1430e83e8e..d3cccb5298 100644 --- a/spec/System/TestTradeQueryCurrency_spec.lua +++ b/spec/System/TestTradeQueryCurrency_spec.lua @@ -61,15 +61,5 @@ describe("TradeQuery Currency Conversion", function() local result = mock_tradeQuery:GetTotalPriceString() assert.are.equal(result, "5 chaos, 10 div") end) - - it("keeps sparse row order while aggregating prices", function() - mock_tradeQuery.totalPrice = { - [2] = { currency = "chaos", amount = 3 }, - [4] = { currency = "div", amount = 1 }, - [5] = { currency = "chaos", amount = 2 }, - } - local result = mock_tradeQuery:GetTotalPriceString() - assert.are.equal(result, "5 chaos, 1 div") - end) end) end) diff --git a/src/Classes/TradeQuery.lua b/src/Classes/TradeQuery.lua index 625b576123..95d62dd7db 100644 --- a/src/Classes/TradeQuery.lua +++ b/src/Classes/TradeQuery.lua @@ -1021,33 +1021,16 @@ end -- Method to update the Total Price string sum of all items function TradeQueryClass:GetTotalPriceString() local text = "" - local sortedPrice = { } - local currencyOrder = { } - local priceIndexes = { } - for index in pairs(self.totalPrice) do - t_insert(priceIndexes, index) - end - t_sort(priceIndexes, function(a, b) - if type(a) == "number" and type(b) == "number" then - return a < b - elseif type(a) == "number" then - return true - elseif type(b) == "number" then - return false - end - return tostring(a) < tostring(b) - end) - for _, index in ipairs(priceIndexes) do - local entry = self.totalPrice[index] - if sortedPrice[entry.currency] then - sortedPrice[entry.currency] = sortedPrice[entry.currency] + entry.amount + local sorted_price = { } + for _, entry in pairs(self.totalPrice) do + if sorted_price[entry.currency] then + sorted_price[entry.currency] = sorted_price[entry.currency] + entry.amount else - sortedPrice[entry.currency] = entry.amount - t_insert(currencyOrder, entry.currency) + sorted_price[entry.currency] = entry.amount end end - for _, currency in ipairs(currencyOrder) do - text = text .. tostring(sortedPrice[currency]) .. " " .. currency .. ", " + for currency, value in pairs(sorted_price) do + text = text .. tostring(value) .. " " .. currency .. ", " end if text ~= "" then text = text:sub(1, -3) diff --git a/src/HeadlessWrapper.lua b/src/HeadlessWrapper.lua index 958c163a31..2b391a68b9 100644 --- a/src/HeadlessWrapper.lua +++ b/src/HeadlessWrapper.lua @@ -207,9 +207,6 @@ build = mainObject.main.modes["BUILD"] function newBuild() mainObject.main:SetMode("BUILD", false, "Help, I'm stuck in Path of Building!") runCallback("OnFrame") - if build and build.skillsTab then - build.skillsTab:UpdateGlobalGemCountAssignments() - end end function loadBuildFromXML(xmlText, name) mainObject.main:SetMode("BUILD", false, name or "", xmlText) From a8df859f22f59b496fd8131b67e8b365b2f2139b Mon Sep 17 00:00:00 2001 From: LocalIdentity Date: Sat, 23 May 2026 00:15:24 +1000 Subject: [PATCH 06/11] Revert "Regenerate ModCache" This reverts commit 81ba44210df7f090b335caf9d2dd045739745bd5. --- src/Data/ModCache.lua | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/Data/ModCache.lua b/src/Data/ModCache.lua index 59ed51fa81..ad786db07a 100644 --- a/src/Data/ModCache.lua +++ b/src/Data/ModCache.lua @@ -5146,13 +5146,20 @@ c["Grants Skill: Kelari's Malediction"]={{[1]={flags=0,keywordFlags=0,name="Extr c["Grants Skill: Kelari, the Tainted Sands"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=1,skillId="SummonSandDjinnPlayer"}}},nil} c["Grants Skill: Level 11 Black Powder Blitz"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="BlackPowderBlitzReservationPlayer"}}},nil} c["Grants Skill: Level 11 Blink"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="BlinkReservationPlayer"}}},nil} +c["Grants Skill: Level 11 Bone Blast"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="BoneBlastPlayer"}}},nil} c["Grants Skill: Level 11 Bursting Fen Toad"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="ExplodingPoisonToadPlayer"}}},nil} +c["Grants Skill: Level 11 Chaos Bolt"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="WeaponGrantedChaosboltPlayer"}}},nil} c["Grants Skill: Level 11 Compose Requiem"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="CrossbowRequiemAmmoPlayer"}}},nil} c["Grants Skill: Level 11 Crackling Palm"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="CracklingPalmPlayer"}}},nil} c["Grants Skill: Level 11 Decompose"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="CorpseCloudPlayer"}}},nil} +c["Grants Skill: Level 11 Discipline"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="DisciplinePlayer"}}},nil} c["Grants Skill: Level 11 Ember Fusillade"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="EmberFusilladePlayer"}}},nil} +c["Grants Skill: Level 11 Feast of Flesh"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="FeastOfFleshPlayer"}}},nil} +c["Grants Skill: Level 11 Firebolt"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="FireboltPlayer"}}},nil} +c["Grants Skill: Level 11 Freezing Shards"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="FreezingShardsPlayer"}}},nil} c["Grants Skill: Level 11 Future-Past"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="FuturePastPlayer"}}},nil} c["Grants Skill: Level 11 Gemini Surge"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="GeminiSurgePlayer"}}},nil} +c["Grants Skill: Level 11 Heart of Ice"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="HeartOfIcePlayer"}}},nil} c["Grants Skill: Level 11 Herald of Ash"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="HeraldOfAshPlayer"}}},nil} c["Grants Skill: Level 11 Herald of Ice"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="HeraldOfIcePlayer"}}},nil} c["Grants Skill: Level 11 Herald of Thunder"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="HeraldOfThunderPlayer"}}},nil} @@ -5160,26 +5167,42 @@ c["Grants Skill: Level 11 Icestorm"]={{[1]={flags=0,keywordFlags=0,name="ExtraSk c["Grants Skill: Level 11 Impurity"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="ImpurityPlayer"}}},nil} c["Grants Skill: Level 11 Lightning Bolt"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="UniqueBreachLightningBoltPlayer"}}},nil} c["Grants Skill: Level 11 Living Bomb"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="LivingBombPlayer"}}},nil} +c["Grants Skill: Level 11 Malice"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="MalicePlayer"}}},nil} +c["Grants Skill: Level 11 Mana Drain"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="ManaDrainPlayer"}}},nil} +c["Grants Skill: Level 11 Parry"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="ParryPlayer"}}},nil} c["Grants Skill: Level 11 Phantasmal Arrow"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="PhantasmalArrowPlayer"}}},nil} +c["Grants Skill: Level 11 Power Siphon"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="PowerSiphonPlayer"}}},nil} c["Grants Skill: Level 11 Purity of Fire"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="PurityOfFirePlayer"}}},nil} c["Grants Skill: Level 11 Purity of Ice"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="PurityOfIcePlayer"}}},nil} c["Grants Skill: Level 11 Purity of Lightning"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="PurityOfLightningPlayer"}}},nil} +c["Grants Skill: Level 11 Raise Shield"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="ShieldBlockPlayer"}}},nil} +c["Grants Skill: Level 11 Sigil of Power"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="SigilOfPowerPlayer"}}},nil} +c["Grants Skill: Level 11 Skeletal Warrior Minion"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="SummonSkeletalWarriorsPlayer"}}},nil} +c["Grants Skill: Level 11 Solar Orb"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="SolarOrbPlayer"}}},nil} c["Grants Skill: Level 11 Spark"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="SparkPlayer"}}},nil} c["Grants Skill: Level 11 Thundergod's Wrath"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="MetaCastLightningSpellOnHitPlayer"}}},nil} c["Grants Skill: Level 11 Valako's Charge"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="ValakosChargePlayer"}}},nil} +c["Grants Skill: Level 11 Volatile Dead"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="VolatileDeadPlayer"}}},nil} c["Grants Skill: Level 11 Withering Presence"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=11,skillId="WitheringPresencePlayer"}}},nil} c["Grants Skill: Level 14 Life Remnants"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=14,skillId="LifeRemnantsPlayer"}}},nil} c["Grants Skill: Level 20 Black Powder Blitz"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="BlackPowderBlitzReservationPlayer"}}},nil} c["Grants Skill: Level 20 Blink"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="BlinkReservationPlayer"}}},nil} +c["Grants Skill: Level 20 Bone Blast"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="BoneBlastPlayer"}}},nil} c["Grants Skill: Level 20 Bursting Fen Toad"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="ExplodingPoisonToadPlayer"}}},nil} +c["Grants Skill: Level 20 Chaos Bolt"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="WeaponGrantedChaosboltPlayer"}}},nil} c["Grants Skill: Level 20 Chaotic Infusion"]={nil,nil} c["Grants Skill: Level 20 Chaotic Infusion 20% increased Attack Speed"]={nil,nil} c["Grants Skill: Level 20 Compose Requiem"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="CrossbowRequiemAmmoPlayer"}}},nil} c["Grants Skill: Level 20 Crackling Palm"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="CracklingPalmPlayer"}}},nil} c["Grants Skill: Level 20 Decompose"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="CorpseCloudPlayer"}}},nil} +c["Grants Skill: Level 20 Discipline"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="DisciplinePlayer"}}},nil} c["Grants Skill: Level 20 Ember Fusillade"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="EmberFusilladePlayer"}}},nil} +c["Grants Skill: Level 20 Feast of Flesh"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="FeastOfFleshPlayer"}}},nil} +c["Grants Skill: Level 20 Firebolt"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="FireboltPlayer"}}},nil} +c["Grants Skill: Level 20 Freezing Shards"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="FreezingShardsPlayer"}}},nil} c["Grants Skill: Level 20 Future-Past"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="FuturePastPlayer"}}},nil} c["Grants Skill: Level 20 Gemini Surge"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="GeminiSurgePlayer"}}},nil} +c["Grants Skill: Level 20 Heart of Ice"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="HeartOfIcePlayer"}}},nil} c["Grants Skill: Level 20 Herald of Ash"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="HeraldOfAshPlayer"}}},nil} c["Grants Skill: Level 20 Herald of Ice"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="HeraldOfIcePlayer"}}},nil} c["Grants Skill: Level 20 Herald of Thunder"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="HeraldOfThunderPlayer"}}},nil} @@ -5195,14 +5218,23 @@ c["Grants Skill: Level 20 Icestorm"]={{[1]={flags=0,keywordFlags=0,name="ExtraSk c["Grants Skill: Level 20 Impurity"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="ImpurityPlayer"}}},nil} c["Grants Skill: Level 20 Lightning Bolt"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="UniqueBreachLightningBoltPlayer"}}},nil} c["Grants Skill: Level 20 Living Bomb"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="LivingBombPlayer"}}},nil} +c["Grants Skill: Level 20 Malice"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="MalicePlayer"}}},nil} +c["Grants Skill: Level 20 Mana Drain"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="ManaDrainPlayer"}}},nil} +c["Grants Skill: Level 20 Parry"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="ParryPlayer"}}},nil} c["Grants Skill: Level 20 Phantasmal Arrow"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="PhantasmalArrowPlayer"}}},nil} c["Grants Skill: Level 20 Pinnacle of Power"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="PinnacleOfPowerPlayer"}}},nil} +c["Grants Skill: Level 20 Power Siphon"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="PowerSiphonPlayer"}}},nil} c["Grants Skill: Level 20 Purity of Fire"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="PurityOfFirePlayer"}}},nil} c["Grants Skill: Level 20 Purity of Ice"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="PurityOfIcePlayer"}}},nil} c["Grants Skill: Level 20 Purity of Lightning"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="PurityOfLightningPlayer"}}},nil} +c["Grants Skill: Level 20 Raise Shield"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="ShieldBlockPlayer"}}},nil} +c["Grants Skill: Level 20 Sigil of Power"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="SigilOfPowerPlayer"}}},nil} +c["Grants Skill: Level 20 Skeletal Warrior Minion"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="SummonSkeletalWarriorsPlayer"}}},nil} +c["Grants Skill: Level 20 Solar Orb"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="SolarOrbPlayer"}}},nil} c["Grants Skill: Level 20 Spark"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="SparkPlayer"}}},nil} c["Grants Skill: Level 20 Thundergod's Wrath"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="MetaCastLightningSpellOnHitPlayer"}}},nil} c["Grants Skill: Level 20 Valako's Charge"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="ValakosChargePlayer"}}},nil} +c["Grants Skill: Level 20 Volatile Dead"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="VolatileDeadPlayer"}}},nil} c["Grants Skill: Level 20 Withering Presence"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=20,skillId="WitheringPresencePlayer"}}},nil} c["Grants Skill: Life Remnants"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=1,skillId="LifeRemnantsPlayer"}}},nil} c["Grants Skill: Manifest Weapon"]={{[1]={flags=0,keywordFlags=0,name="ExtraSkill",type="LIST",value={level=1,skillId="ManifestWeaponPlayer"}}},nil} From b160fe5d112ddde46a4b11977a705437b4557ce7 Mon Sep 17 00:00:00 2001 From: LocalIdentity Date: Sat, 23 May 2026 00:15:49 +1000 Subject: [PATCH 07/11] Revert wrong test --- spec/System/TestItemParse_spec.lua | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/spec/System/TestItemParse_spec.lua b/spec/System/TestItemParse_spec.lua index d0b07260d4..27f7f234a3 100644 --- a/spec/System/TestItemParse_spec.lua +++ b/spec/System/TestItemParse_spec.lua @@ -142,22 +142,6 @@ describe("TestItemParse", function() assert.are.equals("Adds 39 to 62 Fire Damage", item.explicitModLines[1].line) end) - it("Pasted variable-level base granted skills parse at exact exported levels", function() - local cases = { - { base = "Bone Wand", line = "Grants Skill: Level 11 Bone Blast", skillId = "BoneBlastPlayer", level = 11 }, - { base = "Withered Wand", line = "Grants Skill: Level 20 Chaos Bolt", skillId = "WeaponGrantedChaosboltPlayer", level = 20 }, - { base = "Stoic Sceptre", line = "Grants Skill: Level 20 Discipline", skillId = "DisciplinePlayer", level = 20 }, - } - - for _, case in ipairs(cases) do - local item = new("Item", "Rarity: Normal\n" .. case.base .. "\nImplicits: 1\n" .. case.line) - - assert.are.equals(1, #item.grantedSkills) - assert.are.equals(case.skillId, item.grantedSkills[1].skillId) - assert.are.equals(case.level, item.grantedSkills[1].level) - end - end) - --TODO: POB2 Leagues? --it("League", function() --end) From 7b2ae3ecbd4c9a3b4155eb052aa45e5d13e9f57b Mon Sep 17 00:00:00 2001 From: LocalIdentity Date: Sat, 23 May 2026 00:24:02 +1000 Subject: [PATCH 08/11] Change variable name --- src/Modules/CalcDefence.lua | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Modules/CalcDefence.lua b/src/Modules/CalcDefence.lua index 8211b3e81b..2abe1a59ca 100644 --- a/src/Modules/CalcDefence.lua +++ b/src/Modules/CalcDefence.lua @@ -3077,16 +3077,16 @@ function calcs.buildDefenceEstimations(env, actor) end iterationMultiplier = 1 -- to speed it up, run recursively but accelerated - -- Stateful pool mechanics are not associative: collapsing too many hits into one - -- can cross MoM/life-loss-prevention boundaries differently from repeated hits. - local speedUp = DamageIn["StatefulEHP"] and m_min(data.misc.ehpCalcSpeedUp, 4) or data.misc.ehpCalcSpeedUp + -- MoM/life-loss-prevention mechanics can collapse too many hits into one + -- resulting in eHP jumps so we slow the acceleration. + local speedUp = DamageIn["LimitEHPSpeedup"] and 4 or data.misc.ehpCalcSpeedUp DamageIn["cyclesRan"] = DamageIn["cyclesRan"] or false if not DamageIn["cyclesRan"] and poolTable.Life > 0 and DamageIn["iterations"] < maxIterations then Damage = { } for _, damageType in ipairs(dmgTypeList) do Damage[damageType] = DamageIn[damageType] * speedUp end - Damage["StatefulEHP"] = DamageIn["StatefulEHP"] + Damage["LimitEHPSpeedup"] = DamageIn["LimitEHPSpeedup"] if DamageIn.GainWhenHit then Damage.GainWhenHit = true Damage.LifeWhenHit = DamageIn.LifeWhenHit @@ -3138,7 +3138,7 @@ function calcs.buildDefenceEstimations(env, actor) for _, damageType in ipairs(dmgTypeList) do DamageIn[damageType] = output[damageType.."TakenHit"] end - DamageIn["StatefulEHP"] = output["preventedLifeLossTotal"] > 0 + DamageIn["LimitEHPSpeedup"] = output["preventedLifeLossTotal"] > 0 output["NumberOfDamagingHits"] = numberOfHitsToDie(DamageIn) end @@ -3230,7 +3230,7 @@ function calcs.buildDefenceEstimations(env, actor) output["LifeLossLostOverTime"] = 0 output["LifeBelowHalfLossLostOverTime"] = 0 end - DamageIn["StatefulEHP"] = DamageIn["TrackRecoupable"] or DamageIn["TrackLifeLossOverTime"] or DamageIn.GainWhenHit + DamageIn["LimitEHPSpeedup"] = DamageIn["TrackRecoupable"] or DamageIn["TrackLifeLossOverTime"] or DamageIn.GainWhenHit averageAvoidChance = averageAvoidChance / 5 output["ConfiguredDamageChance"] = 100 * (blockEffect * suppressionEffect * effectiveDeflectMulti * (1 - averageAvoidChance / 100)) output["NumberOfMitigatedDamagingHits"] = (output["ConfiguredDamageChance"] ~= 100 or DamageIn["TrackRecoupable"] or DamageIn["TrackLifeLossOverTime"] or DamageIn.GainWhenHit) and numberOfHitsToDie(DamageIn) or output["NumberOfDamagingHits"] From 9b1fd9ac00834b257a6dd45543466c204f215334 Mon Sep 17 00:00:00 2001 From: LocalIdentity Date: Sat, 23 May 2026 00:43:39 +1000 Subject: [PATCH 09/11] Simplify test --- spec/Fixtures/issue1754_block_ehp.xml | 796 -------------------------- spec/System/TestDefence_spec.lua | 80 +-- 2 files changed, 23 insertions(+), 853 deletions(-) delete mode 100644 spec/Fixtures/issue1754_block_ehp.xml diff --git a/spec/Fixtures/issue1754_block_ehp.xml b/spec/Fixtures/issue1754_block_ehp.xml deleted file mode 100644 index 25f4298c63..0000000000 --- a/spec/Fixtures/issue1754_block_ehp.xml +++ /dev/null @@ -1,796 +0,0 @@ - - - - - - - - - - -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - https://www.pathofexile.com/passive-skill-tree/AAAABgIBmLOoS-B_NJGKbCdclipOP9vFgWhlumpKQ4WadaMPYOFndqMZgWxOGXJISbq0Jhb710aZbpD4W2rtFgrJ6lMmjBfMleSg7Nq7hs48U4uSjxwyHiT562uU01NjHsudsTO7jpqu11u3nmdF0J0_KE9-axrZ-s1MlmUEsLAix7koetQKECBG7QsfgxSMu2_FO48BbNNvmSf31deWjCRhMA12mKu2W4f9CzAeKZGcBJEPlG4PJaZorn6ggqzrKw5MD232igVIcwdIxW9-r5hpyqR_wIVyi8p4aqAJuy4KlPLfbNPYZdZGT2nkKhbJabT1ZBOopqic1iLpgSEt-xzWG9ixI8v0sDtO9CIWTti2z8pBVYJZ6YnoxUKFgu7R12fIMILKFHqNgNAu4EKV9f1g5xTUs5EAAA== - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Rarity: RARE -Chimeric Sliver -Ruby -Unique ID: 3f2a054bb6d41d7fe0d14122cc3d55f5b9055285249b5bf81d740ad1d59b6136 -Item Level: 80 -LevelReq: 0 -Implicits: 0 -18% increased Armour -14% increased Attack Damage -19% increased Warcry Speed -{desecrated}2% increased Strength - - - - - - - Rarity: RARE -Loath Wound -Time-Lost Ruby -Unique ID: 79ae21e9e5c5904ecda572dff79ab1d719eac798e1d9cfb35975a9116d915bc5 -Item Level: 78 -LevelReq: 0 -Radius: Medium -Implicits: 0 -Upgrades Radius to Medium -25% increased Effect of Notable Passive Skills in Radius -25% increased Effect of Small Passive Skills in Radius - - - - - - Rarity: UNIQUE -From Nothing -Diamond -Unique ID: 60219c236ed8e1330ab16b8919afb429b870bc28c23e445c4da72a8d0b22b76c -Item Level: 84 -LevelReq: 0 -Radius: Small -Limited to: 1 -Implicits: 0 -Passives in Radius of Resolute Technique can be Allocated -without being connected to your tree -Corrupted - - - - Rarity: RARE -Ghoul Sole -Tasalian Greaves -Armour: 432 -Unique ID: 816f7b3213e27a3f552b294e0066bcb695b3c7a2075c16a000c1cd456ab4311c -Item Level: 82 -Quality: 20 -Sockets: S S -Rune: Soul Core of Tacati -Rune: Soul Core of Tacati -LevelReq: 80 -Implicits: 1 -{enchant}{rune}+22% to Chaos Resistance -+123 to Armour -+110 to maximum Life -+31% to Fire Resistance -+30% to Lightning Resistance -+32% of Armour also applies to Elemental Damage -{desecrated}35% increased Movement Speed -Corrupted - - - - - - - - - - Rarity: RARE -Torment Touch -Vaal Mitts -Armour: 485 -Unique ID: ec3d3e1408f66406a34cc183c6ffb0a87025355c3295bc90ab8be9f8d32c539e -Item Level: 79 -Quality: 20 -Sockets: S -Rune: Idol of Grold -LevelReq: 75 -Implicits: 2 -{enchant}{rune}32% increased total Power counted by Warcries -{enchant}{rune}Bonded: 32% increased Glory generation -36% increased Armour -+156 to maximum Life -+2 to Level of all Melee Skills -+42% of Armour also applies to Elemental Damage -60% increased effect of Socketed Items -{desecrated}+151 to Armour - - - - - - - - - - - Rarity: NORMAL -Evasive Leg -Unique ID: ef550b724c26f2472f48418e92bafea5c8c4a804a960cc7b09066163fba3e44e -Item Level: 99 -LevelReq: 0 -Implicits: 1 -20% increased Evasion Rating - - - - Rarity: NORMAL -Combat Arm -Unique ID: f36295e737b0e7d2e531713f8153dd41e79d029fb6591afd25302dd9f630b099 -Item Level: 99 -LevelReq: 0 -Implicits: 1 -10% increased Attack Speed - - - - Rarity: NORMAL -Guarding Arm -Unique ID: 8a1cb54bc4dfaba7a8cbdc7046e0726011a7a0424d8db8d9e9560e694079f61f -Item Level: 99 -LevelReq: 0 -Implicits: 1 -10% increased Block chance - - - - Rarity: MAGIC -Experimenter's Staunching Charm of the Bountiful -Unique ID: 7b7a2820b483555344ef9156b5dffceebb5dc44774c9109af7f472a6d9a518f8 -Item Level: 81 -Quality: 17 -LevelReq: 18 -Implicits: 1 -Used when you start Bleeding -36% increased Duration -53% increased Charges - - - - - Rarity: UNIQUE -Rite of Passage -Golden Charm -Unique ID: 897c5364a769621daab36f5fcee7e3a3264b236f7cceac264eb9f37546caf810 -Item Level: 81 -Quality: 0 -LevelReq: 50 -Implicits: 1 -Used when you kill a Rare or Unique enemy -Possessed by Spirit Of The Ox for 20 seconds on use - - - Rarity: RARE -Phoenix Bind -Plate Belt -Charm Slots: 2 -Unique ID: 430b1c0f872adfb91474657f630483dbeb06359ccc10a3de78d59f9dc469c3b4 -Item Level: 79 -LevelReq: 24 -Implicits: 2 -+121 to Armour -Has 2 Charm Slots -+142 to Armour -+169 to maximum Life -+123 to maximum Mana -+26 to Strength -+39% to Cold Resistance -{desecrated}+17% to Fire and Chaos Resistances - - - - - - - - - - - Rarity: RARE -Phoenix Pendant -Pearlescent Amulet -Unique ID: f3ab9699d86ffae4a2943e4aab99607bce30d6ae9bdb0ef28b51e1d9eab928ca -Item Level: 78 -LevelReq: 30 -Implicits: 2 -{enchant}Allocates Reinforced Barrier -+10% to all Elemental Resistances -+175 to maximum Life -9% increased maximum Life -+33 to Strength -+32% to Fire Resistance -+29% to Cold Resistance -{desecrated}34% increased Armour - - - - - - - - - - - Rarity: MAGIC -Substantial Ultimate Mana Flask of the Abundant -Unique ID: 2f3e029b0a0e34fc09b8f199f1617aab732678e3f4058ac2b4ab858a14607528 -Item Level: 80 -Quality: 10 -LevelReq: 60 -Implicits: 0 -62% increased Amount Recovered -57% increased Charges - - - - - Rarity: RARE -Onslaught Crest -Imperial Greathelm -Armour: 1269 -Unique ID: 26849265030f23ddccb3e1ddd940f30695811b039ce53ac28c6086af7ca9e2a4 -Item Level: 83 -Quality: 30 -Sockets: S -Rune: Soul Core of Tacati -LevelReq: 80 -Implicits: 1 -{enchant}{rune}+11% to Chaos Resistance -+195 to Armour -91% increased Armour -+174 to maximum Life -+29 to Strength -+29% to Cold Resistance -{desecrated}+29% of Armour also applies to Chaos Damage -Corrupted - - - - - - - - - - Rarity: MAGIC -Seething Ultimate Life Flask of the Brewer -Unique ID: 2375dec92a0147d9ea02dc37bc7f94fb4a993d0886a056c1507e2e836eea3934 -Item Level: 79 -Quality: 22 -LevelReq: 60 -Implicits: 0 -50% reduced Amount Recovered -Instant Recovery -29% reduced Charges per use - - - - - - Rarity: RARE -Armageddon Shatter -Structured Hammer -Unique ID: 0ad6ea8224a123ac010f9c13bee9c7ae415c70fd127db1c79aa110449e0eb4e5 -Item Level: 82 -Quality: 20 -Sockets: S -Rune: Farrul's Rune of the Hunt -LevelReq: 62 -Implicits: 3 -{enchant}{rune}50% increased Attack Damage against Rare or Unique Enemies -{enchant}{rune}Bonded: +1 to Level of all Attack Skills -40% chance to Daze on Hit -Adds 11 to 19 Physical Damage -96% increased Elemental Damage with Attacks -+5 to Level of all Melee Skills -+26 to Strength -Gain 20% of Damage as Extra Physical Damage -{desecrated}Break Armour equal to 4% of Physical Damage dealt - - - - - - - - - - - Rarity: RARE -Carrion Span -Tawhoan Tower Shield -Armour: 1215 -Unique ID: 2957b34aa214fbdc6d0b652646718c039598d898641ffb48e728d0266aa3343f -Item Level: 80 -Quality: 20 -Sockets: S S -Rune: Greater Iron Rune -Rune: Greater Iron Rune -LevelReq: 80 -Implicits: 4 -{enchant}{rune}36% increased Armour, Evasion and Energy Shield -{enchant}{rune}Bonded: +20 to maximum Life -{enchant}{rune}Bonded: +20 to maximum Mana -Grants Skill: Raise Shield -+253 to Armour -89% increased Armour -+185 to maximum Life -+31 to Strength -8% additional Physical Damage Reduction -+28% to Cold Resistance -Corrupted - - - - - - - - - - - - - Rarity: MAGIC -Experimenter's Grounding Charm of the Ample -Unique ID: 4a9b1924da5c16f078735bbea50effa0b1a2ed71596e0ddf13ead3596c58fc35 -Item Level: 81 -Quality: 0 -LevelReq: 32 -Implicits: 1 -Used when you become Shocked -38% increased Duration -69% increased Charges - - - - - Rarity: RARE -Demon Grip -Amethyst Ring -Unique ID: 3a9a4bc4c6c7bf6c851d9e3465e170235570e40f8d701c4bbd68eb17f54bc0e1 -Item Level: 80 -LevelReq: 20 -Implicits: 1 -+11% to Chaos Resistance -+199 to Evasion Rating -+172 to maximum Mana -+29 to Strength -+34% to Lightning Resistance -Leech 6.34% of Physical Attack Damage as Mana -{desecrated}+115 to maximum Life - - - - - - - - - - Rarity: RARE -Pain Shelter -Soldier Cuirass -Armour: 2031 -Unique ID: c1272d9098b999d72429ccdf1fe25c25781e51c8b2d9392ca792f7a41c8ab87e -Item Level: 79 -Quality: 24 -Sockets: S S -Rune: The Greatwolf's Rune of Willpower -Rune: Craiceann's Rune of Warding -LevelReq: 65 -Implicits: 4 -{enchant}{rune}50% reduced effect of Curses on you -{enchant}{rune}15% of Damage is taken from Mana before Life -{enchant}{rune}Bonded: 8% increased Curse Magnitudes -{enchant}{rune}Bonded: 8% of Maximum Life Converted to Energy Shield -+265 to Armour -105% increased Armour -10% increased maximum Life -Hits against you have 47% reduced Critical Damage Bonus -+30% to Lightning Resistance -{desecrated}+43% of Armour also applies to Elemental Damage -Corrupted - - - - - - - - - - - - - Rarity: NORMAL -Evasive Leg -Unique ID: 1170f441dfecf0cfa4c4f7b19833710477ff62ac3fb66c2c1c658daa1f097ccf -Item Level: 99 -LevelReq: 0 -Implicits: 1 -30% increased Evasion Rating - - - - Rarity: RARE -Kraken Hold -Prismatic Ring -Unique ID: 5942bb3b406d8bb45907c9efb803bc7c1e516064294f1739977e1d4f5fcb112c -Item Level: 80 -LevelReq: 35 -Implicits: 1 -+12% to all Elemental Resistances -+164 to Evasion Rating -+116 to maximum Life -+156 to maximum Mana -+35% to Fire Resistance -Leech 7.39% of Physical Attack Damage as Life -{desecrated}+20% to Lightning and Chaos Resistances - - - - - - - - - - Rarity: UNIQUE -Carrion Span -Tawhoan Tower Shield -Armour: 1215 -Unique ID: 2957b34aa214fbdc6d0b652646718c039598d898641ffb48e728d0266aa3343f -Item Level: 80 -Quality: 20 -Sockets: S S -Rune: Greater Iron Rune -Rune: Greater Iron Rune -LevelReq: 80 -Implicits: 4 -{enchant}{rune}36% increased Armour, Evasion and Energy Shield -{enchant}{rune}Bonded: +20 to maximum Life -{enchant}{rune}Bonded: +20 to maximum Mana -Grants Skill: Raise Shield -+253 to Armour -89% increased Armour -+185 to maximum Life -+31 to Strength -+28% to Cold Resistance -Corrupted - - - - - - - - - - - - Rarity: UNIQUE -Undying Hate -Timeless Jewel -Variant: Amanamu -Variant: Kulemak -Variant: Kurgal -Variant: Tecrod -Variant: Ulaman -Selected Variant: 1 -LevelReq: 0 -Radius: Very Large -Limited to: 1 -Implicits: 0 -{variant:1}{range:0.5}Glorifying the defilement of (100-8000) souls in tribute to Amanamu -{variant:2}{range:0.5}Glorifying the defilement of (100-8000) souls in tribute to Kulemak -{variant:3}{range:0.5}Glorifying the defilement of (100-8000) souls in tribute to Kurgal -{variant:4}{range:0.5}Glorifying the defilement of (100-8000) souls in tribute to Tecrod -{variant:5}{range:0.5}Glorifying the defilement of (100-8000) souls in tribute to Ulaman -Passives in radius are Conquered by the Abyssals -Historic - - - - - - - - - - Rarity: UNIQUE -Pain Shelter -Soldier Cuirass -Armour: 2031 -Unique ID: c1272d9098b999d72429ccdf1fe25c25781e51c8b2d9392ca792f7a41c8ab87e -Item Level: 79 -Quality: 24 -Sockets: S S -Rune: The Greatwolf's Rune of Willpower -Rune: Craiceann's Rune of Warding -LevelReq: 65 -Implicits: 4 -{enchant}{rune}50% reduced effect of Curses on you -{enchant}{rune}Bonded: 8% increased Curse Magnitudes -{enchant}{rune}Bonded: 8% of Maximum Life Converted to Energy Shield -+265 to Armour -105% increased Armour -10% increased maximum Life -Hits against you have 47% reduced Critical Damage Bonus -+30% to Lightning Resistance -{desecrated}+43% of Armour also applies to Elemental Damage -Corrupted - - - - - - - - - - - - Rarity: UNIQUE -Torment Touch -Vaal Mitts -Armour: 485 -Unique ID: ec3d3e1408f66406a34cc183c6ffb0a87025355c3295bc90ab8be9f8d32c539e -Item Level: 79 -Quality: 20 -Sockets: S -Rune: Idol of Grold -LevelReq: 75 -Implicits: 2 -{enchant}{rune}32% increased total Power counted by Warcries -{enchant}{rune}Bonded: 32% increased Glory generation -36% increased Armour -+156 to maximum Life -+2 to Level of all Melee Skills -+42% of Armour also applies to Elemental Damage -+170 to mana -{desecrated}+151 to Armour - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/spec/System/TestDefence_spec.lua b/spec/System/TestDefence_spec.lua index 9e40f45509..37bc257a40 100644 --- a/spec/System/TestDefence_spec.lua +++ b/spec/System/TestDefence_spec.lua @@ -528,79 +528,45 @@ describe("TestDefence", function() assert.are.equals(0, floor(poolsRemaining.OverkillDamage)) end) - it("does not reduce stateful EHP when block chance increases", function() - -- Regression fixture from #1754. The build uses life-loss prevention and MoM, - -- so the EHP speedup path must preserve monotonic block improvements. - local defendersResolveNodeId = 64327 - local adjacentBlockNodeId = 39517 - + it("limits EHP speedup when hit damage is delayed", function() local function assertClose(actual, expected) assert.is_true(math.abs(actual - expected) < 0.01) end - local function readFixture(path) - local file = assert(io.open(path, "rb")) - local contents = file:read("*a") - file:close() - return contents - end - - local function loadIssue1754Build(extraNodeIds) + local function calcEHP(extraMods) newBuild() - local xmlText = readFixture("../spec/Fixtures/issue1754_block_ehp.xml") - xmlText = xmlText:gsub('nodes="([^"]*)"', function(nodes) - local allocated = { } - for nodeId in nodes:gmatch("%d+") do - allocated[nodeId] = true - end - for _, nodeId in ipairs(extraNodeIds) do - if not allocated[tostring(nodeId)] then - nodes = nodes .. "," .. tostring(nodeId) - end - end - return 'nodes="' .. nodes .. '"' - end, 1) - loadBuildFromXML(xmlText, "issue 1754") + build.configTab.input.enemyPhysicalDamage = "1000" + build.configTab.input.enemyFireDamage = "1000" + build.configTab.input.enemyColdDamage = "1000" + build.configTab.input.enemyLightningDamage = "1000" + build.configTab.input.enemyChaosDamage = "0" + build.configTab.input.customMods = [[ ++4000 to maximum Life ++4000 to maximum Mana +75% of Damage is taken from Mana before Life +25% of Life Loss from Hits is prevented, then that much Life is lost over 4 seconds instead ++75% to all Elemental Resistances ++75% to Chaos Resistance +]] .. (extraMods or "") + build.configTab:BuildModList() runCallback("OnFrame") runCallback("OnFrame") local calcsOutput = build.calcsTab.calcsOutput - local result = { + return { TotalEHP = calcsOutput.TotalEHP, EffectiveBlockChance = calcsOutput.EffectiveBlockChance, NumberOfMitigatedDamagingHits = calcsOutput.NumberOfMitigatedDamagingHits, } - newBuild() - return result end - local base = loadIssue1754Build({ }) - local baseEHP = base.TotalEHP - local baseBlock = base.EffectiveBlockChance - local defendersResolve = loadIssue1754Build({ defendersResolveNodeId }) - local defendersResolveEHP = defendersResolve.TotalEHP - local defendersResolveBlock = defendersResolve.EffectiveBlockChance - local adjacentBlock = loadIssue1754Build({ defendersResolveNodeId, adjacentBlockNodeId }) - local adjacentBlockEHP = adjacentBlock.TotalEHP - local adjacentBlockChance = adjacentBlock.EffectiveBlockChance - local defendersResolveAddsBlock = defendersResolveBlock > baseBlock - local defendersResolveAddsEHP = defendersResolveEHP > baseEHP - local adjacentNodeAddsBlock = adjacentBlockChance > defendersResolveBlock - local adjacentNodeAddsEHP = adjacentBlockEHP > defendersResolveEHP + local base = calcEHP() + local block = calcEHP("\n+10% to Block chance\n") newBuild() - assertClose(baseEHP, 220458.8068172) - assertClose(baseBlock, 40.04) - assertClose(base.NumberOfMitigatedDamagingHits, 48.806236082942) - assertClose(defendersResolveEHP, 224844.36839123) - assertClose(defendersResolveBlock, 43.16) - assertClose(defendersResolve.NumberOfMitigatedDamagingHits, 49.77713289867) - assertClose(adjacentBlockEHP, 227484.13584363) - assertClose(adjacentBlockChance, 44.98) - assertClose(adjacentBlock.NumberOfMitigatedDamagingHits, 50.361537374709) - assert.is_true(defendersResolveAddsBlock) - assert.is_true(defendersResolveAddsEHP) - assert.is_true(adjacentNodeAddsBlock) - assert.is_true(adjacentNodeAddsEHP) + assertClose(base.TotalEHP, 11724.334582067) + assertClose(block.TotalEHP, 12341.404823229) + assertClose(block.EffectiveBlockChance, 10) + assert.is_true(block.TotalEHP > base.TotalEHP) end) end) From b80d7b799a14374108f853af931fa8f6144928a0 Mon Sep 17 00:00:00 2001 From: LocalIdentity Date: Sat, 23 May 2026 00:50:26 +1000 Subject: [PATCH 10/11] Add back block test --- spec/System/TestDefence_spec.lua | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/spec/System/TestDefence_spec.lua b/spec/System/TestDefence_spec.lua index e25b3c9a00..8eddeeac7f 100644 --- a/spec/System/TestDefence_spec.lua +++ b/spec/System/TestDefence_spec.lua @@ -527,6 +527,28 @@ describe("TestDefence", function() assert.are.equals(0, floor(poolsRemaining.Life)) assert.are.equals(0, floor(poolsRemaining.OverkillDamage)) end) + + it("uses block chance against projectile spells", function() + build.configTab.input.enemyIsBoss = "None" + build.configTab.input.enemyDamageType = "SpellProjectile" + build.configTab.input.customMods = [[ + 20% chance to block + ]] + build.configTab:BuildModList() + runCallback("OnFrame") + + assert.are.equals(20, build.calcsTab.calcsOutput.EffectiveBlockChance) + assert.are.equals(20, build.calcsTab.calcsOutput.EffectiveProjectileBlockChance) + assert.are.equals(20, build.calcsTab.calcsOutput.EffectiveSpellProjectileBlockChance) + assert.are.equals(80, build.calcsTab.calcsOutput.ConfiguredDamageChance) + + build.configTab.input.enemyDamageType = "Average" + build.configTab:BuildModList() + runCallback("OnFrame") + + assert.are.equals(15, build.calcsTab.calcsOutput.EffectiveAverageBlockChance) + assert.are.equals(85, build.calcsTab.calcsOutput.ConfiguredDamageChance) + end) it("limits EHP speedup when hit damage is delayed", function() local function assertClose(actual, expected) From 887efcaa81a284fbcec57a5564062d7c3f19b20c Mon Sep 17 00:00:00 2001 From: LocalIdentity Date: Sat, 23 May 2026 01:15:54 +1000 Subject: [PATCH 11/11] Fix Test --- spec/System/TestDefence_spec.lua | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/spec/System/TestDefence_spec.lua b/spec/System/TestDefence_spec.lua index 8eddeeac7f..762103f5b9 100644 --- a/spec/System/TestDefence_spec.lua +++ b/spec/System/TestDefence_spec.lua @@ -557,20 +557,18 @@ describe("TestDefence", function() local function calcEHP(extraMods) newBuild() - build.configTab.input.enemyPhysicalDamage = "1000" - build.configTab.input.enemyFireDamage = "1000" - build.configTab.input.enemyColdDamage = "1000" - build.configTab.input.enemyLightningDamage = "1000" + build.configTab.input.enemyPhysicalDamage = "500" + build.configTab.input.enemyFireDamage = "500" + build.configTab.input.enemyColdDamage = "500" + build.configTab.input.enemyLightningDamage = "500" build.configTab.input.enemyChaosDamage = "0" build.configTab.input.customMods = [[ +4000 to maximum Life - +4000 to maximum Mana - 75% of Damage is taken from Mana before Life - 25% of Life Loss from Hits is prevented, then that much Life is lost over 4 seconds instead + 75% of Life Loss from Hits is prevented, then that much Life is lost over 4 seconds instead +75% to all Elemental Resistances +75% to Chaos Resistance ]] .. (extraMods or "") - build.configTab:BuildModList() + pob1and2Compat() runCallback("OnFrame") runCallback("OnFrame") local calcsOutput = build.calcsTab.calcsOutput @@ -586,8 +584,8 @@ describe("TestDefence", function() newBuild() - assertClose(base.TotalEHP, 11724.334582067) - assertClose(block.TotalEHP, 12341.404823229) + assertClose(base.TotalEHP, 17582.417582418) + assertClose(block.TotalEHP, 19008.019008019) assertClose(block.EffectiveBlockChance, 10) assert.is_true(block.TotalEHP > base.TotalEHP) end)