Skip to content
Back to skills

Trigger Driven Abilities

ASecurity

Battle ability implementation pattern: when an ability's effect does not override or mutate the detection event's properties, split it into a detection listener that fires triggerAbility and an effect listener on UnitTriggerAbility. Applies whenever writing or refactoring abilities in src/battle/abilities.

  • 5 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added September 20, 2026
code-qualityrefactoring

Security analysis

A100/100

Scanned September 20, 2026

npx -y skills add lxsmnsyc/overwander --skill trigger-driven-abilities --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Trigger Driven Abilities?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Trigger Driven Abilities
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/lxsmnsyc-trigger-driven-abilities/badge)](https://www.skillsdirectory.com/skills/lxsmnsyc-trigger-driven-abilities)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: trigger-driven-abilities
description: >
  Battle ability implementation pattern: when an ability's effect does
  not override or mutate the detection event's properties, split it
  into a detection listener that fires triggerAbility and an effect
  listener on UnitTriggerAbility. Applies whenever writing or
  refactoring abilities in src/battle/abilities.
---

When implementing an ability, separate _detection_ from _effect_ whenever the effect stands on its own — i.e. it does not read or mutate the event that detected the activation.

## The pattern

```ts
createAbility(
  Abilities.Example,
  (battle) =>
    new MergedAbilityLifecycle([
      // Detection: guards live here; fire the trigger and nothing else
      battle.on(BattleEvents.SomeEvent, EventPriority.Post, (event) => {
        if (/* activation conditions */) {
          event.source.triggerAbility(Abilities.Example);
        }
      }),
      // Effect: rides the trigger at Exact priority
      battle.on(BattleEvents.UnitTriggerAbility, EventPriority.Exact, (event) => {
        if (event.ability === Abilities.Example) {
          // apply the effect to event.source (or closure state)
        }
      }),
    ]),
),
```

## Rules

- Effect listeners on `UnitTriggerAbility` use `EventPriority.Exact` — the trigger's canonical resolution. Post stays free for observers (visual layer, tests).
- The effect may only use `event.source`, `event.ability`, and the ability's closure state (e.g. Flash Fire's `activated` set). Chance rolls and condition guards belong in detection.
- `Unit.triggerAbility` already checks the unit has the ability, so the effect listener needs only the ability-id match.
- A lifecycle wrapper around a **single** listener is noise: return the `battle.on(...)` itself. `MergedLifecycle` is for two or more, or for a spread whose length is not known at the call site.
- Self-boosts guard against re-triggering naturally when the detection condition can't match the effect (e.g. Defiant detects negative stage deltas; its own boost is positive).

## Field-presence abilities

When the effect depends on "any holder currently on the field" (Unnerve, Cloud Nine, Damp), keep a closure `Set` of on-field holders instead of scanning every unit in the hot path:

- `UnitEntersField` adds the holder (and fires the entry cue if any).
- `UnitLeavesField`, `UnitFaints`, and a matching `UnitRemoveAbility` remove it.
- The suppression/effect check reduces to `holders.size > 0` (or a per-team lookup, as Unnerve does with its team status).
- When the suppression fires a cue, it fires for **every** holder in the set, never just the first one.

## When to stay inline

- The effect **mutates the detection event** (Shield Dust disabling the attack effect, Run Away setting escape success, Inner Focus vetoing a stage drop, Arena Trap blocking escape).
- The effect **requires context the trigger event cannot carry** (Static/Poison Point/Cute Charm/Effect Spore/Stench afflict the attacker; Pickup needs the item id; Synchronize reflects onto the inflicter). Do not stash such context in closure state to force the pattern — keep the effect inline.
- One ability id covers **multiple distinct effect contexts** (Dry Skin's water absorb vs rain heal vs sun chip) — a single trigger listener cannot tell which fired.

In those cases, fire `triggerAbility` purely as the visual cue at the point of effect, and keep the cue off speculative check events (use real-attempt events like `UnitTriggerMoveFailed` / `UnitAddStatusFailed`).

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…