Leaderboard System

Per-planet global rankings for the two designated leaderboard planets. Each leaderboard planet replaces the normal Challenge Mode / progress-bar UI with a tier-reward track and a top-50 leaderboard table.

Leaderboard planets

Defined in src/starship-survivors/data/planet-config.ts via the optional isLeaderboard?: boolean field on PlanetDef. Exactly two planets have it set to true:

Planet IDNameBossEnemy setPressure
3THE VOIDSTARcenotaphbugsenemyCountMult: 2.0, spawnGraceSeconds: 0
31SPEEDWAYspirebugs_shooterenemyCountMult: 1.0, spawnGraceSeconds: 1

Voidstar is the high-pressure track (double enemy count, zero grace). Speedway is a shooter-gauntlet track at normal pressure.

Ranking metric

Players are ranked by highest tier reached on the planet. Tier is the 4-minute mission difficulty-escalation counter (game._currentLevel). It is tracked per planet — your Voidstar best and Speedway best are independent records.

Source-of-truth lives in Supabase. The hydrated runtime cache is useTierStore (src/starship-survivors/stores/tierStore.ts), populated from bootstrap_player on app open and updated from finalize_run responses.

Submission flow

When a run ends, finalizeRun(result, planetIndex) in src/starship-survivors/services/runProgressionService.ts posts the run to the finalize_run Supabase RPC. The payload includes:

  • planet_id — from useSessionStore.getState().runDef.context.planetId
  • tier_reachedresult.progression.tierReached
  • weapons_at_end, kills, duration_s, result, and other run fields

The server atomically writes match_history, computes currency rewards, updates player_currencies, and (when the run is on a leaderboard planet) updates the player’s tier_record for that planet. The response includes tier_record: { planet_id, highest_tier } and the client mirrors it into useTierStore.updateFromRunResult(...).

There is no separate “leaderboard submit” call — every finalized run on a leaderboard planet is also a leaderboard submission, because the rank metric is the persisted tier record.

Fetching the leaderboard

LeaderboardWrapper (src/metagame/components/LeaderboardWrapper.tsx) fetches the top entries on mount via the get_tier_leaderboard RPC, keyed by p_planet_id. The header comment says “top 50 by highest tier.”

Each entry has:

  • player_id, display_name, rank
  • highest_tier — the displayed score
  • kills — lifetime kills shown next to the name with a 💀 suffix
  • weapons_json: WeaponEntry[] — the weapon loadout at end-of-best-run, rendered as icons via getWeaponIconPath with an emoji fallback from WEAPON_EMOJIS

The row for the local player gets the lb-row-self CSS modifier so the player can spot themselves in the list.

Hub UI behavior

HubScreen.tsx (src/metagame/screens/HubScreen.tsx) branches on PLANETS[activePlanetId]?.isLeaderboard:

  • Normal planets render PlanetProgressBar + Challenge Mode controls in the hub-progress-challenge-row.
  • Leaderboard planets render the tier-reward track (claimable milestones every 5 tiers, granted by claim_tier_milestone RPC) and overlay LeaderboardWrapper inside the hub-building-overlap container.

Challenge Mode exclusion

Leaderboard planets are explicitly carved out of Challenge Mode:

const canUseChallengeMode = challengeUnlocked && !_activePlanetIsLeaderboard;

Inline comment at HubScreen.tsx:103-105:

Challenge Mode is unavailable on leaderboard planets (Voidstar, Speedway) per the design — those are their own ranked tracks.

The Challenge Mode toggle does not render on these planets even after the player clears the planet’s final boss. This keeps the ranked tracks on a single fair difficulty surface.

Milestone rewards on leaderboard planets

Even though leaderboard planets don’t run the normal progress-bar track, they still grant tier milestone gems: every 5 tiers reached unlocks a claim via claim_tier_milestone. useTierStore.canClaimMilestone(planetId) checks highest_tier >= nextMilestone && !alreadyClaimed. Reward formula: getTierMilestoneGems(milestone) = milestone * 2 gems.

Key files

  • src/starship-survivors/data/planet-config.tsisLeaderboard field on planets 3 and 31
  • src/metagame/components/LeaderboardWrapper.tsx — top-50 table, get_tier_leaderboard RPC
  • src/metagame/screens/HubScreen.tsx — leaderboard-planet UI branching, Challenge Mode exclusion
  • src/starship-survivors/services/runProgressionService.tsfinalize_run submission with tier_reached + planet_id
  • src/starship-survivors/stores/tierStore.ts — hydrated per-planet tier record cache + milestone tracking
  • src/metagame/components/ChallengePopover.tsxclaim_tier_milestone RPC call site