Files
Mana-Loop/docs/specs/attunements/enchanter/systems/room-enchantments-spec.md
T
n8n-gitea 718aed38b1
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m21s
feat: implement Transference Channel system for Enchanter attunement
- Add isChanneling, channelSpeedMultiplier, channelDrainRate to CombatState
- Add startChanneling/stopChanneling actions to combat store
- Add transference-channeling discipline with 3 perks (channel-efficiency, channel-power, channel-mastery)
- Add channelIntensity and channelEfficiency to KNOWN_BONUS_STATS
- Create combat-channel.ts with drain + speed multiplier computation
- Apply channel speed multiplier to equipment spells and melee attacks
- Add Channel Transference hold-button UI to SpireCombatPage
- Add compact channel status indicator to SpireCombatControls
- Channel state resets on spire exit, persists across room transitions
- All 1235 existing tests pass
2026-06-14 21:56:20 +02:00

24 KiB
Raw Blame History

Room Enchantments System — Design Spec

Describes the Room Enchantments system: a semi-combat, semi-preparation system for the Enchanter attunement. Footwear enchantments passively stamp the room with elemental auras during combat. The longer the fight, the more the room becomes the Enchanter's weapon.


1. Objective

The Enchanter currently has no active combat system. The enchanting pipeline (Design → Prepare → Apply) is entirely offline. The Room Enchantments system adds an always-on combat presence that scales with time: as the Enchanter fights in a room, their enchanted footwear gradually covers the room with elemental aura effects that damage enemies, apply debuffs, or buff the player.

Design goals:

  • Give the Enchanter an active combat identity distinct from Invocation (parallel cast track) and Golemancy (summoned units): the environmental controller
  • Create a "semi-combat, semi-preparation" loop: choose footwear enchantments offline, then they work passively during combat with no active input required
  • Give transference mana a combat application through the boots_sigil_transference enchantment and the coverage-rate discipline
  • Make longer fights (especially guardians) more rewarding the longer they go
  • Keep the system simple: one coverage meter, linear scaling, no thresholds or activation decisions during combat

2. Identity

Property Value
System name Room Enchantments
Attunement Enchanter
Equipment slot Feet
Core resource Coverage meter (0100) per room
Primary mana Transference (discipline fuel + transference sigil regen)
Combat role Environmental aura control — scales with time-in-combat
Preparation Enchant boots via the existing Design → Prepare → Apply pipeline
Active element None — entirely passive during combat

Attunement Comparison

Attunement Combat Identity Scaling Player Input
Invoker Parallel auto-cast (guardian spells) Charge meter fill/spend Auto-activate
Fabricator Summoned golems (independent actors) Golem maintenance Design golems offline
Enchanter Environmental aura control Time-in-combat coverage Enchant boots offline

3. Core Mechanic: The Coverage Meter

3.1 The Meter

Each room has a single coverage meter from 0 to 100, stored on FloorState:

// Addition to FloorState in types/game.ts
roomEnchantment: {
  coverage: number;   // 0-100, current coverage percentage
} | null;             // null when no footwear room enchantments are equipped
  • null when the player has no footwear with room enchantment sigils equipped.
  • Initialized to { coverage: 0 } on room entry if footwear has room enchantments.
  • Resets to 0 on every room transition (fresh room, fresh canvas).

3.2 Coverage Growth

Coverage grows by a flat amount per combat tick:

coveragePerTick = baseRate + disciplineBonus

where:
  baseRate = 0.2
  disciplineBonus = roomCoverageRateBonus (from the `room-coverage-rate` capped perk)

At base rate (no discipline bonus):

  • 0.2 per tick → 500 ticks to reach 100
  • At 200ms/tick → 100 seconds real time to full coverage

Coverage growth requires ALL of the following:

  1. Player is in climb action
  2. Current room has living enemies (floorHP > 0)
  3. Player has at least one footwear room enchantment equipped

Coverage does NOT scale with cast speed, attack speed, transference spending, or any other stat. It is purely time-based. This is intentional — the system is designed to be zero-input during combat.

