Skip to content

feat(parser): Vraska, Betrayal's Sting -2 become-Treasure and -9 poison-to-difference#5403

Merged
matthewevans merged 1 commit into
mainfrom
ship/vraska-betrayals-sting
Jul 9, 2026
Merged

feat(parser): Vraska, Betrayal's Sting -2 become-Treasure and -9 poison-to-difference#5403
matthewevans merged 1 commit into
mainfrom
ship/vraska-betrayals-sting

Conversation

@matthewevans

Copy link
Copy Markdown
Member

Both loyalty abilities parsed to Unimplemented.

[-9] 'If target player has fewer than nine poison counters, they get a number of
poison counters equal to the difference': split_leading_conditional severed the
threshold from the body, and the body's 'a number of ... equal to the difference'
had no literal count for try_parse_player_counter. A new whole-sentence oracle_nom
rider (player_counter_difference) dispatched in parse_effect_chain_ir BEFORE the
conditional split captures the threshold N and emits GivePlayerCounter with a
ClampMin{0, Offset{9, Multiply{-1, Ref(TargetControllerCounter{Poison})}}} count =
max(0, 9 - current), bound to the same chosen target player. condition:None — the
clamp encodes the 'if fewer than' gate (>=9 -> 0 -> no-op). parse_player_counter_kind
is consolidated into oracle_nom/primitives.rs as the single kind authority.

[-2] 'Target creature becomes a Treasure artifact with {T},Sac:Add any color and
loses all other card types and abilities': ordered ContinuousModification set
(SetCardTypes[Artifact], RemoveAllSubtypes{Creature}+{Artifact} BEFORE AddSubtype
so Treasure survives per CR 613.7a written-order, RemoveAllAbilities before the
granted ability per CR 613.8a).

No new engine variants. CR 107.1b/122.1/122.1f/205.1a/613.1d/613.1f/613.7a/613.8a.

…on-to-difference

Both loyalty abilities parsed to Unimplemented.

[-9] 'If target player has fewer than nine poison counters, they get a number of
poison counters equal to the difference': split_leading_conditional severed the
threshold from the body, and the body's 'a number of ... equal to the difference'
had no literal count for try_parse_player_counter. A new whole-sentence oracle_nom
rider (player_counter_difference) dispatched in parse_effect_chain_ir BEFORE the
conditional split captures the threshold N and emits GivePlayerCounter with a
ClampMin{0, Offset{9, Multiply{-1, Ref(TargetControllerCounter{Poison})}}} count =
max(0, 9 - current), bound to the same chosen target player. condition:None — the
clamp encodes the 'if fewer than' gate (>=9 -> 0 -> no-op). parse_player_counter_kind
is consolidated into oracle_nom/primitives.rs as the single kind authority.

[-2] 'Target creature becomes a Treasure artifact with {T},Sac:Add any color and
loses all other card types and abilities': ordered ContinuousModification set
(SetCardTypes[Artifact], RemoveAllSubtypes{Creature}+{Artifact} BEFORE AddSubtype
so Treasure survives per CR 613.7a written-order, RemoveAllAbilities before the
granted ability per CR 613.8a).

No new engine variants. CR 107.1b/122.1/122.1f/205.1a/613.1d/613.1f/613.7a/613.8a.
@matthewevans matthewevans enabled auto-merge July 9, 2026 01:13

@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 implements parsing and logic for Vraska, Betrayal's Sting's loyalty abilities, specifically adding a threshold player-counter top-up rider for the [-9] ability and a full-replacement animation parser for the [-2] ability, alongside corresponding integration tests. Feedback highlights a critical omission where the new player_counter_difference::try_parse is not wired into the main parser dispatch, a medium issue regarding incomplete subtype removal for non-creature/non-artifact card types under CR 205.1a, and a parser limitation failing to handle the singular 'counter' form.

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.

pub mod enchant;
pub mod error;
pub mod filter;
pub mod player_counter_difference;

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.

critical

