Skip to main content

Card and pile lifecycle

Guide

Understand CardInstance creation, drawing, play, retention, discard, and exhaust, plus the boundary between the Run Master Deck and battle piles

When a card enters battle, its reusable GameCard definition becomes an independent CardInstance. Drawing, playing, and ending turns then move that instance among four battle piles. The Pile tab in Game Card Monitor shows the current location of every instance

The Pile tab in Game Card Monitor listing the live hand, draw pile, discard pile, and exhaust pile

Hand, Draw, Discard, and Exhaust in the image above form the card cycle for the current battle. Master belongs to Run data retained between battles. Understanding those piles starts with the distinction between an authored definition and a battle instance

Card definitions and battle instances

GameCard is a reusable authored definition containing the name, description, base cost, classification, Target, art, and Behavior. CardInstance is one specific card in the current battle and adds an instance ID, upgrade state, and instance cost changes. Use GameCard for content browsing, database lookup, and deck configuration; use CardInstance for hand display, play, movement, and temporary changes

Runtime resultAPI
Active base or upgraded cardGCSApi.GetActiveCard(instance)
Cost after Run modifiers, temporary rules, and HooksGCSApi.GetEffectiveEnergyCost(instance)
Current card-face descriptionGCSApi.FormatDescription(card, instance)

The four battle piles

PileContentsRead with
HandCards the player can currently interact withGCSApi.Hand()
DrawCards waiting to be drawnGCSApi.DrawPile()
DiscardDiscarded cards that can be shuffled back into DrawGCSApi.DiscardPile()
ExhaustCards removed from the regular cycle for this battleGCSApi.ExhaustPile()

The diagram above shows the default pile cycle. FlowGraphs and GCSApi can also create, move, or remove cards directly, but every change should still use a supported runtime entry

warning
  • Hand(), DrawPile(), DiscardPile(), and ExhaustPile() return empty lists when no battle is active, never null
  • GCSApi.CurrentHand exposes the Manager's current hand and may be null outside battle, so regular UI should prefer Hand()

Draw order and shuffling

The draw pile uses the end of its list as the top, meaning the last element is drawn next. When a new card is inserted into Draw, DrawPilePosition determines its position

PositionResult
TopDrawn before the current pile contents
BottomDrawn after the current pile contents
RandomInserted at a random position
tip

If Draw is empty during a draw operation, GCS shuffles Discard back into Draw and publishes OnDrawPileShuffled. When both piles are empty, the draw operation ends

Opening hand and turn draws

At battle start, GCS creates the draw pile from the Run Master Deck or the player's Starting Deck. After shuffling, it places Innate cards into the opening hand first, then fills the hand to BattleRules.StartingHandSize. From the second player turn onward, runtime clears DrawnThisTurn, publishes OnDrawPhase, executes draw-phase behaviors, and draws according to BattleRules.DrawPerTurn

tip

Turn 1 does not repeat this process because the opening hand was generated during initialization

The hand limit comes from BattleRules.MaxHandSize and may be changed by Hand Size Hook. A full hand produces different results based on the source of the operation:

  • Opening-hand and automatic turn draws send overflow cards to Discard, publishing OnCardDiscarded and running status plus card On Discard Behaviors for each card

  • GCSApi.DrawCards and FlowGraph Draw Cards stop drawing additional cards; for every actual draw, runtime publishes OnCardDrawn, completes player-status On Drawn branches in snapshot order, then completes the card's own branch before the next card proceeds

  • Adding or moving a card to a full hand sends that card to Discard through the same discard event and Behavior lifecycle

Playing a card

After GCSApi.TryPlayCard(card, target) succeeds, runtime processes the card in this order:

  1. Confirm phase, hand membership, definition, cost, play Hooks, and the resolved target before committing anything
  2. Remove the instance from Hand, spend energy, publish OnEnergySpent for a positive cost, and add the instance to PlayedThisTurn
  3. Publish OnCardPlayed, execute the card's On Play Behavior, then execute matching player-status On Play branches
  4. Register persistent Hooks created by the card after every deferred branch completes
  5. Preserve any pile destination authored by the On Play graph; otherwise run the exhaust event and lifecycle for an Exhaust card, or place a regular card in Discard without treating the default post-play destination as a discard lifecycle

Because runtime removes the card before On Play begins, hand queries, draws, and choices inside that Behavior see the post-play hand. Custom UI only needs to respond to card events and refresh its view; it should not delete the instance when the card is clicked

Processing the hand at turn end

The default HandDiscardRule.DiscardOnTurnEnd processes the remaining hand as follows

Card stateResult
Regular cardMoves to Discard through the discard event and status plus card lifecycle
RetainRemains in Hand and publishes OnCardRetained
EtherealMoves to Exhaust and publishes OnCardExhausted

When the Encounter uses HandDiscardRule.Persist, regular cards also remain, but Ethereal cards still move to Exhaust. If a card's On Turn End Behavior moves that instance first, cleanup preserves the authored destination and never adds a duplicate to another pile

Creating and moving cards

TaskAPI
Draw cardsGCSApi.DrawCards(count)
Add a card to the handGCSApi.AddCardToHand(card)
Move an existing instanceGCSApi.MoveCardToPile(instance, pile)
ShuffleGCSApi.ShuffleDrawPile()

PileScope.Master and PileScope.All mean different things as read scopes and movement targets

ValueReadingAs a movement target
MasterConverts the Run Master Deck to temporary instances for the queryRemoves the card from all four battle piles without changing the Master Deck
AllCombines all four battle piles without the Master DeckRemoves the card from all four battle piles

Moving an existing instance into Discard or Exhaust publishes OnCardDiscarded or OnCardExhausted and completes matching status plus card Behaviors before the effect wave continues. Moving the same instance to its current pile again is a no-op, so a repeated destination does not duplicate events or lifecycle execution

Creating an instance directly in Discard or Exhaust with Add Cards establishes its initial location and does not count as a discard or exhaust lifecycle. The same distinction applies to post-play cleanup: normal and nested cards enter the default Discard destination without OnCardDiscarded, while explicit movement, overflow, and end-of-turn cleanup do run the discard lifecycle

warning

Do not modify CardPile.Cards directly. Writing to the list bypasses pile rules, card events, and UI refreshes

Run Master Deck

The Run Master Deck persists between battles in the same Run. It records card GUIDs, upgrade states, and cost changes. At the start of a new battle, runtime creates new CardInstance objects and battle piles from it

TaskAPI
Read or replaceGetMasterDeck(), SetMasterDeck()
Add or remove cardsAddCardToMasterDeck(), RemoveCardFromMasterDeck()
Upgrade a cardUpgradeCardInMasterDeck()
Clear before a new RunClearMasterDeck()

Changing the Master Deck does not retroactively alter a battle that has already started. See starting a battle for multi-battle Run initialization

Refresh card UI from events

GCS publishes matching events when a card is drawn, played, discarded, exhausted, retained, added to hand, transformed, upgraded, or otherwise modified. Events determine when to animate or refresh the interface; read the current pile contents again through queries such as GCSApi.Hand()

See the event reference for complete event names and arguments, and custom UI integration for a hand UI example