feat: add Invocation System spec for Invoker attunement
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m22s
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m22s
- New spec at docs/specs/attunements/invoker/systems/invocation-system-spec.md - Invocation Charge meter (0-100) fills passively, drains while invoking - Channeled guardian auto-casts spells from their elements at reduced cost - Spell selection picks best affordable spell, steps down as mana depletes - No guardian swap mid-invocation; ends when charge depleted or room cleared - Pact Affinity extended with diminishing-returns cast speed bonus (max 50%) - New guardian-invocation discipline with 4 perks (efficiency, speed, sustain, mastery)
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
# Circular Dependencies
|
# 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.
|
Found: 4 circular chain(s) — these MUST be fixed before modifying involved files.
|
||||||
|
|
||||||
1. 1) data/guardian-encounters.ts > data/guardian-procedural.ts
|
1. 1) data/guardian-encounters.ts > data/guardian-procedural.ts
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"_meta": {
|
"_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.",
|
"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."
|
"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."
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -177,25 +177,27 @@ spell in `SPELLS_DEF` where `spell.elem` is `'metal'`, `'fire'`, or `'earth'`.
|
|||||||
|
|
||||||
### 5.2 Spell Selection Algorithm
|
### 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
|
1. Gather all spells from the invoked guardian's elements (the full guardian spellbook)
|
||||||
2. Filter to spells the player has learned (spells[spellId].learned === true)
|
2. Filter to spells the player can afford at the effective cost multiplier:
|
||||||
3. Filter to spells the player can afford at the effective cost multiplier:
|
|
||||||
- For raw cost: rawMana >= cost.amount × effectiveCostMultiplier
|
- For raw cost: rawMana >= cost.amount × effectiveCostMultiplier
|
||||||
- For element cost: elements[element].current >= 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)
|
3. From remaining spells, pick the one with the highest base damage (spell.dmg)
|
||||||
5. If tied, pick the highest tier
|
4. If tied, pick the highest tier
|
||||||
6. If still tied, pick the lowest cost (most efficient)
|
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
|
The invoked guardian is fixed for the entire invocation — the player cannot swap
|
||||||
guardian:
|
to a different guardian mid-invocation. If the player cannot afford **any** spell
|
||||||
1. Try the next guardian in the scoring order (§3.2)
|
from the **invoked guardian's** elements at the effective cost multiplier,
|
||||||
2. If no guardian has affordable spells, invocation ends (§3.3, condition 4)
|
invocation ends immediately (§3.3, condition 4).
|
||||||
|
|
||||||
### 5.4 Invocation Spell Cast Slot
|
### 5.4 Invocation Spell Cast Slot
|
||||||
|
|
||||||
@@ -207,7 +209,7 @@ In `processCombatTick`, the invocation spell is processed similarly to equipment
|
|||||||
spells:
|
spells:
|
||||||
- It has its own `castProgress` accumulator
|
- It has its own `castProgress` accumulator
|
||||||
- It uses the same `HOURS_PER_TICK × spellCastSpeed × effectiveAttackSpeed` progress formula
|
- 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
|
- 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
|
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
|
// Select the best affordable spell from a spellbook
|
||||||
export function selectInvocationSpell(
|
export function selectInvocationSpell(
|
||||||
spellbook: SpellDef[],
|
spellbook: SpellDef[],
|
||||||
playerSpells: Record<string, SpellState>,
|
|
||||||
rawMana: number,
|
rawMana: number,
|
||||||
elements: Record<string, { current: number; max: number; unlocked: boolean }>,
|
elements: Record<string, { current: number; max: number; unlocked: boolean }>,
|
||||||
costMultiplier: number,
|
costMultiplier: number,
|
||||||
@@ -476,11 +477,10 @@ spell block, add an **invocation block**:
|
|||||||
b. If charge <= 0: end invocation (but let current cast finish)
|
b. If charge <= 0: end invocation (but let current cast finish)
|
||||||
c. Accumulate cast progress for invocation spell
|
c. Accumulate cast progress for invocation spell
|
||||||
d. On cast completion:
|
d. On cast completion:
|
||||||
- Deduct mana at effectiveCostMultiplier
|
|
||||||
- Calculate damage using calcDamage() with invocation spell element
|
- Calculate damage using calcDamage() with invocation spell element
|
||||||
- Apply damage via applyDamageToRoom()
|
- Apply damage via applyDamageToRoom()
|
||||||
- Re-evaluate spell selection (§5.2)
|
- 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
|
e. If room cleared: end invocation
|
||||||
|
|
||||||
2. If activeInvocation === null AND invocationCharge >= 100
|
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-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-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-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-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-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-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. |
|
| AC-9 | After invocation ends, charge must fully recharge to 100 before invocation can reactivate. |
|
||||||
|
|||||||
Reference in New Issue
Block a user