feat: implement DoT/debuff runtime system (spec §6, AC-12, AC-13)
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m18s
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m18s
- 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)
This commit is contained in:
@@ -0,0 +1,348 @@
|
||||
# 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
|
||||
|
||||
```typescript
|
||||
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:
|
||||
|
||||
1. Computes total raw regen (base + attunement regen + discipline bonus + equipment) × temporalEcho × meditationMultiplier
|
||||
2. Subtracts incursion reduction: `× (1 - incursionStrength)`
|
||||
3. Computes total conversion drain: sum of all active attunement conversion rates
|
||||
4. Applies: `rawMana += totalRegen - totalConversionDrain` (per tick)
|
||||
5. For each attunement with conversion: adds `conversionRate × HOURS_PER_TICK` to 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
|
||||
|
||||
```typescript
|
||||
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 |
|
||||
Reference in New Issue
Block a user