Files
JokerComboAdvisor/CLAUDE.md
T
funman300 8783785283 Initial release: Joker Combo Advisor v1.0.0
Steamodded mod that teaches Balatro joker synergies: hover tooltips
explain why jokers combo (famous-pair blurbs + tag-based reasons),
combo discovery tracking with catalog grey-out and run recap, an
in-game synergy catalog with real card sprites, learning mode, and
a HUD shortcut.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 21:54:04 -07:00

125 lines
6.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project
"Joker Combo Advisor" — a Steamodded (SMODS) mod for Balatro that scores shop/pack
jokers against the player's current jokers, shows a "Combo Advisor" tooltip on hover,
and pulses strongly recommended cards. Pure Lua, no build step.
## Commands
Syntax check everything:
```sh
luac -p main.lua synergies.lua config.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):
```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
```
Expected score tiers: famous explicit pair + tags ≈ 6, hand-type/suit cluster = 4,
single tag match = 2, generic mult+xmult complement = 1, unrelated = 0.
## 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.
- 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.
- Crash/load logs: `~/.local/share/Balatro/Mods/lovely/log/` (newest file).
## Architecture
Three-layer split; `JCA` is the single global namespace:
- `synergies.lua` — data only. Returns `{ jokers = {...}, pairs = {...} }`. Each of
the 150 vanilla jokers gets `gives` (capabilities it adds) and `wants`
(capabilities that strengthen it) tag lists, converted to sets at the bottom of
the file. `PAIRS` lists famous explicit combos worth a flat +4.
- `main.lua` — engine + game integration:
- Scoring: `JCA.score(a, b)` = explicit pair bonus (+4) + 2 per give→want tag
match in each direction + 1 for the generic "+Mult/+Chips source with an
×Mult joker" complement. `JCA.partners_for(card)` aggregates over
`G.jokers.cards`; total ≥ `JCA.config.threshold` ⇒ recommended.
- UI hooks: wraps `Card:generate_UIBox_ability_table` to append a tooltip entry
to `aut.info` (format: array of rows-of-UIT-nodes plus a `.name` string —
must match what vanilla `info_tip_from_rows` expects), and wraps
`Card:update` for the ~2.5s pulse on recommended shop cards. Both hook bodies
are pcall-wrapped so a scoring bug can never crash a run — keep it that way.
- Config tab: registered on `SMODS.current_mod.config_tab`, guarded by
`if SMODS.current_mod` so standalone tests don't need full stubs.
- Synergy catalog: `SMODS.current_mod.extra_tabs` adds Combos/Engine/
Economy/Hands tabs that render real `Card` objects in `CardArea`s
(collection-style, so hover shows each joker's own tooltip). One theme
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`.
- 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
per-run in `G.GAME.jca_run_combos`. Undiscovered pairs render face-down
with a `???` caption in the Combos tab; blurb replaces it once fielded.
- Post-run recap: wraps `create_UIBox_game_over`/`create_UIBox_win` and
inserts a "Combos fielded this run" row (from `G.GAME.jca_run_combos`)
relative to vanilla button ids (`from_game_over`; `from_game_won` or
`win_cta`) via a parent-trail walk — anchor depths differ per screen.
- Learning mode (`config.learning_mode`): suppresses the pulse and the
"Strong pick!" line but never the explanations. No-synergy buy-area
tooltips show "Looking for:"/"Offers:" hints from `TAG_LABEL`.
`synergies.lua` also exports `theme_info` (one teaching sentence per
catalog tag — keep ≤ ~90 chars and cover every THEME_TABS tag).
- The real (Lovely-patched) game source is readable at
`~/.local/share/Balatro/Mods/lovely/dump/` — check it before assuming
what a vanilla function returns or expects.
- `config.lua` — default settings (`pulse`, `owned_tooltip`, `threshold`).
Steamodded loads and persists this table itself; read it only via
`SMODS.current_mod.config` (exposed as `JCA.config`).
## Conventions and gotchas
- **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.
- 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
the generic mult×xmult line. Every non-engine tag (anything besides
mult/chips/xmult) MUST have a `TAG_TEXT` entry; new `PAIRS` entries need a
blurb under ~34 chars so name + blurb fits one tooltip row.
- Vanilla center keys contain intentional misspellings/abbreviations — do not
"fix" them: `j_gluttenous_joker`, `j_selzer`, `j_ticket` (Golden Ticket),
`j_trousers` (Spare Trousers), `j_ring_master` (Showman), `j_caino` (Canio),
`j_delayed_grat`, `j_todo_list`.
- Unknown joker keys (from other mods) must keep scoring 0 — never index
`JCA.db[key]` without a nil guard.
- Display names go through `localize{type='name_text', set='Joker', ...}` with a
pcall + `center.name` fallback; anti-synergies are deliberately not modeled.
- The UI hooks are the only untested-in-game surface; if a tooltip regression is
reported, suspect the `aut.info` entry format first and check the lovely log.