[Medium] [Task] Combat spec gap: implement regular enemy defenses — armor, barrier, dodge (spec §5.1, §5.2, §5.3) #257

Closed
opened 2026-06-03 13:33:14 +02:00 by Anexim · 2 comments
Owner

Combat Spec Gap: Regular Enemy Defenses (Armor, Barrier, Dodge)

Source of truth: docs/specs/spire-combat-spec.md §5.1, §5.2, §5.3, §11
Affected ACs: AC-7, AC-8, AC-9, AC-10

Gap Summary

Enemy defensive stats (armor, barrier, dodgeChance) are populated on EnemyState during room generation (utils/enemy-generator.ts, utils/room-utils.ts) but are never applied in the combat tick pipeline. The onDamageDealt function in stores/pipelines/combat-tick.ts only handles guardian-specific defenses (shield, barrier, healthRegen). Regular enemies deal and take full damage with no defensive reduction.

What Exists

  • EnemyState.armor, .barrier, .dodgeChance fields in types/game.ts
  • MODIFIER_CONFIG in utils/enemy-generator.ts with full config for armored, shield, agile, mage, swarm modifiers
  • getFloorArmor(), getEnemyBarrier(), getDodgeChance() functions in utils/room-utils.ts
  • Guardian defensive pipeline in combat-tick.ts (shield absorb → barrier reduce → health regen)
  • speedRoomBonus constant infrastructure in room generation

What's Missing

1. Damage reduction pipeline for regular enemies (spec §5.2)

The makeOnDamageDealt function in stores/pipelines/combat-tick.ts must apply this order for all enemies (not just guardians):

onDamageDealt(dmg, enemy):
  // 1. Dodge check
  if enemy.dodgeChance > 0 && Math.random() < enemy.dodgeChance:
    activityLog("Attack dodged!")
    return 0

  // 2. Barrier absorption (percentage)
  if enemy.barrier > 0:
    dmg ×= (1 - enemy.barrier)
    // Mage barrier recharge: enemy.barrier = min(barrierMax, enemy.barrier + rechargeRate × HOURS_PER_TICK)

  // 3. Armor reduction (percentage)
  if enemy.armor > 0:
    dmg ×= (1 - enemy.armor)

  return dmg

Current code: none of these steps exist. onDamageDealt only checks if (guardian && ...).

2. Enemy reference in onDamageDealt

The current makeOnDamageDealt signature is (damage: number) => { rawMana, elements, modifiedDamage }. It has no access to the target enemy. The pipeline must be refactored so the per-enemy damage callbacks receive the enemy being hit.

3. Speed room + agile additive dodge (spec §4.5)

When a room is type speed AND the enemy has the agile modifier:

effectiveDodge = min(0.75, speedRoomBonus + agileDodgeChance)

Where speedRoomBonus = 0.20 (constant). Currently neither the dodge check nor the additive combination exists.

4. Mage barrier recharge (spec §5.2)

Mage-enemy barrier must recharge each tick:

enemy.barrier = min(barrierMax, enemy.barrier + barrierRechargeRate × HOURS_PER_TICK)

MODIFIER_CONFIG.mage.barrierRechargeRate = 0.05 (5% of max HP per tick) exists in data but is never called.

Files to Change

File Change
stores/pipelines/combat-tick.ts Refactor makeOnDamageDealt to accept enemy target; add dodge/barrier/armor reduction for all enemies not just guardians; add mage barrier recharge; add speed room + agile additive dodge
stores/combat-actions.ts Pass enemy reference through to onDamageDealt calls in processCombatTick
stores/combat-state.types.ts May need to expose room type to the damage callback for speed room detection

Acceptance Criteria

  • AC-7: Armored enemies reduce damage by their armor percentage
  • AC-8: Barrier enemies absorb a percentage of each hit before HP is reduced
  • AC-9: Agile enemies dodge attacks at their dodge chance rate
  • AC-10: Speed room + agile modifier combines additively for dodge chance (capped at 0.75)

