Skip to main content

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 namefire before life, 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 desktop app binds members differently

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:

FieldPurpose
clientsWhich aliases this member binds to
schoolMetadata
enchantEnchant policy for this member
targetsDefault enemy/ally for this member
fallbackpass 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.

You cannot mix members: with top-level shorthand

Setting 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.

Shared phases may not hardcode a client alias — this is a load error

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.