Skip to content

Commit 3f03277

Browse files
mcagnionclaude
andcommitted
fix(trade): preserve every valid influence pair in the query UI
The influence callback was over-normalizing Ignore+None and None+specific pairs, forcing them back to None/None and locking users out of states the query state resolver already supports (exactCount=1, exactCount=1 plus that specific). Users stuck at None/None could not return to an unfiltered pair. Drop the UI normalization. The callback keeps only the eldritch side-effect it was carrying. All pair combinations now flow through to resolveInfluenceQueryState as intended. Same specific on both sides (Shaper/Shaper) is redundant at the item level, so resolveInfluenceQueryState treats the second slot as None: the pair now generates the same query as Shaper/None (exactly 1 of that type, pseudo_has_influence capped at 1). Without the None constraint the duplicate specific would have silently produced a looser query than the paired form. Test coverage added asserts state equality and query-cost equality with the paired form. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 5f3bb5d commit 3f03277

2 files changed

Lines changed: 37 additions & 33 deletions

File tree

spec/System/TestTradeQueryGenerator_spec.lua

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,27 @@ describe("TradeQueryGenerator", function()
9999
it("hasAnyInfluenceModId is pseudo.pseudo_has_influence_count", function()
100100
assert.are.equal(mock_queryGen._hasAnyInfluenceModId, "pseudo.pseudo_has_influence_count")
101101
end)
102+
103+
-- Same specific influence on both sides is redundant at the item level and must
104+
-- collapse to the <specific> / None semantic (exactly 1 of that type), not
105+
-- double-count and not collapse to <specific> / Ignore. Verify via the
106+
-- needsHasInfluenceFilter gate that builds the pseudo_has_influence cap — without
107+
-- it, the query would match items with any number of influences including Shaper.
108+
it("Shaper+Shaper produces the full Shaper+None query state", function()
109+
local dup = resolve(SHAPER, SHAPER)
110+
local paired = resolve(SHAPER, NONE)
111+
assert.are.equal(dup.exactCount, paired.exactCount)
112+
assert.are.equal(dup.hasNoneConstraint, paired.hasNoneConstraint)
113+
assert.are.equal(#dup.specificInfluenceModIds, #paired.specificInfluenceModIds)
114+
assert.are.equal(dup.specificInfluenceModIds[1], paired.specificInfluenceModIds[1])
115+
assert.are.equal(cost(dup), cost(paired))
116+
assert.are.equal(needs(dup), needs(paired))
117+
end)
118+
119+
it("Shaper+Shaper needs the pseudo_has_influence cap to enforce exactly 1", function()
120+
local state = resolve(SHAPER, SHAPER)
121+
assert.is_true(needs(state))
122+
end)
102123
end)
103124

104125
describe("Filter prioritization", function()

src/Classes/TradeQueryGenerator.lua

Lines changed: 16 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -149,15 +149,20 @@ local function resolveInfluenceQueryState(selection1, selection2)
149149
ignoreSelectionCount = ignoreSelectionCount + 1
150150
elseif isNoInfluenceSelection(selectionIndex) then
151151
noneSelectionCount = noneSelectionCount + 1
152+
elseif isSpecificInfluenceSelection(selectionIndex) then
153+
local influenceModId = hasInfluenceModIds[selectionIndex - INFLUENCE_ANY_INDEX]
154+
if not seenSpecificInfluenceModIds[influenceModId] then
155+
seenSpecificInfluenceModIds[influenceModId] = true
156+
t_insert(state.specificInfluenceModIds, influenceModId)
157+
positiveSelectionCount = positiveSelectionCount + 1
158+
else
159+
-- Same specific on both sides is redundant at the item level; treat the
160+
-- second slot as None so the pair behaves exactly like <specific> / None
161+
-- (exactly 1 of that type, pseudo_has_influence capped at 1).
162+
state.hasNoneConstraint = true
163+
end
152164
else
153165
positiveSelectionCount = positiveSelectionCount + 1
154-
if isSpecificInfluenceSelection(selectionIndex) then
155-
local influenceModId = hasInfluenceModIds[selectionIndex - INFLUENCE_ANY_INDEX]
156-
if not seenSpecificInfluenceModIds[influenceModId] then
157-
seenSpecificInfluenceModIds[influenceModId] = true
158-
t_insert(state.specificInfluenceModIds, influenceModId)
159-
end
160-
end
161166
end
162167
end
163168

@@ -1328,29 +1333,7 @@ Remove: %s will be removed from the search results.]], term, term, term)
13281333
controls.jewelTypeLabel = new("LabelControl", {"RIGHT",controls.jewelType,"LEFT"}, {-5, 0, 0, 16}, "Jewel Type:")
13291334
updateLastAnchor(controls.jewelType)
13301335
elseif slot and not isAbyssalJewelSlot and context.slotTbl.slotName ~= "Watcher's Eye" then
1331-
local function normalizeInfluenceSelections(changedControl)
1332-
local changedDropdown = changedControl == 1 and controls.influence1 or changedControl == 2 and controls.influence2 or nil
1333-
local otherDropdown = changedControl == 1 and controls.influence2 or changedControl == 2 and controls.influence1 or nil
1334-
1335-
if changedDropdown and otherDropdown then
1336-
if isIgnoredSelection(changedDropdown.selIndex) and isNoInfluenceSelection(otherDropdown.selIndex) then
1337-
changedDropdown:SetSel(INFLUENCE_NONE_INDEX)
1338-
return
1339-
elseif isNoInfluenceSelection(changedDropdown.selIndex) and isIgnoredSelection(otherDropdown.selIndex) then
1340-
otherDropdown:SetSel(INFLUENCE_NONE_INDEX)
1341-
return
1342-
elseif isSpecificInfluenceSelection(changedDropdown.selIndex) and changedDropdown.selIndex == otherDropdown.selIndex then
1343-
changedDropdown:SetSel(INFLUENCE_ANY_INDEX)
1344-
return
1345-
end
1346-
end
1347-
1348-
if isIgnoredSelection(controls.influence1.selIndex) and isNoInfluenceSelection(controls.influence2.selIndex) then
1349-
controls.influence1:SetSel(INFLUENCE_NONE_INDEX)
1350-
elseif isNoInfluenceSelection(controls.influence1.selIndex) and isIgnoredSelection(controls.influence2.selIndex) then
1351-
controls.influence2:SetSel(INFLUENCE_NONE_INDEX)
1352-
end
1353-
1336+
local function refreshInfluenceDependentControls()
13541337
-- influenced items can't have eldritch implicits
13551338
if controls.copyEldritch and isEldritchModSlot then
13561339
local hasInfluence1 = controls.influence1 and not isIgnoredSelection(controls.influence1.selIndex) and not isNoInfluenceSelection(controls.influence1.selIndex)
@@ -1360,14 +1343,14 @@ Remove: %s will be removed from the search results.]], term, term, term)
13601343
end
13611344

