coder / coder/balatrobot

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

Open
#212 0 comments 0 reactions 1 assignee Claimed by @S1M0N38 View on GitHub
completed-in-dev
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.