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:
- Phases are sorted by
priority, highest first. - The first phase whose
when:is satisfied is selected. Nowhen:means always eligible. - Its actions are walked in order.
- Each action's own
when:guard is evaluated. A failing guard skips the action. - 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. - If no action in the phase resolves, the runner moves to the next eligible phase.
- If no phase produces anything,
fallbackruns.
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 }
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-levelmembers: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.