Cap generic mult/xmult bonus per candidate; opt Stencil out of it

Any xMult joker used to pulse 'recommended' on a board of four unrelated
+Mult/+Chips jokers: the generic engine-complement +1 in JCA.score
accumulated once per owned joker in partners_for, reaching the threshold
of 4 with zero listable partners (pairwise scores never hit 2), so the
card pulsed while its own tooltip said 'No synergy with your jokers.'

- partners_for now counts the generic +1 once per candidate. Tag/pair
  scores are even, so the generic bonus can no longer flip a
  recommendation at all; a pulsing card always has a nameable partner.
- Joker Stencil is the one xMult joker the generic rule actively
  misreads (its multiplier grows with EMPTY slots), so entries support
  {no_generic = true} which suppresses the rule in score() and the
  generic fallback lines in explain(). Stencil also gains copy_target:
  its real famous synergy is being copied by Blueprint/Brainstorm.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
funman300
2026-07-10 13:48:20 -07:00
parent b588444304
commit e37b56471b
3 changed files with 43 additions and 18 deletions
+9 -2
View File
@@ -62,8 +62,15 @@ Three-layer split; `JCA` is the single global namespace:
- `main.lua` — engine + game integration:
- Scoring: `JCA.score(a, b)` = explicit pair bonus (+4) + 2 per give→want tag
match in each direction + 1 for the generic "+Mult/+Chips source with an
×Mult joker" complement. `JCA.partners_for(card)` aggregates over
`G.jokers.cards`; total ≥ `JCA.config.threshold` ⇒ recommended.
×Mult joker" complement (second return value flags when that +1 applied).
`JCA.partners_for(card)` aggregates over `G.jokers.cards`; total ≥
`JCA.config.threshold` ⇒ recommended. The generic +1 counts toward the
total once per candidate, not per owned joker — since tag/pair scores are
even and the threshold is 4, the generic bonus alone can never trigger a
recommendation, so a pulsing card always has a nameable partner. Jokers
whose multiplier the generic rule misreads opt out with
`{no_generic = true}` in `synergies.lua` (Joker Stencil: its ×Mult grows
with *empty* slots, so "+Mult jokers feed it" is backwards).
- UI hooks: wraps `Card:generate_UIBox_ability_table` to append a tooltip entry
to `aut.info` (format: array of rows-of-UIT-nodes plus a `.name` string —
must match what vanilla `info_tip_from_rows` expects), and wraps
+20 -5
View File
@@ -39,8 +39,10 @@ end
-- Scoring -------------------------------------------------------------------
-- Synergy score between two joker center keys. 0 = no known synergy.
-- Second return marks scores whose +1 came from the generic engine
-- complement, so partners_for can count that bonus once per candidate.
function JCA.score(a, b)
local s = 0
local s, generic = 0, false
if JCA.pair_bonus[pair_key(a, b)] then s = s + 4 end
local A, B = JCA.db[a], JCA.db[b]
if A and B then
@@ -51,14 +53,18 @@ function JCA.score(a, b)
if A.wants[t] then s = s + 2 end
end
-- Generic engine complement: a +Mult/+Chips source and an xMult
-- multiplier always help each other a little.
-- multiplier usually help each other a little. no_generic opts out
-- jokers whose multiplier this reasoning misreads (Joker Stencil).
if not (A.no_generic or B.no_generic) then
local a_scaler = A.gives.mult or A.gives.chips
local b_scaler = B.gives.mult or B.gives.chips
if (A.gives.xmult and b_scaler) or (B.gives.xmult and a_scaler) then
s = s + 1
generic = true
end
end
return s
end
return s, generic
end
-- One short reason why `partner` works with `hovered`, phrased from the
@@ -83,11 +89,13 @@ function JCA.explain(hovered, partner)
end
end
end
if not (A.no_generic or B.no_generic) then
if B.gives.xmult and (A.gives.mult or A.gives.chips) then
return 'multiplies what this adds'
elseif A.gives.xmult and (B.gives.mult or B.gives.chips) then
return 'feeds this multiplier'
end
end
return nil
end
@@ -102,14 +110,21 @@ end
-- Owned jokers with synergy score >= 2 against `card`, strongest first.
-- Returns the partner list and the total score across all owned jokers.
-- The generic mult/xmult complement counts toward the total once per
-- candidate, not once per owned joker — otherwise any xMult card would
-- pulse "recommended" on a board of four unrelated +Mult jokers.
function JCA.partners_for(card)
local partners, total = {}, 0
local partners, total, generic_seen = {}, 0, false
if not (G.jokers and G.jokers.cards) then return partners, 0 end
local key = card.config.center.key
for _, owned in ipairs(G.jokers.cards) do
if owned ~= card and owned.config.center.set == 'Joker' then
local s = JCA.score(key, owned.config.center.key)
local s, generic = JCA.score(key, owned.config.center.key)
total = total + s
if generic then
if generic_seen then total = total - 1 end
generic_seen = true
end
if s >= 2 then
partners[#partners + 1] = {
key = owned.config.center.key,
+6 -3
View File
@@ -24,8 +24,9 @@
--- Jokers not in this table (e.g. from other mods) simply score 0.
local J = {}
local function j(key, gives, wants)
J[key] = { gives = gives or {}, wants = wants or {} }
local function j(key, gives, wants, opts)
J[key] = { gives = gives or {}, wants = wants or {},
no_generic = opts and opts.no_generic or nil }
end
-- Common jokers ------------------------------------------------------------
@@ -100,7 +101,9 @@ j('j_hanging_chad', {'retrig_scoring'})
j('j_shoot_the_moon', {'mult'}, {'retrig_held','hand_size'})
-- Uncommon jokers ----------------------------------------------------------
j('j_stencil', {'xmult'})
-- Stencil's xMult shrinks as slots fill, so the generic "+Mult feeds the
-- multiplier" rule is backwards for it; its real synergy is being copied.
j('j_stencil', {'xmult','copy_target'}, nil, {no_generic = true})
j('j_four_fingers', {'straight','flush'})
j('j_mime', {'retrig_held','copy_target'})
j('j_ceremonial', {'mult'})