# **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).