Skip to content

feat!(lua.endpoints): support reroll boss blind during BLIND_SELECT #212

Description

@S1M0N38

The API cannot trigger the game's "Reroll Boss Blind" action — the conditional button that appears during blind selection when the player holds the Director's Cut or Retcon voucher. Verified against vendors/balatro/ and confirmed it is the only voucher-gated conditional button in the base game.

Bug

There is no endpoint to reroll the upcoming boss blind. A player holding v_directors_cut (1 reroll per ante, $10) or v_retcon (unlimited rerolls, $10) sees a "Reroll Boss" button at BLIND_SELECT, but the API exposes nothing equivalent.

The button is defined in create_UIBox_blind_select (functions/UI_definitions.lua:1427):

(G.GAME.used_vouchers["v_retcon"] or G.GAME.used_vouchers["v_directors_cut"]) and
UIBox_button({label = {localize('b_reroll_boss'), localize('$')..'10'}, button = "reroll_boss", func = 'reroll_boss_button'}) or nil

It is gated by G.FUNCS.reroll_boss_button (functions/button_callbacks.lua:2784), which requires $10 available and, for Director's Cut only, that the ante's reroll hasn't been used (not G.GAME.round_resets.boss_rerolled). The handler G.FUNCS.reroll_boss (button_callbacks.lua:2800) deducts $10, calls get_new_boss() for a random weighted pick (functions/common_events.lua:2338), and rebuilds the boss blind UI.

set.lua:276 already calls G.FUNCS.reroll_boss() — but as a debug override: it pre-seeds G.GAME.perscribed_bosses[ante] so the result is deterministic, free, and doesn't set boss_rerolled. This is semantically distinct from the player action and not a substitute. The legitimate player reroll (random, costs $10, respects the voucher gate) is entirely unreachable.

Gamestate gap: blinds.boss exposes key/name/score/effect/status but not whether a reroll is available. A client must infer it from used_vouchers + tracking boss_rerolled locally.

Fix

Add a reroll_boss endpoint (no params, mirrors reroll for the shop). Gate on BLIND_SELECT state, require v_directors_cut or v_retcon, verify $10 funds (accounting for bankrupt_at), and for Director's Cut ensure not G.GAME.round_resets.boss_rerolled. Call G.FUNCS.reroll_boss({}) without touching perscribed_bosses, so the result stays random. Optionally surface a boss_reroll_available boolean on blinds.boss in gamestate.lua so clients can detect the action without recomputing the gate.

PoC test

def test_reroll_boss_blind(self, client):
    """Reroll the upcoming boss blind for $10 when holding Director's Cut."""
    gamestate = load_fixture(client, "reroll_boss",
        "state-BLIND_SELECT--used_vouchers.v_directors_cut-1--money-20")
    original_boss = gamestate["blinds"]["boss"]["key"]
    response = api(client, "reroll_boss", {})
    after = assert_gamestate_response(response)
    assert after["blinds"]["boss"]["key"] != original_boss  # random new boss
    assert after["money"] == 10  # $10 deducted

Sibling of #209.

Metadata

Metadata

Assignees

Labels

completed-in-devThis issue have been solved in dev branch

Fields

No fields configured for Feature.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions