Skip to content

Add support for 702.190b with Sneak'ed creatures entering tapped and attacking#14960

Open
muz wants to merge 6 commits into
magefree:masterfrom
muz:702_190b_sneak_tapped_and_attacking
Open

Add support for 702.190b with Sneak'ed creatures entering tapped and attacking#14960
muz wants to merge 6 commits into
magefree:masterfrom
muz:702_190b_sneak_tapped_and_attacking

Conversation

@muz
Copy link
Copy Markdown
Contributor

@muz muz commented May 29, 2026

Linked to #14006
Fixes #14895

702.190b A permanent spell cast using sneak enters the battlefield tapped and attacking (see rule
506.3a). It will be attacking the same player, planeswalker, or battle as the creature that was
returned to its owner’s hand to pay the sneak cost of the spell that became that permanent.

Copy link
Copy Markdown
Member

@JayDi85 JayDi85 left a comment

Choose a reason for hiding this comment

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

Whole code is so messy with many hacks. Smells like a wrong implementation. Need deeply research for other possible solutions. Maybe game engine improves, but not such card’s code. Will return later here.

Comment thread Mage/src/main/java/mage/abilities/keyword/SneakAbility.java Outdated
@JayDi85
Copy link
Copy Markdown
Member

JayDi85 commented May 29, 2026

@muz something useful in cost adjusters and target adjusters?

IMG_2223

@muz
Copy link
Copy Markdown
Contributor Author

muz commented May 29, 2026

Whole code is so messy with many hacks. Smells like a wrong implementation. Need deeply research an other possible solutions. Maybe game engine improves, but not such card’s code. Will return later here.

I'll be the first to admit it's not the most elegant solution for sure. Do you want to elaborate specifically on which bits have you concerned?

The code syntactically could be tidied up a bit, but if getting this PR has gotten the discussion going and things moving in the right direction to address user reported bugs and missing features from a new set, then it's still an improvement (not saying it needs merging yet, but getting timely attention on it is good).

I went through a number of iterations locally before landing on this initial pass that at least has the test case passing and seems to be OK-ish with some rudimentary testing. Sneak is definitely a weird one in the sense that it does cast the spell, so interaction and the stack needs interacting with unlike Ninjitsu, but it also needs to retain some knowledge of the state of the items being used to pay the cost (namely attack target) so the incoming permanent from the spell itself enters both tapped, and attacking the correct item (if possible, otherwise it defaults to just being tapped per rule 506.3c)

// Sync targets and effects from the card's spell ability at target-selection
// time. This allows SneakAbility to be constructed before addEffect/addTarget
// are called on the card's spell ability (construction order doesn't matter).
this.setTargetAdjuster((ability, game) -> {
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.

This is an interesting solution direction, which would take care of #14975 if it works.
However, I worry it might not work right with playable checks, because target adjusters aren't called for that. I guess since it'll never have targets during playable check, the problem is simply that it might appear playable when no legal targets.

Copy link
Copy Markdown
Member

@JayDi85 JayDi85 May 31, 2026

Choose a reason for hiding this comment

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

Playable check calls target and cost adjusters (if it miss somewhere then must be fixed). If you need different logic (e.g. allow more targets before real select) then custom target class with possible targets and playable state check can help.

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.

Well, I don't know for sure, but this comment suggests it doesn't. So would need to be tested for this use case. (But I think it's better to have a utility method that adds the effects to both SpellAbility rather than trying to grope them from a TargetAdjuster.)

// Note: in playability check for cards, targets are not adjusted.
void adjustTargets(Ability ability, Game game);

public boolean activate(Game game, Set<MageIdentifier> allowedIdentifiers, boolean noMana) {
if (!super.activate(game, allowedIdentifiers, noMana)
public ActivationStatus canActivate(UUID playerId, Game game) {
if (game.getStep() == null
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.

interesting null check, I believe that's null only at the very start of the game


@Override
public void adjustTargets(Game game) {
// Skip the second run (inside AbilityImpl.activate's mode loop) if
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.

I'm not following this. If you still think this is the best approach, can you walk me through what's happening in more detail?

// can find the synced targets on the ability. The adjustTargets override above
// prevents the second run (inside super) from overwriting those selections.
adjustTargets(game);
return super.activate(game, allowedIdentifiers, noMana);
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.

These workarounds are not filling me with confidence. Especially if discrepancies between test behavior and in game behavior. I suspect the target adjuster approach is not actually what we want to do.

}
// Enter tapped
permanent.setTapped(true);
// Enter attacking — build a CombatGroup directly, since addAttackerToCombat
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.

Why do you need to build a CombatGroup manually? I don't believe any other implementation needs to do that. I would expect addAttackingCreature to work. (Maybe current usages of addAttackerToCombat really ought to be using addAttackingCreature?)

if (!super.pay(ability, game, source, controllerId, noMana, costToPay)) {
return false;
}
// Register the ETB effect now that defenderId is known (set in addReturnTarget during super.pay())
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.

should this comment be one line lower? it seems independent of the costs tag?

@xenohedron
Copy link
Copy Markdown
Contributor

xenohedron commented May 31, 2026

OverloadAbility was recently reworked (#14015) to add effects/targets to both the card base SpellAbility and the OverloadAbility.

I think for instants/sorceries that have alternate casting implemented as a SpellAbility, we probably want a general purpose solution to solve the problem of effects/targets getting to both. This has caused bugs for DisturbAbility (7da8557) and SpectacleAbility (#10164) in the past.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Sneak not working right

3 participants