From ebe94f1460f1dffff19870f739b60b8639c644fd Mon Sep 17 00:00:00 2001 From: LocalIdentity <31035929+LocalIdentity@users.noreply.github.com> Date: Thu, 13 Aug 2026 12:20:29 +1000 Subject: [PATCH 1/2] Fix Reservation rounding The client keeps flat reservation and percentage reservation in integer units while applying increased reservation, resource-specific final reservation, and generic final reservation as separate stages. PoB previously multiplied the combined floating-point modifiers and rounded only once. We now track the two final reservation multipliers separately, truncate each intermediate stage, retain percentage reservation in permyriad until the final result, and round only after efficiency. Added a Clarity test where 34 with two 50% final stages becomes 76 rather than 77. --- spec/System/TestSkills_spec.lua | 10 ++++++++++ src/Modules/CalcPerform.lua | 23 ++++++++++++++++------- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/spec/System/TestSkills_spec.lua b/spec/System/TestSkills_spec.lua index af8896f64c..81bd9381f7 100644 --- a/spec/System/TestSkills_spec.lua +++ b/spec/System/TestSkills_spec.lua @@ -405,6 +405,16 @@ describe("TestSkills", function() assert.are.equals(7, round(finalCost)) end) + it("applies reservation final modifiers as separate integer stages", function() + build.skillsTab:PasteSocketGroup("Clarity 1/0 1\n") + build.configTab.input.customMods = "50% more Mana Reservation\n50% more Reservation" + build.configTab:BuildModList() + runCallback("OnFrame") + + -- 34 base becomes 51 after Mana Reservation, then 76 after generic Reservation. + assert.are.equals(76, build.calcsTab.mainEnv.player.mainSkill.skillData.ManaReservedBase) + end) + it("evaluates BaseFlag tags using PoB 1 skill data", function() build.skillsTab:PasteSocketGroup("Absolution 20/0 1\n") runCallback("OnFrame") diff --git a/src/Modules/CalcPerform.lua b/src/Modules/CalcPerform.lua index 95e90c3825..3d63a97c47 100644 --- a/src/Modules/CalcPerform.lua +++ b/src/Modules/CalcPerform.lua @@ -1964,7 +1964,9 @@ function calcs.perform(env, skipEHP) activeSkill.skillData["ManaReservationPercentForced"] = nil end for name, values in pairs(pool) do - values.more = skillModList:More(skillCfg, name.."Reserved", "Reserved") + values.resourceMore = skillModList:More(skillCfg, name.."Reserved") + values.genericMore = skillModList:More(skillCfg, "Reserved") + values.more = values.resourceMore * values.genericMore values.inc = skillModList:Sum("INC", skillCfg, name.."Reserved", "Reserved") values.efficiency = m_max(skillModList:Sum("INC", skillCfg, name.."ReservationEfficiency", "ReservationEfficiency"), -100) values.efficiencyMore = skillModList:More(skillCfg, name.."ReservationEfficiency", "ReservationEfficiency") @@ -1973,19 +1975,26 @@ function calcs.perform(env, skipEHP) if activeSkill.skillData[name.."ReservationFlatForced"] then values.reservedFlat = activeSkill.skillData[name.."ReservationFlatForced"] else - local baseFlatVal = m_floor(values.baseFlat * mult) + local baseFlatVal = m_modf(values.baseFlat * mult) values.reservedFlat = 0 - if values.more > 0 and values.inc > -100 and baseFlatVal ~= 0 then - values.reservedFlat = m_max(round(baseFlatVal * (100 + values.inc) / 100 * values.more / (1 + values.efficiency / 100) / values.efficiencyMore, 0), 0) + if baseFlatVal ~= 0 then + -- Reservation modifiers use separate integer stages before efficiency. + local modifiedFlat = baseFlatVal + m_modf(baseFlatVal * values.inc / 100) + modifiedFlat = modifiedFlat + m_modf(modifiedFlat * (values.resourceMore - 1)) + modifiedFlat = modifiedFlat + m_modf(modifiedFlat * (values.genericMore - 1)) + values.reservedFlat = m_max(round(modifiedFlat / (1 + values.efficiency / 100) / values.efficiencyMore, 0), 0) end end if activeSkill.skillData[name.."ReservationPercentForced"] then values.reservedPercent = activeSkill.skillData[name.."ReservationPercentForced"] else - local basePercentVal = values.basePercent * mult + local basePercentVal = m_modf(values.basePercent * 100 * mult) values.reservedPercent = 0 - if values.more > 0 and values.inc > -100 and basePercentVal ~= 0 then - values.reservedPercent = m_max(round(basePercentVal * (100 + values.inc) / 100 * values.more / (1 + values.efficiency / 100) / values.efficiencyMore, 2), 0) + if basePercentVal ~= 0 then + local modifiedPercent = basePercentVal + m_modf(basePercentVal * values.inc / 100) + modifiedPercent = modifiedPercent + m_modf(modifiedPercent * (values.resourceMore - 1)) + modifiedPercent = modifiedPercent + m_modf(modifiedPercent * (values.genericMore - 1)) + values.reservedPercent = m_max(round(modifiedPercent / (1 + values.efficiency / 100) / values.efficiencyMore, 0) / 100, 0) end end if activeSkill.activeMineCount then From 6121b0f631d1d1caeabffb715563e1c51d79c3b4 Mon Sep 17 00:00:00 2001 From: LocalIdentity Date: Thu, 20 Aug 2026 16:32:50 +1000 Subject: [PATCH 2/2] Update test --- spec/System/TestSkills_spec.lua | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/spec/System/TestSkills_spec.lua b/spec/System/TestSkills_spec.lua index 81bd9381f7..cc7144abef 100644 --- a/spec/System/TestSkills_spec.lua +++ b/spec/System/TestSkills_spec.lua @@ -405,14 +405,15 @@ describe("TestSkills", function() assert.are.equals(7, round(finalCost)) end) - it("applies reservation final modifiers as separate integer stages", function() - build.skillsTab:PasteSocketGroup("Clarity 1/0 1\n") - build.configTab.input.customMods = "50% more Mana Reservation\n50% more Reservation" - build.configTab:BuildModList() + it("rounds Supreme Ego mana reservation down", function() + build.skillsTab:PasteSocketGroup("Precision 1/0 1\n") + local supremeEgo = build.spec.tree.keystoneMap["Supreme Ego"] + build.spec:AllocNode(build.spec.nodes[supremeEgo.id]) + build.spec:BuildAllDependsAndPaths() runCallback("OnFrame") - -- 34 base becomes 51 after Mana Reservation, then 76 after generic Reservation. - assert.are.equals(76, build.calcsTab.mainEnv.player.mainSkill.skillData.ManaReservedBase) + -- 22 base + floor(40% of 22) = 30, rather than round(22 * 1.4) = 31. + assert.are.equals(30, build.calcsTab.mainEnv.player.mainSkill.skillData.ManaReservedBase) end) it("evaluates BaseFlag tags using PoB 1 skill data", function()