fix: Hasty Enchanter now applies correct 25% design speed bonus (was 6.25%)
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m19s

This commit is contained in:
2026-06-08 11:52:07 +02:00
parent 971b876537
commit b3b13b6a55
5 changed files with 116 additions and 16 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
# Circular Dependencies # 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. 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 1. 1) stores/golem-combat-actions.ts > stores/golem-combat-helpers.ts
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"_meta": { "_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.", "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."
}, },
+1
View File
@@ -228,6 +228,7 @@ Mana-Loop/
│ │ │ │ ├── floor-utils.upgraded.test.ts │ │ │ │ ├── floor-utils.upgraded.test.ts
│ │ │ │ ├── formatting.test.ts │ │ │ │ ├── formatting.test.ts
│ │ │ │ ├── guardian-names.test.ts │ │ │ │ ├── guardian-names.test.ts
│ │ │ │ ├── hasty-enchanter.test.ts
│ │ │ │ ├── mana-utils.test.ts │ │ │ │ ├── mana-utils.test.ts
│ │ │ │ ├── melee-auto-attack.test.ts │ │ │ │ ├── melee-auto-attack.test.ts
│ │ │ │ ├── melee-defense-bypass.test.ts │ │ │ │ ├── melee-defense-bypass.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);
});
});
+5 -14
View File
@@ -11,7 +11,7 @@ import { EQUIPMENT_TYPES } from './data/equipment';
// Progress per tick expressed as a fraction of HOURS_PER_TICK // Progress per tick expressed as a fraction of HOURS_PER_TICK
const DESIGN_PROGRESS_PER_TICK = 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 ────────────────────────────────────────── // ─── Design Creation & Calculation ──────────────────────────────────────────
@@ -78,17 +78,6 @@ export function calculateDesignTime(effects: DesignEffect[]): number {
return time; 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 ─────────────────────────────────────────────────────── // ─── XP & Progression ───────────────────────────────────────────────────────
export function calculateEnchantingXpFromDesign(design: EnchantmentDesign): number { export function calculateEnchantingXpFromDesign(design: EnchantmentDesign): number {
@@ -127,8 +116,10 @@ export function calculateDesignProgress(
let timeBonus = 0; let timeBonus = 0;
if (isRepeatDesign && hasSpecial(computedEffects, SPECIAL_EFFECTS.HASTY_ENCHANTER)) { if (isRepeatDesign && hasSpecial(computedEffects, SPECIAL_EFFECTS.HASTY_ENCHANTER)) {
timeBonus = DESIGN_PROGRESS_PER_TICK * HASTY_ENCHANTER_BONUS_MULTIPLIER; const baseIncrement = progress - currentProgress;
progress += timeBonus; 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) { if (hasSpecial(computedEffects, SPECIAL_EFFECTS.INSTANT_DESIGNS) && Math.random() < INSTANT_DESIGN_CHANCE) {