aether-shards/specs/EffectProcessor.spec.md

316 lines
8.7 KiB
Markdown
Raw Normal View History

# **Effect Processor Specification: The Game Logic Engine**
This document defines the architecture for the **Effect Processor**, the central system responsible for executing all changes to the game state (Damage, Healing, Movement, Spawning).
## **1. System Overview**
The EffectProcessor is a stateless logic engine. It takes a **Definition** (What to do) and a **Context** (Who is doing it to whom), and applies the necessary mutations to the UnitManager or VoxelGrid.
### **Architectural Role**
- **Input:** EffectDefinition (JSON), Source (Unit), Target (Unit/Tile).
- **Output:** State Mutation (HP changed, Unit moved) + EffectResult (Log data).
- **Pattern:** Strategy Pattern. Each effect_type maps to a specific Handler Function.
## **2. Integration Points**
### **A. Calling the Processor**
The Processor is never called directly by the UI. It is invoked by:
1. **SkillManager:** When an Active Skill is executed.
2. **EventSystem:** When a Passive Item triggers (e.g., "On Hit -> Apply Burn").
3. **Environmental Hazard:** When a unit starts their turn on Fire/Acid.
### **B. Dependencies**
The Processor requires injection of:
- VoxelGrid: To check collision, modify terrain, or move units.
- UnitManager: To find neighbors (Chain Lightning) or spawn tokens (Turrets).
- RNG: A seeded random number generator for damage variance and status chances.
## **3. Data Structure (JSON Schema)**
Every effect in the game must adhere to this structure.
```typescript
/**
* Effects.ts
* Type definitions for the Game Logic Engine (Effects, Passives, and Triggers).
*/
// =============================================================================
// 1. EFFECT DEFINITIONS (The "What")
// =============================================================================
/**
* List of all valid actions the EffectProcessor can execute.
*/
export type EffectType =
// Combat
| "DAMAGE"
| "HEAL"
| "CHAIN_DAMAGE"
| "REDIRECT_DAMAGE"
| "PREVENT_DEATH"
// Status & Stats
| "APPLY_STATUS"
| "REMOVE_STATUS"
| "REMOVE_ALL_DEBUFFS"
| "APPLY_BUFF"
| "GIVE_AP"
| "ADD_CHARGE"
| "ADD_SHIELD"
| "CONVERT_DAMAGE_TO_HEAL"
| "DYNAMIC_BUFF"
// Movement & Physics
| "TELEPORT"
| "MOVE_TO_TARGET"
| "SWAP_POSITIONS"
| "PHYSICS_PULL"
| "PUSH"
// World & Spawning
| "SPAWN_OBJECT"
| "SPAWN_HAZARD"
| "SPAWN_LOOT"
| "MODIFY_TERRAIN"
| "DESTROY_VOXEL"
| "DESTROY_OBJECTS"
| "REVEAL_OBJECTS"
| "COLLECT_LOOT"
// Meta / Logic
| "REPEAT_SKILL"
| "CANCEL_EVENT"
| "REDUCE_COST"
| "BUFF_SPAWN"
| "MODIFY_AOE";
/**
* A generic container for parameters used by Effect Handlers.
* In a strict system, this would be a Union of specific interfaces,
* but for JSON deserialization, a loose interface is often more flexible.
*/
export interface EffectParams {
// -- Combat Magnitude --
power?: number; // Base amount (Damage/Heal)
attribute?: string; // Stat to scale off (e.g., "strength", "magic")
scaling?: number; // Multiplier for attribute (Default: 1.0)
element?: "PHYSICAL" | "FIRE" | "ICE" | "SHOCK" | "VOID" | "TECH";
// -- Chaining --
bounces?: number;
decay?: number;
synergy_trigger?: string; // Status ID that triggers bonus effect
// -- Status/Buffs --
status_id?: string;
duration?: number;
stat?: string; // For Buffs
value?: number; // For Buffs/Mods
chance?: number; // 0.0 to 1.0 (Proc chance)
// -- Physics --
force?: number; // Distance
destination?: "TARGET" | "BEHIND_TARGET" | "ADJACENT_TO_TARGET";
// -- World --
object_id?: string; // Unit ID to spawn
hazard_id?: string;
tag?: string; // Filter for objects (e.g. "COVER")
range?: number; // AoE radius
// -- Logic --
percentage?: number; // 0.0 - 1.0
amount?: number; // Flat amount (AP/Charge)
amount_range?: [number, number]; // [min, max]
set_hp?: number; // Hard set HP value
shape?: "CIRCLE" | "LINE" | "CONE" | "SINGLE";
size?: number;
multiplier?: number;
}
/**
* The runtime payload passed to EffectProcessor.process().
* Combines the Type and the Params.
*/
export interface EffectDefinition extends EffectParams {
type: EffectType;
// Optional Override Condition for the Effect itself
// (Distinct from the Passive Trigger condition)
condition?: ConditionDefinition;
// Conditional Multiplier (e.g. Execute damage)
conditional_multiplier?: {
condition: string; // Condition Tag
value: number;
};
}
// =============================================================================
// 2. PASSIVE DEFINITIONS (The "When")
// =============================================================================
/**
* Triggers that the EventSystem listens for.
*/
export type TriggerType =
// Stat Calculation Hooks
| "ON_STAT_CALC"
| "STATIC_STAT_MOD"
| "ON_SKILL_COST_CALC"
| "ON_ATTACK_CALC"
| "ON_DAMAGE_CALC"
// Combat Events
| "ON_ATTACK_HIT"
| "ON_SKILL_HIT"
| "ON_SKILL_CAST"
| "ON_DAMAGED"
| "ON_ALLY_DAMAGED"
| "ON_DAMAGE_DEALT"
| "ON_HEAL_DEALT"
| "ON_HEAL_OVERFLOW"
| "ON_KILL"
| "ON_LETHAL_DAMAGE"
| "ON_SYNERGY_TRIGGER"
// Turn Lifecycle
| "ON_TURN_START"
| "ON_TURN_END"
| "ON_ACTION_COMPLETE"
// World Events
| "ON_MOVE_COMPLETE"
| "ON_HAZARD_TICK"
| "ON_TRAP_TRIGGER"
| "ON_OBJECT_DESTROYED"
| "ON_SPAWN_OBJECT"
| "ON_LEVEL_START"
// Meta / UI
| "GLOBAL_SHOP_PRICE"
| "ON_SKILL_TARGETING"
| "AURA_UPDATE";
/**
* How a stat modifier interacts with the base value.
*/
export type ModifierType =
| "ADD"
| "MULTIPLY"
| "ADD_PER_ADJACENT_ENEMY"
| "ADD_PER_DESTROYED_VOXEL"
| "ADD_STAT"
| "MULTIPLY_DAMAGE";
/**
* An entry in `passive_registry.json`.
* Defines a rule: "WHEN [Trigger] IF [Condition] THEN [Action]".
*/
export interface PassiveDefinition {
id: string;
name: string;
description: string;
// -- The Trigger --
trigger: TriggerType;
// -- The Check --
condition?: ConditionDefinition;
limit?: number; // Max times per run/turn
// -- The Effect (Action) --
// Maps to EffectDefinition.type
action?: EffectType;
// -- The Parameters --
// Merged into EffectDefinition
params?: EffectParams;
// -- For Stat Modifiers (simpler than full Effects) --
stat?: string;
modifier_type?: ModifierType;
value?: number;
range?: number; // For Aura/Per-Adjacent checks
// -- For Complex Stat Lists --
modifiers?: { stat: string; value: number }[];
// -- For Auras --
target?: "ALLIES" | "ENEMIES" | "SELF";
}
// =============================================================================
// 3. CONDITIONS (The "If")
// =============================================================================
export type ConditionType =
| "SOURCE_IS_ADJACENT"
| "IS_ADJACENT"
| "IS_BASIC_ATTACK"
| "SKILL_TAG" // value: string
| "DAMAGE_TYPE" // value: string
| "TARGET_TAG" // value: string
| "OWNER_IS_SELF"
| "IS_FLANKING"
| "TARGET_LOCKED"
| "DID_NOT_ATTACK"
| "TARGET_HP_LOW"
| "IS_MECHANICAL";
export interface ConditionDefinition {
type: ConditionType;
value?: string | number | boolean;
}
```
## 4. Handler Specifications
### Handler: `DAMAGE`
- **Logic:** `FinalDamage = (BasePower + (Source[Attribute] * Scaling)) - Target.Defense`.
- **Element Check:** If Target has Resistance/Weakness to `element`, modify FinalDamage.
- **Result:** `Target.currentHP -= FinalDamage`.
### Handler: `CHAIN_DAMAGE`
- **Logic:** Apply `DAMAGE` to primary target. Then, scan for N nearest enemies within Range R. Apply `DAMAGE * Decay` to them.
- **Synergy:** If `condition.target_status` is present on a target, the chain may branch or deal double damage.
### Handler: `TELEPORT`
- **Logic:** Validate destination tile (must be Air and Unoccupied). Update `Unit.position` and `VoxelGrid.unitMap`.
- **Visuals:** Trigger "Vanish" VFX at old pos, "Appear" VFX at new pos.
### Handler: `MODIFY_TERRAIN`
- **Logic:** Update `VoxelGrid` ID at Target coordinates.
- **Use Case:** Sapper's "Breach Charge" turns `ID_WALL` into `ID_AIR`.
- **Safety:** Check `VoxelGrid.isDestructible()`. Do not destroy bedrock.
---
## 5. Conditions of Acceptance (CoA)
**CoA 1: Attribute Scaling**
- Given a Damage Effect with `power: 10` and `attribute: "magic"`, if Source has `magic: 5`, the output damage must be 15.
**CoA 2: Conditional Logic**
- Given an Effect with `condition: { target_status: "WET" }`, if the target does _not_ have the "WET" status, the effect must **not** execute (return early).
**CoA 3: State Mutation**
- When `APPLY_STATUS` is executed, the Target unit's `statusEffects` array must contain the new ID with the correct duration.
**CoA 4: Physics Safety**
- When `PUSH` is executed, the system must check `VoxelGrid.isSolid()` behind the target. If a wall exists, the unit must **not** move into the wall (optionally take "Smash" damage instead).