fix: resolve enchanting spec vs code discrepancies (issue #324)
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m19s

- D2: Move spell_iceShard to basic-spells.ts at cost 75 (canonical), remove duplicate from frost-spells.ts
- D7: disenchantEquipment now adds 'Ready for Enchantment' tag and resets rarity to 'common'
- D8: disenchantEquipment now credits recovered mana to raw mana pool
- D14: Wire enchantPower stat from discipline effects into efficiencyBonus via new getEnchantingEfficiencyBonus() helper
- D3: Fix spec file reference from crafting-attunements.ts to data/attunements.ts
- D1/D6: Add missing metalSpellFocus to spec capacity table
This commit is contained in:
2026-06-09 09:33:30 +02:00
parent b0e553c290
commit e45c206321
10 changed files with 46 additions and 16 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
# Circular Dependencies # Circular Dependencies
Generated: 2026-06-08T21:50:12.078Z Generated: 2026-06-08T23:26:23.477Z
Found: 2 circular chain(s) — these MUST be fixed before modifying involved files. Found: 2 circular chain(s) — these MUST be fixed before modifying involved files.
1. 1) stores/golem-combat-actions.ts > stores/golem-combat-helpers.ts 1. 1) stores/golem-combat-actions.ts > stores/golem-combat-helpers.ts
+2 -1
View File
@@ -1,6 +1,6 @@
{ {
"_meta": { "_meta": {
"generated": "2026-06-08T21:50:10.004Z", "generated": "2026-06-08T23:26:19.136Z",
"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."
}, },
@@ -720,6 +720,7 @@
"stores/golem-combat-actions.ts": [ "stores/golem-combat-actions.ts": [
"constants.ts", "constants.ts",
"data/golems/index.ts", "data/golems/index.ts",
"data/golems/types.ts",
"data/golems/utils.ts", "data/golems/utils.ts",
"stores/golem-combat-helpers.ts", "stores/golem-combat-helpers.ts",
"types.ts" "types.ts"
@@ -281,6 +281,7 @@ Same formula as Prepare stage (§4.5).
| **Catalyst** | basicCatalyst | 40 | | **Catalyst** | basicCatalyst | 40 |
| | fireCatalyst | 55 | | | fireCatalyst | 55 |
| | voidCatalyst | 75 | | | voidCatalyst | 75 |
| | metalSpellFocus | 50 |
| **Sword** | ironBlade | 30 | | **Sword** | ironBlade | 30 |
| | steelBlade | 40 | | | steelBlade | 40 |
| | crystalBlade | 55 | | | crystalBlade | 55 |
@@ -643,7 +644,7 @@ capacity). It affects:
| `src/lib/game/crafting-prep.ts` | Prepare stage logic, disenchant recovery | | `src/lib/game/crafting-prep.ts` | Prepare stage logic, disenchant recovery |
| `src/lib/game/crafting-apply.ts` | Apply stage logic, free enchant, Pure Essence | | `src/lib/game/crafting-apply.ts` | Apply stage logic, free enchant, Pure Essence |
| `src/lib/game/crafting-utils.ts` | Shared utilities, capacity cost, cancellation refund | | `src/lib/game/crafting-utils.ts` | Shared utilities, capacity cost, cancellation refund |
| `src/lib/game/crafting-attunements.ts` | Attunement-crafting integration, enchanting XP | | `src/lib/game/data/attunements.ts` | Attunement-crafting integration, enchanting XP |
| `src/lib/game/data/enchantments/` | All enchantment effect definitions (7 categories) | | `src/lib/game/data/enchantments/` | All enchantment effect definitions (7 categories) |
| `src/lib/game/crafting-actions/design-actions.ts` | Design stage store actions | | `src/lib/game/crafting-actions/design-actions.ts` | Design stage store actions |
| `src/lib/game/crafting-actions/preparation-actions.ts` | Prepare stage store actions | | `src/lib/game/crafting-actions/preparation-actions.ts` | Prepare stage store actions |
@@ -5,6 +5,7 @@ import type { EnchantmentDesign, DesignEffect } from '../types';
import * as CraftingUtils from '../crafting-utils'; import * as CraftingUtils from '../crafting-utils';
import * as CraftingDesign from '../crafting-design'; import * as CraftingDesign from '../crafting-design';
import { computeEffects } from '../effects/upgrade-effects'; import { computeEffects } from '../effects/upgrade-effects';
import { getEnchantingEfficiencyBonus } from '../effects/discipline-effects';
import { hasSpecial, SPECIAL_EFFECTS } from '../effects/special-effects'; import { hasSpecial, SPECIAL_EFFECTS } from '../effects/special-effects';
export interface DesignActionsParams { export interface DesignActionsParams {
@@ -32,7 +33,8 @@ export function startDesigningEnchantment(
const equipType = CraftingUtils.getEquipmentType(equipmentTypeId); const equipType = CraftingUtils.getEquipmentType(equipmentTypeId);
if (!equipType) return false; if (!equipType) return false;
const totalCapacityCost = CraftingDesign.calculateDesignCapacityCost(effects, 0); const efficiencyBonus = getEnchantingEfficiencyBonus();
const totalCapacityCost = CraftingDesign.calculateDesignCapacityCost(effects, efficiencyBonus);
if (totalCapacityCost > equipType.baseCapacity) { if (totalCapacityCost > equipType.baseCapacity) {
return false; return false;
@@ -1,6 +1,7 @@
// ─── Disenchanting Actions ───────────────────────────────────────────────── // ─── Disenchanting Actions ─────────────────────────────────────────────────
import type { CraftingState } from '../stores/craftingStore.types'; import type { CraftingState } from '../stores/craftingStore.types';
import { useManaStore } from '../stores/manaStore';
export function disenchantEquipment( export function disenchantEquipment(
instanceId: string, instanceId: string,
@@ -19,6 +20,11 @@ export function disenchantEquipment(
totalRecovered += Math.floor(ench.actualCost * recoveryRate); 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) => ({ set((s) => ({
equipmentInstances: { equipmentInstances: {
...s.equipmentInstances, ...s.equipmentInstances,
@@ -26,6 +32,8 @@ export function disenchantEquipment(
...instance, ...instance,
enchantments: [], enchantments: [],
usedCapacity: 0, usedCapacity: 0,
rarity: 'common',
tags: [...(instance.tags || []), 'Ready for Enchantment'],
}, },
}, },
log: [`✨ Disenchanted ${instance.name}, recovered ${totalRecovered} mana.`], log: [`✨ Disenchanted ${instance.name}, recovered ${totalRecovered} mana.`],
@@ -159,4 +159,14 @@ export const BASIC_SPELL_EFFECTS: Record<string, EnchantmentEffectDef> = {
allowedEquipmentCategories: ALL_CASTER, allowedEquipmentCategories: ALL_CASTER,
effect: { type: 'spell', spellId: 'darkPulse' } effect: { type: 'spell', spellId: 'darkPulse' }
}, },
spell_iceShard: {
id: 'spell_iceShard',
name: 'Ice Shard',
description: 'Grants the ability to cast Ice Shard (14 water/ice damage)',
category: 'spell',
baseCapacityCost: 75,
maxStacks: 1,
allowedEquipmentCategories: ALL_CASTER,
effect: { type: 'spell', spellId: 'iceShard' }
},
}; };
@@ -15,16 +15,6 @@ export const FROST_SPELL_EFFECTS: Record<string, EnchantmentEffectDef> = {
allowedEquipmentCategories: ALL_CASTER, allowedEquipmentCategories: ALL_CASTER,
effect: { type: 'spell', spellId: 'frostBite' } effect: { type: 'spell', spellId: 'frostBite' }
}, },
spell_iceShard: {
id: 'spell_iceShard',
name: 'Ice Shard',
description: 'Grants the ability to cast Ice Shard (18 frost damage, freeze)',
category: 'spell',
baseCapacityCost: 95,
maxStacks: 1,
allowedEquipmentCategories: ALL_CASTER,
effect: { type: 'spell', spellId: 'iceShard' }
},
spell_frostNova: { spell_frostNova: {
id: 'spell_frostNova', id: 'spell_frostNova',
name: 'Frost Nova', name: 'Frost Nova',
@@ -153,3 +153,18 @@ export function computeDisciplineEffects(_state?: DisciplineStoreState): Discipl
return { bonuses, multipliers, specials, meditationCapBonus, conversions }; return { bonuses, multipliers, specials, meditationCapBonus, conversions };
} }
/**
* Compute the enchanting efficiency bonus from discipline effects.
* The enchantPower stat from active disciplines is converted to an efficiency bonus
* that reduces enchantment capacity costs.
*
* Formula: efficiencyBonus = enchantPower / 100 (capped at 0.9)
*
* Example: enchantPower = 15 → efficiencyBonus = 0.15 (15% cost reduction)
*/
export function getEnchantingEfficiencyBonus(): number {
const effects = computeDisciplineEffects();
const enchantPower = effects.bonuses.enchantPower || 0;
return Math.min(0.9, enchantPower / 100);
}
+3 -1
View File
@@ -9,6 +9,7 @@ import { useManaStore } from './manaStore';
import { useCombatStore } from './combatStore'; import { useCombatStore } from './combatStore';
import { useUIStore } from './uiStore'; import { useUIStore } from './uiStore';
import { getEnchantingEfficiencyBonus } from '../effects/discipline-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';
@@ -36,6 +37,7 @@ export const useCraftingStore = create<CraftingStore>()(
// Enchantment design actions // Enchantment design actions
startDesigningEnchantment: (name, equipmentTypeId, effects) => { startDesigningEnchantment: (name, equipmentTypeId, effects) => {
const state = get(); // crafting state const state = get(); // crafting state
const efficiencyBonus = getEnchantingEfficiencyBonus();
const validation = CraftingDesign.validateDesignEffects(effects, equipmentTypeId, 0, state.unlockedEffects); const validation = CraftingDesign.validateDesignEffects(effects, equipmentTypeId, 0, state.unlockedEffects);
if (!validation.valid) return false; if (!validation.valid) return false;
@@ -43,7 +45,7 @@ export const useCraftingStore = create<CraftingStore>()(
const equipType = CraftingUtils.getEquipmentType(equipmentTypeId); const equipType = CraftingUtils.getEquipmentType(equipmentTypeId);
if (!equipType) return false; if (!equipType) return false;
const totalCapacityCost = CraftingDesign.calculateDesignCapacityCost(effects, 0); const totalCapacityCost = CraftingDesign.calculateDesignCapacityCost(effects, efficiencyBonus);
if (totalCapacityCost > equipType.baseCapacity) return false; if (totalCapacityCost > equipType.baseCapacity) return false;
@@ -8,6 +8,7 @@ import { calculateDesignProgress, createCompletedDesignFromProgress } from '../.
import { calculatePreparationTick, completePreparation } from '../../crafting-prep'; import { calculatePreparationTick, completePreparation } from '../../crafting-prep';
import { calculateApplicationTick, applyEnchantments, updateEnchanterAttunement } from '../../crafting-apply'; import { calculateApplicationTick, applyEnchantments, updateEnchanterAttunement } from '../../crafting-apply';
import { useCraftingStore } from '../craftingStore'; import { useCraftingStore } from '../craftingStore';
import { getEnchantingEfficiencyBonus } from '../../effects/discipline-effects';
import type { TickContext, TickWrites } from '../tick-pipeline'; import type { TickContext, TickWrites } from '../tick-pipeline';
interface EnchantingTickParams { interface EnchantingTickParams {
@@ -51,7 +52,7 @@ export function processEnchantingTicks(
effects: activeProgress.effects, effects: activeProgress.effects,
required: activeProgress.required, required: activeProgress.required,
}, },
0, getEnchantingEfficiencyBonus(),
); );
useCraftingStore.getState().saveDesign(completedDesign); useCraftingStore.getState().saveDesign(completedDesign);
addLog('Design "' + completedDesign.name + '" completed!'); addLog('Design "' + completedDesign.name + '" completed!');