# KeyRota: advanced rotations (KRC), documentation

Source: https://keyrota.app/documentation/en/advanced (Updated October 18, 2026). JSON Schema: https://keyrota.app/documentation/keyrota-rotation.schema.json. Index for AI agents: https://keyrota.app/documentation/llms.txt.

---

<!-- https://keyrota.app/documentation/en/advanced -->

## Advanced rotations (KRC)

> [!TIP] We recommend building rotations with an AI agent
> It is much faster and more convenient than setting a rotation up by hand. The more advanced the AI agent, the closer the rotation matches your requirements on the first try. We recommend the latest Anthropic and OpenAI models with the reasoning level "Extra (xhigh)". How to do it: [Building with an AI agent](https://keyrota.app/documentation/en/ai).

An advanced rotation is your own list of rules: which ability to press and when. KeyRota checks the rules in combat and presses the first button that fits. The format and the engine of these rotations are called **KRC** (KeyRota Customization).

### Basic or advanced

| | Blizzard (basic) | Advanced (KRC) |
|---|---|---|
| Who picks the button | The game: Blizzard's suggestion | Your rules |
| Setup | None | Slots, auras and rules: in the builder or from a file |
| Specializations | Any damage (melee and ranged DPS) or tank specialization | The one the rotation is written for |
| Burst and target mode | Not used | The Burst and Targets buttons; the rules decide what they mean |
| When to choose | To start quickly on any character | You need an exact priority: DoTs, resource pooling, cooldowns in a burst window, your own multi-target logic |

In the app's Rotation block the Blizzard profile comes first, your rotations below it, under "Your rotations". You can switch at any time while the rotation is stopped.

### What a rotation is made of

| Part | What it is | How many |
|---|---|---|
| **Slots** | The rotation's buttons. Rotation slot N is slot N of the Better Assistant profile. | up to 12 |
| **Auras** | Your buffs and your debuffs on the target, by spell ID. Rotation aura N is aura N of the Better Assistant list. | up to 12 |
| **Resources** | Player and target health and the specialization's resources: energy, combo points, rage, mana and others. All values are percentages. | per specialization |
| **Combat** | The number of enemies nearby and the distance to the target. | 2 |
| **Toggles** | The quick settings panel buttons: target mode (two flags in rules: `aoe` and `st`), Burst, Healing / defense, Ground, Movement. | 6 |
| **Rules** | Which slot to press and under which conditions. The order of the rules is the priority. | unlimited |

A rule is made of condition groups. A rule holds when **at least one** of its groups holds (OR), and a group holds when **all** of its conditions hold (AND). A rule without groups always holds.

*KRC text:*

```text
use Envenom when  # Envenom
	Combo_Points >= 70 and Envenom_Buff.down  # 5+ CP, no buff
	or
	Combo_Points >= 70 and Energy >= 70  # 5+ CP, plenty of energy
```

*JSON:*

```json
{
  "use": "Envenom",
  "name": "Envenom",
  "when": [
    {"all": ["Combo_Points >= 70", "Envenom_Buff.down"], "comment": "5+ CP, no buff"},
    {"all": ["Combo_Points >= 70", "Energy >= 70"], "comment": "5+ CP, plenty of energy"}
  ]
}
```


It reads: "Press Envenom if there are 5 or more combo points and no Envenom buff, **or** if there are 5 or more combo points and at least 70% energy". Why 5 combo points is `70` is explained in [Resources](https://keyrota.app/documentation/en/conditions#resources).

### How KeyRota chooses a button

1. Rules are checked **from top to bottom**, in list order.
2. For each rule its groups are checked. If none holds, KeyRota moves on to the next rule.
3. If the rule holds but its spell **can't be used** right now (cooldown, not enough resource, target too far, no target, spell not learned), KeyRota also moves on to the next rule. You don't need a condition for the slot's own readiness.
4. The first rule that holds and whose spell can be used is **chosen**. The global cooldown (GCD) doesn't change the choice: a rule waiting for the end of the GCD stays chosen, and the rules below don't overtake it.
5. If no rule fits: with "Ignore main icon" on, KeyRota waits; with it off, it presses the game's suggestion.

Waiting is normal. A rotation that pools energy, for example, often presses nothing for half a second or a second until there is enough energy for a rule above.

### An unknown value never allows a rule

Sometimes KeyRota doesn't know a value: an aura isn't read (its ID is not in the Better Assistant profile), there is no target (the target's health and distance are unknown), enemies weren't counted. A condition that reads an unknown value is **false**: `X.up` and `X.down`, `TargetHP >= 50` and `TargetHP < 50` alike. An unknown value can only forbid a rule, never allow it: better to skip a press than to press the wrong thing.

> [!TIP]
> If a rule never fires, first check that every aura it reads is in the Better Assistant profile with the right ID. More in [FAQ and troubleshooting](https://keyrota.app/documentation/en/faq#never-fires).

### When a button is pressed

- **A normal spell** (on the GCD) is pressed in the spell queue window at the very end of the GCD: the game casts it as soon as the GCD ends.
- **Slot flags** change that: "Manual only (M)", "Channel (C)", "Off the GCD (O)", "In the queue window (Q)". What each does and how to combine them is in [Slot flags](https://keyrota.app/documentation/en/rotation-json#flags).
- **Channels.** While a channel is going, KeyRota presses nothing so as not to break it. During a **cast** KeyRota waits until only the queue window is left.
- **Your own presses.** When you press an ability key yourself, KeyRota steps aside and doesn't fight you.

### Quick start

If KeyRota isn't installed yet, begin with the [first steps](https://keyrota.app/documentation/en/start).

1. In KeyRota open Rotation — New — In the builder….
2. On the General page choose the specialization and name the rotation.
3. On the Slots page add the buttons and their spell IDs; on the Auras page, the buffs and debuffs the rules will read.
4. On the Rules page add the rules in priority order.
5. On the General page click "Build from slots and auras", then "Copy". In the game: `/ba` — the Profile tab — paste the string — Apply.
6. Put the slot spells on your action bars and bind keys to them (a macro with `#showtooltip` works too).
7. Click Save, choose the profile in the list and start the rotation with F1 (on macOS, ⌃⇧Space).

Every button, with a worked example, is in [Rotation builder](https://keyrota.app/documentation/en/editor).

### In this section

- [Rotation builder](https://keyrota.app/documentation/en/editor) — every page and button of the window and a rotation built step by step.
- [Rotation JSON](https://keyrota.app/documentation/en/rotation-json) — every field of the format, slot flags, names, limits, errors and the KRC text.
- [Conditions](https://keyrota.app/documentation/en/conditions) — every condition type, operators, percentages and unknown values.
- [Pattern examples](https://keyrota.app/documentation/en/patterns) — DoTs, pooling, builder and spender, AoE, Burst, defensives and other templates.
- [Building with an AI agent](https://keyrota.app/documentation/en/ai) — a prompt template, log analysis and checking the result.
- [FAQ and troubleshooting](https://keyrota.app/documentation/en/faq) — why a rule doesn't fire, presses early or late, breaks a channel.

---

<!-- https://keyrota.app/documentation/en/editor -->

## Rotation builder

The rotation builder is the KeyRota window where you put an advanced rotation together without typing it: slots with flags, auras, rules, with the KRC text and the JSON beside them. The documentation is the same for Windows and macOS; the screenshots were taken on Windows. They show the Assassination raid rotation from the [examples](https://keyrota.app/documentation/en/patterns#example-files).

### Opening it

- **Your rotation.** Select the profile in the Rotation block of the main window and click Configure.
- **A new rotation.** New — In the builder…. The specialization comes from the game or from the current profile. The rotation becomes a profile when you first save it.
- **From a file or the clipboard.** New — From file… accepts KRC text (`.txt`), rotation JSON and a KeyRota profile file; New — From clipboard takes the same from the clipboard. Open the new profile in the builder with Configure.

![A new rotation in the builder: the General page, no slots or rules yet](https://keyrota.app/documentation/img/en/editor-new.png)

For a **Blizzard profile** the builder shows only the name and the key press delay: the game picks the main button. "Build your own rotation" creates a new profile with an advanced rotation; the Blizzard profile stays in the list.

![A Blizzard profile in the builder: the profile name, the key press delay and "Build your own rotation"](https://keyrota.app/documentation/img/en/editor-blizzard.png)

#### The window

- **On the left**, the pages: General, Slots, Auras, Rules (with the number of rules), Text and JSON. Above them, "Your rotation" and the chosen specialization.
- **On the right**, the selected page.
- **At the bottom**, the status bar: whether the rotation is valid ("The rotation is valid: 15 rules, 11 slots, 10 auras"), whether there are unsaved changes and, on an error, where it is and a Show button. On the right, Close and Save.

Nothing is applied until Save (on Windows also Ctrl+S).

### General

![The General page: profile, specialization, rotation name, Ignore main icon, key press delay](https://keyrota.app/documentation/img/en/editor-general.png)

| Control | What it does |
|---|---|
| **Profile name** | The profile's name in the main window's rotation list. |
| **Specialization** | Sets the rotation's resources (energy, combo points, rage…) and the class and specialization of the Better Assistant profile. Changing it replaces the resources unless the rules read the old specialization's resources (health doesn't count). |
| **Rotation name** | The first line of the KRC text (`rotation "…"`), shown in the KeyRota log. No double quotes. A good place for the version: "Assassination v1.3". |
| **Ignore main icon** | On: KeyRota presses only by the rules; if no rule matches, it waits. Off: it then presses the game's suggestion. Usually on for your own rotations: waiting (for example, pooling energy) is normal. |
| **Key press delay** | A pause before each press, like a human's: None, Fixed (an interval of 0–1000 ms) or Random (a range of 0–1000 ms). "Also for interrupts and defensives" applies the same pause to those. |

![The General page further down: the Better Assistant block, the enemy counter radius, Only enemies in combat and the description](https://keyrota.app/documentation/img/en/editor-general-ba.png)

| Control | What it does |
|---|---|
| **Profile: …** | A summary of the Better Assistant profile string: specialization, number of spells and auras. |
| **Build from slots and auras** | Builds the Better Assistant profile string from the slots' spell IDs, each slot's "Better Assistant shows it" setting, the auras in order, the specialization, the radius and Only enemies in combat. If a profile of the same specialization is already pasted, its other settings are kept. |
| **From clipboard** | Paste a ready profile string. Its specialization must match the rotation's. |
| **Copy** | Copy the string to paste it in the game. |
| **Enemy counter radius, yards** | 1–100, 8 by default. Used by the "Enemies nearby" condition (`enemies`). |
| **Only enemies in combat** | On by default: enemies that aren't in combat yet aren't counted. |
| **Description** | Notes on the rotation: lines starting with `#` in the KRC text. They don't affect how the rotation works. |

Warnings appear under the buttons: which slots used in rules have no spell ID (they won't be in the Better Assistant profile) and what in an already pasted profile doesn't match the rotation.

> [!IMPORTANT]
> Auras in the Better Assistant profile go in a row, starting from the first. If aura 3 has no ID and aura 4 has one, the profile won't build: "Aura 3 has no spell number". Fill in the ID or renumber the auras.

#### The Better Assistant profile in the game

Rotation slot N is slot N in Better Assistant, aura N is aura N in its list. The profile string puts your slots and auras there in the right order.

1. On the General page click "Build from slots and auras", then "Copy".
2. In the game type `/ba`, open the **Profile** tab, paste the string, click **Apply** and confirm.
3. After any change to slots or auras (IDs, order, With a target / In combat) build and paste the profile again.

### Slots

![The Slots page: 12 places on the left, on the right the selected slot 10 Vanish with ID 1856](https://keyrota.app/documentation/img/en/editor-slots.png)

On the left are the 12 places: number, name, spell ID, flags ("off the GCD, queued") and "unused" when the slot isn't in any rule. An empty place says "Empty"; select it to get an "Add slot" button.

| Control | What it does |
|---|---|
| **Name** | The spell's name in the game. Shown in the rule list and the log. |
| **Spell ID** | The number in the Wowhead link: in `wowhead.com/spell=1856/vanish` it is `1856`. Better Assistant uses it to put the spell into this slot. |
| **Better Assistant shows it** | With a target or In combat. The slot is ready only when Better Assistant shows it. In combat is for healing and defensives that must work without a target. |
| **How to press** | Four flag switches, see the table below. |
| **Name in text** | The slot's name in rules and in the KRC text: letters, digits and `_`, no spaces. |
| **Comment** | A comment at the end of the declaration line in the text. |
| **Used in rules** | The rules that press or read the slot. "Remove slot" is available only when the slot is unused. |

For KeyRota to press a slot, its spell must be on an action bar in the game with a key bound: KeyRota takes the key from your bars. A macro with `#showtooltip Name` works too. A slot without a key is never pressed.

#### How to press: flags

![The How to press switches of the Vanish slot: Off the GCD (O) and In the queue window (Q) are on](https://keyrota.app/documentation/img/en/editor-slot-flags.png)

| Switch | What it does | When to use it |
|---|---|---|
| no flags | The slot is pressed like a normal spell: at the end of the global cooldown, in the queue window. | Almost every spell on the GCD. |
| **Manual only (M)** | KeyRota never presses this slot itself and skips its rules. Pressing its key yourself pauses the rotation. | Abilities you want to press only yourself. |
| **Channel (C)** | While this spell channels, KeyRota presses nothing and doesn't interrupt it. | Channeled spells such as Killing Spree. |
| **Off the GCD (O)** | Doesn't wait for the global cooldown: pressed right away, even mid-GCD. | Off-GCD spells such as Keep It Rolling. |
| **In the queue window (Q)** | Only together with Off the GCD: press at the very end of the GCD, in the spell queue window. | Macros whose second line is on the GCD (Adrenaline Rush + Killing Spree), and buttons that need the previous hit's result (Vanish after Kingsbane). |

Every flag combination and its JSON is in [Slot flags](https://keyrota.app/documentation/en/rotation-json#flags).

### Auras

![The Auras page: aura 1 "Garrote on target [703]", ID 703, name in text Garrote_DoT, used in rules 3, 4, 5, 7, 8, 10](https://keyrota.app/documentation/img/en/editor-auras.png)

Auras are your buffs and your debuffs on the target that the rules read. Rotation aura N is aura N in the Better Assistant list.

| Control | What it does |
|---|---|
| **Name** | The aura's name; shown in rules. Handy with the ID in brackets: "Garrote on target [703]". |
| **Spell ID** | The aura's number on Wowhead. Better Assistant looks for it among your buffs and your debuffs on the target. |
| **Name in text**, **Comment** | As for slots. |
| **Used in rules** | Where the aura is read. "Remove aura" works only for an unused aura. |

> [!NOTE]
> An aura's ID and the ID of the spell that applies it can differ. The Vanish spell is `1856`, the buff it gives is `11327`; Stealth is `1784`, Stealth with the Subterfuge talent is `115191`. Check the ID of the buff or debuff itself on Wowhead.

Only **your** auras are visible: buffs you put on yourself and debuffs you put on the target. Buffs from other players and other players' debuffs on the target aren't seen by the rules.

### Rules

![The Rules page: 15 rules on the left, on the right rule 4 Deathmark with its condition group](https://keyrota.app/documentation/img/en/editor-rules.png)

Rules are checked from top to bottom: KeyRota presses the first one that is met and whose spell can be cast; if it can't (cooldown, resource, range), it checks the next one.

**The toolbar above the list:**

| Button | What it does |
|---|---|
| **+** | A new rule below the selected one. It presses the first slot and has no conditions yet. |
| **Duplicate** | A copy of the rule below it. |
| **Bin** | Delete the rule. |
| **Up**, **Down** | Move up (checked earlier) or down (checked later). |

Each rule in the list shows its number (the priority), the slot, the name and the number of groups; a rule with an error has a red mark.

**The selected rule:**

| Control | What it does |
|---|---|
| **Press** | Which slot to press when the rule is met. |
| **Name** | Shown in the log when the rule fires. |
| **Description** | Notes above the rule in the KRC text. |
| **When to press** | Condition groups: the rule is met when at least one group is met (OR); a group, when all its conditions are met (AND). |

![The condition groups of the Rupture rule: group 1 and group 2 separated by OR](https://keyrota.app/documentation/img/en/editor-rule-groups.png)

Each group has "Group N", a "Group comment" box and Duplicate group and Delete group buttons. A condition row has **what to check**, the **property**, the **comparison** and the **number**; the cross removes the condition. "+ Condition" adds a condition to the group, "+ Group (OR)" a new group. A group holds up to 16 conditions and a rule up to 16 groups: at the limit the buttons are disabled. A rule without groups says "No conditions: the rule fires whenever the spell can be cast".

| What to check | Properties | In KRC text |
|---|---|---|
| **Auras** (each aura of the rotation) | active, absent, remaining %, stacks | `Garrote_DoT.up`, `.down`, `.duration <= 30`, `.stacks >= 3` |
| **Slots** (each slot) | ready, on cooldown, cooldown %, charges, glowing, not glowing | `Kingsbane.ready`, `.oncd`, `.cd <= 3`, `.charges >= 2`, `.glow`, `.noglow` |
| **Resources** | player and target health, the specialization's resources, % | `PlayerHP <= 40`, `Energy >= 70` |
| **Combat**: Enemies nearby | a number | `enemies >= 3` |
| **Combat**: Distance to target | yards | `target.distance <= 8` |
| **Quick settings panel** | on, off | `aoe == 1`, `st == 1`, `cd == 1`, `defensives == 1`, `ground_spells == 1`, `movement == 1` |

Comparisons: `<`, `≤`, `=`, `≥`, `>`; the number is 0 to 100. A new condition starts as "active" for the first aura; when you change what to check, the property and the number reset to defaults (`≥ 50` for a resource, `≥ 2` for enemies, `≤ 8` for the distance). Every condition in detail: [Conditions](https://keyrota.app/documentation/en/conditions).

### Errors and the status bar

![An error in rule 9: group 1 without conditions is outlined in red; the status bar says "Rule 9, group 1: the group has no conditions" with Show](https://keyrota.app/documentation/img/en/editor-problem.png)

- **Green**: "The rotation is valid: …" — no errors, it can be saved.
- **Red** names the place: "Rule 9, group 1: the group has no conditions". Show opens that place. The rule is marked in the list, the group is outlined in red.
- A rotation with an error isn't saved: "A rotation with an error can't be saved: fix what's marked below".
- While the rotation runs you can't save: "Stop the rotation to save".

### Text and JSON

![The Text and JSON page in KRC text mode: the start of the rotation text with the description and the buff1–buff7 declarations](https://keyrota.app/documentation/img/en/editor-text.png)

![The Text and JSON page in JSON mode: the start of the rotation JSON with format, version, title, specID, ignoreMainIcon, enemies and notes](https://keyrota.app/documentation/img/en/editor-json.png)

| Control | What it does |
|---|---|
| **KRC text / JSON** | Switches the view. |
| **Copy** | Copy the whole text or JSON. |
| **Paste** | Replace the rotation with the clipboard: KRC text, rotation JSON or a KeyRota profile. |
| **Open…** | Load a rotation from a `.txt` or `.json` file. |
| **Save…** | Save the current view to a file: the text as `.txt`, the JSON as `.json`. |

An edit in the box applies at once if it has no errors; an error shows under the box, and the other pages keep the last valid version.

**What the text keeps and what only the JSON keeps.** KRC text has no spell IDs, slot flags, With a target / In combat, specialization, Ignore main icon or enemy counter settings. When you edit the text, these stay with the slots and auras of the same numbers. The JSON keeps everything. When you paste or open a new version of the rotation, slots and auras with the same numbers keep their IDs, visibility and flags if the new version has none.

### Saving and closing

- Save (on Windows also Ctrl+S) writes the rotation into the profile; the button briefly shows Saved. A new rotation becomes a profile and is selected in the list.
- Closing with unsaved changes asks "Save changes?": Save, Don't save or Cancel. So does closing KeyRota.
- Export in the main window saves the profile with its rotation as JSON; Import accepts both a KeyRota profile and a bare rotation JSON.

### Example: a rotation from scratch

Let's build a small Assassination rotation: two DoTs (Garrote and Rupture), a builder (Mutilate), a spender (Envenom), a defensive (Crimson Vial at 40% health or lower) and a cooldown on the Burst button (Deathmark).

**1. New profile.** Rotation — New — In the builder….

**2. General.** Profile name: "Assassination: example". Specialization: Assassination (the resources become `PlayerHP`, `TargetHP`, `Energy`, `Combo_Points`). Rotation name: "Assassination: example v1". Ignore main icon: on.

**3. Slots.** For each row of the table: select the place on the left, click "Add slot", fill in Name, Spell ID and Name in text; for Crimson Vial choose In combat. No flags are needed.

| Slot | Name | ID | Name in text | Better Assistant shows it |
|---|---|---|---|---|
| 1 | Garrote | 703 | `Garrote` | With a target |
| 2 | Rupture | 1943 | `Rupture` | With a target |
| 3 | Envenom | 32645 | `Envenom` | With a target |
| 4 | Mutilate | 1329 | `Mutilate` | With a target |
| 5 | Deathmark | 360194 | `Deathmark` | With a target |
| 6 | Crimson Vial | 185311 | `Crimson_Vial` | In combat |

**4. Auras.** The same on the Auras page:

| Aura | Name | ID | Name in text |
|---|---|---|---|
| 1 | Garrote on target [703] | 703 | `Garrote_DoT` |
| 2 | Rupture on target [1943] | 1943 | `Rupture_DoT` |
| 3 | Envenom [32645] | 32645 | `Envenom_Buff` |
| 4 | Deathmark on target [360194] | 360194 | `Deathmark_DoT` |

**5. Rules.** On the Rules page click "+", choose the slot in Press and type the Name. "+ Group (OR)" adds a group with a default condition: change what to check, the property, the comparison and the number. "+ Condition" adds the next condition to the same group. The rules, in this order:

| # | Press | Groups (OR), conditions in a group (AND) |
|---|---|---|
| 1 | Crimson Vial | Healing / defense on, Player health ≤ 40 |
| 2 | Deathmark | Burst on, Garrote on target active, Rupture on target active, Envenom active |
| 3 | Garrote | (1) Garrote on target absent; (2) Garrote on target active, remaining ≤ 30% |
| 4 | Rupture | (1) Combo Points ≥ 70, Rupture on target absent; (2) Combo Points ≥ 70, Rupture on target active, remaining ≤ 30% |
| 5 | Envenom | (1) Combo Points ≥ 70, Envenom absent; (2) Combo Points ≥ 70, Envenom active, remaining ≤ 20%; (3) Combo Points ≥ 70, Energy ≥ 70; (4) Combo Points ≥ 70, Deathmark on target active |
| 6 | Mutilate | Combo Points < 70 |

Why:
- **The defensive first**: at low health Crimson Vial matters more than damage. It fires only when Healing / defense is on in the quick settings panel.
- **Deathmark on Burst**: `cd == 1` is the Burst button (F3 on Windows, ⌃⇧B on macOS), not the spell's cooldown. Turn Burst off and Deathmark waits.
- **DoTs with the pandemic window**: an absent aura has 0 left, so the refresh takes two groups: "no DoT" and "DoT up with ≤ 30% left".
- **5 combo points = 70**: out of 7 combo points, 5 is 71%; the threshold `70` works with any rounding. The table is in [Resources](https://keyrota.app/documentation/en/conditions#resources).
- **Mutilate last** and only below 5 combo points, so that no energy is wasted while Envenom waits for its conditions.

**6. Check.** The status bar should say "The rotation is valid: 6 rules, 6 slots, 4 auras". Open Text and JSON to see the same as below.

**7. The Better Assistant profile.** General — "Build from slots and auras" — "Copy"; in the game `/ba` — Profile — paste — Apply.

**8. Keys.** Put the six spells on your action bars and bind keys to them.

**9. Run it.** Save, close the window, choose the profile "Assassination: example" and start the rotation (F1 on Windows, ⌃⇧Space on macOS). On the quick settings panel turn on Healing / defense if you want Crimson Vial; the Burst button turns Deathmark on and off.

*KRC text:*

```text
rotation "Assassination: example v1"

# Example from the KeyRota documentation: two bleeds, a builder, a spender with energy pooling,
# a defensive at low health and Deathmark on the Burst button.
# Combo points in percent of 7: 5 points = 71%, threshold 70 = 5+.

buff1	Garrote_DoT = "Garrote on target [703]"
buff2	Rupture_DoT = "Rupture on target [1943]"
buff3	Envenom_Buff = "Envenom [32645]"
buff4	Deathmark_DoT = "Deathmark on target [360194]"
slot1	Garrote = "Garrote"
slot2	Rupture = "Rupture"
slot3	Envenom = "Envenom"
slot4	Mutilate = "Mutilate"
slot5	Deathmark = "Deathmark"
slot6	Crimson_Vial = "Crimson Vial"
resource1	PlayerHP
resource2	TargetHP
resource3	Energy
resource4	Combo_Points

# --- Defensive: only with Healing / defense on ---
use Crimson_Vial when  # Crimson Vial
	defensives == 1 and PlayerHP <= 40  # health 40% or lower

# --- Cooldown: only with Burst on ---
use Deathmark when  # Deathmark
	cd == 1 and Garrote_DoT.up and Rupture_DoT.up and Envenom_Buff.up  # Burst on, bleeds and Envenom up

# --- Bleeds ---
use Garrote when  # Garrote
	Garrote_DoT.down  # no bleed
	or
	Garrote_DoT.up and Garrote_DoT.duration <= 30  # pandemic window: 30% or less left

use Rupture when  # Rupture
	Combo_Points >= 70 and Rupture_DoT.down  # 5+ combo points, no bleed
	or
	Combo_Points >= 70 and Rupture_DoT.up and Rupture_DoT.duration <= 30  # 5+ combo points, pandemic window

# --- Spender: 5+ combo points, pooling energy ---
use Envenom when  # Envenom
	Combo_Points >= 70 and Envenom_Buff.down  # no buff
	or
	Combo_Points >= 70 and Envenom_Buff.up and Envenom_Buff.duration <= 20  # buff running out
	or
	Combo_Points >= 70 and Energy >= 70  # don't cap energy
	or
	Combo_Points >= 70 and Deathmark_DoT.up  # Deathmark on the target

# --- Builder: below 5 combo points ---
use Mutilate when  # Mutilate
	Combo_Points < 70  # below 5 combo points
```

*JSON:*

```json
{
  "format": "keyrota-rotation",
  "version": 1,
  "title": "Assassination: example v1",
  "specID": 259,
  "ignoreMainIcon": true,
  "enemies": {"range": 8, "combatOnly": true},
  "notes": [
    "Example from the KeyRota documentation: two bleeds, a builder, a spender with energy pooling,",
    "a defensive at low health and Deathmark on the Burst button.",
    "Combo points in percent of 7: 5 points = 71%, threshold 70 = 5+."
  ],
  "resources": ["PlayerHP", "TargetHP", "Energy", "Combo_Points"],
  "auras": [
    {"n": 1, "alias": "Garrote_DoT", "label": "Garrote on target [703]", "spell": 703},
    {"n": 2, "alias": "Rupture_DoT", "label": "Rupture on target [1943]", "spell": 1943},
    {"n": 3, "alias": "Envenom_Buff", "label": "Envenom [32645]", "spell": 32645},
    {"n": 4, "alias": "Deathmark_DoT", "label": "Deathmark on target [360194]", "spell": 360194}
  ],
  "slots": [
    {"n": 1, "alias": "Garrote", "label": "Garrote", "spell": 703, "show": "target"},
    {"n": 2, "alias": "Rupture", "label": "Rupture", "spell": 1943, "show": "target"},
    {"n": 3, "alias": "Envenom", "label": "Envenom", "spell": 32645, "show": "target"},
    {"n": 4, "alias": "Mutilate", "label": "Mutilate", "spell": 1329, "show": "target"},
    {"n": 5, "alias": "Deathmark", "label": "Deathmark", "spell": 360194, "show": "target"},
    {"n": 6, "alias": "Crimson_Vial", "label": "Crimson Vial", "spell": 185311, "show": "combat"}
  ],
  "rules": [
    {
      "use": "Crimson_Vial",
      "name": "Crimson Vial",
      "note": ["--- Defensive: only with Healing / defense on ---"],
      "when": [{"all": ["defensives == 1", "PlayerHP <= 40"], "comment": "health 40% or lower"}]
    },
    {
      "use": "Deathmark",
      "name": "Deathmark",
      "note": ["--- Cooldown: only with Burst on ---"],
      "when": [
        {
          "all": ["cd == 1", "Garrote_DoT.up", "Rupture_DoT.up", "Envenom_Buff.up"],
          "comment": "Burst on, bleeds and Envenom up"
        }
      ]
    },
    {
      "use": "Garrote",
      "name": "Garrote",
      "note": ["--- Bleeds ---"],
      "when": [
        {"all": ["Garrote_DoT.down"], "comment": "no bleed"},
        {
          "all": ["Garrote_DoT.up", "Garrote_DoT.duration <= 30"],
          "comment": "pandemic window: 30% or less left"
        }
      ]
    },
    {
      "use": "Rupture",
      "name": "Rupture",
      "when": [
        {"all": ["Combo_Points >= 70", "Rupture_DoT.down"], "comment": "5+ combo points, no bleed"},
        {
          "all": ["Combo_Points >= 70", "Rupture_DoT.up", "Rupture_DoT.duration <= 30"],
          "comment": "5+ combo points, pandemic window"
        }
      ]
    },
    {
      "use": "Envenom",
      "name": "Envenom",
      "note": ["--- Spender: 5+ combo points, pooling energy ---"],
      "when": [
        {"all": ["Combo_Points >= 70", "Envenom_Buff.down"], "comment": "no buff"},
        {
          "all": ["Combo_Points >= 70", "Envenom_Buff.up", "Envenom_Buff.duration <= 20"],
          "comment": "buff running out"
        },
        {"all": ["Combo_Points >= 70", "Energy >= 70"], "comment": "don't cap energy"},
        {"all": ["Combo_Points >= 70", "Deathmark_DoT.up"], "comment": "Deathmark on the target"}
      ]
    },
    {
      "use": "Mutilate",
      "name": "Mutilate",
      "note": ["--- Builder: below 5 combo points ---"],
      "when": [{"all": ["Combo_Points < 70"], "comment": "below 5 combo points"}]
    }
  ]
}
```


Download this example: [JSON](https://keyrota.app/documentation/examples/starter-assassination.en.json), [KRC text](https://keyrota.app/documentation/examples/starter-assassination.en.txt). Open it with Open… on the Text and JSON page or with New — From file….

---

<!-- https://keyrota.app/documentation/en/rotation-json -->

## Rotation JSON

The JSON is the complete description of an advanced rotation: everything the KRC text has, plus spell IDs, slot flags and profile settings. The builder shows it on the Text and JSON page, and KeyRota accepts it as a file (New — From file…, Import). The format is called `keyrota-rotation`, version 1. There is a [JSON Schema](#schema) to check it.

### Example file

The shortest useful rotation: Rupture at 5+ combo points when it isn't on the target, otherwise Mutilate.

*minimal.json:*

```json
{
  "format": "keyrota-rotation",
  "version": 1,
  "title": "Assassination: minimum",
  "specID": 259,
  "resources": ["PlayerHP", "TargetHP", "Energy", "Combo_Points"],
  "auras": [{"n": 1, "alias": "Rupture_DoT", "label": "Rupture on target [1943]", "spell": 1943}],
  "slots": [
    {"n": 2, "alias": "Rupture", "label": "Rupture", "spell": 1943},
    {"n": 4, "alias": "Mutilate", "label": "Mutilate", "spell": 1329}
  ],
  "rules": [
    {
      "use": "Rupture",
      "name": "Rupture",
      "when": [{"all": ["Combo_Points >= 70", "Rupture_DoT.down"], "comment": "5+ combo points, no bleed"}]
    },
    {"use": "Mutilate", "name": "Mutilate"}
  ]
}
```


Everything the file leaves out takes its default: slots show With a target and have no flags, the enemy counter is 8 yards, enemies in combat only. A rule without `when` always holds.

### Top-level fields

| Field | Type | Required | Default | What it is |
|---|---|---|---|---|
| `format` | string | yes | — | Always `"keyrota-rotation"`. |
| `version` | integer | yes | — | Always `1`. A newer one is refused: "update KeyRota". |
| `title` | string | no | `""` | Rotation name: the first text line, the name in the log. No double quotes. |
| `specID` | integer 0–100000 | no | `0` | WoW specialization, `0` = any. Needed for the Better Assistant profile. |
| `ignoreMainIcon` | true / false | no | `true` | On: press only by the rules. Off: when no rule matches, press the game's suggestion. |
| `enemies` | object | no | `{"range": 8, "combatOnly": true}` | The enemy counter for the `enemies` condition. |
| `notes` | string or list of strings | no | `[]` | Description: `#` lines at the top of the text. |
| `resources` | list of strings and `null` | no | `[]` | Names of `resource1…N` in order. |
| `auras` | list | no | `[]` | Auras `buff1…12`. |
| `slots` | list | no | `[]` | Slots `slot1…12`. |
| `rules` | list | no | `[]` | Rules in priority order. |
| `footer` | string or list of strings | no | `[]` | Comments after the last rule. |

Any other field is an error, "unexpected field": typos are not swallowed. So don't add `"$schema"` to the file; attach the schema in your editor or validator.

Almost everywhere `null` may stand for a value; it means the same as leaving the field out.

### format and version

`"format": "keyrota-rotation"` and `"version": 1` are required. If `version` is above 1, KeyRota refuses: "version 2 is newer than this KeyRota understands: update it". Without `version`: "version 1 required".

### title

A string without double quotes `"`. Line breaks become spaces. In KRC text it is `rotation "…"`. A good place for the version: `"Assassination v1.3"`.

### specID

The game's specialization ID. It decides the rotation's resources (when created in the builder) and the class and specialization in the Better Assistant profile. `0` means any specialization: the rotation works, but the Better Assistant profile won't build ("Choose a specialization: the Better Assistant profile needs it").

| Class | Specialization | specID | Specialization resources (`resource3`…) |
|---|---|---|---|
| Warrior | Arms | 71 | `Rage` |
| Warrior | Fury | 72 | `Rage` |
| Warrior | Protection | 73 | `Rage` |
| Paladin | Holy | 65 | `Mana`, `Holy_Power` |
| Paladin | Protection | 66 | `Mana`, `Holy_Power` |
| Paladin | Retribution | 70 | `Mana`, `Holy_Power` |
| Hunter | Beast Mastery | 253 | `Focus` |
| Hunter | Marksmanship | 254 | `Focus` |
| Hunter | Survival | 255 | `Focus` |
| Rogue | Assassination | 259 | `Energy`, `Combo_Points` |
| Rogue | Outlaw | 260 | `Energy`, `Combo_Points` |
| Rogue | Subtlety | 261 | `Energy`, `Combo_Points` |
| Priest | Discipline | 256 | `Mana`, `Insanity` |
| Priest | Holy | 257 | `Mana`, `Insanity` |
| Priest | Shadow | 258 | `Mana`, `Insanity` |
| Death Knight | Blood | 250 | `Runes`, `Runic_Power` |
| Death Knight | Frost | 251 | `Runes`, `Runic_Power` |
| Death Knight | Unholy | 252 | `Runes`, `Runic_Power` |
| Shaman | Elemental | 262 | `Mana`, `Maelstrom` |
| Shaman | Enhancement | 263 | `Mana`, `Maelstrom` |
| Shaman | Restoration | 264 | `Mana`, `Maelstrom` |
| Mage | Arcane | 62 | `Mana`, `Arcane_Charges` |
| Mage | Fire | 63 | `Mana`, `Arcane_Charges` |
| Mage | Frost | 64 | `Mana`, `Arcane_Charges` |
| Warlock | Affliction | 265 | `Mana`, `Soul_Shards` |
| Warlock | Demonology | 266 | `Mana`, `Soul_Shards` |
| Warlock | Destruction | 267 | `Mana`, `Soul_Shards` |
| Monk | Brewmaster | 268 | `Energy`, `Stagger` |
| Monk | Mistweaver | 270 | `Mana` |
| Monk | Windwalker | 269 | `Energy`, `Chi` |
| Druid | Balance | 102 | `Mana`, `Astral_Power` |
| Druid | Feral | 103 | `Energy`, `Combo_Points` |
| Druid | Guardian | 104 | `Rage` |
| Druid | Restoration | 105 | `Mana`, `Astral_Power` |
| Demon Hunter | Havoc | 577 | `Fury` |
| Demon Hunter | Vengeance | 581 | `Fury` |
| Demon Hunter | Devourer | 1480 | `Fury` |
| Evoker | Devastation | 1467 | `Mana`, `Essence` |
| Evoker | Preservation | 1468 | `Mana`, `Essence` |
| Evoker | Augmentation | 1473 | `Mana`, `Essence` |

### ignoreMainIcon

`true` (default): KeyRota presses only by the rules; when none matches, it waits. `false`: it then presses the game's suggestion, like the Blizzard profile. The suggestion is the last resort after all the rules; it never overtakes a rule.

### enemies

```json
"enemies": {"range": 8, "combatOnly": true}
```


| Field | Type | Default | What it is |
|---|---|---|---|
| `range` | integer 1–100 | `8` | The enemy counter radius in yards. |
| `combatOnly` | true / false | `true` | Count only enemies already in combat. |

The `enemies` condition reads this counter (see [Enemies nearby](https://keyrota.app/documentation/en/conditions#enemies)). In the builder these are "Enemy counter radius, yards" and "Only enemies in combat" on the General page. The values also go into the Better Assistant profile.

### notes and footer

The rotation's description (`notes`) and the comments after the last rule (`footer`). A list of strings, or one string with `\n`. An empty string `""` is a blank line in the text. Blank lines at the edges are dropped.

```json
"notes": ["Based on a guide and top logs, patch 12.1", "", "v1.1: Envenom earlier on 4+ targets"]
```


In KRC text these are `# …` lines before the declarations. They don't affect how the rotation works.

### resources

Resource names in order: the first item is `resource1`, the second `resource2`, and so on. **The number decides what the resource is**; the name is only how you write it in conditions:

- `resource1` is the player's health, `resource2` the target's health;
- `resource3` and on are the specialization's resources in the order of the [specID table](#spec-id): for a rogue `resource3` is energy, `resource4` combo points.

```json
"resources": ["PlayerHP", "TargetHP", "Energy", "Combo_Points"]
```


Use the standard names from the table: the builder shows them in plain words ("Player health", "Energy"), and other people and AI agents understand the conditions right away. `null` skips a number: `["PlayerHP", null, "Energy"]` declares only `resource1` and `resource3`. An undeclared resource can't be read: "resource2 is not declared".

All resources are **percentages** of the maximum, 0–100. How to turn combo points, energy and runes into percentages: [Resources](https://keyrota.app/documentation/en/conditions#resources) in Conditions.

### auras

Auras are your buffs and your debuffs on the target. Rotation aura `n` is aura `n` in the Better Assistant list.

| Field | Type | Required | Default | What it is |
|---|---|---|---|---|
| `n` | integer 1–12 | yes | — | Aura number (`buffN`). Each number once. |
| `alias` | string | no | from `label` | The name in conditions. See [Names](#aliases). |
| `label` | string | no | `""` | Display name. No double quotes `"`. Handy with the ID in brackets: `"Garrote on target [703]"`. |
| `spell` | integer 0–100000000 | no | `0` | The aura's Wowhead ID. `0` = not set: the aura won't be in the Better Assistant profile and will be unknown. |
| `note` | string | no | `""` | A comment at the end of the declaration line. |

```json
"auras": [
  {"n": 1, "alias": "Garrote_DoT", "label": "Garrote on target [703]", "spell": 703},
  {"n": 2, "alias": "Rupture_DoT", "label": "Rupture on target [1943]", "spell": 1943}
]
```


The order in the list doesn't matter, `n` does. Numbers may have gaps, but for the Better Assistant profile the auras with IDs must go in a row from the first.

### slots

Slots are the rotation's buttons. Rotation slot `n` is slot `n` in Better Assistant.

| Field | Type | Required | Default | What it is |
|---|---|---|---|---|
| `n` | integer 1–12 | yes | — | Slot number (`slotN`). |
| `alias` | string | no | from `label` | The name in rules and conditions. |
| `label` | string | no | `""` | The spell's name. No double quotes `"`. |
| `spell` | integer 0–100000000 | no | `0` | The Wowhead spell ID. Better Assistant uses it to put the spell into the slot. |
| `note` | string | no | `""` | A comment at the end of the declaration line. |
| `show` | `"target"` / `"combat"` | no | `"target"` | When Better Assistant shows the slot. |
| `manual` | true / false | no | `false` | Flag **M**. |
| `channel` | true / false | no | `false` | Flag **C**. |
| `offGcd` | true / false | no | `false` | Flag **O**. |
| `queue` | true / false | no | `false` | Flag **Q**, only with `offGcd`. |

#### show

- `"target"`: the slot is shown when you have a target. True for almost every attack.
- `"combat"`: the slot is shown in combat even without a target. For healing and defensives: Crimson Vial must work when there's no target.

A slot is ready only when Better Assistant shows it. A `"target"` slot without a target isn't ready, and its rules are skipped.

### Slot flags

Flags say **how** to press a slot once its rule is chosen. In the builder they are the How to press switches.

| Flags | In JSON | How it's pressed | Example |
|---|---|---|---|
| none | all `false` | In the queue window at the end of the GCD: the spell goes off as soon as the GCD ends. | Mutilate, Envenom |
| **M** | `"manual": true` | Never. The slot's rules are skipped. Pressing its key yourself pauses the rotation. | An ability you press yourself |
| **C** | `"channel": true` | Like a normal spell; while the channel goes, nothing is pressed. After the press the slot isn't pressed again until the channel starts. | Killing Spree (51690) |
| **O** | `"offGcd": true` | At once, even mid-GCD. | Keep It Rolling (381989) |
| **O + Q** | `"offGcd": true, "queue": true` | Off the GCD, but only in the queue window at the end of the GCD. | The Adrenaline Rush + Killing Spree macro (13750); Vanish (1856) after Kingsbane |
| **C + O** | `"channel": true, "offGcd": true` | A channel that starts off the GCD: at once, then nothing until the channel ends. | Rare |
| **C + O + Q** | all three | The same, in the queue window. | Rare |
| **Q** without O | `"queue": true` | Error: "queue only together with offGcd". | — |
| **M** + others | `"manual": true, …` | M wins: the slot isn't pressed, the other flags don't matter. | — |

Why **O + Q** and not just **O**: a button with O alone is pressed at once, mid-GCD, before the previous hit has landed. Vanish after Kingsbane with O alone would be pressed before Kingsbane's combo points arrive, and the Vanish rule (`Combo_Points < 65`) would check the old value. With Q the button waits for the end of the GCD.

Strict priority: an off-GCD rule lower in the list **doesn't overtake** a higher rule that is waiting for the GCD.

```json
"slots": [
  {"n": 1, "alias": "Adrenaline_Rush", "label": "Adrenaline Rush (macro with Killing Spree)", "spell": 13750, "offGcd": true, "queue": true},
  {"n": 4, "alias": "Keep_It_Rolling", "label": "Keep It Rolling", "spell": 381989, "offGcd": true},
  {"n": 8, "alias": "Killing_Spree", "label": "Killing Spree", "spell": 51690, "channel": true},
  {"n": 11, "alias": "Crimson_Vial", "label": "Crimson Vial", "spell": 185311, "show": "combat"}
]
```


### rules

Rules in priority order: the first in the list is checked first.

| Field | Type | Required | Default | What it is |
|---|---|---|---|---|
| `use` | string | yes | — | The slot to press: its `alias` or `slotN`. |
| `name` | string | no | `""` | The action's name, shown in the log. In the text, the comment on the `use` line. |
| `note` | string or list of strings | no | `[]` | Notes above the rule (`#` lines before `use`). |
| `when` | list of groups | no | `[]` | OR groups. Empty or missing: a rule without conditions, pressed whenever the spell can be used. |

#### when: groups and conditions

| Field | Type | Required | What it is |
|---|---|---|---|
| `all` | non-empty list of strings | yes | The group's conditions, all must hold (AND). |
| `comment` | string | no | The group's comment (in the text, at the end of its line). |

```json
{
  "use": "Garrote",
  "name": "Garrote",
  "note": ["Refresh in the pandemic window"],
  "when": [
    {"all": ["Garrote_DoT.down"], "comment": "no DoT"},
    {"all": ["Garrote_DoT.up", "Garrote_DoT.duration <= 30"], "comment": "30% or less left"}
  ]
}
```


One slot may appear in several rules: for example, Garrote from Stealth high in the list and ordinary Garrote upkeep lower down.

#### Conditions in JSON

Each condition is a string in KRC syntax: `Garrote_DoT.down`, `Rupture_DoT.duration <= 25`, `Kingsbane.cd <= 3`, `Combo_Points >= 70`, `enemies >= 2`, `target.distance <= 8`, `cd == 1`. The full list is in [Conditions](https://keyrota.app/documentation/en/conditions).

- Refer by name (`Garrote_DoT`) or by number: `buff1`, `slot3`, `resource4`.
- ` and ` inside a string splits it into two conditions: `"Combo_Points >= 70 and Energy >= 70"` is two conditions, and both count toward the limit of 16.
- No `#` and no line breaks in a string.
- KeyRota's own engine reads the conditions, so the JSON and the text always mean the same.

### Names

A name (`alias`) is how auras, slots and resources are called in conditions and in `use`.

- The first character is a letter (any alphabet) or `_`; then letters, digits `0–9` and `_`. Cyrillic is fine; spaces, hyphens and dots are not: `Fan_of_Knives`, not `Fan-of-Knives`.
- A name is unique among all auras, slots and resources.
- Reserved: `use`, `when`, `and`, `or`, `rotation`, `gcd`, `enemies`, `target`, `aoe`, `st`, `cd`, `defensives`, `ground_spells`, `movement`, and names like `buffN`, `slotN`, `resourceN`.
- Keywords are lowercase only: `use`, `or`, `.up`. `Use`, `OR` and `.UP` are errors.

**A name from the label.** Without `alias`, KeyRota makes one from `label`: brackets and what's inside them are dropped, letters and digits stay, everything else becomes `_`. for example, `"Garrote on target [703]"` becomes `Garrote_on_target`. An empty or unusable name becomes `Aura_N` or `Slot_N`; a taken one gets `_2`, `_3`. If a rule refers to `buffN` or `slotN` missing from the lists, they are declared automatically as `Aura_N` and `Slot_N`.

### Required fields and defaults

| Where | Required | Defaults |
|---|---|---|
| File | `format`, `version` | `title` `""`, `specID` `0`, `ignoreMainIcon` `true`, `enemies` `{"range": 8, "combatOnly": true}`, empty lists |
| Aura | `n` | `alias` from `label`, `label` `""`, `spell` `0` |
| Slot | `n` | `show` `"target"`, all flags `false`, `spell` `0` |
| Rule | `use` | `name` `""`, `note` `[]`, `when` `[]` (no conditions) |
| Group | `all` (non-empty) | `comment` `""` |

### Limits

| What | Limit |
|---|---|
| Auras | 12 (`n` 1–12) |
| Slots | 12 (`n` 1–12) |
| OR groups in a rule | 16 |
| AND conditions in a group | 16 |
| Rules | unlimited |
| Number in a condition | integer 0–100 |
| `enemies.range` | 1–100 |
| `spell` | 0–100000000 |
| `specID` | 0–100000 |
| Resources | up to 99 |

Exactly 16 groups of 16 conditions is fine; 17 is an error.

### Errors

A rotation with an error doesn't load at all: there is no partial execution. An error always names the place. In JSON it's a path: `rules[3].when[1]: unknown name Foo` (counting from zero). In the builder, "Rule 4, group 2: unknown name Foo" (counting from one). In KRC text, a line number.

**JSON structure:**

| Message | Cause |
|---|---|
| `unexpected field “offgcd”` | A misspelt or unknown field (case matters: `offGcd`). |
| `missing field “format”` | A required field is missing. |
| `format: expected “keyrota-rotation”` | Another value of `format`. |
| `version: version 1 required` | No `version`, or below 1. |
| `slots[0]: missing number n (1–12)` | A slot or aura without `n`. |
| `slots[2].n: number outside 1..12` | `n` out of range. |
| `slots[3]: number 4 is already taken` | Two slots (or two auras) with the same `n`. |
| `slots[1].show: expected “target” or “combat”` | Another value of `show`. |
| `slots[0]: queue only together with offGcd` | `queue: true` without `offGcd: true`. |
| `rules[2].use: no slot “Garotte”` | `use` refers to a slot that doesn't exist. |
| `rules[0].when[1].all: a non-empty list of conditions is required` | An empty group or no `all`. |
| `rules[0].when[0].all[2]: condition is empty or contains # or a line break` | An empty condition string, or `#` in it. |
| `….spell: expected an integer` | Wrong type: a string instead of a number, `true` instead of a number and so on. |

**Conditions and declarations** (the same as in KRC text):

| Code | Example message | Cause |
|---|---|---|
| `unknown_ref` | `unknown name Foo` | No aura, slot or resource with that name. |
| `unknown_ref` | `resource5 is not declared` | The resource isn't declared in `resources`. |
| `wrong_kind` | `Garrote is a slot, expected a buff` | An aura property on a slot (`Garrote.up`) or the other way round. |
| `bad_condition` | `can't parse condition "Energy => 50"` | A typo in the condition or the operator. |
| `bad_condition` | `"Garrote.distance <= 8": only target has distance` | Only the target has a distance. |
| `value_range` | `value 120 outside 0..100` | A number above 100. There are no negative or fractional numbers. |
| `toggle_value` | `"cd >= 1": a toggle only takes == 0 or == 1` | A toggle is compared only with `==` and 0 or 1. |
| `bad_alias` | `invalid name Fan-of-Knives` | Invalid characters in a name. |
| `reserved_alias` | `name cd is reserved` | A reserved name. |
| `duplicate_alias` | `name Garrote is already declared` | Two auras, slots or resources with one name. |
| `index_range` | `buff13: number outside 1..12` | A reference to a number above 12. |
| `too_many_groups` | `more than 16 OR groups` | More than 16 groups in a rule. |
| `too_many_conditions` | `17 conditions in a group (maximum 16)` | More than 16 conditions in a group. |

The builder also reports "the group has no conditions" (an empty group), "no slot selected" and "quotes aren't allowed in the name".

### KRC text

KRC text is the same rotation as lines. The Text and JSON page shows it, and you can keep it as a `.txt` file. Text and JSON convert into each other without losing meaning.

*KRC text:*

```text
rotation "Assassination: minimum"

buff1	Rupture_DoT = "Rupture on target [1943]"
slot2	Rupture = "Rupture"
slot4	Mutilate = "Mutilate"
resource1	PlayerHP
resource2	TargetHP
resource3	Energy
resource4	Combo_Points

use Rupture when  # Rupture
	Combo_Points >= 70 and Rupture_DoT.down  # 5+ combo points, no bleed

use Mutilate when  # Mutilate
```


#### Lines

- `#` outside double quotes starts a comment up to the end of the line. Inside a `"label"`, `#` is an ordinary character.
- Blank lines and comment-only lines are skipped: that's how descriptions and separators are written.
- Any other line is a declaration, `use`, `or` or a condition line. Otherwise it's a `stray_line` error: "line outside a rule: "…" (start prose with #)".
- Any spaces and tabs between parts. The canonical form uses a tab after `buffN`/`slotN` and at the start of condition lines.

#### Declarations

Only before the first `use`:

```text
rotation "Name"                    # optional, at most once
buff1	Garrote_DoT = "Garrote on target [703]"   # aura 1
slot1	Garrote = "Garrote"                         # slot 1
resource1	PlayerHP                               # resource 1
```


`buffN` and `slotN` are 1–12, `resourceN` 1–99. The quoted label may be empty: `slot5	Filler = ""`. A declaration after the first `use` is a `late_declaration` error.

#### Rules

```text
use Rupture when  # Rupture
	Combo_Points >= 70 and Rupture_DoT.down  # no DoT
	or
	Combo_Points >= 70 and Rupture_DoT.up and Rupture_DoT.duration <= 30  # pandemic
```


- `use Slot when` opens a rule; a comment on that line is the action's name.
- Each condition line is one group (conditions joined with `and`). Groups are separated by a line with the single word `or`.
- A rule without condition lines has no conditions.
- Two condition lines in a row without `or` is a `split_group` error; `or` at the start, at the end or twice in a row is `empty_group`.

#### Text and JSON: how they map

| In the text | In JSON |
|---|---|
| `rotation "…"` | `title` |
| `# …` lines before the first `use` | `notes` |
| A comment at the end of a `buffN` / `slotN` line | The aura's / slot's `note` |
| `# …` lines right before `use` (and between the rule's groups) | The rule's `note` |
| A comment on the `use` line | The rule's `name` |
| A comment at the end of a condition line | The group's `comment` |
| `# …` lines after the last rule | `footer` |
| — (not in the text) | `specID`, `ignoreMainIcon`, `enemies`, `spell`, `show`, flags |

Blank lines at the edges of comment blocks are dropped; inside they stay as `""`.

### Import and storage

- KeyRota keeps the rotation inside the profile. Export in the main window saves the KeyRota profile (JSON) with its rotation; Import accepts a KeyRota profile and a bare rotation JSON.
- New — From file… accepts `.txt` with KRC text, rotation JSON and a KeyRota profile; New — From clipboard, the same from the clipboard.
- In the builder: Text and JSON — Paste or Open… replace the rotation; slots and auras with the same numbers keep their IDs, With a target / In combat and flags if the new version has none.
- A text rotation (without JSON) takes the slots' IDs and visibility from the Better Assistant profile already stored in the KeyRota profile, and the auras' IDs also from `[703]` marks in their labels.

### The Better Assistant profile from JSON

"Build from slots and auras" in the builder makes the Better Assistant profile string from the rotation JSON:

- **12 slots**: slot N is `slots[n = N].spell`, shown by `show`. A slot without an ID stays empty.
- **Auras** in order from the first: `auras[n = 1].spell`, `auras[n = 2].spell`… An aura without an ID in the middle is an error.
- **Class and specialization** from `specID`.
- **Enemy counter** from `enemies.range` and `enemies.combatOnly`.
- If the KeyRota profile already holds a Better Assistant profile of the same specialization, its other settings and the matching slots and auras are kept.

In the game: `/ba` — Profile — paste — Apply.

### JSON Schema

The format's schema: [keyrota-rotation.schema.json](https://keyrota.app/documentation/keyrota-rotation.schema.json) (draft 2020-12). It checks fields, types, ranges, `queue` without `offGcd` and the shape of condition strings. It doesn't check that `n` and names are unique, that names in conditions exist, or the limit of 16 after splitting strings at ` and `: KeyRota does that on load.

To check a file:

```text
npx ajv-cli validate --spec=draft2020 -s keyrota-rotation.schema.json -d my-rotation.json
```


Don't put a `"$schema"` field into a rotation file: KeyRota takes it for an unexpected field.

---

<!-- https://keyrota.app/documentation/en/conditions -->

## Conditions

A condition is one check: "Garrote is on the target", "energy is at least 70%", "Burst is on". Conditions make up groups (AND), groups make up a rule (OR). In the builder you pick a condition from lists; in KRC text and JSON it is written as a string, which is what the examples show.

### Syntax

| Kind | Form | Example |
|---|---|---|
| Aura: present / absent | `Aura.up`, `Aura.down` | `Garrote_DoT.down` |
| Aura: time left, stacks | `Aura.duration op N`, `Aura.stacks op N` | `Rupture_DoT.duration <= 30` |
| Slot: ready / not ready | `Slot.ready`, `Slot.oncd` | `Kingsbane.ready` |
| Slot: cooldown, charges | `Slot.cd op N`, `Slot.charges op N` | `Deathmark.cd <= 3` |
| Slot: glow | `Slot.glow`, `Slot.noglow` | `Ambush.glow` |
| Resource | `Resource op N` | `Combo_Points >= 70` |
| Enemies nearby | `enemies op N` | `enemies >= 3` |
| Distance to target | `target.distance op N` | `target.distance <= 8` |
| Toggle | `aoe`, `st`, `cd`, `defensives`, `ground_spells`, `movement` `== 0` or `== 1` | `cd == 1` |

**Operators:** `<`, `<=`, `==`, `=` (the same as `==`), `>=`, `>`. There is no "not equal".

**The number** `N` is an integer from 0 to 100. No negative or fractional numbers and nothing above 100: `value 120 outside 0..100`.

**Spaces** around the operator are optional: `Energy>=70` and `Energy >= 70` are the same. No spaces inside a name or between a name and its property: `Garrote_DoT.up`, not `Garrote_DoT .up`.

**References** are by name (`Garrote_DoT`) or by number: `buff1`, `slot3`, `resource4`. Keywords are lowercase.

### Auras

An aura is your buff or your debuff on the target, declared in the aura list with its ID. Other players' buffs and debuffs aren't visible.

#### .up and .down

`Envenom_Buff.up`: the aura is present. `Envenom_Buff.down`: it is absent. If the aura is unknown (not read), both are false.

```text
use Garrote when  # Garrote: apply it when it's missing
	Garrote_DoT.down
```


#### .duration: time left in percent

`Rupture_DoT.duration <= 30`: no more than 30% of the aura's **full** duration is left. The full duration is the one this application got: Rupture at 5 combo points lasts 24 s, at 7 it lasts 32 s, so 30% is 7.2 s and 9.6 s.

> [!IMPORTANT]
> An absent aura has **0** left. So `Rupture_DoT.duration <= 30` also holds when there is no Rupture at all. To refresh only an aura that is up, write `Rupture_DoT.up and Rupture_DoT.duration <= 30`, and cover "the aura is missing" with a separate group with `Rupture_DoT.down`.

The pandemic window in WoW is 30% of the duration: refreshing within it loses nothing. Hence the usual threshold `<= 30`.

*KRC text:*

```text
use Rupture when  # Rupture
	Combo_Points >= 70 and Rupture_DoT.down  # no DoT
	or
	Combo_Points >= 70 and Rupture_DoT.up and Rupture_DoT.duration <= 30  # pandemic
```

*JSON:*

```json
{
  "use": "Rupture",
  "name": "Rupture",
  "when": [
    {"all": ["Combo_Points >= 70", "Rupture_DoT.down"], "comment": "no DoT"},
    {"all": ["Combo_Points >= 70", "Rupture_DoT.up", "Rupture_DoT.duration <= 30"], "comment": "pandemic"}
  ]
}
```


Seconds to percent: `percent = seconds / full duration × 100`. Garrote lasts 18 s: "14 s left" is `<= 78`, "5.4 s left" is `<= 30`.

**An aura without a duration** (Stealth and the like) always reads **1** left. Use only `.up` and `.down` for such auras.

#### .stacks: number of stacks

`Opportunity.stacks >= 6`: the aura has 6 stacks or more. An absent aura has 0 stacks.

> [!NOTE]
> The stack count starts at two: an aura with a single stack reads as **0**, like an aura without a counter. To check that an aura is present use `.up`; use `.stacks` for thresholds of 2 and above.

```text
use Pistol_Shot when  # Pistol Shot at 6 stacks of Opportunity
	Opportunity.stacks >= 6 and Combo_Points < 80
```


### Slots

Slot conditions read the state of **another** button: "press Envenom only while Deathmark isn't ready yet" and so on. The rule's own slot is checked automatically: if its spell can't be used, KeyRota simply moves on to the next rule.

#### .ready and .oncd

`Kingsbane.ready`: the slot's spell can be used, not counting the global cooldown. That needs all of:

- the spell is learned (the talent is taken);
- there is enough resource;
- the target is in range, if one is needed;
- the cooldown is below 0.2 s or a charge is available;
- Better Assistant shows the slot (With a target: you have a target; In combat: you are in combat).

`Kingsbane.oncd`: the slot is **not ready** for any of these reasons, not only the cooldown. If the state is unknown, both are false.

```text
use Preparation when  # Preparation when Adrenaline Rush, Between the Eyes and Killing Spree are on cooldown
	Adrenaline_Rush.oncd and Between_the_Eyes.oncd and Killing_Spree.oncd
```


#### .cd: cooldown left in percent

`Deathmark.cd <= 3`: no more than 3% of the full cooldown is left. `0` is ready, `100` was just used. The global cooldown is not included. For a spell with charges `.cd` is the recharge of the **next** charge, so the spell can be ready with `cd > 0`.

| Cooldown | 1% = | Example |
|---|---|---|
| 30 s | 0.3 s | `X.cd <= 10`: ≤ 3 s left |
| 1 min | 0.6 s | `Kingsbane.cd <= 3`: ≤ 1.8 s left |
| 2 min | 1.2 s | `Deathmark.cd <= 3`: ≤ 3.6 s left; `Deathmark.cd >= 44`: still ≥ 52 s |
| 3 min | 1.8 s | `X.cd >= 50`: still ≥ 90 s |

A typical use is "hold for a big cooldown": don't spend Kingsbane if Deathmark is about to come back, spend it if Deathmark is far away.

```text
use Kingsbane when  # Kingsbane
	cd == 1 and Deathmark_DoT.up  # under Deathmark
	or
	cd == 1 and Deathmark.cd >= 44  # Deathmark more than 52 s away
```


#### .charges

`Barbed_Shot.charges >= 2`: the spell has 2 charges or more. Spells without charges have 0. A typical pattern keeps the charges from capping:

```text
use Barbed_Shot when  # Barbed Shot
	Barbed_Shot.charges >= 2  # both charges: don't waste the recharge
	or
	Barbed_Shot.charges == 1 and Barbed_Shot.cd <= 20  # the second one is almost back
```


#### .glow and .noglow: the button glows

`Ambush.glow`: the game highlights this spell's button (the same glow you see on your action bar on a proc). `Ambush.noglow`: it doesn't. Use `.glow` only if this spell's button really glows in the game; if you aren't sure, check the proc's buff instead: `Blindside.up`.

### Resources

`Energy >= 70`: at least 70% of maximum energy. **All resources are percentages** from 0 to 100: health, energy, mana, rage, runes, combo points.

| Resource | Number | Example |
|---|---|---|
| Player health | `resource1` (`PlayerHP`) | `PlayerHP <= 40`: health at 40% or lower |
| Target health | `resource2` (`TargetHP`) | `TargetHP >= 50`: the target isn't at half yet. Unknown without a target |
| Specialization resources | `resource3` and on | `Energy >= 70`, `Combo_Points >= 70`, `Rage >= 40` |

**To percent:** `percent = value / maximum × 100`. With 250 energy, 175 energy is 70%. Maximum energy and other resources change with talents, so thresholds in percent carry over between characters better than raw values.

**Combo points.** With a maximum of 7 combo points one point is about 14.3%. The value is rounded to a whole number, and depending on the rounding 2 points read 28 or 29, 3 read 42 or 43, 6 read 85 or 86. So put the threshold **halfway between the steps**: then it is right with any rounding.

| Combo points | % | "at least N" | "at most N" |
|---|---|---|---|
| 0 | 0 | — | `< 7` |
| 1 | 14 | `>= 7` | `< 20` |
| 2 | 28–29 | `>= 20` | `< 35` |
| 3 | 42–43 | `>= 35` | `< 50` |
| 4 | 57 | `>= 50` | `< 65` |
| 5 | 71 | `>= 65` (`>= 70`) | `< 80` |
| 6 | 85–86 | `>= 80` | `< 95` |
| 7 | 100 | `>= 95` | — |

For another maximum, "at least N" is `(N − 0.5) / maximum × 100`. With 5 combo points, "at least 4" is `>= 70` and "at least 5" is `>= 90`.

### Enemies nearby

`enemies >= 3`: 3 or more enemies within the counter's radius. The radius and "Only enemies in combat" are set on the General page (`enemies.range` and `enemies.combatOnly` in JSON). If the count is unknown, the condition is false.

- For enemies to be counted, enemy nameplates must be on in the game (the V key by default), with minor enemies not hidden.
- Choose the radius by your abilities: Fan of Knives hits within 8 yards, so 8 is the honest radius for "Fan of Knives or Mutilate".
- For a single target, `enemies <= 1` is safer than `enemies == 1`: the rule keeps working if the enemy isn't counted.

### Distance to the target

`target.distance <= 8`: the target is 8 yards away or closer. Without a target the distance is unknown and the condition is false. The distance is approximate: the condition holds only when the distance surely meets the threshold; in borderline cases it is false.

```text
use Pistol_Shot when  # the target is out of melee but within 20 yards
	target.distance >= 10 and target.distance <= 20
```


### Panel toggles

Toggles are the buttons of the quick settings panel over the game, plus hotkeys. The rotation decides what they mean.

| Condition | Panel button | Key: Windows, macOS | Usual meaning |
|---|---|---|---|
| `aoe == 1` | Targets: Multiple | F2, ⌃⇧T, cycling | Force multi-target mode |
| `st == 1` | Targets: Single | F2, ⌃⇧T, cycling | Force a single target |
| `aoe == 0 and st == 0` | Targets: Auto | F2, ⌃⇧T, cycling | The rules decide, usually by `enemies` |
| `cd == 1` | Burst | F3, ⌃⇧B | Big cooldowns: use now or hold |
| `defensives == 1` | Healing / defense | — | Healing and defensive abilities |
| `ground_spells == 1` | Ground | — | Ground-targeted spells |
| `movement == 1` | Movement | — | Actions while moving |

- Compare only with `== 0` or `== 1`.
- **`cd == 1` is the Burst button, not a spell's cooldown.** A slot's cooldown is `Slot.cd`.
- `aoe` and `st` are never 1 at the same time. The target mode key cycles Auto, Single, Multiple.
- By default Burst is on, the target mode is Auto and the other buttons are off. KeyRota remembers their state.
- A panel button is active only if the rotation reads that toggle; with the Blizzard profile they are all dimmed.
- Toggles are never unknown.

### How conditions are evaluated

| Condition | Value present | Aura absent | Unknown |
|---|---|---|---|
| `X.up` | true | false | **false** |
| `X.down` | false | true | **false** |
| `X.duration op N`, `X.stacks op N` | compared | compared with **0** | **false** |
| `X.ready` / `X.oncd` | by readiness | — | **false** (both) |
| `X.glow` / `X.noglow` | by the glow | — | **false** (both) |
| `X.cd`, `X.charges`, a resource, `enemies`, `target.distance` | compared | — | **false** |
| a toggle | compared | — | never |

A group is true when all its conditions are true. A rule holds when at least one group is true, or when it has no groups at all.

### "Not", parentheses and OR inside a group

KRC has no `not`, `!=` or parentheses. Everything is expressed with groups:

- **"Not"** is the paired condition: not `.up` is `.down`, not `.ready` is `.oncd`, not `.glow` is `.noglow`; for numbers, the inverse operator: not `>= 70` is `< 70`.
- **(A or B) and C** is two groups: `A and C`, `B and C`.
- **A and not (B and C)** is two groups: `A and not-B`, `A and not-C`.
- **The same condition in every group** (like channel protection) is repeated in each group.

```text
use Fan_of_Knives when  # Fan of Knives: (Multiple mode OR Auto and 2+ enemies) AND below 5 CP
	aoe == 1 and Combo_Points < 70
	or
	st == 0 and enemies >= 2 and Combo_Points < 70
```


### Cheat sheet

| Condition | Meaning |
|---|---|
| `X.up` / `X.down` | aura present / absent |
| `X.duration <= 30` | ≤ 30% left (an absent aura has 0) |
| `X.up and X.duration <= 30` | up and due for a refresh |
| `X.stacks >= 2` | 2 stacks or more (1 stack reads as 0) |
| `X.ready` / `X.oncd` | slot ready / not ready for any reason |
| `X.cd <= 3` | ≤ 3% of the cooldown left |
| `X.charges >= 2` | 2 charges or more |
| `X.glow` / `X.noglow` | button glowing / not |
| `PlayerHP <= 40` | your health ≤ 40% |
| `TargetHP >= 50` | the target at ≥ 50% (false without a target) |
| `Combo_Points >= 70` | 5+ combo points out of 7 |
| `Energy >= 70` | energy ≥ 70% |
| `enemies >= 3` | 3+ enemies in the radius |
| `target.distance <= 8` | the target within 8 yards (false without a target) |
| `aoe == 1` / `st == 1` | Multiple / Single mode |
| `aoe == 0 and st == 0` | Auto mode |
| `cd == 1` | Burst on |
| `defensives == 1` | Healing / defense on |
| `ground_spells == 1` | Ground on |
| `movement == 1` | Movement on |

---

<!-- https://keyrota.app/documentation/en/patterns -->

## Pattern examples

Each pattern is a small but complete rotation: the KRC text on the left, the same JSON on the right. Paste it into the builder (Text and JSON — Paste) to see how it looks on the Slots and Rules pages. The spell IDs are real, from Wowhead. At the end: [complete rotations](#example-files).

### A DoT refreshed in the pandemic window

Apply a DoT when it's missing and refresh it when 30% or less is left: refreshing within that window loses nothing. An absent aura has 0 left, so the refresh is a separate group with `.up`.

*KRC text:*

```text
rotation "Pattern: DoT with the pandemic window"

buff1	Garrote_DoT = "Garrote on target [703]"
slot1	Garrote = "Garrote"
slot4	Mutilate = "Mutilate"
resource1	PlayerHP
resource2	TargetHP
resource3	Energy
resource4	Combo_Points

use Garrote when  # Garrote
	Garrote_DoT.down  # no DoT
	or
	Garrote_DoT.up and Garrote_DoT.duration <= 30  # pandemic

use Mutilate when  # Mutilate
```

*JSON:*

```json
{
  "format": "keyrota-rotation",
  "version": 1,
  "title": "Pattern: DoT with the pandemic window",
  "specID": 259,
  "resources": ["PlayerHP", "TargetHP", "Energy", "Combo_Points"],
  "auras": [
    {"n": 1, "alias": "Garrote_DoT", "label": "Garrote on target [703]", "spell": 703}
  ],
  "slots": [
    {"n": 1, "alias": "Garrote", "label": "Garrote", "spell": 703},
    {"n": 4, "alias": "Mutilate", "label": "Mutilate", "spell": 1329}
  ],
  "rules": [
    {"use": "Garrote", "name": "Garrote", "when": [
      {"all": ["Garrote_DoT.down"], "comment": "no DoT"},
      {"all": ["Garrote_DoT.up", "Garrote_DoT.duration <= 30"], "comment": "pandemic"}
    ]},
    {"use": "Mutilate", "name": "Mutilate"}
  ]
}
```


- To avoid a new DoT on a target about to die on 3+ targets, add `TargetHP >= 50` to the "no DoT" group and a separate group `Garrote_DoT.down and enemies <= 2` for one or two targets.
- A long DoT with a variable duration (Rupture: 4 s + 4 s per combo point) works the same way: the percentages are of that application's duration.

### Builder and spender by combo points

Spend at 5+ combo points out of 7, build below that. The thresholds sit halfway between the steps, see the [combo point table](https://keyrota.app/documentation/en/conditions#resources).

*KRC text:*

```text
rotation "Pattern: builder and spender"

slot3	Envenom = "Envenom"
slot4	Mutilate = "Mutilate"
resource1	PlayerHP
resource2	TargetHP
resource3	Energy
resource4	Combo_Points

use Envenom when  # Envenom
	Combo_Points >= 70  # 5+ CP

use Mutilate when  # Mutilate
	Combo_Points < 70  # below 5 CP
```

*JSON:*

```json
{
  "format": "keyrota-rotation",
  "version": 1,
  "title": "Pattern: builder and spender",
  "specID": 259,
  "resources": ["PlayerHP", "TargetHP", "Energy", "Combo_Points"],
  "slots": [
    {"n": 3, "alias": "Envenom", "label": "Envenom", "spell": 32645},
    {"n": 4, "alias": "Mutilate", "label": "Mutilate", "spell": 1329}
  ],
  "rules": [
    {"use": "Envenom", "name": "Envenom", "when": [
      {"all": ["Combo_Points >= 70"], "comment": "5+ CP"}
    ]},
    {"use": "Mutilate", "name": "Mutilate", "when": [
      {"all": ["Combo_Points < 70"], "comment": "below 5 CP"}
    ]}
  ]
}
```


A builder with `Combo_Points < 70` doesn't overflow combo points: at 5+ it stays silent even if the spender isn't ready yet (not enough energy), and KeyRota waits.

### Resource pooling

Don't spend at once; keep the buff up and pool energy: Envenom when its buff is running out, under Deathmark, or when energy is high. The rest of the time no rule fits at 5+ combo points, and KeyRota waits for more energy: that's pooling.

*KRC text:*

```text
rotation "Pattern: energy pooling"

buff3	Envenom_Buff = "Envenom [32645]"
buff4	Deathmark_DoT = "Deathmark on target [360194]"
slot3	Envenom = "Envenom"
slot4	Mutilate = "Mutilate"
resource1	PlayerHP
resource2	TargetHP
resource3	Energy
resource4	Combo_Points

use Envenom when  # Envenom
	Combo_Points >= 70 and Envenom_Buff.down  # no buff
	or
	Combo_Points >= 70 and Envenom_Buff.duration <= 20  # buff running out
	or
	Combo_Points >= 70 and Deathmark_DoT.up  # under Deathmark
	or
	Combo_Points >= 70 and Energy >= 70  # don't cap energy

use Mutilate when  # Mutilate
	Combo_Points < 70
```

*JSON:*

```json
{
  "format": "keyrota-rotation",
  "version": 1,
  "title": "Pattern: energy pooling",
  "specID": 259,
  "resources": ["PlayerHP", "TargetHP", "Energy", "Combo_Points"],
  "auras": [
    {"n": 3, "alias": "Envenom_Buff", "label": "Envenom [32645]", "spell": 32645},
    {"n": 4, "alias": "Deathmark_DoT", "label": "Deathmark on target [360194]", "spell": 360194}
  ],
  "slots": [
    {"n": 3, "alias": "Envenom", "label": "Envenom", "spell": 32645},
    {"n": 4, "alias": "Mutilate", "label": "Mutilate", "spell": 1329}
  ],
  "rules": [
    {"use": "Envenom", "name": "Envenom", "when": [
      {"all": ["Combo_Points >= 70", "Envenom_Buff.down"], "comment": "no buff"},
      {"all": ["Combo_Points >= 70", "Envenom_Buff.duration <= 20"], "comment": "buff running out"},
      {"all": ["Combo_Points >= 70", "Deathmark_DoT.up"], "comment": "under Deathmark"},
      {"all": ["Combo_Points >= 70", "Energy >= 70"], "comment": "don't cap energy"}
    ]},
    {"use": "Mutilate", "name": "Mutilate", "when": [
      {"all": ["Combo_Points < 70"]}
    ]}
  ]
}
```


- The `Energy >= 70` threshold keeps energy from capping.
- The `Envenom_Buff.duration <= 20` group also fires without the buff (0 left); the `.down` group is there for clarity.
- To tune pooling change only the energy threshold: lower spends earlier, higher pools longer.

### Single target and multiple targets

The player sets the target mode with the target mode key (it cycles Auto, Single, Multiple); in Auto the rules decide by the enemy count. Each AoE rule gets two groups, "Multiple mode" and "Auto with 2+ enemies"; the single-target rule gets "Single mode" and "Auto with at most one enemy".

*KRC text:*

```text
rotation "Pattern: single and multiple targets"

slot3	Envenom = "Envenom"
slot4	Mutilate = "Mutilate"
slot6	Fan_of_Knives = "Fan of Knives"
resource1	PlayerHP
resource2	TargetHP
resource3	Energy
resource4	Combo_Points

use Envenom when  # Envenom
	Combo_Points >= 70

use Fan_of_Knives when  # Fan of Knives: 2+ targets
	aoe == 1 and Combo_Points < 70  # Multiple mode
	or
	st == 0 and enemies >= 2 and Combo_Points < 70  # Auto mode, 2+ enemies

use Mutilate when  # Mutilate: single target
	st == 1 and Combo_Points < 70  # Single mode
	or
	aoe == 0 and enemies <= 1 and Combo_Points < 70  # Auto mode, one enemy
```

*JSON:*

```json
{
  "format": "keyrota-rotation",
  "version": 1,
  "title": "Pattern: single and multiple targets",
  "specID": 259,
  "enemies": {"range": 8, "combatOnly": true},
  "resources": ["PlayerHP", "TargetHP", "Energy", "Combo_Points"],
  "slots": [
    {"n": 3, "alias": "Envenom", "label": "Envenom", "spell": 32645},
    {"n": 4, "alias": "Mutilate", "label": "Mutilate", "spell": 1329},
    {"n": 6, "alias": "Fan_of_Knives", "label": "Fan of Knives", "spell": 51723}
  ],
  "rules": [
    {"use": "Envenom", "name": "Envenom", "when": [
      {"all": ["Combo_Points >= 70"]}
    ]},
    {"use": "Fan_of_Knives", "name": "Fan of Knives: 2+ targets", "when": [
      {"all": ["aoe == 1", "Combo_Points < 70"], "comment": "Multiple mode"},
      {"all": ["st == 0", "enemies >= 2", "Combo_Points < 70"], "comment": "Auto mode, 2+ enemies"}
    ]},
    {"use": "Mutilate", "name": "Mutilate: single target", "when": [
      {"all": ["st == 1", "Combo_Points < 70"], "comment": "Single mode"},
      {"all": ["aoe == 0", "enemies <= 1", "Combo_Points < 70"], "comment": "Auto mode, one enemy"}
    ]}
  ]
}
```


- `st == 0 and enemies >= 2` means "not Single mode": in Multiple mode the rule fires through its first group anyway.
- `enemies <= 1` is safer than `enemies == 1`: on a single target the enemy may not be counted.
- Take the counter radius (`enemies.range`) from your AoE ability: Fan of Knives hits within 8 yards.

### Cooldowns on Burst and "only under the cooldown"

`cd == 1` is the Burst button. A big cooldown is pressed only while Burst is on, and you hold it by turning Burst off, for example before a phase where the boss is immune. Whatever strengthens the cooldown window is pressed only under it: here Vanish only while Deathmark is on the target, and Kingsbane under Deathmark or when Deathmark is far away.

*KRC text:*

```text
rotation "Pattern: Burst"

buff1	Garrote_DoT = "Garrote on target [703]"
buff2	Rupture_DoT = "Rupture on target [1943]"
buff4	Deathmark_DoT = "Deathmark on target [360194]"
buff10	Vanish_Buff = "Vanish [11327]"
slot1	Garrote = "Garrote"
slot4	Mutilate = "Mutilate"
slot8	Deathmark = "Deathmark"
slot9	Kingsbane = "Kingsbane"
slot10	Vanish = "Vanish"
resource1	PlayerHP
resource2	TargetHP
resource3	Energy
resource4	Combo_Points

use Garrote when  # Garrote from Stealth
	Vanish_Buff.up

use Deathmark when  # Deathmark
	cd == 1 and Garrote_DoT.up and Rupture_DoT.up  # Burst on, DoTs up

use Kingsbane when  # Kingsbane
	cd == 1 and Deathmark_DoT.up and Combo_Points < 50  # under Deathmark, 0-3 CP
	or
	cd == 1 and Deathmark.cd >= 44 and Combo_Points < 50  # Deathmark more than 52 s away

use Vanish when  # Vanish
	Deathmark_DoT.up and Combo_Points < 65  # under Deathmark, 0-4 CP

use Mutilate when  # Mutilate
	Combo_Points < 70
```

*JSON:*

```json
{
  "format": "keyrota-rotation",
  "version": 1,
  "title": "Pattern: Burst",
  "specID": 259,
  "resources": ["PlayerHP", "TargetHP", "Energy", "Combo_Points"],
  "auras": [
    {"n": 1, "alias": "Garrote_DoT", "label": "Garrote on target [703]", "spell": 703},
    {"n": 2, "alias": "Rupture_DoT", "label": "Rupture on target [1943]", "spell": 1943},
    {"n": 4, "alias": "Deathmark_DoT", "label": "Deathmark on target [360194]", "spell": 360194},
    {"n": 10, "alias": "Vanish_Buff", "label": "Vanish [11327]", "spell": 11327}
  ],
  "slots": [
    {"n": 1, "alias": "Garrote", "label": "Garrote", "spell": 703},
    {"n": 4, "alias": "Mutilate", "label": "Mutilate", "spell": 1329},
    {"n": 8, "alias": "Deathmark", "label": "Deathmark", "spell": 360194},
    {"n": 9, "alias": "Kingsbane", "label": "Kingsbane", "spell": 385627},
    {"n": 10, "alias": "Vanish", "label": "Vanish", "spell": 1856, "offGcd": true, "queue": true}
  ],
  "rules": [
    {"use": "Garrote", "name": "Garrote from Stealth", "when": [
      {"all": ["Vanish_Buff.up"]}
    ]},
    {"use": "Deathmark", "name": "Deathmark", "when": [
      {"all": ["cd == 1", "Garrote_DoT.up", "Rupture_DoT.up"], "comment": "Burst on, DoTs up"}
    ]},
    {"use": "Kingsbane", "name": "Kingsbane", "when": [
      {"all": ["cd == 1", "Deathmark_DoT.up", "Combo_Points < 50"], "comment": "under Deathmark, 0-3 CP"},
      {"all": ["cd == 1", "Deathmark.cd >= 44", "Combo_Points < 50"], "comment": "Deathmark more than 52 s away"}
    ]},
    {"use": "Vanish", "name": "Vanish", "when": [
      {"all": ["Deathmark_DoT.up", "Combo_Points < 65"], "comment": "under Deathmark, 0-4 CP"}
    ]},
    {"use": "Mutilate", "name": "Mutilate", "when": [
      {"all": ["Combo_Points < 70"]}
    ]}
  ]
}
```


- Vanish is off the GCD but has In the queue window too (**O + Q**): otherwise it would be pressed mid-GCD right after Kingsbane, before Kingsbane's combo points arrive, and would check the old count.
- Turn Burst off and Deathmark and Kingsbane wait; Vanish waits for Deathmark.
- Kingsbane at 0–3 combo points: 5 more arrive after it, and none are wasted.

### Channel protection

The game ends some channels slightly before the last hit, and the next press cuts it off. The protection is the channel's own aura: while more than 8% of it is left, no rule holds. The condition goes into **every** group of every rule. Flag **C** on the channel's slot keeps it from being pressed twice at the start.

*KRC text:*

```text
rotation "Pattern: channel protection"

buff10	Killing_Spree_Buff = "Killing Spree, channel [51690]"
slot8	Killing_Spree = "Killing Spree"
slot9	Dispatch = "Dispatch"
slot11	Sinister_Strike = "Sinister Strike"
resource1	PlayerHP
resource2	TargetHP
resource3	Energy
resource4	Combo_Points

use Killing_Spree when  # Killing Spree
	Combo_Points >= 80 and Killing_Spree_Buff.duration <= 8

use Dispatch when  # Dispatch
	Combo_Points >= 80 and Killing_Spree_Buff.duration <= 8

use Sinister_Strike when  # Sinister Strike
	Combo_Points < 80 and Killing_Spree_Buff.duration <= 8
```

*JSON:*

```json
{
  "format": "keyrota-rotation",
  "version": 1,
  "title": "Pattern: channel protection",
  "specID": 260,
  "resources": ["PlayerHP", "TargetHP", "Energy", "Combo_Points"],
  "auras": [
    {"n": 10, "alias": "Killing_Spree_Buff", "label": "Killing Spree, channel [51690]", "spell": 51690}
  ],
  "slots": [
    {"n": 8, "alias": "Killing_Spree", "label": "Killing Spree", "spell": 51690, "channel": true},
    {"n": 9, "alias": "Dispatch", "label": "Dispatch", "spell": 2098},
    {"n": 11, "alias": "Sinister_Strike", "label": "Sinister Strike", "spell": 193315}
  ],
  "rules": [
    {"use": "Killing_Spree", "name": "Killing Spree", "when": [
      {"all": ["Combo_Points >= 80", "Killing_Spree_Buff.duration <= 8"]}
    ]},
    {"use": "Dispatch", "name": "Dispatch", "when": [
      {"all": ["Combo_Points >= 80", "Killing_Spree_Buff.duration <= 8"]}
    ]},
    {"use": "Sinister_Strike", "name": "Sinister Strike", "when": [
      {"all": ["Combo_Points < 80", "Killing_Spree_Buff.duration <= 8"]}
    ]}
  ]
}
```


- Why `<= 8` and not `.down`: without the channel the aura has 0 left and the condition holds; during the channel it waits only for the last 8%, not for extra fractions of a second after the end.
- If the aura isn't read, the condition is false in every rule and KeyRota presses nothing: better to skip a press than to cut the channel. So the channel's aura must be in the Better Assistant profile.

### An off-GCD macro in the queue window

Two kinds of "off the GCD":
- **O**: the button is pressed at once, even mid-GCD. That's how Keep It Rolling (extending Roll the Bones) loses no time.
- **O + Q**: the button is off the GCD, but its macro or its result depends on the GCD. The Adrenaline Rush macro with Killing Spree as the second line (Killing Spree is on the GCD) is pressed in the queue window at the end of the GCD, and Killing Spree goes off right after Adrenaline Rush.

*The macro in the game (slot 1):*

```text
#showtooltip Adrenaline Rush
/cast Adrenaline Rush
/cast Killing Spree
```


*KRC text:*

```text
rotation "Pattern: off the GCD"

buff2	Triple_Threat = "Roll the Bones, level 3 [1214935]"
buff3	Jackpot = "Roll the Bones, level 4 [1214937]"
buff6	Adrenaline_Rush_Buff = "Adrenaline Rush [13750]"
slot1	Adrenaline_Rush = "Adrenaline Rush + Killing Spree (macro)"
slot4	Keep_It_Rolling = "Keep It Rolling"
slot11	Sinister_Strike = "Sinister Strike"
resource1	PlayerHP
resource2	TargetHP
resource3	Energy
resource4	Combo_Points

use Adrenaline_Rush when  # Adrenaline Rush (+ Killing Spree by the macro)
	Adrenaline_Rush_Buff.down and Combo_Points >= 7 and Combo_Points < 80  # 1-5 CP: at 0 the macro's Killing Spree doesn't cast

use Keep_It_Rolling when  # Keep It Rolling
	Triple_Threat.up
	or
	Jackpot.up

use Sinister_Strike when  # Sinister Strike
	Combo_Points < 80
```

*JSON:*

```json
{
  "format": "keyrota-rotation",
  "version": 1,
  "title": "Pattern: off the GCD",
  "specID": 260,
  "resources": ["PlayerHP", "TargetHP", "Energy", "Combo_Points"],
  "auras": [
    {"n": 2, "alias": "Triple_Threat", "label": "Roll the Bones, level 3 [1214935]", "spell": 1214935},
    {"n": 3, "alias": "Jackpot", "label": "Roll the Bones, level 4 [1214937]", "spell": 1214937},
    {"n": 6, "alias": "Adrenaline_Rush_Buff", "label": "Adrenaline Rush [13750]", "spell": 13750}
  ],
  "slots": [
    {"n": 1, "alias": "Adrenaline_Rush", "label": "Adrenaline Rush + Killing Spree (macro)", "spell": 13750, "offGcd": true, "queue": true},
    {"n": 4, "alias": "Keep_It_Rolling", "label": "Keep It Rolling", "spell": 381989, "offGcd": true},
    {"n": 11, "alias": "Sinister_Strike", "label": "Sinister Strike", "spell": 193315}
  ],
  "rules": [
    {"use": "Adrenaline_Rush", "name": "Adrenaline Rush (+ Killing Spree by the macro)", "when": [
      {"all": ["Adrenaline_Rush_Buff.down", "Combo_Points >= 7", "Combo_Points < 80"], "comment": "1-5 CP: at 0 the macro's Killing Spree doesn't cast"}
    ]},
    {"use": "Keep_It_Rolling", "name": "Keep It Rolling", "when": [
      {"all": ["Triple_Threat.up"]},
      {"all": ["Jackpot.up"]}
    ]},
    {"use": "Sinister_Strike", "name": "Sinister Strike", "when": [
      {"all": ["Combo_Points < 80"]}
    ]}
  ]
}
```


A macro slot's ID is the ID of the macro's main spell (Adrenaline Rush, 13750): Better Assistant uses it to put the macro's spell into the slot, and KeyRota takes the key from the macro button on your bar.

### A defensive by health

Healing or a defensive below a health threshold, only when Healing / defense is on in the panel. The slot is shown **in combat** (In combat, `"show": "combat"`) so that it works without a target. The rule goes first: at low health it matters more than damage.

*KRC text:*

```text
rotation "Pattern: a defensive by health"

slot4	Mutilate = "Mutilate"
slot11	Crimson_Vial = "Crimson Vial"
resource1	PlayerHP
resource2	TargetHP
resource3	Energy
resource4	Combo_Points

use Crimson_Vial when  # Crimson Vial
	defensives == 1 and PlayerHP <= 40  # HP 40% or lower

use Mutilate when  # Mutilate
```

*JSON:*

```json
{
  "format": "keyrota-rotation",
  "version": 1,
  "title": "Pattern: a defensive by health",
  "specID": 259,
  "resources": ["PlayerHP", "TargetHP", "Energy", "Combo_Points"],
  "slots": [
    {"n": 4, "alias": "Mutilate", "label": "Mutilate", "spell": 1329},
    {"n": 11, "alias": "Crimson_Vial", "label": "Crimson Vial", "spell": 185311, "show": "combat"}
  ],
  "rules": [
    {"use": "Crimson_Vial", "name": "Crimson Vial", "when": [
      {"all": ["defensives == 1", "PlayerHP <= 40"], "comment": "HP 40% or lower"}
    ]},
    {"use": "Mutilate", "name": "Mutilate"}
  ]
}
```


Healing / defense is off by default: turn it on in the quick settings panel when you want automatic defensives.

### Procs: by buff or by glow

Check a proc by its buff: in the Assassination raid rotation the Blindside buff (`Blindside.up`) allows the Ambush rule. If the game highlights the button on a proc, a rule can read the glow (`Ambush.glow`), but only if you actually see that glow on your bar.

*KRC text:*

```text
rotation "Pattern: a proc"

buff7	Blindside = "Blindside [121153]"
slot4	Mutilate = "Mutilate"
slot5	Ambush = "Ambush"
resource1	PlayerHP
resource2	TargetHP
resource3	Energy
resource4	Combo_Points

use Ambush when  # Ambush (Blindside)
	Blindside.up and Combo_Points < 70

use Mutilate when  # Mutilate
	Combo_Points < 70
```

*JSON:*

```json
{
  "format": "keyrota-rotation",
  "version": 1,
  "title": "Pattern: a proc",
  "specID": 259,
  "resources": ["PlayerHP", "TargetHP", "Energy", "Combo_Points"],
  "auras": [
    {"n": 7, "alias": "Blindside", "label": "Blindside [121153]", "spell": 121153}
  ],
  "slots": [
    {"n": 4, "alias": "Mutilate", "label": "Mutilate", "spell": 1329},
    {"n": 5, "alias": "Ambush", "label": "Ambush", "spell": 8676}
  ],
  "rules": [
    {"use": "Ambush", "name": "Ambush (Blindside)", "when": [
      {"all": ["Blindside.up", "Combo_Points < 70"]}
    ]},
    {"use": "Mutilate", "name": "Mutilate", "when": [
      {"all": ["Combo_Points < 70"]}
    ]}
  ]
}
```


### Charges

A spell with charges is pressed at two charges or when the second is almost back, so that its recharge never idles. Example: the hunter's Barbed Shot (2 charges).

*KRC text:*

```text
rotation "Pattern: charges"

slot1	Barbed_Shot = "Barbed Shot"
slot2	Kill_Command = "Kill Command"
resource1	PlayerHP
resource2	TargetHP
resource3	Focus

use Barbed_Shot when  # Barbed Shot
	Barbed_Shot.charges >= 2  # both charges
	or
	Barbed_Shot.charges == 1 and Barbed_Shot.cd <= 20  # the second one is almost back

use Kill_Command when  # Kill Command
```

*JSON:*

```json
{
  "format": "keyrota-rotation",
  "version": 1,
  "title": "Pattern: charges",
  "specID": 253,
  "resources": ["PlayerHP", "TargetHP", "Focus"],
  "slots": [
    {"n": 1, "alias": "Barbed_Shot", "label": "Barbed Shot", "spell": 217200},
    {"n": 2, "alias": "Kill_Command", "label": "Kill Command", "spell": 34026}
  ],
  "rules": [
    {"use": "Barbed_Shot", "name": "Barbed Shot", "when": [
      {"all": ["Barbed_Shot.charges >= 2"], "comment": "both charges"},
      {"all": ["Barbed_Shot.charges == 1", "Barbed_Shot.cd <= 20"], "comment": "the second one is almost back"}
    ]},
    {"use": "Kill_Command", "name": "Kill Command"}
  ]
}
```


For a spell with charges `.cd` is the next charge's recharge: `Barbed_Shot.cd <= 20` means at most 20% of its recharge is left.

### Distance to the target

When the target is out of melee range but within range of a ranged ability, press that instead of idling. Without a target the distance is unknown and the rule stays silent.

*KRC text:*

```text
rotation "Pattern: distance"

slot10	Pistol_Shot = "Pistol Shot"
slot11	Sinister_Strike = "Sinister Strike"
resource1	PlayerHP
resource2	TargetHP
resource3	Energy
resource4	Combo_Points

use Pistol_Shot when  # Pistol Shot: the target is far
	target.distance >= 10 and target.distance <= 20 and Combo_Points < 80

use Sinister_Strike when  # Sinister Strike
	Combo_Points < 80
```

*JSON:*

```json
{
  "format": "keyrota-rotation",
  "version": 1,
  "title": "Pattern: distance",
  "specID": 260,
  "resources": ["PlayerHP", "TargetHP", "Energy", "Combo_Points"],
  "slots": [
    {"n": 10, "alias": "Pistol_Shot", "label": "Pistol Shot", "spell": 185763},
    {"n": 11, "alias": "Sinister_Strike", "label": "Sinister Strike", "spell": 193315}
  ],
  "rules": [
    {"use": "Pistol_Shot", "name": "Pistol Shot: the target is far", "when": [
      {"all": ["target.distance >= 10", "target.distance <= 20", "Combo_Points < 80"]}
    ]},
    {"use": "Sinister_Strike", "name": "Sinister Strike", "when": [
      {"all": ["Combo_Points < 80"]}
    ]}
  ]
}
```


A melee rule doesn't need to check the distance: if the target is too far, the spell can't be used, and KeyRota moves on to the next rule by itself.

### Movement

The Movement panel button is your signal to the rotation: "I'll have to run now". The rotation decides what to do, for example switch to a ranged ability.

*KRC text:*

```text
rotation "Pattern: movement"

slot10	Pistol_Shot = "Pistol Shot"
slot11	Sinister_Strike = "Sinister Strike"
resource1	PlayerHP
resource2	TargetHP
resource3	Energy
resource4	Combo_Points

use Pistol_Shot when  # Pistol Shot while moving
	movement == 1 and Combo_Points < 80

use Sinister_Strike when  # Sinister Strike
	movement == 0 and Combo_Points < 80
```

*JSON:*

```json
{
  "format": "keyrota-rotation",
  "version": 1,
  "title": "Pattern: movement",
  "specID": 260,
  "resources": ["PlayerHP", "TargetHP", "Energy", "Combo_Points"],
  "slots": [
    {"n": 10, "alias": "Pistol_Shot", "label": "Pistol Shot", "spell": 185763},
    {"n": 11, "alias": "Sinister_Strike", "label": "Sinister Strike", "spell": 193315}
  ],
  "rules": [
    {"use": "Pistol_Shot", "name": "Pistol Shot while moving", "when": [
      {"all": ["movement == 1", "Combo_Points < 80"]}
    ]},
    {"use": "Sinister_Strike", "name": "Sinister Strike", "when": [
      {"all": ["movement == 0", "Combo_Points < 80"]}
    ]}
  ]
}
```


### Ground-targeted spells

The Ground button allows spells that are placed on the ground. KeyRota only presses a key, so the macro sets the place: `[@player]` at your feet, `[@cursor]` under the cursor.

*The macro in the game:*

```text
#showtooltip Death and Decay
/cast [@player] Death and Decay
```


*KRC text:*

```text
rotation "Pattern: ground spells"

slot1	Death_and_Decay = "Death and Decay (macro @player)"
resource1	PlayerHP
resource2	TargetHP
resource3	Runes
resource4	Runic_Power

use Death_and_Decay when  # Death and Decay
	ground_spells == 1 and enemies >= 2  # Ground on, 2+ enemies
```

*JSON:*

```json
{
  "format": "keyrota-rotation",
  "version": 1,
  "title": "Pattern: ground spells",
  "specID": 250,
  "resources": ["PlayerHP", "TargetHP", "Runes", "Runic_Power"],
  "slots": [
    {"n": 1, "alias": "Death_and_Decay", "label": "Death and Decay (macro @player)", "spell": 43265}
  ],
  "rules": [
    {"use": "Death_and_Decay", "name": "Death and Decay", "when": [
      {"all": ["ground_spells == 1", "enemies >= 2"], "comment": "Ground on, 2+ enemies"}
    ]}
  ]
}
```


### The complete starter rotation

The rotation from the [builder example](https://keyrota.app/documentation/en/editor#walkthrough): two DoTs, a builder, a spender with pooling, a defensive and Deathmark on Burst.

*KRC text:*

```text
rotation "Assassination: example v1"

# Example from the KeyRota documentation: two bleeds, a builder, a spender with energy pooling,
# a defensive at low health and Deathmark on the Burst button.
# Combo points in percent of 7: 5 points = 71%, threshold 70 = 5+.

buff1	Garrote_DoT = "Garrote on target [703]"
buff2	Rupture_DoT = "Rupture on target [1943]"
buff3	Envenom_Buff = "Envenom [32645]"
buff4	Deathmark_DoT = "Deathmark on target [360194]"
slot1	Garrote = "Garrote"
slot2	Rupture = "Rupture"
slot3	Envenom = "Envenom"
slot4	Mutilate = "Mutilate"
slot5	Deathmark = "Deathmark"
slot6	Crimson_Vial = "Crimson Vial"
resource1	PlayerHP
resource2	TargetHP
resource3	Energy
resource4	Combo_Points

# --- Defensive: only with Healing / defense on ---
use Crimson_Vial when  # Crimson Vial
	defensives == 1 and PlayerHP <= 40  # health 40% or lower

# --- Cooldown: only with Burst on ---
use Deathmark when  # Deathmark
	cd == 1 and Garrote_DoT.up and Rupture_DoT.up and Envenom_Buff.up  # Burst on, bleeds and Envenom up

# --- Bleeds ---
use Garrote when  # Garrote
	Garrote_DoT.down  # no bleed
	or
	Garrote_DoT.up and Garrote_DoT.duration <= 30  # pandemic window: 30% or less left

use Rupture when  # Rupture
	Combo_Points >= 70 and Rupture_DoT.down  # 5+ combo points, no bleed
	or
	Combo_Points >= 70 and Rupture_DoT.up and Rupture_DoT.duration <= 30  # 5+ combo points, pandemic window

# --- Spender: 5+ combo points, pooling energy ---
use Envenom when  # Envenom
	Combo_Points >= 70 and Envenom_Buff.down  # no buff
	or
	Combo_Points >= 70 and Envenom_Buff.up and Envenom_Buff.duration <= 20  # buff running out
	or
	Combo_Points >= 70 and Energy >= 70  # don't cap energy
	or
	Combo_Points >= 70 and Deathmark_DoT.up  # Deathmark on the target

# --- Builder: below 5 combo points ---
use Mutilate when  # Mutilate
	Combo_Points < 70  # below 5 combo points
```

*JSON:*

```json
{
  "format": "keyrota-rotation",
  "version": 1,
  "title": "Assassination: example v1",
  "specID": 259,
  "ignoreMainIcon": true,
  "enemies": {"range": 8, "combatOnly": true},
  "notes": [
    "Example from the KeyRota documentation: two bleeds, a builder, a spender with energy pooling,",
    "a defensive at low health and Deathmark on the Burst button.",
    "Combo points in percent of 7: 5 points = 71%, threshold 70 = 5+."
  ],
  "resources": ["PlayerHP", "TargetHP", "Energy", "Combo_Points"],
  "auras": [
    {"n": 1, "alias": "Garrote_DoT", "label": "Garrote on target [703]", "spell": 703},
    {"n": 2, "alias": "Rupture_DoT", "label": "Rupture on target [1943]", "spell": 1943},
    {"n": 3, "alias": "Envenom_Buff", "label": "Envenom [32645]", "spell": 32645},
    {"n": 4, "alias": "Deathmark_DoT", "label": "Deathmark on target [360194]", "spell": 360194}
  ],
  "slots": [
    {"n": 1, "alias": "Garrote", "label": "Garrote", "spell": 703, "show": "target"},
    {"n": 2, "alias": "Rupture", "label": "Rupture", "spell": 1943, "show": "target"},
    {"n": 3, "alias": "Envenom", "label": "Envenom", "spell": 32645, "show": "target"},
    {"n": 4, "alias": "Mutilate", "label": "Mutilate", "spell": 1329, "show": "target"},
    {"n": 5, "alias": "Deathmark", "label": "Deathmark", "spell": 360194, "show": "target"},
    {"n": 6, "alias": "Crimson_Vial", "label": "Crimson Vial", "spell": 185311, "show": "combat"}
  ],
  "rules": [
    {
      "use": "Crimson_Vial",
      "name": "Crimson Vial",
      "note": ["--- Defensive: only with Healing / defense on ---"],
      "when": [{"all": ["defensives == 1", "PlayerHP <= 40"], "comment": "health 40% or lower"}]
    },
    {
      "use": "Deathmark",
      "name": "Deathmark",
      "note": ["--- Cooldown: only with Burst on ---"],
      "when": [
        {
          "all": ["cd == 1", "Garrote_DoT.up", "Rupture_DoT.up", "Envenom_Buff.up"],
          "comment": "Burst on, bleeds and Envenom up"
        }
      ]
    },
    {
      "use": "Garrote",
      "name": "Garrote",
      "note": ["--- Bleeds ---"],
      "when": [
        {"all": ["Garrote_DoT.down"], "comment": "no bleed"},
        {
          "all": ["Garrote_DoT.up", "Garrote_DoT.duration <= 30"],
          "comment": "pandemic window: 30% or less left"
        }
      ]
    },
    {
      "use": "Rupture",
      "name": "Rupture",
      "when": [
        {"all": ["Combo_Points >= 70", "Rupture_DoT.down"], "comment": "5+ combo points, no bleed"},
        {
          "all": ["Combo_Points >= 70", "Rupture_DoT.up", "Rupture_DoT.duration <= 30"],
          "comment": "5+ combo points, pandemic window"
        }
      ]
    },
    {
      "use": "Envenom",
      "name": "Envenom",
      "note": ["--- Spender: 5+ combo points, pooling energy ---"],
      "when": [
        {"all": ["Combo_Points >= 70", "Envenom_Buff.down"], "comment": "no buff"},
        {
          "all": ["Combo_Points >= 70", "Envenom_Buff.up", "Envenom_Buff.duration <= 20"],
          "comment": "buff running out"
        },
        {"all": ["Combo_Points >= 70", "Energy >= 70"], "comment": "don't cap energy"},
        {"all": ["Combo_Points >= 70", "Deathmark_DoT.up"], "comment": "Deathmark on the target"}
      ]
    },
    {
      "use": "Mutilate",
      "name": "Mutilate",
      "note": ["--- Builder: below 5 combo points ---"],
      "when": [{"all": ["Combo_Points < 70"], "comment": "below 5 combo points"}]
    }
  ]
}
```


### A complete raid rotation

A real Assassination raid rotation (Fatebound): 15 rules, Burst, Vanish under Deathmark, copying DoTs with Crimson Tempest, AoE and single target, energy pooling.

**KRC text: Assassination, raid**
*KRC text:*

```text
rotation "Assassination, raid v2.8: Burst button, Method 12.1, SimulationCraft, top logs"

# Assassination, raid. Based on the Method guide (patch 12.1), the SimulationCraft action list and the top Warcraft Logs players.
# Ignore main icon: on. Deathmark and Kingsbane only with Burst on; Vanish only while Deathmark is on the target.
# Combo points in percent of 7: 3 = 43%, 4 = 57%, 5 = 71%, 6 = 86%, 7 = 100%.

# v2.4: Kingsbane only at 0–3 combo points (Implacable Strikes then add 5), Vanish at 0–4 (Garrote from stealth adds 3).
# v2.5: Crimson Tempest only when just one of the two bleeds is fresh; before that it was pressed several times in a row.
# v2.6: with Burst on and Deathmark ready or already on the target, Garrote and Rupture go on a target at any health.
# v2.7 and v2.8: a new target without bleeds, Burst on, Deathmark ready, 2+ enemies nearby: Crimson Tempest copies the bleeds first (at any combo points), then Deathmark.

buff1	Garrote_DoT = "Garrote on target [703]"
buff2	Rupture_DoT = "Rupture on target [1943]"
buff3	Envenom_Buff = "Envenom [32645]"
buff4	Deathmark_DoT = "Deathmark on target [360194]"
buff5	Improved_Garrote = "Improved Garrote [392401]"
buff6	Darkest_Night = "Darkest Night [457280]"
buff7	Blindside = "Blindside [121153]"
buff8	Stealth_Buff = "Stealth [1784]"
buff9	Stealth_Sub = "Stealth (Subterfuge) [115191]"
buff10	Vanish_Buff = "Vanish [11327]"
slot1	Garrote = "Garrote"
slot2	Rupture = "Rupture"
slot3	Envenom = "Envenom"
slot4	Mutilate = "Mutilate"
slot5	Ambush = "Ambush"
slot6	Fan_of_Knives = "Fan of Knives"
slot7	Crimson_Tempest = "Crimson Tempest"
slot8	Deathmark = "Deathmark"
slot9	Kingsbane = "Kingsbane"
slot10	Vanish = "Vanish"
slot11	Crimson_Vial = "Crimson Vial"
resource1	PlayerHP
resource2	TargetHP
resource3	Energy
resource4	Combo_Points

# --- Defensive: only with Healing / defense on ---
use Crimson_Vial when  # Crimson Vial
	defensives == 1 and PlayerHP <= 40  # health 40% or lower

# === Stealth ===
use Garrote when  # Garrote from stealth
	Stealth_Buff.up  # in stealth
	or
	Stealth_Sub.up  # in stealth (Subterfuge)
	or
	Vanish_Buff.up  # right after Vanish

use Garrote when  # Garrote: the Improved Garrote window
	Improved_Garrote.up and Garrote_DoT.down  # the window is open, no Garrote on the target
	or
	Improved_Garrote.up and Garrote_DoT.duration <= 78  # SimulationCraft: refresh with 14 of 18 seconds left

# === Cooldowns (Burst button) ===
use Deathmark when  # Deathmark
	cd == 1 and Garrote_DoT.up and Rupture_DoT.up and Kingsbane.cd <= 3 and Envenom_Buff.duration >= 35 and Stealth_Buff.down and Stealth_Sub.down  # Burst on, bleeds up, Kingsbane ready, Envenom for 2+ more seconds

use Kingsbane when  # Kingsbane
	cd == 1 and Garrote_DoT.up and Rupture_DoT.up and Envenom_Buff.up and Deathmark_DoT.up and Combo_Points < 50 and Stealth_Buff.down and Stealth_Sub.down  # Burst on, Deathmark on the target, 0–3 combo points
	or
	cd == 1 and Garrote_DoT.up and Rupture_DoT.up and Envenom_Buff.up and Deathmark.cd >= 44 and Combo_Points < 50 and Stealth_Buff.down and Stealth_Sub.down  # Burst on, Deathmark more than 52 s away, 0–3 combo points

use Vanish when  # Vanish
	Deathmark_DoT.up and Improved_Garrote.down and Combo_Points < 65 and Stealth_Buff.down and Stealth_Sub.down  # Deathmark on the target, 0–4 combo points (Garrote from stealth adds 3)

# === Bleeds ===
use Crimson_Tempest when  # Crimson Tempest: copy the bleeds to a new target for Deathmark
	cd == 1 and Deathmark.cd <= 3 and Garrote_DoT.down and Rupture_DoT.down and Envenom_Buff.up and enemies >= 2  # combo points don't matter

# No new bleed on adds about to die (like SimulationCraft’s “the target lives 12+ more seconds”): on 1–2 targets and in Single mode always;
# on 3+ targets only if the target has 50%+ health, or Burst is on and Deathmark is ready, or Deathmark is already on the target.
use Garrote when  # Garrote: upkeep
	Garrote_DoT.down and Combo_Points < 95 and enemies <= 2
	or
	Garrote_DoT.down and Combo_Points < 95 and st == 1
	or
	Garrote_DoT.down and Combo_Points < 95 and TargetHP >= 50  # 3+ targets: only a target that will live
	or
	Garrote_DoT.down and Combo_Points < 95 and cd == 1 and Deathmark.cd <= 3  # Burst on and Deathmark ready: health doesn't matter
	or
	Garrote_DoT.down and Combo_Points < 95 and Deathmark_DoT.up  # Deathmark on the target (copied bleeds)
	or
	Garrote_DoT.up and Garrote_DoT.duration <= 30 and Combo_Points < 95  # pandemic window (already up)

use Rupture when  # Rupture
	Combo_Points >= 70 and Rupture_DoT.down and enemies <= 2
	or
	Combo_Points >= 70 and Rupture_DoT.down and st == 1
	or
	Combo_Points >= 70 and Rupture_DoT.down and TargetHP >= 50  # 3+ targets: only a target that will live
	or
	Combo_Points >= 70 and Rupture_DoT.down and cd == 1 and Deathmark.cd <= 3  # Burst on and Deathmark ready
	or
	Combo_Points >= 70 and Rupture_DoT.down and Deathmark_DoT.up  # Deathmark on the target
	or
	Combo_Points >= 70 and Rupture_DoT.up and Rupture_DoT.duration <= 25 and Darkest_Night.down  # refresh; don't spend the 7 combo points of Darkest Night on it

# === Builders: up to 5 combo points, up to 7 with Darkest Night ===

# Crimson Tempest once right after a fresh Rupture (on 4+ targets also after a fresh Garrote); otherwise Fan of Knives.
# Only one of the two may be fresh (94%+): right after Crimson Tempest both are fresh, so the next one waits.
use Crimson_Tempest when  # Crimson Tempest: spread the bleeds right after a fresh Rupture or Garrote
	aoe == 1 and Darkest_Night.down and Combo_Points < 70 and Garrote_DoT.up and Rupture_DoT.up and Rupture_DoT.duration >= 94 and Garrote_DoT.duration < 94
	or
	aoe == 1 and Darkest_Night.down and Combo_Points < 70 and Garrote_DoT.up and Rupture_DoT.up and Garrote_DoT.duration >= 94 and Rupture_DoT.duration < 94
	or
	aoe == 1 and Darkest_Night.up and Combo_Points < 95 and Garrote_DoT.up and Rupture_DoT.up and Rupture_DoT.duration >= 94 and Garrote_DoT.duration < 94
	or
	aoe == 1 and Darkest_Night.up and Combo_Points < 95 and Garrote_DoT.up and Rupture_DoT.up and Garrote_DoT.duration >= 94 and Rupture_DoT.duration < 94
	or
	st == 0 and enemies >= 4 and Darkest_Night.down and Combo_Points < 70 and Garrote_DoT.up and Rupture_DoT.up and Rupture_DoT.duration >= 94 and Garrote_DoT.duration < 94
	or
	st == 0 and enemies >= 4 and Darkest_Night.down and Combo_Points < 70 and Garrote_DoT.up and Rupture_DoT.up and Garrote_DoT.duration >= 94 and Rupture_DoT.duration < 94
	or
	st == 0 and enemies >= 4 and Darkest_Night.up and Combo_Points < 95 and Garrote_DoT.up and Rupture_DoT.up and Rupture_DoT.duration >= 94 and Garrote_DoT.duration < 94
	or
	st == 0 and enemies >= 4 and Darkest_Night.up and Combo_Points < 95 and Garrote_DoT.up and Rupture_DoT.up and Garrote_DoT.duration >= 94 and Rupture_DoT.duration < 94
	or
	st == 0 and enemies >= 2 and enemies <= 3 and Darkest_Night.down and Combo_Points < 70 and Garrote_DoT.up and Rupture_DoT.up and Rupture_DoT.duration >= 94 and Garrote_DoT.duration < 94  # 2–3 targets: only after a fresh Rupture
	or
	st == 0 and enemies >= 2 and enemies <= 3 and Darkest_Night.up and Combo_Points < 95 and Garrote_DoT.up and Rupture_DoT.up and Rupture_DoT.duration >= 94 and Garrote_DoT.duration < 94  # 2–3 targets: only after a fresh Rupture

use Fan_of_Knives when  # Fan of Knives: single target, Darkest Night, one combo point short
	st == 1 and Darkest_Night.up and Combo_Points >= 80 and Combo_Points < 95
	or
	aoe == 0 and enemies <= 1 and Darkest_Night.up and Combo_Points >= 80 and Combo_Points < 95

use Fan_of_Knives when  # Fan of Knives: 2+ targets
	aoe == 1 and Darkest_Night.down and Combo_Points < 70
	or
	aoe == 1 and Darkest_Night.up and Combo_Points < 95
	or
	st == 0 and enemies >= 2 and Darkest_Night.down and Combo_Points < 70
	or
	st == 0 and enemies >= 2 and Darkest_Night.up and Combo_Points < 95

use Ambush when  # Ambush (Blindside)
	Blindside.up and st == 1 and Darkest_Night.down and Combo_Points < 70
	or
	Blindside.up and st == 1 and Darkest_Night.up and Combo_Points < 95
	or
	Blindside.up and aoe == 0 and enemies <= 1 and Darkest_Night.down and Combo_Points < 70
	or
	Blindside.up and aoe == 0 and enemies <= 1 and Darkest_Night.up and Combo_Points < 95

use Mutilate when  # Mutilate
	st == 1 and Darkest_Night.down and Combo_Points < 70
	or
	st == 1 and Darkest_Night.up and Combo_Points < 95
	or
	aoe == 0 and enemies <= 1 and Darkest_Night.down and Combo_Points < 70
	or
	aoe == 0 and enemies <= 1 and Darkest_Night.up and Combo_Points < 95

# === Spender: 5+ combo points (7 with Darkest Night) ===
# SimulationCraft: keep Envenom up rather than pressing it at once: in the buff's last second, while Deathmark is on the target, before energy caps, earlier on 4+ targets.
use Envenom when  # Envenom
	Darkest_Night.down and Combo_Points >= 70 and Envenom_Buff.down
	or
	Darkest_Night.down and Combo_Points >= 70 and Envenom_Buff.duration <= 20  # about 1 s left
	or
	Darkest_Night.down and Combo_Points >= 70 and Deathmark_DoT.up
	or
	Darkest_Night.down and Combo_Points >= 70 and Energy >= 70  # don't cap energy
	or
	Darkest_Night.down and Combo_Points >= 70 and Energy >= 30 and enemies >= 4
	or
	Darkest_Night.up and Combo_Points >= 95 and Envenom_Buff.down
	or
	Darkest_Night.up and Combo_Points >= 95 and Envenom_Buff.duration <= 20
	or
	Darkest_Night.up and Combo_Points >= 95 and Deathmark_DoT.up
	or
	Darkest_Night.up and Combo_Points >= 95 and Energy >= 70
	or
	Darkest_Night.up and Combo_Points >= 95 and Energy >= 30 and enemies >= 4
```


Its JSON is in the files below. The second rotation is Outlaw (Trickster) with the Adrenaline Rush macro (**O + Q**), Keep It Rolling (**O**), Killing Spree (**C**) and channel protection in every rule.

### Example files

| Example | Files |
|---|---|
| The starter Assassination rotation from the builder example | [JSON](https://keyrota.app/documentation/examples/starter-assassination.en.json), [KRC text](https://keyrota.app/documentation/examples/starter-assassination.en.txt) |
| Assassination, raid: 15 rules | [JSON](https://keyrota.app/documentation/examples/assassination-raid.en.json), [KRC text](https://keyrota.app/documentation/examples/assassination-raid.en.txt) |
| Outlaw, raid and Mythic+: 13 rules, flags O, OQ, C | [JSON](https://keyrota.app/documentation/examples/outlaw-raid.en.json), [KRC text](https://keyrota.app/documentation/examples/outlaw-raid.en.txt) |
| The shortest useful rotation | [JSON](https://keyrota.app/documentation/examples/minimal.en.json), [KRC text](https://keyrota.app/documentation/examples/minimal.en.txt) |
| A syntax sample: every condition and flag (no IDs, not for combat) | [JSON](https://keyrota.app/documentation/examples/syntax-every-condition.en.json), [KRC text](https://keyrota.app/documentation/examples/syntax-every-condition.en.txt) |

Every example exists in all the site's languages: the labels, rules and comments are translated, while the conditions, spell IDs and flags are the same. The language is in the file name: `.en`, `.ru`, `.de`, `.fr`, `.es`, `.it`, `.pt`, `.ko`, `.zh`, `.zh-TW`.

To open a file: New — From file… in the main window, or Open… on the builder's Text and JSON page.

---

<!-- https://keyrota.app/documentation/en/ai -->

## Building a rotation with an AI agent

> [!TIP] A link to this page is enough
> You can simply give your AI agent the link to this page: it will figure everything out on its own and ask you for any missing details if it needs them.

An AI agent with web and file access can build a KRC rotation for you: study the logs of the best players of your specialization, guides and the SimulationCraft action list, compare them with your talents and logs, and hand you a ready JSON. Here is what to give it, a ready prompt, and how to check the result.

### What you need

**An agent** that can open websites (Warcraft Logs, Raider.IO, Wowhead, GitHub) and read files. Without web access, give it the documentation and log excerpts as files.

**Documentation for the agent** (the links are already in the prompt):

| Link | What's inside |
|---|---|
| [llms.txt](https://keyrota.app/documentation/llms.txt) | The documentation index for AI agents: all languages, files and examples |
| [krc-guide.en.md](https://keyrota.app/documentation/krc-guide.en.md) | The whole documentation in one Markdown file |
| [keyrota-rotation.schema.json](https://keyrota.app/documentation/keyrota-rotation.schema.json) | JSON Schema to check the answer |
| [Assassination, raid](https://keyrota.app/documentation/examples/assassination-raid.en.json), [Outlaw, raid and Mythic+](https://keyrota.app/documentation/examples/outlaw-raid.en.json) | Real raid rotations as a model |

**Your data** (the placeholders in the prompt):

| Placeholder | What to put in | Example |
|---|---|---|
| `{CLASS}` | Class | Rogue |
| `{SPEC}` | Specialization | Assassination |
| `{HERO_TALENTS}` | Hero talents | Fatebound |
| `{TALENT_STRING}` | The talent export string from the game (talent window, Share) | `CMQAAAAA…` |
| `{CONTENT}` | Raid or Mythic+ | raid |
| `{RAID_OR_DUNGEONS}` | The raid or a list of dungeons | The Venomous Abyss |
| `{DIFFICULTY}` | Difficulty or key level | heroic |
| `{BOSSES}` | Bosses the rotation is for | all |
| `{ITEM_LEVEL}` | Item level | 328 |
| `{MY_LOGS}` | A link to your character or report on Warcraft Logs | `https://www.warcraftlogs.com/character/eu/<realm>/<name>` |
| `{COMBAT_LOG_FILE}` | The combat log file you attach | `WoWCombatLog-101726_214449.txt` |
| `{PREFERENCES}` | Your wishes | "cooldowns only on the Burst button, no defensives" |

### The prompt

The agent collects the data itself, compares it with yours and explains every rule. Fill in the braces and attach a combat log if you have one.

*Prompt:*

```text
You are a World of Warcraft expert and a KeyRota rotation author. Build me a KeyRota advanced rotation: JSON in the keyrota-rotation format, version 1.

MY CHARACTER
- Class and specialization: {CLASS}, {SPEC}. Hero talents: {HERO_TALENTS}.
- Talent string from the game: {TALENT_STRING}
- Content: {CONTENT}: {RAID_OR_DUNGEONS}. Difficulty or key levels: {DIFFICULTY}. Bosses: {BOSSES}.
- Item level: {ITEM_LEVEL}.
- My Warcraft Logs: {MY_LOGS}
- Combat log (attached): {COMBAT_LOG_FILE}
- Preferences: {PREFERENCES}

STEP 1. LEARN THE KEYROTA FORMAT
- https://keyrota.app/documentation/llms.txt and https://keyrota.app/documentation/krc-guide.en.md: slots (up to 12), auras (up to 12, only my buffs and my debuffs on the target), resources in percent, all conditions, slot flags, the panel toggles aoe, st, cd, defensives, ground_spells, movement, rules checked top-down, OR groups of AND conditions, limits of 16 groups of 16 conditions, an unknown value makes a condition false.
- JSON Schema: https://keyrota.app/documentation/keyrota-rotation.schema.json
- Models: https://keyrota.app/documentation/examples/assassination-raid.en.json and https://keyrota.app/documentation/examples/outlaw-raid.en.json

STEP 2. COLLECT THE DATA
1. Warcraft Logs: {SPEC} {CLASS} damage rankings for every boss ({CONTENT}, {DIFFICULTY}); for Mythic+, for every dungeon and key level. Take the 5-10 best logs with the hero talents {HERO_TALENTS}.
2. Compare their talents with my string. Note the differences that change the priority and say whether I should take them.
3. From the top logs collect: the opener and the cast order; uptime of the important buffs and debuffs; resources at casts (combo points or the equivalent at finishers, energy or the equivalent before spending, resource lost to capping); when and together with what big cooldowns are used and how they are lined up with the phases; when and at what health defensives are used; from how many targets they switch to AoE; casts per minute and time without casts.
4. For Mythic+: Raider.IO (the meta, top runs, talents per dungeon).
5. Guides (Wowhead, Method, the class Discord) and the SimulationCraft action list (APL) for {SPEC} on the current expansion's branch: use it as the base priority and check it against the logs.
6. My logs and combat log: where my uptimes, resources at spenders, cooldowns and pace differ from the top players.

STEP 3. WRITE THE ROTATION
- "format": "keyrota-rotation", "version": 1, the specID of my specialization, resources from the specialization table in the docs (resource1 PlayerHP, resource2 TargetHP, then the specialization's resources), "ignoreMainIcon": true.
- Slots: real Wowhead spell IDs (the spell field). "show": "combat" for healing and defensives. Flags: offGcd for off-GCD spells; offGcd and queue for macros whose second line is on the GCD and for buttons that need the previous hit's result; channel for channels; manual for what I press myself. For a macro slot, write the macro text (#showtooltip and /cast).
- Auras: IDs of the buffs and debuffs themselves, not of the spells that apply them. Only my auras.
- Rules in priority order. Defensives first and only with defensives == 1. Big cooldowns only with cd == 1 (the Burst button, not a spell cooldown); whatever strengthens the cooldown window only under it. Single target and AoE: a group aoe == 1 and a group st == 0 and enemies >= N for AoE; a group st == 1 and a group aoe == 0 and enemies <= 1 for single target. DoTs: a group X.down and a group X.up and X.duration <= 30. Resources in percent, thresholds halfway between steps (combo points out of 7: "at least 5" is Combo_Points >= 70, "at most 3" is Combo_Points < 50). If the next button cuts a channel off, add X.duration <= 8 for the channel's aura to every group of every rule.
- KRC has no not, != or parentheses: use the pairs up/down, ready/oncd, glow/noglow, inverse operators and several groups.
- Write the labels, rule names and comments in English, and the names used in conditions (alias) in Latin letters.
- Give every rule a name and every group a short comment: what and why.
- At the end check: the JSON passes the schema; every name in the conditions is declared; no more than 16 conditions in a group and 16 groups in a rule; the IDs exist on Wowhead for the current patch.

ANSWER
1. The whole rotation JSON in one block.
2. Macro texts for the macro slots.
3. A table: rule, what it does, where it comes from (which log, guide or APL line).
4. What to turn on in the game: quick settings panel buttons, the enemy counter radius, what to put on the action bars.
5. A test plan: a training dummy, then a combat log against the top logs; which numbers to compare.
```


### A filled-in prompt

The same prompt in full, filled in for Assassination in a heroic raid:

*Prompt: Assassination, heroic raid:*

```text
You are a World of Warcraft expert and a KeyRota rotation author. Build me a KeyRota advanced rotation: JSON in the keyrota-rotation format, version 1.

MY CHARACTER
- Class and specialization: Rogue, Assassination. Hero talents: Fatebound.
- Talent string from the game: CMQAAAAAAAAAAAAAAAAAAAAAAYmlZmFDGAAAAAYWGsNDAAAAAILzMzMzMjxyMzMbzsMzMPgZMzYMMzYMAGYBmxoxsBYbAbGAMzMGD
- Content: raid: The Venomous Abyss. Difficulty: heroic. Bosses: all; pay special attention to Ula'tek (damage windows on the Heart).
- Item level: 328.
- My Warcraft Logs: https://www.warcraftlogs.com/character/eu/<realm>/<name>
- Combat log (attached): WoWCombatLog-101726_214449.txt, 5 minutes on a training dummy, single target.
- Preferences: Deathmark and Kingsbane only on the Burst button; Vanish only under Deathmark; Crimson Vial at 40% health or lower; energy pooling as in SimulationCraft.

STEP 1. LEARN THE KEYROTA FORMAT
- https://keyrota.app/documentation/llms.txt and https://keyrota.app/documentation/krc-guide.en.md: slots (up to 12), auras (up to 12, only my buffs and my debuffs on the target), resources in percent, all conditions, slot flags, the panel toggles aoe, st, cd, defensives, ground_spells, movement, rules checked top-down, OR groups of AND conditions, limits of 16 groups of 16 conditions, an unknown value makes a condition false.
- JSON Schema: https://keyrota.app/documentation/keyrota-rotation.schema.json
- Models: https://keyrota.app/documentation/examples/assassination-raid.en.json and https://keyrota.app/documentation/examples/outlaw-raid.en.json

STEP 2. COLLECT THE DATA
1. Warcraft Logs: Assassination Rogue damage rankings for every boss (raid The Venomous Abyss, heroic difficulty). Take the 5-10 best logs with the hero talents Fatebound.
2. Compare their talents with my string. Note the differences that change the priority and say whether I should take them.
3. From the top logs collect: the opener and the cast order; uptime of Garrote, Rupture and the Envenom buff; combo points at Envenom, Rupture, Kingsbane and Vanish, energy before Envenom, energy and combo points lost to capping; when and together with what Deathmark and Kingsbane are used and how they are lined up with the phases (especially with the Heart on Ula'tek); when and at what health Crimson Vial and other defensives are used; from how many targets they switch to Fan of Knives and Crimson Tempest; casts per minute and time without casts.
4. Guides (Wowhead, Method, the rogue Discord) and the SimulationCraft action list (APL) for Assassination on the current expansion's branch: use it as the base priority and check it against the logs.
5. My logs and combat log: where my uptimes, combo points at spenders, cooldowns and pace differ from the top players.

STEP 3. WRITE THE ROTATION
- "format": "keyrota-rotation", "version": 1, "specID": 259, "resources": ["PlayerHP", "TargetHP", "Energy", "Combo_Points"], "ignoreMainIcon": true.
- Slots: real Wowhead spell IDs (the spell field). "show": "combat" for Crimson Vial. Flags: offGcd for off-GCD spells; offGcd and queue for Vanish (it needs the previous hit's combo points); channel for channels, if there are any; manual for what I press myself. For a macro slot, write the macro text (#showtooltip and /cast).
- Auras: IDs of the buffs and debuffs themselves, not of the spells that apply them (for example, the Vanish buff is not the same as the spell). Only my auras.
- Rules in priority order. Crimson Vial first and only with defensives == 1 and health at 40% or lower. Deathmark and Kingsbane only with cd == 1 (the Burst button, not a spell cooldown); Vanish only while Deathmark is on the target. Single target and AoE: a group aoe == 1 and a group st == 0 and enemies >= 2 for Fan of Knives; a group st == 1 and a group aoe == 0 and enemies <= 1 for Mutilate. Garrote and Rupture: a group X.down and a group X.up and X.duration <= 30. Combo points out of 7 in percent, thresholds halfway between steps: "at least 5" is Combo_Points >= 70, "at most 3" is Combo_Points < 50. Energy pooling: Envenom at 5+ combo points when the buff is running out, under Deathmark or at Energy >= 70.
- KRC has no not, != or parentheses: use the pairs up/down, ready/oncd, glow/noglow, inverse operators and several groups.
- Write the labels, rule names and comments in English, and the names used in conditions (alias) in Latin letters.
- Give every rule a name and every group a short comment: what and why.
- At the end check: the JSON passes the schema; every name in the conditions is declared; no more than 16 conditions in a group and 16 groups in a rule; the IDs exist on Wowhead for the current patch.

ANSWER
1. The whole rotation JSON in one block.
2. Macro texts for the macro slots.
3. A table: rule, what it does, where it comes from (which log, guide or APL line).
4. What to turn on in the game: quick settings panel buttons, the enemy counter radius, what to put on the action bars.
5. A test plan: a training dummy, then a combat log against the top logs; which numbers to compare.
```


### How to analyse logs

Hints for the agent and for you: where to get the data and what to compare.

**Warcraft Logs**
- Rankings: `https://www.warcraftlogs.com/zone/rankings/<zone id>?boss=<boss id>&class=<Class>&spec=<Spec>&difficulty=<3|4|5>&metric=dps` (3 normal, 4 heroic, 5 mythic). Mythic+ rankings are split by key level.
- In a player's report: the Casts tab (order and timing of casts), Buffs and Debuffs (uptime), Resources (resources and capping), Damage Done (by ability and target), Deaths.
- Talents from a log: the player's summary has the talent export string; open it in Wowhead's talent calculator.
- Compare behaviour, not DPS: DoT and buff uptime, resources at spenders, casts per minute, time without casts, big cooldown timing relative to the fight's phases.

**Raider.IO** (Mythic+)
- Top runs of a dungeon: `https://raider.io/api/v1/mythic-plus/runs?season=<season>&region=world&dungeon=<slug>&page=0`; seasons and dungeons: `https://raider.io/api/v1/mythic-plus/static-data?expansion_id=<id>`.
- The meta changes week to week: take fresh data, not old conclusions.

**SimulationCraft**
- A specialization's action list (APL) is in the `simulationcraft/simc` repository on GitHub, on the current expansion's branch, in the `ActionPriorityLists` folder.
- How to turn an APL into KRC:

| SimulationCraft APL | KRC |
|---|---|
| `actions` lines, top-down | rules, top-down |
| `buff.X.up` | `X.up` |
| `dot.X.remains`, `buff.X.remains` | `X.duration` in percent |
| `cooldown.X.remains` | `X.cd` in percent |
| `combo_points`, `energy` | resources in percent |
| `active_enemies` | `enemies` |
| `target.time_to_die`, exact seconds | none; the nearest substitute, for example "the target lives 12 more seconds" is `TargetHP >= 50` on 3+ targets |

**The combat log**
- With advanced logging every `SPELL_CAST_SUCCESS` carries the caster's resources (type, current value, maximum); energy is type 3.
- `SPELL_AURA_APPLIED`, `SPELL_AURA_REFRESH`, `SPELL_AURA_REMOVED`: aura uptime and refreshes; `SPELL_ENERGIZE`: resource gains; `SPELL_CAST_FAILED`: presses the game refused.
- Count: casts per minute, gaps between GCDs, DoT uptime, combo points at finishers, energy capping, big cooldown timing.

### Recording a combat log

1. In the game's settings, Network section, turn on Advanced Combat Logging. Without it the log has no resources or positions.
2. Before the pull type `/combatlog` in chat: recording starts. After the fight type `/combatlog` again: recording stops.
3. The file is in the game folder: `World of Warcraft\_retail_\Logs\WoWCombatLog-MMDDYY_HHMMSS.txt`. The game writes it with a delay: wait a minute after you stop recording.
4. The Warcraft Logs uploader may clear the file after uploading: copy it first.

Do the first tests on a training dummy: 2–3 minutes on a single target, then on a group of dummies for AoE.

### Testing and iterating

1. Load the rotation (see below), build and paste the Better Assistant profile, put the slot spells on your bars with keys.
2. Test on a dummy. The Log in the main window shows what is pressed. The KeyRota log file (the folder button next to Log) records, for each GCD, the chosen rule and why the rules above were skipped: conditions, not ready, unknown, manual.
3. Give the agent the rotation JSON, a fresh combat log and, if needed, the KeyRota log. Ask it to compare with the top logs: uptime, resources at spenders, cooldowns, idle time.
4. Change one rule at a time and keep the version in the rotation name ("v1.3"). Keep the old versions: Export in the main window or Text and JSON — Save….
5. After the raid or the key, compare with Warcraft Logs again.

### Loading the result

- **As a file:** save the JSON from the answer as a `.json` file, then Rotation — New — From file….
- **From the clipboard:** copy the JSON, then New — From clipboard.
- **Into an open rotation:** the builder — Text and JSON — JSON — Paste. Slots with the same numbers keep their IDs, flags and visibility if the new JSON has none.

The builder's status bar should say "The rotation is valid". If there's an error, it names the place: give it to the agent word for word. Then General — "Build from slots and auras" — "Copy", in the game `/ba` — Profile — paste — Apply. Create the macros from the answer in the game and put them on your bars.

---

<!-- https://keyrota.app/documentation/en/faq -->

## FAQ and troubleshooting

### A rule never fires

Check in this order:

1. **A rule above always wins.** Rules are checked top-down: if a rule above holds and its spell can be used, yours never gets its turn. Move your rule up or narrow the conditions of the one above.
2. **An unknown value.** A condition on an unknown value is false, even `.down`. An aura is unknown when it isn't in the Better Assistant profile or has a wrong ID. Build the profile again ("Build from slots and auras") and paste it in the game.
3. **The aura's ID, not the spell's.** A buff can have a different ID than the spell that applies it: Vanish is `1856`, its buff `11327`. And the rules see only your auras, not buffs from other players.
4. **The slot isn't ready.** Not enough resource, the target too far, a cooldown, the talent not taken. Such a rule is skipped without an error.
5. **Better Assistant doesn't show the slot.** A With a target slot isn't ready without a target. Use In combat for healing and defensives.
6. **No key.** The slot's spell must be on an action bar with a key bound (or a macro with `#showtooltip`).
7. **A toggle is off.** Healing / defense is off by default, Burst may have been turned off with the Burst key (F3 on Windows, ⌃⇧B on macOS), the target mode may still be Single or Multiple.
8. **Percentages.** 5 combo points is `70`, not `5`. See [Resources](https://keyrota.app/documentation/en/conditions#resources).
9. **Stacks.** A single stack reads as 0: `X.stacks >= 1` can't tell one stack from no aura; use `.up`.
10. **An aura without a duration.** Stealth and the like always read 1 left: `.duration` doesn't work for them.

The KeyRota log file shows which rule was chosen and why the ones above were skipped: the folder button next to Log in the main window.

### It presses too early or too late

| What you see | Cause | What to do |
|---|---|---|
| An off-GCD spell waits for the end of the GCD | No O flag | Turn on Off the GCD (O). |
| An off-GCD button is pressed before the previous hit's result arrives | Flag O presses at once, mid-GCD | Add In the queue window (Q): O + Q. |
| A macro whose second line is on the GCD doesn't go off in full | The macro was pressed mid-GCD | O + Q: the macro is pressed at the end of the GCD. |
| A lower off-GCD rule doesn't overtake a higher one | By design: strict priority | Move the rule up. |
| After spending, the rotation "stands still" | Resource pooling: no rule holds | That's normal; to spend earlier, lower the energy threshold. |
| The rotation presses nothing while you press your own buttons | KeyRota steps aside for your ability presses | That's normal. |

### A channel gets cut

- Turn on Channel (C) for the slot: during the channel KeyRota presses nothing and doesn't press the slot twice at the start.
- The game ends some channels slightly before the last hit, and a press right after the channel's end cuts it. Add the channel's aura to the rotation's auras and the condition `Channel_aura.duration <= 8` to **every group of every rule**. Example: [Channel protection](https://keyrota.app/documentation/en/patterns#channel).

### It doesn't switch to AoE

1. Is the target mode Auto (F2 on Windows, ⌃⇧T on macOS, cycling Auto, Single, Multiple)? In Single mode the rules with `st == 0` stay silent.
2. Are enemy nameplates on in the game (V), with minor enemies not hidden?
3. Is the counter radius on the General page at least the radius of your AoE ability?
4. Only enemies in combat: enemies not yet in combat aren't counted. With packs that come in one by one, the count can be lower.
5. The rule's threshold: `enemies >= 2` or `>= 3`?
6. After changing the radius, build and paste the Better Assistant profile again.

### Burst doesn't fire, or fires at the wrong time

- `cd == 1` is **the Burst button** (F3 on Windows, ⌃⇧B on macOS, the Burst button on the panel), not a spell's cooldown. A spell's cooldown is `Slot.cd`.
- Burst is on by default. Turn it off before a moment when the cooldown must not be spent (immunity, a phase change) and back on when it's time.
- If a big cooldown depends on auras (DoTs on the target), it waits for them. Check in the KeyRota log which condition of the rule doesn't hold.
- For things that must go only under the cooldown, check the cooldown's aura (`Deathmark_DoT.up`), not the Burst button.

### How to count percentages

- **Resources and health:** `value / maximum × 100`. 175 energy out of 250 is `70`.
- **Combo points out of 7:** put the threshold halfway between steps: "at least 5" is `>= 70`, "at most 3" is `< 50`. [The table](https://keyrota.app/documentation/en/conditions#resources).
- **Aura time left:** a percentage of that application's duration. 30% is the pandemic window.
- **Cooldown:** a percentage of the full cooldown without the GCD. For a 2-minute cooldown 1% is 1.2 s.

### "A rotation with an error can't be saved"

The builder doesn't save a rotation with an error: KeyRota never runs a rotation partially. The status bar names the place, for example "Rule 9, group 1: the group has no conditions". Click Show to open the rule. Common causes:

| Message | What to do |
|---|---|
| the group has no conditions | Add a condition or delete the empty group. |
| no slot selected | Choose a slot in Press. |
| Q flag only together with O | Turn on Off the GCD (O) or turn Q off. |
| unknown name … | A name in a condition matches no aura, slot or resource: check Name in text. |
| … is a slot, expected a buff | An aura property (`.up`, `.duration`) on a slot, or a slot property (`.ready`, `.cd`) on an aura. |
| quotes aren't allowed in the name | Remove `"` from the rotation, slot or aura name. |

Every message is in [Errors](https://keyrota.app/documentation/en/rotation-json#errors).

### How `.oncd` differs from `.cd > 0`

`X.oncd`: the slot is not ready for **any** reason: a cooldown, no resource, the target too far, the slot not shown. `X.cd > 0`: a cooldown is running. For a spell with charges `.cd > 0` is possible while the slot is ready: a charge is available, the next one is recharging.

### How to write "not" and parentheses

KRC has no `not`, `!=` or parentheses. "Not" is the paired condition (`up`/`down`, `ready`/`oncd`, `glow`/`noglow`) or the inverse operator (instead of "not `>= 70`" write `< 70`). "(A or B) and C" is two groups: `A and C`, `B and C`. More: ["Not", parentheses and OR](https://keyrota.app/documentation/en/conditions#logic).

### More than 12 slots or auras?

No: a rotation has up to 12 slots and up to 12 auras, like the Better Assistant profile. Macros help save room (one button, several spells), and one slot can appear in several rules. The number of rules is unlimited.

### A rotation with a talent you don't have

If a talent isn't taken, the spell isn't learned, the slot isn't ready, and its rules are simply skipped. One rotation can work with different talent builds: the rules for missing spells stay silent.

### Moving or sharing a rotation

- Export in the main window saves the profile with its rotation; Import loads it on another computer.
- In the builder, Text and JSON — Save… saves the rotation as `.json` or `.txt`; to open it, New — From file….
- The Better Assistant profile is rebuilt from the rotation's slots and auras: no need to pass it on separately.

### Why KeyRota sometimes presses nothing

With Ignore main icon on, when no rule fits KeyRota waits. In rotations that pool energy that's normal: at 5+ combo points the spender waits for its condition and the builder stays silent. If the wait looks wrong, check in the KeyRota log which rules were skipped and why.
