Battle Phases and Execution Order
Every battle phase, the exact order of operations inside it, and when card play is legal

Phase list
GCSApi.Phase returns the current BattlePhase; outside an active battle it returns BattlePhase.None
| Phase | What happens | Player input |
|---|---|---|
| None | No active battle | No card play |
| Initialize | Battle state is being built | No card play |
| PlayerTurnStart | Armor decay, energy refill, draw phase, start-turn status/card timings | No card play until the phase enters PlayerPhase |
| PlayerPhase | Player may play hand cards and end the turn | Card play allowed when affordable and playable |
| PlayerTurnEnd | Discard phase, end-turn status timings, status decay, hook ticking, delayed triggers | No card play |
| EnemyPhase | Each alive enemy resolves start-turn statuses, behavior, end-turn statuses, and gaps | No card play |
| BattleWon | Battle is won and a reward may be offered | Reward selection may be required |
| BattleLost | Battle is lost | No card play |
The core loop cycles four phases; win and loss exits are evaluated by the phase machinery on turn transitions and after card plays:
Battle start flow
GCSApi.StartBattle(encounter) delegates to the manager and state machine:
- Create a fresh
BattleStateView - Build player state from the encounter player unit
- Build enemy states from encounter enemies
- Build the initial draw pile from the run master deck or the player starting deck
- Shuffle the draw pile
- Prioritize innate cards for the opening hand
- Bind the optional GES event bridge
- Draw the starting hand
- Raise
OnBattleStarted - Resolve enemy intents
- Run battle-start status behavior
- Enter
PlayerTurnStart
If the encounter is null, the state machine logs an error and does not start the battle
Player turn start
At PlayerTurnStart, GCS:
- Marks the battle as player-owned
- Applies armor decay to the player
- Resolves max energy and sets current energy
- Ticks delayed triggers scheduled for
PlayerTurnStart - Clears
PlayedThisTurn - Runs player status
OnTurnStart - Runs hand-card turn-start timing
- On turns after the first, raises
OnDrawPhase, runs draw-phase behavior, and draws the per-turn cards - Resets per-turn attack flags
- Raises
OnTurnStarted - Skips to player turn end if the player has pending skipped turns
- Enters
PlayerPhase
Armor decay follows the encounter's BattleRules.ArmorDecayRule; the default ResetOnTurnStart resets armor at the start of each of the unit's turns, and an Armor Reset Hook can change the reset value. Persist carries armor across turns untouched
The first turn draws its starting hand during battle start, before PlayerTurnStart; turn 1 has no separate draw phase
Player action phase
TryPlayCard can only succeed in PlayerPhase
A card play attempt fails when:
- The phase is not
PlayerPhase - The card is null
- The player unit is missing
- The card is not currently in hand
- The card has no active card definition
- The player cannot pay the effective energy cost
- The on-play hook cancels the play
- The card has the
Unplayablekeyword - The card playable hook cancels or returns false
When play succeeds, GCS:
- Spends energy
- Adds the card to
PlayedThisTurn - Resolves the target, falling back to the first alive enemy when no target is provided
- Raises
OnCardPlayed - Runs the card's on-play FlowGraph
- Registers card hooks
- Moves the card from hand to discard or exhaust
- Checks pending win/loss transitions
- Ends the player turn if an effect requested it
Call GCSApi.CanPlayCard(card) before enabling a card button; to decide whether your UI should ask for a target, pass the card definition to GCSApi.RequiresTarget(GCSApi.GetActiveCard(instance))
Player turn end
When GCSApi.EndPlayerTurn() is called in PlayerPhase, the state machine enters PlayerTurnEnd:
- Raise
OnDiscardPhase - Run discard-phase status behavior
- Move normal hand cards to discard, keep
Retaincards, and exhaustEtherealcards - Run player status
OnTurnEnd - Tick per-turn statuses
- Tick active hooks
- Tick card cost rules
- Tick delayed triggers scheduled for
PlayerTurnEnd - Raise
OnTurnEnded - If an extra turn is pending, enter
PlayerTurnStart - Otherwise enter
EnemyPhase
Enemy phase
At EnemyPhase, GCS:
- Marks the battle as no longer player-owned
- Raises
OnEnemyTurnStarted - Iterates alive enemies in order
- Applies armor decay to the acting enemy
- Runs enemy start-turn statuses
- Resolves enemy behavior unless skipped
- Runs enemy end-turn statuses
- Ticks per-turn statuses and hooks
- Waits between visible enemy actions
- Ticks delayed triggers scheduled for
EnemyPhaseEnd - Resolves all enemy intents for the next player turn
- Increments
TurnNumber - Enters
PlayerTurnStart
The demo presentation layer can add attack lunges and gaps between enemies, but the phase logic stays owned by the state machine
Choice and reward waits
Some graph nodes and battle outcomes intentionally pause progression
| Wait state | API indicator | Meaning |
|---|---|---|
| Effect choice | GCSApi.IsWaitingForChoice | A Choice node is waiting for the player to select or skip |
| Battle reward | GCSApi.IsWaitingForReward | A won battle has offered a reward and waits for ApplyReward or SkipReward |
While waiting, queued transitions do not proceed; custom UI should disable unrelated controls and show the appropriate picker
Battle end
When the battle is won:
- Battle-end status behavior runs
- Reward candidates are rolled
OnBattleRewardOfferedis raised- The state machine waits for reward selection or skip
OnBattleRewardSelectedorOnBattleRewardSkippedis raisedOnBattleEndedis raised withWon = true
When the battle is lost:
- Battle-end status behavior runs
OnBattleEndedis raised withWon = false
GCSApi.IsBattleOver is true in BattleWon or BattleLost
Safe phase checks
| Task | Check |
|---|---|
| Show battle HUD | GCSApi.IsBattleActive |
| Enable card buttons | GCSApi.Phase == BattlePhase.PlayerPhase and GCSApi.CanPlayCard(card) |
| Show end-turn button | GCSApi.Phase == BattlePhase.PlayerPhase |
| Show choice UI | GCSApi.IsWaitingForChoice |
| Show reward UI | GCSApi.IsWaitingForReward |
| Show victory/defeat overlay | GCSApi.IsBattleOver and the OnBattleEnded payload |
Events report what happened; read GCSApi.Phase for the current phase so UI stays aligned even when one operation raises several events