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

85
src/run/crisis.ts Normal file
View File

@@ -0,0 +1,85 @@
/**
* Crisis System — Chemical Plague
*
* When escalation reaches its threshold, a crisis activates.
* The Chemical Plague poisons the atmosphere — player must craft
* the correct neutralizing compound to stop it.
*/
import type { CrisisConfig } from './types';
// ─── Chemical Plague Configuration ───────────────────────────────
export const CHEMICAL_PLAGUE: CrisisConfig = {
type: 'chemical-plague',
name: 'Chemical Plague',
nameRu: 'Химическая Чума',
description: 'A chain reaction is poisoning the atmosphere. Neutralize it with the right compound!',
descriptionRu: 'Цепная реакция отравляет атмосферу. Нейтрализуй правильным соединением!',
triggerThreshold: 0.8,
neutralizer: 'CaO', // Calcium oxide (quicklime) — absorbs acid gases
neutralizeAmount: 3, // need 3 units to fully neutralize
};
// ─── Crisis State ────────────────────────────────────────────────
export interface CrisisState {
config: CrisisConfig;
active: boolean;
/** Progress 0.01.0 — at 1.0 the world is fully poisoned */
progress: number;
resolved: boolean;
/** How many neutralizer units have been applied */
neutralizeApplied: number;
}
/** Damage growth rate per second during active crisis */
const CRISIS_DAMAGE_RATE = 0.01; // reaches 1.0 in ~100 seconds
/** Create a new crisis state from config */
export function createCrisisState(config: CrisisConfig): CrisisState {
return {
config,
active: true,
progress: 0,
resolved: false,
neutralizeApplied: 0,
};
}
/** Advance crisis damage over time */
export function applyCrisisDamage(crisis: CrisisState, deltaMs: number): void {
if (!crisis.active || crisis.resolved) return;
const deltaSec = deltaMs / 1000;
crisis.progress = Math.min(1.0, crisis.progress + CRISIS_DAMAGE_RATE * deltaSec);
}
/** Attempt to neutralize the crisis with a compound. Returns true if correct compound. */
export function attemptNeutralize(
crisis: CrisisState,
compoundId: string,
amount: number,
): boolean {
if (compoundId !== crisis.config.neutralizer) return false;
if (crisis.resolved) return true;
crisis.neutralizeApplied += amount;
// Reduce progress proportionally
const reductionPerUnit = 1.0 / crisis.config.neutralizeAmount;
crisis.progress = Math.max(0, crisis.progress - amount * reductionPerUnit);
// Check if fully neutralized
if (crisis.neutralizeApplied >= crisis.config.neutralizeAmount) {
crisis.resolved = true;
crisis.active = false;
crisis.progress = 0;
}
return true;
}
/** Check if crisis has been resolved */
export function isCrisisResolved(crisis: CrisisState): boolean {
return crisis.resolved;
}