Files
JokerComboAdvisor/synergies.lua
T
funman300 47721d1acf Fix a backwards clash, and add 19 verified traps
Every entry here was checked against the running Steamodded source in
Mods/lovely/dump, not from memory.

The headline is a correction: the shipped CLASHES table warned that
Smeared Joker + Flower Pot "cannot make all four" suits. It is backwards.
Flower Pot fills its four suit slots with a greedy if/elseif chain
(card.lua:4256) and SMODS.smeared_check makes Hearts answer for Diamonds
and Spades for Clubs, so the second Heart falls through into the Diamond
slot: any two red + two black cards fill the pot. It is one of the
strongest combos in the game and the mod was steering players away from
it -- while simultaneously scoring the pair +2 via suit_flex, which
should have been the tell. It is now a famous pair.

New clashes (4 -> 22), all traced to the mechanism:
* Burglar zeroes discards_left on blind select (card.lua:2905), which
  switches off every discard-gated joker for the rest of the run: Banner,
  Delayed Gratification, Castle, Yorick, Hit the Road, Burnt, Mail-In
  Rebate, Faceless, Trading Card, and it wipes the discards Drunkard and
  Merry Andy hand you. Delayed Gratification is the cruellest -- "no
  discards used" reads like a synergy, but the code also requires
  discards_left > 0, so it pays exactly $0.
* Vampire strips enhancements in the `before` pass, ahead of the
  per-card `individual` pass where enhancement payoffs are read, so it
  eats what Golden Ticket, Glass, Stone Joker, Marble and Driver's
  License are counting.
* Joker Stencil scales on EMPTY slots, so Riff-Raff and Abstract Joker
  fight it.
* Splash makes every played card score, so a face card that used to be a
  harmless kicker now resets Ride the Bus.

New cautions (3 -> 15): the positional traps (Blueprint dead in the
rightmost slot, Brainstorm in the leftmost), Mime doing nothing without a
held-card payoff, and the decay timers, whose numbers are exact from
game.lua (Ice Cream 100/5 = 20 hands, Popcorn 20/4 = 5 rounds).

Also corrects the stale game path in CLAUDE.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 11:22:35 -07:00

406 lines
24 KiB
Lua

