Skip to main content

Runtime Architecture

GCS ยท Runtime

How the GCS runtime is layered: manager, state machine, state view, controller, flow execution, events, and presentation.

Read this before writing custom battle UI or gameplay code, so your code mutates state through the right surface instead of editing internal lists.

The GCS runtime turns authored content into a live battle. The boundary worth memorizing is short: content assets describe what can happen, battle state stores what is happening right now, and the controller is the only safe way to change that state.

Runtime layersโ€‹

LayerMain typesResponsibilityRule for your code
ManagerGameCardManager, GCSApiHolds the active databases, the active battle, the run master deck, and the public entry point.External code starts with GCSApi.
State machineBattleStateMachineOwns battle phases, turn transitions, choice waits, reward waits, and enemy turns.Never force phase changes. Call StartBattle, TryPlayCard, EndPlayerTurn.
State viewIBattleState, BattleStateViewExposes turn, energy, units, piles, played/drawn history, and effective card costs.Read through GCSApi.Battle or the GCSApi helpers.
ControllerIBattleController, BattleStateViewMutates health, armor, energy, statuses, cards, delayed triggers, variables, and summoned enemies.Mutate through GCSApi or FlowGraph nodes, not by editing lists and fields.
Flow executionGameCardFlowExecutor, EffectActionContextRuns card, status, and enemy FlowGraphs with source, target, card, battle, controller, and databases in scope.Graphs read and write through context helpers and controller actions.
EventsGCSEvents, IGCSEventChannel, GCSEventNamesNotifies UI, integrations, and custom systems when battle state changes.Subscribe with GCSApi shortcuts or typed named events.
PresentationUnitView, FloatingTextSystem, FX nodes, demo UIShows state changes through UI, animation, VFX, SFX, floating text, and camera feedback.Presentation observes runtime results. It never owns gameplay rules.

Public entry pointโ€‹

GCSApi is the documented runtime facade. It is intentionally broad so custom UI, sample code, tools, and gameplay integrations do not need to depend on internal classes.

Use it for:

  • Starting and disposing battles.
  • Playing hand cards and ending the player turn.
  • Reading battle phase, turn, energy, unit state, enemy intents, card piles, and status stacks.
  • Applying controlled state changes.
  • Reading active databases and finding content by GUID.
  • Maintaining the run master deck.
  • Subscribing to events.
  • Discovering extension registries.

GCSApi.Manager, GCSApi.Battle, and GCSApi.Controller are exposed for integration work, but most code should prefer the named helper methods. They state intent more clearly and handle a missing battle with safe defaults.


Read state versus mutate stateโ€‹

GCS separates read-only state from writable control.

SurfaceTypeWhat it can do
GCSApi.BattleIBattleStateRead current turn, energy, player, piles, alive enemies, played/drawn history, and effective card cost.
GCSApi.ControllerIBattleControllerEverything IBattleState can read, plus mutations that go through runtime rules.
GCSApi helpersStatic methodsSafe, named shortcuts for the same common reads and writes.
warning

Directly editing UnitState.Statuses, CardPile.Cards, or UnitState.CurrentHp bypasses hooks, events, UI updates, and validation. Use controller-backed methods instead.


Battle state modelโ€‹

A live battle contains:

StateMeaning
TurnNumberStarts at 1 and increments after each enemy phase resolves.
IsPlayerTurnTrue during player turn start, action, and end phases; false during the enemy phase.
PlayerEnergyEnergy still available this turn.
PlayerUnitThe player's unit state.
AliveEnemyUnitsEnemies still alive. Dead units are excluded.
HandPile, DrawPile, DiscardPile, ExhaustPileRuntime card zones.
PlayedThisTurnCards played during the current turn, in play order.
DrawnThisTurnCards drawn during the current turn, in draw order.

The draw pile is ordered so the last element is the next card drawn.

The Battle tab of the Game Card Monitor shows this model live in Play Mode: active encounter, phase, turn, a player snapshot, and the enemy list.

Game Card Monitor Battle tab showing the live battle snapshot, player state, and enemy list


Unit state modelโ€‹

UnitState is the live unit record used by the battle.

Field or propertyMeaning
UnitIdRuntime id used by UI and events.
SourceThe authored GameUnit asset behind the unit.
CurrentHp, MaxHpCurrent and maximum health.
CurrentArmorCurrent armor.
CurrentEnergyEnergy value on the unit. For the player's battle pool, read GCSApi.PlayerEnergy.
StatusesRuntime stack counts by GameStatus.
StatusAppliersThe unit that applied each status, when known.
ActiveHooksRuntime hooks contributed by statuses and cards.
WasAttackedThisTurnWhether the unit has been attacked this turn.
SkipNextTurnsPending skipped turns.
IsDeadTrue when current HP is 0 or below.
HpPercentInteger percentage of current HP over max HP.

For external reads, prefer GCSApi.Hp, GCSApi.MaxHp, GCSApi.Armor, GCSApi.HpPercent, GCSApi.HasStatus, and GCSApi.StatusesOn.


Flow execution contextโ€‹

When a FlowGraph runs, GCS builds an execution context that carries:

Context valueWhat it means
SourceThe unit responsible for the behavior.
HostThe unit or owner currently hosting the behavior.
TargetThe chosen or resolved target.
BattleRead-only battle state.
ControllerRule-aware mutation surface.
CardThe active card instance, when the graph is card-related.
Status databaseActive status lookup.
Card databaseActive card lookup.
Enemy unit databaseActive enemy lookup.

Built-in nodes use this context automatically. Custom nodes should also read inputs and mutate state through the context and controller rather than through unrelated global lookups.

To watch this context feed real executions, open the Flow tab of the Game Card Monitor. It lists every graph run the executor reports, newest first:

Game Card Monitor Flow tab listing FlowGraph executions during a live battle


Waits and async presentationโ€‹

The battle state machine can pause progression while presentation or player choices are still resolving.

MechanismUsed for
Choice waitChoice nodes pause execution until the player picks or skips.
Animation gateAnimation and presentation can hold phase transitions until a gated action completes.
Flow waitWait nodes delay a continuation by seconds or by animation id.
Reward waitAfter a win, reward selection keeps the battle waiting until applied or skipped.

This is why custom UI should react to events and state changes instead of assuming one API call completes every visible step immediately.


Events are notifications, not state storageโ€‹

Events tell systems what just changed. They are not the source of truth. A custom UI should use events to know when to refresh, then read the current state through GCSApi:

  1. Subscribe to a relevant event.
  2. In the handler, read current state from GCSApi.
  3. Update your view model or UI widgets.
  4. Dispose subscriptions when the UI is destroyed.

This keeps UI correct even when several events fire in quick succession.


Integration boundariesโ€‹

NeedRecommended surface
Custom battle UIGCSApi read helpers plus typed event shortcuts.
Card play controlsGCSApi.CurrentHand, GCSApi.CanPlayCard, GCSApi.TryPlayCard, GCSApi.RequiresTarget.
Unit panelsGCSApi.Player(), GCSApi.AliveEnemies(), unit helpers, HP/status/intent events.
Content browserGCSApi.Cards, Decks, PlayerUnits, EnemyUnits, Statuses, Encounters, and the Find* helpers.
Runtime mutation from external systemsGCSApi mutation helpers or GCSApi.Controller.
Custom event bridgeGCSApi.Subscribe, GCSApi.Events, GCSApi.RaiseGameEvent, or GES bridge events.

Avoid depending on demo-only components such as sample hand views, reward views, or scene-specific presenters unless you are modifying the demo itself.