Skip to main content

libstrat

libstrat is a YAML format for describing combat. Instead of hardcoding spell logic in Go or Lua, you write a file that answers:

  • Which client or clients run this?
  • What happens first in the fight?
  • When do I heal, blade, trap, or flee?
  • Which target gets hit?
  • Which cards get enchanted, and with what?

The combat runner loads the file, and each round it walks your phases in priority order, finds the first one whose conditions hold, and executes the first action in that phase that resolves to a real card.

Why data and not code

Combat decisions are made under time pressure, hundreds of times a session, with no operator watching. A strategy expressed as data can be fully validated when the file loads — unknown fields, bad client aliases, malformed expressions, and impossible enchant chains all become errors before a single round is played.

The same logic written imperatively fails in the middle of a duel, which is the worst possible time to discover a typo.

The loop back from actions to phases is the part worth remembering: a phase that matches but resolves nothing does not end the round — the runner falls through to the next eligible phase.

A strategy is identified by its name:, not its filename

Two files in your strategy folder that both say name: storm are one strategy — whichever loads second silently replaces the first. Renaming the file changes nothing; changing name: changes everything. See File format → name.

The smallest useful file

$schema: https://scm.kebab.sh/strat/config.json
name: pass-only
description: Always passes.

phases:
default:
actions:
- pass

A real one

$schema: https://scm.kebab.sh/strat/config.json
name: storm-hitter
description: Storm hitter that buffs first, then AOE attacks.
school: storm

clients:
- p2

enchant:
auto: true
damage:
prefer: epic
fallback: colossal

targets:
default_enemy: boss
default_ally: self

fallback: pass

phases:
setup:
priority: 50
when:
no_blade: storm
actions:
- blade: { school: storm, target: self }

execute:
priority: 10
actions:
- cast:
type: damage
school: storm
aoe: true
min_pips: 4
enchant: required
- cast: { type: damage, school: storm, target: boss }
- wand_hit: { target: first }

Read top to bottom: this is for p2, it prefers Epic then Colossal for damage enchants, it targets the boss by default, and it passes if nothing resolves. At priority 50 it blades if it has no storm blade; at priority 10 it tries an enchanted AOE of at least 4 pips, then any storm damage spell at the boss, then a wand hit.

Where to go

PageCovers
Getting startedWriting and loading your first file
File formatEvery top-level field
PhasesPriority, ordering, resolution
Conditionswhen: in all its forms
ExpressionsThe expr: mini-language
Actionscast, blade, trap, heal, and friends
TargetingTarget selectors and client aliases
EnchantsEnchant policies and chains
TeamsOne file, several characters
ExamplesAnnotated complete strategies
TroubleshootingWhen it loads but does nothing

Two hosts, one format

The same file is run by two different programs: the libwiz console and the desktop app's trainer. The format, the validation, and the round-by-round evaluation are identical in both. Everything around the file is not — which clients a strategy binds to, whether p1/p2 targets work at all, and what the default strategy is called all differ.

Read Getting started → which host is running it before you write anything that names a client alias. It is the single most common source of "this worked yesterday on the other machine".

Relationship to Deimos

The format and much of the evaluation semantics are modelled on Deimos, so that existing strategies port over. Where libstrat deliberately differs, it is almost always in the direction of failing at load time instead of silently evaluating false forever. Those divergences are catalogued in Expressions.