Files
2026-05-27 23:29:53 +01:00

181 lines
4.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# OpenSpec Change: enterprise-gaming-combat
- **Change ID**: enterprise-gaming-combat
- **Package**: `@suaveplan/combat`
- **Category**: gaming/combat
- **Target path**: `gaming/combat/combat`
- **Status**: PROPOSED
- **Date**: 2026-04-24
- **Reference spec**: `/mnt/s/web/suaveplan/enterprise/combat.md`
---
## Summary
Deliver `@suaveplan/combat` on the genesis branch from scratch at enterprise quality. The package provides core combat mechanics: multiple damage types (physical, elemental, true), health/armor/shield layers, resistances, status effects (burn, stun, slow, poison) with duration and tick logic, damage calculation pipeline with modifier chain, and `CombatManager` that processes hit events and emits `@suaveplan/events` combat messages. Genesis adds Zod schemas for all combat data, structured errors, OTel damage counters, injectable clock, and 100% coverage including property tests for damage pipeline commutativity.
---
## Current-state audit (genesis)
Package does not exist on genesis yet — fresh build.
---
## Available workspace deps on genesis
| Package | Role |
|---|---|
| `@suaveplan/error` (`workspace:^`) | `CombatError`; `COMBAT.*` codes |
| `@suaveplan/types` (`workspace:^`) | `Clock`, `IdGenerator` |
| `@suaveplan/events` (`workspace:^`) | `combat:hit`, `combat:death`, `combat:status` events |
| `zod` (`catalog:forms`) | `DamageEvent`, `StatusEffect`, `CombatantStats` schemas |
| `@opentelemetry/api` (`catalog:observability`) | Damage counter, death counter |
---
## Public API surface
```ts
type DamageType = 'physical' | 'fire' | 'ice' | 'lightning' | 'poison' | 'true'
interface CombatantStats {
maxHealth: number
maxArmor: number
maxShield: number
resistances: Partial<Record<DamageType, number>> // 0–1 reduction
defense: number
}
function createCombatant(id: string, stats: CombatantStats): Combatant
interface Combatant {
readonly id: string
readonly health: number
readonly armor: number
readonly shield: number
readonly isDead: boolean
readonly statusEffects: readonly StatusEffect[]
applyDamage(event: DamageEvent): DamageResult
applyHealing(amount: number, type?: 'health' | 'shield'): void
applyStatus(effect: StatusEffect): void
tick(dt: number): void // processes status effect durations + ticks
revive(health?: number): void
}
interface DamageEvent {
sourceId: string
targetId: string
amount: number
type: DamageType
isCritical?: boolean
modifiers?: DamageModifier[]
}
interface DamageResult {
finalDamage: number
healthDamage: number
armorDamage: number
shieldDamage: number
wasKilled: boolean
statusesApplied: string[]
}
interface StatusEffect {
id: string
name: string
duration: number
tickInterval?: number
tickDamage?: number
tickDamageType?: DamageType
speedModifier?: number
damageModifier?: number
}
interface DamageModifier {
type: 'multiply' | 'add' | 'set'
value: number
priority?: number
}
function createCombatManager(options?: CombatManagerOptions): CombatManager
interface CombatManager {
registerCombatant(combatant: Combatant): void
unregisterCombatant(id: string): void
processHit(event: DamageEvent): DamageResult
tick(dt: number): void
getCombatant(id: string): Combatant | undefined
}
```
### Error codes
```ts
type CombatErrorCode =
| 'COMBAT_COMBATANT_NOT_FOUND'
| 'COMBAT_TARGET_DEAD'
| 'COMBAT_INVALID_DAMAGE'
| 'COMBAT_STATUS_OVERFLOW'
```
---
## New additions for genesis
1. **Damage pipeline** — ordered modifier chain: resistances → armor absorption → shield drain → health damage.
2. **Status effect stacking** — configurable max-stack count; refresh vs. stack vs. replace policy.
3. **Critical hit multiplier** — `isCritical` doubles damage before modifier chain.
4. **OTel** — `combat.damage_total` counter (by type); `combat.deaths_total` counter; `combat.active_combatants` gauge.
---
## File layout
```text
gaming/combat/combat/
package.json
tsconfig.json
vitest.config.ts
src/
index.ts
error-codes.ts
constants/index.ts
types/common.ts
types/common.md
core/
combatant/
combatant.ts
combatant.md
combatant.types.ts
combatant.test.ts
index.ts
damage-pipeline/
damage-pipeline.ts
damage-pipeline.md
damage-pipeline.types.ts
damage-pipeline.test.ts
index.ts
status-effect/
status-effect.ts
status-effect.md
status-effect.types.ts
status-effect.test.ts
index.ts
combat-manager/
combat-manager.ts
combat-manager.md
combat-manager.types.ts
combat-manager.test.ts
index.ts
tests/
integration.test.ts
property.test.ts
```
---
## Impact
**Downstream**: `@suaveplan/weapons` feeds damage events into combat manager; `@suaveplan/r3f-combat` renders damage numbers. Prerequisites: `@suaveplan/error`, `@suaveplan/types`, `@suaveplan/events`.