fix: resolve 7 circular dependency chains in src/lib/game
Build and Publish Mana Loop Docker Image / build-and-publish (push) Failing after 57s

- equipment/utils.ts: import directly from individual equipment modules instead of index.ts
- golems/utils.ts: import directly from individual golem modules instead of index.ts
- combatStore.ts: extract CombatState to combat-state.types.ts, remove debugSetTime (was only user of gameStore import)
- combat-actions.ts: import CombatState from combat-state.types.ts instead of combatStore
- stores/index.ts: re-export CombatState from combat-state.types.ts
- GameStateDebug.tsx: replace debugSetTime calls with direct useGameStore.setState()

Verification: bunx madge --circular src/lib/game → No circular dependency found!
This commit is contained in:
2026-05-18 14:46:57 +02:00
parent ea3035ec5e
commit 084fea2a25
8 changed files with 148 additions and 115 deletions
+1
View File
@@ -259,6 +259,7 @@ Mana-Loop/
│ │ ├── stores/
│ │ │ ├── attunementStore.ts
│ │ │ ├── combat-actions.ts
│ │ │ ├── combat-state.types.ts
│ │ │ ├── combatStore.ts
│ │ │ ├── craftingStore.ts
│ │ │ ├── discipline-slice.ts
+3 -4
View File
@@ -31,7 +31,6 @@ export function GameStateDebug() {
const resetGame = useGameStore((s) => s.resetGame);
const debugSetFloor = useCombatStore((s) => s.debugSetFloor);
const resetFloorHP = useCombatStore((s) => s.resetFloorHP);
const debugSetTime = useCombatStore((s) => s.debugSetTime);
const handleReset = () => {
if (confirmReset) {
@@ -187,13 +186,13 @@ export function GameStateDebug() {
<Button size="sm" variant="outline" onClick={() => useGameStore.setState({ day: 1, hour: 0 })}>
Day 1
</Button>
<Button size="sm" variant="outline" onClick={() => debugSetTime?.(10, 0)}>
<Button size="sm" variant="outline" onClick={() => useGameStore.setState({ day: 10, hour: 0 })}>
Day 10
</Button>
<Button size="sm" variant="outline" onClick={() => debugSetTime?.(20, 0)}>
<Button size="sm" variant="outline" onClick={() => useGameStore.setState({ day: 20, hour: 0 })}>
Day 20
</Button>
<Button size="sm" variant="outline" onClick={() => debugSetTime?.(30, 0)}>
<Button size="sm" variant="outline" onClick={() => useGameStore.setState({ day: 30, hour: 0 })}>
Day 30
</Button>
</div>
+21 -1
View File
@@ -1,7 +1,27 @@
// ─── Equipment Helper Functions ─────────────────────────
import type { EquipmentType, EquipmentSlot, EquipmentCategory } from './types';
import { EQUIPMENT_TYPES } from './index';
import { ACCESSORIES_EQUIPMENT } from './accessories';
import { BODY_EQUIPMENT } from './body';
import { CASTER_EQUIPMENT } from './casters';
import { CATALYST_EQUIPMENT } from './catalysts';
import { FEET_EQUIPMENT } from './feet';
import { HANDS_EQUIPMENT } from './hands';
import { HEAD_EQUIPMENT } from './head';
import { SHIELD_EQUIPMENT } from './shields';
import { SWORD_EQUIPMENT } from './swords';
const EQUIPMENT_TYPES: Record<string, EquipmentType> = {
...ACCESSORIES_EQUIPMENT,
...BODY_EQUIPMENT,
...CASTER_EQUIPMENT,
...CATALYST_EQUIPMENT,
...FEET_EQUIPMENT,
...HANDS_EQUIPMENT,
...HEAD_EQUIPMENT,
...SHIELD_EQUIPMENT,
...SWORD_EQUIPMENT,
};
export function getEquipmentType(id: string): EquipmentType | undefined {
return EQUIPMENT_TYPES[id];
+9 -1
View File
@@ -1,7 +1,15 @@
// ─── Golem Helper Functions ─────────────────────────
import type { GolemDef, GolemManaCost } from './types';
import { GOLEMS_DEF } from './index';
import { BASE_GOLEMS } from './base-golems';
import { ELEMENTAL_GOLEMS } from './elemental-golems';
import { HYBRID_GOLEMS } from './hybrid-golems';
const GOLEMS_DEF = {
...BASE_GOLEMS,
...ELEMENTAL_GOLEMS,
...HYBRID_GOLEMS,
};
// Get golem slots based on Fabricator attunement level
// Level 2 = 1, Level 4 = 2, Level 6 = 3, Level 8 = 4, Level 10 = 5
+1 -1
View File
@@ -2,7 +2,7 @@
// Extracted combat logic from combatStore.ts
import { SPELLS_DEF, GUARDIANS, HOURS_PER_TICK } from '../constants';
import type { CombatState } from './combatStore';
import type { CombatState } from './combat-state.types';
import type { SpellState } from '../types';
import { getFloorMaxHP, getFloorElement, calcDamage, canAffordSpellCost, deductSpellCost } from '../utils';
import { usePrestigeStore } from './prestigeStore';
+111
View File
@@ -0,0 +1,111 @@
// ─── Combat State Types ────────────────────────────────────────────────────────
// Shared types for combat store and combat actions to avoid circular dependency
import type { GameAction, SpellState, FloorState, GolemancyState, ActivityLogEntry, AchievementState, EquipmentSpellState, ActivityEventType } from '../types';
export interface CombatState {
// Floor state
currentFloor: number;
floorHP: number;
floorMaxHP: number;
maxFloorReached: number;
// Action state
activeSpell: string;
currentAction: GameAction;
castProgress: number;
// Spire mode
spireMode: boolean;
// Room system for special floors
currentRoom: FloorState;
// Spire climbing state
clearedFloors: Record<number, boolean>;
climbDirection: 'up' | 'down' | null;
isDescending: boolean;
// Golemancy (summoned golems)
golemancy: GolemancyState;
// Equipment spell states for multi-casting
equipmentSpellStates: EquipmentSpellState[];
// Combat special effect tracking
comboHitCount: number;
floorHitCount: number;
// Spells
spells: Record<string, SpellState>;
// Activity Log (for Spire Mode UI)
activityLog: ActivityLogEntry[];
// Achievements
achievements: AchievementState;
// Stats tracking
totalSpellsCast: number;
totalDamageDealt: number;
totalCraftsCompleted: number;
// Actions
setCurrentFloor: (floor: number) => void;
advanceFloor: () => void;
setFloorHP: (hp: number) => void;
setMaxFloorReached: (floor: number) => void;
setAction: (action: GameAction) => void;
setSpell: (spellId: string) => void;
setCastProgress: (progress: number) => void;
// Room state
setCurrentRoom: (room: FloorState) => void;
// Spire climbing
setClimbDirection: (direction: 'up' | 'down' | null) => void;
setClearedFloor: (floor: number, cleared: boolean) => void;
setIsDescending: (descending: boolean) => void;
climbDownFloor: () => void;
exitSpireMode: () => void;
startClimbUp: () => void;
startClimbDown: () => void;
// Golemancy
toggleGolem: (golemId: string) => void;
setEnabledGolems: (golemIds: string[]) => void;
// Spells
learnSpell: (spellId: string) => void;
setSpellState: (spellId: string, state: Partial<SpellState>) => void;
// Activity Log
addActivityLog: (eventType: ActivityEventType, message: string, details?: ActivityLogEntry['details']) => void;
// Stats
incrementSpellsCast: () => void;
addDamageDealt: (damage: number) => void;
incrementCraftsCompleted: () => void;
// Spire mode
enterSpireMode: () => void;
// Combat tick
processCombatTick: (
rawMana: number,
elements: Record<string, { current: number; max: number; unlocked: boolean }>,
maxMana: number,
attackSpeedMult: number,
onFloorCleared: (floor: number, wasGuardian: boolean) => void,
onDamageDealt: (damage: number) => { rawMana: number; elements: Record<string, { current: number; max: number; unlocked: boolean }> },
) => { rawMana: number; elements: Record<string, { current: number; max: number; unlocked: boolean }>; logMessages: string[]; totalManaGathered: number };
// Reset
resetCombat: (startFloor: number, spellsToKeep?: string[]) => void;
// Debug helpers
debugSetFloor: (floor: number) => void;
resetFloorHP: () => void;
debugSetTime: (day: number, hour: number) => void;
}
+1 -107
View File
@@ -10,113 +10,7 @@ import { useGameStore } from './gameStore';
import { generateFloorState } from '../utils/room-utils';
import { addActivityLogEntry } from '../utils/activity-log';
import { processCombatTick, makeInitialSpells } from './combat-actions';
export interface CombatState {
// Floor state
currentFloor: number;
floorHP: number;
floorMaxHP: number;
maxFloorReached: number;
// Action state
activeSpell: string;
currentAction: GameAction;
castProgress: number;
// Spire mode
spireMode: boolean;
// Room system for special floors
currentRoom: FloorState;
// Spire climbing state
clearedFloors: Record<number, boolean>;
climbDirection: 'up' | 'down' | null;
isDescending: boolean;
// Golemancy (summoned golems)
golemancy: GolemancyState;
// Equipment spell states for multi-casting
equipmentSpellStates: EquipmentSpellState[];
// Combat special effect tracking
comboHitCount: number;
floorHitCount: number;
// Spells
spells: Record<string, SpellState>;
// Activity Log (for Spire Mode UI)
activityLog: ActivityLogEntry[];
// Achievements
achievements: AchievementState;
// Stats tracking
totalSpellsCast: number;
totalDamageDealt: number;
totalCraftsCompleted: number;
// Actions
setCurrentFloor: (floor: number) => void;
advanceFloor: () => void;
setFloorHP: (hp: number) => void;
setMaxFloorReached: (floor: number) => void;
setAction: (action: GameAction) => void;
setSpell: (spellId: string) => void;
setCastProgress: (progress: number) => void;
// Room state
setCurrentRoom: (room: FloorState) => void;
// Spire climbing
setClimbDirection: (direction: 'up' | 'down' | null) => void;
setClearedFloor: (floor: number, cleared: boolean) => void;
setIsDescending: (descending: boolean) => void;
climbDownFloor: () => void;
exitSpireMode: () => void;
startClimbUp: () => void;
startClimbDown: () => void;
// Golemancy
toggleGolem: (golemId: string) => void;
setEnabledGolems: (golemIds: string[]) => void;
// Spells
learnSpell: (spellId: string) => void;
setSpellState: (spellId: string, state: Partial<SpellState>) => void;
// Activity Log
addActivityLog: (eventType: ActivityEventType, message: string, details?: ActivityLogEntry['details']) => void;
// Stats
incrementSpellsCast: () => void;
addDamageDealt: (damage: number) => void;
incrementCraftsCompleted: () => void;
// Spire mode
enterSpireMode: () => void;
// Combat tick
processCombatTick: (
rawMana: number,
elements: Record<string, { current: number; max: number; unlocked: boolean }>,
maxMana: number,
attackSpeedMult: number,
onFloorCleared: (floor: number, wasGuardian: boolean) => void,
onDamageDealt: (damage: number) => { rawMana: number; elements: Record<string, { current: number; max: number; unlocked: boolean }> },
) => { rawMana: number; elements: Record<string, { current: number; max: number; unlocked: boolean }>; logMessages: string[]; totalManaGathered: number };
// Reset
resetCombat: (startFloor: number, spellsToKeep?: string[]) => void;
// Debug helpers
debugSetFloor: (floor: number) => void;
resetFloorHP: () => void;
debugSetTime: (day: number, hour: number) => void;
}
import type { CombatState } from './combat-state.types';
export const useCombatStore = create<CombatState>()(
persist(
+1 -1
View File
@@ -12,7 +12,7 @@ export { useManaStore, makeInitialElements } from './manaStore';
export type { ManaState } from './manaStore';
export { useCombatStore, makeInitialSpells } from './combatStore';
export type { CombatState } from './combatStore';
export type { CombatState } from './combat-state.types';
export { useCraftingStore } from './craftingStore';
export type { CraftingState, CraftingActions } from './craftingStore';