4.2 KiB
4.2 KiB
| description | globs | alwaysApply |
|---|---|---|
| Turn lifecycle logic - activation, reset, and tick system for unit turns | src/systems/TurnSystem.js, src/systems/TurnSystem.ts | 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, orstandardActionUsedto 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.
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
// 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: 1applied 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:
- State: Maintain a
globalTickcounter and reference to UnitManager - End Turn Logic: Implement
endTurn(unit). Reset the unit's charge to 0. Tick their cooldowns/statuses - Time Loop: Implement
advanceToNextTurn(). Loop through all alive units, adding Speed to Charge. Stop as soon as one or more units reach 100 - 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
- 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