Skip to content

feat(parser+engine): continuous filter-scoped keyword-cost grants (Scavenge/Encore/Flashback/Embalm/Foretell) don't support cost deltas or hand zone (Varolz, Dream Devourer, Bohn, ...) #5045

Description

@jsdevninja

Summary

The engine already has the building blocks for granting a keyword ability whose cost is the recipient's own mana cost — ManaCost::SelfManaCost plus ContinuousModification::AddKeyword — used today for one-shot single-target grants like Snapcaster Mage's flashback trigger. It also has a continuous, filter-scoped variant of that same grant for a handful of graveyard keywords (GraveyardGrantedKeywordKind in parser/oracle_static/keyword_grant.rs + oracle.rs's parse_graveyard_keyword_continuation), covering Flashback/Escape/Mayhem/Scavenge/Encore.

That continuous-grant class stops short of real, tournament-legal cards in three ways:

  1. No cost-delta support. ManaCost::SelfManaCost only represents "equal to its mana cost" — there's no way to express "equal to its mana cost reduced by {2}", which several real cards need.
  2. Graveyard-only. The combinator hard-requires the affected filter to be the controller's graveyard (target_filter_is_your_graveyard). Hand-zone grants (Foretell) aren't recognized at all.
  3. Missing keywords. Embalm isn't in GraveyardGrantedKeywordKind even though the runtime resolver (resolve_self_cost_graveyard_activated_keyword) already handles it.

There's also a latent runtime gap: foretell_cost/can_foretell_card (game/casting.rs) read obj.keywords directly instead of going through the off-zone-aware effective_off_zone_keyword path that graveyard keywords already use — so even a correctly-parsed continuous Foretell grant on a hand card would silently never be castable.

Real cards affected (verified against data/mtgjson/AtomicCards.json)

Card Legality Zone Keyword Cost formula
Varolz, the Scar-Striped Commander/Legacy/Modern/Pioneer/Vintage Graveyard Scavenge mana cost
Young Deathclaws Commander/Legacy/Vintage Graveyard Scavenge mana cost
Wire Surgeons Commander/Legacy/Vintage Graveyard Encore mana cost
Return the Past Commander/Legacy/Vintage Graveyard Flashback mana cost (during your turn only)
Bohn, Beguiling Balladeer Commander/Legacy/Vintage Hand Foretell mana cost reduced by {2}
Dream Devourer Modern/Pioneer/Commander/Standard-legal formats Hand Foretell mana cost reduced by {2}
Naktamun (Plane) Planechase Graveyard Embalm mana cost
The Cave of Skulls (Plane) Planechase Graveyard Scavenge mana cost
Singing Towers of Darillium (Plane) Planechase Hand Foretell mana cost reduced by {2}

Varolz and Dream Devourer are real constructed/Commander staples — this isn't an edge-case cluster.

Proposed fix (building-block, not per-card)

  • Add ManaCost::SelfManaCostAdjusted { delta: i32 } alongside the existing SelfManaCost/SelfManaValue markers (CR 601.2f: generic mana adjusted, floored at {0}), resolved at the single existing choke point resolve_keyword_mana_cost (game/keywords.rs).
  • Extend GraveyardGrantedKeywordKind with Embalm (CR 702.128a) — the runtime resolver already supports it, this is a pure parser gap.
  • Add a hand-zone counterpart for Foretell (CR 702.143a/d), reusing the already-generic FilterProp::WithoutKeywordKind for the "without foretell" exclusion clause that's already a first-class filter predicate.
  • Extend the shared cost-suffix combinator to recognize "reduced by {N}" / "minus {N}" (CR 601.2f cost reduction).
  • Fix foretell_cost/can_foretell_card to resolve through effective_off_zone_keyword instead of reading obj.keywords directly, so continuously-granted Foretell on a hand card is actually castable.
  • Add a genuine runtime test proving a filter-scoped (not SpecificObject) continuous grant activates correctly in both hand and graveyard zones — today only single-target grants are tested end-to-end.

This is scoped to keep each card's mechanic within its own CR section (Scavenge/Encore/Flashback/Embalm are all CR 702.x graveyard-cast activated abilities; Foretell is CR 702.143 hand-zone) — no cross-section unification.

Fossilize (Sue, Everlasting Dinosaur) and Madness (Falkenrath Gorger's "isn't on the battlefield" multi-zone filter) are deliberately out of scope: Fossilize has no Comprehensive Rules entry to annotate against, and Falkenrath Gorger's zone scope is broader than hand+graveyard.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions