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

Card definition versus card instance
| Concept | Type | Meaning |
|---|---|---|
| Card definition | GameCard | Authored asset: name, cost, tags, rarity, text, targeting, and behavior |
| Card instance | CardInstance | Runtime copy in a battle pile; it points to a GameCard and tracks battle-specific state |
| Active card | CardInstance.GetActiveCard() or GCSApi.GetActiveCard(instance) | The upgraded variant when the instance is upgraded; otherwise the base card |
| Effective cost | GCSApi.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
| Pile | API | Meaning |
|---|---|---|
| Hand | GCSApi.Hand() or GCSApi.CurrentHand | Cards currently available in the player's hand |
| Draw pile | GCSApi.DrawPile() | Cards waiting to be drawn; the last element is drawn next |
| Discard pile | GCSApi.DiscardPile() | Cards discarded this battle and available for reshuffle |
| Exhaust pile | GCSApi.ExhaustPile() | Cards removed from play for the rest of the battle |
| Played this turn | GCSApi.PlayedThisTurn() | Cards successfully played during the current turn, in play order |
| Drawn this turn | GCSApi.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:
| Position | Meaning |
|---|---|
| Top | Drawn before the current pile contents |
| Bottom | Drawn after the current pile contents |
| Random | Inserted into a random slot |
Opening hand and per-turn draw
At battle start:
- GCS builds the draw pile from the run master deck or the player starting deck
- It shuffles the draw pile
- It prioritizes innate cards for the opening hand
- It draws the starting hand
On later player turns:
DrawnThisTurnis clearedOnDrawPhaseis raised- Draw-phase status behavior runs
- GCS draws
BattleRules.DrawPerTurncards
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
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:
- The card must be in the current hand
- The active card definition is resolved
- Effective cost is checked
- Playability hooks run
- Energy is spent
- The card instance is added to
PlayedThisTurn OnCardPlayedis raised- The card's on-play FlowGraph runs
- Card hooks are registered
- The card leaves hand
- If the active card has the
Exhaustkeyword, it moves to exhaust and raisesOnCardExhausted; 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 behavior | Runtime result |
|---|---|
| Normal hand card | Moves to discard |
Card with the Retain keyword | Stays in hand and raises OnCardRetained |
Card with the Ethereal keyword | Moves to exhaust and raises OnCardExhausted |
| Exhausting card already played | Moved 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
| Task | API |
|---|---|
| Add a card to hand | GCSApi.AddCardToHand(card) |
| Move an existing instance | GCSApi.MoveCardToPile(instance, PileScope.Discard) |
| Shuffle draw pile | GCSApi.ShuffleDrawPile() |
| Draw cards | GCSApi.DrawCards(count) |
| Read active card definition | GCSApi.GetActiveCard(instance) |
| Read effective cost | GCSApi.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:
| Value | As a read scope | As a move destination |
|---|---|---|
Hand, Draw, Discard, Exhaust | Reads that one battle pile | Moves the card into that pile; A full hand overflows to discard |
Master | Reads the run-level master deck as temporary card instances, preserving upgrade and cost-delta state | Removes the card from all four battle piles without changing the run-level master deck |
All | Reads hand, draw, discard, and exhaust together; it does not include the master deck | Removes the card from all four battle piles, the same as Master |
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
| Need | API |
|---|---|
| Inspect run deck | GCSApi.GetMasterDeck() |
| Replace run deck | GCSApi.SetMasterDeck(entries) |
| Add card to run deck | GCSApi.AddCardToMasterDeck(card, isUpgraded) |
| Remove from run deck | GCSApi.RemoveCardFromMasterDeck(cardGuid, removeFirstOnly) |
| Upgrade in run deck | GCSApi.UpgradeCardInMasterDeck(cardGuid) |
| Clear run deck | GCSApi.ClearMasterDeck() |
Changing the master deck does not retroactively change cards already in a live battle unless you also mutate the runtime piles
Card events
| Event | Payload | Use for |
|---|---|---|
| OnCardPlayed | CardPlayedEventArgs | Card play UI, statistics, combo counters |
| OnCardModified | CardModifiedEventArgs | Cost or upgrade refresh |
| OnCardDrawn | CardLifecycleEventArgs | Draw animation and hand widget creation |
| OnCardDiscarded | CardLifecycleEventArgs | Discard animation |
| OnCardExhausted | CardLifecycleEventArgs | Exhaust animation |
| OnCardRetained | CardLifecycleEventArgs | Retain feedback |
| OnCardAddedToHand | CardLifecycleEventArgs | Generated-card UI |
| OnCardAddedToDeck | CardLifecycleEventArgs | Run deck updates |
| OnDrawPileShuffled | No payload | Shuffle feedback |
Always refresh from the current piles after handling an event; the event tells you what changed; the API tells you the current truth