From ff71a8ef125a5f838dda41f5fdbca91ec4da44be Mon Sep 17 00:00:00 2001 From: funman300 Date: Tue, 14 Jul 2026 13:30:20 -0700 Subject: [PATCH] Split rank-limited retriggers so Hack stops courting face payoffs Business Card claimed a synergy with Hack, but Hack only retriggers 2s-5s and Business Card only pays on faces. The single retrig_scoring tag lumped every retriggerer with every retrigger payoff. retrig_scoring now means rank-agnostic (Dusk, Seltzer, Hanging Chad, Splash). Hack gives retrig_low; Sock and Buskin gives retrig_faces. Payoffs want only the scopes their ranks can reach: low-rank payoffs (Even Steven, Odd Todd, Walkie Talkie, Fibonacci, Wee) add retrig_low, face payoffs (Scary Face, Business, Photograph, Triboulet) add retrig_faces, rank-agnostic ones (Hiker, the suit gems, The Idol) add both. Scholar keeps only retrig_scoring - no retriggerer targets Aces. This also kills Hack+Scholar, Hack+Scary Face, Hack+Photograph, Hack+Triboulet, and Sock and Buskin vs every low-rank payoff. Both new tags are full themes (catalog page, label, teaching line) so Hack and Sock and Buskin stay visible in the Engine tab. Co-Authored-By: Claude Fable 5 --- AUDIT.md | 10 ++++++---- main.lua | 3 +++ synergies.lua | 47 ++++++++++++++++++++++++++++------------------- test.lua | 13 ++++++++++--- 4 files changed, 47 insertions(+), 26 deletions(-) diff --git a/AUDIT.md b/AUDIT.md index d08f38c..57deffa 100644 --- a/AUDIT.md +++ b/AUDIT.md @@ -14,7 +14,7 @@ does not get redone from scratch every session. | Famous pairs | 36 | | Clashes | 25 | | Cautions | 15 | -| Themes | 29 | +| Themes | 31 | | Jokers in no famous pair | 102 | ## Jokers the advisor is silent about @@ -88,8 +88,10 @@ A tag with two is fragile: drop either joker and the theme dies. | `pair` | 4 | 3 | 4 | yes | | `planet` | 3 | 3 | 3 | yes | | `probability` | 1 | 7 | 8 | yes | +| `retrig_faces` | 1 | 10 | 11 | yes | | `retrig_held` | 1 | 4 | 5 | yes | -| `retrig_scoring` | 6 | 16 | 22 | yes | +| `retrig_low` | 1 | 11 | 12 | yes | +| `retrig_scoring` | 4 | 16 | 20 | yes | | `sell_value` | 2 | 2 | 4 | yes | | `spades` | 2 | 3 | 3 | yes | | `spectral_gen` | 2 | 2 | **2** | yes | @@ -107,9 +109,7 @@ mis-tagged, the error is spread across the whole database. | joker | partners scoring >= 2 | |---|---| -| `j_sock_and_buskin` | 18 | | `j_dusk` | 16 | -| `j_hack` | 16 | | `j_hanging_chad` | 16 | | `j_selzer` | 16 | | `j_smeared` | 16 | @@ -119,6 +119,8 @@ mis-tagged, the error is spread across the whole database. | `j_bull` | 15 | | `j_four_fingers` | 12 | | `j_seance` | 12 | +| `j_sock_and_buskin` | 12 | +| `j_blueprint` | 11 | Jokers with **no** partner scoring >= 2 anywhere in the game: 25 diff --git a/main.lua b/main.lua index fbbf904..d776fd0 100644 --- a/main.lua +++ b/main.lua @@ -393,6 +393,7 @@ end -- mult/chips/xmult are deliberately absent — they hint nothing specific). local TAG_LABEL = { faces = 'face cards', retrig_scoring = 'played-card retriggers', + retrig_low = 'low-card (2-5) retriggers', retrig_faces = 'face-card retriggers', retrig_held = 'held-card retriggers', hand_size = 'hand size', hands_up = 'extra hands', discards_up = 'discards', probability = 'listed odds', money = 'money', @@ -819,6 +820,8 @@ local THEME_TABS = { { label = 'Engine', themes = { {tag = 'faces', name = 'Face cards'}, {tag = 'retrig_scoring', name = 'Retrigger played cards'}, + {tag = 'retrig_low', name = 'Retrigger low cards'}, + {tag = 'retrig_faces', name = 'Retrigger face cards'}, {tag = 'retrig_held', name = 'Retrigger held cards'}, {tag = 'copy_target', name = 'Copy targets (Blueprint/Brainstorm)'}, {tag = 'probability', name = 'Probability'}, diff --git a/synergies.lua b/synergies.lua index 45b75a5..8e1d3ea 100644 --- a/synergies.lua +++ b/synergies.lua @@ -8,7 +8,12 @@ --- "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_scoring retriggers (or multiplies triggers of) ANY played +--- card -- reserve it for rank-agnostic retriggerers +--- retrig_low / retrig_faces rank-limited retriggers (Hack's 2s-5s, Sock +--- and Buskin's faces); wanted only by payoffs those +--- ranks can reach, so Hack never claims to feed a +--- face payoff like Business Card --- retrig_held retriggers cards held in hand --- hand_size / hands_up / discards_up resource increases --- probability improves listed probabilities / cares about them @@ -53,16 +58,16 @@ 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_scary_face', {'chips'}, {'faces','retrig_scoring','retrig_faces'}) -- Abstract Joker's Mult is the joker count (`#G.jokers.cards`, card.lua:4119), so -- anything that hands you spare Jokers feeds it. Riff-Raff is +6 Mult on its own. j('j_abstract', {'mult'}, {'joker_gen'}) 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_even_steven', {'mult'}, {'retrig_scoring','retrig_low'}) +j('j_odd_todd', {'chips'}, {'retrig_scoring','retrig_low'}) j('j_scholar', {'chips','mult'}, {'retrig_scoring'}) -j('j_business', {'money'}, {'faces','probability','retrig_scoring'}) +j('j_business', {'money'}, {'faces','probability','retrig_scoring','retrig_faces'}) j('j_supernova', {'mult'}) j('j_ride_the_bus', {'mult'}) j('j_egg', {'sell_value'}) @@ -97,7 +102,7 @@ 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_walkie_talkie', {'chips','mult'}, {'retrig_scoring','retrig_low'}) j('j_smiley', {'mult'}, {'faces'}) j('j_ticket', {'money'}, {'gold_gen'}) j('j_swashbuckler', {'mult'}, {'sell_value','joker_gen'}) @@ -114,16 +119,16 @@ 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_fibonacci', {'mult'}, {'retrig_scoring','retrig_low'}) j('j_steel_joker', {'xmult'}) -j('j_hack', {'retrig_scoring'}) +j('j_hack', {'retrig_low'}) -- retriggers 2-5 ONLY, never faces/aces 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_hiker', {'chips'}, {'retrig_scoring','retrig_low','retrig_faces'}) j('j_card_sharp', {'xmult'}) j('j_madness', {'xmult','copy_target'}) j('j_seance', {'spectral_gen'}, {'straight','flush','spectral_gen'}) @@ -138,7 +143,7 @@ j('j_bootstraps', {'mult'}, {'money'}) -- 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_photograph', {'xmult','copy_target'}, {'faces','retrig_scoring','retrig_faces'}) j('j_stone', {'chips'}, {'stone_gen'}) j('j_lucky_cat', {'xmult'}, {'probability'}) j('j_baseball', {'xmult'}) @@ -152,21 +157,21 @@ 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_sock_and_buskin', {'retrig_faces'}, {'faces'}) -- retriggers faces ONLY 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_rough_gem', {'money','diamonds'}, {'retrig_scoring','retrig_low','retrig_faces','diamonds','suit_flex'}) +j('j_bloodstone', {'xmult','hearts'}, {'retrig_scoring','retrig_low','retrig_faces','hearts','suit_flex','probability'}) +j('j_arrowhead', {'chips','spades'}, {'retrig_scoring','retrig_low','retrig_faces','spades','suit_flex'}) +j('j_onyx_agate', {'mult','clubs'}, {'retrig_scoring','retrig_low','retrig_faces','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_idol', {'xmult','copy_target'}, {'retrig_scoring','retrig_low','retrig_faces'}) j('j_seeing_double', {'xmult','clubs'}, {'clubs','suit_flex'}) j('j_matador', {'money'}) j('j_hit_the_road', {'xmult'}, {'discards_up'}) @@ -189,11 +194,11 @@ 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'}) +j('j_wee', {'chips'}, {'retrig_scoring','retrig_low'}) -- Legendary jokers ---------------------------------------------------------- j('j_caino', {'xmult','copy_target'}, {'faces'}) -j('j_triboulet', {'xmult','copy_target'}, {'retrig_scoring'}) +j('j_triboulet', {'xmult','copy_target'}, {'retrig_scoring','retrig_faces'}) j('j_yorick', {'xmult','copy_target'}, {'discards_up'}) j('j_chicot', {}) j('j_perkeo', {'tarot_gen'}, {'tarot_gen'}) @@ -345,6 +350,8 @@ local CAUTIONS = { 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_low = {give = 'retriggers 2s through 5s', want = 'pays per extra trigger'}, + retrig_faces = {give = 'retriggers face 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'}, @@ -395,7 +402,7 @@ local TAG_TEXT = { -- 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', + 'retrig_held', 'retrig_low', 'retrig_faces', 'retrig_scoring', 'faces', 'gold_gen', 'stone_gen', 'enhance_gen', 'card_gen', 'tarot_gen', 'spectral_gen', 'planet', 'probability', 'discards_up', 'hands_up', 'hand_size', @@ -411,6 +418,8 @@ local TAG_ORDER = { 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_low = 'Hack replays each scored 2-5; low-rank payoffs like Fibonacci collect twice.', + retrig_faces = 'Sock and Buskin replays scored face cards, so face payoffs trigger again.', 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.', diff --git a/test.lua b/test.lua index c5d9fa2..1bc0464 100644 --- a/test.lua +++ b/test.lua @@ -140,10 +140,17 @@ section('Engine: scoring tiers') eq(JCA.score('j_baron', 'j_mime'), 6, 'famous pair + shared tags = 6') eq(JCA.score('j_blueprint', 'j_brainstorm'), 4, 'famous pair alone = 4') eq(JCA.score('j_jolly', 'j_sly'), 4, 'hand-type cluster (2 + 2) = 4') --- Hack gives retrig_scoring, which The Idol wants; neither side triggers the --- generic complement (Hack adds no Mult or Chips), so this is a clean 2. +-- Hack gives retrig_low, which The Idol wants (the Idol card can roll any +-- rank); neither side triggers the generic complement (Hack adds no Mult or +-- Chips), so this is a clean 2. eq(JCA.score('j_hack', 'j_idol'), 2, 'single tag match = 2') eq(JCA.score('j_8_ball', 'j_scary_face'), 0, 'unrelated jokers = 0') +-- Rank-limited retriggers must not claim payoffs their ranks cannot reach: +-- Hack replays only 2s-5s, so it never feeds a face or Ace payoff; Sock and +-- Buskin replays only faces, so Wee's 2s mean nothing to it. +eq(JCA.score('j_hack', 'j_business'), 0, 'Hack cannot retrigger Business Card faces') +eq(JCA.score('j_hack', 'j_scholar'), 0, 'Hack cannot retrigger Scholar Aces') +eq(JCA.score('j_sock_and_buskin', 'j_wee'), 0, 'Sock and Buskin cannot retrigger 2s') -- The generic complement stacks on top of a tag match: Gluttonous gives clubs -- (Blackboard wants them) AND feeds Blackboard's xMult, so 2 + 1. eq(JCA.score('j_gluttenous_joker', 'j_blackboard'), 3, 'tag match + generic complement = 3') @@ -505,7 +512,7 @@ JCA.register('j_mod_test', {'mult', 'bogus_tag'}, {'retrig_scoring'}) ok(JCA.db.j_mod_test, 'JCA.register adds a joker') ok(JCA.db.j_mod_test.gives.mult, 'known tags survive registration') ok(not JCA.db.j_mod_test.gives.bogus_tag, 'unknown tags are dropped, not fatal') -ok(JCA.score('j_mod_test', 'j_hack') >= 2, 'a registered joker scores against vanilla') +ok(JCA.score('j_mod_test', 'j_dusk') >= 2, 'a registered joker scores against vanilla') local before = JCA.score('j_mod_test', 'j_baron') JCA.register_pair('j_mod_test', 'j_baron', 'test blurb') eq(JCA.score('j_mod_test', 'j_baron') - before, 4, 'JCA.register_pair adds the +4 pair bonus')