Teams
A team is one strategy file describing several characters. Each member owns its clients, enchant policy, targets, and fallback; each phase names the members that run it.
Without a members: block, a file is treated as a single implicit member — which is why every
other page in this section works without mentioning teams.
Structure
$schema: https://scm.kebab.sh/strat/config.json
name: life_fire
members:
life:
clients: [p1]
enchant:
support: { prefer: sharpen }
trap: { prefer: potent }
targets:
default_ally: p2
default_enemy: boss
fallback: pass
fire:
clients: [p2]
enchant:
damage: { prefer: epic }
targets:
default_enemy: boss
fallback: pass
phases:
life-main:
members: [life]
priority: 100
actions:
- cast: { name: 'Tri Blade', enchant: required }
- pass
fire-main:
members: [fire]
priority: 100
actions:
- cast: { name: 'Fire Dragon', enchant: required }
- pass
Each member gets its own copy of the strategy: its own enchant policy, its own target defaults, its own fallback, and only the phases assigned to it.
Note that both phases here have priority 100 and never conflict — they are assigned to different members, so they are evaluated in different contexts. Priority only orders phases within a member.
How members bind to clients
On the console, enable combat life_fire starts the whole team at once, and members are
matched to hooked clients by these rules:
- Members are considered in alphabetical order by member name —
firebeforelife, not file order. - A member takes the first hooked client matching its
clients:list that no earlier member has already claimed. - In a multi-member file, each member claims exactly one client.
- A member with no
clients:list matches anything still unclaimed. In a multi-member file that means it takes whichever client happens to be first — which is why every member in a team should name its clients explicitly. - A member that matches nothing simply does not run. The console logs how many clients were
left idle;
combat dry-run <name>shows the same mapping before you commit to it.
combat dry-run life_fire
name: life_fire
targets: life=p1; fire=p2
type: team
life: p1 (5 phase(s))
fire: p2 (4 phase(s))
target preview: p1=life,p2=fire
The trainer does not read clients: and does not bind members to clients at all. Instead it
registers each member as a separate selectable strategy named life_fire/life and
life_fire/fire, and you point each one at a client yourself from the trainer's own controls.
A single-member file (no members: block) registers under its plain name: on both hosts.
Member fields
Each member accepts the same shorthand the top level does:
| Field | Purpose |
|---|---|
clients | Which aliases this member binds to |
school | Metadata |
enchant | Enchant policy for this member |
targets | Default enemy/ally for this member |
fallback | pass or flee |
Those five are the whole list — a member is not a strategy, so it takes no phases:, no
name:, and no retry_failed_casts. Anything else inside a member is an unknown-field load
error.
members: with top-level shorthandSetting both members: and a top-level clients, enchant, targets, fallback, or
school is a load error:
strategy "x" mixes members: with top-level clients/enchant/targets/fallback; use one form
The message lists four fields but school triggers it too — and the published schema does not
know that, so an editor will let members: and a top-level school: through. If you hit this
error and the four named fields are all absent, school: is the culprit.
Pick one form. retry_failed_casts, cast_retry_limit, name, and description stay at the
top level and apply to every member.
Assigning phases
members: on a phase restricts it. A phase with no members: runs for every member.
phases:
everyone-emergency:
priority: 200 # no members: → all members run this
when:
health_below: 25
actions:
- heal: { school: life, target: self }
life-setup:
members: [life]
priority: 100
actions:
- cast: { name: 'Tri Blade', enchant: required }
fire-hit:
members: [fire]
priority: 100
actions:
- cast: { name: 'Fire Dragon', enchant: required }
A shared phase runs in each member's own context, so target: self means a different
participant depending on who is executing it.
A phase that runs for more than one member must use relative targets: omit target: to fall
back to that member's default_ally/default_enemy, or use self.
phase "setup" in strategy "life_fire" is shared across members but action 1 targets a
hardcoded client alias "p1"; use relative targets (ally/enemy/self)
A phase counts as shared when it names two or more members, or when it names none at all in
a file with more than one member. The second case is the surprising one: adding a second member
to a file can turn a previously valid phase into a load error, because a phase with no
members: now runs for everybody.
If a phase genuinely needs to name p1, assign it to a single member.
A real example
life_fire.yml, one of the example strategies that ships with the suite, is a working
two-character team: a life support character that lays sharpened blades and potent feints,
and a fire hitter that spends them. Copy it into your strategy folder to try it.
phases:
life-main:
members: [life]
priority: 100
actions:
# sharpened blade on the hitter, if it doesn't have one
- cast:
name: 'Tri Blade'
enchant: required
enchants:
- prefer: sharpen
when:
no_blade: { target: p2, school: fire, enchanted: true }
# plain blade once the sharpened one is down
- cast:
name: 'Tri Blade'
enchant: none
when:
all:
- has_blade: { target: p2, school: fire, enchanted: true }
- no_blade: { target: p2, school: fire, enchanted: false }
# potent feint on the boss
- cast:
name: 'Feint'
enchant: required
enchants:
- prefer: potent
when:
no_trap: { target: boss, enchanted: true }
# plain feint on top
- cast:
name: 'Feint'
enchant: none
when:
all:
- has_trap: { target: boss, enchanted: true }
- no_trap: { target: boss, enchanted: false }
- pass
The structure is a checklist inside a single phase, driven by guards. Each step asks the board
whether its effect is already present, so nothing is double-applied and anything stripped is
reapplied. The trailing pass stops the support character from throwing chip damage once the
board is set.
The fire member is much simpler, because its job is to spend what the support laid down:
fire-main:
members: [fire]
priority: 100
actions:
- cast: { name: 'Frenzy', enchant: none }
- cast: { name: 'Fire Dragon', enchant: required }
- cast: { name: 'Phantasmania', enchant: required }
- pass
Teams versus separate files
One team file when the characters' behaviour is coupled — the support needs to know who the hitter is, and the two lists of phases only make sense together. Editing one file keeps them in sync.
Separate files when each character is independently useful, or when you mix and match. A
generic support-life strategy that buffs default_ally works with any hitter.
The life_fire example is coupled: the life member targets p2 explicitly and the whole
blade/feint sequence assumes a fire hitter. That belongs in one file.
Running one
From the console, one command starts the whole team:
strategies
strategies validate
combat dry-run life_fire
enable combat life_fire
status
combat dry-run prints the member-to-client mapping before you start anything; status shows
per-client combat state once it is running. A member whose clients: list matches nothing
hooked simply does nothing, and the console logs how many clients it left idle.
In the desktop app there is no team-level entry. life_fire/life and life_fire/fire appear
as two separate strategies under the Trainer's Build tab, and you start each against the client
you want. Note that this example's target: p2 guards will not work there — see
Targeting → client aliases.