As many are aware by now, the Shadows over Innistrad: Remastered release on March 21st introduced an unfortunate bug to Magic: The Gathering Arena. This bug affected many cards that confer an ability that mentions the title of the conferring card, such as [[Citizen's Crowbar]] and [[Ninja's Kunai]].
Equipped creature gets +1/+1 and has "{oW}, {oT}, Sacrifice Citizen's Crowbar: Destroy target artifact or enchantment."
Equipped creature has "{1}, {T}, Sacrifice Ninja's Kunai: Ninja's Kunai deals 3 damage to any target."
Instead of sacrificing the conferring object, these abilities sacrificed all permanents controlled by the ability's controller. In Kunai's case, this was also followed up by each of the sacrificed objects dealing 3 damage to the target.
...ouch. What is happening? Why is it happening? How did we miss this happening? Well, do we have a story for you!
The story of how this bug came about requires some background in how MTG Arena is coded. Join me as I break down and explain the most relevant aspects here along with what we learned.
Much of our rules engine code is machine-generated: we use a natural-language processing solution to interpret the English words on the card and create code (this is an article, or a series thereof, by itself!). This has two relevant features: one, every release involves a new generation of all the code that comes from card text - we don't just freeze the original parsed code. Two, due to being machine-generated, many components of the card behavior code are highly generic, as this example will illustrate. The buggy component that arose here is a code snippet (called a Rule in the language we use) responsible for identifying what resources are available to pay a cost, named ProposeEffectCostResource. Every card text that involves non-mana costs has its own version of this Rule:
"Discard a card: Draw a card." has a ProposeEffectCostResource Rule that proposes every card in your hand.
"As an additional cost to cast this spell, exile a red card from your graveyard." would propose each red card in your graveyard.
"Crew 3" proposes each untapped creature you control, weighted by their power.
Let's put a pin in ProposeEffectCostResource for now to discuss self-referential cards. In the Theros Beyond Death expansion, [[Heliod's Punishment]] was introduced, which was MTG Arena's first card that involved a self-reference in a conferred ability ("Remove a task counter from Heliod's Punishment", "destroy Heliod's Punishment").
Enchanted creature can't attack or block. It loses all abilities and has "{oT}: Remove a task counter from Heliod's Punishment. Then if it has no task counters on it, destroy Heliod's Punishment."
This is quite tricky! Most abilities that include a self-reference mean "this card", or perhaps "the card that put this ability on the stack". Heliod's Punishment attached to your [[Runeclaw Bear]] is not talking about Runeclaw Bear in its mentioning of Heliod's Punishment, even though Runeclaw Bear has the ability. So what is it talking about? It's saying "the card that conferred the ability that was activated". That is, we care about the particular ability-on-permanent to know what the self-reference means. We decided that the salient feature of these cards was that they were on Auras and Equipment and made special code to handle self-references in those cases.
Returning to the subject of effect cost resources, Streets of New Capenna introduced [[Falco Spara, Pactweaver]].
You may cast spells from the top of your library by removing a counter from a creature you control in addition to paying their other costs.
What does the ProposeEffectCostResource Rule look like here?
It proposes each type of counter from among permanents you control, and it's invoked whenever you cast a spell using Falco's ability. Lovely. But what if you have multiple copies of Falco out? Legendary sure doesn't mean what it used to. . .
Well, we don't want to make a separate action for each Falco you have out - we just have one action for "you're casting a particular card using a Falco ability" - we don't keep track of which ability-on-a-Falco is responsible, as it's irrelevant (and if it were displayed, perhaps misleading to a player!). But we ran into a problem here...
Even though only one Falco ability is relevant for the action, ALL of them were using their cost payment Rules for that action. Your selection of a counter was filled up redundantly, and when you picked one, each Falco would remove that type of counter from the permanent you chose.
Still with us? Great – also, we're hiring.
So, we made the decision to decouple the ProposeEffectCostResource Rule from abilities-on-cards, and instead have them associated with just the ability text - all the Falcos have the same ability text, so the Rule executes only once. Our work for the conferred-self-reference stuff for Heliod's Punishment stepped in a later part of writing this Rule, so it reintroduced the ability-on-card to the Rule, and everything was awesome.
But then along came Mean Old [[Gutter Grime]] in Shadows over Innistrad: Remastered.
Whenever a nontoken creature you control dies, put a slime counter on Gutter Grime, then create a green Ooze creature token with "This creature's power and toughness are each equal to the number of slime counters on Gutter Grime."
- Gutter Grime has a conferred ability with a self-reference, just like Heliod's Punishment.
- Unlike Heliod's Punishment, it's not an Aura or Equipment. Our solution to the conferred self-reference had to be completely rethought.
- After a lot of sweat and maybe a few tears, we had such a solution: it involved moving that reference to the conferred-ability-on-a-card to earlier in the code generation process. Later, ProposeEffectCostResource deletes that constraint from the Rule it creates.
And thus, the bug: Such cost-resource Rules for conferred self-referencing abilities lose track of the relevant ability. They now proposed resources without that constraint. For "sacrifice", there's still the constraints of "it's on the battlefield" and "you control it", but costs that don't involve a user selection are simply paid by using, well ... all of the qualified resources.
And with Ninja's Kunai, there's actually two different self-references in its conferred ability: * "Sacrifice Ninja's Kunai." This first one is the type we've been talking about, meaning "the card that conferred this ability". * "Ninja's Kunai deals 3 damage to any target" This second self-reference is interpreted to mean "the permanent that was sacrificed." This explains the... explosive nature of Kunai's bug: each of the sacrificed permanents is interpreted to be that latter "Ninja's Kunai", so each of them deals damage. This feature is usually useful (examples: [[Nightmare Shepherd]] triggering on a Mutated creature dying, [[Skyclave Apparition]] dying after its enters-the-battlefield ability triggered twice due to [[Panharmonicon]]).
In Ian's article, we celebrated our over 3000 regression tests, run every night to ward against releasing buggy code. You may wonder how we didn't catch this. Writing a regression test requires a good deal of effort and thought, since they take the form of scripted games of Magic: The Gathering using our rules engine. Some of these tests take over a day to write. Even the simplest ones involve at least 15 minutes of effort to ideate, write, and validate. That may not sound like a lot of time, until you multiply it by the hundreds of cards in each major card set. Therefore, we don't create such tests for every new card on MTG Arena – we focus on the cards that required specific developer effort to work correctly. For everything else, our (human) QA team tests newly added cards at the beginning of a set's implementation, and again before release. It's unreasonable to expect them to also test every other card we've ever shipped with each and every release!
With a project this big and a game this complex, bugs are inevitable. It's still truly disheartening when they're as impactful as this one, especially knowing how hard my team works to prevent them from happening. Now that we've fixed this bug, the fix's verification is part of our regression test suite. We're also already reconsidering our code analysis methodology so we can be more confident we're not wrecking old cards' behaviors by implementing new ones, making this sort of situation rarer in the first place. Last, but certainly not least - I will also continue to be incredibly proud and impressed by the work my team has produced for this game.
#wotc_staff
External link →