- Fix conversion rate level scaling from linear (1+level*0.5) to exponential (1.5^(level-1)) in conversion-rates.ts - Fix getAttunementLevelMultiplier formula to match spec §4.3 - Add level-up logging in attunementStore.ts via combat store addActivityLog - Clarify getAttunementConversionRate returns flat base rate (level scaling applied separately) - Update spec §8 to describe time-based puzzle room system matching code implementation - Add 17 regression tests verifying exponential scaling, base rate behavior, and spec table values
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 |
Time-based progression system: Each puzzle room has a base time requirement
that varies by floor range (4h for floors 1–20, 8h for 21–50, 16h for 51–100,
24h for 101+). Each relevant attunement reduces the total time needed, up to
a maximum 90% reduction shared across all relevant attunements. Progress
accumulates at HOURS_PER_TICK (0.04h) per tick. The room completes when
puzzleProgress >= puzzleRequired.
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 |