diff --git a/main.lua b/main.lua index 98710f7..ca2d740 100644 --- a/main.lua +++ b/main.lua @@ -342,7 +342,14 @@ function JCA.sticker_cautions(card) local key = card.config and card.config.center and card.config.center.key local a = card.ability or {} if a.perishable and key and JCA.scalers[key] then - out[#out + 1] = 'perishable: gains die in 5 rounds' + -- Read the round count, never hardcode it: perishable_rounds is a game + -- variable (game.lua:1961 defaults it to 5, and vanilla's own sticker + -- tooltip reads it at common_events.lua:3339), so a challenge or + -- another mod can move it. perish_tally is what this card has left + -- (card.lua:680) and is the honest number when it is already ticking. + local n = a.perish_tally or (G.GAME and G.GAME.perishable_rounds) or 5 + out[#out + 1] = ('perishable: gains die in %d round%s') + :format(n, n == 1 and '' or 's') end if a.rental and key and JCA.db[key] and JCA.db[key].gives.money then out[#out + 1] = 'rental: rent may eat its payout' @@ -1282,12 +1289,17 @@ end -- Win: the buttons live inside the right-hand stats column; two levels up -- places the recap below the whole two-column stats row. 'win_cta' replaces -- 'from_game_won' when the CTA variant of the screen is shown. +-- +-- Keep BOTH pcall returns. The first is the ok flag; on a throw the second is +-- the error MESSAGE, which is a truthy string -- so testing it alone reads a +-- failure as a success and skips the win_cta fallback, which exists precisely +-- because the two win screens anchor at different ids. if create_UIBox_win then local orig_win = create_UIBox_win function create_UIBox_win(...) local ui = orig_win(...) - local _, inserted = pcall(insert_recap, ui, 'from_game_won', 2, 1) - if not inserted then pcall(insert_recap, ui, 'win_cta', 2, 1) end + local ok, inserted = pcall(insert_recap, ui, 'from_game_won', 2, 1) + if not (ok and inserted) then pcall(insert_recap, ui, 'win_cta', 2, 1) end return ui end end diff --git a/test.lua b/test.lua index 4c4cca6..5eabbf5 100644 --- a/test.lua +++ b/test.lua @@ -148,6 +148,24 @@ for _, f in ipairs(FusionJokers.fusions) do end end +-- Post-run recap screens. main.lua wraps these at load, so they have to exist +-- before it is required. Each returns a FRESH tree shaped like vanilla's +-- (root > column > row > button), because the recap is inserted by walking up +-- from the button id. RECAP_ANCHOR picks which id the win screen exposes, so +-- the win_cta fallback can be exercised. +RECAP_ANCHOR = 'from_game_won' +local function button_tree(id) + return {n = 'ROOT', nodes = { + {n = 'C', nodes = { + {n = 'R', nodes = { + {n = 'C', config = {id = id}}, + }}, + }}, + }} +end +create_UIBox_game_over = function() return button_tree('from_game_over') end +create_UIBox_win = function() return button_tree(RECAP_ANCHOR) end + local data = dofile('synergies.lua') dofile('main.lua') @@ -506,6 +524,42 @@ eq(G.GAME.jca_peak_synergy, 7, 'selling down does not lower the recorded peak') G.GAME = {} JCA.config.discovered, JCA.config.themes_found = saved_disc, saved_themes +-------------------------------------------------------------------------------- +section('Engine: the post-run recap finds its anchor on both screens') +-------------------------------------------------------------------------------- + +-- The recap is inserted relative to a vanilla button id by walking UP the +-- parent trail, and the two win screens anchor at different ids -- so the +-- fallback is the whole point of the hook, not decoration. +local function tree_texts(node, out) + out = out or {} + if type(node) ~= 'table' then return out end + if node.config and node.config.text then out[#out + 1] = node.config.text end + for _, child in ipairs(node.nodes or {}) do tree_texts(child, out) end + return out +end +local function has_recap(ui) + for _, t in ipairs(tree_texts(ui)) do + if t == 'Combos fielded this run' then return true end + end + return false +end + +G.GAME = {jca_run_combos = {['j_baron|j_mime'] = true}} +ok(has_recap(create_UIBox_game_over()), 'the game over screen gets the recap') +ok(has_recap(create_UIBox_win()), 'so does the win screen, at its own anchor') + +-- The CTA variant of the win screen replaces 'from_game_won' with 'win_cta'; +-- the first insert finds nothing and the fallback has to carry it. +RECAP_ANCHOR = 'win_cta' +ok(has_recap(create_UIBox_win()), 'and the win_cta variant falls back correctly') +RECAP_ANCHOR = 'from_game_won' + +-- Nothing fielded and no peak: no row at all, rather than an empty heading. +G.GAME = {} +ok(not has_recap(create_UIBox_win()), 'a run with no combos gets no recap row') +G.GAME = {} + -------------------------------------------------------------------------------- section('Engine: deck context counts base suits') -------------------------------------------------------------------------------- @@ -708,6 +762,20 @@ eq(#JCA.sticker_cautions(stickered('j_ride_the_bus', {})), 0, eq(#JCA.sticker_cautions(stickered('j_mod_unknown', {perishable = true, rental = true})), 0, 'unknown keys never warn and never error') +-- The round count is a GAME VARIABLE (G.GAME.perishable_rounds, game.lua:1961), +-- not the constant 5 -- a challenge or another mod can move it, and vanilla's +-- own sticker tooltip reads it. Hardcoding it would state a wrong number. +G.GAME = {perishable_rounds = 3} +eq(JCA.sticker_cautions(stickered('j_ride_the_bus', {perishable = true}))[1], + 'perishable: gains die in 3 rounds', 'the caution reads the game round count') +eq(JCA.sticker_cautions(stickered('j_ride_the_bus', + {perishable = true, perish_tally = 1}))[1], + 'perishable: gains die in 1 round', + 'a ticking sticker reports what is LEFT, and says "round" once') +G.GAME = {} +eq(JCA.sticker_cautions(stickered('j_ride_the_bus', {perishable = true}))[1], + 'perishable: gains die in 5 rounds', 'falling back to vanilla 5 outside a run') + -------------------------------------------------------------------------------- section('Data: themes are reachable') --------------------------------------------------------------------------------