13621345
controls.influence1 = new("DropDownControl", { "TOPLEFT", lastItemAnchor, "BOTTOMLEFT" }, { 0, 5, 100, 18 },
1363-
influenceDropdownNames, function() normalizeInfluenceSelections(1) end)
1346+
influenceDropdownNames, refreshInfluenceDependentControls)
13641347
controls.influence1:SetSel(self.lastInfluence1 or INFLUENCE_IGNORE_INDEX)
13651348
controls.influence1Label = new("LabelControl", {"RIGHT",controls.influence1,"LEFT"}, {-5, 0, 0, 16}, "^7Influence 1:")
13661349

13671350
controls.influence2 = new("DropDownControl", { "TOPLEFT", controls.influence1, "BOTTOMLEFT" }, { 0, 5, 100, 18 },
1368-
influenceDropdownNames, function() normalizeInfluenceSelections(2) end)
1351+
influenceDropdownNames, refreshInfluenceDependentControls)
13691352
controls.influence2:SetSel(self.lastInfluence2 or INFLUENCE_IGNORE_INDEX)
1370-
normalizeInfluenceSelections()
1353+
refreshInfluenceDependentControls()
13711354
controls.influence2Label = new("LabelControl", { "RIGHT", controls.influence2, "LEFT" }, { -5, 0, 0, 16 },
13721355
"^7Influence 2:")
13731356
updateLastAnchor(controls.influence2, 46)

0 commit comments

Comments
 (0)