Out of Scope

  • Guardian-specific shield/barrier/regen (already implemented in combat-tick.ts)
  • New enemy modifiers beyond the 5 defined in spec §5.1
  • Visual UI for dodge/armor notifications (activity log entries are sufficient)
# Combat Spec Gap: Regular Enemy Defenses (Armor, Barrier, Dodge) > **Source of truth:** `docs/specs/spire-combat-spec.md` §5.1, §5.2, §5.3, §11 > **Affected ACs:** AC-7, AC-8, AC-9, AC-10 ## Gap Summary Enemy defensive stats (`armor`, `barrier`, `dodgeChance`) are populated on `EnemyState` during room generation (`utils/enemy-generator.ts`, `utils/room-utils.ts`) but are **never applied** in the combat tick pipeline. The `onDamageDealt` function in `stores/pipelines/combat-tick.ts` only handles guardian-specific defenses (shield, barrier, healthRegen). Regular enemies deal and take full damage with no defensive reduction. ## What Exists - `EnemyState.armor`, `.barrier`, `.dodgeChance` fields in `types/game.ts` - `MODIFIER_CONFIG` in `utils/enemy-generator.ts` with full config for `armored`, `shield`, `agile`, `mage`, `swarm` modifiers - `getFloorArmor()`, `getEnemyBarrier()`, `getDodgeChance()` functions in `utils/room-utils.ts` - Guardian defensive pipeline in `combat-tick.ts` (shield absorb → barrier reduce → health regen) - `speedRoomBonus` constant infrastructure in room generation ## What's Missing ### 1. Damage reduction pipeline for regular enemies (spec §5.2) The `makeOnDamageDealt` function in `stores/pipelines/combat-tick.ts` must apply this order for **all** enemies (not just guardians): ``` onDamageDealt(dmg, enemy): // 1. Dodge check if enemy.dodgeChance > 0 && Math.random() < enemy.dodgeChance: activityLog("Attack dodged!") return 0 // 2. Barrier absorption (percentage) if enemy.barrier > 0: dmg ×= (1 - enemy.barrier) // Mage barrier recharge: enemy.barrier = min(barrierMax, enemy.barrier + rechargeRate × HOURS_PER_TICK) // 3. Armor reduction (percentage) if enemy.armor > 0: dmg ×= (1 - enemy.armor) return dmg ``` Current code: none of these steps exist. `onDamageDealt` only checks `if (guardian && ...)`. ### 2. Enemy reference in onDamageDealt The current `makeOnDamageDealt` signature is `(damage: number) => { rawMana, elements, modifiedDamage }`. It has **no access to the target enemy**. The pipeline must be refactored so the per-enemy damage callbacks receive the enemy being hit. ### 3. Speed room + agile additive dodge (spec §4.5) When a room is type `speed` AND the enemy has the `agile` modifier: ``` effectiveDodge = min(0.75, speedRoomBonus + agileDodgeChance) ``` Where `speedRoomBonus = 0.20` (constant). Currently neither the dodge check nor the additive combination exists. ### 4. Mage barrier recharge (spec §5.2) Mage-enemy barrier must recharge each tick: ``` enemy.barrier = min(barrierMax, enemy.barrier + barrierRechargeRate × HOURS_PER_TICK) ``` `MODIFIER_CONFIG.mage.barrierRechargeRate = 0.05` (5% of max HP per tick) exists in data but is never called. ## Files to Change | File | Change | |---|---| | `stores/pipelines/combat-tick.ts` | Refactor `makeOnDamageDealt` to accept enemy target; add dodge/barrier/armor reduction for all enemies not just guardians; add mage barrier recharge; add speed room + agile additive dodge | | `stores/combat-actions.ts` | Pass enemy reference through to `onDamageDealt` calls in `processCombatTick` | | `stores/combat-state.types.ts` | May need to expose room type to the damage callback for speed room detection | ## Acceptance Criteria - [ ] AC-7: Armored enemies reduce damage by their armor percentage - [ ] AC-8: Barrier enemies absorb a percentage of each hit before HP is reduced - [ ] AC-9: Agile enemies dodge attacks at their dodge chance rate - [ ] AC-10: Speed room + agile modifier combines additively for dodge chance (capped at 0.75) ## Out of Scope - Guardian-specific shield/barrier/regen (already implemented in `combat-tick.ts`) - New enemy modifiers beyond the 5 defined in spec §5.1 - Visual UI for dodge/armor notifications (activity log entries are sufficient)
Anexim added the ai:todo label 2026-06-03 13:33:14 +02:00
n8n-gitea was assigned by Anexim 2026-06-03 13:33:14 +02:00
Anexim added ai:in-progress and removed ai:todo labels 2026-06-03 13:45:19 +02:00
Author
Owner

