feat!(lua.endpoints): support reroll boss blind during BLIND_SELECT
- Dominant language
- Python
- Stars
- 72
- Forks
- 18
- PR merge metrics
- No merged PRs in 30d
Description
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`):
```lua
(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
```python
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.
Contributor guide
Assessment
This issue has not been assessed yet.