Per-planet challenge definitions with rarity tiers. Generates a flat array of 45 challenges (15 per planet × 3 active planets) at module load. Rewards are warp crystals (gems) plus planet XP bounty.
Purpose
Defines the shape of a challenge: planet binding, category, rarity, scope, condition, reward.
Hand-tunes targets per planet so harder planets demand more.
Generates the canonical CHALLENGES array and lookup helpers for store and UI consumers (future challengeStore, future HubScreen cards).
ID format: {planetShort}_{category}_{rarity} (e.g. ls_kills_common).
Exports
Symbol
Type
Description
CHALLENGE_RARITIES
ChallengeRarity[]
Ordered rarity list (common → legendary).
CHALLENGES
ChallengeDef[]
Flat array, all 45 challenges. Ordered by planet then category.
STARTER_WEAPONS
readonly ['rifle', 'shotgun']
Always-in-pool weapons regardless of challenge completion.
getChallengesForPlanet(planetId)
(PlanetId) => ChallengeDef[]
Filter by planet.
getChallengesByCategory(planetId, category)
(PlanetId, ChallengeCategory) => ChallengeDef[]
Filter by planet and category.
getChallengeById(id)
(string) => ChallengeDef | undefined
O(1) lookup via internal CHALLENGE_BY_ID map.
Reward Tables
Gems per rarity (shared across categories)
Rarity
Gems
Common
5
Uncommon
15
Rare
40
Epic
100
Legendary
300
XP bounty base (Landing Site values; scaled per planet)
Rarity
Base XP
Common
30
Uncommon
60
Rare
150
Epic
400
Legendary
800
XP awarded = round(XP_BOUNTY_BASE[rarity] * PLANET_XP_BOUNTY_MULT[planetId]).
Planet XP bounty multipliers
Planet ID
Planet
Multiplier
12
Landing Site
1.0
21
Sunrise City
1.5
3
Voidstar
2.0
30–37
Solaris, Speedway, Eden, Old Earth, Network Station, Delphi, Desolation, Obelisk
1.0 (placeholder)
Per-Planet Targets
Targets are hand-tuned. Format: [Common, Uncommon, Rare, Epic, Legendary].
Landing Site (id 12, short ls) — easiest
Category
Targets
tier
3, 5, 8, 12, 20
kills
50, 150, 300, 2 000, 10 000
events
1, 3, 5, 25, 100
Sunrise City (id 21, short sc) — medium
Category
Targets
tier
4, 6, 10, 15, 25
kills
75, 200, 500, 3 000, 15 000
events
2, 4, 6, 30, 120
Voidstar (id 3, short vs) — hardest, 2× enemy pressure
Category
Targets
tier
5, 8, 12, 18, 30
kills
100, 300, 750, 5 000, 25 000
events
2, 5, 8, 40, 150
Placeholder planets (30–37) use LANDING_SITE_TARGETS until tuned.
Scope Rules
Category
Common
Uncommon
Rare
Epic
Legendary
tier
run
run
run
run
run
kills
run
run
run
lifetime
lifetime
events
run
run
run
lifetime
lifetime
tier is always per-run (you reach a tier within one run). kills and events flip to lifetime at epic+.
Description string suffix is rendered from scope:
'run' → " in a single run"
'lifetime' → " total"
Names by Category × Rarity
Rarity
Tier
Kills
Events
Common
Warm Up
Pest Control
First Contact
Uncommon
Proving Ground
Scrapper
Opportunist
Rare
Deep Run
Ace Pilot
Trailblazer
Epic
Endurance
War Machine
Veteran Explorer
Legendary
Unstoppable
Extinction Event
Cartographer
Planet Short Codes (for ID generation)
ID
Short
Planet
12
ls
Landing Site
21
sc
Sunrise City
3
vs
Voidstar
30
so
Solaris
31
sp
Speedway
32
ed
Eden
33
oe
Old Earth
34
ns
Network Station
35
dp
Delphi
36
ds
Desolation
37
ob
Obelisk
Generation Flow
buildPlanetChallenges(planetId) looks up PLANET_SHORT, PLANET_TARGETS, and PLANET_XP_BOUNTY_MULT for the planet.
Loops i = 0..4 over CHALLENGE_RARITIES.
For each rarity, computes gems = GEMS[rarity] and xp = round(XP_BOUNTY_BASE[rarity] * xpMult).
Pushes three ChallengeDef entries — one each for tier, kills, events — with the rarity’s target and the appropriate scope.
Returns 15 challenges per planet.
CHALLENGES concatenates the output for planets 12, 21, 3 → 45 total.
CHALLENGE_BY_ID indexes the flat list at module load for O(1) lookup.
scopeLabel(scope) returns the description suffix. Event descriptions pluralize via t.events[i] > 1 ? 's' : ''. Kill counts render with toLocaleString() for thousands separators.
Dependencies
import type { PlanetId } from './planet-config' — only type import. No runtime coupling.
Targets and XP multipliers are local constants; the file is self-contained at runtime.
Consumers (declared in source comments)
Future challengeStore — tracks completion state.
Future HubScreen UI — challenge display cards.
Notes / Gotchas
The XP multiplier table comment claims it “matches the threshold scaling in planet-progression.ts” — placeholder planets all default to 1.0 and LANDING_SITE_TARGETS, so once those planets ship they need both a real PLANET_XP_BOUNTY_MULT value and a real PlanetTargets entry.
Only planets 12, 21, 3 are currently built into CHALLENGES despite tables and short codes existing for 30–37. Activating a new planet requires adding a ...buildPlanetChallenges(<id>) line to the CHALLENGES array.
STARTER_WEAPONS lives in this file but is unrelated to the challenge generator — it is a sibling export documenting weapons always available regardless of challenge completion.
CHALLENGE_BY_ID is module-private; lookups must go through getChallengeById.
EXTRACT-CANDIDATE
Rarity → reward mapping (GEMS, XP_BOUNTY_BASE) duplicates a pattern likely needed elsewhere for any rarity-driven reward system (loot, missions). Consider hoisting to a shared rarity-rewards.ts if a second consumer appears.
Planet short-code table (PLANET_SHORT) overlaps with PLANET_XP_BOUNTY_MULT and the placeholder planet list — both want to know “which planets exist and at what difficulty.” A unified planet-registry.ts keyed by PlanetId would deduplicate.
Scope-label suffix logic (scopeLabel) and the pluralization inline in the events description are description-builder concerns. If more categories or localized strings arrive, factor into a challenge-copy.ts formatter.
Per-planet PlanetTargets table is currently inlined as three named constants plus a placeholder fallback. When all 11 planets ship, a single Record<PlanetId, PlanetTargets> literal with no fallback would catch missing tunings at type-check time.