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)