Skip to content

feat(parser): compound negated-type subject animation with granted abilities (Bello, Bard of the Brambles)#5444

Merged
matthewevans merged 5 commits into
phase-rs:mainfrom
jsdevninja:feat/bello-compound-negated-type-subject-animation
Jul 9, 2026
Merged

feat(parser): compound negated-type subject animation with granted abilities (Bello, Bard of the Brambles)#5444
matthewevans merged 5 commits into
phase-rs:mainfrom
jsdevninja:feat/bello-compound-negated-type-subject-animation

Conversation

@jsdevninja

Copy link
Copy Markdown
Contributor

Summary

Fixes #5442. Bello, Bard of the Brambles' static —

"During your turn, each non-Equipment artifact and non-Aura enchantment you control with mana value 4 or greater is a 4/4 Elemental creature in addition to its other types and has indestructible, haste, and "Whenever this creature deals combat damage to a player, draw a card.""

(Oracle text verified against the Scryfall API) — previously strict-failed (Unimplemented("static_structure"), data/card-data.json); the whole line collapsed into a gap.

Same structural shape as #5219 (Life and Limb's compound-subject animation static): a compound subject joined by "and" where each conjunct is independently resolvable and the predicate class is independently resolvable elsewhere, but the combination was unreached. Bello's variant differs from #5219's own fix (parse_compound_all_subjects_type_change) in two ways that make it a distinct class, not a duplicate:

  • Subject grammar: Life and Limb repeats the quantifier per conjunct ("All <X> and all <Y>", plural "are"); Bello has a single shared quantifier and per-conjunct negated-type exclusions ("each non-Equipment artifact and non-Aura enchantment", singular "is") with a trailing qualifier (you control, with mana value 4 or greater) that applies to both conjuncts despite appearing once, after the second.
  • Predicate: alongside the type change, Bello grants a mixed bare-keyword + quoted-trigger list ("indestructible, haste, and "..."") — a combination the shared additive-type-clause helper only partially supported (quoted portion only).

Fix

