coder / coder/balatrobot

fix!(lua.endpoints): allow buy/pack of Negative-edition cards when slots are full

Open
#208 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 `buy` and `pack` endpoints reject valid purchases of **Negative-edition** jokers/consumables when inventory is full. The game allows them because a Negative card brings its own slot. Verified against `vendors/lovely/dump/` and confirmed empirically.

## Bug

Both endpoints use a plain `count >= limit` check:
- `src/lua/endpoints/buy.lua:134` (jokers), `buy.lua:149` (consumables)
- `src/lua/endpoints/pack.lua:142` (jokers)

The game uses a more permissive formula that accounts for the card's own slot capacity. Live source:
- `check_for_buy_space` (`functions/button_callbacks.lua:2427`): `#G.jokers.cards < G.jokers.config.card_limit + card.ability.card_limit - card.ability.extra_slots_used`
- `can_select_card` (`functions/button_callbacks.lua:2114`, SMODS-patched in `smods/lovely/booster.toml:333` "Support negative-ish on Jokers"): same `card_limit` math

Empirical: added a Negative joker to a full inventory (5/5) → count went 6/6 (limit +1). The Negative edition grants `card.ability.card_limit = 1`, so the game allows the purchase/selection; balatrobot blocks it with "joker slots are full".

## Fix

Replicate the game formula. For jokers:
```lua
-- instead of: if count >= limit
if #G.jokers.cards >= G.jokers.config.card_limit + (card.ability.card_limit or 0) - (card.ability.extra_slots_used or 0)
```
Same for consumables (`G.consumeables`). Read the shop/pack card's `ability.card_limit`/`extra_slots_used` from the live card object (`G.shop_jokers.cards[pos]` / `G.pack_cards.cards[pos]`), not the formatted gamestate.

## PoC test

```python
def test_buy_negative_joker_when_full(self, client):
"""Negative joker can be bought even when joker slots are full."""
gamestate = load_fixture(client, "buy",
"state-SHOP--jokers.count-5--shop.cards[0].key-j_joker--shop.cards[0].edition-e_negative")
assert gamestate["jokers"]["count"] == 5
assert gamestate["jokers"]["limit"] == 5
assert gamestate["shop"]["cards"][0]["modifier"]["edition"] == "e_negative"
# Game allows: negative brings +1 slot. balatrobot currently blocks.
response = api(client, "buy", {"card": 0})
after = assert_gamestate_response(response)
assert after["jokers"]["count"] == 6
assert after["jokers"]["limit"] == 6
```

**Fixture note:** placing a Negative joker in the shop requires either a seed hunt or extending `add` to place jokers/consumables into `G.shop_jokers`/`G.consumeables` (it currently only adds packs/vouchers to shop areas).

Sibling of #202–#206.

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.