3.3 Coverage Cap

Coverage is hard-capped at 100. Once full, room enchantments operate at full strength and stop accumulating.


4. Enchantment Definitions

4.1 Effect Category

Room enchantments use category: 'special' with unique specialId values, following the exact same pattern as existing equipment effects (e.g., sword_firespecialId: 'fireBlade'). They are defined alongside existing enchantment definitions.

Allowed equipment category: ['feet'] only.

4.2 Effect Structure

// Room enchantment effects use the same EnchantmentEffectDef type
// category: 'special'
// effect.type: 'special'
// effect.specialId: a unique string handled by the room enchantment tick processor

The specialId is a new convention: room_<element>_<effectType> (e.g., room_fire_damage, room_frost_debuff, room_transference_buff).

Note: Room enchantment specialId values use the game's existing mana type names (e.g., death not poison, transference not arcane) for consistency with the 22 defined mana types.

4.3 Scaling Formula

Each room enchantment's effect scales linearly with coverage:

effectMagnitude = baseMagnitude × (coverage / 100)

Examples:

  • boots_sigil_fire at 25% coverage: burn = 5 × 0.25 = 1.25 damage/tick to all enemies
  • boots_sigil_fire at 50% coverage: burn = 5 × 0.50 = 2.5 damage/tick to all enemies
  • boots_sigil_fire at 100% coverage: burn = 5 × 1.00 = 5 damage/tick to all enemies

All magnitudes are floating-point. Damage per tick is applied as-is (not rounded per tick; rounding only occurs on display).

4.4 Enchantment Table

Enchant ID Name specialId Effect Type Base Magnitude (at 100%) Capacity Cost
boots_sigil_fire Blazing Footsteps room_fire_damage Room DoT (burn all enemies) 5 dmg/tick 30
boots_sigil_frost Frozen Trail room_frost_debuff Enemy slow 10% slow 25
boots_sigil_death Necrotic Tread room_death_damage Room DoT (death all enemies) 3 dmg/tick 25
boots_sigil_lightning Shocking Stride room_lightning_damage Single-target chain dmg 3 dmg/tick random enemy 28
boots_sigil_dark Shadow Patch room_dark_dodge_debuff Enemy dodge reduction 10% dodge 22
boots_sigil_earth Scoured Earth room_earth_armor_debuff Enemy armor reduction 5% armor 28
boots_sigil_transference_ground Transference Grounds room_transference_buff Player cast speed +5% cast speed 20
boots_sigil_transference_path Conductive Path room_transference_regen Transference mana regen +10%/hr regen 20

All costs fit within footwear capacity range (1535), meaning players can fit 12 room enchantments per pair of boots.

4.5 Effect Application Rules

DoT effects (damage over time: fire, death, lightning):

  • Applied to ALL living enemies in the room each tick
  • Bypass armor — room-wide DoT ignores enemy armor, dodge, and barrier
  • Do NOT apply to dead enemies (hp ≤ 0)
  • For single-target effects (lightning): target is chosen randomly among living enemies each tick

Debuff effects (frost slow, dark dodge reduction, dark armor reduction):

  • Applied as a modifier to all living enemies, recalculated each tick based on current coverage
  • Slow: reduces enemy dodge chance (negative value subtracted from base dodge)
  • Dodge reduction: directly reduces enemy dodge chance
  • Armor reduction: directly reduces enemy effectiveArmor (minimum 0)
  • Applied at the end of the room enchantment phase (after the DoT/debuff phase). Debuffs take effect on the next tick — there is a one-tick delay before reduced dodge/armor affects incoming attacks. This is intentional: the room enchantment phase runs late in the tick pipeline.

Buff/Regen effects (transference cast speed, transference regen):

  • Applied as a modifier to the player's combat bonuses, recalculated each tick
  • Stored in the combat tick result and factored into existing computeAllEffects() math
  • Cast speed: added to attackSpeedMultiplier as 1 + (baseBonus × coverage%)
  • Transference regen: added to transference mana regen per hour as baseRegen × coverage%