Starting implementation of regular enemy defenses (armor, barrier, dodge). This is the foundational combat spec gap that #258 (DoT) and #259 (golemancy) depend on. Plan: refactor makeOnDamageDealt to accept enemy target, add dodge/barrier/armor pipeline for all enemies, add speed room + agile combo, add mage barrier recharge.

Starting implementation of regular enemy defenses (armor, barrier, dodge). This is the foundational combat spec gap that #258 (DoT) and #259 (golemancy) depend on. Plan: refactor `makeOnDamageDealt` to accept enemy target, add dodge/barrier/armor pipeline for all enemies, add speed room + agile combo, add mage barrier recharge.
Anexim added ai:done and removed ai:in-progress labels 2026-06-03 14:29:34 +02:00
Author
Owner

Implementation complete. All 921 tests pass (45 test files).

What was implemented:

  • applyEnemyDefenses() pipeline in combat-tick.ts: dodge → barrier → armor reduction for ALL enemies (spec §5.2)
  • Speed room + agile additive dodge: min(0.75, dodgeChance + 0.20) (spec §4.5)
  • Mage barrier recharge per tick (spec §5.2)
  • effectiveArmor support for future armor_corrode debuff compatibility
  • 16 new regression tests in enemy-defenses.test.ts

Files changed:

  • src/lib/game/stores/pipelines/combat-tick.ts (184 lines) — Core defense pipeline
  • src/lib/game/stores/gameStore.ts (397 lines) — Defense context capture + mage recharge
  • src/lib/game/__tests__/enemy-defenses.test.ts (345 lines) — New tests

Architecture decision: Enemy defense context is passed via closure to makeOnDamageDealt() rather than changing the onDamageDealt callback signature — this avoids cascading type changes across combat-actions.ts, combat-store.ts, and combat-state.types.ts.

Next: This unblocks issues #258 (DoT/debuff system) and #259 (golemancy combat).

✅ Implementation complete. All 921 tests pass (45 test files). **What was implemented:** - `applyEnemyDefenses()` pipeline in `combat-tick.ts`: dodge → barrier → armor reduction for ALL enemies (spec §5.2) - Speed room + agile additive dodge: `min(0.75, dodgeChance + 0.20)` (spec §4.5) - Mage barrier recharge per tick (spec §5.2) - `effectiveArmor` support for future armor_corrode debuff compatibility - 16 new regression tests in `enemy-defenses.test.ts` **Files changed:** - `src/lib/game/stores/pipelines/combat-tick.ts` (184 lines) — Core defense pipeline - `src/lib/game/stores/gameStore.ts` (397 lines) — Defense context capture + mage recharge - `src/lib/game/__tests__/enemy-defenses.test.ts` (345 lines) — New tests **Architecture decision:** Enemy defense context is passed via closure to `makeOnDamageDealt()` rather than changing the `onDamageDealt` callback signature — this avoids cascading type changes across `combat-actions.ts`, `combat-store.ts`, and `combat-state.types.ts`. **Next:** This unblocks issues #258 (DoT/debuff system) and #259 (golemancy combat).
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Anexim/Mana-Loop#257