Give Perkeo its consumable wants; make theme fragility a decision

Perkeo duplicates a RANDOM held consumable (card.lua:2788), so Spectral
and Planet supply feed it exactly like Tarot supply always did - and the
spectral_gen theme gains the third member it needed (the Seance +
Perkeo negative-spectral archetype, now discoverable three ways).

The other five two-member themes (diamonds, hearts, four_kind, gold_gen,
stone_gen) are vanilla reality: no third joker exists. They get
FRAGILE_OK verdicts in audit.lua, rendered in AUDIT.md, and the audit
now fails on a fragile theme with no verdict - or a stale verdict for a
theme that grew - the same discipline as the silent-joker list.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
funman300
2026-07-15 19:46:41 -07:00
parent 048ab40f85
commit 4a86a3e1ff
3 changed files with 62 additions and 4 deletions
+13 -3
View File
@@ -85,7 +85,7 @@ A tag with two is fragile: drop either joker and the theme dies.
| `joker_gen` | 1 | 3 | 4 | yes | | `joker_gen` | 1 | 3 | 4 | yes |
| `money` | 15 | 3 | 17 | yes | | `money` | 15 | 3 | 17 | yes |
| `pair` | 3 | 3 | 3 | yes | | `pair` | 3 | 3 | 3 | yes |
| `planet` | 3 | 3 | 3 | yes | | `planet` | 3 | 4 | 4 | yes |
| `probability` | 1 | 7 | 8 | yes | | `probability` | 1 | 7 | 8 | yes |
| `retrig_faces` | 1 | 10 | 11 | yes | | `retrig_faces` | 1 | 10 | 11 | yes |
| `retrig_held` | 1 | 4 | 5 | yes | | `retrig_held` | 1 | 4 | 5 | yes |
@@ -93,7 +93,7 @@ A tag with two is fragile: drop either joker and the theme dies.
| `retrig_scoring` | 4 | 17 | 21 | yes | | `retrig_scoring` | 4 | 17 | 21 | yes |
| `sell_value` | 2 | 2 | 4 | yes | | `sell_value` | 2 | 2 | 4 | yes |
| `spades` | 2 | 3 | 3 | yes | | `spades` | 2 | 3 | 3 | yes |
| `spectral_gen` | 2 | 2 | **2** | yes | | `spectral_gen` | 2 | 3 | 3 | yes |
| `stone_gen` | 1 | 1 | **2** | yes | | `stone_gen` | 1 | 1 | **2** | yes |
| `straight` | 7 | 6 | 8 | yes | | `straight` | 7 | 6 | 8 | yes |
| `suit_flex` | 1 | 12 | 13 | yes | | `suit_flex` | 1 | 12 | 13 | yes |
@@ -101,6 +101,16 @@ A tag with two is fragile: drop either joker and the theme dies.
| `three_kind` | 4 | 3 | 4 | yes | | `three_kind` | 4 | 3 | 4 | yes |
| `two_pair` | 3 | 3 | 3 | yes | | `two_pair` | 3 | 3 | 3 | yes |
### Fragile by design
Two-member themes reviewed and accepted: vanilla has no third joker to add.
- **`diamonds`** — Greedy Joker and Rough Gem are the only Diamond payoffs in vanilla.
- **`four_kind`** — DNA enables it, The Family pays it; no other vanilla joker cares.
- **`gold_gen`** — Midas Mask makes Gold, Golden Ticket cashes it - the whole economy.
- **`hearts`** — Lusty Joker and Bloodstone are the only Heart payoffs in vanilla.
- **`stone_gen`** — Marble adds Stones, Stone Joker counts them; Vampire eats them rather than wanting them.
## Load-bearing jokers ## Load-bearing jokers
The jokers that appear in the most scoring relationships. If one of these is The jokers that appear in the most scoring relationships. If one of these is
@@ -116,8 +126,8 @@ mis-tagged, the error is spread across the whole database.
| `j_to_the_moon` | 16 | | `j_to_the_moon` | 16 |
| `j_bootstraps` | 15 | | `j_bootstraps` | 15 |
| `j_bull` | 15 | | `j_bull` | 15 |
| `j_seance` | 13 |
| `j_four_fingers` | 12 | | `j_four_fingers` | 12 |
| `j_seance` | 12 |
| `j_sock_and_buskin` | 12 | | `j_sock_and_buskin` | 12 |
| `j_blueprint` | 11 | | `j_blueprint` | 11 |
+44
View File
@@ -244,6 +244,18 @@ end
w('') w('')
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Two-member themes, reviewed and accepted: vanilla simply has no third joker.
-- A fragile theme NOT in this table fails the audit -- either find a member or
-- record why none exists. A stale entry (theme grew past two) also fails, so
-- the table never drifts from the data.
local FRAGILE_OK = {
diamonds = 'Greedy Joker and Rough Gem are the only Diamond payoffs in vanilla.',
hearts = 'Lusty Joker and Bloodstone are the only Heart payoffs in vanilla.',
four_kind = 'DNA enables it, The Family pays it; no other vanilla joker cares.',
gold_gen = 'Midas Mask makes Gold, Golden Ticket cashes it - the whole economy.',
stone_gen = 'Marble adds Stones, Stone Joker counts them; Vampire eats them rather than wanting them.',
}
w('## Tag health') w('## Tag health')
w('') w('')
w('A tag with one member can never fire (it takes two jokers to make a synergy).') w('A tag with one member can never fire (it takes two jokers to make a synergy).')
@@ -251,15 +263,34 @@ w('A tag with two is fragile: drop either joker and the theme dies.')
w('') w('')
w('| tag | givers | wanters | jokers | theme? |') w('| tag | givers | wanters | jokers | theme? |')
w('|---|---|---|---|---|') w('|---|---|---|---|---|')
local fragile_unreviewed, fragile_stale = {}, {}
local fragile_counts = {}
for _, t in ipairs(sorted(members)) do for _, t in ipairs(sorted(members)) do
if not ENGINE[t] then if not ENGINE[t] then
local n = 0 local n = 0
for _ in pairs(members[t]) do n = n + 1 end for _ in pairs(members[t]) do n = n + 1 end
fragile_counts[t] = n
if data.theme_info[t] and n <= 2 and not FRAGILE_OK[t] then
fragile_unreviewed[#fragile_unreviewed + 1] = t
end
w('| `%s` | %d | %d | %s | %s |', t, givers[t] or 0, wanters[t] or 0, w('| `%s` | %d | %d | %s | %s |', t, givers[t] or 0, wanters[t] or 0,
n <= 2 and ('**' .. n .. '**') or tostring(n), n <= 2 and ('**' .. n .. '**') or tostring(n),
data.theme_info[t] and 'yes' or '-') data.theme_info[t] and 'yes' or '-')
end end
end end
for t in pairs(FRAGILE_OK) do
if (fragile_counts[t] or 0) > 2 then fragile_stale[#fragile_stale + 1] = t end
end
w('')
w('### Fragile by design')
w('')
w('Two-member themes reviewed and accepted: vanilla has no third joker to add.')
w('')
for _, t in ipairs(sorted(FRAGILE_OK)) do
if (fragile_counts[t] or 0) <= 2 then
w('- **`%s`** — %s', t, FRAGILE_OK[t])
end
end
w('') w('')
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
@@ -312,3 +343,16 @@ if #buckets.unreviewed > 0 then
:format(#buckets.unreviewed, n_jokers, table.concat(buckets.unreviewed, '\n '))) :format(#buckets.unreviewed, n_jokers, table.concat(buckets.unreviewed, '\n ')))
os.exit(1) os.exit(1)
end end
-- A theme hanging on two jokers is a discovery achievement one rebalance away
-- from impossible; that has to be a decision too.
if #fragile_unreviewed > 0 then
io.stderr:write(('\n%d fragile theme(s) (two members) have no FRAGILE_OK verdict:\n %s\n')
:format(#fragile_unreviewed, table.concat(fragile_unreviewed, '\n ')))
os.exit(1)
end
if #fragile_stale > 0 then
io.stderr:write(('\nFRAGILE_OK verdict(s) for themes that are no longer fragile -- '
.. 'delete them:\n %s\n'):format(table.concat(fragile_stale, '\n ')))
os.exit(1)
end
+5 -1
View File
@@ -201,7 +201,11 @@ j('j_caino', {'xmult','copy_target'}, {'faces'})
j('j_triboulet', {'xmult','copy_target'}, {'retrig_scoring','retrig_faces'}) j('j_triboulet', {'xmult','copy_target'}, {'retrig_scoring','retrig_faces'})
j('j_yorick', {'xmult','copy_target'}, {'discards_up'}) j('j_yorick', {'xmult','copy_target'}, {'discards_up'})
j('j_chicot', {}) j('j_chicot', {})
j('j_perkeo', {'tarot_gen'}, {'tarot_gen'}) -- Perkeo duplicates a RANDOM held consumable (pseudorandom_element over
-- G.consumeables.cards, card.lua:2788), so every consumable economy feeds it:
-- Tarot and Planet supply give it targets, and duplicating Spectrals is the
-- famous negative-spectral archetype with Seance.
j('j_perkeo', {'tarot_gen'}, {'tarot_gen','spectral_gen','planet'})
-- Famous explicit combos (flat bonus on top of tag matching), each with a -- Famous explicit combos (flat bonus on top of tag matching), each with a
-- short "why it works" line shown in the tooltip. Keep blurbs under ~34 -- short "why it works" line shown in the tooltip. Keep blurbs under ~34