From b3b13b6a55f164ef340522f8e60403f0f539f9a2 Mon Sep 17 00:00:00 2001 From: n8n-gitea Date: Mon, 8 Jun 2026 11:52:07 +0200 Subject: [PATCH] fix: Hasty Enchanter now applies correct 25% design speed bonus (was 6.25%) --- docs/circular-deps.txt | 2 +- docs/dependency-graph.json | 2 +- docs/project-structure.txt | 1 + .../game/__tests__/hasty-enchanter.test.ts | 108 ++++++++++++++++++ src/lib/game/crafting-design.ts | 19 +-- 5 files changed, 116 insertions(+), 16 deletions(-) create mode 100644 src/lib/game/__tests__/hasty-enchanter.test.ts diff --git a/docs/circular-deps.txt b/docs/circular-deps.txt index 566d2ce..8eaddb4 100644 --- a/docs/circular-deps.txt +++ b/docs/circular-deps.txt @@ -1,5 +1,5 @@ # Circular Dependencies -Generated: 2026-06-08T09:22:18.219Z +Generated: 2026-06-08T09:39:59.023Z Found: 1 circular chain(s) — these MUST be fixed before modifying involved files. 1. 1) stores/golem-combat-actions.ts > stores/golem-combat-helpers.ts diff --git a/docs/dependency-graph.json b/docs/dependency-graph.json index 81a49d1..345fda9 100644 --- a/docs/dependency-graph.json +++ b/docs/dependency-graph.json @@ -1,6 +1,6 @@ { "_meta": { - "generated": "2026-06-08T09:22:16.306Z", + "generated": "2026-06-08T09:39:57.039Z", "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." }, diff --git a/docs/project-structure.txt b/docs/project-structure.txt index 27a9887..6b11e5e 100644 --- a/docs/project-structure.txt +++ b/docs/project-structure.txt @@ -228,6 +228,7 @@ Mana-Loop/ │ │ │ │ ├── floor-utils.upgraded.test.ts │ │ │ │ ├── formatting.test.ts │ │ │ │ ├── guardian-names.test.ts +│ │ │ │ ├── hasty-enchanter.test.ts │ │ │ │ ├── mana-utils.test.ts │ │ │ │ ├── melee-auto-attack.test.ts │ │ │ │ ├── melee-defense-bypass.test.ts diff --git a/src/lib/game/__tests__/hasty-enchanter.test.ts b/src/lib/game/__tests__/hasty-enchanter.test.ts new file mode 100644 index 0000000..031727b --- /dev/null +++ b/src/lib/game/__tests__/hasty-enchanter.test.ts @@ -0,0 +1,108 @@ +// ─── Regression Test: Hasty Enchanter 25% Design Speed Bonus ────────────────── +// Bug #301: Hasty Enchanter applied ~6.25% speed increase (additive 0.01 per tick) +// instead of the spec-defined 25% (time *= 0.75, i.e. 4/3 progress per tick). + +import { describe, it, expect } from 'vitest'; +import { calculateDesignProgress } from '../crafting-design'; +import { HOURS_PER_TICK } from '../constants'; + +function makeComputedEffects(specials: string[] = []) { + return { + maxManaMultiplier: 1, + maxManaBonus: 0, + regenMultiplier: 1, + regenBonus: 0, + clickManaMultiplier: 1, + clickManaBonus: 0, + meditationEfficiency: 1, + spellCostMultiplier: 1, + conversionEfficiency: 0, + baseDamageMultiplier: 1, + baseDamageBonus: 0, + attackSpeedMultiplier: 1, + critChanceBonus: 0, + critDamageMultiplier: 1, + elementalDamageMultiplier: 1, + studySpeedMultiplier: 1, + studyCostMultiplier: 1, + progressRetention: 0, + instantStudyChance: 0, + freeStudyChance: 0, + elementCapMultiplier: 1, + elementCapBonus: 0, + perElementCapBonus: {}, + perElementRegenBonus: {}, + conversionCostMultiplier: 1, + doubleCraftChance: 0, + permanentRegenBonus: 0, + specials: new Set(specials), + activeUpgrades: [], + skillLevelMultiplier: 1, + enchantmentPowerMultiplier: 1, + }; +} + +describe('Hasty Enchanter — design speed bonus', () => { + it('should apply 4/3 progress per tick (not additive 0.01) for repeat designs', () => { + const effects = makeComputedEffects(['hastyEnchanter']); + const required = 2; // 2 hours required + const result = calculateDesignProgress(0, required, effects, true); + + // Expected: progress = HOURS_PER_TICK * (4/3) = 0.04 * 1.333... = 0.05333... + const expectedProgress = HOURS_PER_TICK * (4 / 3); + expect(result.progress).toBeCloseTo(expectedProgress, 10); + expect(result.isComplete).toBe(false); + }); + + it('should reduce total ticks to 75% of original (25% faster)', () => { + const effects = makeComputedEffects(['hastyEnchanter']); + const required = 2; // 2 hours required + + // Without haste: ticks needed = 2 / 0.04 = 50 ticks + // With haste (4/3 multiplier): effective progress per tick = 0.04 * 4/3 = 0.05333... + // Ticks needed with haste = 2 / 0.05333... = 37.5 ticks + // Ratio = 37.5 / 50 = 0.75 + + let progress = 0; + let ticks = 0; + while (progress < required) { + const result = calculateDesignProgress(progress, required, effects, true); + progress = result.progress; + ticks++; + if (ticks > 1000) break; // safety valve + } + + // 50 ticks * 0.75 = 37.5 ticks expected + expect(ticks).toBeLessThanOrEqual(38); + expect(ticks).toBeGreaterThanOrEqual(37); + }); + + it('should NOT apply haste bonus for first-time designs (isRepeatDesign=false)', () => { + const effects = makeComputedEffects(['hastyEnchanter']); + const required = 2; + const result = calculateDesignProgress(0, required, effects, false); + + // Without haste: progress = HOURS_PER_TICK = 0.04 + expect(result.progress).toBeCloseTo(HOURS_PER_TICK, 10); + expect(result.timeBonus).toBe(0); + }); + + it('should NOT apply haste bonus without HASTY_ENCHANTER special', () => { + const effects = makeComputedEffects([]); // no specials + const required = 2; + const result = calculateDesignProgress(0, required, effects, true); + + expect(result.progress).toBeCloseTo(HOURS_PER_TICK, 10); + expect(result.timeBonus).toBe(0); + }); + + it('timeBonus should reflect the extra progress beyond base tick', () => { + const effects = makeComputedEffects(['hastyEnchanter']); + const required = 2; + const result = calculateDesignProgress(0, required, effects, true); + + // timeBonus = hastedIncrement - baseIncrement = (0.04 * 4/3) - 0.04 = 0.04 * (1/3) ≈ 0.01333... + const expectedBonus = HOURS_PER_TICK * (1 / 3); + expect(result.timeBonus).toBeCloseTo(expectedBonus, 10); + }); +}); diff --git a/src/lib/game/crafting-design.ts b/src/lib/game/crafting-design.ts index 0f2f28c..e96d5cb 100644 --- a/src/lib/game/crafting-design.ts +++ b/src/lib/game/crafting-design.ts @@ -11,7 +11,7 @@ import { EQUIPMENT_TYPES } from './data/equipment'; // Progress per tick expressed as a fraction of HOURS_PER_TICK const DESIGN_PROGRESS_PER_TICK = HOURS_PER_TICK; -const HASTY_ENCHANTER_BONUS_MULTIPLIER = 0.25; +const HASTY_ENCHANTER_SPEED_MULTIPLIER = 4 / 3; // 1 / 0.75 — 25% less time means 4/3 progress per tick // ─── Design Creation & Calculation ────────────────────────────────────────── @@ -78,17 +78,6 @@ export function calculateDesignTime(effects: DesignEffect[]): number { return time; } -export function getDesignTimeWithHaste( - effects: DesignEffect[], - isRepeatDesign: boolean, - computedEffects: ComputedEffects -): number { - const time = calculateDesignTime(effects); - return isRepeatDesign && hasSpecial(computedEffects, SPECIAL_EFFECTS.HASTY_ENCHANTER) - ? time * 0.75 - : time; -} - // ─── XP & Progression ─────────────────────────────────────────────────────── export function calculateEnchantingXpFromDesign(design: EnchantmentDesign): number { @@ -127,8 +116,10 @@ export function calculateDesignProgress( let timeBonus = 0; if (isRepeatDesign && hasSpecial(computedEffects, SPECIAL_EFFECTS.HASTY_ENCHANTER)) { - timeBonus = DESIGN_PROGRESS_PER_TICK * HASTY_ENCHANTER_BONUS_MULTIPLIER; - progress += timeBonus; + const baseIncrement = progress - currentProgress; + const hastedIncrement = baseIncrement * HASTY_ENCHANTER_SPEED_MULTIPLIER; + progress = currentProgress + hastedIncrement; + timeBonus = hastedIncrement - baseIncrement; } if (hasSpecial(computedEffects, SPECIAL_EFFECTS.INSTANT_DESIGNS) && Math.random() < INSTANT_DESIGN_CHANCE) {