Conversation
Previously, used_vouchers extracted descriptions from static voucher_data.description which was unreliable. Now uses get_voucher_effect() that fetches effect text via the game's localize() function with proper loc_vars for each voucher type. Also adds strip_color_codes() helper and comprehensive parametrized tests covering all 32 voucher types. Closes #154.
Improve error messages across 6 endpoint files by adding actionable guidance to help bots self-heal from failed tool calls. Changes: - buy.lua: Add endpoint suggestions for empty shop/slot errors - use.lua: Add card parameter guidance for consumable errors - discard.lua/play.lua: Add card limit suggestions - pack.lua: Add pack buying and target selection hints - skip.lua: Add boss blind selection suggestion - Update test_buy.py to match new error messages Closes #148.
Closes #143.
There was a problem hiding this comment.
Pull request overview
This PR introduces version 2 of the balatrobot API with breaking changes (!). The main focus is on restructuring how tags are represented in the game state and improving error messages across all endpoints to be more actionable and helpful.
Changes:
- Restructured tag representation from flat
tag_name/tag_effectfields to nestedtagobjects withkey,name, andeffectfields - Added
tagsarray to gamestate for tracking accumulated player-owned tags - Enhanced error messages across all endpoints with actionable guidance (e.g., suggesting
reroll,sell, etc.) - Added support for selling jokers when Buffoon packs are open (SMODS_BOOSTER_OPENED state)
- Implemented voucher effect extraction using game's localize function
- Added comprehensive Tag enum definitions and test coverage
Reviewed changes
Copilot reviewed 18 out of 18 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/lua/utils/types.lua | Updated Blind type to use nested Tag object instead of flat tag_name/tag_effect fields; added Tag class definition |
| src/lua/utils/openrpc.json | Updated OpenRPC schema to reflect Tag object structure and enhanced sell endpoint description |
| src/lua/utils/gamestate.lua | Implemented voucher effect extraction, tag ownership tracking, and updated blind tag structure |
| src/lua/utils/enums.lua | Added comprehensive Tag.Key enum definitions for all Balatro tag types |
| src/lua/endpoints/sell.lua | Added support for SMODS_BOOSTER_OPENED state with Buffoon pack validation |
| src/lua/endpoints/skip.lua | Enhanced error message with actionable guidance |
| src/lua/endpoints/buy.lua | Enhanced error messages with actionable guidance |
| src/lua/endpoints/add.lua | Updated to support pack additions and refactored voucher handling to use dedicated SMODS function |
| src/lua/endpoints/use.lua | Enhanced error messages with actionable guidance |
| src/lua/endpoints/play.lua | Enhanced error message with actionable guidance |
| src/lua/endpoints/discard.lua | Enhanced error messages with actionable guidance |
| src/lua/endpoints/pack.lua | Enhanced error messages with actionable guidance |
| tests/lua/endpoints/test_skip.py | Added tests for tag accumulation after skipping blinds |
| tests/lua/endpoints/test_pack.py | Added tests for selling jokers during Buffoon pack selection |
| tests/lua/endpoints/test_gamestate.py | Added comprehensive test coverage for voucher effects and tag structure |
| tests/lua/endpoints/test_buy.py | Updated error message expectations |
| tests/lua/endpoints/test_add.py | Updated error message expectations |
| docs/api.md | Updated documentation to reflect new Tag structure and enhanced endpoint descriptions |
Comments suppressed due to low confidence (4)
src/lua/endpoints/add.lua:409
- The comment says "For jokers and consumables" but this else branch will also execute for vouchers and packs, creating unnecessary params that won't be used. Consider adding an explicit check:
elseif card_type == "joker" or card_type == "consumable" thento match the comment and avoid creating unused params for vouchers and packs.
else
-- For jokers and consumables - just pass the key
params = {
key = args.key,
skip_materialize = true,
stickers = {},
force_stickers = true,
}
-- Add edition if provided
if edition_value then
params.edition = edition_value
end
-- Add eternal if provided (jokers only - validation already done)
if args.eternal then
params.stickers[#params.stickers + 1] = "eternal"
end
-- Add perishable if provided (jokers only - validation already done)
if args.perishable then
params.stickers[#params.stickers + 1] = "perishable"
end
-- Add rental if provided (jokers only - validation already done)
if args.rental then
params.stickers[#params.stickers + 1] = "rental"
end
end
tests/lua/endpoints/test_skip.py:43
- Grammar issue in comment: "because it used immediately" should be "because it is used immediately"
assert "tag_investment" not in gamestate["tags"] # because it used immediately
tests/lua/endpoints/test_skip.py:53
- Grammar issue in comment: "because it used immediately" should be "because it is used immediately"
assert "tag_investment" not in gamestate["tags"] # because it used immediately
src/lua/utils/types.lua:58
- Typo: "bilnd" should be "blind"
---@field status Blind.Status Status of the bilnd
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
tests/lua/endpoints/test_skip.py
Outdated
| assert gamestate["tags"][0]["key"] == "tag_polychrome" | ||
| assert "tag_investment" not in gamestate["tags"] # because it used immediately |
There was a problem hiding this comment.
This test file has a bug that will cause test_skip_big_boss to fail. The test at line 54-58 (not shown in diff) expects the error message "Cannot skip Boss blind" but skip.lua line 39 now returns "Cannot skip Boss blind. Use select to select and play the boss blind." The expected error message in the test needs to be updated to match the new implementation.
tests/lua/endpoints/test_skip.py
Outdated
| assert gamestate["blinds"]["big"]["status"] == "SKIPPED" | ||
| assert gamestate["blinds"]["boss"]["status"] == "SELECT" | ||
| assert gamestate["tags"][0]["key"] == "tag_polychrome" | ||
| assert "tag_investment" not in gamestate["tags"] # because it used immediately |
There was a problem hiding this comment.
This assertion is checking if the string "tag_investment" is in a list of tag objects. Since gamestate["tags"] is a list of objects (each with "key", "name", "effect" fields), the in operator will never find a string match. This should likely be checking if any tag in the list has key == "tag_investment", such as: assert not any(tag["key"] == "tag_investment" for tag in gamestate["tags"])
tests/lua/endpoints/test_skip.py
Outdated
| assert gamestate["state"] == "BLIND_SELECT" | ||
| assert gamestate["blinds"]["boss"]["status"] == "SELECT" | ||
| assert gamestate["tags"][0]["key"] == "tag_polychrome" | ||
| assert "tag_investment" not in gamestate["tags"] # because it used immediately |
There was a problem hiding this comment.
This assertion is checking if the string "tag_investment" is in a list of tag objects. Since gamestate["tags"] is a list of objects (each with "key", "name", "effect" fields), the in operator will never find a string match. This should likely be checking if any tag in the list has key == "tag_investment", such as: assert not any(tag["key"] == "tag_investment" for tag in gamestate["tags"])
Uh oh!
There was an error while loading. Please reload this page.