chore: commit investigation state
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m25s

This commit is contained in:
2026-06-10 09:56:14 +02:00
parent 8bca8f85d5
commit fef7de8d09
5 changed files with 167 additions and 3 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
# Circular Dependencies
Generated: 2026-06-09T16:48:20.172Z
Generated: 2026-06-09T17:09:05.689Z
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
View File
@@ -1,6 +1,6 @@
{
"_meta": {
"generated": "2026-06-09T16:48:18.218Z",
"generated": "2026-06-09T17:09:03.568Z",
"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."
},
+1
View File
@@ -215,6 +215,7 @@ Mana-Loop/
│ │ │ │ ├── discipline-math.test.ts
│ │ │ │ ├── discipline-prerequisites.test.ts
│ │ │ │ ├── discipline-reactivate-bug.test.ts
│ │ │ │ ├── earth-desync.test.ts
│ │ │ │ ├── enemy-barrier-utils.test.ts
│ │ │ │ ├── enemy-defenses.test.ts
│ │ │ │ ├── enemy-generator.test.ts
+145
View File
@@ -0,0 +1,145 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { useManaStore } from '../stores/manaStore';
import { ELEMENTS, BASE_UNLOCKED_ELEMENTS } from '../constants/elements';
// ─── Regression test: Earth element desync after Unlock All ─────────────────
// https://gitea.tailf367e3.ts.net/Anexim/Mana-Loop/issues/338
// https://gitea.tailf367e3.ts.net/Anexim/Mana-Loop/issues/339
function resetManaStore() {
useManaStore.setState({
rawMana: 10,
meditateTicks: 0,
totalManaGathered: 0,
elements: Object.fromEntries(
Object.keys(ELEMENTS).map(k => [
k,
{
current: BASE_UNLOCKED_ELEMENTS.includes(k) ? 0 : 0,
max: 10,
baseMax: 10,
unlocked: BASE_UNLOCKED_ELEMENTS.includes(k),
},
])
) as Record<string, { current: number; max: number; baseMax: number; unlocked: boolean }>,
elementRegen: {},
});
}
describe('Earth element desync after Unlock All', () => {
beforeEach(() => {
resetManaStore();
});
it('earth element exists in initial state', () => {
const elements = useManaStore.getState().elements;
expect(elements.earth).toBeDefined();
expect(elements.earth.unlocked).toBe(false);
});
it('unlockElement unlocks earth correctly', () => {
const result = useManaStore.getState().unlockElement('earth', 0);
expect(result.success).toBe(true);
const earth = useManaStore.getState().elements.earth;
expect(earth).toBeDefined();
expect(earth.unlocked).toBe(true);
expect(earth.current).toBe(0);
expect(earth.max).toBe(10);
expect(earth.baseMax).toBe(10);
});
it('unlockElement preserves earth state fields', () => {
// First give earth some mana
useManaStore.setState((s) => ({
elements: {
...s.elements,
earth: { ...s.elements.earth, current: 5, max: 20, baseMax: 20 },
},
}));
const result = useManaStore.getState().unlockElement('earth', 0);
expect(result.success).toBe(true);
const earth = useManaStore.getState().elements.earth;
expect(earth.unlocked).toBe(true);
expect(earth.current).toBe(5);
expect(earth.max).toBe(20);
expect(earth.baseMax).toBe(20);
});
it('unlock all elements via unlockElement preserves all fields', () => {
const elements = useManaStore.getState().elements;
for (const id of Object.keys(elements)) {
if (!elements[id].unlocked) {
useManaStore.getState().unlockElement(id, 0);
}
}
const updatedElements = useManaStore.getState().elements;
const earth = updatedElements.earth;
expect(earth).toBeDefined();
expect(earth.unlocked).toBe(true);
expect(earth.current).toBe(0);
expect(earth.max).toBe(10);
expect(earth.baseMax).toBe(10);
});
it('all 22 elements are unlocked after unlock all', () => {
const elements = useManaStore.getState().elements;
for (const id of Object.keys(elements)) {
if (!elements[id].unlocked) {
useManaStore.getState().unlockElement(id, 0);
}
}
const updatedElements = useManaStore.getState().elements;
const unlockedCount = Object.values(updatedElements).filter(e => e.unlocked).length;
expect(unlockedCount).toBe(Object.keys(ELEMENTS).length);
});
it('earth shows in unlocked elements list after unlock all', () => {
const elements = useManaStore.getState().elements;
for (const id of Object.keys(elements)) {
if (!elements[id].unlocked) {
useManaStore.getState().unlockElement(id, 0);
}
}
const updatedElements = useManaStore.getState().elements;
const unlockedIds = Object.entries(updatedElements)
.filter(([, state]) => state.unlocked)
.map(([id]) => id);
expect(unlockedIds).toContain('earth');
});
it('earth element has valid state after unlock (not missing fields)', () => {
useManaStore.getState().unlockElement('earth', 0);
const earth = useManaStore.getState().elements.earth;
// These would be undefined if the element state was corrupted
expect(earth.current).toBeDefined();
expect(earth.max).toBeDefined();
expect(earth.baseMax).toBeDefined();
expect(earth.unlocked).toBe(true);
});
it('unlockElement with cost 0 does not deduct raw mana', () => {
const before = useManaStore.getState().rawMana;
useManaStore.getState().unlockElement('earth', 0);
const after = useManaStore.getState().rawMana;
expect(after).toBe(before);
});
it('unlockElement twice for earth is a no-op', () => {
const result1 = useManaStore.getState().unlockElement('earth', 0);
expect(result1.success).toBe(true);
const result2 = useManaStore.getState().unlockElement('earth', 0);
expect(result2.success).toBe(false); // Already unlocked
const earth = useManaStore.getState().elements.earth;
expect(earth.unlocked).toBe(true);
});
});
+19 -1
View File
@@ -105,7 +105,13 @@ export const useManaStore = create<ManaStore>()(
if (state.elements[element]?.unlocked) return fail(ErrorCode.INVALID_INPUT, `Element ${element} is already unlocked`);
if (state.rawMana < cost) return fail(ErrorCode.INSUFFICIENT_MANA, `Need ${cost} raw mana, have ${state.rawMana}`);
set({ rawMana: state.rawMana - cost, elements: { ...state.elements, [element]: { ...state.elements[element], unlocked: true } } });
// If the element doesn't exist in the store (e.g. from an old save), create it
const existing = state.elements[element];
const newElement = existing
? { ...existing, unlocked: true }
: { current: 0, max: 10, baseMax: 10, unlocked: true };
set({ rawMana: state.rawMana - cost, elements: { ...state.elements, [element]: newElement } });
return okVoid();
},
@@ -174,6 +180,18 @@ export const useManaStore = create<ManaStore>()(
persistedState.elements[k].baseMax = persistedState.elements[k].max ?? 10;
}
}
// Add any missing elements that exist in ELEMENTS but not in the save
for (const k of Object.keys(ELEMENTS)) {
if (!persistedState.elements[k]) {
const isUnlocked = BASE_UNLOCKED_ELEMENTS.includes(k);
persistedState.elements[k] = {
current: isUnlocked ? 0 : 0,
max: 10,
baseMax: 10,
unlocked: isUnlocked,
};
}
}
}
return persistedState;
},