--- description: Turn lifecycle logic - activation, reset, and tick system for unit turns globs: src/systems/TurnSystem.js, src/systems/TurnSystem.ts alwaysApply: false --- # **Turn Lifecycle Rule** This rule defines the exact state changes that occur when a unit becomes active (Start Turn) and when they finish (End Turn). ## **1. Start of Turn (Activation Phase)** This logic runs immediately when TurnSystem identifies a unit as the new active actor. ### **A. Action Point (AP) Regeneration** The unit must be given their budget for the turn. - **Formula:** Base AP (3) + Math.floor(Speed / 5) - **Constraint:** AP does _not_ roll over. It resets to this max value every turn. This encourages players to use their actions rather than hoard them ### **B. Cooldown Reduction** - Iterate through all skills in `unit.skills` - If `cooldown > 0`, decrement by 1 - _Note:_ This ensures a skill used on Turn 1 with a 1-turn cooldown is ready again on Turn 2 ### **C. Status Effect Tick (The "Upkeep" Step)** - Iterate through `unit.statusEffects` - **Apply Effect:** If the effect is a "DoT" (Damage over Time) or "HoT" (Heal over Time), apply the value now - **Decrement Duration:** Reduce the effect's duration by 1 - **Expire:** If duration reaches 0, remove the effect immediately (unless it is "Permanent") - **Stun Check:** If a STUN status is active, skip the Action Phase and immediately trigger **End Turn** ## **2. End of Turn (Resolution Phase)** This logic runs when the player clicks "End Turn" or the AI finishes its logic. ### **A. Charge Meter Consumption** - **Logic:** We do _not_ set Charge to 0. We subtract 100 - **Why?** If a unit has 115 Charge (because they are very fast), setting it to 0 deletes that extra 15 speed advantage. Subtracting 100 lets them keep the 15 head-start on the next race - **Formula:** `unit.chargeMeter = Math.max(0, unit.chargeMeter - 100)` ### **B. Action Slot Reset** - Reset flags like `hasMoved`, `hasAttacked`, or `standardActionUsed` to false so the UI is clean for the next time they act ## **3. The Tick Loop (The "Race")** This runs whenever no unit is currently active. ```js while (no_unit_has_100_charge) { globalTick++; for (all_units as unit) { // Gain Charge unit.chargeMeter += unit.stats.speed; // Cap? // No cap, but we check for >= 100 break condition } } // Sort by Charge (Descending) -> Highest Charge wins ``` ## **4. TypeScript Interfaces** ```typescript // src/types/TurnSystem.ts export interface TurnState { /** The ID of the unit currently acting */ activeUnitId: string | null; /** How many "Ticks" have passed in total (Time) */ globalTime: number; /** Ordered list of who acts next (predicted) for the UI */ projectedQueue: string[]; } export interface TurnEvent { type: 'TURN_CHANGE'; previousUnitId: string; nextUnitId: string; /** Did we wrap around a "virtual round"? */ isNewRound: boolean; } ``` ## **5. Conditions of Acceptance (CoA)** **CoA 1: Speed determines frequency** - If Unit A has Speed 20 and Unit B has Speed 10: - Unit A should act roughly twice as often as Unit B over 10 turns **CoA 2: Queue Prediction** - The system must expose a `getPredictedQueue(depth)` method that "simulates" future ticks without applying them, so the UI can show the "Next 5 Units" list correctly **CoA 3: Status Duration** - A Status Effect with `duration: 1` applied on Turn X must expire exactly at the _start_ of the unit's _next_ turn (Turn X+1), ensuring it affects them for one full action phase ## **6. Implementation Requirements** Create `src/systems/TurnSystem.js`: 1. **State:** Maintain a `globalTick` counter and reference to UnitManager 2. **End Turn Logic:** Implement `endTurn(unit)`. Reset the unit's charge to 0. Tick their cooldowns/statuses 3. **Time Loop:** Implement `advanceToNextTurn()`. Loop through all alive units, adding Speed to Charge. Stop as soon as one or more units reach 100 4. **Tie Breaking:** If multiple units pass 100 in the same tick, the one with the highest total charge goes first. If equal, Player beats Enemy 5. **Prediction:** Implement `simulateQueue(depth)` which clones the current charge state and runs the loop virtually to return an array of the next depth Unit IDs