Skip to content

fix(lua.endpoints): dismiss win overlay so endless mode stays responsive#200

Merged
S1M0N38 merged 5 commits into
devfrom
endless
Jun 15, 2026
Merged

fix(lua.endpoints): dismiss win overlay so endless mode stays responsive#200
S1M0N38 merged 5 commits into
devfrom
endless

Conversation

@S1M0N38

@S1M0N38 S1M0N38 commented Jun 13, 2026

Copy link
Copy Markdown
Collaborator

Problem

After winning the ante-8 boss blind, play() returned won=true the instant the win was detected — but win_game() fires right after and raises the win overlay (G.OVERLAY_MENU) while pausing the game (G.SETTINGS.paused = true). The bot was left in a paused session where every subsequent endless-mode endpoint ran on wall-clock REAL time instead of turbo, leaving endless play permanently degraded (~9s per play vs ~1.6s).

Root cause

Two compounding issues in src/lua/endpoints/play.lua:

  1. The polling event was killed by the pause. The condition event was created with created_on_pause = true, but Balatro's event.lua ignores that config field — it only honours pause_force (self.created_on_pause = config.pause_force or G.SETTINGS.paused). The event was created while unpaused, so it got pause_skip'd the moment win_game() paused, and never recovered.

  2. The overlay was never dismissed. The won branch responded immediately and left the overlay up, keeping the game paused for all following requests.

Fix

In src/lua/endpoints/play.lua:

  • Switch the polling event from created_on_pause = truepause_force = true so it keeps running while paused.
  • Dismiss the win overlay inside the event: if G.GAME.won and G.OVERLAY_MENU then G.FUNCS.exit_overlay_menu() end. This is safe because the overlay only appears after ROUND_EVAL is entered (so G.round_eval exists), and the delayed win events (Jimbo speech, endless-round text) guard against a nil G.OVERLAY_MENU.
  • The won path now flows through the existing has_blind1 and has_cash_out_button checkpoint before responding, instead of short-circuiting early.

Tests

Two new regression tests, both driven live rather than via save/load fixtures — load resets the run and discards the paused/overlay state, which masked the bug entirely:

  • test_play_endless_mode_after_won — drives a full win, continues into endless mode, and asserts the next endless play stays responsive (elapsed < 5s). This is the test that fails without the fix (~9.5s) and passes with it (~1.6s).
  • test_won_persists_through_endless_cycle — asserts won=true survives the cash_out → next_round → select cycle.

Removed the state-SELECTING_HAND--won-true fixture from fixtures.json: the save/load mechanism structurally cannot capture the paused/overlay state, so it could not represent this scenario.

Verification

make all green against the documented environment (.envrc):

lint / format / typecheck : clean (63 files unchanged)
tests/cli                 : 157 passed, 14 skipped
tests/lua                 : 513 passed, 2 skipped

⚠️ Reviewer / CI note

The responsiveness test requires the turbo profile + headless render set in .envrc:

export BALATROBOT_RENDER=headless
export BALATROBOT_SETTINGS=turbo

Under the slow default (render=headfull), every game op is ~5x slower and the elapsed < 5s assertion becomes unreliable. There is currently no CI workflow in .github/ setting these, so reviewers should direnv allow (or source .envrc) before running tests/lua locally.

Commits

  • a621bab fix(lua.endpoints): dismiss win overlay so endless mode stays responsive
  • 282244d test(lua): add endless-mode win overlay regression tests
  • 81d2ac7 docs(skill): shorten balatrobot serve startup wait

S1M0N38 added 3 commits June 14, 2026 00:46
Winning the ante-8 boss triggers win_game(), which pauses the game and
raises the win overlay AFTER play() had already returned won=true. The
bot was left in a paused session where every subsequent endless-mode
endpoint ran on wall-clock REAL time instead of turbo.

The polling event never recovered from the pause because event.lua
ignores `created_on_pause` in config (it only honours `pause_force`).
Switch to pause_force=true, and dismiss the overlay inside the event
once ROUND_EVAL is entered (G.round_eval already exists; the delayed
win events guard against a nil G.OVERLAY_MENU). The won path now flows
through the existing cash_out_button checkpoint before responding.
Drive the win cycle live instead of via save/load fixtures: `load`
resets the run and discards the paused/overlay state, which masked
the bug entirely. The play test asserts an endless play stays
responsive (elapsed < 5s) rather than crawling on wall-clock time;
the gamestate test asserts won=true persists across the
cash_out/next_round/select cycle. Removes the now-unused
state-SELECTING_HAND--won-true fixture that could not capture the
paused/overlay state.
5s is enough for the server health check on the turbo profile; the
previous 10s wait added needless delay to the quick-reference example.
Copilot AI review requested due to automatic review settings June 13, 2026 22:48

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes an endless-mode responsiveness regression caused by the ante-8 win overlay pausing the game and preventing the play endpoint’s polling event from completing, leaving the bot session effectively “stuck” running on wall-clock time.

Changes:

  • Update play endpoint polling to run during pause and dismiss the win overlay during ROUND_EVAL so endless mode remains responsive.
  • Add two live-driven Lua API regression tests to validate endless-mode responsiveness and won=true persistence through the endless transition cycle.
  • Remove an unrepresentable save/load fixture for the paused/overlay win state and shorten the CLI skill doc startup wait.

Reviewed changes

Copilot reviewed 4 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/lua/endpoints/play.lua Keeps the polling event running while paused and dismisses the win overlay before responding, so endless play doesn’t degrade.
tests/lua/endpoints/test_play.py Adds a live regression test asserting endless-mode play remains fast after a win.
tests/lua/endpoints/test_gamestate.py Adds a live regression test asserting won=true persists across the endless round-transition cycle.
tests/fixtures/fixtures.json Removes a fixture that cannot represent the paused/overlay state via save/load.
.agents/skills/balatrobot/SKILL.md Reduces the suggested startup sleep before calling the CLI health check.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +102 to +103
# Drive to the ante-8 boss and win it.
api(client, "menu")
blocking = false,
blockable = false,
created_on_pause = true,
pause_force = true,
S1M0N38 added 2 commits June 15, 2026 08:46
Expose G.SETTINGS.paused as GameState.paused so callers can detect a
session stuck behind a blocking overlay (win screen, pause menu, game
over). Previously the only externally observable symptom of such a
stuck state was wall-clock speed — a signal no clean assertion could
rely on. Added to the gamestate extractor, type definition, OpenRPC
schema, and API docs.
The endless-mode regression test asserted elapsed < 5s, a flaky
wall-clock check that measures speed rather than game state. Rewrite
it to assert paused=false on the endless play via the new GameState
field. Verified red against the pre-fix play.lua (paused=true failure)
and green with the fix restored.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants