How to design a new reward card

This guide walks through adding a new post-mission reward card — the cards revealed on the post-run reward screen and bundled into the staged chests of the level reward track.

Before you start

Reward cards are the presentation layer over a numeric reward payload. Each RewardCardDef has an id, a type (the dispatch category), a rarity (which drives reveal-animation intensity), and cosmetic fields (name, description, icon, color). A ResolvedReward pairs a card with a numeric amount — that pair is what actually shows up on the reward screen.

The post-run pool always awards three slots: credits (always), one ship pull (only if survived), and star XP (always, with a 10× multiplier on first win of the day). The pool never awards Warp Crystals (premium-only) or Pull Tickets. Most new card additions are new value ranges or rarity tiers under an existing type, not new types — adding a brand-new type requires a new branch in the reward-application engine code.

Step 1 — Identity

Decide the card’s identity fields in REWARD_CARDS.

FieldDescription
idUnique snake_case string. Pattern is <type>_<variant> (e.g. credits_small, pull_uncommon, star_xp_daily). Must be unique across REWARD_CARDS because REWARD_CARD_MAP is keyed by id.
nameDisplay name on the card face. Variants of the same type may share names (e.g. both pull_common and pull_uncommon are named “Ship Signal”).
typeOne of credits, ship_pull, star_xp, level_xp, mod_drop. Determines the icon family and the destination (currency pouch vs collection vs progress track).
rarityOne of common, uncommon, rare, epic, legendary. Drives reveal-animation intensity (god rays, particles).
descriptionOne-line flavor below the name.
iconEmoji or icon identifier for card rendering.
colorHex string for the card glow/accent.

Existing entries and their conventions:

idtyperaritynameiconcolor
credits_smallcreditscommonCredits🔩#88aacc
pull_commonship_pullcommonShip Signal🚀#44aa44
pull_uncommonship_pulluncommonShip Signal🚀#4488ff
pull_rareship_pullrareRare Ship Signal🚀#0070dd
star_xp_basestar_xpcommonStar Progress#ffdd44
star_xp_dailystar_xprareFirst Win Bonus🌟#ffaa00
level_xplevel_xpcommonLevel XP📖#50a0ff
mod_drop_commonmod_dropcommonMod Drop🧩#9ca3af

Step 2 — Roll table

Decide the numeric range for the card’s amount. Numeric ranges live separately from the card definition — CREDITS_RANGES for credits, the STAR_XP_* constants for star XP, and per-tier overrides for ship-pull rarity in MIN_PULL_BY_TIER.

Performance tier comes from MissionResult and is one of bronze, silver, gold, diamond. New value ranges should follow the existing tier curve.

Credits per-tier roll (rolled uniformly between min and max, then rounded):

TierMinMax
bronze515
silver1230
gold2555
diamond45100

Star XP rates:

ConstantValue
STAR_XP_BASE3
STAR_XP_DAILY_MULT10
First-win-of-day star XP30

Minimum ship-pull rarity per tier:

TierMin rarityResolved card id
bronzecommonpull_common
silvercommonpull_common
golduncommonpull_uncommon
diamonduncommonpull_uncommon

If your card’s type already has a roll branch in rollPostRunRewards, your work is to extend the range tables. If it doesn’t, see Custom-element rule below.

Step 3 — Register

Two registration steps.

WhereWhat
REWARD_CARDS in reward-cards.tsAppend the new RewardCardDef entry. The barrel REWARD_CARD_MAP is rebuilt on module load.
LEVEL_REWARD_TRACK in reward-cards.tsOptional. If the card belongs in one of the 5 staged chests, add { cardId, amount } to the appropriate threshold entry.

Existing level-reward-track chests:

Threshold %Rewards
20credits_small ×10, star_xp_base ×5
40credits_small ×25, pull_common ×1
60credits_small ×40, pull_uncommon ×1
80credits_small ×60, pull_uncommon ×1
100credits_small ×150, pull_rare ×1

Points earned toward the level track per arcade run (track caps at 100 total points):

TierPoints per runRuns to fully complete
bronze5~20
silver8~12–13
gold12~8–9
diamond18~6

Step 4 — First-win-of-day bonus

The first-win-of-day path replaces star_xp_base with star_xp_daily in the post-run rewards. The amount uses STAR_XP_BASE × STAR_XP_DAILY_MULT (3 × 10 = 30). The branch only fires when both isFirstWinOfDay and survived are true.

If your new card is a daily-bonus variant of an existing type, follow the star_xp_base / star_xp_daily pattern: two distinct RewardCardDef entries (one base, one bonus), with the dispatch in rollPostRunRewards choosing between them.

Step 5 — VFX

Reveal-animation intensity is driven by the card’s rarity field. The five tiers (common, uncommon, rare, epic, legendary) escalate god rays and particles. Pick the rarity that matches the in-game frequency of the card, not the size of the payload — a pull_rare is rare because it drops infrequently, not because it grants a large amount.

The color field drives the card glow/accent. Pick a hex that contrasts with the rarity-tier base color so the card pops against its rarity frame.

Step 6 — Validate

Run through this list after adding the card:

CheckExpected
Card lookupREWARD_CARD_MAP[id] returns the new def.
Pool inclusionA post-run reward roll at the relevant tier produces the card (or, if it’s a level-track-only card, it appears in the chest at its threshold).
Amount rangeRolled amount stays within the configured range across many rolls.
Multi-card chestThe post-run screen lays out the 2–3 cards (credits, optional pull, star XP) plus any new cards without overflow.
First-win-of-dayIf your card swaps on the daily flag, both branches resolve correctly.
Warp Crystal exclusionThe card does not award Warp Crystals or Pull Tickets via the non-premium pool.
ConsoleNo console errors at run end or reward reveal.

Custom-element rule

A new card that maps cleanly onto an existing type only needs steps 1–6 above — rollPostRunRewards already dispatches on type and pushes the resolved card onto the rewards array.

A new card whose type is not one of credits, ship_pull, star_xp, level_xp, mod_drop requires a new branch in rollPostRunRewards (and a new value in the RewardCardType union in reward-cards.ts). Add the dispatch alongside the existing three rules: credits-always, ship-pull-if-survived, star-XP-always. Most additions can avoid this — re-use an existing type with a new variant id and a new value range.