/** * Meta-Progression — persists between runs. * * Manages the Codex (permanent knowledge), spores (currency), * unlocked schools, and run history. */ import type { MetaState, CodexEntry, RunSummary } from './types'; import type { RunState } from './types'; import { calculateSpores, countDiscoveries } from './state'; /** Create a fresh meta state (first time playing) */ export function createMetaState(): MetaState { return { spores: 0, codex: [], totalRuns: 0, totalDeaths: 0, unlockedSchools: ['alchemist'], bestRunTime: 0, bestRunDiscoveries: 0, runHistory: [], }; } /** Apply run results to meta state after a run ends (death or completion) */ export function applyRunResults(meta: MetaState, run: RunState): void { // 1. Calculate and add spores const sporesEarned = calculateSpores(run); meta.spores += sporesEarned; // 2. Add new codex entries (skip duplicates) const existingIds = new Set(meta.codex.map(e => `${e.type}:${e.id}`)); for (const elemId of run.discoveries.elements) { const key = `element:${elemId}`; if (!existingIds.has(key)) { meta.codex.push({ id: elemId, type: 'element', discoveredOnRun: run.runId }); } } for (const reactionId of run.discoveries.reactions) { const key = `reaction:${reactionId}`; if (!existingIds.has(key)) { meta.codex.push({ id: reactionId, type: 'reaction', discoveredOnRun: run.runId }); } } for (const compoundId of run.discoveries.compounds) { const key = `compound:${compoundId}`; if (!existingIds.has(key)) { meta.codex.push({ id: compoundId, type: 'compound', discoveredOnRun: run.runId }); } } for (const creatureId of run.discoveries.creatures) { const key = `creature:${creatureId}`; if (!existingIds.has(key)) { meta.codex.push({ id: creatureId, type: 'creature', discoveredOnRun: run.runId }); } } // 3. Update stats meta.totalRuns += 1; meta.totalDeaths += 1; // every run ends in death (for now) const discoveryCount = countDiscoveries(run); if (run.elapsed > meta.bestRunTime) { meta.bestRunTime = run.elapsed; } if (discoveryCount > meta.bestRunDiscoveries) { meta.bestRunDiscoveries = discoveryCount; } // 4. Add to run history const summary: RunSummary = { runId: run.runId, schoolId: run.schoolId, duration: run.elapsed, phase: run.phase, discoveries: discoveryCount, sporesEarned, crisisResolved: run.crisisResolved, }; meta.runHistory.push(summary); } /** Check if a school is unlocked */ export function isSchoolUnlocked(meta: MetaState, schoolId: string): boolean { return meta.unlockedSchools.includes(schoolId); } /** Get codex entries filtered by type */ export function getCodexEntries( meta: MetaState, type: CodexEntry['type'], ): CodexEntry[] { return meta.codex.filter(e => e.type === type); } /** Get total codex entry count */ export function getCodexCount(meta: MetaState): number { return meta.codex.length; }