Proposing arcana
The catalog is at 109 arcana as of year-1. New ideas are welcome, but most arcanum proposals are best discussed before code lands — duplicate behavior, balance overlap, and tone drift are common pitfalls.
Open an issue first
Section titled “Open an issue first”Before writing code, open an issue using the arcanum-proposal
label. Include:
- Name — Aziraphale-Crowley register. “The bus ticket” not “Lucky Charm.”
- Tier — common (~30 chip), uncommon (~60), rare (~100), mythic (~200).
- Trigger —
onWin,onSurrender,onArcanum, etc. Reuse existing triggers; new triggers require engine wiring. - What it does — one sentence. The math should fit on one line.
- Why this charm fills a gap — what does the catalog not already do?
- Composition — does it interact with Long-Game / Heavy Marks / drawback negotiation / build identities?
The single-line math rule
Section titled “The single-line math rule”Every shipped arcanum has a one-line behavior. The cascade is loud because there are 109 of them — each charm doing too much would be illegible.
| Good | Bad |
|---|---|
| ”+5 chips per 7 in a winning hand" | "+5 chips per 7 in a winning hand, except on Tuesdays, with a multiplier that increases by…" |
| "×3 mult on first 3 pair-wins per campaign" | "Variable mult that depends on three persistent flags and the player’s deck size” |
If you can’t write the behavior in one line, the charm is two charms or one too-complex charm.
Tier expectations
Section titled “Tier expectations”| Tier | Felt impact | Fire frequency | Example |
|---|---|---|---|
| Common | +5-15% lift | Often (most hands) | bus_ticket, paper_flower |
| Uncommon | +15-30% lift | Many hands | oilcloth_apron |
| Rare | +30-60% lift | Some hands | the_marked_card |
| Mythic | +60-100%+ lift | Rare or capped | black_envelope, pair_hunter |
The decision-impact harness (test/sim/decision_impact_test.dart)
asserts every non-vow charm produces ≥15% score lift in Monte Carlo.
A new charm needs to pass this.
Naming + tone
Section titled “Naming + tone”| Yes | No |
|---|---|
| The Pawn Ticket | LuckBoost+ |
| The Hollow Mirror | DoubleDamage |
| The Bone Pact | Scary Skeleton |
| Stationmaster’s Whistle | Whistle of +5 Multiplier |
Names should evoke the dealer’s pocket or his table — small objects, old, slightly tarnished, with personal weight. The flavor line on fire is in the same register:
flavor: 'a tooth, returned to its socket.'The implementation checklist
Section titled “The implementation checklist”If your proposal is accepted, the implementation looks like:
- Add a class in
lib/sim/arcana/arcana.dart(alphabetical-ish inside the relevant phase block). - Register in the
arcanumRegistrymap at the bottom of that file. - Unlock in
MetaProgression.empty.arcanaUnlockedso it appears in the shop. - Icon glyph in
lib/ui/arcanum_icon.dart. - Asset at
assets/arcana/<id>.png. Either generate viatools/generate_charm_icons.py(Gemini) or commit manually. - Tests in
test/sim/<group>_test.dartcovering:- fire conditions (when does it fire, when doesn’t it)
- math (does the chip/mult delta match the design)
- rate-limits if applicable
- decision-impact (the Monte Carlo will tell you if the lift is in the right range)
- Bump catalog count in
test/sim/arcana_test.dart:catalog has N arcana. - Bump unlocked count in
test/data/meta_progression_test.dart.
Drawbacks have an extra step
Section titled “Drawbacks have an extra step”Drawback arcana need:
bool get isDrawback => true;String get drawbackCost => '...';(the constraint)String get drawbackReturn => '...';(the benefit)- The
expectedDrawbackIdsset intest/sim/drawback_marking_test.dartneeds your new id.
The drawback-negotiation prompt picks up the cost/return text automatically.
Engine wiring
Section titled “Engine wiring”Most arcana fit existing triggers (onWin, onSurrender,
onResolve, etc.). If yours needs a new trigger or a new
runStateFlag, the proposal review will determine whether the engine
wiring is in scope.
The big “deferred” engine bits (vow_of_plunger’s any_double_allowed, strangers_half’s any_split_allowed) ship as flag-setters waiting for NightRunner integration. New drawback charms can do the same.
The catalog feel goal
Section titled “The catalog feel goal”~92 charms × 8 chain triggers × 11 archetype builds × 15 drawback flavors = 121,440 tuple states (≥80% inert; ≥6,000 felt-distinct combos)
A new charm should add to this combinatorial space, not duplicate existing tuples. The proposal review checks for tuple overlap with the current catalog.