--- description: Combat state and movement logic for turn-based combat loop globs: src/systems/TurnSystem.js, src/systems/MovementSystem.js, src/types/CombatState.ts, src/types/Actions.ts alwaysApply: false --- # **Combat State & Movement Rule** This rule defines the logic for managing the Turn-Based Combat Loop and the execution of Movement Actions. ## **1. TypeScript Interfaces (Data Models)** ```typescript export type CombatPhase = | "INIT" | "TURN_START" | "WAITING_FOR_INPUT" | "EXECUTING_ACTION" | "TURN_END" | "COMBAT_END"; export interface CombatState { /** Whether combat is currently active */ isActive: boolean; /** Current Round number */ round: number; /** Ordered list of Unit IDs for the current round */ turnQueue: string[]; /** The ID of the unit currently taking their turn */ activeUnitId: string | null; /** Current phase of the turn loop */ phase: CombatPhase; } // src/types/Actions.ts export interface ActionRequest { type: "MOVE" | "SKILL" | "ITEM" | "WAIT"; sourceId: string; targetId?: string; // For targeted skills targetPosition?: { x: number; y: number; z: number }; // For movement/AoE skillId?: string; itemId?: string; } export interface MovementResult { success: boolean; path: { x: number; y: number; z: number }[]; costAP: number; finalPosition: { x: number; y: number; z: number }; } ``` ## **2. Conditions of Acceptance (CoA)** These checks ensure the combat loop feels fair and responsive. ### **System: TurnSystem (src/systems/TurnSystem.js)** - **CoA 1: Initiative Roll:** Upon starting combat, all active units must be sorted into the turnQueue based on their Speed stat (Highest First) - **CoA 2: Turn Start Hygiene:** When a unit's turn begins: - Their `currentAP` must reset to `baseAP` - Status effects (DoTs) must tick - Cooldowns must decrement - **CoA 3: Cycling:** Calling `endTurn()` must move the `activeUnitId` to the next in the queue. If the queue is empty, increment round and re-roll/reset the queue ### **System: MovementSystem (src/systems/MovementSystem.js)** - **CoA 1: Validation:** Moving to a tile must fail if: - The tile is blocked/occupied - No path exists - The unit has insufficient AP for the _entire_ path - **CoA 2: Execution:** A successful move must: - Update the Unit's position in the UnitManager - Update the VoxelGrid occupancy map - Deduct the correct AP cost (including terrain modifiers) - **CoA 3: Path Snapping:** If the user clicks a tile, but the unit only has AP to reach halfway, the system should allow moving to the _furthest reachable tile_ on that path (optional QoL) ## **3. Implementation Requirements** ### **The Turn System** Create `src/systems/TurnSystem.js`. It should manage the CombatState. 1. Implement `startCombat(units)`: Sorts units by speed into turnQueue and sets phase to TURN_START 2. Implement `startTurn()`: Refills the active unit's AP, processes cooldowns/statuses, and sets phase to WAITING_FOR_INPUT 3. Implement `endTurn()`: Rotates the queue. If queue is empty, start new Round 4. It should accept the UnitManager in the constructor to access unit stats 5. Dispatch events: `combat-start`, `turn-start`, `turn-end` ### **The Movement System** Create `src/systems/MovementSystem.js`. It coordinates Pathfinding, VoxelGrid, and UnitManager. 1. Implement `validateMove(unit, targetPos)`: Returns `{ valid: boolean, cost: number, path: [] }`. It checks `A*` pathfinding and compares cost vs `unit.currentAP` 2. Implement `executeMove(unit, targetPos)`: - Validates the move first - Updates `grid.moveUnit(unit, targetPos)` - Deducts AP - Returns a Promise that resolves when the visual movement (optional animation hook) would handle it, or immediately for logic