- 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>
86 lines
2.9 KiB
TypeScript
86 lines
2.9 KiB
TypeScript
/**
|
||
* 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.0–1.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;
|
||
}
|