chore: cleanup — remove dead weight (prisma, db, examples, python scripts, workflow docs, redundant tsconfigs)
Build and Publish Mana Loop Docker Image / build-and-publish (push) Failing after 34s

This commit is contained in:
2026-05-13 12:16:11 +02:00
parent 6ad48efff9
commit bb268d4dea
108 changed files with 4747 additions and 2000 deletions
@@ -5,8 +5,8 @@
*/
import { describe, it, expect } from 'vitest';
import { SKILLS_DEF } from '../constants';
import { calcInsight } from '../computed-stats';
import { SKILLS_DEF } from '@/lib/game/constants';
import { calcInsight } from '@/lib/game/computed-stats';
import type { GameState } from '../types';
function createMockState(overrides: Partial<GameState> = {}): GameState {
@@ -3,7 +3,7 @@
*/
import { describe, it, expect } from 'vitest';
import { SKILLS_DEF, SKILL_EVOLUTION_PATHS, getTierMultiplier, getNextTierSkill, generateTierSkillDef } from '../constants';
import { SKILLS_DEF, SKILL_EVOLUTION_PATHS, getTierMultiplier, getNextTierSkill, generateTierSkillDef } from '@/lib/game/constants';
import { SKILL_EVOLUTION_PATHS as EVOLUTION_PATHS, getUpgradesForSkillAtMilestone, getNextTierSkill as NextTier, getTierMultiplier as TierMultiplier, generateTierSkillDef as GenerateTier } from '../skill-evolution';
describe('Integration Tests', () => {
@@ -11,8 +11,8 @@ import {
computeElementMax,
computeRegen,
computeClickMana,
} from '../computed-stats';
import { SKILLS_DEF } from '../constants';
} from '@/lib/game/computed-stats';
import { SKILLS_DEF } from '@/lib/game/constants';
import type { GameState } from '../types';
// ─── Test Helpers ───────────────────────────────────────────────────────────
@@ -3,8 +3,8 @@
*/
import { describe, it, expect } from 'vitest';
import { PRESTIGE_DEF } from '../constants';
import { computeMaxMana, computeElementMax } from '../computed-stats';
import { PRESTIGE_DEF } from '@/lib/game/constants';
import { computeMaxMana, computeElementMax } from '@/lib/game/computed-stats';
import type { GameState } from '../types';
function createMockState(overrides: Partial<GameState> = {}): GameState {
@@ -3,7 +3,7 @@
*/
import { describe, it, expect } from 'vitest';
import { SKILLS_DEF } from '../constants';
import { SKILLS_DEF } from '@/lib/game/constants';
describe('Skill Prerequisites', () => {
it('Mana Overflow should require Mana Well 3', () => {
@@ -5,7 +5,7 @@
*/
import { describe, it, expect } from 'vitest';
import { SKILLS_DEF } from '../constants';
import { SKILLS_DEF } from '@/lib/game/constants';
describe('Enchanter Skills', () => {
describe('Enchanting (Unlock enchantment design)', () => {
@@ -10,8 +10,8 @@ import {
getStudySpeedMultiplier,
getStudyCostMultiplier,
getMeditationBonus,
} from '../computed-stats';
import { SKILLS_DEF } from '../constants';
} from '@/lib/game/computed-stats';
import { SKILLS_DEF } from '@/lib/game/constants';
describe('Study Skills', () => {
describe('Quick Learner (+10% study speed)', () => {
@@ -3,7 +3,7 @@
*/
import { describe, it, expect } from 'vitest';
import { SKILLS_DEF } from '../constants';
import { SKILLS_DEF } from '@/lib/game/constants';
describe('Study Times', () => {
it('all skills should have reasonable study times', () => {
@@ -3,8 +3,8 @@
*/
import { describe, it, expect } from 'vitest';
import { SKILLS_DEF } from './constants';
import { calcInsight } from './store';
import { SKILLS_DEF } from '@/lib/game/constants';
import { calcInsight } from '@/lib/game/store';
import type { GameState } from './types';
function createMockState(overrides: Partial<GameState> = {}): GameState {
@@ -10,8 +10,8 @@ import {
computeElementMax,
computeRegen,
computeClickMana,
} from './store';
import { SKILLS_DEF } from './constants';
} from '@/lib/game/store';
import { SKILLS_DEF } from '@/lib/game/constants';
import type { GameState } from './types';
// ─── Test Helpers ───────────────────────────────────────────────────────────
@@ -3,8 +3,8 @@
*/
import { describe, it, expect } from 'vitest';
import { SKILLS_DEF, PRESTIGE_DEF } from './constants';
import { computeMaxMana, computeElementMax } from './store';
import { SKILLS_DEF, PRESTIGE_DEF } from '@/lib/game/constants';
import { computeMaxMana, computeElementMax } from '@/lib/game/store';
import type { GameState } from './types';
function createMockState(overrides: Partial<GameState> = {}): GameState {
@@ -7,8 +7,8 @@ import {
getStudySpeedMultiplier,
getStudyCostMultiplier,
getMeditationBonus,
} from './store';
import { SKILLS_DEF } from './constants';
} from '@/lib/game/store';
import { SKILLS_DEF } from '@/lib/game/constants';
describe('Study Skills', () => {
describe('Quick Learner (+10% study speed)', () => {
@@ -3,7 +3,7 @@
*/
import { describe, it, expect, beforeEach } from 'vitest';
import { useCombatStore } from './stores';
import { useCombatStore } from '@/lib/game/stores';
// ─── Test Fixtures ───────────────────────────────────────────────────
@@ -9,7 +9,7 @@ import {
usePrestigeStore,
useCombatStore,
computeMaxMana,
} from './stores';
} from '@/lib/game/stores';
// ─── Test Fixtures ───────────────────────────────────────────────────
@@ -9,8 +9,8 @@ import {
usePrestigeStore,
useCombatStore,
useUIStore,
} from './stores';
import { ELEMENTS } from './constants';
} from '@/lib/game/stores';
import { ELEMENTS } from '@/lib/game/constants';
// ─── Test Fixtures ───────────────────────────────────────────────────
@@ -6,7 +6,7 @@ import { describe, it, expect, beforeEach } from 'vitest';
import {
usePrestigeStore,
useManaStore,
} from './stores';
} from '@/lib/game/stores';
// ─── Test Fixtures ───────────────────────────────────────────────────
@@ -8,8 +8,8 @@ import {
useSkillStore,
usePrestigeStore,
getStudySpeedMultiplier,
} from './stores';
import { ELEMENTS } from './constants';
} from '@/lib/game/stores';
import { ELEMENTS } from '@/lib/game/constants';
// ─── Test Fixtures ───────────────────────────────────────────────────
@@ -3,7 +3,7 @@
*/
import { describe, it, expect, beforeEach } from 'vitest';
import { useUIStore } from './stores';
import { useUIStore } from '@/lib/game/stores';
// ─── Test Fixtures ───────────────────────────────────────────────────
@@ -0,0 +1,492 @@
import { describe, it, expect } from 'vitest';
import {
computeStats,
BASE_STATS,
SKILLS_V2,
getBaseSkillId,
hasPrerequisites,
} from '../../constants/skills-v2';
import type { ComputedStats } from '../../constants/skills-v2-types';
// Helper to create a minimal prestige state
const emptyPrestige = {};
describe('computeStats()', () => {
describe('base stats with no skills', () => {
it('should return base stats when no skills are provided', () => {
const result = computeStats({}, emptyPrestige);
expect(result.maxMana).toBe(100);
expect(result.manaRegen).toBe(2);
expect(result.clickMana).toBe(1);
expect(result.baseDamage).toBe(5);
expect(result.elementCap).toBe(10);
});
it('should have all base values correct', () => {
const result = computeStats({}, emptyPrestige);
expect(result).toMatchObject({
maxMana: 100,
manaRegen: 2,
clickMana: 1,
elementCap: 10,
studySpeed: 1,
studyCostMult: 1,
meditationEfficiency: 1,
enchantCapacity: 100,
enchantSpeed: 1,
enchantPower: 1,
disenchantRecovery: 1,
baseDamage: 5,
damageMultiplier: 1,
attackSpeed: 1,
critChance: 0,
critMultiplier: 1.5,
armorPierce: 0,
insightGain: 1,
golemDamage: 1,
golemDuration: 1,
pactMultiplier: 1,
spellDamage: 1,
guardianDamage: 1,
craftSpeed: 1,
repairSpeed: 1,
elementalDamage: 1,
});
});
});
describe('Mana Well skill', () => {
it('should add 100 max mana per level', () => {
const result = computeStats({ manaWell: 5 }, emptyPrestige);
expect(result.maxMana).toBe(100 + 5 * 100);
});
it('should be 100 at level 0', () => {
const result = computeStats({ manaWell: 0 }, emptyPrestige);
expect(result.maxMana).toBe(100);
});
it('should be 1100 at max level 10', () => {
const result = computeStats({ manaWell: 10 }, emptyPrestige);
expect(result.maxMana).toBe(1100);
});
});
describe('Mana Flow skill', () => {
it('should add 1 mana regen per level', () => {
const result = computeStats({ manaFlow: 5 }, emptyPrestige);
expect(result.manaRegen).toBe(2 + 5);
});
it('should be 2 at level 0', () => {
const result = computeStats({ manaFlow: 0 }, emptyPrestige);
expect(result.manaRegen).toBe(2);
});
it('should be 12 at max level 10', () => {
const result = computeStats({ manaFlow: 10 }, emptyPrestige);
expect(result.manaRegen).toBe(12);
});
});
describe('Mana Tap skill', () => {
it('should add 1 click mana at level 1', () => {
const result = computeStats({ manaTap: 1 }, emptyPrestige);
expect(result.clickMana).toBe(2);
});
it('should be 1 at level 0', () => {
const result = computeStats({ manaTap: 0 }, emptyPrestige);
expect(result.clickMana).toBe(1);
});
});
describe('Mana Surge skill', () => {
it('should add 3 click mana per level', () => {
const result = computeStats({ manaSurge: 1 }, emptyPrestige);
expect(result.clickMana).toBe(1 + 3);
});
it('should stack with manaTap', () => {
const result = computeStats({ manaTap: 1, manaSurge: 1 }, emptyPrestige);
expect(result.clickMana).toBe(1 + 1 + 3);
});
});
describe('Mana Spring skill', () => {
it('should add 2 mana regen at level 1', () => {
const result = computeStats({ manaSpring: 1 }, emptyPrestige);
expect(result.manaRegen).toBe(2 + 2);
});
it('should stack with manaFlow', () => {
const result = computeStats({ manaFlow: 5, manaSpring: 1 }, emptyPrestige);
expect(result.manaRegen).toBe(2 + 5 + 2);
});
});
describe('Mana Overflow skill', () => {
it('should multiply click mana by compounding 1.25 per level', () => {
// multiply effects compound per level: 1 * 1.25^2 = 1.5625
const result = computeStats({ manaOverflow: 2 }, emptyPrestige);
expect(result.clickMana).toBeCloseTo(1.5625, 2);
});
it('should be 1 at level 0', () => {
const result = computeStats({ manaOverflow: 0 }, emptyPrestige);
expect(result.clickMana).toBe(1);
});
});
describe('Quick Learner skill', () => {
it('should multiply study speed by compounding 1.10 per level', () => {
// 1.1^5 = 1.61051
const result = computeStats({ quickLearner: 5 }, emptyPrestige);
expect(result.studySpeed).toBeCloseTo(1.61051, 2);
});
it('should be ~2.5937 at max level 10', () => {
const result = computeStats({ quickLearner: 10 }, emptyPrestige);
expect(result.studySpeed).toBeCloseTo(2.593742, 2);
});
});
describe('Focused Mind skill', () => {
it('should multiply study cost by compounding 0.95 per level', () => {
// 0.95^2 = 0.9025
const result = computeStats({ focusedMind: 2 }, emptyPrestige);
expect(result.studyCostMult).toBeCloseTo(0.9025, 2);
});
});
describe('Meditation Focus skill', () => {
it('should multiply meditation efficiency at level 1', () => {
// BASE (1) * (1 + 1.5) = 2.5
const result = computeStats({ meditation: 1 }, emptyPrestige);
expect(result.meditationEfficiency).toBe(2.5);
});
});
describe('Deep Trance skill', () => {
it('should multiply meditation efficiency further', () => {
// meditation: 1 * (1+1.5) = 2.5, deepTrance: 2.5 * (1+1.8) = 7.0
const result = computeStats({ meditation: 1, deepTrance: 1 }, emptyPrestige);
expect(result.meditationEfficiency).toBe(7.0);
});
});
describe('Void Meditation skill', () => {
it('should multiply meditation efficiency to max', () => {
// 1 * 2.5 * 2.8 * 3.5 = 24.5
const result = computeStats({ meditation: 1, deepTrance: 1, voidMeditation: 1 }, emptyPrestige);
expect(result.meditationEfficiency).toBe(24.5);
});
});
describe('Combat skills', () => {
it('Arcane Fury should multiply damage', () => {
const result = computeStats({ arcaneFury: 5 }, emptyPrestige);
expect(result.damageMultiplier).toBeCloseTo(1.61051, 2);
});
it('Combat Training should add base damage', () => {
const result = computeStats({ combatTraining: 3 }, emptyPrestige);
expect(result.baseDamage).toBe(5 + 3 * 5);
});
it('Precision should add crit chance', () => {
const result = computeStats({ precision: 4 }, emptyPrestige);
expect(result.critChance).toBe(0.2);
});
it('Precision should cap at 1.0', () => {
const result = computeStats({ precision: 25 }, emptyPrestige);
expect(result.critChance).toBe(1.0);
});
it('Elemental Mastery should multiply elemental damage', () => {
// 1 * 1.15^4 = ~1.749
const result = computeStats({ elementalMastery: 4 }, emptyPrestige);
expect(result.elementalDamage).toBeCloseTo(1.749, 2);
});
it('Attack Speed should multiply attack speed (compounding)', () => {
// 1 * 0.9^3 = 0.729
const result = computeStats({ attackSpeed: 3 }, emptyPrestige);
expect(result.attackSpeed).toBeCloseTo(0.729, 2);
});
it('Armor Piercing should add armor pierce', () => {
const result = computeStats({ armorPiercing: 4 }, emptyPrestige);
expect(result.armorPierce).toBe(0.2);
});
});
describe('Enchanting skills', () => {
it('Enchanting should affect enchantCapacity and enchantSpeed', () => {
const result = computeStats({ enchanting: 5 }, emptyPrestige);
expect(result.enchantCapacity).toBe(100 + 5 * 10); // add 10 per level
expect(result.enchantSpeed).toBeCloseTo(0.9, 2); // multiply by 0.98^5
});
it('Essence Refining should multiply enchantPower', () => {
const result = computeStats({ enchanting: 4, essenceRefining: 1 }, emptyPrestige);
expect(result.enchantPower).toBeCloseTo(1.1, 2);
});
});
describe('Golemancy skills', () => {
it('Golem Mastery should multiply golemDamage', () => {
const result = computeStats({ golemMastery: 5 }, emptyPrestige);
expect(result.golemDamage).toBeCloseTo(1.61051, 2);
});
it('Golem Longevity should add golemDuration', () => {
const result = computeStats({ golemLongevity: 3 }, emptyPrestige);
expect(result.golemDuration).toBe(1 + 3);
});
it('Golem Efficiency should multiply attackSpeed', () => {
const result = computeStats({ golemEfficiency: 2 }, emptyPrestige);
expect(result.attackSpeed).toBeCloseTo(0.9025, 2); // 0.95^2
});
});
describe('Invocation / Pact skills', () => {
it('Invocation should multiply spellDamage', () => {
const result = computeStats({ invocation: 5 }, emptyPrestige);
expect(result.spellDamage).toBeCloseTo(1.27628, 2); // 1.05^5
});
it('Pact Mastery should multiply pactMultiplier', () => {
const result = computeStats({ pactMastery: 5 }, emptyPrestige);
expect(result.pactMultiplier).toBeCloseTo(1.61051, 2); // 1.1^5
});
it('Guardian Lore should multiply guardianDamage', () => {
const result = computeStats({ guardianLore: 3 }, emptyPrestige);
expect(result.guardianDamage).toBeCloseTo(1.728, 2); // 1.2^3
});
});
describe('Element capacity skills', () => {
it('Fire Mana Cap should increase fireCap', () => {
const result = computeStats({ fireManaCap: 5 }, emptyPrestige);
expect(result.fireCap).toBe(5 * 10);
});
it('Multiple element caps should contribute to elementCap', () => {
const result = computeStats({ fireManaCap: 3, waterManaCap: 2 }, emptyPrestige);
// fireCap=30, waterCap=20 -> elementCap = 10 + 30 + 20 = 60
expect(result.elementCap).toBe(60);
});
it('All base element caps should contribute', () => {
const result = computeStats({
fireManaCap: 10, waterManaCap: 10, airManaCap: 10, earthManaCap: 10,
}, emptyPrestige);
// Each adds 10*10=100, total 400 + base 10 = 410
expect(result.elementCap).toBe(410);
});
});
describe('Hybrid skills', () => {
it('Pact-Weaving should multiply enchantPower', () => {
const result = computeStats({ pactWeaving: 3 }, emptyPrestige);
expect(result.enchantPower).toBeCloseTo(1.331, 2); // 1.1^3
});
it('Guardian Constructs should affect golemDamage and golemDuration', () => {
const result = computeStats({ guardianConstructs: 2 }, emptyPrestige);
expect(result.golemDamage).toBeCloseTo(1.3225, 2); // 1.15^2
expect(result.golemDuration).toBe(1.5); // add 0.25*2=0.5, but cap floor is 1, so 1 + 0.5 = 1.5
});
it('Enchanted Golemancy should affect enchantPower and golemDamage', () => {
const result = computeStats({ enchantedGolemancy: 3 }, emptyPrestige);
expect(result.enchantPower).toBeCloseTo(1.157625, 2); // 1.05^3
expect(result.golemDamage).toBeCloseTo(1.331, 2); // 1.1^3
});
});
describe('Prestige upgrades', () => {
it('manaWell prestige should increase maxMana', () => {
const result = computeStats({}, { manaWell: 3 });
expect(result.maxMana).toBe(100 + 3 * 500);
});
it('manaFlow prestige should increase manaRegen', () => {
const result = computeStats({}, { manaFlow: 2 });
expect(result.manaRegen).toBe(2 + 2 * 0.5);
});
it('elementalAttune prestige should increase elementCap', () => {
const result = computeStats({}, { elementalAttune: 4 });
expect(result.elementCap).toBe(10 + 4 * 25);
});
it('pactBinding prestige should increase pactMultiplier', () => {
const result = computeStats({}, { pactBinding: 2 });
expect(result.pactMultiplier).toBe(1 + 2 * 0.1);
});
it('insightAmp prestige should multiply insightGain', () => {
const result = computeStats({}, { insightAmp: 2 });
expect(result.insightGain).toBe(1 + 1 * 0.25 * 2);
});
it('prestige should stack with skills', () => {
const result = computeStats({ manaWell: 5 }, { manaWell: 3 });
expect(result.maxMana).toBe(100 + 5 * 100 + 3 * 500);
});
});
describe('Skill stacking', () => {
it('should correctly stack multiple skills', () => {
const result = computeStats({
manaWell: 3,
manaFlow: 2,
manaTap: 1,
precision: 4,
}, emptyPrestige);
expect(result.maxMana).toBe(100 + 300);
expect(result.manaRegen).toBe(2 + 2);
expect(result.clickMana).toBe(1 + 1);
expect(result.critChance).toBe(0.2);
});
it('should handle all skills with effects at once without interference', () => {
// Only test skills that have effects defined (skip research skills with empty effects)
const allSkillsWithEffects: Record<string, number> = {};
for (const [id, def] of Object.entries(SKILLS_V2)) {
if (def.effects.length > 0) {
allSkillsWithEffects[id] = 1;
}
}
const result = computeStats(allSkillsWithEffects, emptyPrestige);
// Should not throw and should produce reasonable values
expect(result.maxMana).toBeGreaterThan(0);
expect(result.manaRegen).toBeGreaterThan(0);
expect(result.baseDamage).toBeGreaterThan(0);
expect(result.elementCap).toBeGreaterThanOrEqual(10);
});
});
describe('edge cases', () => {
it('should ignore negative levels (no effect applied)', () => {
const result = computeStats({}, emptyPrestige);
expect(result.maxMana).toBe(100);
});
it('should ignore unknown skill IDs', () => {
const result = computeStats({ unknownSkill: 5 } as any, emptyPrestige);
expect(result.maxMana).toBe(100);
});
it('should clamp critChance to 1.0', () => {
const result = computeStats({ precision: 100 } as any, emptyPrestige);
expect(result.critChance).toBe(1.0);
});
it('should clamp armorPierce to 1.0', () => {
const result = computeStats({ armorPiercing: 100 } as any, emptyPrestige);
expect(result.armorPierce).toBe(1.0);
});
it('should clamp attackSpeed minimum to 0.1', () => {
const result = computeStats({ attackSpeed: 100 } as any, emptyPrestige);
expect(result.attackSpeed).toBe(0.1);
});
it('should clamp maxMana minimum to 1', () => {
const result = computeStats({}, emptyPrestige);
expect(result.maxMana).toBeGreaterThanOrEqual(1);
});
it('should clamp baseDamage minimum to 1', () => {
const result = computeStats({}, emptyPrestige);
expect(result.baseDamage).toBeGreaterThanOrEqual(1);
});
});
});
describe('getBaseSkillId()', () => {
it('should strip _tN suffix for tiered skills', () => {
expect(getBaseSkillId('manaWell_t2')).toBe('manaWell');
expect(getBaseSkillId('manaWell_t5')).toBe('manaWell');
expect(getBaseSkillId('quickLearner_t3')).toBe('quickLearner');
});
it('should return same ID for non-tiered skills', () => {
expect(getBaseSkillId('manaWell')).toBe('manaWell');
expect(getBaseSkillId('fireManaCap')).toBe('fireManaCap');
});
});
describe('hasPrerequisites()', () => {
it('should return true when no prerequisites', () => {
expect(hasPrerequisites({}, undefined)).toBe(true);
expect(hasPrerequisites({}, {})).toBe(true);
});
it('should return true when prerequisites are met', () => {
expect(hasPrerequisites({ manaWell: 5 }, { manaWell: 3 })).toBe(true);
});
it('should return false when prerequisites are not met', () => {
expect(hasPrerequisites({ manaWell: 2 }, { manaWell: 3 })).toBe(false);
expect(hasPrerequisites({}, { manaWell: 1 })).toBe(false);
});
it('should check multiple prerequisites', () => {
expect(hasPrerequisites({ a: 2, b: 3 }, { a: 1, b: 2 })).toBe(true);
expect(hasPrerequisites({ a: 2, b: 1 }, { a: 1, b: 2 })).toBe(false);
});
});
describe('SKILLS_V2', () => {
it('should have manaWell defined', () => {
expect(SKILLS_V2.manaWell).toBeDefined();
expect(SKILLS_V2.manaWell.id).toBe('manaWell');
expect(SKILLS_V2.manaWell.maxLevel).toBe(10);
expect(SKILLS_V2.manaWell.effects).toHaveLength(1);
});
it('should have all core skills defined', () => {
const coreSkills = ['manaWell', 'manaFlow', 'quickLearner', 'focusedMind', 'meditation'];
for (const id of coreSkills) {
expect(SKILLS_V2[id]).toBeDefined();
expect(SKILLS_V2[id].effects.length).toBeGreaterThan(0);
}
});
it('should have correct manaWell effect', () => {
const effect = SKILLS_V2.manaWell.effects[0];
expect(effect.stat).toBe('maxMana');
expect(effect.mode).toBe('add');
expect(effect.valuePerLevel).toBe(100);
});
it('should have correct manaFlow effect', () => {
const effect = SKILLS_V2.manaFlow.effects[0];
expect(effect.stat).toBe('manaRegen');
expect(effect.mode).toBe('add');
expect(effect.valuePerLevel).toBe(1);
});
it('should handle prerequisite fields', () => {
expect(SKILLS_V2.manaOverflow.prerequisites).toEqual({ manaWell: 3 });
expect(SKILLS_V2.deepTrance.prerequisites).toEqual({ meditation: 1 });
expect(SKILLS_V2.manaTap.prerequisites).toBeUndefined();
});
it('should handle attunementRequired fields', () => {
expect(SKILLS_V2.enchanting.attunementRequired).toBe('enchanter');
expect(SKILLS_V2.invocation.attunementRequired).toBe('invoker');
expect(SKILLS_V2.golemMastery.attunementRequired).toBe('fabricator');
expect(SKILLS_V2.manaWell.attunementRequired).toBeUndefined();
});
});
console.log('✅ computeStats() and skill v2 tests defined.');
@@ -3,7 +3,7 @@
*/
import { describe, it, expect } from 'vitest';
import { calcDamage, getFloorMaxHP, getFloorElement } from '../index';
import { calcDamage, getFloorMaxHP, getFloorElement } from '@/lib/game/stores/index';
import type { GameState } from '../types';
function createMockState(overrides: Partial<GameState> = {}): GameState {
@@ -112,7 +112,7 @@ describe('Combat Calculations', () => {
describe('getFloorMaxHP', () => {
it('should return guardian HP for guardian floors', () => {
// Import GUARDIANS from constants
import { GUARDIANS } from '../../constants';
import { GUARDIANS } from '@/lib/game/constants';
expect(getFloorMaxHP(10)).toBe(GUARDIANS[10].hp);
expect(getFloorMaxHP(100)).toBe(GUARDIANS[100].hp);
});
@@ -3,7 +3,7 @@
*/
import { describe, it, expect } from 'vitest';
import { SKILLS_DEF, PRESTIGE_DEF, GUARDIANS } from '../../constants';
import { SKILLS_DEF, PRESTIGE_DEF, GUARDIANS } from '@/lib/game/constants';
describe('Skill Definitions', () => {
it('all skills should have valid categories', () => {
@@ -3,9 +3,9 @@
*/
import { describe, it, expect } from 'vitest';
import { computeMaxMana, computeRegen, computeClickMana, computeElementMax } from '../index';
import { computeMaxMana, computeRegen, computeClickMana, computeElementMax } from '@/lib/game/stores/index';
import type { GameState } from '../types';
import { ELEMENTS } from '../../constants';
import { ELEMENTS } from '@/lib/game/constants';
function createMockState(overrides: Partial<GameState> = {}): GameState {
const elements: Record<string, { current: number; max: number; unlocked: boolean }> = {};
@@ -3,8 +3,8 @@
*/
import { describe, it, expect } from 'vitest';
import { getMeditationBonus, calcInsight, getIncursionStrength } from '../index';
import { MAX_DAY, INCURSION_START_DAY } from '../../constants';
import { getMeditationBonus, calcInsight, getIncursionStrength } from '@/lib/game/stores/index';
import { MAX_DAY, INCURSION_START_DAY } from '@/lib/game/constants';
import type { GameState } from '../types';
function createMockState(overrides: Partial<GameState> = {}): GameState {
@@ -3,8 +3,8 @@
*/
import { describe, it, expect } from 'vitest';
import { canAffordSpellCost } from '../index';
import { rawCost, elemCost } from '../../constants';
import { canAffordSpellCost } from '@/lib/game/stores/index';
import { rawCost, elemCost } from '@/lib/game/constants';
describe('Spell Cost System', () => {
describe('rawCost', () => {
@@ -3,7 +3,7 @@
*/
import { describe, it, expect } from 'vitest';
import { getStudySpeedMultiplier, getStudyCostMultiplier } from '../index';
import { getStudySpeedMultiplier, getStudyCostMultiplier } from '@/lib/game/stores/index';
describe('Study Speed Functions', () => {
describe('getStudySpeedMultiplier', () => {
@@ -3,7 +3,7 @@
*/
import { describe, it, expect } from 'vitest';
import { fmt, fmtDec } from '../index';
import { fmt, fmtDec } from '@/lib/game/stores/index';
describe('Utility Functions', () => {
describe('fmt', () => {
@@ -3,7 +3,7 @@
*/
import { describe, it, expect, beforeEach } from 'vitest';
import { useCombatStore } from '../combatStore';
import { useCombatStore } from '@/lib/game/stores/combatStore';
// Reset stores before each test
beforeEach(() => {
@@ -3,7 +3,7 @@
*/
import { describe, it, expect, beforeEach } from 'vitest';
import { useManaStore } from '../manaStore';
import { useManaStore } from '@/lib/game/stores/manaStore';
// Reset stores before each test
beforeEach(() => {
@@ -3,7 +3,7 @@
*/
import { describe, it, expect, beforeEach } from 'vitest';
import { usePrestigeStore } from '../prestigeStore';
import { usePrestigeStore } from '@/lib/game/stores/prestigeStore';
import { useManaStore } from '../manaStore';
// Reset stores before each test
@@ -3,7 +3,7 @@
*/
import { describe, it, expect, beforeEach } from 'vitest';
import { useSkillStore } from '../skillStore';
import { useSkillStore } from '@/lib/game/stores/skillStore';
// Reset stores before each test
beforeEach(() => {
@@ -3,7 +3,7 @@
*/
import { describe, it, expect, beforeEach } from 'vitest';
import { useUIStore } from '../uiStore';
import { useUIStore } from '@/lib/game/stores/uiStore';
// Reset stores before each test
beforeEach(() => {
@@ -3,9 +3,9 @@
*/
import { describe, it, expect } from 'vitest';
import { calcDamage } from '../../utils';
import { calcDamage } from '@/lib/game/utils';
import type { GameState } from '../../types';
import { ELEMENTS } from '../../constants';
import { ELEMENTS } from '@/lib/game/constants';
function createMockState(overrides: Partial<GameState> = {}): GameState {
const elements: Record<string, { current: number; max: number; unlocked: boolean }> = {};
@@ -3,8 +3,8 @@
*/
import { describe, it, expect } from 'vitest';
import { getFloorMaxHP, getFloorElement } from '../../utils';
import { GUARDIANS } from '../../constants';
import { getFloorMaxHP, getFloorElement } from '@/lib/game/utils';
import { GUARDIANS } from '@/lib/game/constants';
describe('Floor Functions', () => {
describe('getFloorMaxHP', () => {
@@ -3,7 +3,7 @@
*/
import { describe, it, expect } from 'vitest';
import { fmt, fmtDec } from '../../utils';
import { fmt, fmtDec } from '@/lib/game/utils';
describe('Formatting Functions', () => {
describe('fmt (format number)', () => {
@@ -3,7 +3,7 @@
*/
import { describe, it, expect } from 'vitest';
import { GUARDIANS } from '../../constants';
import { GUARDIANS } from '@/lib/game/constants';
describe('Guardians', () => {
it('should have guardians on expected floors', () => {
@@ -3,8 +3,8 @@
*/
import { describe, it, expect } from 'vitest';
import { getIncursionStrength } from '../../utils';
import { MAX_DAY, INCURSION_START_DAY } from '../../constants';
import { getIncursionStrength } from '@/lib/game/utils';
import { MAX_DAY, INCURSION_START_DAY } from '@/lib/game/constants';
describe('Incursion Strength', () => {
describe('getIncursionStrength', () => {
@@ -3,9 +3,9 @@
*/
import { describe, it, expect } from 'vitest';
import { calcInsight } from '../../utils';
import { calcInsight } from '@/lib/game/utils';
import type { GameState } from '../../types';
import { ELEMENTS } from '../../constants';
import { ELEMENTS } from '@/lib/game/constants';
function createMockState(overrides: Partial<GameState> = {}): GameState {
const elements: Record<string, { current: number; max: number; unlocked: boolean }> = {};
@@ -3,9 +3,9 @@
*/
import { describe, it, expect } from 'vitest';
import { computeMaxMana, computeElementMax, computeRegen, computeClickMana } from '../../utils';
import { computeMaxMana, computeElementMax, computeRegen, computeClickMana } from '@/lib/game/utils';
import type { GameState } from '../../types';
import { ELEMENTS } from '../../constants';
import { ELEMENTS } from '@/lib/game/constants';
function createMockState(overrides: Partial<GameState> = {}): GameState {
const elements: Record<string, { current: number; max: number; unlocked: boolean }> = {};
@@ -104,22 +104,43 @@ describe('Mana Calculation Functions', () => {
const state = createMockState({ prestigeUpgrades: { manaWell: 3 } });
expect(computeMaxMana(state)).toBe(100 + 3 * 500);
});
it('should stack manaWell skill and prestige', () => {
const state = createMockState({
skills: { manaWell: 5 },
prestigeUpgrades: { manaWell: 2 },
});
expect(computeMaxMana(state)).toBe(100 + 5 * 100 + 2 * 500);
});
});
describe('computeRegen', () => {
it('should return base regen with no upgrades', () => {
// Base regen is 2, but computeRegen now includes attunement regen
// Enchanter (active, level 1) adds rawManaRegen * 1.5^0 = rawManaRegen
// Default enchanter rawManaRegen is 0.5, so base with enchanter = 2 + 0.5 = 2.5
it('should return base regen with no upgrades (includes attunement regen)', () => {
const state = createMockState();
expect(computeRegen(state)).toBe(2);
// Base 2 + enchanter regen (0.5 * 1 = 0.5) = 2.5
expect(computeRegen(state)).toBeCloseTo(2.5, 1);
});
it('should add regen from manaFlow skill', () => {
const state = createMockState({ skills: { manaFlow: 5 } });
expect(computeRegen(state)).toBe(2 + 5 * 1);
// Base 2 + manaFlow 5 + enchanter 0.5 = 7.5
expect(computeRegen(state)).toBeCloseTo(2 + 5 * 1 + 0.5, 1);
});
it('should add regen from manaSpring skill', () => {
const state = createMockState({ skills: { manaSpring: 1 } });
expect(computeRegen(state)).toBe(2 + 2);
// Base 2 + manaSpring 2 + enchanter 0.5 = 4.5
expect(computeRegen(state)).toBeCloseTo(2 + 2 + 0.5, 1);
});
it('should multiply by temporal echo prestige', () => {
const state = createMockState({ prestigeUpgrades: { temporalEcho: 2 } });
// (2 + 0.5 enchanter) * 1.2 = 3.0
expect(computeRegen(state)).toBeCloseTo(2.5 * 1.2, 1);
});
});
@@ -138,6 +159,28 @@ describe('Mana Calculation Functions', () => {
const state = createMockState({ skills: { manaSurge: 1 } });
expect(computeClickMana(state)).toBe(1 + 3);
});
it('should stack manaTap and manaSurge', () => {
const state = createMockState({ skills: { manaTap: 1, manaSurge: 1 } });
expect(computeClickMana(state)).toBe(1 + 1 + 3);
});
});
describe('computeElementMax', () => {
it('should return base element cap with no upgrades', () => {
const state = createMockState();
expect(computeElementMax(state)).toBe(10);
});
it('should add cap from elemAttune skill', () => {
const state = createMockState({ skills: { elemAttune: 5 } });
expect(computeElementMax(state)).toBe(10 + 5 * 50);
});
it('should add cap from prestige upgrades', () => {
const state = createMockState({ prestigeUpgrades: { elementalAttune: 3 } });
expect(computeElementMax(state)).toBe(10 + 3 * 25);
});
});
});
@@ -3,7 +3,7 @@
*/
import { describe, it, expect } from 'vitest';
import { getMeditationBonus } from '../../utils';
import { getMeditationBonus } from '@/lib/game/utils';
describe('Meditation Bonus', () => {
describe('getMeditationBonus', () => {
@@ -3,7 +3,7 @@
*/
import { describe, it, expect } from 'vitest';
import { PRESTIGE_DEF } from '../../constants';
import { PRESTIGE_DEF } from '@/lib/game/constants';
describe('Prestige Upgrades', () => {
it('should have prestige upgrades with valid costs', () => {
@@ -3,7 +3,7 @@
*/
import { describe, it, expect } from 'vitest';
import { SKILLS_DEF } from '../../constants';
import { SKILLS_DEF } from '@/lib/game/constants';
describe('Skill Definitions', () => {
it('should have skills with valid categories', () => {
@@ -3,8 +3,8 @@
*/
import { describe, it, expect } from 'vitest';
import { canAffordSpellCost } from '../../utils';
import { rawCost, elemCost } from '../../constants';
import { canAffordSpellCost } from '@/lib/game/utils';
import { rawCost, elemCost } from '@/lib/game/constants';
describe('Spell Cost System', () => {
describe('rawCost', () => {
@@ -3,7 +3,7 @@
*/
import { describe, it, expect } from 'vitest';
import { SPELLS_DEF } from '../../constants';
import { SPELLS_DEF } from '@/lib/game/constants';
describe('Spell Definitions', () => {
it('should have manaBolt as a basic spell', () => {
@@ -3,7 +3,7 @@
*/
import { describe, it, expect } from 'vitest';
import { getStudySpeedMultiplier, getStudyCostMultiplier } from '../../utils';
import { getStudySpeedMultiplier, getStudyCostMultiplier } from '@/lib/game/constants';
describe('Study Speed Functions', () => {
describe('getStudySpeedMultiplier', () => {
@@ -15,6 +15,10 @@ describe('Study Speed Functions', () => {
expect(getStudySpeedMultiplier({ quickLearner: 1 })).toBe(1.1);
expect(getStudySpeedMultiplier({ quickLearner: 5 })).toBe(1.5);
});
it('should increase by 10% per level up to max', () => {
expect(getStudySpeedMultiplier({ quickLearner: 10 })).toBe(2.0);
});
});
describe('getStudyCostMultiplier', () => {
@@ -26,6 +30,10 @@ describe('Study Speed Functions', () => {
expect(getStudyCostMultiplier({ focusedMind: 1 })).toBe(0.95);
expect(getStudyCostMultiplier({ focusedMind: 5 })).toBe(0.75);
});
it('should decrease by 5% per level up to max', () => {
expect(getStudyCostMultiplier({ focusedMind: 10 })).toBe(0.5);
});
});
});
+53
View File
@@ -47,6 +47,15 @@ export interface CraftingState {
materials: Record<string, number>;
blueprints: string[];
};
// Enchantment selection state (single source of truth for enchanting UI)
enchantmentSelection: {
selectedEquipmentType: string | null;
selectedEffects: DesignEffect[];
designName: string;
selectedDesign: string | null;
selectedEquipmentInstance: string | null;
};
}
export interface CraftingActions {
@@ -86,6 +95,14 @@ export interface CraftingActions {
// Equipment crafting actions
startCraftingEquipment: (blueprintId: string) => boolean;
cancelEquipmentCrafting: () => void;
// Enchantment selection actions (store as source of truth)
setSelectedEquipmentType: (type: string | null) => void;
setSelectedEffects: (effects: DesignEffect[]) => void;
setDesignName: (name: string) => void;
setSelectedDesign: (id: string | null) => void;
setSelectedEquipmentInstance: (id: string | null) => void;
resetEnchantmentSelection: () => void;
}
export type CraftingStore = CraftingState & CraftingActions;
@@ -108,6 +125,13 @@ export const useCraftingStore = create<CraftingStore>()(
materials: {},
blueprints: [],
},
enchantmentSelection: {
selectedEquipmentType: null,
selectedEffects: [],
designName: '',
selectedDesign: null,
selectedEquipmentInstance: null,
},
// Actions
setDesignProgress: (progress) => set({ designProgress: progress }),
@@ -311,6 +335,34 @@ export const useCraftingStore = create<CraftingStore>()(
useUIStore.getState().addLog(cancelResult.logMessage);
},
// Enchantment selection actions
setSelectedEquipmentType: (type) => {
set((s) => ({ enchantmentSelection: { ...s.enchantmentSelection, selectedEquipmentType: type }}));
},
setSelectedEffects: (effects) => {
set((s) => ({ enchantmentSelection: { ...s.enchantmentSelection, selectedEffects: effects } }));
},
setDesignName: (name) => {
set((s) => ({ enchantmentSelection: { ...s.enchantmentSelection, designName: name } }));
},
setSelectedDesign: (id) => {
set((s) => ({ enchantmentSelection: { ...s.enchantmentSelection, selectedDesign: id } }));
},
setSelectedEquipmentInstance: (id) => {
set((s) => ({ enchantmentSelection: { ...s.enchantmentSelection, selectedEquipmentInstance: id } }));
},
resetEnchantmentSelection: () => {
set((s) => ({
enchantmentSelection: {
selectedEquipmentType: null,
selectedEffects: [],
designName: '',
selectedDesign: null,
selectedEquipmentInstance: null,
},
}));
},
// Loot inventory actions
deleteMaterial: (materialId: string, amount: number) => {
set((state) => {
@@ -366,6 +418,7 @@ export const useCraftingStore = create<CraftingStore>()(
equipmentInstances: state.equipmentInstances,
equippedInstances: state.equippedInstances,
lootInventory: state.lootInventory,
enchantmentSelection: state.enchantmentSelection,
}),
}
)