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.
| Field | Description |
|---|---|
id | Unique 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. |
name | Display 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”). |
type | One of credits, ship_pull, star_xp, level_xp, mod_drop. Determines the icon family and the destination (currency pouch vs collection vs progress track). |
rarity | One of common, uncommon, rare, epic, legendary. Drives reveal-animation intensity (god rays, particles). |
description | One-line flavor below the name. |
icon | Emoji or icon identifier for card rendering. |
color | Hex string for the card glow/accent. |
Existing entries and their conventions:
| id | type | rarity | name | icon | color |
|---|---|---|---|---|---|
credits_small | credits | common | Credits | 🔩 | #88aacc |
pull_common | ship_pull | common | Ship Signal | 🚀 | #44aa44 |
pull_uncommon | ship_pull | uncommon | Ship Signal | 🚀 | #4488ff |
pull_rare | ship_pull | rare | Rare Ship Signal | 🚀 | #0070dd |
star_xp_base | star_xp | common | Star Progress | ⭐ | #ffdd44 |
star_xp_daily | star_xp | rare | First Win Bonus | 🌟 | #ffaa00 |
level_xp | level_xp | common | Level XP | 📖 | #50a0ff |
mod_drop_common | mod_drop | common | Mod 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):
| Tier | Min | Max |
|---|---|---|
| bronze | 5 | 15 |
| silver | 12 | 30 |
| gold | 25 | 55 |
| diamond | 45 | 100 |
Star XP rates:
| Constant | Value |
|---|---|
STAR_XP_BASE | 3 |
STAR_XP_DAILY_MULT | 10 |
| First-win-of-day star XP | 30 |
Minimum ship-pull rarity per tier:
| Tier | Min rarity | Resolved card id |
|---|---|---|
| bronze | common | pull_common |
| silver | common | pull_common |
| gold | uncommon | pull_uncommon |
| diamond | uncommon | pull_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.
| Where | What |
|---|---|
REWARD_CARDS in reward-cards.ts | Append the new RewardCardDef entry. The barrel REWARD_CARD_MAP is rebuilt on module load. |
LEVEL_REWARD_TRACK in reward-cards.ts | Optional. 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 |
|---|---|
| 20 | credits_small ×10, star_xp_base ×5 |
| 40 | credits_small ×25, pull_common ×1 |
| 60 | credits_small ×40, pull_uncommon ×1 |
| 80 | credits_small ×60, pull_uncommon ×1 |
| 100 | credits_small ×150, pull_rare ×1 |
Points earned toward the level track per arcade run (track caps at 100 total points):
| Tier | Points per run | Runs to fully complete |
|---|---|---|
| bronze | 5 | ~20 |
| silver | 8 | ~12–13 |
| gold | 12 | ~8–9 |
| diamond | 18 | ~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:
| Check | Expected |
|---|---|
| Card lookup | REWARD_CARD_MAP[id] returns the new def. |
| Pool inclusion | A 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 range | Rolled amount stays within the configured range across many rolls. |
| Multi-card chest | The post-run screen lays out the 2–3 cards (credits, optional pull, star XP) plus any new cards without overflow. |
| First-win-of-day | If your card swaps on the daily flag, both branches resolve correctly. |
| Warp Crystal exclusion | The card does not award Warp Crystals or Pull Tickets via the non-premium pool. |
| Console | No 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.