diff --git a/CLAUDE.md b/CLAUDE.md index b205c3d..581bf68 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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 diff --git a/main.lua b/main.lua index 5e75050..07b4afb 100644 --- a/main.lua +++ b/main.lua @@ -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. - 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 + -- 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 end - return s + return s, generic end -- One short reason why `partner` works with `hovered`, phrased from the @@ -83,10 +89,12 @@ function JCA.explain(hovered, partner) end end end - 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' + 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, diff --git a/synergies.lua b/synergies.lua index c584fe1..95d731d 100644 --- a/synergies.lua +++ b/synergies.lua @@ -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'})