Skip to content
15 changes: 13 additions & 2 deletions crates/engine/src/parser/oracle_effect/subject.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2836,13 +2836,24 @@ pub(super) fn parse_subject_application(
// In trigger effects: "they" refers to the triggering player (for player-type
// subjects like "an opponent") or the triggering source (for object subjects).
// Outside trigger context: anaphoric reference to previously mentioned objects.
if lower == "they" {
// CR 608.2d: an optional "may" modal parallels the "that player may " /
// "the player may " forms above — "they may pay {2}" (Wandering Archaic,
// Umbilicus) is the pronoun-subject counterpart of "that player may pay
// {2}" (Smothering Tithe, Mind Whip); both must set `is_optional` so
// `lower_subject_predicate_ast` marks the lowered ability optional and
// `resolve_they_pronoun`'s existing player/object dispatch is unchanged.
if let Ok((_, is_optional)) = all_consuming(alt((
value(true, tag::<_, _, OracleError<'_>>("they may")),
value(false, tag("they")),
)))
.parse(lower.as_str())
{
return Some(SubjectApplication {
affected: resolve_they_pronoun(ctx),
target: None,
multi_target: None,
inherits_parent: false,
is_optional: false,
is_optional,
});
}

Expand Down
55 changes: 55 additions & 0 deletions crates/engine/src/parser/oracle_trigger_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21888,6 +21888,61 @@ fn smothering_tithe_that_player_pays_as_triggering_player() {
}
}

/// CR 608.2k + CR 608.2d (issue #6477): the bare-pronoun counterpart of
/// `smothering_tithe_that_player_pays_as_triggering_player` — "they may pay"
/// anaphors back to "an opponent" from the trigger condition and must resolve
/// identically to the explicit "that player may pay" phrasing: the opponent
/// who cast the spell pays (not the Wandering Archaic controller), and the
/// payment is optional so a decline can gate the copy.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
#[test]
fn wandering_archaic_they_pay_as_triggering_player() {
let def = parse_trigger_line(
"Whenever an opponent casts an instant or sorcery spell, they may pay {2}. If they don't, you may copy that spell. You may choose new targets for the copy.",
"Wandering Archaic",
);

assert_eq!(def.mode, TriggerMode::SpellCast);
let execute = def.execute.as_ref().expect("should have execute");
match &*execute.effect {
Effect::PayCost {
payer,
cost: AbilityCost::Mana { cost },
..
} => {
assert_eq!(
payer,
&TargetFilter::TriggeringPlayer,
"the opponent who cast the spell pays, not the Wandering Archaic controller"
);
assert_eq!(cost, &crate::types::mana::ManaCost::generic(2));
}
other => panic!("expected PayCost, got: {other:?}"),
}
assert!(execute.optional, "they may pay should be optional");

let sub = execute
.sub_ability
.as_ref()
.expect("copy should remain chained");
assert_eq!(
sub.condition,
Some(AbilityCondition::Not {
condition: Box::new(AbilityCondition::effect_performed())
}),
"the copy is gated on the opponent having declined payment"
);
assert!(sub.optional, "you may copy that spell");
match &*sub.effect {
Effect::CopySpell {
target, retarget, ..
} => {
assert_eq!(target, &TargetFilter::TriggeringSource);
assert_eq!(retarget, &CopyRetargetPermission::MayChooseNewTargets);
}
other => panic!("expected CopySpell sub_ability, got: {other:?}"),
}
}

/// CR 603.4: Wedding Ring — "an opponent who controls F draws
/// a card" parses the relative clause into an `ObjectCount >= 1`
/// intervening-if scoped to the triggering player, ANDed with the
Expand Down
Loading