Note on debuff timing: Because the room enchantment phase runs after the DoT/debuff phase in the tick pipeline, debuffs (frost slow, dark dodge reduction, dark armor reduction) are applied to enemy state at the end of the tick. The modified enemy stats take effect on the following tick when the enemy defense pipeline runs. This means there is always a one-tick delay between coverage growth and debuff impact on incoming attacks.

4.6 Stacking Multiple Enchantments

If the player has two room enchantments on their boots (e.g., boots_sigil_fire + boots_sigil_frost):

  • Both share the same coverage meter (there is only one)
  • Both scale off the same coverage percentage
  • Both apply their effects independently each tick
  • Total capacity cost must fit within the footwear's total capacity

5. State Changes

5.1 FloorState (types/game.ts)

export interface FloorState {
  // ... all existing fields ...

  // Room enchantment state — null when no footwear room enchants are equipped
  roomEnchantment: {
    coverage: number;   // 0-100
  } | null;
}

5.2 Combat Store Additions

The combat store needs a new field to persist the previous room's coverage across room transitions (for the resonant-stamps perk). Add to CombatState in combat-state.types.ts:

// Room enchantment carryover (for resonant-stamps perk)
lastRoomCoverage: number;  // 0-20, carryover from previous room, resets on spire exit

And the corresponding action:

setLastRoomCoverage: (value: number) => void;

5.3 Combat Processing

The room enchantment tick is processed in combat-actions.tsprocessCombatTick(), executed in the tick pipeline immediately after the DoT/debuff phase:

Tick order in processCombatTick:
  1. Golem maintenance
  2. Active spell casting
  3. Equipment spell states
  4. Invocation tick
  5. Melee attacks
  6. Golem attacks
  7. DoT/debuff tick processing
  8. ── Room enchantment tick ← NEW

> **File size note:** `combat-actions.ts` is currently 377 lines. Adding the room
> enchantment phase may push it toward the 400-line limit. If so, extract the phase
> logic into a new file (e.g., `combat-room-enchantments.ts`) and call it from
> `processCombatTick`.

5.4 New Utility File

A new file src/lib/game/utils/room-enchantments-utils.ts exports:

// Compute coverage per tick from discipline stats
export function computeCoveragePerTick(disciplineBonus: number): number;

// Apply all room enchantment effects for one tick
// Returns updated { enemies, rawMana, elementStates, playerBuffs }
export function applyRoomEnchantmentTick(params: {
  coverage: number;
  auraMagnitude: number;  // roomEnchantmentAuraMagnitude from discipline effects
  equippedRoomEnchantments: Array<{ specialId: string; baseMagnitude: number }>;
  enemies: EnemyState[];
  rawMana: number;
  elements: Record<string, { current: number; max: number; unlocked: boolean }>;
}): {
  enemies: EnemyState[];
  rawMana: number;
  elements: Record<string, { current: number; max: number; unlocked: boolean }>;
  playerBuffs: {
    castSpeedBonus: number;
    transferenceRegenBonus: number;
  };
};

// Get list of room enchantment effects from equipped footwear
export function getEquippedRoomEnchantments(
  equippedInstances: Record<string, string | null>,
  equipmentInstances: Record<string, EquipmentInstance>,
): Array<{ specialId: string; baseMagnitude: number }>;

5.5 Room Transition Behavior

Coverage resets on every room transition. This is handled in the existing advanceRoomOrFloor() flow in combat-descent-actions.ts:

  • When a new FloorState is generated (via generateSpireFloorState()), the new room starts with roomEnchantment: null
  • Just before the FloorState is finalized, the current room's coverage is saved to lastRoomCoverage on the combat store (capped at 20 if resonant-stamps perk is active, otherwise 0)
  • At the start of the combat tick, if roomEnchantment is null and the player has footwear room enchantments equipped, it is initialized to { coverage: lastRoomCoverage }
  • If the player has no footwear room enchantments, it stays null and the room enchantment tick is skipped entirely
  • lastRoomCoverage resets to 0 on spire exit

