fix!(lua.endpoints): cash_out fires before round-eval rows commit — rewards under-collected, sometimes $0

オープン
#231 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

評価

難易度
3/5
見積もり時間
1〜2日
初心者へのやさしさ
72/100
issue の種類
バグ
明瞭さ
明確に書かれている
活発さ
静か
技術スタック
lua, python
領域
api, backend

調査の方向性

src/lua/endpoints/cash_out.lua の requires_state と execute から始め、既存の SHOP 側の待機処理を round-eval フローと比較します。bot.py に影響を受ける ROUND_EVAL caller が示されています。報告された seed または differential-replay のトレースで検証し、cash_out が eval rows を待機し、期待される rewards を維持すれば issue は完了とみなします。

索引モデルが issue の本文から書いたものです。

説明

Description

Calling cash_out as soon as the game reaches ROUND_EVAL collects less money
than the game owes — usually short exactly one blind reward ($3/$4/$5), and
$0 in the worst case. A human cannot reproduce this: the game only spawns the
Cash Out button after the eval rows have finished paying out, but the endpoint
bypasses that gate.

Root Cause

src/lua/endpoints/cash_out.lua:

  • requires_state = { G.STATES.ROUND_EVAL } (line 22) passes the moment the
    state flips, while the dollar rows (blind reward, interest, per-joker payouts)
    are still being queued and committed by the event manager.
  • execute then calls G.FUNCS.cash_out({ config = {} }) immediately (line 28).

The endpoint carefully waits for the SHOP transition on the response side, but
does not wait for eval completion on the entry side.

Evidence

Found with a differential-replay harness that drives a vanilla-faithful simulator
and balatrobot with identical action traces and deep-diffs the full gamestate
after every action (~1,200 organic games).

  • Signature: after cash-out, live money == expected money minus exactly one
    blind reward. Confirmed 4× before we added a workaround.
  • Partial commits happen: one case committed interest but dropped the $5 boss
    reward (seed LSOYQISY).
  • Zero-payout extreme: when hands_left == 0 and there is no interest (the
    payout is only the reward row), cash-out commits $0. Reproduced
    deterministically across bosses (Psychic, Pillar) and across game sessions
    (seeds LSAZ99YB, LS88BC4Z).
  • Workaround that eliminates it completely: a flat 12s wait before calling
    cash_out (normal speed, --no-audio).

The under-collection is irreversible once collected — the money is simply gone
for the rest of the run.

Impact

Any bot that auto-cashes on seeing ROUND_EVAL is systematically poorer than
the game intends. balatrollm does exactly this (bot.py:
case "ROUND_EVAL": ... call("cash_out")), so BalatroBench run economies — and
any result that turns on being a few dollars short in the shop — are affected.

Suggested Fix

Gate execution the way the game gates the button: add a condition event that
waits until the round-eval UI has finished creating its rows (e.g. the Cash Out
button exists), and only then call G.FUNCS.cash_out. This mirrors the
endpoint's existing SHOP-side wait.

We can provide full repro bundles (action trace + both gamestates + field diff
at the divergence step) for any of the seeds above.

主要言語
Python
スター
79
フォーク
23
PR マージ指標
30日以内にマージされた PR はありません

環境構築

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

coder/balatrobot のほかの issue

coder/balatrobot の issue をすべて見る

似ている issue

Python の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。