Skip to main content

Phases

A phase is a named bundle of a priority, an optional condition, and an ordered list of actions. Phases are how you express "do this first, unless that, and otherwise this".

phases:
emergency:
priority: 100
when:
health_below: 30
actions:
- heal: { target: self, school: life }
- pass

setup:
priority: 50
when:
no_blade: fire
actions:
- blade: { school: fire }

execute:
priority: 10
actions:
- cast: { type: damage, school: fire }
- wand_hit: { target: first }

Resolution

Each round, before any phase is looked at, the runner checks whether you are stunned. If you are, it passes and stops there. Nothing in your file runs on a stunned round — which is why a phase guarded on is_stunned: true can never fire. See Conditions.

Otherwise:

  1. Phases are sorted by priority, highest first.
  2. The first phase whose when: is satisfied is selected. No when: means always eligible.
  3. Its actions are walked in order.
  4. Each action's own when: guard is evaluated. A failing guard skips the action.
  5. The first action that resolves is executed — a card it can actually cast, or a card-less action like pass/flee/draw, which always resolve.
  6. If no action in the phase resolves, the runner moves to the next eligible phase.
  7. If no phase produces anything, fallback runs.

Step 6 is the one people miss. A phase matching its condition does not commit the round to that phase — if nothing inside it resolves, play continues down the list.

That is what makes an emergency phase safe:

emergency:
priority: 100
when:
health_below: 30
actions:
- heal: { school: life, target: self }

At 25% health with no heal in hand, this phase is selected, its single action fails to resolve, and the runner falls through to whatever is next. You still attack. Without fall-through you would stand there passing while dying.

Priority

Higher runs first. Any integer; negatives are fine.

Phases with no priority default to 0, and phases sharing a priority fall back to alphabetical order by phase name. That tiebreak is deterministic but not something to design around — always give every phase a distinct explicit priority instead of relying on how its name happens to sort.

A spacing convention that leaves room to insert later:

emergency: { priority: 100, ... }
setup: { priority: 75, ... }
pressure: { priority: 50, ... }
execute: { priority: 10, ... }

Conditions

when: accepts the full condition grammar — see Conditions.

phases:
aoe:
priority: 60
when:
all:
- enemies_at_least: 3
- pips_above: 3
actions:
- cast: { type: damage, aoe: true }

A phase with no when: is always eligible, which is how you write a default phase. Give it the lowest priority so it only runs when everything above declines.

Action order

Within a phase, actions are tried top to bottom and the first that resolves wins. Order from most specific to least specific.

# good
actions:
- cast: { name: 'Glowbug Squall', enchant: required }
- cast: { type: damage, school: storm, aoe: true, min_pips: 4 }
- cast: { type: damage, school: storm }
- wand_hit: { target: first }

# bad — the first line swallows every round
actions:
- cast: { type: damage }
- cast: { name: 'Glowbug Squall', enchant: required }

A pass at the end of a phase makes it terminal — pass always resolves, so nothing below or after that phase ever runs:

emergency:
priority: 100
when:
health_below: 25
actions:
- heal: { school: life }
- pass # stop here even if the heal failed

Use that deliberately. Below 25% health you may genuinely prefer to pass rather than fall through into an attack phase that draws more aggro.

Per-action guards

Any action can carry its own when:, evaluated before the action tries to resolve a card. A failing guard skips to the next action.

This lets one phase walk a checklist:

phases:
main:
priority: 75
actions:
- blade: { school: fire, target: p1 }
when:
no_blade: { target: p1, school: fire }

- blade: { school: fire, target: p2 }
when:
no_blade: { target: p2, school: fire }

- trap: { school: fire, target: boss }
when:
no_trap: { target: boss, school: fire }

- cast: { type: damage, school: fire }
when:
all:
- has_blade: { target: p1, school: fire }
- has_trap: { target: boss, school: fire }

Round one blades p1, round two blades p2, round three traps the boss, round four hits. Each guard checks the board rather than counting rounds, so a blade that gets stripped is reapplied automatically.

The YAML shape is worth noting: when: sits as a sibling of the action key, not inside it.

- blade: { school: fire, target: p1 } # the action
when: # its guard, same list item
no_blade: { target: p1, school: fire }
Guard targets default to self

In scalar and boolean condition forms, the implicit target is the caster — same as a phase-level when:. To check a different participant, use the object form with an explicit target:, as above.

Phases versus guards

Both express conditions, so which do you use?

Phases for mutually exclusive modes. Emergency versus normal, boss versus mob, opener versus mid-fight. Each mode has its own list of actions and only one runs.

Guards for a checklist inside one mode. Setup steps that each need doing once, where the order matters but the mode does not change.

Splitting a checklist across phases works but is noisier, and you have to keep the priorities consistent as it grows:

# fine, but verbose
phases:
blade-p1: { priority: 90, when: { no_blade: { target: p1 } }, actions: [ ... ] }
blade-p2: { priority: 89, when: { no_blade: { target: p2 } }, actions: [ ... ] }
trap: { priority: 88, when: { no_trap: { target: boss } }, actions: [ ... ] }

# better
phases:
setup:
priority: 90
actions:
- blade: { target: p1 }
when: { no_blade: { target: p1 } }
- blade: { target: p2 }
when: { no_blade: { target: p2 } }
- trap: { target: boss }
when: { no_trap: { target: boss } }

Members

In a team file, members: on a phase restricts it to named members. A phase with no members: runs for all of them. See Teams.

phases:
life-setup:
members: [life]
priority: 100
actions:
- cast: { name: 'Tri Blade', enchant: required }

Two rules are enforced at load time:

  • members: on a phase in a file that has no top-level members: block is an error — phase "x" ... assigns members but no members: block is defined.
  • A name that is not a defined member is an error — phase "x" ... references unknown member.

A third rule catches the mistake that would otherwise be invisible: a phase that runs for more than one member may not target a hardcoded client alias. See Teams → shared phases.

What is not a phase field

priority, when, actions, and members are the whole list. Anything else inside a phase is an unknown-field load error, including plausible-looking ones like description or enabled.

A phase whose actions: list is empty is also an error. If you want a phase that deliberately does nothing, give it a single - pass.