6. Discipline Integration

6.1 New Discipline: Room Enchanting

A new Enchanter discipline that directly enhances the Room Enchantment system.

Definition:

Field Value
ID room-enchanting
Name Room Enchanting
Attunement enchanter
Mana Type transference
Base Cost 10
Stat Bonus roomEnchantmentAuraMagnitude +0.10 (base)
Scaling Factor 100
Difficulty Factor 150
Drain Base 3

Main stat: roomEnchantmentAuraMagnitude

This stat is a multiplier on all room enchantment aura magnitudes:

effectMagnitude = baseMagnitude × coverage% × (1 + roomEnchantmentAuraMagnitude)

The stat scales with XP via the standard discipline math:

StatBonus = baseValue × (XP / scalingFactor)^0.65
          = 0.10 × (XP / 100)^0.65

This replaces the old empowered-auras perk — the main stat now continuously scales aura strength, while a new capped perk handles coverage rate.

Magnitude scaling at key XP levels:

XP Stat Bonus Magnitude Multiplier Fire DoT @ 100% coverage
0 +0.000 1.000× 5.00 dmg/tick
100 +0.100 1.100× 5.50 dmg/tick
300 +0.204 1.204× 6.02 dmg/tick
500 +0.285 1.285× 6.42 dmg/tick
1000 +0.447 1.447× 7.23 dmg/tick
2000 +0.701 1.701× 8.50 dmg/tick

Perks:

Perk ID Type Threshold Bonus Description
room-coverage-rate capped 100 Coverage rate +0.03/tick per tier, max 4 tiers Room fills faster with each tier
resonant-stamps once 500 Carry 20% coverage between rooms (max 20% starting coverage) Head start on each room, but never instant

Perk details:

  • room-coverage-rate (capped, threshold 100 XP, interval 150 XP, max 4 tiers): Each tier adds +0.03/tick to the coverage growth rate. Tier progression: 1 tier at 100 XP, 2 tiers at 250 XP, 3 tiers at 400 XP, 4 tiers at 550 XP (capped).

    Tiers Rate Bonus Total Rate Seconds to Fill
    0 (no perk) +0.00 0.20/tick 100.0s
    1 (100 XP) +0.03 0.23/tick 87.0s
    2 (250 XP) +0.06 0.26/tick 76.9s
    3 (400 XP) +0.09 0.29/tick 69.0s
    4 (550 XP) +0.12 0.32/tick 62.5s

    At max tier (4), the room fills in ~62.5 seconds instead of 100 seconds — a 37.5% speedup. The perk is capped so coverage always requires meaningful combat time.

  • resonant-stamps (once @ 500 XP): When transitioning between rooms on the same floor, the carryover is computed as min(20, previousRoom.coverage × 0.2). This means 20% of the previous room's coverage value carries over, capped at a maximum of 20 percentage points. Examples: ending at 80% → next room starts at 16%; ending at 100% → next room starts at 20% (the cap). This ensures the player always needs to actively build coverage during combat — the perk provides a head start but never eliminates the buildup phase.

    State persistence: The carryover value is stored in lastRoomCoverage on the combat store (see §5.2). When advanceRoomOrFloor() generates a new FloorState with roomEnchantment: null, the room enchantment tick reads lastRoomCoverage to initialize the new room's starting coverage. This field persists across room transitions within a climb but resets to 0 on spire exit.

Combined progression at key XP levels (stat + capped perk):

XP Coverage Rate Magnitude Fire DoT @ 100% Time to Fill
0 0.20/tick 1.000× 5.00 dmg/tick 100.0s
100 0.23/tick 1.100× 5.50 dmg/tick 87.0s
300 0.26/tick 1.204× 6.02 dmg/tick 76.9s
500 0.29/tick 1.285× 6.42 dmg/tick 69.0s
1000 0.32/tick 1.447× 7.23 dmg/tick 62.5s
2000 0.32/tick 1.701× 8.50 dmg/tick 62.5s

