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

Đang mở
#231 0 bình luận 0 reaction 0 người được giao Xem trên GitHub

Chưa có ai nhận issue này.

Đánh giá

Độ khó
3/5
Thời gian dự kiến
1-2 ngày
Mức phù hợp với người mới
72/100
Loại issue
Lỗi
Độ rõ ràng
Đặc tả rõ ràng
Mức độ hoạt động
Ít trao đổi
Công nghệ
lua, python
Lĩnh vực
api, backend

Hướng nghiên cứu

Bắt đầu trong src/lua/endpoints/cash_out.lua tại requires_state và execute, sau đó so sánh cơ chế chờ hiện có ở phía SHOP với luồng round-eval; bot.py cho thấy caller ROUND_EVAL bị ảnh hưởng. Xác minh bằng các seed đã được báo cáo hoặc các trace differential-replay, và xem issue là hoàn tất khi cash_out chờ các eval rows và giữ nguyên các rewards mong đợi.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

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.

Ngôn ngữ chính
Python
Star
80
Fork
23
Chỉ số merge pull request
Không có pull request nào được merge trong 30 ngày

Chuẩn bị môi trường

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của coder/balatrobot

Tất cả issue của coder/balatrobot

Issue tương tự

Thêm issue về Python

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.