- Add ActiveEffect, EffectType types to game.ts; activeEffects + effectiveArmor on EnemyState - Add SpellOnHitEffect + onHitEffect field to SpellDefinition - Wire onHitEffect to fire (burn), death (curse), lightning (armor_corrode), frost (freeze), soul (bypassArmor burn) - Add applyOnHitEffect() — applies on-hit effect on successful spell hit (spec §6.2) - Add processDoTPhase() — ticks all active effects after weapon/golem attacks (spec §6.3) - Add bypassArmor/bypassBarrier support in applyEnemyDefenses() (AC-13) - Export standalone applyEnemyDefenses from combat-tick.ts for DoT pipeline - Split DoT runtime into separate dot-runtime.ts (135 lines) to keep combat-actions.ts under 400 lines - Update all enemy generation sites with activeEffects/effectiveArmor defaults - Fix test helpers for new required fields All 921 tests pass (45 test files)
12 KiB
Attunement System — Design Spec
Describes the three-attunement class system: Enchanter, Invoker, and Fabricator. Covers slot assignments, unlock conditions, leveling, regen/conversion scaling, discipline pool gating, and interaction with mana conversion and the incursion system.
1. Objective
Attunements are class-like specializations that gate access to discipline pools and unique capabilities. A player can have multiple attunements active simultaneously, each contributing raw mana regen and (for Enchanter and Fabricator) automatic mana conversion. Attunements level up independently through attunement-specific XP sources, scaling their regen and conversion rates exponentially.
Design goals:
- Three distinct attunements with unique identities and roles
- Attunements unlock over time, expanding the player's options
- Leveling provides meaningful exponential scaling without being mandatory
- Discipline pool access is gated behind attunement unlock status
- Invoker's lack of primary mana creates a distinct pact-dependent playstyle
2. The Three Attunements
2.1 Enchanter (Right Hand) — Starting Attunement
| Property | Value |
|---|---|
| ID | enchanter |
| Slot | rightHand |
| Icon | ✨ |
| Color | #1ABC9C (Teal) |
| Primary Mana | transference |
| Raw Mana Regen | +0.5/hour (base) |
| Conversion Rate | 0.2 raw→transference/hour (base) |
| Unlock | Starting (unlocked by default) |
| Capabilities | ['enchanting'] |
| Skill Categories | ['enchant', 'effectResearch'] |
Disciplines: 10 disciplines across 4 files (core: 4, utility: 2, spells: 3, special: 1)
2.2 Invoker (Chest) — Locked
| Property | Value |
|---|---|
| ID | invoker |
| Slot | chest |
| Icon | 💜 |
| Color | #9B59B6 (Purple) |
| Primary Mana | None (gains elemental mana from pacts) |
| Raw Mana Regen | +0.3/hour (base) |
| Conversion Rate | None (0 at all levels) |
| Unlock | Defeat first Guardian |
| Capabilities | ['pacts', 'guardianPowers', 'elementalMastery'] |
| Skill Categories | ['invocation', 'pact'] |
Disciplines: 2 disciplines
2.3 Fabricator (Left Hand) — Locked
| Property | Value |
|---|---|
| ID | fabricator |
| Slot | leftHand |
| Icon | ⚒️ |
| Color | #F4A261 (Earth) |
| Primary Mana | earth |
| Raw Mana Regen | +0.4/hour (base) |
| Conversion Rate | 0.25 raw→earth/hour (base) |
| Unlock | Prove crafting worth |
| Capabilities | ['golemCrafting', 'gearCrafting', 'earthShaping'] |
| Skill Categories | ['fabrication', 'golemancy'] |
Disciplines: 5 disciplines
3. Unlock Conditions
| Attunement | Condition | Implementation |
|---|---|---|
| Enchanter | Starting | Present in initial state: { active: true, level: 1, experience: 0 } |
| Invoker | Defeat first Guardian | Descriptive: "Defeat your first guardian and choose the path of the Invoker" |
| Fabricator | Prove crafting worth | Descriptive: "Prove your worth as a crafter" |
Unlocking is performed via debugUnlockAttunement(attunementId) in the store, which
initializes the attunement at { active: true, level: 1, experience: 0 }. The
conditions are currently descriptive strings rather than hard-coded mechanical checks.
4. Attunement Leveling
4.1 XP Thresholds
Level 1: 0 XP (starting)
Level 2: 1,000 XP
Level ≥ 3: Math.floor(1000 * Math.pow(2, level - 2) * 1.25)
| Level | XP Threshold | Cumulative XP |
|---|---|---|
| 1 | 0 | 0 |
| 2 | 1,000 | 1,000 |
| 3 | 2,500 | 3,500 |
| 4 | 5,000 | 8,500 |
| 5 | 10,000 | 18,500 |
| 6 | 20,000 | 38,500 |
| 7 | 40,000 | 78,500 |
| 8 | 80,000 | 158,500 |
| 9 | 160,000 | 318,500 |
| 10 | 320,000 | 638,500 |
Max Level: MAX_ATTUNEMENT_LEVEL = 10
4.2 Level-Up Mechanism
addAttunementXP(attunementId, amount):
state.experience += amount
while state.experience >= xpForNextLevel && level < MAX:
state.experience -= xpForNextLevel
level += 1
log("Attunement leveled up!")
XP does not roll over beyond the threshold check — the threshold amount is subtracted and any remainder carries into the next level.
4.3 Regen and Conversion Rate Scaling
Both raw mana regen and conversion rate use the same exponential formula:
scaledValue = baseValue × 1.5^(level - 1)
Effective raw mana regen by level (per attunement):
| Level | Enchanter (0.5) | Invoker (0.3) | Fabricator (0.4) |
|---|---|---|---|
| 1 | 0.500/hr | 0.300/hr | 0.400/hr |
| 2 | 0.750/hr | 0.450/hr | 0.600/hr |
| 3 | 1.125/hr | 0.675/hr | 0.900/hr |
| 4 | 1.688/hr | 1.013/hr | 1.350/hr |
| 5 | 2.531/hr | 1.519/hr | 2.025/hr |
| 6 | 3.797/hr | 2.278/hr | 3.038/hr |
| 7 | 5.695/hr | 3.417/hr | 4.556/hr |
| 8 | 8.543/hr | 5.126/hr | 6.834/hr |
| 9 | 12.814/hr | 7.689/hr | 10.252/hr |
| 10 | 19.221/hr | 11.533/hr | 15.377/hr |
Effective conversion rate by level:
| Level | Enchanter (0.2) | Fabricator (0.25) |
|---|---|---|
| 1 | 0.200/hr | 0.250/hr |
| 2 | 0.300/hr | 0.375/hr |
| 3 | 0.450/hr | 0.563/hr |
| 4 | 0.675/hr | 0.844/hr |
| 5 | 1.013/hr | 1.266/hr |
| 6 | 1.519/hr | 1.898/hr |
| 7 | 2.278/hr | 2.848/hr |
| 8 | 3.417/hr | 4.271/hr |
| 9 | 5.126/hr | 6.407/hr |
| 10 | 7.689/hr | 9.610/hr |
Invoker has conversionRate = 0 at all levels — no auto-conversion.
Total regen = sum of baseRegen × 1.5^(level-1) across all active attunements.
Total conversion drain = sum of baseConversionRate × 1.5^(level-1) across active attunements
that have a non-zero conversion rate. This drain is applied to the raw mana pool.
5. Attunement XP Gain Sources
5.1 Enchanting → Enchanter XP
calculateEnchantingXP(capacityUsed: number): number {
return Math.max(1, Math.floor(capacityUsed / 10));
}
- 1 Enchanter XP per 10 capacity used (floored), minimum 1 XP per enchant.
5.2 Other Sources
The addAttunementXP(attunementId, amount) store action is the generic mechanism.
Any system can call it to award XP to any attunement. In the codebase as-is,
only enchanting has an explicit calculation function. Invoker and Fabricator XP
gain is expected to be called from their respective systems (pact signing and
item fabrication) but explicit calculation functions are not yet defined.
6. Discipline Pool Gating
6.1 Skill Categories
Attunements gate discipline access through skill categories:
| Category | Disciplines |
|---|---|
| Always available | mana, study, research |
| Enchanter | enchant, effectResearch |
| Invoker | invocation, pact |
| Fabricator | fabrication, golemancy |
The function getAvailableSkillCategories() iterates all active attunements,
collects their skillCategories into a Set, and returns the deduplicated array.
6.2 Discipline Pool Counts per Attunement
| Attunement | File | Count |
|---|---|---|
| Enchanter Core | enchanter.ts |
4 |
| Enchanter Utility | enchanter-utility.ts |
2 |
| Enchanter Spells | enchanter-spells.ts |
3 |
| Enchanter Special | enchanter-special.ts |
1 |
| Invoker | invoker.ts |
2 |
| Fabricator | fabricator.ts |
5 |
| Attunement-gated total | 17 |
The remaining 47 disciplines are available regardless of attunement status (base, elemental, elemental-regen, elemental-regen-advanced pools).
6.3 Capability Gating
Each attunement grants capabilities that unlock specific game systems:
| Capability | System |
|---|---|
enchanting |
Enchantment Design/Prepare/Apply pipeline |
pacts |
Guardian pact signing and boon system |
guardianPowers |
Guardian power access |
elementalMastery |
Element mastery bonuses |
golemCrafting |
Golem summoning (Golemancy) |
gearCrafting |
Gear fabrication recipes |
earthShaping |
Earth mana shaping |
7. Mana Conversion Interaction
7.1 Conversion Flow
Each tick, the mana system:
- Computes total raw regen (base + attunement regen + discipline bonus + equipment) × temporalEcho × meditationMultiplier
- Subtracts incursion reduction:
× (1 - incursionStrength) - Computes total conversion drain: sum of all active attunement conversion rates
- Applies:
rawMana += totalRegen - totalConversionDrain(per tick) - For each attunement with conversion: adds
conversionRate × HOURS_PER_TICKto the target element
7.2 Invoker's Unique Position
The Invoker has no automatic conversion — conversionRate = 0. Instead, it gains
elemental mana types exclusively by signing Guardian pacts. Each guardian's
unlocksMana array is resolved through resolveMultiUnlockChain(element), which
unlocks the guardian's element and all base components.
Example: Signing a Metal guardian (floor 90) unlocks fire, earth, and metal.
7.3 Conversion and Incursion
Incursion reduces net raw mana regeneration:
effectiveRegen = max(0, baseRegen × (1 - incursionStrength) × meditationMult - totalConversionPerTick)
As incursion strength approaches 95% (day 30), conversion drains can exceed regen, causing raw mana to decrease. Since conversion is contingent on available raw mana, attunement conversion effectively stalls during peak incursion if the raw pool is insufficient.
8. Puzzle Room Interaction
From spire-climbing-spec.md §4.3, puzzle rooms appear on every 7th floor and have
per-attunement variants:
| Room Type | Description |
|---|---|
enchanter_trial |
Enchanter-themed puzzle challenge |
fabricator_trial |
Fabricator-themed puzzle challenge |
invoker_trial |
Invoker-themed puzzle challenge |
hybrid_enchanter_fabricator |
Dual attunement challenge |
hybrid_enchanter_invoker |
Dual attunement challenge |
hybrid_fabricator_invoker |
Dual attunement challenge |
Progress scales at 1.5–2% per tick base, with attunement bonus of 2.5–3% per relevant attunement level.
9. State Fields
interface AttunementState {
id: string;
active: boolean;
level: number; // 1–10
experience: number; // current XP toward next level
}
// Initial state (prestige):
attunements: {
enchanter: { id: 'enchanter', active: true, level: 1, experience: 0 }
}
10. Acceptance Criteria
| # | Criterion |
|---|---|
| AC-1 | Enchanter is the only active attunement at game start (level 1, 0 XP). |
| AC-2 | Invoker and Fabricator are locked until unlocked; their unlock conditions are displayed in the Attunements tab. |
| AC-3 | Attunement XP accumulates and triggers level-ups at the correct thresholds; each level requires the exact XP specified in the formula. |
| AC-4 | Regen and conversion rates scale by 1.5^(level-1) — a level 10 Enchanter converts at 7.69 raw→transference/hour. |
| AC-5 | Both raw regen and conversion from all active attunements are summed and applied each tick. |
| AC-6 | Invoker has no automatic mana conversion at any level. |
| AC-7 | Enchanting awards Enchanter XP at 1 per 10 capacity used (minimum 1). |
| AC-8 | Attunement skill categories correctly gate discipline pool access — Enchanter disciplines require Enchanter to be active. |
| AC-9 | Attunement tab shows unlocked/locked visual distinction, XP progress bar, level badge, and all attunement capabilities. |
| AC-10 | Puzzle rooms on every 7th floor use per-attunement room types with the correct progress scaling. |
| AC-11 | Incursion correctly reduces net raw mana regeneration, potentially stalling conversion at peak incursion. |
11. Files Reference
| File | Role |
|---|---|
src/lib/game/data/attunements.ts |
Attunement definitions (the 3 attunements) |
src/lib/game/stores/attunementStore.ts |
Attunement state, leveling, XP, unlock |
src/lib/game/types/attunements.ts |
Attunement type definitions |
src/components/game/tabs/AttunementsTab.tsx |
Attunement UI display |
src/lib/game/stores/manaStore.ts |
Mana regen, conversion, incursion effects |
docs/specs/spire-climbing-spec.md |
Puzzle room types per attunement |