Skip to content

engine: an observed activation cannot say "you" #823

Description

@ninthworld

Goal

A card that says whenever you activate an ability watches only its controller's
activations, so Sarkhan's Whelp stops firing on an opponent's Sarkhan.

Context and evidence

Parent: #819.

ActivatorScope in crates/sage-engine/src/ability/trigger.rs has exactly two members:

pub enum ActivatorScope {
    /// Any player's activation, the watcher's own controller included.
    #[default]
    Any,
    /// Only an opponent of the watcher's controller …
    Opponents,
}

Sarkhan's Whelp prints Whenever you activate an ability of a Sarkhan planeswalker, this
creature deals 1 damage to any target.
There is no scope for "you", so
sarkhan_s_whelp.json is authored "activator": "any" — and an opponent activating their own
Sarkhan fires your Whelp.

This is the eighteenth wrong card in the audit, and the only one that is a mechanic
finding rather than an authoring one: the vocabulary offers no correct answer, so no amount of
care in the JSON would have got it right.

exclusions.json does not name the gap. Its nearest entry — "Abilities that trigger on a
loyalty ability specifically"
— asserts in its blocker text that the activation filter
covers "who activated it", which is what makes the missing scope invisible. Per
crates/sage-engine/AGENTS.md, that file "claims to name every mechanic considered and left
out of scope, so a gap that is neither built nor named makes it false."

Building it is the right fix rather than naming it, and that is also the direction #791 is
taking the exclusion registry as a whole. It is a one-variant change: ActivatorScope is a
small Copy enum consumed in one predicate.

Related: #776 item 7 notes the adjacent gap — nothing filters an observed activation by
whether it was a loyalty ability. Distinct question, same enum, and worth resolving in the
same read even if it is left to the card that needs it.

Scope

  • ActivatorScope gains the controller-only scope, documented alongside Any and Opponents
    and scoped from the same "you" every other trigger scope is written from.
  • The predicate that reads it follows; it is wildcard-free, so the compiler names the site.
  • sarkhan_s_whelp.json is corrected.
  • The server's rules-text formatter renders the new scope.
  • exclusions.json: the loyalty entry's blocker text no longer implies the activator filter is
    complete. Coordinate with Remove exclusions.json and prohibit card/mechanic denylists #791 — if the registry is being removed, this is a deletion rather
    than an edit.

Acceptance criteria

  • An opponent activating a Sarkhan planeswalker they control does not fire
    sarkhan_s_whelp.
  • The controller activating a Sarkhan planeswalker they control does fire it, once per
    activation.
  • A mana ability of a Sarkhan still fires nothing — the existing structural exclusion
    (a mana ability never reaches the stack, CR 605.3a) is unaffected.
  • The rendered rules text for sarkhan_s_whelp says "you", not "a player".
  • exclusions.json no longer asserts the activator filter is complete.
  • make compat regenerated; make verify green.

Test and validation evidence

  • An engine test with a Sarkhan on each side, activating both, asserting one fire.
  • A rules-text test for the new scope's rendering.
  • make compat drift check; make verify.

Dependencies and sequencing

Independent of every other child of #819 and small enough to land at any point. Coordinate
with #791 on the exclusions.json half only.

Estimated scope

Small — localized change: one enum variant, one predicate arm, one formatter arm, one JSON
file.

Risks and constraints

None material. The scope is Copy, closed, and read in one place.

Non-goals

Not the loyalty-ability filter of #776 item 7 — related, and fine to do here if it is free, but
it blocks no card. Not a general rework of the trigger vocabulary.

Documentation impact

docs/card-schema.md if the trigger vocabulary's scopes are enumerated there.
exclusions.json as above. No ADR.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions