aether-shards/.cursor/rules/logic/EffectProcessor/RULE.md
Matthew Mone 5c335b4b3c Add HubScreen and MissionBoard components for campaign management
Introduce the HubScreen as the main interface for managing resources, units, and mission selection, integrating with the GameStateManager for dynamic data binding. Implement the MissionBoard component to display and select available missions, enhancing user interaction with mission details and selection logic. Update the GameStateManager to handle transitions between game states, ensuring a seamless experience for players. Add tests for HubScreen and MissionBoard to validate functionality and integration with the overall game architecture.
2025-12-31 10:49:26 -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()