parse_each_compound_subject_type_change (crates/engine/src/parser/oracle_static/type_change.rs), dispatched in parse_static_line_inner right after its closest sibling (parse_each_noncreature_subject_is_creature_with_pt_mv):

  • Subject: delegates wholesale to the general target-phrase grammar (parse_type_phrase) instead of hand-rolling a conjunct splitter. That grammar already recurses per "and"-leg — restarting its own leading non- scan on each recursive call (starts_with_type_word's non- arm) — and backfills the shared trailing qualifiers (controller, mana value) from the last leg onto every earlier leg via distribute_controller_to_or / distribute_properties_to_or. Declines (via a 2+-leg Or-of-Typed guard) unless the subject is a genuine compound, so single-subject lines keep falling through to their existing dispatcher unchanged.
  • Predicate: composes parse_animation_spec (base P/T + leading type/subtype grant) with parse_additive_type_clause_modifications (any extra type noun the animation spec stops short of), then extracts the granted-ability tail locally — reusing the same split_keyword_list + push_grant_clause_modifications + parse_quoted_ability_modifications composition parse_continuous_modifications already uses elsewhere — so a mixed bare-keyword/quoted-trigger list is captured in full. Kept local to the new function rather than folded into the shared additive-type-clause helper, which has several other call sites this change should not risk perturbing.
  • Gated on the CR 205.1b additive marker ("in addition to its/their other types"), mirroring feat(parser): compound-subject animation static — Life and Limb (CR 611.3) #5219's own review-fix, so a bare replacement compound isn't misclaimed as additive.

I cross-checked this design against #5406 (Rukarumel, cited in #5442 as the structurally closest precedent): that PR's subject-delegation target (parse_continuous_subject_filter) requires every conjunct to independently carry its own controller anchor ("Slivers you control and nontoken creatures you control", repeated per leg) — which does not fit Bello's shared, non-repeated "you control". parse_type_phrase's distribution machinery is the correct fit here.

CR references (grep-verified against docs/MagicCompRules.txt)

  • CR 611.3 / CR 611.3a — a continuous effect from a static applies at any moment to whatever its text indicates (the compound Or-distributed affected set; the During your turn, window).
  • CR 613.1d — Layer 4 type-changing effects.
  • CR 613.4b — Layer 7b base power/toughness.
  • CR 205.1b — additive type/subtype grants ("in addition to its other types").
  • CR 604.1 / CR 603.1 — the granted quoted ability is a triggered ability (GrantTrigger).
  • CR 702.10 / CR 702.12 — Haste / Indestructible.

Testing

Environment note: this sandbox has no Tilt, no MSVC Build Tools, and no WSL/Docker, so I could not run cargo build/test/clippy locally. I verified as much as this environment allows:

  • cargo fmt --all -- --check — clean.
  • ./scripts/check-parser-combinators.sh upstream/main — clean (no forbidden string-dispatch patterns in the added lines).
  • Every helper function's signature, behavior, and call-site composition was traced by reading source directly (not from memory) — including manually tracing parse_type_phrase's recursive per-leg negation handling and its distribute_*_to_or backfill functions line-by-line to confirm the compound-subject + shared-qualifier distribution actually produces the intended Or shape.
  • This PR has not been compiled or test-run. Please treat cargo test -p engine/Tilt's test-engine as the first real verification pass before merge.

New tests (oracle_static/tests.rs):

  • bello_compound_negated_type_subject_animation_with_granted_abilities — full Oracle text: Or-of-2 negated-type legs (non-Equipment artifact / non-Aura enchantment, both you control + Cmc >= 4), DuringYourTurn condition, base P/T, type/subtype grant, both bare keywords, and the granted trigger.
  • compound_negated_type_subject_animation_declines_non_additive_predicate — the CR 205.1b gate declines a compound predicate missing the additive marker.
  • compound_negated_type_subject_animation_single_subject_falls_through — a single-subject line is not claimed by the compound handler.

Scope

Parser-only (crates/engine/src/parser/oracle_static/{type_change,dispatch,tests}.rs) — no engine types/runtime changes; SetPower/SetToughness/AddType/AddSubtype/AddKeyword/GrantTrigger are all pre-existing, already-wired modifications.

🤖 Generated with Claude Code

…ilities (Bello, Bard of the Brambles)

"During your turn, each non-Equipment artifact and non-Aura enchantment you
control with mana value 4 or greater is a 4/4 Elemental creature in addition
to its other types and has indestructible, haste, and \"Whenever this
creature deals combat damage to a player, draw a card.\"" was fully dropped
(Unimplemented "static_structure") — no handler recognized the heterogeneous
compound subject (a non-Equipment artifact union'd with a non-Aura
enchantment), and the additive-type-clause helper's granted-ability tail only
extracted the quoted trigger, silently dropping bare keywords listed
alongside it.

parse_each_compound_subject_type_change (crates/engine/src/parser/oracle_static/type_change.rs)
delegates the subject wholesale to the general target-phrase grammar
(parse_type_phrase), which already recurses per "and"-leg and backfills
shared trailing qualifiers (controller, mana value) onto every leg, then
composes the predicate from parse_animation_spec (base P/T + leading
type/subtype) + parse_additive_type_clause_modifications (any extra type
noun) + a granted-ability-tail extraction (kept local to this function, not
folded into the shared helper) that applies the same split_keyword_list +
push_grant_clause_modifications + parse_quoted_ability_modifications
composition parse_continuous_modifications already uses elsewhere, so a
mixed bare-keyword/quoted-trigger list is captured in full.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@jsdevninja jsdevninja requested a review from matthewevans as a code owner July 9, 2026 14:56

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for parsing compound-subject continuous animation effects with negated-type legs and mixed granted abilities, specifically targeting cards like Bello, Bard of the Brambles. It adds the parse_each_compound_subject_type_change parser in type_change.rs, integrates it into the static line parser dispatch, and includes comprehensive unit tests verifying correct parsing, additive semantics, and fall-through behavior. The implementation adheres to the repository's architectural guidelines, utilizing existing parser helpers and providing appropriate Magic Comprehensive Rules (CR) annotations. I have no feedback to provide as the changes are highly idiomatic and well-tested.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

…gger

QuantityExpr::Fixed is a struct variant ({ value: i32 }), not a tuple
variant — CI caught the tuple-call-syntax typo in the new Bello test
(E0533). Separately, a doc-comment line wrapped right at "P/T + type
change", making "+ type change ..." look like a line-leading markdown
list bullet to clippy::doc_lazy_continuation; rewrapped so no comment
line starts with +/-/*/N.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown

Parse changes introduced by this PR · 1 card(s), 2 signature(s) (baseline: main 6e2cbb2c4e1f)

1 card(s) · static/Continuous · added: Continuous (affects=mv 4+ you control artifact non-Equipment or mv 4+ you control enchantment non-Aura, conditional=during your turn, mods=base power 4, base t…

Examples: Bello, Bard of the Brambles

1 card(s) · ability/static_structure · removed: static_structure

Examples: Bello, Bard of the Brambles

2 card(s) had Oracle-text changes (errata/reprint) — excluded as non-parser.

@matthewevans matthewevans left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MED] The Bello subject filter cross-contaminates one conjunct's negated subtype into the other. Evidence: crates/engine/src/parser/oracle_static/type_change.rs:1866 delegates the whole subject to parse_type_phrase, and the parse-diff sticky for this head reports Bello as artifact non-Equipment or ... enchantment non-Aura non-Equipment; Oracle only says non-Aura enchantment. The new test at crates/engine/src/parser/oracle_static/tests.rs:4111 asserts the enchantment leg contains Non(Aura) but never asserts it does not also contain Non(Equipment), so this regression is not pinned. Why it matters: Bello would incorrectly exclude any qualifying enchantment that also has the Equipment subtype or otherwise carries that subtype in a type-changing edge case, and the parser output no longer matches the card text. Suggested fix: split/parse the two subject legs so each leg keeps only its own non-* qualifier while shared you control / mana-value constraints are still distributed, and add negative assertions for both legs.

@matthewevans matthewevans self-assigned this Jul 9, 2026
@matthewevans matthewevans added the enhancement New feature or request label Jul 9, 2026

@matthewevans matthewevans left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Parser change reviewed against current head: prior negated-subtype finding is resolved, parse-diff is limited to Bello, and required checks are green.

@matthewevans matthewevans added this pull request to the merge queue Jul 9, 2026
@matthewevans matthewevans removed their assignment Jul 9, 2026
Merged via the queue into phase-rs:main with commit 30b42f2 Jul 9, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

parser: compound-subject animation static strict-fails (Bello, Bard of the Brambles)

2 participants