refactor: remove GUARDIANS constant, consolidate into guardian-data.ts
- Delete src/lib/game/constants/guardians.ts (the old static GUARDIANS constant) - Create src/lib/game/data/guardian-data.ts with BASE_GUARDIANS (same data, new home) - Remove GUARDIANS export from constants/index.ts - Update all 11 files that imported GUARDIANS to use getGuardianForFloor() or BASE_GUARDIANS: - useGameDerived.ts, combat-actions.ts, gameStore.ts, prestigeStore.ts - combat-utils.ts, room-utils.ts, floor-utils.ts, spire-utils.ts - SpireCombatPage.tsx, SpireHeader.tsx - Update 4 test files to use getGuardianForFloor() instead of GUARDIANS constant - guardian-encounters.ts now imports BASE_GUARDIANS from guardian-data.ts - Split room-utils.test.ts (505 lines) into room-utils.test.ts + room-utils-floor-state.test.ts
This commit is contained in:
@@ -3,7 +3,7 @@ import {
|
||||
computePactMultiplier,
|
||||
computePactInsightMultiplier,
|
||||
} from '../utils/pact-utils';
|
||||
import { GUARDIANS } from '../constants';
|
||||
import { getGuardianForFloor } from '../data/guardian-encounters';
|
||||
// Helper: compute actual multiplier values
|
||||
function getDamageMult(mult: number, extraPacts: number, mitigation: number): number {
|
||||
const numAdditional = extraPacts;
|
||||
@@ -41,14 +41,14 @@ describe('computePactMultiplier', () => {
|
||||
});
|
||||
|
||||
it('should return guardian damage multiplier for a single guardian pact', () => {
|
||||
const floor10 = GUARDIANS[10];
|
||||
const floor10 = getGuardianForFloor(10)!;
|
||||
const result = computePactMultiplier({ signedPacts: [10] });
|
||||
expect(result).toBe(floor10.damageMultiplier);
|
||||
});
|
||||
|
||||
it('should multiply damage multipliers for multiple guardian pacts', () => {
|
||||
const floor10 = GUARDIANS[10];
|
||||
const floor20 = GUARDIANS[20];
|
||||
const floor10 = getGuardianForFloor(10)!;
|
||||
const floor20 = getGuardianForFloor(20)!;
|
||||
const result = computePactMultiplier({ signedPacts: [10, 20] });
|
||||
// With 2 pacts: baseMult = f10 * f20, then penalty = 0.5 * 1 = 0.5
|
||||
// effectivePenalty = max(0, 0.5 - 0) = 0.5
|
||||
@@ -58,8 +58,8 @@ describe('computePactMultiplier', () => {
|
||||
});
|
||||
|
||||
it('should apply interference mitigation to reduce penalty', () => {
|
||||
const floor10 = GUARDIANS[10];
|
||||
const floor20 = GUARDIANS[20];
|
||||
const floor10 = getGuardianForFloor(10)!;
|
||||
const floor20 = getGuardianForFloor(20)!;
|
||||
const baseMult = floor10.damageMultiplier * floor20.damageMultiplier;
|
||||
|
||||
// No mitigation: penalty = 0.5, result = baseMult * 0.5
|
||||
@@ -79,8 +79,8 @@ describe('computePactMultiplier', () => {
|
||||
});
|
||||
|
||||
it('should apply synergy bonus when mitigation exceeds 5', () => {
|
||||
const floor10 = GUARDIANS[10];
|
||||
const floor20 = GUARDIANS[20];
|
||||
const floor10 = getGuardianForFloor(10)!;
|
||||
const floor20 = getGuardianForFloor(20)!;
|
||||
const baseMult = floor10.damageMultiplier * floor20.damageMultiplier;
|
||||
|
||||
// mitigation = 6: synergyBonus = (6-5)*0.1 = 0.1, result = baseMult * 1.1
|
||||
@@ -92,8 +92,8 @@ describe('computePactMultiplier', () => {
|
||||
});
|
||||
|
||||
it('should scale synergy bonus with higher mitigation', () => {
|
||||
const floor10 = GUARDIANS[10];
|
||||
const floor20 = GUARDIANS[20];
|
||||
const floor10 = getGuardianForFloor(10)!;
|
||||
const floor20 = getGuardianForFloor(20)!;
|
||||
const baseMult = floor10.damageMultiplier * floor20.damageMultiplier;
|
||||
|
||||
const mit5 = computePactMultiplier({
|
||||
@@ -115,9 +115,9 @@ describe('computePactMultiplier', () => {
|
||||
});
|
||||
|
||||
it('should handle three pacts with penalty', () => {
|
||||
const floor10 = GUARDIANS[10];
|
||||
const floor20 = GUARDIANS[20];
|
||||
const floor30 = GUARDIANS[30];
|
||||
const floor10 = getGuardianForFloor(10)!;
|
||||
const floor20 = getGuardianForFloor(20)!;
|
||||
const floor30 = getGuardianForFloor(30)!;
|
||||
const baseMult = floor10.damageMultiplier * floor20.damageMultiplier * floor30.damageMultiplier;
|
||||
|
||||
// 3 pacts: numAdditional = 2, basePenalty = 1.0, effectivePenalty = 1.0
|
||||
@@ -130,9 +130,9 @@ describe('computePactMultiplier', () => {
|
||||
});
|
||||
|
||||
it('should handle three pacts with partial mitigation', () => {
|
||||
const floor10 = GUARDIANS[10];
|
||||
const floor20 = GUARDIANS[20];
|
||||
const floor30 = GUARDIANS[30];
|
||||
const floor10 = getGuardianForFloor(10)!;
|
||||
const floor20 = getGuardianForFloor(20)!;
|
||||
const floor30 = getGuardianForFloor(30)!;
|
||||
const baseMult = floor10.damageMultiplier * floor20.damageMultiplier * floor30.damageMultiplier;
|
||||
|
||||
// 3 pacts: numAdditional = 2, basePenalty = 1.0
|
||||
@@ -146,7 +146,7 @@ describe('computePactMultiplier', () => {
|
||||
});
|
||||
|
||||
it('should handle mix of guardian and non-guardian floors', () => {
|
||||
const floor10 = GUARDIANS[10];
|
||||
const floor10 = getGuardianForFloor(10)!;
|
||||
// Non-guardian floors contribute nothing (no entry in GUARDIANS)
|
||||
const result = computePactMultiplier({ signedPacts: [5, 10] });
|
||||
// Only floor 10 counts: baseMult = floor10.damageMultiplier
|
||||
@@ -181,22 +181,22 @@ describe('computePactInsightMultiplier', () => {
|
||||
});
|
||||
|
||||
it('should return guardian insight multiplier for a single guardian pact', () => {
|
||||
const floor10 = GUARDIANS[10];
|
||||
const floor10 = getGuardianForFloor(10)!;
|
||||
const result = computePactInsightMultiplier({ signedPacts: [10] });
|
||||
expect(result).toBe(floor10.insightMultiplier);
|
||||
});
|
||||
|
||||
it('should multiply insight multipliers for multiple guardian pacts', () => {
|
||||
const floor10 = GUARDIANS[10];
|
||||
const floor20 = GUARDIANS[20];
|
||||
const floor10 = getGuardianForFloor(10)!;
|
||||
const floor20 = getGuardianForFloor(20)!;
|
||||
const result = computePactInsightMultiplier({ signedPacts: [10, 20] });
|
||||
const expected = floor10.insightMultiplier * floor20.insightMultiplier * 0.5;
|
||||
expect(result).toBe(expected);
|
||||
});
|
||||
|
||||
it('should apply interference mitigation to reduce penalty', () => {
|
||||
const floor10 = GUARDIANS[10];
|
||||
const floor20 = GUARDIANS[20];
|
||||
const floor10 = getGuardianForFloor(10)!;
|
||||
const floor20 = getGuardianForFloor(20)!;
|
||||
const baseMult = floor10.insightMultiplier * floor20.insightMultiplier;
|
||||
|
||||
const noMitigation = computePactInsightMultiplier({
|
||||
@@ -213,8 +213,8 @@ describe('computePactInsightMultiplier', () => {
|
||||
});
|
||||
|
||||
it('should apply synergy bonus when mitigation exceeds 5', () => {
|
||||
const floor10 = GUARDIANS[10];
|
||||
const floor20 = GUARDIANS[20];
|
||||
const floor10 = getGuardianForFloor(10)!;
|
||||
const floor20 = getGuardianForFloor(20)!;
|
||||
const baseMult = floor10.insightMultiplier * floor20.insightMultiplier;
|
||||
|
||||
const result = computePactInsightMultiplier({
|
||||
@@ -226,9 +226,9 @@ describe('computePactInsightMultiplier', () => {
|
||||
});
|
||||
|
||||
it('should handle three pacts with full penalty', () => {
|
||||
const floor10 = GUARDIANS[10];
|
||||
const floor20 = GUARDIANS[20];
|
||||
const floor30 = GUARDIANS[30];
|
||||
const floor10 = getGuardianForFloor(10)!;
|
||||
const floor20 = getGuardianForFloor(20)!;
|
||||
const floor30 = getGuardianForFloor(30)!;
|
||||
const baseMult = floor10.insightMultiplier * floor20.insightMultiplier * floor30.insightMultiplier;
|
||||
|
||||
const result = computePactInsightMultiplier({
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { generateFloorState } from '../utils/room-utils';
|
||||
import { PUZZLE_ROOMS, SWARM_CONFIG, SPEED_ROOM_CONFIG } from '../constants';
|
||||
import { getGuardianForFloor } from '../data/guardian-encounters';
|
||||
import { getFloorMaxHP, getDodgeChance } from '../utils/floor-utils';
|
||||
|
||||
// ─── generateFloorState ───────────────────────────────────────────────────────
|
||||
|
||||
describe('generateFloorState', () => {
|
||||
it('should return a FloorState object', () => {
|
||||
const state = generateFloorState(1);
|
||||
expect(typeof state).toBe('object');
|
||||
expect(state).toHaveProperty('roomType');
|
||||
expect(state).toHaveProperty('enemies');
|
||||
});
|
||||
|
||||
it('should generate guardian state for guardian floor', () => {
|
||||
const state = generateFloorState(10);
|
||||
expect(state.roomType).toBe('guardian');
|
||||
expect(state.enemies.length).toBe(1);
|
||||
const g10 = getGuardianForFloor(10)!;
|
||||
expect(state.enemies[0].name).toBe(g10.name);
|
||||
expect(state.enemies[0].hp).toBe(g10.hp);
|
||||
expect(state.enemies[0].element).toBe(g10.element);
|
||||
});
|
||||
|
||||
it('should generate combat state for non-guardian floor with combat', () => {
|
||||
const originalRandom = Math.random;
|
||||
Math.random = () => 0.5; // Won't trigger special rooms
|
||||
const state = generateFloorState(5);
|
||||
expect(state.roomType).toBe('combat');
|
||||
expect(state.enemies.length).toBe(1);
|
||||
expect(state.enemies[0].hp).toBe(getFloorMaxHP(5));
|
||||
Math.random = originalRandom;
|
||||
});
|
||||
|
||||
it('should generate swarm state for swarm room', () => {
|
||||
const originalRandom = Math.random;
|
||||
Math.random = () => 0.14; // < SWARM_ROOM_CHANCE (0.15)
|
||||
const state = generateFloorState(5);
|
||||
expect(state.roomType).toBe('swarm');
|
||||
expect(Array.isArray(state.enemies)).toBe(true);
|
||||
expect(state.enemies.length).toBeGreaterThanOrEqual(SWARM_CONFIG.minEnemies);
|
||||
Math.random = originalRandom;
|
||||
});
|
||||
|
||||
it('should generate speed state for speed room', () => {
|
||||
const originalRandom = Math.random;
|
||||
Math.random = () => 0.09; // < SPEED_ROOM_CHANCE (0.10)
|
||||
const state = generateFloorState(5);
|
||||
expect(state.roomType).toBe('speed');
|
||||
expect(state.enemies.length).toBe(1);
|
||||
Math.random = originalRandom;
|
||||
});
|
||||
|
||||
it('should generate puzzle state for puzzle room', () => {
|
||||
const originalRandom = Math.random;
|
||||
Math.random = () => 0.19; // < PUZZLE_ROOM_CHANCE (0.20)
|
||||
const state = generateFloorState(7);
|
||||
expect(state.roomType).toBe('puzzle');
|
||||
expect(Array.isArray(state.enemies)).toBe(true);
|
||||
expect(state.enemies).toEqual([]);
|
||||
expect(state.puzzleProgress).toBe(0);
|
||||
expect(typeof state.puzzleRequired).toBe('number');
|
||||
expect(typeof state.puzzleId).toBe('string');
|
||||
expect(typeof state.puzzleAttunements).toBe('object');
|
||||
Math.random = originalRandom;
|
||||
});
|
||||
|
||||
it('should fill puzzle attunements from PUZZLE_ROOMS', () => {
|
||||
const originalRandom = Math.random;
|
||||
Math.random = () => 0.19;
|
||||
const state = generateFloorState(7);
|
||||
expect(state.roomType).toBe('puzzle');
|
||||
expect(state.puzzleAttunements.length).toBeGreaterThan(0);
|
||||
expect(typeof state.puzzleAttunements[0]).toBe('string');
|
||||
Math.random = originalRandom;
|
||||
});
|
||||
|
||||
it('should use correct element for floor', () => {
|
||||
const state = generateFloorState(1);
|
||||
expect(state.enemies[0].element).toBe('fire');
|
||||
const state2 = generateFloorState(2);
|
||||
expect(state2.enemies[0].element).toBe('water');
|
||||
});
|
||||
|
||||
it('combat enemy HP should match floor max HP', () => {
|
||||
const state = generateFloorState(50);
|
||||
expect(state.enemies[0].hp).toBe(getFloorMaxHP(50));
|
||||
});
|
||||
|
||||
it('speed room should have correct dodge chance', () => {
|
||||
const originalRandom = Math.random;
|
||||
Math.random = () => 0.09; // Speed room
|
||||
const speedState = generateFloorState(50);
|
||||
expect(speedState.roomType).toBe('speed');
|
||||
expect(speedState.enemies[0].dodgeChance).toBe(getDodgeChance(50));
|
||||
Math.random = originalRandom;
|
||||
});
|
||||
|
||||
it('should handle very high floor number', () => {
|
||||
const state = generateFloorState(1000);
|
||||
expect(state.roomType).toBe('guardian');
|
||||
});
|
||||
|
||||
it('should handle floor 0', () => {
|
||||
const state = generateFloorState(0);
|
||||
expect(state.roomType).toBe('combat');
|
||||
});
|
||||
});
|
||||
@@ -4,12 +4,10 @@ import {
|
||||
getFloorArmor,
|
||||
getDodgeChance,
|
||||
getEnemyBarrier,
|
||||
generateFloorState,
|
||||
getPuzzleProgressSpeed,
|
||||
} from '../utils/room-utils';
|
||||
import { GUARDIANS, FLOOR_ELEM_CYCLE, PUZZLE_ROOMS, SWARM_CONFIG, SPEED_ROOM_CONFIG, FLOOR_ARMOR_CONFIG } from '../constants';
|
||||
import type { EnemyState, FloorState } from '../types';
|
||||
import { getFloorMaxHP, getFloorElement, getEnemyName } from '../utils/floor-utils';
|
||||
import { FLOOR_ELEM_CYCLE, PUZZLE_ROOMS, SWARM_CONFIG, SPEED_ROOM_CONFIG, FLOOR_ARMOR_CONFIG } from '../constants';
|
||||
import { getAllGuardianFloors } from '../data/guardian-encounters';
|
||||
|
||||
// ─── generateRoomType ─────────────────────────────────────────────────────────
|
||||
|
||||
@@ -21,8 +19,8 @@ describe('generateRoomType', () => {
|
||||
});
|
||||
|
||||
it('should return "guardian" for guardian floors', () => {
|
||||
// Get all guardian floors from GUARDIANS object
|
||||
for (const floor of Object.keys(GUARDIANS).map(Number)) {
|
||||
// Get all guardian floors from the unified system
|
||||
for (const floor of getAllGuardianFloors().filter(f => f <= 100)) {
|
||||
// Override and restore Math.random to ensure consistent result
|
||||
const originalRandom = Math.random;
|
||||
Math.random = () => 0.1; // Anything < PUZZLE_ROOM_CHANCE ensures non-puzzle
|
||||
@@ -163,7 +161,7 @@ describe('getFloorArmor', () => {
|
||||
});
|
||||
|
||||
it('should return 0 for guardian floors', () => {
|
||||
expect(getFloorArmor(10)).toBe(0); // Ignis Prime has armor 0.10 in GUARDIANS
|
||||
expect(getFloorArmor(10)).toBe(0); // Floor 10 is a guardian floor
|
||||
});
|
||||
|
||||
it('should return armor between min and max for non-guardian floor with armor', () => {
|
||||
@@ -310,114 +308,6 @@ describe('getEnemyBarrier', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// ─── generateFloorState ───────────────────────────────────────────────────────
|
||||
|
||||
describe('generateFloorState', () => {
|
||||
it('should return a FloorState object', () => {
|
||||
const state = generateFloorState(1);
|
||||
expect(typeof state).toBe('object');
|
||||
expect(state).toHaveProperty('roomType');
|
||||
expect(state).toHaveProperty('enemies');
|
||||
});
|
||||
|
||||
it('should generate guardian state for guardian floor', () => {
|
||||
const state = generateFloorState(10);
|
||||
expect(state.roomType).toBe('guardian');
|
||||
expect(state.enemies.length).toBe(1);
|
||||
expect(state.enemies[0].name).toBe(GUARDIANS[10].name);
|
||||
expect(state.enemies[0].hp).toBe(GUARDIANS[10].hp);
|
||||
expect(state.enemies[0].element).toBe(GUARDIANS[10].element);
|
||||
});
|
||||
|
||||
it('should generate combat state for non-guardian floor with combat', () => {
|
||||
const originalRandom = Math.random;
|
||||
Math.random = () => 0.5; // Won't trigger special rooms
|
||||
const state = generateFloorState(5);
|
||||
expect(state.roomType).toBe('combat');
|
||||
expect(state.enemies.length).toBe(1);
|
||||
expect(state.enemies[0].hp).toBe(getFloorMaxHP(5));
|
||||
Math.random = originalRandom;
|
||||
});
|
||||
|
||||
it('should generate swarm state for swarm room', () => {
|
||||
const originalRandom = Math.random;
|
||||
Math.random = () => 0.14; // < SWARM_ROOM_CHANCE (0.15)
|
||||
const state = generateFloorState(5);
|
||||
expect(state.roomType).toBe('swarm');
|
||||
expect(Array.isArray(state.enemies)).toBe(true);
|
||||
expect(state.enemies.length).toBeGreaterThanOrEqual(SWARM_CONFIG.minEnemies);
|
||||
Math.random = originalRandom;
|
||||
});
|
||||
|
||||
it('should generate speed state for speed room', () => {
|
||||
const originalRandom = Math.random;
|
||||
Math.random = () => 0.09; // < SPEED_ROOM_CHANCE (0.10)
|
||||
const state = generateFloorState(5);
|
||||
expect(state.roomType).toBe('speed');
|
||||
expect(state.enemies.length).toBe(1);
|
||||
Math.random = originalRandom;
|
||||
});
|
||||
|
||||
it('should generate puzzle state for puzzle room', () => {
|
||||
const originalRandom = Math.random;
|
||||
Math.random = () => 0.19; // < PUZZLE_ROOM_CHANCE (0.20)
|
||||
const state = generateFloorState(7);
|
||||
expect(state.roomType).toBe('puzzle');
|
||||
expect(Array.isArray(state.enemies)).toBe(true); // Array, even if empty
|
||||
expect(state.enemies).toEqual([]);
|
||||
expect(state.puzzleProgress).toBe(0); // Empty array is truthy, empty is falsy
|
||||
expect(typeof state.puzzleRequired).toBe('number');
|
||||
expect(typeof state.puzzleId).toBe('string');
|
||||
expect(typeof state.puzzleAttunements).toBe('object');
|
||||
Math.random = originalRandom;
|
||||
});
|
||||
|
||||
it('should fill puzzle attunements from PUZZLE_ROOMS', () => {
|
||||
const originalRandom = Math.random;
|
||||
Math.random = () => 0.19;
|
||||
const state = generateFloorState(7);
|
||||
expect(state.roomType).toBe('puzzle');
|
||||
expect(state.puzzleAttunements.length).toBeGreaterThan(0);
|
||||
expect(typeof state.puzzleAttunements[0]).toBe('string');
|
||||
Math.random = originalRandom;
|
||||
});
|
||||
|
||||
it('should use correct element for floor', () => {
|
||||
// Test multiple floors to verify element cycle
|
||||
const state = generateFloorState(1);
|
||||
expect(state.enemies[0].element).toBe('fire');
|
||||
|
||||
const state2 = generateFloorState(2);
|
||||
expect(state2.enemies[0].element).toBe('water');
|
||||
});
|
||||
|
||||
it('combat enemy HP should match floor max HP', () => {
|
||||
const state = generateFloorState(50);
|
||||
// Non-guardian floor 50 returns combat
|
||||
expect(state.enemies[0].hp).toBe(getFloorMaxHP(50));
|
||||
});
|
||||
|
||||
it('speed room should have correct dodge chance', () => {
|
||||
const state = generateFloorState(50);
|
||||
const originalRandom = Math.random;
|
||||
Math.random = () => 0.09; // Speed room
|
||||
const speedState = generateFloorState(50);
|
||||
expect(speedState.roomType).toBe('speed');
|
||||
expect(speedState.enemies[0].dodgeChance).toBe(getDodgeChance(50));
|
||||
Math.random = originalRandom;
|
||||
});
|
||||
|
||||
it('should handle very high floor number', () => {
|
||||
const state = generateFloorState(1000);
|
||||
expect(state.roomType).toBe('guardian');
|
||||
});
|
||||
|
||||
it('should handle floor 0', () => {
|
||||
const state = generateFloorState(0);
|
||||
expect(state.roomType).toBe('combat'); // Guardian? No. Special room? No. Default combat.
|
||||
});
|
||||
});
|
||||
|
||||
// ─── getPuzzleProgressSpeed ───────────────────────────────────────────────────
|
||||
|
||||
describe('getPuzzleProgressSpeed', () => {
|
||||
|
||||
Reference in New Issue
Block a user