--- Synergy database for Joker Combo Advisor.
--- Each joker lists tags it GIVES (capabilities it adds to a build) and tags
--- it WANTS (capabilities that make it stronger). The scorer matches one
--- joker's gives against another's wants, in both directions.
---
--- Tag glossary:
--- mult / chips / xmult engine roles (also used for the generic
--- "scaler + multiplier" complement rule)
--- money earns money; money = wants money (econ payoffs)
--- faces makes cards count as face cards / wants face cards
--- retrig_scoring retriggers (or multiplies triggers of) played cards
--- retrig_held retriggers cards held in hand
--- hand_size / hands_up / discards_up resource increases
--- probability improves listed probabilities / cares about them
--- tarot_gen / spectral_gen / planet consumable economies
--- stone_gen / gold_gen / enhance_gen / card_gen deck modification
--- joker_gen / sell_value sell-fodder economies
--- copy_target a strong effect worth copying (for Blueprint et al.)
--- pair, two_pair, three_kind, four_kind, straight, flush hand-type
--- clusters (jokers keying the same hand reinforce
--- each other; enablers like Four Fingers give these)
--- hearts, diamonds, spades, clubs, suit_flex suit clusters
---
--- Jokers not in this table (e.g. from other mods) simply score 0.
local J = {}
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 ------------------------------------------------------------
j('j_joker', {'mult'})
j('j_greedy_joker', {'mult','diamonds'}, {'diamonds','suit_flex'})
j('j_lusty_joker', {'mult','hearts'}, {'hearts','suit_flex'})
j('j_wrathful_joker', {'mult','spades'}, {'spades','suit_flex'})
j('j_gluttenous_joker', {'mult','clubs'}, {'clubs','suit_flex'})
j('j_jolly', {'mult','pair'}, {'pair'})
j('j_zany', {'mult','three_kind'}, {'three_kind'})
j('j_mad', {'mult','two_pair'}, {'two_pair'})
j('j_crazy', {'mult','straight'}, {'straight'})
j('j_droll', {'mult','flush'}, {'flush'})
j('j_sly', {'chips','pair'}, {'pair'})
j('j_wily', {'chips','three_kind'}, {'three_kind'})
j('j_clever', {'chips','two_pair'}, {'two_pair'})
j('j_devious', {'chips','straight'}, {'straight'})
j('j_crafty', {'chips','flush'}, {'flush'})
j('j_half', {'mult'})
j('j_credit_card', {})
j('j_banner', {'chips'}, {'discards_up'})
j('j_mystic_summit', {'mult'})
j('j_8_ball', {'tarot_gen'}, {'probability'})
j('j_misprint', {'mult'})
j('j_raised_fist', {'mult'}, {'retrig_held','hand_size'})
j('j_chaos', {})
j('j_scary_face', {'chips'}, {'faces','retrig_scoring'})
j('j_abstract', {'mult'})
j('j_delayed_grat', {'money'})
j('j_gros_michel', {'mult'})
j('j_even_steven', {'mult'}, {'retrig_scoring'})
j('j_odd_todd', {'chips'}, {'retrig_scoring'})
j('j_scholar', {'chips','mult'}, {'retrig_scoring'})
j('j_business', {'money'}, {'faces','probability','retrig_scoring'})
j('j_supernova', {'mult'})
j('j_ride_the_bus', {'mult'})
j('j_egg', {'sell_value'})
j('j_runner', {'chips','straight'}, {'straight'})
j('j_ice_cream', {'chips'})
j('j_splash', {'retrig_scoring'}) -- makes every played card score
j('j_blue_joker', {'chips'})
j('j_faceless', {'money'}, {'faces','discards_up'})
j('j_green_joker', {'mult'})
j('j_superposition', {'tarot_gen','straight'}, {'straight'})
j('j_todo_list', {'money'})
j('j_cavendish', {'xmult'})
j('j_red_card', {'mult'})
j('j_square', {'chips'})
j('j_riff_raff', {'joker_gen'})
j('j_shortcut', {'straight'})
j('j_vagabond', {'tarot_gen'})
j('j_cloud_9', {'money'})
j('j_rocket', {'money'})
j('j_luchador', {})
j('j_gift', {'sell_value'})
j('j_turtle_bean', {'hand_size'})
j('j_erosion', {'mult'})
j('j_reserved_parking', {'money'}, {'faces','retrig_held','probability','hand_size'})
j('j_mail', {'money'}, {'discards_up'})
j('j_to_the_moon', {'money'}, {'money'})
j('j_hallucination', {'tarot_gen'}, {'probability'})
j('j_fortune_teller', {'mult'}, {'tarot_gen'})
j('j_juggler', {'hand_size'})
j('j_drunkard', {'discards_up'})
j('j_golden', {'money'})
j('j_popcorn', {'mult'})
j('j_walkie_talkie', {'chips','mult'}, {'retrig_scoring'})
j('j_smiley', {'mult'}, {'faces'})
j('j_ticket', {'money'}, {'gold_gen'})
j('j_swashbuckler', {'mult'}, {'sell_value','joker_gen'})
j('j_hanging_chad', {'retrig_scoring'})
j('j_shoot_the_moon', {'mult'}, {'retrig_held','hand_size'})
-- Uncommon jokers ----------------------------------------------------------
-- 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'})
j('j_marble', {'stone_gen','card_gen','enhance_gen'})
j('j_loyalty_card', {'xmult'})
j('j_dusk', {'retrig_scoring'})
j('j_fibonacci', {'mult'}, {'retrig_scoring'})
j('j_steel_joker', {'xmult'})
j('j_hack', {'retrig_scoring'})
j('j_pareidolia', {'faces'})
j('j_space', {}, {'probability'})
j('j_burglar', {'hands_up'})
j('j_blackboard', {'xmult'}, {'spades','clubs'})
j('j_sixth_sense', {'spectral_gen'}, {'spectral_gen'})
j('j_constellation', {'xmult','planet'}, {'planet'})
j('j_hiker', {'chips'}, {'retrig_scoring'})
j('j_card_sharp', {'xmult'})
j('j_madness', {'xmult','copy_target'})
j('j_seance', {'spectral_gen'}, {'straight','flush','spectral_gen'})
j('j_vampire', {'xmult'}, {'enhance_gen'})
j('j_hologram', {'xmult','copy_target'}, {'card_gen'})
j('j_cartomancer', {'tarot_gen'})
j('j_astronomer', {'planet'}, {'planet'})
j('j_burnt', {}, {'discards_up'})
j('j_bootstraps', {'mult'}, {'money'})
-- DNA copies a played card into the deck, which is how you stack duplicate
-- ranks -- the only vanilla route to a reliable Four of a Kind. Pure enabler:
-- it gives the matching-hand tags without wanting them.
j('j_dna', {'card_gen','pair','three_kind','four_kind'})
j('j_midas_mask', {'gold_gen','enhance_gen'}, {'faces'})
j('j_photograph', {'xmult','copy_target'}, {'faces','retrig_scoring'})
j('j_stone', {'chips'}, {'stone_gen'})
j('j_lucky_cat', {'xmult'}, {'probability'})
j('j_baseball', {'xmult'})
j('j_bull', {'chips'}, {'money'})
j('j_diet_cola', {})
j('j_trading', {'money'}, {'discards_up'})
j('j_flash', {'mult'})
j('j_trousers', {'mult','two_pair'}, {'two_pair'})
j('j_ramen', {'xmult'})
j('j_selzer', {'retrig_scoring'})
j('j_castle', {'chips'}, {'discards_up'})
j('j_mr_bones', {})
j('j_acrobat', {'xmult'}, {'hands_up'})
j('j_sock_and_buskin', {'retrig_scoring'}, {'faces'})
j('j_troubadour', {'hand_size'})
j('j_certificate', {'card_gen'})
j('j_smeared', {'flush','suit_flex'})
j('j_throwback', {'xmult'})
j('j_rough_gem', {'money','diamonds'}, {'retrig_scoring','diamonds','suit_flex'})
j('j_bloodstone', {'xmult','hearts'}, {'retrig_scoring','hearts','suit_flex','probability'})
j('j_arrowhead', {'chips','spades'}, {'retrig_scoring','spades','suit_flex'})
j('j_onyx_agate', {'mult','clubs'}, {'retrig_scoring','clubs','suit_flex'})
j('j_glass', {'xmult'})
j('j_ring_master', {})
j('j_flower_pot', {'xmult'}, {'suit_flex'})
j('j_merry_andy', {'discards_up'})
j('j_oops', {'probability'})
j('j_idol', {'xmult','copy_target'}, {'retrig_scoring'})
j('j_seeing_double', {'xmult','clubs'}, {'clubs','suit_flex'})
j('j_matador', {'money'})
j('j_hit_the_road', {'xmult'}, {'discards_up'})
j('j_satellite', {'money','planet'}, {'planet'})
-- Rare jokers ---------------------------------------------------------------
j('j_obelisk', {'xmult'})
j('j_ancient', {'xmult'}, {'suit_flex'})
j('j_campfire', {'xmult'}, {'joker_gen','sell_value'})
j('j_baron', {'xmult','copy_target'}, {'retrig_held','hand_size'})
j('j_duo', {'xmult','pair'}, {'pair'})
j('j_trio', {'xmult','three_kind'}, {'three_kind'})
j('j_family', {'xmult','four_kind'}, {'four_kind'})
j('j_order', {'xmult','straight'}, {'straight'})
j('j_tribe', {'xmult','flush'}, {'flush'})
j('j_stuntman', {'chips'})
-- Sell Invisible Joker to duplicate one of your Jokers: the same "the stronger
-- the target, the better" logic as Blueprint and Brainstorm.
j('j_invisible', {}, {'copy_target'})
j('j_blueprint', {}, {'copy_target'})
j('j_brainstorm', {}, {'copy_target'})
j('j_drivers_license', {'xmult'}, {'enhance_gen'})
j('j_wee', {'chips'}, {'retrig_scoring'})
-- Legendary jokers ----------------------------------------------------------
j('j_caino', {'xmult','copy_target'}, {'faces'})
j('j_triboulet', {'xmult','copy_target'}, {'retrig_scoring'})
j('j_yorick', {'xmult','copy_target'}, {'discards_up'})
j('j_chicot', {})
j('j_perkeo', {'tarot_gen'}, {'tarot_gen'})
-- Famous explicit combos (flat bonus on top of tag matching), each with a
-- short "why it works" line shown in the tooltip. Keep blurbs under ~34
-- characters so a name + blurb fits on one tooltip row.
local PAIRS = {
{'j_baron', 'j_mime', 'held Kings trigger twice'},
{'j_baron', 'j_shoot_the_moon', 'Kings and Queens pay in hand'},
{'j_burglar', 'j_mystic_summit', 'zero discards, max Mult'},
{'j_burglar', 'j_acrobat', 'more hands, big final hand'},
{'j_hack', 'j_wee', 'retriggered 2s grow Wee fast'},
{'j_hanging_chad', 'j_photograph', 'first face scores three times'},
{'j_pareidolia', 'j_sock_and_buskin', 'every card is a face to retrigger'},
{'j_pareidolia', 'j_photograph', 'any first card is the face'},
{'j_pareidolia', 'j_midas_mask', 'every played card turns Gold'},
{'j_midas_mask', 'j_ticket', 'Gold cards feed Golden Ticket'},
{'j_midas_mask', 'j_vampire', 'eats the new Gold for xMult'},
{'j_dna', 'j_hologram', 'copied cards grow Hologram'},
{'j_constellation', 'j_astronomer', 'cheap Planets stack xMult'},
{'j_fortune_teller','j_cartomancer', 'free Tarots grow the Mult'},
{'j_egg', 'j_swashbuckler', 'growing sell value becomes Mult'},
{'j_gift', 'j_swashbuckler', 'inflated sell values become Mult'},
{'j_four_fingers', 'j_square', 'four-card hands suit them both'},
{'j_marble', 'j_stone', 'adds Stones that pay chips'},
{'j_oops', 'j_lucky_cat', 'better odds, more Lucky payoffs'},
{'j_blueprint', 'j_brainstorm', 'copy chains double your best'},
{'j_mime', 'j_raised_fist', 'lowest held card pays twice'},
{'j_mime', 'j_shoot_the_moon', 'held Queens trigger twice'},
{'j_hack', 'j_fibonacci', 'retriggered low cards pay again'},
{'j_photograph', 'j_sock_and_buskin', 'the first face fires again'},
{'j_smiley', 'j_sock_and_buskin', 'faces pay their Mult twice'},
{'j_four_fingers', 'j_shortcut', '4-card gap Straights'},
{'j_four_fingers', 'j_smeared', '4-card two-suit Flushes'},
{'j_smeared', 'j_bloodstone', 'half the deck counts as Hearts'},
{'j_bull', 'j_to_the_moon', 'interest piles up, chips follow'},
{'j_marble', 'j_hologram', 'every added Stone grows Hologram'},
{'j_egg', 'j_campfire', 'sell the grown Egg for xMult'},
-- Flower Pot fills its four suit slots with a greedy if/elseif chain
-- (card.lua:4256) and Smeared makes Hearts answer for Diamonds and Spades
-- for Clubs, so the second Heart falls through into the Diamond slot: any
-- two red + two black cards fill the pot. It is a top-tier combo, and this
-- mod used to warn against it.
{'j_smeared', 'j_flower_pot', 'any 2 red + 2 black fill it'},
}
-- Known traps: pairs that actively hurt each other. Warning-only — they
-- never subtract from the synergy score; the tooltip just flags them with
-- a red "Clashes" line. Keep warnings under ~34 chars (one tooltip row).
local CLASHES = {
{'j_ride_the_bus', 'j_pareidolia', 'every card counts as a face'},
-- Burglar sets discards_left to 0 when the blind is selected
-- (`ease_discard(-discards_left)`, card.lua:2905), so every joker that
-- reads a discard is switched off for the whole run.
{'j_burglar', 'j_banner', 'no discards left to count'},
{'j_burglar', 'j_delayed_grat', 'pays per discard; you get 0'},
{'j_burglar', 'j_castle', 'no discards, never grows'},
{'j_burglar', 'j_yorick', 'no discards, never grows'},
{'j_burglar', 'j_hit_the_road', 'no discards, never grows'},
{'j_burglar', 'j_burnt', 'no discards to level a hand'},
{'j_burglar', 'j_mail', 'no discards, no rebate'},
{'j_burglar', 'j_faceless', 'no discards, no payout'},
{'j_burglar', 'j_trading', 'no discards to trade in'},
{'j_burglar', 'j_drunkard', 'its extra discards get wiped'},
{'j_burglar', 'j_merry_andy', 'its extra discards get wiped'},
-- Vampire strips enhancements in the `before` pass (card.lua:3851/3906),
-- which runs ahead of the per-card `individual` pass where the enhancement
-- payoffs are read — so it eats the cards they are counting, permanently.
{'j_vampire', 'j_steel_joker', 'drains the Steel it counts'},
{'j_vampire', 'j_lucky_cat', 'strips Lucky cards before they pay'},
{'j_vampire', 'j_ticket', 'eats Gold before Ticket pays'},
{'j_vampire', 'j_glass', 'strips Glass before it shatters'},
{'j_vampire', 'j_drivers_license', 'strips the 16 enhanced cards'},
{'j_vampire', 'j_stone', 'eats the Stone cards it counts'},
{'j_vampire', 'j_marble', 'eats the Stones Marble adds'},
-- Joker Stencil's xMult is the count of EMPTY joker slots.
{'j_stencil', 'j_riff_raff', 'fills the slots Stencil needs'},
{'j_stencil', 'j_abstract', 'one wants jokers, one wants slots'},
-- Splash makes every played card score, so a face card that used to be a
-- harmless kicker now lands in the scoring hand and resets the counter.
{'j_splash', 'j_ride_the_bus', 'any played face card resets it'},
}
-- Per-joker cautions for the buy-area tooltip: the joker is fine on its
-- own, but it interacts badly with a board you want to keep. Same length
-- budget as clash warnings.
local CAUTIONS = {
j_ceremonial = 'eats the joker to its right',
j_madness = 'destroys a random joker each blind',
j_stencil = 'xMult grows with EMPTY joker slots',
-- Positional traps: both copy a neighbour, and neither says so on the card.
j_blueprint = 'dead in the rightmost slot',
j_brainstorm = 'dead in the leftmost slot',
j_riff_raff = 'needs free joker slots',
-- Does nothing on its own: Mime retriggers held-card abilities, so with no
-- Steel/Gold cards and no Baron/Shoot the Moon it retriggers nothing.
j_mime = 'dead without held-card payoffs',
-- Board-wide side effects the card text buries.
j_burglar = 'wipes ALL discards each blind',
j_vampire = 'permanently eats enhancements',
j_obelisk = 'resets on your most played hand',
j_stuntman = 'costs you 2 hand size',
-- Decay: the numbers are exact (chips 100 / chip_mod 5, mult 20 / extra 4,
-- h_size 5 / h_mod 1, odds 6 -- all from game.lua).
j_ice_cream = 'melts to nothing in 20 hands',
j_popcorn = 'decays to nothing in 5 rounds',
j_turtle_bean = 'hand size shrinks back each round',
j_gros_michel = '1 in 6 to self-destruct each round',
}
-- How to phrase a tag link in the tooltip, from the partner's perspective:
-- `give` when the partner provides the tag, `want` when the partner consumes
-- it, `both` for cluster tags where the pair simply shares the theme.
-- Every tag that appears in the joker table must have an entry here.
local TAG_TEXT = {
faces = {give = 'turns cards into faces', want = 'pays off face cards'},
retrig_scoring = {give = 'retriggers scored cards', want = 'pays per extra trigger'},
retrig_held = {give = 'retriggers cards in hand', want = 'pays off held-card triggers'},
hand_size = {give = 'adds hand size', want = 'wants a big hand'},
hands_up = {give = 'grants extra hands', want = 'uses the extra hands'},
discards_up = {give = 'grants extra discards', want = 'feeds on discards'},
probability = {give = 'improves listed odds', want = 'relies on listed odds'},
money = {give = 'earns money', want = 'scales with your money'},
tarot_gen = {give = 'creates Tarot cards', want = 'rewards Tarot use'},
planet = {both = 'shares the Planet economy'},
spectral_gen = {both = 'shares the Spectral theme'},
gold_gen = {give = 'creates Gold cards', want = 'pays off Gold cards'},
stone_gen = {give = 'adds Stone cards', want = 'pays off Stone cards'},
enhance_gen = {give = 'enhances cards', want = 'feeds on enhancements'},
card_gen = {give = 'adds cards to the deck', want = 'grows as the deck grows'},
joker_gen = {give = 'creates spare Jokers', want = 'profits when Jokers sell'},
sell_value = {give = 'raises sell values', want = 'turns sell value into Mult'},
copy_target = {give = 'is a prime copy target', want = 'copies your best Joker'},
suit_flex = {give = 'makes suits count as others', want = 'cares which suits score'},
pair = {both = 'both reward Pairs'},
two_pair = {both = 'both reward Two Pair'},
three_kind = {both = 'both reward Three of a Kind'},
four_kind = {both = 'both reward Four of a Kind'},
straight = {both = 'both reward Straights'},
flush = {both = 'both reward Flushes'},
hearts = {both = 'both love Hearts'},
diamonds = {both = 'both love Diamonds'},
spades = {both = 'both love Spades'},
clubs = {both = 'both love Clubs'},
}
-- Order in which tags are considered when picking the one reason to show;
-- most specific mechanics first, broad engine roles last.
local TAG_ORDER = {
'retrig_held', 'retrig_scoring', 'faces',
'gold_gen', 'stone_gen', 'enhance_gen', 'card_gen',
'tarot_gen', 'spectral_gen', 'planet',
'probability', 'discards_up', 'hands_up', 'hand_size',
'money', 'sell_value', 'joker_gen',
'hearts', 'diamonds', 'spades', 'clubs',
'pair', 'two_pair', 'three_kind', 'four_kind', 'straight', 'flush',
'copy_target', 'suit_flex',
}
-- One-sentence mechanic explainers shown at the top of each catalog theme
-- page. Teaching voice: explain how the mechanic works, not what to buy.
-- Every tag listed in main.lua's THEME_TABS needs an entry (max ~90 chars).
local THEME_INFO = {
faces = 'Face cards trigger these effects; makers like Pareidolia turn every card into one.',
retrig_scoring = 'Retriggers make each scored card count again, so per-card payoffs double up.',
retrig_held = 'Some jokers pay for cards sitting in hand; retriggering the hand pays them again.',
copy_target = 'Blueprint and Brainstorm mirror a joker - the stronger the target, the better.',
probability = 'Listed "1 in X" odds are dice rolls; odds boosters make gambling jokers hit more.',
hand_size = 'A bigger hand holds more Kings, steel and gold - held-card builds love the space.',
hands_up = 'Extra hands per round are fuel for jokers that reward or count hands played.',
discards_up = 'Extra discards are fuel: several jokers grow or pay out each time you discard.',
money = 'Money is an engine too - some jokers earn it, others turn savings into power.',
sell_value = 'Sell value can be inflated first, then cashed in as Mult or a big sale.',
joker_gen = 'Spare jokers are fodder: sell them for profit or feed them to scaling jokers.',
tarot_gen = 'Free Tarot cards fix your deck and feed the jokers that count Tarot use.',
planet = 'Planets level up poker hands; this crew makes them cheaper and rewards each one.',
spectral_gen = 'Spectral cards warp the deck; these jokers create or lean on that chaos.',
gold_gen = 'Gold cards pay while held - creators supply them, payoffs cash them in.',
stone_gen = 'Stone cards are pure chips; add them, then pay off the heavier deck.',
enhance_gen = 'Enhanced cards (gold, steel, stone...) power the jokers that eat or count them.',
card_gen = 'Adding cards grows the deck - and the jokers that scale with its size.',
pair = 'These all fire on a Pair, so one played hand triggers the whole set at once.',
two_pair = 'Two Pair sets off every one of these together - stack them and replay it.',
three_kind = 'Three of a Kind triggers all of these in a single played hand.',
four_kind = 'Four of a Kind is rare but sets off this whole crew at once.',
straight = 'Straights trigger all of these; enablers make gaps and 4-card runs count.',
flush = 'Flushes trigger all of these; enablers make them far easier to assemble.',
suit_flex = 'Suit benders make cards count as two suits, unlocking every suit joker.',
hearts = 'Hearts pay these jokers when scored - suit benders widen the supply.',
diamonds = 'Diamonds pay these jokers when scored - suit benders widen the supply.',
spades = 'Spades pay these jokers when scored - suit benders widen the supply.',
clubs = 'Clubs pay these jokers when scored - suit benders widen the supply.',
}
-- Convert gives/wants arrays into sets for fast lookup.
for _, entry in pairs(J) do
local g, w = {}, {}
for _, t in ipairs(entry.gives) do g[t] = true end
for _, t in ipairs(entry.wants) do w[t] = true end
entry.gives, entry.wants = g, w
end
return { jokers = J, pairs = PAIRS, clashes = CLASHES, cautions = CAUTIONS,
tag_text = TAG_TEXT, tag_order = TAG_ORDER, theme_info = THEME_INFO }