import { describe, it, expect, beforeEach } from 'vitest'; import type { RunState } from '../src/run/types'; import { RunPhase, ESCALATION_RATE } from '../src/run/types'; import { createRunState, advancePhase, updateEscalation } from '../src/run/state'; import { getEscalationEffects, type EscalationEffects, } from '../src/run/escalation'; import { createCrisisState, applyCrisisDamage, attemptNeutralize, isCrisisResolved, getCrisisPlayerDamage, getCrisisTint, CHEMICAL_PLAGUE, } from '../src/run/crisis'; // ─── Escalation Effects ────────────────────────────────────────── describe('Escalation Effects', () => { it('at 0 escalation, all multipliers are 1.0', () => { const fx = getEscalationEffects(0); expect(fx.creatureSpeedMultiplier).toBe(1.0); expect(fx.creatureAggroRange).toBe(1.0); expect(fx.creatureAttackMultiplier).toBe(1.0); expect(fx.reactionInstability).toBe(0); expect(fx.environmentalDamage).toBe(0); }); it('at 0.5 escalation, effects are moderate', () => { const fx = getEscalationEffects(0.5); expect(fx.creatureSpeedMultiplier).toBeGreaterThan(1.0); expect(fx.creatureSpeedMultiplier).toBeLessThan(2.0); expect(fx.creatureAggroRange).toBeGreaterThan(1.0); expect(fx.creatureAttackMultiplier).toBeGreaterThan(1.0); expect(fx.reactionInstability).toBeGreaterThan(0); }); it('at 1.0 escalation, effects are maximal', () => { const fx = getEscalationEffects(1.0); expect(fx.creatureSpeedMultiplier).toBe(1.5); expect(fx.creatureAggroRange).toBe(1.8); expect(fx.creatureAttackMultiplier).toBe(1.6); expect(fx.reactionInstability).toBe(0.3); expect(fx.environmentalDamage).toBeGreaterThan(0); }); it('escalation clamps values correctly', () => { const fx = getEscalationEffects(2.0); // over max expect(fx.creatureSpeedMultiplier).toBe(1.5); const fxNeg = getEscalationEffects(-1.0); // under min expect(fxNeg.creatureSpeedMultiplier).toBe(1.0); }); }); // ─── Crisis Damage ─────────────────────────────────────────────── describe('Crisis Player Damage', () => { it('no damage below 30% progress', () => { const crisis = createCrisisState(CHEMICAL_PLAGUE); crisis.progress = 0.2; expect(getCrisisPlayerDamage(crisis, 1000)).toBe(0); }); it('damage increases with progress above 30%', () => { const crisis = createCrisisState(CHEMICAL_PLAGUE); crisis.progress = 0.5; const dmg = getCrisisPlayerDamage(crisis, 1000); expect(dmg).toBeGreaterThan(0); }); it('damage scales with delta time', () => { const crisis = createCrisisState(CHEMICAL_PLAGUE); crisis.progress = 0.8; const dmg1 = getCrisisPlayerDamage(crisis, 1000); const dmg2 = getCrisisPlayerDamage(crisis, 2000); expect(dmg2).toBeCloseTo(dmg1 * 2, 1); }); it('no damage when resolved', () => { const crisis = createCrisisState(CHEMICAL_PLAGUE); crisis.progress = 0.8; crisis.resolved = true; expect(getCrisisPlayerDamage(crisis, 1000)).toBe(0); }); }); describe('Crisis Visual Tint', () => { it('returns no tint at 0 progress', () => { const crisis = createCrisisState(CHEMICAL_PLAGUE); crisis.progress = 0; const tint = getCrisisTint(crisis); expect(tint.alpha).toBe(0); }); it('returns tint at high progress', () => { const crisis = createCrisisState(CHEMICAL_PLAGUE); crisis.progress = 0.8; const tint = getCrisisTint(crisis); expect(tint.alpha).toBeGreaterThan(0); expect(tint.color).toBeDefined(); }); it('returns no tint when resolved', () => { const crisis = createCrisisState(CHEMICAL_PLAGUE); crisis.progress = 0.8; crisis.resolved = true; const tint = getCrisisTint(crisis); expect(tint.alpha).toBe(0); }); }); // ─── Escalation-Phase Integration ──────────────────────────────── describe('Escalation Integration', () => { let state: RunState; beforeEach(() => { state = createRunState(1, 'alchemist'); state.phase = RunPhase.Escalation; }); it('escalation grows steadily over time', () => { // Simulate 30 seconds for (let i = 0; i < 30; i++) { updateEscalation(state, 1000); } expect(state.escalation).toBeCloseTo(ESCALATION_RATE * 30, 2); }); it('effects scale linearly with escalation', () => { updateEscalation(state, 100_000); // ~100 seconds const fx = getEscalationEffects(state.escalation); expect(fx.creatureSpeedMultiplier).toBeGreaterThan(1.0); expect(fx.creatureAggroRange).toBeGreaterThan(1.0); }); it('full escalation cycle reaches crisis threshold', () => { // Simulate until threshold for (let i = 0; i < 200; i++) { updateEscalation(state, 1000); } expect(state.escalation).toBeGreaterThanOrEqual(CHEMICAL_PLAGUE.triggerThreshold); }); });