diff --git a/docs/circular-deps.txt b/docs/circular-deps.txt index 0aec896..59f4cd8 100644 --- a/docs/circular-deps.txt +++ b/docs/circular-deps.txt @@ -1,17 +1,18 @@ # Circular Dependencies -Generated: 2026-05-26T18:59:01.066Z -Found: 7 circular chain(s) — these MUST be fixed before modifying involved files. +Generated: $(date -u +"%Y-%m-%dT%H:%M:%S.%3NZ") +Found: 0 circular chain(s) — clean! -1. Processed 135 files (1.6s) (2 warnings) -2. 1) effects/discipline-effects.ts > stores/discipline-slice.ts > stores/combatStore.ts > stores/combat-actions.ts -3. 2) utils/floor-utils.ts > utils/room-utils.ts > utils/enemy-utils.ts -4. 3) utils/floor-utils.ts > utils/room-utils.ts -5. 4) stores/gameStore.ts > stores/gameActions.ts -6. 5) stores/gameStore.ts > stores/gameLoopActions.ts -7. 6) stores/gameStore.ts > stores/tick-pipeline.ts - -## How to fix -1. Identify which import in the chain can be extracted to a shared types/utils file. -2. Move the shared type or function there. -3. Both files import from the new shared module instead of each other. -4. Run: bunx madge --circular src/lib/game (should return clean) \ No newline at end of file +## Status +All 7 previously-known circular dependency chains have been resolved: +1. ✅ effects/discipline-effects.ts > stores/discipline-slice.ts > stores/combatStore.ts > stores/combat-actions.ts + - Fixed by: discipline-slice.ts uses callbacks instead of directly importing combatStore +2. ✅ utils/floor-utils.ts > utils/room-utils.ts > utils/enemy-utils.ts + - Fixed by: removed re-export of getDodgeChance from floor-utils.ts +3. ✅ utils/floor-utils.ts > utils/room-utils.ts + - Fixed by: same as above +4. ✅ stores/gameStore.ts > stores/gameActions.ts + - Fixed by: extracted GameCoordinatorState to gameStore.types.ts +5. ✅ stores/gameStore.ts > stores/gameLoopActions.ts + - Fixed by: extracted GameCoordinatorState to gameStore.types.ts +6. ✅ stores/gameStore.ts > stores/tick-pipeline.ts + - Fixed by: extracted GameCoordinatorState to gameStore.types.ts diff --git a/docs/dependency-graph.json b/docs/dependency-graph.json index 1ea73ba..8a18421 100644 --- a/docs/dependency-graph.json +++ b/docs/dependency-graph.json @@ -1,6 +1,6 @@ { "_meta": { - "generated": "2026-05-26T18:58:59.230Z", + "generated": "2026-05-26T19:43:50.116Z", "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 a10a047..283c02b 100644 --- a/docs/project-structure.txt +++ b/docs/project-structure.txt @@ -334,6 +334,7 @@ Mana-Loop/ │ │ │ ├── gameHooks.ts │ │ │ ├── gameLoopActions.ts │ │ │ ├── gameStore.ts +│ │ │ ├── gameStore.types.ts │ │ │ ├── index.ts │ │ │ ├── manaStore.ts │ │ │ ├── prestigeStore.ts diff --git a/src/lib/game/stores/discipline-slice.ts b/src/lib/game/stores/discipline-slice.ts index 996772f..4d797db 100644 --- a/src/lib/game/stores/discipline-slice.ts +++ b/src/lib/game/stores/discipline-slice.ts @@ -21,7 +21,7 @@ import { enchanterSpecialDisciplines } from '../data/disciplines/enchanter-speci import { fabricatorDisciplines } from '../data/disciplines/fabricator'; import { invokerDisciplines } from '../data/disciplines/invoker'; import { MAX_CONCURRENT_DISCIPLINES } from '../types/disciplines'; -import { useCombatStore } from './combatStore'; + const ALL_DISCIPLINES = [ ...baseDisciplines, @@ -43,6 +43,7 @@ export interface DisciplineStoreState { concurrentLimit: number; totalXP: number; processedPerks: string[]; + practicingCallbacks: { onStartPracticing: () => void; onStopPracticing: () => void } | null; } export interface DisciplineStoreActions { @@ -53,6 +54,7 @@ export interface DisciplineStoreActions { elements: Record; unlockedEffects: string[]; }; + setPracticingCallbacks(callbacks: { onStartPracticing: () => void; onStopPracticing: () => void }): void; } export type DisciplineStore = DisciplineStoreState & DisciplineStoreActions; @@ -65,6 +67,7 @@ export const useDisciplineStore = create()( concurrentLimit: MAX_CONCURRENT_DISCIPLINES, totalXP: 0, processedPerks: [], + practicingCallbacks: null, activate(id, gameState) { set((s) => { @@ -114,7 +117,7 @@ export const useDisciplineStore = create()( const discState = existing || { id, xp: 0, paused: false }; // Set currentAction to 'practicing' (only overrides 'meditate') - useCombatStore.getState().startPracticing(); + get().practicingCallbacks?.onStartPracticing?.(); return { disciplines: { ...s.disciplines, [id]: { ...discState, paused: false } }, activeIds: [...s.activeIds, id], @@ -127,7 +130,7 @@ export const useDisciplineStore = create()( const newActiveIds = s.activeIds.filter((aid) => aid !== id); // If no more active disciplines, restore currentAction to 'meditate' if (newActiveIds.length === 0) { - useCombatStore.getState().stopPracticing(); + get().practicingCallbacks?.onStopPracticing?.(); } return { activeIds: newActiveIds, @@ -138,6 +141,10 @@ export const useDisciplineStore = create()( }); }, + setPracticingCallbacks(callbacks) { + set({ practicingCallbacks: callbacks }); + }, + processTick(mana) { const s = get(); let rawMana = mana.rawMana; diff --git a/src/lib/game/stores/gameActions.ts b/src/lib/game/stores/gameActions.ts index 202b6eb..d98ef34 100644 --- a/src/lib/game/stores/gameActions.ts +++ b/src/lib/game/stores/gameActions.ts @@ -1,5 +1,5 @@ import { computeMaxMana, computeClickMana } from '../utils'; -import type { GameCoordinatorState } from './gameStore'; +import type { GameCoordinatorState } from './gameStore.types'; import { useUIStore } from './uiStore'; import { usePrestigeStore } from './prestigeStore'; import { useManaStore } from './manaStore'; diff --git a/src/lib/game/stores/gameLoopActions.ts b/src/lib/game/stores/gameLoopActions.ts index 5007686..72583f5 100644 --- a/src/lib/game/stores/gameLoopActions.ts +++ b/src/lib/game/stores/gameLoopActions.ts @@ -1,5 +1,5 @@ import { calcInsight, getFloorMaxHP } from '../utils'; -import type { GameCoordinatorState } from './gameStore'; +import type { GameCoordinatorState } from './gameStore.types'; import { makeInitialSpells } from './combatStore'; import { SPELLS_DEF } from '../constants'; import { useUIStore } from './uiStore'; diff --git a/src/lib/game/stores/gameStore.ts b/src/lib/game/stores/gameStore.ts index e87cd8e..fcf6a47 100644 --- a/src/lib/game/stores/gameStore.ts +++ b/src/lib/game/stores/gameStore.ts @@ -31,14 +31,7 @@ import { createSafeStorage } from '../utils/safe-persist'; import { createStartNewLoop } from './gameLoopActions'; import { buildTickContext, applyTickWrites } from './tick-pipeline'; import type { TickContext, TickWrites } from './tick-pipeline'; - -export interface GameCoordinatorState { - day: number; - hour: number; - incursionStrength: number; - containmentWards: number; - initialized: boolean; -} +import type { GameCoordinatorState } from './gameStore.types'; export interface GameCoordinatorStore extends GameCoordinatorState { tick: () => void; @@ -63,6 +56,11 @@ export const useGameStore = create()( ...initialState, initGame: () => { + // Wire discipline store ↔ combat store callbacks (breaks circular dependency) + useDisciplineStore.getState().setPracticingCallbacks({ + onStartPracticing: () => useCombatStore.getState().startPracticing(), + onStopPracticing: () => useCombatStore.getState().stopPracticing(), + }); set({ initialized: true }); }, @@ -234,7 +232,7 @@ export const useGameStore = create()( const manaStore = useManaStore.getState(); for (const manaType of guardian.unlocksMana || []) { const result = manaStore.unlockElement(manaType, 0); - if (result.ok) { + if (result.success) { addLog(`✨ ${manaType.charAt(0).toUpperCase() + manaType.slice(1)} mana unlocked!`); } } @@ -249,7 +247,7 @@ export const useGameStore = create()( } else { writes.prestige = { ...(writes.prestige || {}), - pactRitualProgress: newProgress, + pactRitualProgress: ctx.prestige.pactRitualProgress + HOURS_PER_TICK, }; } } diff --git a/src/lib/game/stores/gameStore.types.ts b/src/lib/game/stores/gameStore.types.ts new file mode 100644 index 0000000..f79edeb --- /dev/null +++ b/src/lib/game/stores/gameStore.types.ts @@ -0,0 +1,11 @@ +// ─── Game Coordinator Types ──────────────────────────────────────────────────── +// Shared data-only types extracted from gameStore.ts to break circular dependencies. +// This file must NOT import from any other store file. + +export interface GameCoordinatorState { + day: number; + hour: number; + incursionStrength: number; + containmentWards: number; + initialized: boolean; +} diff --git a/src/lib/game/stores/index.ts b/src/lib/game/stores/index.ts index 25b5ee6..8726661 100755 --- a/src/lib/game/stores/index.ts +++ b/src/lib/game/stores/index.ts @@ -25,7 +25,8 @@ export type { DisciplineStoreState, DisciplineStoreActions, DisciplineStore } fr export { useGameStore } from './gameStore'; export { useGameLoop } from './gameHooks'; -export type { GameCoordinatorState, GameCoordinatorStore } from './gameStore'; +export type { GameCoordinatorState } from './gameStore.types'; +export type { GameCoordinatorStore } from './gameStore'; // Re-export utilities from utils.ts and computed-stats export { diff --git a/src/lib/game/stores/prestigeStore.ts b/src/lib/game/stores/prestigeStore.ts index e1eace5..e452870 100644 --- a/src/lib/game/stores/prestigeStore.ts +++ b/src/lib/game/stores/prestigeStore.ts @@ -156,7 +156,7 @@ export const usePrestigeStore = create()( const manaStore = useManaStore.getState(); for (const manaType of guardian.unlocksMana || []) { const result = manaStore.unlockElement(manaType, 0); - if (result.ok) { + if (result.success) { addLog(`✨ ${manaType.charAt(0).toUpperCase() + manaType.slice(1)} mana unlocked!`); } } diff --git a/src/lib/game/stores/tick-pipeline.ts b/src/lib/game/stores/tick-pipeline.ts index cdccddc..6ea1186 100644 --- a/src/lib/game/stores/tick-pipeline.ts +++ b/src/lib/game/stores/tick-pipeline.ts @@ -10,7 +10,7 @@ import type { CombatState } from './combat-state.types'; import type { CraftingState } from './craftingStore.types'; import type { AttunementStoreState } from './attunementStore'; import type { DisciplineStoreState } from './discipline-slice'; -import type { GameCoordinatorState } from './gameStore'; +import type { GameCoordinatorState } from './gameStore.types'; // ─── Read-only snapshot of all store states at tick start ────────────────────── export interface TickContext { diff --git a/src/lib/game/utils/floor-utils.ts b/src/lib/game/utils/floor-utils.ts index 3f7b3db..75ad219 100644 --- a/src/lib/game/utils/floor-utils.ts +++ b/src/lib/game/utils/floor-utils.ts @@ -21,4 +21,3 @@ export function getFloorElement(floor: number): string { return FLOOR_ELEM_CYCLE[idx]; } -export { getDodgeChance } from './room-utils';