fix: Use BASE_ELEMENTS constant for debug unlock to prevent unlocking all 22 elements
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m18s

This commit is contained in:
2026-06-12 09:45:15 +02:00
parent 4b8cdb97d7
commit 7e0e9b9f7c
8 changed files with 149 additions and 8 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
# Circular Dependencies
Generated: 2026-06-12T05:02:18.108Z
Generated: 2026-06-12T07:05:52.901Z
Found: 4 circular chain(s) — these MUST be fixed before modifying involved files.
1. 1) data/guardian-encounters.ts > data/guardian-procedural.ts
+9 -1
View File
@@ -1,6 +1,6 @@
{
"_meta": {
"generated": "2026-06-12T05:02:13.512Z",
"generated": "2026-06-12T07:05:50.633Z",
"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."
},
@@ -581,6 +581,13 @@
"stores/prestigeStore.ts",
"utils/spire-utils.ts"
],
"stores/combat-reset.ts": [
"stores/combat-actions.ts",
"stores/combat-state.types.ts",
"types.ts",
"utils/index.ts",
"utils/spire-utils.ts"
],
"stores/combat-state.types.ts": [
"types.ts"
],
@@ -588,6 +595,7 @@
"data/guardian-encounters.ts",
"stores/combat-actions.ts",
"stores/combat-descent-actions.ts",
"stores/combat-reset.ts",
"stores/combat-state.types.ts",
"stores/discipline-slice.ts",
"stores/golemancy-actions.ts",
+2 -1
View File
@@ -250,7 +250,8 @@ Mana-Loop/
│ │ │ │ ├── store-actions-discipline.test.ts
│ │ │ │ ├── store-actions-mana.test.ts
│ │ │ │ ├── store-actions.test.ts
│ │ │ │ ── tick-integration.test.ts
│ │ │ │ ── tick-integration.test.ts
│ │ │ │ └── unlock-base-elements.test.ts
│ │ │ ├── constants/
│ │ │ │ ├── spells-modules/
│ │ │ │ │ ├── advanced-spells.ts
+4 -2
View File
@@ -11,6 +11,7 @@ import {
} from 'lucide-react';
import { DebugName, useDebug } from '@/components/game/debug/debug-context';
import { useGameStore, useManaStore, useUIStore, usePrestigeStore, useCraftingStore } from '@/lib/game/stores';
import { BASE_ELEMENTS } from '@/lib/game/constants/elements';
import { computeTotalMaxMana } from '@/lib/game/effects';
import { getUnifiedEffects } from '@/lib/game/effects';
@@ -263,8 +264,9 @@ export function GameStateDebug() {
};
const handleUnlockBase = () => {
['fire', 'water', 'air', 'earth', 'light', 'dark', 'death'].forEach(e => {
if (!elements[e]?.unlocked) {
const currentElements = useManaStore.getState().elements;
BASE_ELEMENTS.forEach(e => {
if (!currentElements[e]?.unlocked) {
unlockElement(e, 500);
}
});
@@ -11,6 +11,7 @@ import {
} from 'lucide-react';
import { DebugName, useDebug } from '@/components/game/debug/debug-context';
import { useGameStore, useManaStore, useUIStore, usePrestigeStore, useCraftingStore } from '@/lib/game/stores';
import { BASE_ELEMENTS } from '@/lib/game/constants/elements';
import { computeTotalMaxMana } from '@/lib/game/effects';
import { getUnifiedEffects } from '@/lib/game/effects';
@@ -245,8 +246,9 @@ export function GameStateDebugSection() {
};
const handleUnlockBase = () => {
['fire', 'water', 'air', 'earth', 'light', 'dark', 'death'].forEach(e => {
if (!elements[e]?.unlocked) {
const currentElements = useManaStore.getState().elements;
BASE_ELEMENTS.forEach(e => {
if (!currentElements[e]?.unlocked) {
unlockElement(e, 0);
}
});
@@ -0,0 +1,125 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { useManaStore } from '../stores/manaStore';
import { ELEMENTS, BASE_UNLOCKED_ELEMENTS, BASE_ELEMENTS } from '../constants/elements';
// ─── Regression test: "Unlock All Base Elements" only unlocks 7 base ───────
// Issue #374: Button was unlocking all 22 elements instead of just 7 base.
function resetManaStore() {
useManaStore.setState({
rawMana: 10000,
meditateTicks: 0,
totalManaGathered: 0,
elements: Object.fromEntries(
Object.keys(ELEMENTS).map(k => [
k,
{
current: 0,
max: 10,
baseMax: 10,
unlocked: BASE_UNLOCKED_ELEMENTS.includes(k),
},
])
) as Record<string, { current: number; max: number; baseMax: number; unlocked: boolean }>,
elementRegen: {},
});
}
describe('Unlock All Base Elements debug button', () => {
beforeEach(() => {
resetManaStore();
});
it('BASE_ELEMENTS contains exactly 7 base elements', () => {
expect(BASE_ELEMENTS).toHaveLength(7);
expect(BASE_ELEMENTS).toContain('fire');
expect(BASE_ELEMENTS).toContain('water');
expect(BASE_ELEMENTS).toContain('air');
expect(BASE_ELEMENTS).toContain('earth');
expect(BASE_ELEMENTS).toContain('light');
expect(BASE_ELEMENTS).toContain('dark');
expect(BASE_ELEMENTS).toContain('death');
});
it('BASE_ELEMENTS does NOT contain utility, composite, or exotic elements', () => {
expect(BASE_ELEMENTS).not.toContain('transference');
expect(BASE_ELEMENTS).not.toContain('metal');
expect(BASE_ELEMENTS).not.toContain('sand');
expect(BASE_ELEMENTS).not.toContain('lightning');
expect(BASE_ELEMENTS).not.toContain('frost');
expect(BASE_ELEMENTS).not.toContain('blackflame');
expect(BASE_ELEMENTS).not.toContain('radiantflames');
expect(BASE_ELEMENTS).not.toContain('miasma');
expect(BASE_ELEMENTS).not.toContain('shadowglass');
expect(BASE_ELEMENTS).not.toContain('crystal');
expect(BASE_ELEMENTS).not.toContain('stellar');
expect(BASE_ELEMENTS).not.toContain('void');
expect(BASE_ELEMENTS).not.toContain('soul');
expect(BASE_ELEMENTS).not.toContain('time');
expect(BASE_ELEMENTS).not.toContain('plasma');
});
it('unlockElement for all BASE_ELEMENTS only unlocks those 7', () => {
const store = useManaStore.getState();
// All base elements should start locked (only transference is base-unlocked)
BASE_ELEMENTS.forEach(e => {
expect(store.elements[e]?.unlocked).toBe(false);
});
// Unlock all base elements (cost 0, simulating debug button)
BASE_ELEMENTS.forEach(e => {
store.unlockElement(e, 0);
});
const afterState = useManaStore.getState();
// All 7 base elements should now be unlocked
BASE_ELEMENTS.forEach(e => {
expect(afterState.elements[e]?.unlocked).toBe(true);
});
// Non-base elements should NOT be unlocked
const nonBaseElements = Object.keys(ELEMENTS).filter(
e => !BASE_ELEMENTS.includes(e as never) && !BASE_UNLOCKED_ELEMENTS.includes(e)
);
nonBaseElements.forEach(e => {
expect(afterState.elements[e]?.unlocked).toBe(false);
});
});
it('debug handleUnlockBase does not unlock transference', () => {
const store = useManaStore.getState();
// Verify transference starts unlocked
expect(store.elements['transference']?.unlocked).toBe(true);
// Run the unlock logic
BASE_ELEMENTS.forEach(e => {
if (!store.elements[e]?.unlocked) {
store.unlockElement(e, 0);
}
});
const afterState = useManaStore.getState();
// transference was already unlocked, should still be unlocked
expect(afterState.elements['transference']?.unlocked).toBe(true);
// But no new utility elements were unlocked
expect(afterState.elements['metal']?.unlocked).toBe(false);
});
it('total unlocked count after unlock base is exactly 8 (7 base + transference)', () => {
const store = useManaStore.getState();
const unlockedBefore = Object.values(store.elements).filter(e => e.unlocked).length;
// Only transference should be unlocked initially
expect(unlockedBefore).toBe(1);
BASE_ELEMENTS.forEach(e => {
store.unlockElement(e, 0);
});
const afterState = useManaStore.getState();
const unlockedAfter = Object.values(afterState.elements).filter(e => e.unlocked).length;
expect(unlockedAfter).toBe(8); // 7 base + transference
});
});
+3
View File
@@ -110,3 +110,6 @@ export const ELEMENT_ICON_NAMES: Record<string, string> = {
// ─── Base Unlocked Elements ───────────────────────────────────────────────────
export const BASE_UNLOCKED_ELEMENTS = ['transference'];
// ─── Base Element IDs (7 base elements only — NOT composite/exotic/utility) ──
export const BASE_ELEMENTS = ['fire', 'water', 'air', 'earth', 'light', 'dark', 'death'] as const;
+1 -1
View File
@@ -8,7 +8,7 @@ export { getStudySpeedMultiplier, getStudyCostMultiplier } from './core';
// Element-related constants
export { MANA_PER_ELEMENT, rawCost, elemCost, ELEMENTS, FLOOR_ELEM_CYCLE } from './elements';
export { ELEMENT_OPPOSITES, ELEMENT_ICON_NAMES, BASE_UNLOCKED_ELEMENTS } from './elements';
export { ELEMENT_OPPOSITES, ELEMENT_ICON_NAMES, BASE_UNLOCKED_ELEMENTS, BASE_ELEMENTS } from './elements';
// Spell constants
export { SPELLS_DEF } from './spells';