Add a test suite, and fix the three dead-data bugs it found
There was no test file -- CLAUDE.md described the harness in prose and referred to an "orphan-tag check" that did not exist. test.lua now stubs the game globals and exercises the real engine, database and catalog tab builders. It found three bugs on its first run: * four_kind was the only theme in the catalog that could never be discovered. Theme discovery needs a giver and a wanter on two DIFFERENT fielded jokers, and The Family was its sole member -- so the Hands bar and the overall Progress bar could never reach 100%. DNA is the real enabler (copying a card is how you stack duplicate ranks, the only vanilla route to a reliable Four of a Kind), so it now gives the matching-hand tags as a pure enabler. * hand_level was given by Space Joker and Burnt Joker and wanted by nobody, so it could never contribute to a score and its "want" tooltip text was unreachable. Nothing in vanilla rewards hand levels; the tag is dropped rather than faked. * Invisible Joker had no tags at all, so it scored 0 against the entire game. Selling it duplicates one of your Jokers -- the same "stronger target, better payoff" logic Blueprint and Brainstorm already model -- so it now wants copy_target. The suite guards both directions of the tag contract (no orphan wants, no dead gives), theme reachability, blurb widths, the inert-joker list, and the card materialize fix from the previous commit. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -10,38 +10,42 @@ and pulses strongly recommended cards. Pure Lua, no build step.
|
||||
|
||||
## Commands
|
||||
|
||||
Syntax check everything:
|
||||
Syntax check, then run the test suite — **always both, after every edit**:
|
||||
|
||||
```sh
|
||||
luac -p main.lua synergies.lua config.lua
|
||||
luac -p main.lua synergies.lua config.lua test.lua
|
||||
lua test.lua
|
||||
```
|
||||
|
||||
There is no test framework. Test the engine standalone by stubbing the game globals
|
||||
before loading `main.lua` (the game itself cannot be launched headlessly here):
|
||||
`test.lua` stubs the handful of game globals `main.lua` touches (the game cannot be
|
||||
launched headlessly here) and then exercises the real engine, the real database and
|
||||
the real catalog tab builders. It is the only safety net; there is no other harness.
|
||||
It enforces, among other things:
|
||||
|
||||
```sh
|
||||
lua - <<'EOF'
|
||||
SMODS = { load_file = function(f) return loadfile(f) end,
|
||||
current_mod = { config = dofile('config.lua') } }
|
||||
Card = { generate_UIBox_ability_table = function() end, update = function() end }
|
||||
G = { UIT = {ROOT='ROOT', R='R', T='T'},
|
||||
C = { UI = {TEXT_DARK='dark'}, GREEN='green', CLEAR='clear' },
|
||||
STAGES = {RUN=1}, FUNCS = {} }
|
||||
dofile('main.lua')
|
||||
print(JCA.score('j_baron', 'j_mime')) -- pairwise score
|
||||
-- simulate owned jokers via G.jokers.cards; each card needs
|
||||
-- { config = { center = { key=..., name=..., set='Joker' } } }
|
||||
EOF
|
||||
```
|
||||
- Score tiers: famous explicit pair + tags = 6, hand-type/suit cluster = 4, single
|
||||
tag match = 2, generic mult+xmult complement = +1 on top, unrelated = 0.
|
||||
- **No orphan wants** — a `wants` tag nothing gives is a payoff with no enabler.
|
||||
- **No dead gives** — a non-engine tag given but never wanted can never change a
|
||||
score, so it is invisible dead weight. (This caught `hand_level`, given by Space
|
||||
and Burnt Joker and wanted by nobody.)
|
||||
- **Every theme is discoverable** — theme discovery needs a giver and a wanter on two
|
||||
*different* fielded jokers, so a theme with fewer than two members is an
|
||||
achievement no player can ever earn and pins the Progress tab under 100% forever.
|
||||
(This caught `four_kind`, whose only member was The Family.)
|
||||
- `theme_info` and `THEME_TABS` cover exactly the same tag set.
|
||||
- Pair/clash/caution keys all exist; every blurb fits its ~34-char row.
|
||||
- Every card the catalog pages emplace is materialized (see the UI notes below).
|
||||
- Jokers with no tags at all are listed in `EXPECTED_INERT` on purpose — a joker
|
||||
landing there by accident scores 0 against the entire game and says nothing.
|
||||
|
||||
Expected score tiers: famous explicit pair + tags ≈ 6, hand-type/suit cluster = 4,
|
||||
single tag match = 2, generic mult+xmult complement = 1, unrelated = 0.
|
||||
To poke at the engine by hand, copy the stub block from the top of `test.lua`.
|
||||
|
||||
## In-game verification (this machine)
|
||||
|
||||
- The mod is **symlinked** into the live Mods folder:
|
||||
`~/.local/share/Balatro/Mods/JokerComboAdvisor -> ~/Documents/Balatro Mod`.
|
||||
Edits here go live on next game launch — do not copy files anywhere.
|
||||
`~/.local/share/Balatro/Mods/JokerComboAdvisor -> ~/Documents/JokerComboAdvisor`
|
||||
(this repo). Edits here go live on next game launch — do not copy files anywhere.
|
||||
Lovely reads that folder as `C:\users\steamuser\AppData\Roaming\Balatro\Mods`.
|
||||
- Game: Steam/Proton at `/mnt/games/Steam/steamapps/common/Balatro` (note: second
|
||||
Steam library, not the default paths). Lovely injector + Steamodded 1.0.0-beta
|
||||
are already installed; mods are managed via Balatro Mod Manager.
|
||||
@@ -96,10 +100,16 @@ Three-layer split; `JCA` is the single global namespace:
|
||||
per page; pagers swap the `tab_contents` UIBox from an option-cycle
|
||||
callback — the same pattern as SMODS's achievements tab. Because pages
|
||||
contain live Card/CardArea objects, tab definitions must be rebuilt on
|
||||
every call — never cache the node trees. The same tabs open in-run via
|
||||
a "Combos" HUD button (wrapped `create_UIBox_HUD`, injected into the
|
||||
`button_area` node under Run Info/Options); the pagers work there
|
||||
because vanilla `create_tabs` also names its body `tab_contents`.
|
||||
every call — never cache the node trees. Cards must be `start_materialize`d
|
||||
as they are emplaced (`card_row_node`), exactly as every vanilla collection
|
||||
page does — that call *is* the appear animation, and without it cards pop
|
||||
in with no animation on tab open and page switch. Only the first card of a
|
||||
page build plays the sound (vanilla's `i>1 or j>1` silence flag); tab defs
|
||||
call `begin_page()` so the counter spans the page, not one row. The same
|
||||
tabs open in-run via a "Combos" HUD button (wrapped `create_UIBox_HUD`,
|
||||
injected into the `button_area` node under Run Info/Options); the pagers
|
||||
work there because vanilla `create_tabs` also names its body
|
||||
`tab_contents`.
|
||||
- Discovery: `CardArea:emplace` hook calls `JCA.check_discoveries` when a
|
||||
joker lands in `G.jokers`; famous pairs fielded together persist in
|
||||
`config.discovered` (saved immediately via `SMODS.save_mod_config`) and
|
||||
@@ -132,9 +142,11 @@ Three-layer split; `JCA` is the single global namespace:
|
||||
- **Cluster tags** (hand types `pair`/`straight`/`flush`/…, suits, `planet`,
|
||||
`spectral_gen`): put the same tag in *both* `gives` and `wants` of every member
|
||||
so members mutually reinforce (2+2 = 4). Pure enablers (e.g. Four Fingers,
|
||||
Smeared, Pareidolia) only `give`; pure payoffs only `want`.
|
||||
- Every `wants` tag must be given by at least one joker — the standalone test's
|
||||
orphan-tag check enforces this idea; re-run it after database edits.
|
||||
Smeared, Pareidolia, DNA) only `give`; pure payoffs only `want`.
|
||||
- A tag needs **both sides to exist, on at least two different jokers**: something
|
||||
must give it, something must want it, or it is dead data that can never change a
|
||||
score — and if it is also a theme, an achievement nobody can earn. `lua test.lua`
|
||||
enforces both directions; run it after every `synergies.lua` edit.
|
||||
- Tooltips teach: `JCA.explain(hovered, partner)` picks one reason per partner
|
||||
— famous-pair blurb (third element of each `PAIRS` entry), else the first
|
||||
`TAG_ORDER` match phrased from `TAG_TEXT` (give/want/both variants), else
|
||||
|
||||
Reference in New Issue
Block a user