diff --git a/.gitignore b/.gitignore index 128c8f8..6d70c40 100755 --- a/.gitignore +++ b/.gitignore @@ -50,3 +50,4 @@ server.log # Skills directory .desloppify/ test-results/ +playwright-report/ diff --git a/docs/circular-deps.txt b/docs/circular-deps.txt index e12c6b3..66b6c0b 100644 --- a/docs/circular-deps.txt +++ b/docs/circular-deps.txt @@ -1,4 +1,4 @@ # Circular Dependencies -Generated: 2026-06-01T10:58:05.599Z +Generated: 2026-06-02T08:49:51.414Z No circular dependencies found. ✅ diff --git a/docs/dependency-graph.json b/docs/dependency-graph.json index c5cd177..20634b6 100644 --- a/docs/dependency-graph.json +++ b/docs/dependency-graph.json @@ -1,6 +1,6 @@ { "_meta": { - "generated": "2026-06-01T10:58:03.834Z", + "generated": "2026-06-02T08:49:49.529Z", "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." }, @@ -586,6 +586,16 @@ "types.ts", "types/equipmentSlot.ts" ], + "stores/debugBridge.ts": [ + "stores/attunementStore.ts", + "stores/combatStore.ts", + "stores/craftingStore.ts", + "stores/discipline-slice.ts", + "stores/gameStore.ts", + "stores/manaStore.ts", + "stores/prestigeStore.ts", + "stores/uiStore.ts" + ], "stores/discipline-slice.ts": [ "data/disciplines/base.ts", "data/disciplines/elemental-regen-advanced.ts", diff --git a/docs/project-structure.txt b/docs/project-structure.txt index 194f816..43fc2b0 100644 --- a/docs/project-structure.txt +++ b/docs/project-structure.txt @@ -19,10 +19,6 @@ Mana-Loop/ │ ├── enchanter-happy-path.spec.ts │ ├── fabricator-happy-path.spec.ts │ └── playtest.spec.ts -├── playwright-report/ -│ ├── data/ -│ │ └── 199a0ed84e7318aab410b0ec2f96ea8f6478a4da.png -│ └── index.html ├── public/ │ ├── fonts/ │ │ ├── GeistMonoVF.woff diff --git a/playwright-report/index.html b/playwright-report/index.html deleted file mode 100644 index 3acc56f..0000000 --- a/playwright-report/index.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - Playwright Test Report - - - - -
- - - \ No newline at end of file diff --git a/src/components/game/tabs/DebugTab/DisciplineDebugSection.tsx b/src/components/game/tabs/DebugTab/DisciplineDebugSection.tsx index d0ba818..38f15d1 100644 --- a/src/components/game/tabs/DebugTab/DisciplineDebugSection.tsx +++ b/src/components/game/tabs/DebugTab/DisciplineDebugSection.tsx @@ -4,6 +4,7 @@ import { Button } from '@/components/ui/button'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { BookOpen, Plus, Pause, Play } from 'lucide-react'; import { useDisciplineStore } from '@/lib/game/stores/discipline-slice'; +import { MAX_CONCURRENT_DISCIPLINES } from '@/lib/game/types/disciplines'; import { ALL_DISCIPLINES } from '@/lib/game/data/disciplines'; import { useManaStore } from '@/lib/game/stores/manaStore'; import { DebugName } from '@/components/game/debug/debug-context'; @@ -32,11 +33,18 @@ export function DisciplineDebugSection() { useDisciplineStore.setState((s) => { const disc = s.disciplines[id]; if (!disc) return s; + const newTotalXP = s.totalXP + amount; + const newLimit = Math.min( + MAX_CONCURRENT_DISCIPLINES + Math.floor(newTotalXP / 500), + MAX_CONCURRENT_DISCIPLINES + 3, + ); return { disciplines: { ...s.disciplines, [id]: { ...disc, xp: disc.xp + amount }, }, + totalXP: newTotalXP, + concurrentLimit: Math.max(s.concurrentLimit, newLimit), }; }); }; diff --git a/src/components/game/tabs/DebugTab/GameStateDebugSection.tsx b/src/components/game/tabs/DebugTab/GameStateDebugSection.tsx index f466388..2a0fc94 100644 --- a/src/components/game/tabs/DebugTab/GameStateDebugSection.tsx +++ b/src/components/game/tabs/DebugTab/GameStateDebugSection.tsx @@ -167,8 +167,9 @@ function TimeControlSection({ day, hour, paused, onSetDay, onTogglePause }: { // ─── Quick Actions Section ─────────────────────────────────────────────────── -function QuickActionsSection({ onUnlockBase }: { +function QuickActionsSection({ onUnlockBase, onAddStarterMaterials }: { onUnlockBase: () => void; + onAddStarterMaterials: () => void; }) { return ( @@ -183,6 +184,9 @@ function QuickActionsSection({ onUnlockBase }: { + @@ -248,6 +252,21 @@ export function GameStateDebugSection() { }); }; + const handleAddStarterMaterials = () => { + useCraftingStore.setState((s) => ({ + lootInventory: { + ...s.lootInventory, + materials: { + ...s.lootInventory.materials, + manaCrystalDust: (s.lootInventory.materials.manaCrystalDust || 0) + 20, + earthShard: (s.lootInventory.materials.earthShard || 0) + 10, + metalShard: (s.lootInventory.materials.metalShard || 0) + 5, + elementalCore: (s.lootInventory.materials.elementalCore || 0) + 3, + }, + }, + })); + }; + return (
@@ -259,6 +278,7 @@ export function GameStateDebugSection() {
diff --git a/src/components/game/tabs/DebugTab/PactDebugSection.tsx b/src/components/game/tabs/DebugTab/PactDebugSection.tsx index 341e4d3..2ae5d90 100644 --- a/src/components/game/tabs/DebugTab/PactDebugSection.tsx +++ b/src/components/game/tabs/DebugTab/PactDebugSection.tsx @@ -71,13 +71,16 @@ export function PactDebugSection() { const guardian = getGuardianForFloor(floor); if (!guardian) return; - if (signedPacts.includes(floor)) { + // Always read fresh state from store to avoid stale closures + const currentSignedPacts = usePrestigeStore.getState().signedPacts; + const maxPacts = 1 + (prestigeUpgrades?.pactCapacity || 0); + + if (currentSignedPacts.includes(floor)) { addLog(`⚠️ Already signed pact with ${guardian.name}!`); return; } - const maxPacts = 1 + (prestigeUpgrades?.pactCapacity || 0); - if (signedPacts.length >= maxPacts) { + if (currentSignedPacts.length >= maxPacts) { addLog(`⚠️ Cannot sign more pacts! Maximum: ${maxPacts}.`); return; } @@ -111,8 +114,14 @@ export function PactDebugSection() { }; const signAllPacts = () => { + const maxPacts = 1 + (prestigeUpgrades?.pactCapacity || 0); guardianFloors.forEach((floor) => { - if (!signedPacts.includes(floor)) { + // Read fresh state from store to avoid stale closure bug: + // signedPacts from render-time closure is always the initial value + // during the loop, so the maxPacts check never triggers. + const currentSigned = usePrestigeStore.getState().signedPacts; + if (currentSigned.length >= maxPacts) return; + if (!currentSigned.includes(floor)) { forcePact(floor); } }); diff --git a/src/components/game/tabs/SpireCombatPage/SpireCombatPage.tsx b/src/components/game/tabs/SpireCombatPage/SpireCombatPage.tsx index 651e4cf..7bbd333 100644 --- a/src/components/game/tabs/SpireCombatPage/SpireCombatPage.tsx +++ b/src/components/game/tabs/SpireCombatPage/SpireCombatPage.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useState, useEffect, useMemo } from 'react'; +import { useState, useEffect, useMemo, useRef } from 'react'; import { useShallow } from 'zustand/react/shallow'; import { useCombatStore, useManaStore, usePrestigeStore, fmt, computeMaxMana, computeRegen } from '@/lib/game/stores'; import { computeDisciplineEffects } from '@/lib/game/effects/discipline-effects'; @@ -127,7 +127,15 @@ export function SpireCombatPage() { return base + floorBonus + randomVariation; }, [currentFloor, seededRandom]); + // Track the last floor+totalRooms combo we generated a room for. + // Prevents infinite re-render loop: without this guard, the effect + // fires → setCurrentRoom → store update → re-render → tick advances + // currentFloor → effect fires → ... (loop). + const lastGeneratedRef = useRef(null); useEffect(() => { + const key = `${currentFloor}:${totalRooms}`; + if (lastGeneratedRef.current === key) return; // already generated + lastGeneratedRef.current = key; setRoomsCleared(0); const newRoom = generateSpireFloorState(currentFloor, 0, totalRooms); setCurrentRoom(newRoom); diff --git a/src/components/game/tabs/SpireSummaryTab.tsx b/src/components/game/tabs/SpireSummaryTab.tsx index f97d554..792f0e3 100644 --- a/src/components/game/tabs/SpireSummaryTab.tsx +++ b/src/components/game/tabs/SpireSummaryTab.tsx @@ -4,7 +4,7 @@ import { useMemo } from 'react'; import { useShallow } from 'zustand/react/shallow'; import { useCombatStore, usePrestigeStore } from '@/lib/game/stores'; import { FLOOR_ELEM_CYCLE } from '@/lib/game/constants'; -import { getGuardianForFloor } from '@/lib/game/data/guardian-encounters'; +import { getGuardianForFloor, getAllGuardianFloors } from '@/lib/game/data/guardian-encounters'; import { Card, CardContent } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { ScrollArea } from '@/components/ui/scroll-area';