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

- 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:
2026-06-03 18:38:01 +02:00
parent a2cdf6d21c
commit b506f0bcc3
30 changed files with 3272 additions and 71 deletions
@@ -13,7 +13,8 @@ export const BASIC_ELEMENTAL_SPELLS: Record<string, SpellDef> = {
castSpeed: 2,
unlock: 100,
studyTime: 2,
desc: "Hurl a ball of fire at your enemy."
desc: "Hurl a ball of fire at your enemy.",
onHitEffect: { type: 'burn', duration: 4, magnitude: 3 },
},
emberShot: {
name: "Ember Shot",
@@ -24,7 +25,8 @@ export const BASIC_ELEMENTAL_SPELLS: Record<string, SpellDef> = {
castSpeed: 3,
unlock: 75,
studyTime: 1,
desc: "A quick shot of embers. Efficient fire damage."
desc: "A quick shot of embers. Efficient fire damage.",
onHitEffect: { type: 'burn', duration: 3, magnitude: 2 },
},
waterJet: {
name: "Water Jet",
@@ -145,7 +147,8 @@ export const BASIC_ELEMENTAL_SPELLS: Record<string, SpellDef> = {
castSpeed: 2,
unlock: 150,
studyTime: 3,
desc: "Siphon vital energy from your enemy."
desc: "Siphon vital energy from your enemy.",
onHitEffect: { type: 'curse', duration: 4, magnitude: 0.20 },
},
rotTouch: {
name: "Rot Touch",
@@ -156,6 +159,7 @@ export const BASIC_ELEMENTAL_SPELLS: Record<string, SpellDef> = {
castSpeed: 2,
unlock: 170,
studyTime: 3,
desc: "Touch of decay and rot."
desc: "Touch of decay and rot.",
onHitEffect: { type: 'curse', duration: 4, magnitude: 0.20 },
},
};
@@ -15,7 +15,8 @@ export const FROST_SPELLS: Record<string, SpellDef> = {
unlock: 180,
studyTime: 3,
desc: "A chilling bite of frost. Fast casting with freeze chance.",
effects: [{ type: 'freeze', value: 0.15 }]
effects: [{ type: 'freeze', value: 0.15 }],
onHitEffect: { type: 'freeze', duration: 3, magnitude: 0.15 },
},
iceShard: {
name: "Ice Shard",
@@ -27,7 +28,8 @@ export const FROST_SPELLS: Record<string, SpellDef> = {
unlock: 250,
studyTime: 4,
desc: "A sharp shard of ice. Moderate speed, freeze effect.",
effects: [{ type: 'freeze', value: 0.2 }]
effects: [{ type: 'freeze', value: 0.2 }],
onHitEffect: { type: 'freeze', duration: 3, magnitude: 0.2 },
},
// Tier 2 - Advanced Frost
@@ -15,7 +15,8 @@ export const LIGHTNING_SPELLS: Record<string, SpellDef> = {
unlock: 120,
studyTime: 2,
desc: "A quick spark of lightning. Very fast and hard to dodge.",
effects: [{ type: 'armor_pierce', value: 0.2 }]
effects: [{ type: 'armor_pierce', value: 0.2 }],
onHitEffect: { type: 'armor_corrode', duration: 3, magnitude: 0.15 },
},
lightningBolt: {
name: "Lightning Bolt",
@@ -27,7 +28,8 @@ export const LIGHTNING_SPELLS: Record<string, SpellDef> = {
unlock: 150,
studyTime: 3,
desc: "A bolt of lightning that pierces armor.",
effects: [{ type: 'armor_pierce', value: 0.3 }]
effects: [{ type: 'armor_pierce', value: 0.3 }],
onHitEffect: { type: 'armor_corrode', duration: 3, magnitude: 0.15 },
},
// Tier 2 - Advanced Lightning
@@ -15,7 +15,8 @@ export const SOUL_SPELLS: Record<string, SpellDef> = {
unlock: 30000,
studyTime: 36,
desc: "Strike at the soul. Bypasses all armor and resistances.",
effects: [{ type: 'defense_bypass', value: 1.0 }]
effects: [{ type: 'defense_bypass', value: 1.0 }],
onHitEffect: { type: 'burn', duration: 5, magnitude: 20, bypassArmor: true },
},
spiritBlast: {
name: "Spirit Blast",
@@ -27,6 +28,7 @@ export const SOUL_SPELLS: Record<string, SpellDef> = {
unlock: 60000,
studyTime: 50,
desc: "A blast of pure soul energy. Ignores all defenses entirely.",
effects: [{ type: 'defense_bypass', value: 1.0 }, { type: 'resist_ignore', value: 0.5 }]
effects: [{ type: 'defense_bypass', value: 1.0 }, { type: 'resist_ignore', value: 0.5 }],
onHitEffect: { type: 'burn', duration: 5, magnitude: 35, bypassArmor: true },
},
};