[CRITICAL] Missing wiring for player_counter_difference::try_parse.\n\nWhy it matters: The new player_counter_difference module is declared, but its try_parse function is never actually called or dispatched in parse_effect_chain_ir (or any other parser file) in this PR. Without this wiring, the [-9] ability will still parse to Unimplemented, and the integration tests will fail to compile or run correctly.\n\nSuggested fix: Ensure the modified file containing parse_effect_chain_ir (likely crates/engine/src/parser/oracle_effect/mod.rs or chain.rs) is committed and includes the dispatch to player_counter_difference::try_parse before the conditional split.

Comment on lines +881 to +886
result.push(ContinuousModification::RemoveAllSubtypes {
set: SubtypeSet::Creature,
});
result.push(ContinuousModification::RemoveAllSubtypes {
set: SubtypeSet::Artifact,
});

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.

medium

[MEDIUM] Incomplete subtype removal for non-creature/non-artifact card types.\n\nWhy it matters: If the target creature has other card types (such as an Enchantment Creature or an animated Planeswalker/Land) and becomes a "Treasure artifact", it loses those other card types but will incorrectly retain their associated subtypes (e.g., Enchantment or Planeswalker subtypes) because RemoveAllSubtypes is only applied to Creature and Artifact.\n\nSuggested fix: Add RemoveAllSubtypes for other potential subtype sets (like Enchantment, Planeswalker, and Land) to ensure full compliance with CR 205.1a when losing those card types.

References
  1. Strict fidelity to the MTG Comprehensive Rules (CR) requires that every game rule, validation, and computed value matches the CR exactly (CR 205.1a). (link)

Comment on lines +70 to +72
let (rest, _) = tag::<_, _, OracleError<'_>>(" counters, they get a number of ")
.parse(rest)
.ok()?;

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.

medium

[MEDIUM] Singular counter form is not handled.\n\nWhy it matters: If the threshold is exactly one (e.g., "fewer than one poison counter"), the text will use the singular "counter" instead of "counters", causing the hardcoded " counters, they get a number of " tag to fail to parse.\n\nSuggested fix: Use alt to support both singular and plural forms. Also, propagate the parser error directly instead of swallowing it with .ok()?.

Suggested change
let (rest, _) = tag::<_, _, OracleError<'_>>(" counters, they get a number of ")
.parse(rest)
.ok()?;
let (rest, _) = alt((
tag::<_, _, OracleError<'_>>(" counters, they get a number of "),
tag(" counter, they get a number of "),
))
.parse(rest)?;
References
  1. Universal Review Lenses L2 (Sibling coverage) requires checking if plural/singular/possessive/negated variants are covered when extending parser arms or strings. (link)
  2. Avoid swallowing all errors (e.g., using .ok()) when only specific errors are expected to be handled or ignored. Propagate unexpected errors (such as invariant violations or invalid actions) to prevent masking critical bugs.

@matthewevans matthewevans added this pull request to the merge queue Jul 9, 2026
@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown

Parse changes introduced by this PR · 1 card(s), 4 signature(s) (baseline: main 662b02b67d86)

1 card(s) · ability/GivePlayerCounter · added: GivePlayerCounter (count=max(-1*# of poison counters its controller has+9, 0), counter=Poison, kind=activated, target=player, timing=sorcery speed)

Examples: Vraska, Betrayal's Sting

1 card(s) · ability/become · removed: become (kind=activated, timing=sorcery speed)

Examples: Vraska, Betrayal's Sting

1 card(s) · ability/get · removed: get (kind=activated, timing=sorcery speed)

Examples: Vraska, Betrayal's Sting

1 card(s) · ability/set card types artifact, remove all Creature subtypes, remove all Artifact subt… · added: set card types artifact, remove all Creature subtypes, remove all Artifact subtypes, add subtype Treasure, remove all abilities, grant ability (affects=parent …

Examples: Vraska, Betrayal's Sting

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

Merged via the queue into main with commit 694febd Jul 9, 2026
13 checks passed
@matthewevans matthewevans deleted the ship/vraska-betrayals-sting branch July 9, 2026 01:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant