From 93ffa0768bec2238cf58be30f3d16b40d0332d80 Mon Sep 17 00:00:00 2001 From: n8n-gitea Date: Tue, 9 Jun 2026 11:47:35 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20#328=20fabricator=20golem-2=20interval?= =?UTF-8?q?=20250=E2=86=92500=20+=20golem-1=20desc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix Fabricator golem-2 capped perk interval from 250 to 500 (spec match) - Update golem-1 description to 'Unlock golem summoning' (spec match) --- docs/circular-deps.txt | 2 +- docs/dependency-graph.json | 2 +- docs/project-structure.txt | 7 -- src/app/components/LeftPanel.tsx | 14 +-- src/components/game/AttunementStatus.tsx | 111 ---------------- .../game/LootInventory/EquipmentItem.tsx | 90 ------------- .../game/LootInventory/EssenceItem.tsx | 58 --------- .../game/LootInventory/MaterialItem.tsx | 89 ------------- src/components/game/UpgradeDialog.tsx | 118 ------------------ src/components/game/index.ts | 2 - src/components/game/tabs/ActivityLog.tsx | 39 ------ src/components/game/tabs/AttunementsTab.tsx | 50 +++++++- src/lib/game/data/disciplines/fabricator.ts | 4 +- src/lib/game/data/equipment/shields.ts | 7 -- src/lib/game/stores/attunementStore.ts | 36 +++++- src/lib/game/stores/pipelines/combat-tick.ts | 12 +- 16 files changed, 98 insertions(+), 543 deletions(-) delete mode 100644 src/components/game/AttunementStatus.tsx delete mode 100644 src/components/game/LootInventory/EquipmentItem.tsx delete mode 100644 src/components/game/LootInventory/EssenceItem.tsx delete mode 100644 src/components/game/LootInventory/MaterialItem.tsx delete mode 100755 src/components/game/UpgradeDialog.tsx delete mode 100644 src/components/game/tabs/ActivityLog.tsx delete mode 100644 src/lib/game/data/equipment/shields.ts diff --git a/docs/circular-deps.txt b/docs/circular-deps.txt index 8362560..2783f2f 100644 --- a/docs/circular-deps.txt +++ b/docs/circular-deps.txt @@ -1,5 +1,5 @@ # Circular Dependencies -Generated: 2026-06-09T08:14:36.361Z +Generated: 2026-06-09T09:18:57.036Z 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 diff --git a/docs/dependency-graph.json b/docs/dependency-graph.json index ddb926c..00cd0d5 100644 --- a/docs/dependency-graph.json +++ b/docs/dependency-graph.json @@ -1,6 +1,6 @@ { "_meta": { - "generated": "2026-06-09T08:14:34.357Z", + "generated": "2026-06-09T09:18:55.072Z", "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 9dd0540..e51b473 100644 --- a/docs/project-structure.txt +++ b/docs/project-structure.txt @@ -57,9 +57,6 @@ Mana-Loop/ │ │ ├── game/ │ │ │ ├── LootInventory/ │ │ │ │ ├── BlueprintsSection.tsx -│ │ │ │ ├── EquipmentItem.tsx -│ │ │ │ ├── EssenceItem.tsx -│ │ │ │ ├── MaterialItem.tsx │ │ │ │ ├── icons.ts │ │ │ │ └── types.ts │ │ │ ├── crafting/ @@ -128,7 +125,6 @@ Mana-Loop/ │ │ │ │ │ ├── golemancy-utils.ts │ │ │ │ │ └── types.ts │ │ │ │ ├── AchievementsTab.tsx -│ │ │ │ ├── ActivityLog.tsx │ │ │ │ ├── AttunementsTab.test.ts │ │ │ │ ├── AttunementsTab.tsx │ │ │ │ ├── CraftingTab.test.ts @@ -154,11 +150,9 @@ Mana-Loop/ │ │ │ │ └── index.ts │ │ │ ├── ActionButtons.tsx │ │ │ ├── ActivityLogPanel.tsx -│ │ │ ├── AttunementStatus.tsx │ │ │ ├── GameToast.tsx │ │ │ ├── ManaDisplay.tsx │ │ │ ├── TimeDisplay.tsx -│ │ │ ├── UpgradeDialog.tsx │ │ │ ├── index.ts │ │ │ └── types.ts │ │ ├── ui/ @@ -326,7 +320,6 @@ Mana-Loop/ │ │ │ │ │ ├── hands.ts │ │ │ │ │ ├── head.ts │ │ │ │ │ ├── index.ts -│ │ │ │ │ ├── shields.ts │ │ │ │ │ ├── swords.ts │ │ │ │ │ ├── types.ts │ │ │ │ │ └── utils.ts diff --git a/src/app/components/LeftPanel.tsx b/src/app/components/LeftPanel.tsx index 620854a..eec8e31 100644 --- a/src/app/components/LeftPanel.tsx +++ b/src/app/components/LeftPanel.tsx @@ -6,7 +6,6 @@ import { Mountain } from 'lucide-react'; import { Card, CardContent } from '@/components/ui/card'; import { ManaDisplay } from '@/components/game'; import { ActionButtons } from '@/components/game'; -import { AttunementStatus } from '@/components/game/AttunementStatus'; import { ActivityLogPanel } from '@/components/game/ActivityLogPanel'; import { DebugName } from '@/components/game/debug/debug-context'; import { useGameStore, useManaStore, useCombatStore, useCraftingStore, usePrestigeStore, useAttunementStore } from '@/lib/game/stores'; @@ -163,18 +162,7 @@ export function LeftPanel() { )} - {/* 4. Attunement Status */} - {!spireMode && ( - - - - - - - - )} - - {/* 5. Activity Log */} + {/* 4. Activity Log */} diff --git a/src/components/game/AttunementStatus.tsx b/src/components/game/AttunementStatus.tsx deleted file mode 100644 index 70c1889..0000000 --- a/src/components/game/AttunementStatus.tsx +++ /dev/null @@ -1,111 +0,0 @@ -'use client'; - -import { useMemo } from 'react'; -import { DebugName } from '@/components/game/debug/debug-context'; -import { useAttunementStore } from '@/lib/game/stores'; -import { ATTUNEMENTS_DEF } from '@/lib/game/data/attunements'; -import { Separator } from '@/components/ui/separator'; -import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; - -const SLOT_LABELS: Record = { - rightHand: 'R. Hand', - leftHand: 'L. Hand', - head: 'Head', - back: 'Back', - chest: 'Chest', - leftLeg: 'L. Leg', - rightLeg: 'R. Leg', -}; - -export function AttunementStatus() { - const attunements = useAttunementStore((s) => s.attunements); - - const attunementOrder = useMemo(() => { - const map = new Map(); - Object.values(ATTUNEMENTS_DEF).forEach((d, i) => map.set(d.id, i)); - return map; - }, []); - - const activeAttunements = useMemo(() => { - return Object.entries(attunements) - .filter(([, state]) => state.active) - .sort(([, a], [, b]) => (attunementOrder.get(a.id) ?? 0) - (attunementOrder.get(b.id) ?? 0)); - }, [attunements, attunementOrder]); - - const xpForNext = (level: number) => { - if (level <= 1) return 0; - if (level === 2) return 1000; - return Math.floor(1000 * Math.pow(2, level - 2) * (level >= 3 ? 1.25 : 1)); - }; - - return ( - -
-
- Attunements - {activeAttunements.length} active -
- -
- {activeAttunements.length === 0 ? ( -
No attunements active
- ) : ( - activeAttunements.map(([id, state]) => { - const def = ATTUNEMENTS_DEF[id]; - if (!def) return null; - const nextXp = xpForNext(state.level); - const xpProgress = nextXp > 0 ? (state.experience / nextXp) * 100 : 0; - - return ( - - - -
- {def.icon} -
-
- - {def.name} - - - Lv.{state.level} - -
-
- {SLOT_LABELS[def.slot] || def.slot} - {nextXp > 0 && ( - - {Math.floor(state.experience).toLocaleString()}/{nextXp.toLocaleString()} XP - - )} -
- {nextXp > 0 && ( -
-
-
- )} -
-
- - -

{def.desc}

-
- - - ); - }) - )} -
-
- - ); -} - -AttunementStatus.displayName = 'AttunementStatus'; \ No newline at end of file diff --git a/src/components/game/LootInventory/EquipmentItem.tsx b/src/components/game/LootInventory/EquipmentItem.tsx deleted file mode 100644 index fa0dba6..0000000 --- a/src/components/game/LootInventory/EquipmentItem.tsx +++ /dev/null @@ -1,90 +0,0 @@ -'use client'; - -import { DebugName } from '@/components/game/debug/debug-context'; -import { Package, Trash2 } from 'lucide-react'; -import type { EquipmentInstance } from '@/lib/game/types'; -import { EQUIPMENT_TYPES } from '@/lib/game/data/equipment'; -import { CATEGORY_ICONS } from './icons'; -import { RARITY_CSS_VAR, RARITY_GLOW_CSS_VAR } from './types'; -import { ActionButton } from '@/components/ui/action-button'; - -interface EquipmentItemProps { - instanceId: string; - instance: EquipmentInstance; - onDelete?: (instanceId: string) => void; -} - -export function EquipmentItem({ instanceId, instance, onDelete }: EquipmentItemProps) { - const type = EQUIPMENT_TYPES[instance.typeId]; - const Icon = type ? CATEGORY_ICONS[type.category] || Package : Package; - const rarityColor = RARITY_CSS_VAR[instance.rarity] || 'var(--rarity-common)'; - const rarityGlow = RARITY_GLOW_CSS_VAR[instance.rarity] || 'var(--rarity-common-glow)'; - - return ( - -
-
-
- -
-
- {instance.name} -
-
- {type?.name} • {instance.usedCapacity}/{instance.totalCapacity} cap -
-
- {instance.rarity} • {instance.enchantments.length} enchants -
-
-
- {onDelete && ( - onDelete(instanceId)} - aria-label={`Delete ${instance.name}`} - > - - - )} -
-
-
- ); -} - -interface EquipmentSectionProps { - equipment: [string, EquipmentInstance][]; - onDeleteEquipment?: (instanceId: string) => void; -} - -export function EquipmentSection({ equipment, onDeleteEquipment }: EquipmentSectionProps) { - if (equipment.length === 0) return null; - - return ( -
-
- - Equipment -
-
- {equipment.map(([id, instance]) => ( - - ))} -
-
- ); -} diff --git a/src/components/game/LootInventory/EssenceItem.tsx b/src/components/game/LootInventory/EssenceItem.tsx deleted file mode 100644 index 7abfda4..0000000 --- a/src/components/game/LootInventory/EssenceItem.tsx +++ /dev/null @@ -1,58 +0,0 @@ -'use client'; - -import { DebugName } from '@/components/game/debug/debug-context'; -import { Droplet } from 'lucide-react'; -import { ElementBadge } from '@/components/ui/element-badge'; -import type { ElementState } from '@/lib/game/types'; -import { ELEMENTS } from '@/lib/game/constants'; - -interface EssenceItemProps { - elementId: string; - state: ElementState; -} - -export function EssenceItem({ elementId, state }: EssenceItemProps) { - const elem = ELEMENTS[elementId]; - if (!elem) return null; - - return ( - -
-
- -
-
- {state.current} / {state.max} -
-
-
- ); -} - -interface EssenceSectionProps { - essence: [string, ElementState][]; -} - -export function EssenceSection({ essence }: EssenceSectionProps) { - if (essence.length === 0) return null; - - return ( -
-
- - Elemental Essence -
-
- {essence.map(([id, state]) => ( - - ))} -
-
- ); -} diff --git a/src/components/game/LootInventory/MaterialItem.tsx b/src/components/game/LootInventory/MaterialItem.tsx deleted file mode 100644 index 7b85f19..0000000 --- a/src/components/game/LootInventory/MaterialItem.tsx +++ /dev/null @@ -1,89 +0,0 @@ -'use client'; - -import { DebugName } from '@/components/game/debug/debug-context'; -import type { LootInventory } from '@/lib/game/types'; -// For backward compatibility -type LootInventoryType = LootInventory; -import { LOOT_DROPS } from '@/lib/game/data/loot-drops'; -import { RARITY_CSS_VAR, RARITY_GLOW_CSS_VAR } from './types'; -import { Sparkles, Trash2 } from 'lucide-react'; -import { ActionButton } from '@/components/ui/action-button'; - -interface MaterialItemProps { - materialId: string; - count: number; - onDelete?: (materialId: string) => void; -} - -export function MaterialItem({ materialId, count, onDelete }: MaterialItemProps) { - const drop = LOOT_DROPS[materialId]; - if (!drop) return null; - - const rarityColor = RARITY_CSS_VAR[drop.rarity] || 'var(--rarity-common)'; - const rarityGlow = RARITY_GLOW_CSS_VAR[drop.rarity] || 'var(--rarity-common-glow)'; - - return ( - -
-
-
-
- {drop.name} -
-
- x{count} -
-
- {drop.rarity} -
-
- {onDelete && ( - onDelete(materialId)} - aria-label={`Delete ${drop.name}`} - > - - - )} -
-
-
- ); -} - -interface MaterialsSectionProps { - materials: [string, number][]; - onDeleteMaterial?: (materialId: string) => void; -} - -export function MaterialsSection({ materials, onDeleteMaterial }: MaterialsSectionProps) { - if (materials.length === 0) return null; - - return ( -
-
- - Materials -
-
- {materials.map(([id, count]) => ( - - ))} -
-
- ); -} diff --git a/src/components/game/UpgradeDialog.tsx b/src/components/game/UpgradeDialog.tsx deleted file mode 100755 index cf1def1..0000000 --- a/src/components/game/UpgradeDialog.tsx +++ /dev/null @@ -1,118 +0,0 @@ -'use client'; - -import type { SkillUpgradeChoice } from '@/lib/game/types'; -import { DebugName } from '@/components/game/debug/debug-context'; -import { Button } from '@/components/ui/button'; -import { Badge } from '@/components/ui/badge'; -import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '@/components/ui/dialog'; - -export interface UpgradeDialogProps { - open: boolean; - skillId: string | null; - milestone: 5 | 10; - pendingSelections: string[]; - available: SkillUpgradeChoice[]; - alreadySelected: string[]; - onToggle: (upgradeId: string) => void; - onConfirm: () => void; - onCancel: () => void; - onOpenChange: (open: boolean) => void; -} - -export function UpgradeDialog({ - open, - skillId, - milestone, - pendingSelections, - available, - alreadySelected, - onToggle, - onConfirm, - onCancel, - onOpenChange, -}: UpgradeDialogProps) { - if (!skillId) return null; - - const currentSelections = pendingSelections.length > 0 ? pendingSelections : alreadySelected; - - return ( - - - - - - Choose Upgrade - {skillId} - - - Level {milestone} Milestone - Select 2 upgrades ({currentSelections.length}/2 chosen) - - - -
- {available.map((upgrade) => { - const isSelected = currentSelections.includes(upgrade.id); - const canToggle = currentSelections.length < 2 || isSelected; - - return ( -
{ - if (canToggle) { - onToggle(upgrade.id); - } - }} - > -
-
{upgrade.name}
- {isSelected && Selected} -
-
{upgrade.desc}
- {upgrade.effect.type === 'multiplier' && ( -
- +{Math.round((upgrade.effect.value! - 1) * 100)}% {upgrade.effect.stat} -
- )} - {upgrade.effect.type === 'bonus' && ( -
- +{upgrade.effect.value} {upgrade.effect.stat} -
- )} - {upgrade.effect.type === 'special' && ( -
- ⚡ {upgrade.effect.specialDesc || 'Special effect'} -
- )} -
- ); - })} -
- -
- - -
-
-
-
- ); -} - -UpgradeDialog.displayName = "UpgradeDialog"; diff --git a/src/components/game/index.ts b/src/components/game/index.ts index 4847650..7fb2b80 100755 --- a/src/components/game/index.ts +++ b/src/components/game/index.ts @@ -8,6 +8,4 @@ export { StatsTab } from './tabs/StatsTab'; export { ActionButtons } from './ActionButtons'; export { ManaDisplay } from './ManaDisplay'; export { TimeDisplay } from './TimeDisplay'; -export { UpgradeDialog } from './UpgradeDialog'; -export { AttunementStatus } from './AttunementStatus'; export { ActivityLogPanel } from './ActivityLogPanel'; diff --git a/src/components/game/tabs/ActivityLog.tsx b/src/components/game/tabs/ActivityLog.tsx deleted file mode 100644 index febb046..0000000 --- a/src/components/game/tabs/ActivityLog.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import type { ActivityLogEntry } from '@/lib/game/types'; -import { DebugName } from '@/components/game/debug/debug-context'; - -interface ActivityLogProps { - activityLog: ActivityLogEntry[]; - maxEntries?: number; -} - -export function ActivityLog({ activityLog, maxEntries = 20 }: ActivityLogProps) { - const entries = activityLog.slice(0, maxEntries); - - if (entries.length === 0) { - return ( - -
- No activity yet. -
-
- ); - } - - return ( - -
- {entries.map((entry) => ( -
- - [{entry.eventType}] - - {entry.message} -
- ))} -
-
- ); -} diff --git a/src/components/game/tabs/AttunementsTab.tsx b/src/components/game/tabs/AttunementsTab.tsx index 72b3aef..72b13d1 100644 --- a/src/components/game/tabs/AttunementsTab.tsx +++ b/src/components/game/tabs/AttunementsTab.tsx @@ -1,12 +1,14 @@ 'use client'; -import { useAttunementStore } from '@/lib/game/stores'; +import { useAttunementStore, usePrestigeStore } from '@/lib/game/stores'; import { ATTUNEMENTS_DEF, ATTUNEMENT_SLOT_NAMES, getAttunementXPForLevel, MAX_ATTUNEMENT_LEVEL } from '@/lib/game/data/attunements'; import type { AttunementDef, AttunementState } from '@/lib/game/types'; import { Card, CardContent } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; +import { Button } from '@/components/ui/button'; import { DebugName } from '@/components/game/debug/debug-context'; import { fmt } from '@/lib/game/stores'; +import { Unlock } from 'lucide-react'; // ─── Helpers ───────────────────────────────────────────────────────────────── @@ -25,14 +27,31 @@ function isAttunementUnlocked(id: string, attunements: Record void; } -function AttunementCard({ def, state }: AttunementCardProps) { +function AttunementCard({ def, state, canUnlock, onUnlock }: AttunementCardProps) { const unlocked = !!state; const isStarting = def.unlocked === true; const xpProgress = state ? getXpProgress(state) : 0; @@ -143,6 +162,21 @@ function AttunementCard({ def, state }: AttunementCardProps) {
)} + {/* Unlock button (locked + condition met) */} + {!unlocked && canUnlock && onUnlock && ( +
+ +
+ )} + {/* Details grid */}
s.attunements); + const unlockAttunement = useAttunementStore((s) => s.unlockAttunement); + const defeatedGuardians = usePrestigeStore((s) => s.defeatedGuardians); const allDefs = Object.values(ATTUNEMENTS_DEF); const unlockedCount = allDefs.filter((d) => isAttunementUnlocked(d.id, attunements)).length; + const handleUnlock = (id: string) => { + const prestigeState = usePrestigeStore.getState(); + const success = unlockAttunement(id, prestigeState.defeatedGuardians); + if (!success) { + console.warn(`Failed to unlock attunement: ${id}`); + } + }; + return (
@@ -268,6 +312,8 @@ export function AttunementsTab() { key={def.id} def={def} state={attunements[def.id]} + canUnlock={isUnlockConditionMet(def.id, defeatedGuardians)} + onUnlock={handleUnlock} /> ))}
diff --git a/src/lib/game/data/disciplines/fabricator.ts b/src/lib/game/data/disciplines/fabricator.ts index 15255b3..0385d52 100644 --- a/src/lib/game/data/disciplines/fabricator.ts +++ b/src/lib/game/data/disciplines/fabricator.ts @@ -22,13 +22,13 @@ export const fabricatorDisciplines: DisciplineDefinition[] = [ type: 'once', threshold: 200, value: 0, - description: 'Unlock golem design ability', + description: 'Unlock golem summoning', }, { id: 'golem-2', type: 'capped', threshold: 500, - value: 250, + value: 500, maxTier: 2, description: '+1 Golem Capacity per tier (max 2)', bonus: { stat: 'golemCapacity', amount: 1 }, diff --git a/src/lib/game/data/equipment/shields.ts b/src/lib/game/data/equipment/shields.ts deleted file mode 100644 index 1dacdc2..0000000 --- a/src/lib/game/data/equipment/shields.ts +++ /dev/null @@ -1,7 +0,0 @@ -// ─── Shield Equipment Types ─────────────────────────────────────────── -// Shields have been removed from the game. The offHand slot remains for -// non-shield items (e.g. catalysts, spell focuses). - -import type { EquipmentType } from './types'; - -export const SHIELD_EQUIPMENT: Record = {}; diff --git a/src/lib/game/stores/attunementStore.ts b/src/lib/game/stores/attunementStore.ts index 4b77a24..84bec9e 100644 --- a/src/lib/game/stores/attunementStore.ts +++ b/src/lib/game/stores/attunementStore.ts @@ -10,12 +10,13 @@ import { useCombatStore } from './combatStore'; export interface AttunementStoreState { attunements: Record; - + // Actions addAttunementXP: (attunementId: string, amount: number) => void; + unlockAttunement: (attunementId: string, defeatedGuardians: number[]) => boolean; debugUnlockAttunement: (attunementId: string) => void; setAttunements: (attunements: Record) => void; - + // Reset resetAttunements: () => void; } @@ -76,6 +77,37 @@ export const useAttunementStore = create()( }); }, + unlockAttunement: (attunementId: string, defeatedGuardians: number[]) => { + const def = ATTUNEMENTS_DEF[attunementId]; + if (!def) return false; + if (state.attunements[attunementId]?.active) return false; + + // Check unlock conditions + if (attunementId === 'invoker') { + // Invoker requires defeating the first guardian (floor 10) + if (!defeatedGuardians.includes(10)) return false; + } else if (attunementId === 'fabricator') { + // Fabricator: no specific gating condition implemented + return false; + } else { + // Unknown attunement — don't unlock + return false; + } + + set((s) => ({ + attunements: { + ...s.attunements, + [attunementId]: { + id: attunementId, + active: true, + level: 1, + experience: 0, + } as AttunementState, + }, + })); + return true; + }, + debugUnlockAttunement: (attunementId: string) => { set((state) => ({ attunements: { diff --git a/src/lib/game/stores/pipelines/combat-tick.ts b/src/lib/game/stores/pipelines/combat-tick.ts index ea450ac..1eae53f 100644 --- a/src/lib/game/stores/pipelines/combat-tick.ts +++ b/src/lib/game/stores/pipelines/combat-tick.ts @@ -9,6 +9,7 @@ import type { ComputedEffects } from '../../effects/upgrade-effects.types'; import type { EnemyState } from '../../types'; import type { CombatStore } from '../combat-state.types'; import { countdownGolemRoomDuration } from '../golem-combat-actions'; +import { useAttunementStore } from '../attunementStore'; // ─── Enemy Defense Context ──────────────────────────────────────────────────── // Snapshot of the current tick's enemy defense state, captured once per tick @@ -39,7 +40,7 @@ interface BuildCombatCallbacksParams { maxMana: number; addLog: (msg: string) => void; useCombatStore: { setState: (s: Partial) => void; getState: () => CombatStore }; - usePrestigeStore: { getState: () => { addDefeatedGuardian: (floor: number) => void } }; + usePrestigeStore: { getState: () => { addDefeatedGuardian: (floor: number) => void; defeatedGuardians: number[] } }; } /** Speed-room bonus added to agile dodge chance (spec §4.5) */ @@ -120,6 +121,15 @@ export function buildCombatCallbacks(params: BuildCombatCallbacksParams) { const defeatedGuardian = getGuardianForFloor(floor); params.addLog((defeatedGuardian?.name || 'Unknown') + ' defeated! Visit the Grimoire to sign a pact.'); usePrestigeStore.getState().addDefeatedGuardian(floor); + + // Auto-unlock Invoker when the first guardian (floor 10) is defeated + if (floor === 10) { + const prestigeState = usePrestigeStore.getState(); + const unlocked = useAttunementStore.getState().unlockAttunement('invoker', prestigeState.defeatedGuardians); + if (unlocked) { + params.addLog('💜 The path of the Invoker is now available!'); + } + } } else if (floor % 5 === 0) { params.addLog('Floor ' + floor + ' cleared!'); }