Skip to content

[Card Bug] Mass zone move feeding a scalar "that much/that many" consumer resolves per-recipient/max instead of the total (Valakut Exploration) #7046

Description

@dan-blanchard

Card(s)

Valakut Exploration (first witness). Class: any <mass zone move>, then <effect> that much/that many to each player/opponent chain.

Build/version

Current main (14b11cf5b), observed with the parse landed by the Valakut Exploration misparse fix (PR forthcoming, will be linked here).

Actual behavior

When a ChangeZoneAll completes and a chained sub-effect consumes Ref(EventContextAmount), the amount each recipient reads is not the total number of cards moved:

  • effects/mod.rs::previous_effect_counts_by_player_from_events (~7411) tallies the completed move's ZoneChanged events per owner;
  • install_previous_effect_counts_by_player (~7476) installs that per-player table and sets last_effect_count/last_effect_amount to the per-player max, not the total;
  • the parent-effect postlude (~10105–10118) runs this before the sub resolves;
  • DamageEachPlayer (deal_damage.rs:~2268) resolves per-recipient via resolve_quantity_scoped_with_targets, whose EventContextAmount cascade (quantity.rs:~3686) consults the per-player table before the scalar channels — so each opponent reads its own swept-card count.

Concretely for Valakut Exploration ("put them into their owner's graveyard, then this enchantment deals that much damage to each opponent"): in the card's native pattern every swept card is the controller's own (impulse-exiled off their library), so each opponent's per-player count is 0 and the chained damage resolves for 0 — while coverage counts the card supported with no warning. A mixed-ownership pool (one P0-owned + one P1-owned card) resolves −1/0 — discriminating the per-player-table mechanism from both scalar candidates (stamped total 2 would give −2/−2; installed max would give −1/−1).

Expected behavior

"That much" is the total number of cards moved by the preceding clause (CR 608.2c — later text reads the earlier action's result; matches the card's official ruling). Two cards swept → each opponent is dealt 2. Note there is currently no QuantityRef that can encode the total after the install (the scalar channel itself holds the max), so this needs an engine-side channel rather than a parser workaround.

Steps to reproduce

crates/engine/tests/integration/valakut_exploration_end_step_exile_sweep.rs (landing in the linked PR) carries three CHARACTERIZATION assertions with explicit update-me markers — they are the acceptance criteria for this issue:

  • T1 (mixed-ownership pool, 2 cards): currently −1/0 → should be −2/−2
  • T2 (native landfall pattern, 1 controller-owned card): currently 0 → should be −1
  • T4 (foreign-link hostile, 1 linked card): currently 0 → should be −1

The sweep/gate/link-tracking semantics around them are fully proven (revert-discriminating); only the amount channel is characterized.

Additional context

Found by a downstream consumer's structural cross-check while fixing the card's parser-side misparse (existential intervening-if + swallowed conjunct). The parse now emits the proven DamageEachPlayer{Ref(EventContextAmount), Opponent} shape (Shadowheart's), which is target-state-correct — the gap is exclusively in how the amount channel aggregates after a mass move.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions