Skip to main content

Runtime Errors

GCS ยท Troubleshooting

Play Mode diagnosis with the Monitor as your instrument: startup failures, actions refused by phase timing, card plays that misfire, event subscriptions that stay silent, and visuals lagging behind state.

For developers whose content validates clean but whose battle still misbehaves once Play Mode starts.

Runtime errors appear only after the scene enters Play Mode, which is exactly why the Game Card Monitor exists. Before reading any code, let it answer three questions: did the runtime state change, which phase is the battle in, and which flows and events actually ran. Each failure class below names the tab that exposes it.


Startup failuresโ€‹

Play Mode starts, no battle appears, and the Monitor Battle tab shows nothing active. Work down this list:

  1. GameCardManager exists in the scene and all six databases are assigned.
  2. A GameCardEncounterBootstrap (or your own GCSApi.StartBattle call) references an encounter, and Auto Start On Play is enabled if the battle should begin on its own.
  3. The encounter's player unit and enemy lineup resolve to content inside the active databases.

When setup looks right and the battle still refuses to start, read the first Console error and ignore the rest. Later errors are usually consequences of the first one.


Actions refused by timingโ€‹

A card will not play, End Turn appears dead, or an API call seems ignored. Almost always the battle is not in the phase the action requires:

  • Read the current phase from GCSApi.Phase or the Monitor Phase tab. Card plays and End Turn need PlayerPhase; during PlayerTurnStart, PlayerTurnEnd, EnemyPhase, and the two end states they are refused by design.
  • Check the Monitor Gate tab. The battle holds between phases while animations run; the tab lists every current gate holder with its acquire and release log. A presentation script that acquires the gate and never releases it freezes the battle in a way that looks like input being ignored.
  • Confirm the selected target is still alive, and that no status vetoes the play through Card Playable Hook.

Game Card Monitor Gate tab listing every animation gate holder with its acquire and release history

GCSApi.CanPlayCard(card) gives you the playability verdict from code, which is handy inside custom UI.


Card play misbehaviorโ€‹

The card disappears without effect, stays in hand, lands in the wrong pile, or the energy math surprises you. In that order of likelihood:

  • Open the Monitor Flow tab and confirm the card's behavior graph actually executed. A play with no flow entry means the graph's entry node does not match, exactly the "plays but nothing happens" case.
  • Check the card's tags. Exhaust, Retain, and Unplayable each change where the card goes or whether it can be played at all.
  • Check the encounter's battle rules for hand discard and hand size; a card "vanishing" at turn end is often the discard rule doing its job.
  • Cost surprises come from cost modifiers and Card Cost Hook graphs, not from the printed cost.

If custom nodes are involved, verify every battle mutation goes through ctx.Controller. State changed any other way bypasses events, hooks, and the Monitor alike, and the symptoms get strange.


Subscriptions that stay silentโ€‹

Custom UI does not refresh, a listener fires twice, or it never fires at all. The Monitor Event tab shows every event the battle raised, with payloads, which immediately splits the problem: if the event appears there, the runtime raised it and your subscription is at fault; if it never appears, the thing you are waiting for never happened.

Game Card Monitor Event tab listing raised battle events with their payloads in play order

The usual subscription mistakes:

  • Subscribing before the battle manager exists, so the handler attaches to nothing.
  • Never disposing. Every GCSApi subscription returns an IDisposable; keep it and dispose it when the listening object goes away, or reloads will stack duplicate handlers:
IDisposable _played;

void OnEnable() => _played = GCSApi.OnCardPlayed(args => Refresh());
void OnDisable() => _played?.Dispose();
  • A typed listener with the wrong argument type, or an event name that does not exactly match GCSEventNames.
  • Confusing the two event layers: Raise Internal Event / On Internal Event connect graphs to each other, while C# code listens to the runtime events behind GCSApi.Subscribe.

For GES-bridged events, also confirm GES is installed (the TG_GES_INSTALLED define is managed automatically when it is detected) and that the GES event GUID on the node matches an event in an active GES database.


Mechanics work, visuals do notโ€‹

Damage lands in the Monitor but the screen disagrees: stale HP bars, missing VFX, silent audio, an enemy with no visible body. Since the state is confirmed correct, the search space is presentation only. Inspect, in rough payoff order: scene UI references, unit prefabs, card templates and artwork, status icons, and the VFX, SFX, and animation references on the graph's FX nodes.


Capture a repro before asking for helpโ€‹

When a runtime problem resists diagnosis, record the scene path, the encounter name, the phase shown in the Monitor, the action being performed, the first Console error, and what the Battle, Pile, Unit, Flow, and Event tabs showed. That set separates state bugs from presentation bugs from setup bugs, and it turns "it sometimes breaks" into something answerable.