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:
Денис Шкабатур
2026-02-12 15:10:05 +03:00
parent 22e6c6bcee
commit 5b7dbb4df3
7 changed files with 1006 additions and 0 deletions

183
src/run/types.ts Normal file
View File

@@ -0,0 +1,183 @@
/**
* Run Cycle Types — schools, run phases, meta-progression
*
* Defines the structure of a single run (birth → death → rebirth)
* and the persistent meta-progression between runs.
*/
// ─── Schools ─────────────────────────────────────────────────────
export interface SchoolData {
id: string;
name: string;
nameRu: string;
description: string;
descriptionRu: string;
/** Starting element symbols (looked up in element registry) */
startingElements: string[];
/** Quantity of each starting element */
startingQuantities: Record<string, number>;
/** Scientific principle the school teaches */
principle: string;
principleRu: string;
/** Play style description */
playstyle: string;
playstyleRu: string;
/** Hex color for UI representation */
color: string;
}
// ─── Run Phases ──────────────────────────────────────────────────
/** Phases of a single run in order */
export enum RunPhase {
Awakening = 0,
Exploration = 1,
Escalation = 2,
Crisis = 3,
Resolution = 4,
}
/** Human-readable names for RunPhase */
export const RUN_PHASE_NAMES: Record<RunPhase, string> = {
[RunPhase.Awakening]: 'Awakening',
[RunPhase.Exploration]: 'Exploration',
[RunPhase.Escalation]: 'Escalation',
[RunPhase.Crisis]: 'Crisis',
[RunPhase.Resolution]: 'Resolution',
};
export const RUN_PHASE_NAMES_RU: Record<RunPhase, string> = {
[RunPhase.Awakening]: 'Пробуждение',
[RunPhase.Exploration]: 'Исследование',
[RunPhase.Escalation]: 'Эскалация',
[RunPhase.Crisis]: 'Кризис',
[RunPhase.Resolution]: 'Развязка',
};
// ─── Crisis Types ────────────────────────────────────────────────
export type CrisisType = 'chemical-plague';
export interface CrisisConfig {
type: CrisisType;
name: string;
nameRu: string;
description: string;
descriptionRu: string;
/** Escalation threshold (0-1) at which crisis triggers */
triggerThreshold: number;
/** Compound that neutralizes the crisis */
neutralizer: string;
/** Amount needed to neutralize */
neutralizeAmount: number;
}
// ─── Run State ───────────────────────────────────────────────────
/** State of the current run */
export interface RunState {
/** Unique run identifier */
runId: number;
/** Selected school id */
schoolId: string;
/** Current phase */
phase: RunPhase;
/** Time spent in current phase (ms) */
phaseTimer: number;
/** Total run elapsed time (ms) */
elapsed: number;
/** Escalation level 0.0 1.0 */
escalation: number;
/** Whether crisis has been triggered */
crisisActive: boolean;
/** Whether crisis has been resolved */
crisisResolved: boolean;
/** Discoveries made this run */
discoveries: RunDiscoveries;
/** Whether the player is alive */
alive: boolean;
}
export interface RunDiscoveries {
elements: Set<string>;
reactions: Set<string>;
compounds: Set<string>;
creatures: Set<string>;
}
// ─── Meta-Progression (persists between runs) ────────────────────
export interface CodexEntry {
id: string;
type: 'element' | 'reaction' | 'compound' | 'creature';
discoveredOnRun: number;
}
export interface MetaState {
/** Total spores earned across all runs */
spores: number;
/** All codex entries (permanent knowledge) */
codex: CodexEntry[];
/** Total runs completed */
totalRuns: number;
/** Total deaths */
totalDeaths: number;
/** Unlocked school ids */
unlockedSchools: string[];
/** Best run statistics */
bestRunTime: number;
bestRunDiscoveries: number;
/** Run history summaries */
runHistory: RunSummary[];
}
export interface RunSummary {
runId: number;
schoolId: string;
duration: number;
phase: RunPhase;
discoveries: number;
sporesEarned: number;
crisisResolved: boolean;
}
// ─── Body Composition (for death animation) ──────────────────────
/** Real elemental composition of the human body (simplified) */
export const BODY_COMPOSITION: { symbol: string; fraction: number }[] = [
{ symbol: 'O', fraction: 0.65 },
{ symbol: 'C', fraction: 0.18 },
{ symbol: 'H', fraction: 0.10 },
{ symbol: 'N', fraction: 0.03 },
{ symbol: 'Ca', fraction: 0.015 },
{ symbol: 'P', fraction: 0.01 },
{ symbol: 'S', fraction: 0.003 },
{ symbol: 'Na', fraction: 0.002 },
{ symbol: 'K', fraction: 0.002 },
{ symbol: 'Fe', fraction: 0.001 },
];
// ─── Constants ───────────────────────────────────────────────────
/** Phase durations in ms (approximate, can be adjusted) */
export const PHASE_DURATIONS: Record<RunPhase, number> = {
[RunPhase.Awakening]: 0, // ends when player leaves Cradle
[RunPhase.Exploration]: 180_000, // 3 minutes
[RunPhase.Escalation]: 120_000, // 2 minutes
[RunPhase.Crisis]: 0, // ends when resolved or player dies
[RunPhase.Resolution]: 60_000, // 1 minute
};
/** Escalation growth rate per second (0→1 over Escalation phase) */
export const ESCALATION_RATE = 0.005;
/** Spores awarded per discovery type */
export const SPORE_REWARDS = {
element: 5,
reaction: 10,
compound: 8,
creature: 15,
crisisResolved: 50,
runCompleted: 20,
};