feat!(lua.utils): add revealed field to card gamestate for consumable reveal glitch
- Dominant language
- Python
- Stars
- 72
- Forks
- 18
- PR merge metrics
- No merged PRs in 30d
Description
Add a transient `revealed` boolean to `Card.State`, emitted only by the `use` (and defensively `buy_and_use`) endpoint responses. It marks a hidden card whose identity was visually exposed to an observer during consumable application — distinct from `hidden`, which only reflects the card's current on-screen facing.
## Background: the consumable reveal glitch
Boss blinds that keep cards face-down (The Wheel, The House, The Mark, The Fish — `Blind:stay_flipped`, `blind.lua:605`) can be briefly defeated by applying a conversion consumable. `Card:use_consumeable` runs a flip→modify→flip animation (`card.lua:1114`/`1154` for highlighted cards; `card.lua:1235`/`1266` for Sigil/Ouija on the whole hand). `Card:flip()` (`card.lua:4119`) is a pure toggle with no awareness of `Blind:stay_flipped`, so a face-down card is toggled face-**up** for the duration of the modification, then toggled back. The card ends face-down again, but a human observer has seen its rank and suit.
Affected consumables are exactly those whose center config sets `mod_conv`/`suit_conv` (`game.lua:534–554`) — Magician, Empress, Hierophant, Lovers, Chariot, Justice, Strength, Death, Devil, Tower, Star, Moon, Sun, World — plus **Sigil** and **Ouija**, which flip the entire `G.hand`. `stay_flipped` is never consulted inside `use_consumeable`.
## Why we need a new field
`hidden` is documented and implemented as "card is face down (`facing == 'back'`)" (`types.lua:99`, `gamestate.lua:245`). It describes the **current on-screen state**, not what an observer has learned. After the glitch fires the card is face-down on screen again, so `hidden` is (correctly) still `true` — yet a human would now know its identity.
The API is already omniscient: `extract_card_value` (`gamestate.lua:210`) reads `card.config.card.suit`/`.value` with no `facing` check, so the true rank/suit of hidden cards is **always returned**. `hidden` is therefore the *only* signal distinguishing "unknown to the observer" from "known." Overloading it to also mean "was exposed" would conflate two semantics and silently change meaning for every downstream consumer — notably the fair-play deduction path introduced by the `sort` endpoint (#213), which deliberately lets a bot triangulate hidden cards the way a human would.
A separate `revealed` field keeps the contract clean:
- `hidden` = current on-screen facing (unchanged, everywhere).
- `revealed` = transient "identity was exposed during the last op" (only `use`/`buy_and_use`).
This lets a fair-play bot (or any consumer modeling human perception) know that a card it nominally cannot see was in fact visible for a moment — without breaking the omniscient default or the meaning of `hidden`.
## Proposal
- `src/lua/utils/types.lua` + `src/lua/utils/openrpc.json`: add `revealed boolean?` to `Card.State`.
- `src/lua/utils/gamestate.lua` `extract_card_state`: emit `state.revealed = true` from a transient stamp on the card object.
- `src/lua/endpoints/use.lua`: before `G.FUNCS.use_card`, snapshot the affected hidden cards (`facing == 'back'`) — the targeted set (`args.cards`) for `mod_conv`/`suit_conv` consumables, the whole hand for Sigil/Ouija. The snapshot must precede the call, since `use_consumeable` clears `G.hand.highlighted` (`card.lua:1156`). On completion, stamp them, build the gamestate, clear the stamp. Card-object identity is stable across the op (Death/Strength/change_suit/Sigil/Ouija all mutate in place — no destruction, no redraw).
- `src/lua/endpoints/buy_and_use.lua`: same logic defensively. In practice a no-op — `Blind:disable` (`blind.lua:355`) flips every hidden card face-up when the boss is defeated, before the run ever reaches `SHOP`.
- `docs/api.md`: document the field and the glitch it models.
## Scope
`Card.State` only; transient, scoped to `use`/`buy_and_use` responses. No change to `hidden` semantics or to the default omniscient gamestate returned by `gamestate` / `play` / etc.
Contributor guide
Assessment
This issue has not been assessed yet.