phase 6: school data, run state, meta-progression, crisis system
- Add school data (Alchemist: H, O, C, Na, S, Fe starting kit) - Add run cycle types: phases, escalation, crisis, body composition - Implement run state management (create, advance phase, discoveries, spores) - Implement meta-progression (codex, spore accumulation, run history) - Implement crisis system (Chemical Plague with neutralization) - Add IndexedDB persistence for meta state - 42 new tests (335 total) Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
93
src/run/state.ts
Normal file
93
src/run/state.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* 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<string>(),
|
||||
reactions: new Set<string>(),
|
||||
compounds: new Set<string>(),
|
||||
creatures: new Set<string>(),
|
||||
},
|
||||
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<DiscoveryType, keyof RunDiscoveries> = {
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user