From e8f5eb227c22966a007bf2885e3c1ba2bbfc1c4d Mon Sep 17 00:00:00 2001 From: vaisest <4550061+vaisest@users.noreply.github.com> Date: Fri, 28 Aug 2026 12:51:50 +0300 Subject: [PATCH 1/2] Port Add support for select pseudo stats in trader weights (#10085) --- spec/System/TestTradeQueryGenerator_spec.lua | 103 ++++++++++++++++--- src/Classes/TradeQueryGenerator.lua | 65 ++++++++++-- src/Modules/Common.lua | 2 +- 3 files changed, 145 insertions(+), 25 deletions(-) diff --git a/spec/System/TestTradeQueryGenerator_spec.lua b/spec/System/TestTradeQueryGenerator_spec.lua index 97fb12934b..4f41507039 100644 --- a/spec/System/TestTradeQueryGenerator_spec.lua +++ b/spec/System/TestTradeQueryGenerator_spec.lua @@ -104,23 +104,94 @@ describe("TradeQueryGenerator", function() end) describe("Filter prioritization", function() - -- Pass: Limits mods to MAX_FILTERS (2 in test), preserving top priorities - -- Fail: Exceeds limit, indicating over-generation of filters, risking API query size errors or rate limits - it("respects MAX_FILTERS", function() - local orig_max = _G.MAX_FILTERS - _G.MAX_FILTERS = 2 - mock_queryGen.modWeights = { { weight = 10, tradeModId = "id1" }, { weight = 5, tradeModId = "id2" } } - table.sort(mock_queryGen.modWeights, function(a, b) - return math.abs(a.weight) > math.abs(b.weight) - end) - local prioritized = {} - for i, entry in ipairs(mock_queryGen.modWeights) do - if #prioritized < _G.MAX_FILTERS then - table.insert(prioritized, entry) - end + it("counts socket constraints against MAX_FILTERS", function() + local queryGen = new("TradeQueryGenerator"):TradeQueryGenerator({ itemsTab = { items = {} } }) + queryGen.modWeights = {} + for index = 1, 40 do + table.insert(queryGen.modWeights, { + tradeModId = "explicit.stat_" .. index, + weight = 1, + meanStatDiff = 41 - index, + }) end - assert.are.equal(#prioritized, 2) - _G.MAX_FILTERS = orig_max + queryGen.calcContext = { + testItem = new("Item"):Item("Rarity: RARE\nNew Item\nGold Ring\nImplicits: 0"), + baseOutput = {}, + baseStatValue = 0, + itemCategoryQueryStr = "accessory.ring", + special = {}, + options = { + statWeights = {}, + includeMirrored = false, + sockets = 3, + }, + } + queryGen.tradeTypeIndex = 1 + local query + queryGen.requesterCallback = function(_, queryJson) + query = require("dkjson").decode(queryJson).query + end + queryGen:FinishQuery() + + assert.are.equal(31, #query.stats[1].filters) + assert.is_not_nil(query.filters.equipment_filters.filters.rune_sockets) + end) + end) + + describe("Pseudo stat filters", function() + local function runQuery(modWeights) + local queryGen = new("TradeQueryGenerator"):TradeQueryGenerator({ itemsTab = { items = {} } }) + queryGen.modWeights = modWeights + queryGen.calcContext = { + testItem = new("Item"):Item("Rarity: RARE\nNew Item\nGold Ring\nImplicits: 0"), + baseOutput = {}, + baseStatValue = 0, + itemCategoryQueryStr = "accessory.ring", + special = {}, + options = { statWeights = {}, includeMirrored = true }, + } + queryGen.tradeTypeIndex = 1 + local query + queryGen.requesterCallback = function(_, queryJson) + query = require("dkjson").decode(queryJson).query + end + queryGen:FinishQuery() + return query.stats[1].filters + end + + it("maps single resistances and attributes to pseudo stats, keeping the highest weight", function() + local filters = runQuery({ + -- +#% to Fire Resistance, as explicit and as rune + { tradeModId = "explicit.stat_3372524247", weight = 5, meanStatDiff = 2 }, + { tradeModId = "rune.stat_3372524247", weight = 3, meanStatDiff = 1 }, + -- +# to Strength + { tradeModId = "explicit.stat_4080418644", weight = 7, meanStatDiff = 3 }, + }) + + table.sort(filters, function(a, b) return a.id < b.id end) + assert.are.equal(2, #filters) + assert.are.equal("pseudo.pseudo_total_fire_resistance", filters[1].id) + assert.are.equal(5, filters[1].value.weight) + assert.are.equal("pseudo.pseudo_total_strength", filters[2].id) + assert.are.equal(7, filters[2].value.weight) + end) + + it("drops hybrid resistance and attribute stats", function() + local filters = runQuery({ + -- +#% to Fire and Cold Resistances + { tradeModId = "explicit.stat_2915988346", weight = 9, meanStatDiff = 5 }, + -- +#% to all Elemental Resistances + { tradeModId = "explicit.stat_2901986750", weight = 9, meanStatDiff = 4 }, + -- +# to Strength and Dexterity + { tradeModId = "explicit.stat_538848803", weight = 9, meanStatDiff = 3 }, + -- +# to all Attributes + { tradeModId = "explicit.stat_1379411836", weight = 9, meanStatDiff = 2 }, + -- +# to maximum Life, kept + { tradeModId = "explicit.stat_3299347043", weight = 4, meanStatDiff = 1 }, + }) + + assert.are.equal(1, #filters) + assert.are.equal("explicit.stat_3299347043", filters[1].id) end) end) end) diff --git a/src/Classes/TradeQueryGenerator.lua b/src/Classes/TradeQueryGenerator.lua index 8504674eeb..886f4395d6 100644 --- a/src/Classes/TradeQueryGenerator.lua +++ b/src/Classes/TradeQueryGenerator.lua @@ -1016,23 +1016,72 @@ function TradeQueryGeneratorClass:FinishQuery() local effective_max = MAX_FILTERS - num_extra - local prioritizedMods = {} + local pseudoMap = { + ["3372524247"] = "pseudo.pseudo_total_fire_resistance", + ["4220027924"] = "pseudo.pseudo_total_cold_resistance", + ["1671376347"] = "pseudo.pseudo_total_lightning_resistance", + ["2923486259"] = "pseudo.pseudo_total_chaos_resistance", + ["4080418644"] = "pseudo.pseudo_total_strength", + ["3261801346"] = "pseudo.pseudo_total_dexterity", + ["328541901"] = "pseudo.pseudo_total_intelligence", + } + local ignoredStats = { + -- % all resistances + ["2901986750"] = true, + -- all attributes + ["1379411836"] = true, + ["2897413282"] = true, + } + -- block all hybrid resistance stats + local resElements = { "fire", "cold", "lightning", "chaos" } + for _, elem1 in ipairs(resElements) do + for _, elem2 in ipairs(resElements) do + local stats = { string.format("%s_and_%s_damage_resistance_%%", elem1, elem2) } + ignoredStats[tostring(HashStats(stats))] = true + end + end + -- block all hybrid attribute stats + local attributeElements = { "dexterity", "strength", "intelligence" } + for _, elem1 in ipairs(attributeElements) do + for _, elem2 in ipairs(attributeElements) do + local stats = { string.format("base_%s_and_%s", elem1, elem2) } + ignoredStats[tostring(HashStats(stats))] = true + stats = { string.format("additional_%s_and_%s", elem1, elem2) } + ignoredStats[tostring(HashStats(stats))] = true + end + end + local statFilters = {} + local pseudoMods = {} for _, entry in ipairs(self.modWeights) do - if #prioritizedMods < effective_max then - table.insert(prioritizedMods, entry) + local hash = entry.tradeModId:match("stat_(%d+)") + local filterEntry = { id = entry.tradeModId, value = { weight = (entry.invert == true and entry.weight * -1 or entry.weight) } } + -- avoid adding hybrid stats since we get the weight for them from + -- individual stats + if ignoredStats[hash] then + goto weightContinue + elseif pseudoMap[hash] then + local tradeId = pseudoMap[hash] + filterEntry.id = tradeId + -- avoid adding duplicate pseudo filters: update existing + if pseudoMods[tradeId] then + pseudoMods[tradeId].value.weight = math.max(filterEntry.value.weight, pseudoMods[tradeId].value.weight) + else + pseudoMods[tradeId] = filterEntry + table.insert(statFilters, filterEntry) + end else - break + table.insert(statFilters, filterEntry) end - end - self.modWeights = prioritizedMods + ::weightContinue:: + end for k, v in pairs(self.calcContext.special.queryExtra or {}) do queryTable.query[k] = v end - for _, entry in ipairs(self.modWeights) do - t_insert(queryTable.query.stats[1].filters, { id = entry.tradeModId, value = { weight = (entry.invert == true and entry.weight * -1 or entry.weight) } }) + for _, entry in ipairs(statFilters) do + t_insert(queryTable.query.stats[1].filters, entry) filters = filters + 1 if filters == effective_max then break diff --git a/src/Modules/Common.lua b/src/Modules/Common.lua index e3e6c35abe..d4f1ce179d 100644 --- a/src/Modules/Common.lua +++ b/src/Modules/Common.lua @@ -1088,7 +1088,7 @@ local GGG_STAT_HASH32_SEED = 0xC58F1A7B -- used for calculating the trade hash from stat hash fields local GGG_TRADE_SEED = 0x02312233 ---@param stats string[] ----@param extraStat string extra stat for time-lost jewels +---@param extraStat string? extra stat for time-lost jewels ---@return integer function HashStats(stats, extraStat) if extraStat then From 0244f3ed600d2763098fe1a4a48fe9a0fc693890 Mon Sep 17 00:00:00 2001 From: vaisest <4550061+vaisest@users.noreply.github.com> Date: Fri, 28 Aug 2026 19:24:11 +0300 Subject: [PATCH 2/2] Disable pseudo stat functionality but keep it for parity --- spec/System/TestTradeQueryGenerator_spec.lua | 57 -------------------- src/Classes/TradeQueryGenerator.lua | 18 ++----- 2 files changed, 4 insertions(+), 71 deletions(-) diff --git a/spec/System/TestTradeQueryGenerator_spec.lua b/spec/System/TestTradeQueryGenerator_spec.lua index 4f41507039..d234231191 100644 --- a/spec/System/TestTradeQueryGenerator_spec.lua +++ b/spec/System/TestTradeQueryGenerator_spec.lua @@ -137,61 +137,4 @@ describe("TradeQueryGenerator", function() assert.is_not_nil(query.filters.equipment_filters.filters.rune_sockets) end) end) - - describe("Pseudo stat filters", function() - local function runQuery(modWeights) - local queryGen = new("TradeQueryGenerator"):TradeQueryGenerator({ itemsTab = { items = {} } }) - queryGen.modWeights = modWeights - queryGen.calcContext = { - testItem = new("Item"):Item("Rarity: RARE\nNew Item\nGold Ring\nImplicits: 0"), - baseOutput = {}, - baseStatValue = 0, - itemCategoryQueryStr = "accessory.ring", - special = {}, - options = { statWeights = {}, includeMirrored = true }, - } - queryGen.tradeTypeIndex = 1 - local query - queryGen.requesterCallback = function(_, queryJson) - query = require("dkjson").decode(queryJson).query - end - queryGen:FinishQuery() - return query.stats[1].filters - end - - it("maps single resistances and attributes to pseudo stats, keeping the highest weight", function() - local filters = runQuery({ - -- +#% to Fire Resistance, as explicit and as rune - { tradeModId = "explicit.stat_3372524247", weight = 5, meanStatDiff = 2 }, - { tradeModId = "rune.stat_3372524247", weight = 3, meanStatDiff = 1 }, - -- +# to Strength - { tradeModId = "explicit.stat_4080418644", weight = 7, meanStatDiff = 3 }, - }) - - table.sort(filters, function(a, b) return a.id < b.id end) - assert.are.equal(2, #filters) - assert.are.equal("pseudo.pseudo_total_fire_resistance", filters[1].id) - assert.are.equal(5, filters[1].value.weight) - assert.are.equal("pseudo.pseudo_total_strength", filters[2].id) - assert.are.equal(7, filters[2].value.weight) - end) - - it("drops hybrid resistance and attribute stats", function() - local filters = runQuery({ - -- +#% to Fire and Cold Resistances - { tradeModId = "explicit.stat_2915988346", weight = 9, meanStatDiff = 5 }, - -- +#% to all Elemental Resistances - { tradeModId = "explicit.stat_2901986750", weight = 9, meanStatDiff = 4 }, - -- +# to Strength and Dexterity - { tradeModId = "explicit.stat_538848803", weight = 9, meanStatDiff = 3 }, - -- +# to all Attributes - { tradeModId = "explicit.stat_1379411836", weight = 9, meanStatDiff = 2 }, - -- +# to maximum Life, kept - { tradeModId = "explicit.stat_3299347043", weight = 4, meanStatDiff = 1 }, - }) - - assert.are.equal(1, #filters) - assert.are.equal("explicit.stat_3299347043", filters[1].id) - end) - end) end) diff --git a/src/Classes/TradeQueryGenerator.lua b/src/Classes/TradeQueryGenerator.lua index 886f4395d6..2882a80339 100644 --- a/src/Classes/TradeQueryGenerator.lua +++ b/src/Classes/TradeQueryGenerator.lua @@ -1017,23 +1017,13 @@ function TradeQueryGeneratorClass:FinishQuery() local effective_max = MAX_FILTERS - num_extra local pseudoMap = { - ["3372524247"] = "pseudo.pseudo_total_fire_resistance", - ["4220027924"] = "pseudo.pseudo_total_cold_resistance", - ["1671376347"] = "pseudo.pseudo_total_lightning_resistance", - ["2923486259"] = "pseudo.pseudo_total_chaos_resistance", - ["4080418644"] = "pseudo.pseudo_total_strength", - ["3261801346"] = "pseudo.pseudo_total_dexterity", - ["328541901"] = "pseudo.pseudo_total_intelligence", + -- pseudo stats are disabled for PoE2 due to the trade site counting augment mods in them, + -- which would skew results significantly. however, the feature is kept here for PoB1 parity reasons } local ignoredStats = { - -- % all resistances - ["2901986750"] = true, - -- all attributes - ["1379411836"] = true, - ["2897413282"] = true, } -- block all hybrid resistance stats - local resElements = { "fire", "cold", "lightning", "chaos" } + local resElements = {} for _, elem1 in ipairs(resElements) do for _, elem2 in ipairs(resElements) do local stats = { string.format("%s_and_%s_damage_resistance_%%", elem1, elem2) } @@ -1041,7 +1031,7 @@ function TradeQueryGeneratorClass:FinishQuery() end end -- block all hybrid attribute stats - local attributeElements = { "dexterity", "strength", "intelligence" } + local attributeElements = {} for _, elem1 in ipairs(attributeElements) do for _, elem2 in ipairs(attributeElements) do local stats = { string.format("base_%s_and_%s", elem1, elem2) }