forge icon indicating copy to clipboard operation
forge copied to clipboard

Undo Restore: Morph spells cast by Kaervek the Punisher crash the game if you rollback when paying their Morph costs

Open Jetz72 opened this issue 1 year ago • 6 comments

#5048 has been fixed, but the potential issue described in the "An extra oddity" section of that post can now be reproduced without the workaround I was using. I'll add it as a separate issue though since it seems to be an issue with the experimental new rollback system.

Describe the bug To reiterate from the previous issue:

Kaervek, the Punisher is one of very few cards with the effect "copy some card, you may cast the copy" (i.e. PlayEffect with CopyCard$ True) without either limiting the card to only Instants or Sorceries, or specifying "without paying its mana cost".

This allows him to cast Morph cards face down. While they now enter the battlefield, something seems wrong with the state that they're in. If you click them to flip them face up, then decline to pay the morph cost, the game logic crashes while attempting to roll back.

To Reproduce Check "EXPERIMENTAL Undo restore" in game preferences. Kaervek, the Punisher in battlefield; Sidisi's Pet in graveyard. Lightning Bolt the enemy player. Choose Sidisi's Pet for Kaervek's trigger. Affirm that you want to cast it. Choose to cast it morphed, as a face down 2/2. Pay 3 to cast it, and let the stack resolve. Click the face-down 2/2 to activate its Morph ability. Click cancel when asked to pay the cost. The prompt is not dismissed and the game cannot continue.

Expected behavior The cost payment is cancelled and the game continues.

Log and stack trace

Adding Whenever you commit a crime, exile up to one target black card from your graveyard and copy it. You may cast the copy. If you do, you lose 2 life. (Targeting opponents, anything they control, and/or cards in their graveyards is a crime. Copies of permanent spells become tokens.) (Targeting: [[Sidisi's Pet (205)]]) [Player: Jetz] to stack
Adding Lightning Bolt deals 3 damage to any target. to stack
Adding Whenever you commit a crime, exile up to one target black card from your graveyard and copy it. You may cast the copy. If you do, you lose 2 life. (Targeting opponents, anything they control, and/or cards in their graveyards is a crime. Copies of permanent spells become tokens.) (Targeting: [[Sidisi's Pet (205)]]) [Player: Jetz] to stack
Adding Lightning Bolt deals 3 damage to any target. to stack
Adding (You may cast this card face down as a 2/2 creature for {3}.) to stack
Adding Lightning Bolt deals 3 damage to any target. to stack
Adding (You may cast this card face down as a 2/2 creature for {3}.) to stack
Adding Lightning Bolt deals 3 damage to any target. to stack
Adding Lightning Bolt deals 3 damage to any target. to stack
Adding Lightning Bolt deals 3 damage to any target. to stack
Restoring game state with timestamp of :40
Game-0 > java.lang.NullPointerException: Cannot invoke "forge.game.card.Card.getId()" because the return value of "forge.game.card.Card.getCopiedPermanent()" is null
	at forge.game.GameSnapshot.copyGameState(GameSnapshot.java:368)
	at forge.game.GameSnapshot.assignGameState(GameSnapshot.java:77)
	at forge.game.GameSnapshot.restoreGameState(GameSnapshot.java:59)
	at forge.game.Game.restoreGameState(Game.java:198)
	at forge.game.GameActionUtil.rollbackAbility(GameActionUtil.java:847)
	at forge.player.HumanPlaySpellAbility.playAbility(HumanPlaySpellAbility.java:190)
	at forge.player.HumanPlay.playSpellAbility(HumanPlay.java:107)
	at forge.player.PlayerControllerHuman.playChosenSpellAbility(PlayerControllerHuman.java:1566)
	at forge.game.phase.PhaseHandler.startFirstTurn(PhaseHandler.java:1060)
	at forge.game.GameAction.startGame(GameAction.java:2107)
	at forge.game.Match.startGame(Match.java:90)
	at forge.gamemodes.match.HostedMatch$2.run(HostedMatch.java:259)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
	at java.base/java.lang.Thread.run(Thread.java:1570)

Additional context

  • Version: master at commit 4ccbc9baa6f91b0192d4681e4d79ebc6d0a1469f (latest as of writing)
  • Does not happen if undo restore experiment is disabled.
  • Does not happen with a Morph card played from the hand normally.

Jetz72 avatar Apr 16 '24 13:04 Jetz72

Thanks for the thorough report. I'll check it out tonight if i have time

tehdiplomat avatar Apr 16 '24 13:04 tehdiplomat

I've encountered the issue again under different circumstances. This issue no longer seems specific to morph. Cancelling any payment after a spell is copied through a play effect seems to trigger it.

Case 1: Garth One-Eye in battlefield, "EXPERIMENTAL Undo restore" enabled. Tap Garth, choose any playable card name. Cast the chosen spell and resolve it. Click any other spell from your hand to start casting it normally. Cancel the mana payment. The game logic crashes while attempting to roll back.

Adding {T}: Choose a card name that hasn't been chosen from among Disenchant, Braingeyser, Terror, Shivan Dragon, Regrowth, and Black Lotus. Create a copy of the card with the chosen name. You may cast the copy. (You still pay its costs.)  to stack
Adding {T}: Choose a card name that hasn't been chosen from among Disenchant, Braingeyser, Terror, Shivan Dragon, Regrowth, and Black Lotus. Create a copy of the card with the chosen name. You may cast the copy. (You still pay its costs.)  to stack
Adding Black Lotus to stack
Adding Black Lotus to stack
Restoring game state with timestamp of :31
Game-0 > java.lang.NullPointerException: Cannot invoke "forge.game.card.Card.getId()" because the return value of "forge.game.card.Card.getCopiedPermanent()" is null
	at forge.game.GameSnapshot.copyGameState(GameSnapshot.java:368)
	at forge.game.GameSnapshot.assignGameState(GameSnapshot.java:77)
	at forge.game.GameSnapshot.restoreGameState(GameSnapshot.java:59)
	at forge.game.Game.restoreGameState(Game.java:198)
	at forge.game.GameActionUtil.rollbackAbility(GameActionUtil.java:847)
	at forge.player.HumanPlaySpellAbility.playAbility(HumanPlaySpellAbility.java:190)
	at forge.player.HumanPlay.playSpellAbility(HumanPlay.java:107)
	at forge.player.PlayerControllerHuman.playChosenSpellAbility(PlayerControllerHuman.java:1566)
	at forge.game.phase.PhaseHandler.startFirstTurn(PhaseHandler.java:1060)
	at forge.game.GameAction.startGame(GameAction.java:2107)
	at forge.game.Match.startGame(Match.java:90)
	at forge.gamemodes.match.HostedMatch$2.run(HostedMatch.java:259)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
	at java.base/java.lang.Thread.run(Thread.java:1570)

Case 2: Kaervek, the Punisher in battlefield, Python (vanilla 3/2 for 1BB) in graveyard, "EXPERIMENTAL Undo restore" enabled. Lightning Bolt the enemy player. Choose Python for Kaervek's trigger. Cast and resolve it. Click any other spell from your hand to start casting it normally. Cancel the mana payment. The game logic crashes while attempting to roll back.

Adding Whenever you commit a crime, exile up to one target black card from your graveyard and copy it. You may cast the copy. If you do, you lose 2 life. (Targeting opponents, anything they control, and/or cards in their graveyards is a crime. Copies of permanent spells become tokens.) (Targeting: [[Python (121)]]) [Player: Jetz] to stack
Adding Lightning Bolt deals 3 damage to any target. to stack
Adding Whenever you commit a crime, exile up to one target black card from your graveyard and copy it. You may cast the copy. If you do, you lose 2 life. (Targeting opponents, anything they control, and/or cards in their graveyards is a crime. Copies of permanent spells become tokens.) (Targeting: [[Python (121)]]) [Player: Jetz] to stack
Adding Lightning Bolt deals 3 damage to any target. to stack
Adding Python - Creature 3 / 2 to stack
Adding Lightning Bolt deals 3 damage to any target. to stack
Adding Python - Creature 3 / 2 to stack
Adding Lightning Bolt deals 3 damage to any target. to stack
Adding Lightning Bolt deals 3 damage to any target. to stack
Adding Lightning Bolt deals 3 damage to any target. to stack
Restoring game state with timestamp of :41
Game-3 > java.lang.NullPointerException: Cannot invoke "forge.game.card.Card.getId()" because the return value of "forge.game.card.Card.getCopiedPermanent()" is null
	at forge.game.GameSnapshot.copyGameState(GameSnapshot.java:368)
	at forge.game.GameSnapshot.assignGameState(GameSnapshot.java:77)
	at forge.game.GameSnapshot.restoreGameState(GameSnapshot.java:59)
	at forge.game.Game.restoreGameState(Game.java:198)
	at forge.game.GameActionUtil.rollbackAbility(GameActionUtil.java:847)
	at forge.player.HumanPlaySpellAbility.playAbility(HumanPlaySpellAbility.java:190)
	at forge.player.HumanPlay.playSpellAbility(HumanPlay.java:107)
	at forge.player.PlayerControllerHuman.playChosenSpellAbility(PlayerControllerHuman.java:1566)
	at forge.game.phase.PhaseHandler.startFirstTurn(PhaseHandler.java:1060)
	at forge.game.GameAction.startGame(GameAction.java:2107)
	at forge.game.Match.startGame(Match.java:90)
	at forge.gamemodes.match.HostedMatch$2.run(HostedMatch.java:259)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
	at java.base/java.lang.Thread.run(Thread.java:1570)

Jetz72 avatar Apr 18 '24 02:04 Jetz72

Yea there's something funky going with when we try to copy this data if the card was created out of thin air for some reason.. I'll dig into it more, but it might take some time.

tehdiplomat avatar Apr 19 '24 03:04 tehdiplomat

This issue has not been updated in a while and has now been marked as stale. Stale messages will be auto closed.

github-actions[bot] avatar May 19 '24 09:05 github-actions[bot]

This issue was closed because it has been stalled for 5 days with no activity.

github-actions[bot] avatar May 24 '24 09:05 github-actions[bot]

This issue has not been updated in a while and has now been marked as stale. Stale messages will be auto closed.

github-actions[bot] avatar Jun 24 '24 09:06 github-actions[bot]