Stop giving advice the game will not let you follow
Four fixes, all traced to the running source. * The sell advisor could tell you to sell an ETERNAL joker, which Card:can_sell_card refuses outright (card.lua:1993). It now never names one, though they still count toward the best score -- they are part of the build you are keeping. Advice you cannot take is worse than none. * Copiers CHAIN. SMODS.blueprint_effect recurses (utils.lua:2269) and Blueprint's own blueprint_compat is true, so Blueprint -> Blueprint -> Baron really does give two Barons. The tooltip used to answer "Copying Blueprint", the one reply that helps nobody; it now walks the chain to the joker actually copied and shows the route. Capped at the board size like vanilla, and a Blueprint/Brainstorm ring resolves to nothing instead of hanging. * blueprint_compat is tested for TRUTH by vanilla (utils.lua:2254), not for `~= false` as I had it. A modded joker that simply omits the flag is NOT copyable, and this mod supports other mods' jokers by design, so saying otherwise was a lie about someone else's content. * A debuffed joker copies as nothing (same line), and a debuffed joker does nothing at all this blind -- both now say so. Scoring is left alone: a debuff is temporary and the advisor advises on the build, so hiding a debuffed partner from "Active combos" would misrepresent it. Also: never call SMODS.is_eternal() from an advisor path. It runs a full SMODS.calculate_context per call (utils.lua:3079) -- far too heavy for a hover -- and outside a live run it reported EVERY joker as Eternal, which silently disabled the sell advisor. The smoke harness caught that; the stub could not have. 721 pass locally, 12 in-game. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -295,12 +295,61 @@ eq(JCA.copy_source(G.jokers.cards[1]), nil, 'a non-copier has no copy source')
|
||||
-- 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])
|
||||
local target, status = 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)')
|
||||
eq(status, 'incompatible', 'and flags that vanilla will not copy it (blueprint_compat)')
|
||||
|
||||
-- Vanilla tests the flag for TRUTH, so a modded joker that omits it is NOT
|
||||
-- copyable. `~= false` would wrongly call this one fine.
|
||||
local modded = field('j_blueprint', 'j_baron')
|
||||
modded[2].config.center.blueprint_compat = nil
|
||||
eq(select(2, JCA.copy_source(modded[1])), 'incompatible',
|
||||
'a joker with no blueprint_compat flag at all is treated as uncopyable')
|
||||
|
||||
local fine = field('j_blueprint', 'j_baron')
|
||||
eq(select(2, JCA.copy_source(fine[1])), true, 'a copyable target reports as copyable')
|
||||
fine[2].config.center.blueprint_compat = true
|
||||
eq(select(2, JCA.copy_source(fine[1])), 'ok', 'a copyable target reports as ok')
|
||||
|
||||
-- A debuffed joker is copied as nothing (SMODS.blueprint_effect bails on it).
|
||||
local debuffed = field('j_blueprint', 'j_baron')
|
||||
debuffed[2].config.center.blueprint_compat = true
|
||||
debuffed[2].debuff = true
|
||||
eq(select(2, JCA.copy_source(debuffed[1])), 'debuffed',
|
||||
'copying a debuffed joker yields nothing')
|
||||
|
||||
-- Copiers CHAIN: Blueprint -> Blueprint -> Baron really does give two Barons, so
|
||||
-- the answer must be Baron, not "a Blueprint".
|
||||
local chained = field('j_blueprint', 'j_blueprint', 'j_baron')
|
||||
for _, c in ipairs(chained) do c.config.center.blueprint_compat = true end
|
||||
local final, st, via = JCA.copy_source(chained[1])
|
||||
eq(final, chained[3], 'the chain resolves to the joker actually copied')
|
||||
eq(st, 'ok', 'and reports it as live')
|
||||
eq(#via, 1, 'and records the copier walked through on the way')
|
||||
eq(via[1], chained[2], 'which is the middle Blueprint')
|
||||
|
||||
-- A copier ring copies nothing, and must not hang.
|
||||
local ring = field('j_brainstorm', 'j_blueprint')
|
||||
for _, c in ipairs(ring) do c.config.center.blueprint_compat = true end
|
||||
eq(JCA.copy_source(ring[2]), nil, 'a Blueprint/Brainstorm ring resolves to nothing')
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
section('Engine: the sell advisor respects what you can actually sell')
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
-- Eternal jokers CANNOT be sold (Card:can_sell_card, card.lua:1993). Telling the
|
||||
-- player to sell one is advice they are not allowed to take.
|
||||
--
|
||||
-- 8 Ball scores 0 here and Scary Face 1, so 8 Ball is the natural cut -- until it
|
||||
-- turns out to be Eternal, and the advice has to move to the next one down.
|
||||
local et = field('j_8_ball', 'j_scary_face', 'j_baron', 'j_mime')
|
||||
G.jokers.config.card_limit = 4
|
||||
eq(JCA.weakest_link(), et[1], 'normally it flags the lowest-scoring joker')
|
||||
|
||||
et[1].ability = {eternal = true}
|
||||
local pick = JCA.weakest_link()
|
||||
ok(pick ~= et[1], 'but never an Eternal one, because it cannot be sold',
|
||||
pick and pick.config.center.key or 'nil')
|
||||
eq(pick, et[2], 'it falls through to the next sellable candidate instead')
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
section('Engine: Ceremonial Dagger names its victim')
|
||||
|
||||
Reference in New Issue
Block a user