Skip to main content

Card Zones and Piles

Guide

How runtime card zones behave, how the draw pile is ordered, and which APIs move cards safely

Game Card Monitor Pile tab listing hand, draw, discard, and exhaust contents during a live battle

Card definition versus card instance

ConceptTypeMeaning
Card definitionGameCardAuthored asset: name, cost, tags, rarity, text, targeting, and behavior
Card instanceCardInstanceRuntime copy in a battle pile; it points to a GameCard and tracks battle-specific state
Active cardCardInstance.GetActiveCard() or GCSApi.GetActiveCard(instance)The upgraded variant when the instance is upgraded; otherwise the base card
Effective costGCSApi.GetEffectiveEnergyCost(instance)Cost after card definition, master deck delta, temporary modifiers, and cost hooks

Use instances for runtime actions; use definitions for content browsing, deck construction, and database lookup

Runtime piles

PileAPIMeaning
HandGCSApi.Hand() or GCSApi.CurrentHandCards currently available in the player's hand
Draw pileGCSApi.DrawPile()Cards waiting to be drawn; the last element is drawn next
Discard pileGCSApi.DiscardPile()Cards discarded this battle and available for reshuffle
Exhaust pileGCSApi.ExhaustPile()Cards removed from play for the rest of the battle
Played this turnGCSApi.PlayedThisTurn()Cards successfully played during the current turn, in play order
Drawn this turnGCSApi.DrawnThisTurn()Cards drawn during the current turn, in draw order

Hand(), DrawPile(), DiscardPile(), ExhaustPile(), PlayedThisTurn(), and DrawnThisTurn() never return null; they return empty lists when no battle is active. CurrentHand mirrors the manager and returns null outside battle

Cards move between the four zones along a small set of paths:

Draw pile order

The draw pile is ordered so the last element is the top of the pile; when documentation says "top of the draw pile," it means the element that will be drawn next, while DrawPilePosition controls where newly added cards enter the pile:

PositionMeaning
TopDrawn before the current pile contents
BottomDrawn after the current pile contents
RandomInserted into a random slot

Opening hand and per-turn draw

At battle start:

  1. GCS builds the draw pile from the run master deck or the player starting deck
  2. It shuffles the draw pile
  3. It prioritizes innate cards for the opening hand
  4. It draws the starting hand

On later player turns:

  1. DrawnThisTurn is cleared
  2. OnDrawPhase is raised
  3. Draw-phase status behavior runs
  4. GCS draws BattleRules.DrawPerTurn cards

When a draw finds the draw pile empty, the discard pile shuffles back into the draw pile automatically and OnDrawPileShuffled is raised; if both piles are empty, the draw stops

The hand holds at most BattleRules.MaxHandSize cards (10 by default; a Hand Size Hook can change the limit during battle); while the hand is full, the per-turn draw flips the leftover cards from the top of the draw pile straight into discard and raises OnCardDiscarded for each; A plain GCSApi.DrawCards call stops early instead, and AddCardToHand or a move into a full hand sends the card to discard

info

DrawnThisTurn covers the current turn only; cards drawn on previous turns are not included, even if they are still in hand

Playing a card

When GCSApi.TryPlayCard(card, target) succeeds:

  1. The card must be in the current hand
  2. The active card definition is resolved
  3. Effective cost is checked
  4. Playability hooks run
  5. Energy is spent
  6. The card instance is added to PlayedThisTurn
  7. OnCardPlayed is raised
  8. The card's on-play FlowGraph runs
  9. Card hooks are registered
  10. The card leaves hand
  11. If the active card has the Exhaust keyword, it moves to exhaust and raises OnCardExhausted; otherwise it moves to discard

The runtime removes the card from hand after its behavior resolves; custom UI should refresh from pile events; removing the card manually would duplicate the runtime move

End-of-turn hand handling

At player turn end, GCS handles the remaining hand:

Card behaviorRuntime result
Normal hand cardMoves to discard
Card with the Retain keywordStays in hand and raises OnCardRetained
Card with the Ethereal keywordMoves to exhaust and raises OnCardExhausted
Exhausting card already playedMoved to exhaust during card play, before turn end

This table assumes the default HandDiscardRule.DiscardOnTurnEnd; an encounter with Persist skips the discard step: every card that stays raises OnCardRetained, and Ethereal cards still exhaust

If your UI wants to animate hand cleanup, subscribe to the card lifecycle events and then refresh from GCSApi.Hand()

Creating and moving cards

Use controller-backed APIs for card movement

TaskAPI
Add a card to handGCSApi.AddCardToHand(card)
Move an existing instanceGCSApi.MoveCardToPile(instance, PileScope.Discard)
Shuffle draw pileGCSApi.ShuffleDrawPile()
Draw cardsGCSApi.DrawCards(count)
Read active card definitionGCSApi.GetActiveCard(instance)
Read effective costGCSApi.GetEffectiveEnergyCost(instance)

PileScope is used for both pile reads and move destinations, but Master and All do not mean the same thing in those two operations:

ValueAs a read scopeAs a move destination
Hand, Draw, Discard, ExhaustReads that one battle pileMoves the card into that pile; A full hand overflows to discard
MasterReads the run-level master deck as temporary card instances, preserving upgrade and cost-delta stateRemoves the card from all four battle piles without changing the run-level master deck
AllReads hand, draw, discard, and exhaust together; it does not include the master deckRemoves the card from all four battle piles, the same as Master
warning

Modify piles through GCSApi or the controller; direct edits to CardPile.Cards skip events, pile rules, and the refresh path used by UI

Master deck versus battle piles

The run master deck is the persistent deck list used to start battles; runtime piles are battle state

NeedAPI
Inspect run deckGCSApi.GetMasterDeck()
Replace run deckGCSApi.SetMasterDeck(entries)
Add card to run deckGCSApi.AddCardToMasterDeck(card, isUpgraded)
Remove from run deckGCSApi.RemoveCardFromMasterDeck(cardGuid, removeFirstOnly)
Upgrade in run deckGCSApi.UpgradeCardInMasterDeck(cardGuid)
Clear run deckGCSApi.ClearMasterDeck()

Changing the master deck does not retroactively change cards already in a live battle unless you also mutate the runtime piles

Card events

EventPayloadUse for
OnCardPlayedCardPlayedEventArgsCard play UI, statistics, combo counters
OnCardModifiedCardModifiedEventArgsCost or upgrade refresh
OnCardDrawnCardLifecycleEventArgsDraw animation and hand widget creation
OnCardDiscardedCardLifecycleEventArgsDiscard animation
OnCardExhaustedCardLifecycleEventArgsExhaust animation
OnCardRetainedCardLifecycleEventArgsRetain feedback
OnCardAddedToHandCardLifecycleEventArgsGenerated-card UI
OnCardAddedToDeckCardLifecycleEventArgsRun deck updates
OnDrawPileShuffledNo payloadShuffle feedback

Always refresh from the current piles after handling an event; the event tells you what changed; the API tells you the current truth