aether-shards/.cursor/rules/logic/EffectProcessor/RULE.md
Matthew Mone 2c86d674f4 Add mission debrief and procedural mission generation features
- Introduce the MissionDebrief component to display after-action reports, including XP, rewards, and squad status.
- Implement the MissionGenerator class to create procedural side missions, enhancing replayability and resource management.
- Update mission schema to include mission objects for INTERACT objectives, improving mission complexity.
- Enhance GameLoop and MissionManager to support new mission features and interactions.
- Add tests for MissionDebrief and MissionGenerator to ensure functionality and integration within the game architecture.
2026-01-01 16:08:54 -08:00

5.6 KiB

description globs alwaysApply
Effect Processor architecture - the central system for executing all game state changes src/systems/EffectProcessor.js, src/systems/EffectProcessor.ts, src/types/Effects.ts false

Effect Processor Rule

The EffectProcessor is the central system responsible for executing all changes to the game state (Damage, Healing, Movement, Spawning). It is a stateless logic engine that 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.

1. System Overview

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. See src/types/Effects.ts for full TypeScript definitions.

Effect Types

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";

Effect Parameters

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;
}

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)

6. Implementation Requirements

  • Statelessness: The processor should not hold state. It acts on the Unit and Grid passed to it
  • Schema: Effects must adhere to the EffectDefinition interface (Type + Params)
  • All game state mutations (Damage, Move, Spawn) MUST go through EffectProcessor.process()