PURPOSE
Interactive post-run stats explorer. Reachable only by direct URL (/games/starship-survivors/stats) — no longer part of the live post-run flow as of v5.122 (the flow is now Game Over → /reveal → /ship-pull → Hub). Reads the in-memory MissionResult from sessionStore and renders a mobile-first portrait layout with a top outcome banner, a scrollable tabbed content area, and a bottom tab pill bar plus an EXIT button. Tabs are Overview, Weapons, Enemies, and History.
OWNS
- The exported component
RunStatsScreen(fixed-position fullscreen overlay at zIndex 300). - Local state:
activeTab('overview' | 'weapons' | 'enemies' | 'history') and asavedRefguard so the run is saved to history exactly once per mount. - Internal subcomponents:
OutcomeBanner,OverviewTab,NewArtifactUnlocksPanel,WeaponsTab,EnemiesTab,HistoryTab,BottomControls,StatCard,Bar,ComparisonBadge,ComparisonRow,PBBadge,MiniStat. - Local helpers:
formatTime(M:SS or H:MM:SS) andformatNumber(k / M suffixes). - Shared inline style constants
PANEL,LABEL,VALUE. - Per-tab derived data: sorted/merged weapon stats (with name, color, level, accuracy, dps, damage share); enemy entries grouped by archetype or rarity; per-stat comparison rows against the running average; recent-score bar trend for the last five runs; personal-best diff computation.
READS FROM
useSessionStoreselectors => s.missionResult— the canonicalMissionResultfor this run.useArtifactUnlocksStore.getState()— invoked to push newly-legendary artifacts and per-artifact best-tier history into the persistent unlock store.ARTIFACT_MAPfrom../data/artifacts— to resolve unlocked artifact ids to display names.WEAPON_MAPfrom../data/weapons— to resolve weapon ids to display name and color.ENEMY_TYPE_MAP,RARITY_TINTS, typeEnemyRarityfrom../data/enemies— to resolve enemy ids to name, archetype, rarity, tint.getRunHistory,getRunAverages,getPersonalBests, typeRunHistorySummaryfrom../data/run-history— full local-storage run history, averages computed on prior runs (history.slice(1)), and rolled-up personal bests.MissionResulttype from../data/mission-result.useNavigatefromreact-router-dom.
PUSHES TO
saveRunToHistory(result)from../data/run-history— fired once on mount, gated bysavedRef.useArtifactUnlocksStore.getState().unlockMany(ids)— flushesresult.progression.newlyUnlockedArtifactIdsso newly-legendary artifacts are selectable as starting artifacts in the hub.useArtifactUnlocksStore.getState().recordBestTierMany(bestTier)— flushesresult.progression.artifactBestTierThisRunso the picker can show progress on not-yet-legendary artifacts.navigate('/', { replace: true })— on mount whenresultis missing (page reload clears in-memory state).navigate('/')— when the user taps EXIT TO HUB inBottomControls.
DOES NOT
- Does not run any game-loop logic, spawn entities, or touch the engine/canvas; it is pure metagame React UI.
- Does not write directly to
localStorage; persistence is delegated torun-historyandartifactUnlocksStore. - Does not mutate
MissionResult, the session store, or any other store beyond the artifact-unlock flushes described above. - Does not award currency, XP, or other rewards — those occur upstream in the reveal flow.
- Does not subscribe to any per-frame engine signal; rendering is React-driven from store state.
- Does not handle hardware-back, swipe gestures, or keyboard navigation; tab switching and exit are tap-only.
- Does not request, fetch, or post anything to the server; all data is read from local stores.
- Is no longer part of the live post-run flow — entering it requires the direct URL.
Signals
useEffectonresult→ redirects to/withreplace: truewhenresultis null.useEffectonresult(withsavedRefguard) → callssaveRunToHistory, then conditionally callsunlockManyandrecordBestTierManyon the artifact-unlocks store.useMemoforhistory(empty deps),averages(depends onhistory),personalBests(depends onhistory), plus per-tab memos for sorted weapon entries, grouped enemy entries, and recent-score series.- Tab button
onClick→setActiveTab(tab.id). - Group-toggle button
onClickinEnemiesTab→setGroupBy('archetype' | 'type')(the label “Rarity” maps to the'type'mode internally). - Exit button
onClick→navigate('/').
Entry points
- URL route
/games/starship-survivors/stats— the only way to land on the screen post-v5.122. - Direct mount via the metagame router; no other component links here in the current build. The doc comment notes a future Hub-side “Last Run” link could make it reachable again.
Pattern notes
- All styling is inline CSS via shared
PANEL/LABEL/VALUEobjects plusclamp()font sizes for mobile responsiveness; no external CSS modules or theme tokens. env(safe-area-inset-top)andenv(safe-area-inset-bottom)are added to top banner and bottom controls padding for iOS notch/home-indicator safety.- The component is rendered as a fixed
inset: 0overlay atzIndex: 300with its own background gradient, so it covers whatever is mounted underneath. savedRef = useRef(false)is the idempotency gate that ensuressaveRunToHistoryand the artifact-unlock flushes fire exactly once even if React re-runs the effect.averagesdeliberately excludes the current run by passinghistory.slice(1)togetRunAverages— the just-saved run sits at index 0 inhistory.ComparisonBadgetreats deltas under 3% as neutral (“avg”), positive deltas as green up-arrow, negative as red down-arrow; null/zero averages render nothing.- Weapons are merged from
result.combat.weaponStatswithresult.progression.weaponsAtEndto recover the end-of-run level; accuracy ishits / shots * 100, DPS isdmg / runTimewithrunTimefloored to at least 1 second; the top weapon is tagged with a crown. - Enemies tab groups by archetype by default; the “Rarity” toggle re-bins the same entries by rarity; per-row proportion bars are normalized against
totalKills. - History tab shows a personal-bests banner only if at least one record was tied or beaten this run; comparison rows colour current values green when higher than average and red when lower (no metric-direction awareness — e.g., damage-taken-higher still renders green).
- Recent-score sparkline uses bar heights scaled against
Math.max(...recent.map(r => r.score), 1); the latest run is gold, older runs are grey. - Fonts mix
'Cal Sans', cursivefor headings/labels and'Space Grotesk', sans-seriffor weapon/artifact body text.