/** * Run State Management — creates and updates the state of a single run. * * A "run" is one cycle: Awakening → Exploration → Escalation → Crisis → Resolution. * Each run tracks discoveries, escalation level, and crisis status. */ import { type RunState, type RunDiscoveries, RunPhase, ESCALATION_RATE, SPORE_REWARDS, } from './types'; /** Create a fresh run state for a new run */ export function createRunState(runId: number, schoolId: string): RunState { return { runId, schoolId, phase: RunPhase.Awakening, phaseTimer: 0, elapsed: 0, escalation: 0, crisisActive: false, crisisResolved: false, discoveries: { elements: new Set(), reactions: new Set(), compounds: new Set(), creatures: new Set(), }, alive: true, }; } /** Advance to the next phase. Does nothing if already at Resolution. */ export function advancePhase(state: RunState): void { if (state.phase < RunPhase.Resolution) { state.phase = (state.phase + 1) as RunPhase; state.phaseTimer = 0; } } /** Update escalation level based on elapsed delta (ms). Only active during Escalation and Crisis. */ export function updateEscalation(state: RunState, deltaMs: number): void { if (state.phase < RunPhase.Escalation) return; if (state.phase > RunPhase.Crisis) return; const deltaSec = deltaMs / 1000; state.escalation = Math.min(1.0, state.escalation + ESCALATION_RATE * deltaSec); } /** Discovery type names used in API (singular) */ export type DiscoveryType = 'element' | 'reaction' | 'compound' | 'creature'; /** Map singular type to plural key in RunDiscoveries */ const DISCOVERY_KEY: Record = { element: 'elements', reaction: 'reactions', compound: 'compounds', creature: 'creatures', }; /** Record a discovery during the current run */ export function recordDiscovery( state: RunState, type: DiscoveryType, id: string, ): void { state.discoveries[DISCOVERY_KEY[type]].add(id); } /** Count total unique discoveries in a run */ export function countDiscoveries(state: RunState): number { return ( state.discoveries.elements.size + state.discoveries.reactions.size + state.discoveries.compounds.size + state.discoveries.creatures.size ); } /** Calculate spores earned from a run */ export function calculateSpores(state: RunState): number { let total = 0; total += state.discoveries.elements.size * SPORE_REWARDS.element; total += state.discoveries.reactions.size * SPORE_REWARDS.reaction; total += state.discoveries.compounds.size * SPORE_REWARDS.compound; total += state.discoveries.creatures.size * SPORE_REWARDS.creature; if (state.crisisResolved) total += SPORE_REWARDS.crisisResolved; return total; }