diff --git a/docs/circular-deps.txt b/docs/circular-deps.txt index fef05fd..ee4de90 100644 --- a/docs/circular-deps.txt +++ b/docs/circular-deps.txt @@ -1,5 +1,5 @@ # Circular Dependencies -Generated: 2026-06-12T17:00:42.616Z +Generated: 2026-06-12T17:03:20.112Z Found: 4 circular chain(s) — these MUST be fixed before modifying involved files. 1. 1) data/guardian-encounters.ts > data/guardian-procedural.ts diff --git a/docs/dependency-graph.json b/docs/dependency-graph.json index 2d925c5..2f7c52d 100644 --- a/docs/dependency-graph.json +++ b/docs/dependency-graph.json @@ -1,6 +1,6 @@ { "_meta": { - "generated": "2026-06-12T17:00:40.332Z", + "generated": "2026-06-12T17:03:17.869Z", "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/specs/attunements/invoker/systems/invocation-system-spec.md b/docs/specs/attunements/invoker/systems/invocation-system-spec.md index a34d4a7..434e5d2 100644 --- a/docs/specs/attunements/invoker/systems/invocation-system-spec.md +++ b/docs/specs/attunements/invoker/systems/invocation-system-spec.md @@ -177,25 +177,27 @@ spell in `SPELLS_DEF` where `spell.elem` is `'metal'`, `'fire'`, or `'earth'`. ### 5.2 Spell Selection Algorithm -On activation and after each cast, the invocation spell is re-evaluated: +On activation and after each cast, the invocation spell is re-evaluated. +The player is channeling the **guardian's** power — the invocation spellbook is +all spells from the guardian's elements, regardless of what the player has +personally learned: ``` -1. Gather all spells from the guardian's elements -2. Filter to spells the player has learned (spells[spellId].learned === true) -3. Filter to spells the player can afford at the effective cost multiplier: +1. Gather all spells from the invoked guardian's elements (the full guardian spellbook) +2. Filter to spells the player can afford at the effective cost multiplier: - For raw cost: rawMana >= cost.amount × effectiveCostMultiplier - For element cost: elements[element].current >= cost.amount × effectiveCostMultiplier -4. From remaining spells, pick the one with the highest base damage (spell.dmg) -5. If tied, pick the highest tier -6. If still tied, pick the lowest cost (most efficient) +3. From remaining spells, pick the one with the highest base damage (spell.dmg) +4. If tied, pick the highest tier +5. If still tied, pick the lowest cost (most efficient) ``` -### 5.3 Fallback and Auto-End +### 5.3 Auto-End When Unaffordable -If no spells are affordable at the effective cost multiplier from the current -guardian: -1. Try the next guardian in the scoring order (§3.2) -2. If no guardian has affordable spells, invocation ends (§3.3, condition 4) +The invoked guardian is fixed for the entire invocation — the player cannot swap +to a different guardian mid-invocation. If the player cannot afford **any** spell +from the **invoked guardian's** elements at the effective cost multiplier, +invocation ends immediately (§3.3, condition 4). ### 5.4 Invocation Spell Cast Slot @@ -207,7 +209,7 @@ In `processCombatTick`, the invocation spell is processed similarly to equipment spells: - It has its own `castProgress` accumulator - It uses the same `HOURS_PER_TICK × spellCastSpeed × effectiveAttackSpeed` progress formula -- On cast completion, it deducts mana at the effective cost multiplier +- On cast **start**, it deducts mana at the effective cost multiplier (same pattern as the active spell) - Damage is calculated using the existing `calcDamage()` with the invocation spell's element The invocation spell does **not** interfere with the player's active spell. Both @@ -439,7 +441,6 @@ export function getGuardianSpellbook( // Select the best affordable spell from a spellbook export function selectInvocationSpell( spellbook: SpellDef[], - playerSpells: Record, rawMana: number, elements: Record, costMultiplier: number, @@ -476,11 +477,10 @@ spell block, add an **invocation block**: b. If charge <= 0: end invocation (but let current cast finish) c. Accumulate cast progress for invocation spell d. On cast completion: - - Deduct mana at effectiveCostMultiplier - Calculate damage using calcDamage() with invocation spell element - Apply damage via applyDamageToRoom() - Re-evaluate spell selection (§5.2) - - If no affordable spell from any guardian: end invocation + - If no affordable spell from the invoked guardian: end invocation e. If room cleared: end invocation 2. If activeInvocation === null AND invocationCharge >= 100 @@ -563,9 +563,9 @@ gameStore.tick() | AC-1 | `invocationCharge` fills passively at `baseFillRate × pactCountMultiplier × disciplineMultiplier` per tick while in `climb` action and not invoking. | | AC-2 | Invocation auto-activates when charge ≥ 100, `activeInvocation === null`, and room has enemies. | | AC-3 | The channeled guardian is selected by `bestElementalBonus × tierMultiplier` scoring. | -| AC-4 | The invocation spell is the highest-damage learned spell the player can afford at the effective cost multiplier. | +| AC-4 | The invocation spell is the highest-damage spell from the guardian's elements that the player can afford at the effective cost multiplier (not limited to spells the player has learned). | | AC-5 | Invocation spell steps down to the next affordable spell when the current one becomes unaffordable. | -| AC-6 | If no guardian has affordable spells, invocation ends. | +| AC-6 | If no spells are affordable from the invoked guardian's elements, invocation ends. | | AC-7 | Invocation ends when charge reaches 0, room is cleared, or player leaves `climb`. | | AC-8 | A cast already in progress completes even if charge hits 0 during the cast. | | AC-9 | After invocation ends, charge must fully recharge to 100 before invocation can reactivate. |