The capped perk maxes out at 550 XP (4 tiers, 0.32/tick, 62.5s). Beyond that, only the main stat continues to grow, increasing aura magnitude. At 2000 XP, the room still takes 62.5 seconds to fill but fire does 8.50 dmg/tick instead of 5.00 — a 70% damage increase from the main stat alone.

6.2 Discipline Dependency

room-enchanting (root — no prerequisites)

This is a root discipline with no prerequisites, making it available as soon as the Enchanter attunement is active.

6.3 Stat Registration

The new stat roomEnchantmentAuraMagnitude must be added to:

  • KNOWN_BONUS_STATS in discipline-effects.ts
  • The addBonus() routing in computeDisciplineEffects() (no special routing needed — it's a standard bonus stat)

6.4 Enchantment Unlock Perks

Room enchantment effects are unlocked via perks on the new discipline:

Perk ID Type Threshold Unlocks
room-sigil-fire once 50 boots_sigil_fire
room-sigil-frost once 75 boots_sigil_frost
room-sigil-death once 100 boots_sigil_death
room-sigil-lightning once 125 boots_sigil_lightning
room-sigil-dark once 150 boots_sigil_dark
room-sigil-earth once 175 boots_sigil_earth
room-sigil-transference-ground once 100 boots_sigil_transference_ground
room-sigil-transference-path once 125 boots_sigil_transference_path

7. Data Flow Summary

gameStore.tick()
  → buildTickContext() [snapshots all stores]
  → processCombatTick() [combat-actions.ts]
    → ... (golem, spell, equipment, invocation, melee, DoT phases) ...
    → Room Enchantment Phase:
        1. If roomEnchantment is null and player has footwear room enchants:
             Initialize roomEnchantment = { coverage: lastRoomCoverage }
        2. If roomEnchantment is not null AND floorHP > 0:
             a. coverage += 0.2 + roomCoverageRateBonus
             b. Clamp coverage to 100
             c. Magnitude multiplier = 1.0 + roomEnchantmentAuraMagnitude
             d. For each equipped room enchantment:
                - Compute magnitude = baseMagnitude × (coverage / 100) × magnitude multiplier
             e. Apply to enemies (DoT/debuffs) or player (buffs)
             f. Recalculate floorHP from updated enemy HP
    → applyTickWrites() [writes combat store changes back]

On room transition (advanceRoomOrFloor):
  → Before generating new FloorState, save current coverage to combat store:
     lastRoomCoverage = resonant-stamps active ? min(20, currentRoom.coverage * 0.2) : 0
  → New FloorState generated with roomEnchantment: null
  → Room enchantment tick reads lastRoomCoverage to initialize starting coverage

8. UI Changes

8.1 RoomDisplay Component

Add a Room Enchantment Bar to the combat room display in RoomDisplay.tsx, shown only when the player has footwear room enchantments equipped and the room has living enemies:

┌─────────────────────────────────────────────┐
│ 🔥 Blazing Footsteps ████████████░░░░ 62%   │
│    → Enemies burning: 3.1 dmg/tick          │
│❄️ Frozen Trail     ████████████░░░░ 62%    │
│    → Enemies slowed: 6.2%                   │
└─────────────────────────────────────────────┘
  • Progress bar showing coverage percentage (0100%)
  • Color-coded by element (fire = red, frost = blue, etc.)
  • Shows current effect magnitude in text below each bar
  • Only displayed when roomEnchantment is not null

8.2 Enchantment Designer

Room enchantment effects appear in the existing EffectSelector component under a new "Room" filter category (or under "Special" with a "Room Only" tag). They are only selectable when the equipment type being designed for is in the feet category.

8.3 No New Tabs

The room enchantment system does not require a new tab. All information is visible in the existing SpireCombatPage layout and the existing CraftingTab enchantment designer.


9. Acceptance Criteria

# Criterion
AC-1 roomEnchantment field exists on FloorState and defaults to null.
AC-2 Coverage initializes to { coverage: 0 } on room entry when footwear has room enchantments equipped.
AC-3 Coverage resets to 0 on every room transition (new room = fresh coverage).
AC-4 Coverage grows at 0.2 per tick while in climb action with living enemies.
AC-5 Coverage is hard-capped at 100.
AC-6 Coverage does NOT grow when floorHP <= 0 or currentAction !== 'climb'.
AC-7 At base rate (no discipline), coverage reaches 100 in ~100 seconds real time (500 ticks).
AC-8 Room DoT effects (fire, death, lightning) apply to ALL living enemies each tick, bypassing armor.
AC-9 Room debuff effects (frost slow, dark dodge reduction, earth armor reduction) modify enemy stats at end of room enchantment phase (one-tick delay before affecting incoming attacks).
AC-10 Room buff effects (transference cast speed, transference regen) are factored into player stat computation.
AC-11 Effect magnitudes scale linearly: baseMagnitude × (coverage / 100).
AC-12 Multiple room enchantments on one pair of boots share the same coverage meter and apply independently.
AC-13 Total capacity cost of room enchantments must fit within footwear capacity (1535).
AC-14 The room-enchanting discipline uses transference mana and has roomEnchantmentAuraMagnitude as its stat.
AC-15 room-coverage-rate capped perk adds +0.03/tick per tier, max 4 tiers.
AC-16 room-coverage-rate capped perk reaches max 4 tiers at 550 XP (0.12/tick, 62.5s to fill).
AC-17 resonant-stamps once perk computes carryover as min(20, previousRoom.coverage × 0.2), stored in lastRoomCoverage on the combat store.
AC-18 Room enchantment effects are unlocked via discipline perks at the specified XP thresholds.
AC-19 Room enchantment effects only appear in the EffectSelector when designing for feet equipment.
AC-20 RoomDisplay shows coverage bar and current effect magnitudes when room enchantments are active.
AC-21 Existing saves without roomEnchantment field default to null (backward compatible).
AC-22 Existing saves without roomEnchantmentAuraMagnitude stat default to 0 (backward compatible).

10. Files Reference

File Role
src/lib/game/types/game.ts Add roomEnchantment field to FloorState
src/lib/game/data/enchantments/special-effects.ts Add room enchantment effect definitions (8 effects)
src/lib/game/data/disciplines/enchanter.ts Add room-enchanting discipline definition with perks

Implementation note: enchanter.ts currently has 4 disciplines (146 lines). Adding room-enchanting with its ~8 unlock perks may push it toward the 400-line limit. If so, create a new file enchanter-combat.ts (following the existing pattern of enchanter-utility.ts, enchanter-spells.ts, etc.) and re-export from data/disciplines/index.ts. | src/lib/game/effects/discipline-effects.ts | Add roomEnchantmentAuraMagnitude to KNOWN_BONUS_STATS | | src/lib/game/stores/combat-actions.ts | Add room enchantment phase in processCombatTick (after DoT phase) | | src/lib/game/utils/room-enchantments-utils.ts | NEW — Coverage computation, effect application, equipment scanning | | src/components/game/tabs/SpireCombatPage/RoomDisplay.tsx | Add coverage bar and effect magnitude display | | src/components/game/crafting/EnchantmentDesigner/EffectSelector.tsx | Show room enchantments when designing for feet | | docs/specs/attunements/enchanter/systems/room-enchantments-spec.md | THIS FILE |


11. Out of Scope

  • Active player input during combat (no button to spend transference for faster coverage)
  • Coverage scaling with cast speed, attack speed, or any combat stat
  • Coverage persisting between rooms at launch (added only via resonant-stamps perk)
  • Room enchantments on non-footwear equipment slots
  • Room enchantments interacting with golem combat
  • Room enchantments affecting non-combat rooms (library, recovery, treasure, puzzle)
  • Visual effects / animations for the coverage bar (UI-only indicator in v1)
  • Room enchantment effects that heal the player (violates no-healing rule)
  • More than 8 room enchantment types in v1
  • Composite or exotic element room sigils in v1 (base 7 + transference only)