refactor: wire design-actions.ts into craftingStore.ts, remove dead code
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m23s

- Delegates startDesigningEnchantment, cancelDesign, saveDesign, deleteDesign to design-actions.ts
- Passes enchanter level from attunement store to startDesigningEnchantment (fixes hardcoded 0)
- Removes orphaned disenchant-actions.ts (preparation phase handles recovery inline)
- Removes unused imports (CraftingUtils, CraftingDesign, useUIStore)
- Updates regression test to check design-actions.ts instead of craftingStore.ts
This commit is contained in:
2026-06-15 12:12:56 +02:00
parent a45d38a9c9
commit e76528b449
7 changed files with 35 additions and 126 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
# Circular Dependencies # Circular Dependencies
Generated: 2026-06-14T21:49:16.203Z Generated: 2026-06-15T08:59:04.077Z
Found: 7 circular chain(s) — these MUST be fixed before modifying involved files. Found: 7 circular chain(s) — these MUST be fixed before modifying involved files.
1. 1) data/guardian-encounters.ts > data/guardian-procedural.ts 1. 1) data/guardian-encounters.ts > data/guardian-procedural.ts
+2 -1
View File
@@ -1,6 +1,6 @@
{ {
"_meta": { "_meta": {
"generated": "2026-06-14T21:49:13.932Z", "generated": "2026-06-15T08:59:01.387Z",
"description": "Import dependency graph for src/lib/game. Keys are files, values are arrays of files they import.", "description": "Import dependency graph for src/lib/game. Keys are files, values are arrays of files they import.",
"usage": "To find what a file affects, search for its path in the VALUES. To find what a file depends on, look at its KEY entry." "usage": "To find what a file affects, search for its path in the VALUES. To find what a file depends on, look at its KEY entry."
}, },
@@ -542,6 +542,7 @@
"stores/attunementStore.ts", "stores/attunementStore.ts",
"stores/combatStore.ts", "stores/combatStore.ts",
"stores/craftingStore.ts", "stores/craftingStore.ts",
"stores/discipline-slice.ts",
"stores/gameStore.ts", "stores/gameStore.ts",
"stores/manaStore.ts", "stores/manaStore.ts",
"stores/prestigeStore.ts", "stores/prestigeStore.ts",
-1
View File
@@ -293,7 +293,6 @@ Mana-Loop/
│ │ │ │ ├── crafting-equipment-actions.ts │ │ │ │ ├── crafting-equipment-actions.ts
│ │ │ │ ├── crafting-material-actions.ts │ │ │ │ ├── crafting-material-actions.ts
│ │ │ │ ├── design-actions.ts │ │ │ │ ├── design-actions.ts
│ │ │ │ ├── disenchant-actions.ts
│ │ │ │ ├── equipment-actions.ts │ │ │ │ ├── equipment-actions.ts
│ │ │ │ ├── index.ts │ │ │ │ ├── index.ts
│ │ │ │ └── preparation-actions.ts │ │ │ │ └── preparation-actions.ts
@@ -292,14 +292,15 @@ describe('Issue 79 — startDesigningEnchantment slot 2', () => {
expect(useCraftingStore.getState().designProgress2).not.toBeNull(); expect(useCraftingStore.getState().designProgress2).not.toBeNull();
}); });
it('store code has else-if branch for designProgress2', () => { it('design-actions has else-if branch for designProgress2', () => {
// Verify the source code contains the fix for issue 79 // Verify the source code contains the fix for issue 79
// (design logic was moved to design-actions.ts during refactor)
const source = readFileSync( const source = readFileSync(
'/home/user/repos/Mana-Loop/src/lib/game/stores/craftingStore.ts', '/home/user/repos/Mana-Loop/src/lib/game/crafting-actions/design-actions.ts',
'utf-8' 'utf-8'
); );
// The fix adds an else-if branch for designProgress2 // The fix adds an else-if branch for designProgress2
expect(source).toContain('else if (!state.designProgress2)'); expect(source).toContain('else if (hasEnchantMastery && !state.designProgress2)');
}); });
it('should return false when both slots are occupied', async () => { it('should return false when both slots are occupied', async () => {
@@ -1,41 +0,0 @@
// ─── Disenchanting Actions ─────────────────────────────────────────────────
import type { CraftingState } from '../stores/craftingStore.types';
import { useManaStore } from '../stores/manaStore';
export function disenchantEquipment(
instanceId: string,
get: () => CraftingState,
set: (fn: (state: CraftingState) => Partial<CraftingState>) => void
) {
const state = get();
const instance = state.equipmentInstances[instanceId];
if (!instance || instance.enchantments.length === 0) return;
const disenchantLevel = 0;
const recoveryRate = 0.1 + disenchantLevel * 0.2;
let totalRecovered = 0;
for (const ench of instance.enchantments) {
totalRecovered += Math.floor(ench.actualCost * recoveryRate);
}
// Credit recovered mana to raw mana pool
if (totalRecovered > 0) {
useManaStore.setState((s) => ({ rawMana: s.rawMana + totalRecovered }));
}
set((s) => ({
equipmentInstances: {
...s.equipmentInstances,
[instanceId]: {
...instance,
enchantments: [],
usedCapacity: 0,
rarity: 'common',
tags: [...(instance.tags || []), 'Ready for Enchantment'],
},
},
log: [`✨ Disenchanted ${instance.name}, recovered ${totalRecovered} mana.`],
}));
}
-1
View File
@@ -6,6 +6,5 @@ export { createEquipmentInstance, equipItem, unequipItem, deleteEquipmentInstanc
export { startDesigningEnchantment, cancelDesign, saveDesign, deleteDesign } from './design-actions'; export { startDesigningEnchantment, cancelDesign, saveDesign, deleteDesign } from './design-actions';
export { startPreparing, cancelPreparation } from './preparation-actions'; export { startPreparing, cancelPreparation } from './preparation-actions';
export { startApplying, pauseApplication, resumeApplication, cancelApplication } from './application-actions'; export { startApplying, pauseApplication, resumeApplication, cancelApplication } from './application-actions';
export { disenchantEquipment } from './disenchant-actions';
export { startCraftingEquipment, cancelEquipmentCrafting, deleteMaterial } from './crafting-equipment-actions'; export { startCraftingEquipment, cancelEquipmentCrafting, deleteMaterial } from './crafting-equipment-actions';
export { getEquipmentSpells, getEquipmentEffects, getAvailableCapacity } from './computed-getters'; export { getEquipmentSpells, getEquipmentEffects, getAvailableCapacity } from './computed-getters';
+24 -74
View File
@@ -3,24 +3,21 @@ import { create } from 'zustand';
import { persist } from 'zustand/middleware'; import { persist } from 'zustand/middleware';
import type { EquipmentSlot } from '../types/equipmentSlot'; import type { EquipmentSlot } from '../types/equipmentSlot';
import type { CraftingStore, CraftingState } from './craftingStore.types'; import type { CraftingStore, CraftingState } from './craftingStore.types';
import * as CraftingUtils from '../crafting-utils';
import * as CraftingDesign from '../crafting-design';
import { useManaStore } from './manaStore'; import { useManaStore } from './manaStore';
import { useCombatStore } from './combatStore'; import { useCombatStore } from './combatStore';
import { useUIStore } from './uiStore';
import { getEnchantingEfficiencyBonus } from '../effects/discipline-effects';
import { hasSpecial, SPECIAL_EFFECTS } from '../effects/special-effects';
import * as ApplicationActions from '../crafting-actions/application-actions'; import * as ApplicationActions from '../crafting-actions/application-actions';
import * as PreparationActions from '../crafting-actions/preparation-actions'; import * as PreparationActions from '../crafting-actions/preparation-actions';
import { equipItem as equipItemAction, unequipItem as unequipItemAction } from '../crafting-actions/equipment-actions'; import { equipItem as equipItemAction, unequipItem as unequipItemAction } from '../crafting-actions/equipment-actions';
import { startDesigningEnchantment as startDesignAction, cancelDesign as cancelDesignAction, saveDesign as saveDesignAction, deleteDesign as deleteDesignAction } from '../crafting-actions/design-actions';
import { useAttunementStore } from './attunementStore';
import { ErrorCode } from '../utils/result'; import { ErrorCode } from '../utils/result';
import { createSafeStorage } from '../utils/safe-persist'; import { createSafeStorage } from '../utils/safe-persist';
import { createDefaultCraftingState } from './crafting-initial-state'; import { createDefaultCraftingState } from './crafting-initial-state';
import { craftMaterial as craftMaterialAction } from '../crafting-actions/crafting-material-actions'; import { craftMaterial as craftMaterialAction } from '../crafting-actions/crafting-material-actions';
import { processEquipmentCraftingTick } from './crafting-equipment-tick'; import { processEquipmentCraftingTick } from './crafting-equipment-tick';
import { startCraftingEquipment, cancelEquipmentCrafting, startFabricatorCrafting } from './pipelines/equipment-crafting'; import { startCraftingEquipment, cancelEquipmentCrafting, startFabricatorCrafting } from './pipelines/equipment-crafting';
import { computeEffects } from '../effects/upgrade-effects';
export const useCraftingStore = create<CraftingStore>()( export const useCraftingStore = create<CraftingStore>()(
persist( persist(
@@ -36,100 +33,53 @@ export const useCraftingStore = create<CraftingStore>()(
setApplicationProgress: (progress) => set({ applicationProgress: progress }), setApplicationProgress: (progress) => set({ applicationProgress: progress }),
setEquipmentCraftingProgress: (progress) => set({ equipmentCraftingProgress: progress }), setEquipmentCraftingProgress: (progress) => set({ equipmentCraftingProgress: progress }),
// Enchantment design actions // Enchantment design actions — delegated to design-actions.ts
startDesigningEnchantment: (name, equipmentTypeId, effects) => { startDesigningEnchantment: (name, equipmentTypeId, effects) => {
const state = get(); // crafting state const attunements = useAttunementStore.getState();
const efficiencyBonus = getEnchantingEfficiencyBonus(); const enchanterLevel = attunements.attunements?.enchanter?.level ?? 0;
const success = startDesignAction(
const validation = CraftingDesign.validateDesignEffects(effects, equipmentTypeId, 0, state.unlockedEffects);
if (!validation.valid) return false;
const equipType = CraftingUtils.getEquipmentType(equipmentTypeId);
if (!equipType) return false;
const totalCapacityCost = CraftingDesign.calculateDesignCapacityCost(effects, efficiencyBonus);
if (totalCapacityCost > equipType.baseCapacity) return false;
let updates: Partial<CraftingState> = {};
if (!state.designProgress) {
updates = {
designProgress: {
designId: CraftingUtils.generateDesignId(),
progress: 0,
required: CraftingDesign.calculateDesignTime(effects),
name, name,
equipmentType: equipmentTypeId, equipmentTypeId,
effects, effects,
}, { skills: { enchanting: enchanterLevel } },
}; get,
// Update currentAction in combatStore set as unknown as (fn: (state: CraftingState) => Partial<CraftingState>) => void
);
if (success) {
useCombatStore.setState({ currentAction: 'design' }); useCombatStore.setState({ currentAction: 'design' });
} else if (!state.designProgress2) {
// Check for Enchant Mastery before allowing second design slot
const computedEffects = computeEffects();
const hasEnchantMastery = hasSpecial(computedEffects, SPECIAL_EFFECTS.ENCHANT_MASTERY);
if (!hasEnchantMastery) return false;
updates = {
designProgress2: {
designId: CraftingUtils.generateDesignId(),
progress: 0,
required: CraftingDesign.calculateDesignTime(effects),
name,
equipmentType: equipmentTypeId,
effects,
},
};
useCombatStore.setState({ currentAction: 'design' });
} else {
return false;
} }
return success;
set(updates);
return true;
}, },
cancelDesign: (slot?: 1 | 2) => { cancelDesign: (slot?: 1 | 2) => {
const state = get();
if (slot === 2) { if (slot === 2) {
const state = get();
if (state.designProgress2) { if (state.designProgress2) {
set({ designProgress2: null }); set({ designProgress2: null });
} }
} else if (slot === 1) { } else if (slot === 1) {
const state = get();
if (state.designProgress) { if (state.designProgress) {
set({ designProgress: null }); set({ designProgress: null });
useCombatStore.setState({ currentAction: 'meditate' }); useCombatStore.setState({ currentAction: 'meditate' });
} }
} else if (state.designProgress) { } else {
set({ designProgress: null }); cancelDesignAction(get, set as unknown as (fn: (state: CraftingState) => Partial<CraftingState>) => void);
const state = get();
// If both slots are now null, reset to meditate
if (!state.designProgress && !state.designProgress2) {
useCombatStore.setState({ currentAction: 'meditate' }); useCombatStore.setState({ currentAction: 'meditate' });
} else if (state.designProgress2) { }
set({ designProgress2: null });
} }
}, },
deleteDesign: (designId) => { deleteDesign: (designId) => {
set((state) => ({ deleteDesignAction(designId, set as unknown as (fn: (state: CraftingState) => Partial<CraftingState>) => void);
enchantmentDesigns: state.enchantmentDesigns.filter(d => d.id !== designId),
}));
}, },
// Enchantment design save
saveDesign: (design) => { saveDesign: (design) => {
const state = get(); saveDesignAction(design, get, set as unknown as (fn: (state: CraftingState) => Partial<CraftingState>) => void);
if (state.designProgress2 && state.designProgress2.designId === design.id) {
set((s) => ({
enchantmentDesigns: [...s.enchantmentDesigns, design],
designProgress2: null,
}));
} else {
set((s) => ({
enchantmentDesigns: [...s.enchantmentDesigns, design],
designProgress: null,
}));
useCombatStore.setState({ currentAction: 'meditate' }); useCombatStore.setState({ currentAction: 'meditate' });
}
}, },
// Enchantment application actions // Enchantment application actions