Fix enabler phrasing, wake two silent jokers, read the board order
* The tooltip mis-taught every pure enabler. JCA.explain used a cluster tag's `both` text whenever EITHER side was a full member, so Four Fingers next to Runner claimed "both reward Straights" -- Four Fingers does not reward Straights, it makes them reachable. `both` now requires both sides, and every cluster tag carries give/want phrasings. * Blue Joker and Abstract Joker scored 0 against all 150 jokers. Blue Joker scales on #G.deck.cards, so it wants card_gen (DNA, Marble, Certificate); Abstract scales on #G.jokers.cards, so it wants joker_gen (Riff-Raff is +6 Mult on its own). Both verified in card.lua. Steel and Glass Joker look like the same fix but are NOT: they need Steel/Glass specifically, and the only enhance_gen givers make Gold and Stone, so tagging them would invent a synergy that does not exist. * Copy jokers are positional and the board order was there all along. JCA.copy_source reports what an owned Blueprint/Brainstorm is actually copying, including when the target is one of the 29 centers vanilla refuses to copy (blueprint_compat = false), or when it is copying nothing at all. * The sell advisor now counts clashes when ranking what to cut. JCA.score stays pure -- a trap must never make a card look like a combo -- but a joker eating another one's payoff is exactly the one to sell. The suite caught a real bug while writing this: `(mode == 'right') and cards[idx+1] or cards[1]` falls through to the LEFTMOST joker when the right-hand slot is nil, so a rightmost Blueprint would have named the wrong card. 673 pass here, 9 in-game via the smoke harness. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -237,6 +237,91 @@ for _, t in ipairs(data.tag_order) do
|
||||
ok(members[t] or ENGINE_TAGS[t], ('TAG_ORDER tag %q is used by at least one joker'):format(t))
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
section('Data: tooltip phrasing')
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
-- A cluster tag needs all three phrasings. "both reward Straights" is only true
|
||||
-- when BOTH jokers reward them; a pure enabler (Four Fingers, DNA) gives the tag
|
||||
-- without wanting it and must not be described as rewarding the hand.
|
||||
for _, t in ipairs(sorted_keys(data.tag_text)) do
|
||||
local text = data.tag_text[t]
|
||||
if text.both then
|
||||
ok(text.give and text.want,
|
||||
('cluster tag %q has give/want phrasings, not just "both"'):format(t),
|
||||
'an enabler paired with a payoff would otherwise be told it "rewards" the hand')
|
||||
end
|
||||
end
|
||||
|
||||
-- Four Fingers ENABLES straights (it does not reward them); Runner rewards them.
|
||||
eq(JCA.explain('j_four_fingers', 'j_runner'), 'rewards Straights',
|
||||
'a payoff partner is described as rewarding the hand')
|
||||
eq(JCA.explain('j_runner', 'j_four_fingers'), 'makes Straights easier',
|
||||
'an enabler partner is described as enabling, not rewarding')
|
||||
eq(JCA.explain('j_dna', 'j_family'), 'rewards Four of a Kind',
|
||||
'The Family rewards Four of a Kind')
|
||||
eq(JCA.explain('j_family', 'j_dna'), 'makes Four of a Kind easier',
|
||||
'DNA enables Four of a Kind')
|
||||
-- Two full cluster members still get the "both" phrasing.
|
||||
eq(JCA.explain('j_jolly', 'j_sly'), 'both reward Pairs',
|
||||
'two payoffs still read as "both"')
|
||||
|
||||
for _, t in ipairs(sorted_keys(data.theme_info)) do
|
||||
ok(JCA.tag_label[t], ('theme %q has a TAG_LABEL for learning-mode hints'):format(t))
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
section('Engine: copy jokers are positional')
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
-- Blueprint copies the joker to its RIGHT, Brainstorm the LEFTMOST. The board
|
||||
-- order decides what they actually copy, and a copier in the wrong slot is dead.
|
||||
local bp_board = field('j_baron', 'j_blueprint', 'j_mime')
|
||||
eq(JCA.copy_source(bp_board[2]), bp_board[3], 'Blueprint copies the joker to its right')
|
||||
|
||||
field('j_baron', 'j_mime', 'j_blueprint')
|
||||
eq(JCA.copy_source(G.jokers.cards[3]), nil, 'a rightmost Blueprint copies nothing')
|
||||
|
||||
local bs_board = field('j_baron', 'j_brainstorm', 'j_mime')
|
||||
eq(JCA.copy_source(bs_board[2]), bs_board[1], 'Brainstorm copies the leftmost joker')
|
||||
|
||||
field('j_brainstorm', 'j_baron')
|
||||
eq(JCA.copy_source(G.jokers.cards[1]), nil, 'a leftmost Brainstorm copies nothing')
|
||||
|
||||
field('j_baron', 'j_mime')
|
||||
eq(JCA.copy_source(G.jokers.cards[1]), nil, 'a non-copier has no copy source')
|
||||
|
||||
-- vanilla's own "cannot be copied" flag: 29 jokers carry blueprint_compat = false
|
||||
-- and yield nothing at all when copied.
|
||||
local uncopyable = field('j_blueprint', 'j_splash')
|
||||
uncopyable[2].config.center.blueprint_compat = false
|
||||
local target, copyable = JCA.copy_source(uncopyable[1])
|
||||
eq(target, uncopyable[2], 'it still reports what sits in the copied slot')
|
||||
eq(copyable, false, 'and flags that vanilla will not copy it (blueprint_compat)')
|
||||
|
||||
local fine = field('j_blueprint', 'j_baron')
|
||||
eq(select(2, JCA.copy_source(fine[1])), true, 'a copyable target reports as copyable')
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
section('Engine: the sell advisor weighs clashes')
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
-- Clashes must never touch a synergy score...
|
||||
local pre = JCA.score('j_vampire', 'j_ticket')
|
||||
ok(pre >= 0, 'a clashing pair still scores by its tags alone')
|
||||
ok(JCA.clash_blurb[
|
||||
('j_ticket' < 'j_vampire') and 'j_ticket|j_vampire' or 'j_vampire|j_ticket'],
|
||||
'Vampire + Golden Ticket is a known clash')
|
||||
|
||||
-- ...but on a full board, the joker stuck in a clash is the one to cut, even
|
||||
-- when a joker with no synergy at all is sitting next to it.
|
||||
local board = field('j_vampire', 'j_ticket', 'j_baron', 'j_mime')
|
||||
G.jokers.config.card_limit = 4
|
||||
local cut = JCA.weakest_link()
|
||||
ok(cut == board[1] or cut == board[2],
|
||||
'the sell advisor flags one of the two clashing jokers',
|
||||
cut and cut.config.center.key or 'nil')
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
section('Data: themes are reachable')
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user