Repository navigation
Description
Summary: When a game state or puzzle puts a "divided as you choose" damage spell on the stack with a scripted target (for example humanputonstack=Fire Covenant->701), GameState adds the target but no damage division. Resolving the spell then throws a NullPointerException in DamageDealEffect.resolve.
Environment: Forge desktop 2.0.16-SNAPSHOT-10.09 (build dated 2026-10-09, source a2ba8ce9), macOS, OpenJDK 21. On current master (e5a0f8156, 2026-10-25), GameState.java is identical to that build and DamageDealEffect.java has the same line. Links below point to master.
Steps to reproduce:
-
Use a game state or puzzle whose
[state]section contains:ActivePlayer=Human ActivePhase=MAIN1 humanbattlefield=Mountain;Swamp;Swamp aibattlefield=Grizzly Bears|Id:701 humanputonstack=Fire Covenant->701 -
Apply it. Fire Covenant is on the stack, targeting Grizzly Bears.
-
Let the spell resolve.
Expected: The spell resolves. With a single scripted target, all of the divided damage could go to that target.
Actual: Resolution throws:
java.lang.NullPointerException: Cannot invoke "java.lang.Integer.intValue()" because the return value of "forge.game.spellability.SpellAbility.getDividedValue(forge.game.GameObject)" is null
at forge.game.ability.effects.DamageDealEffect.resolve(DamageDealEffect.java:248)
at forge.game.ability.SpellApiBased.resolve(SpellApiBased.java:44)
at forge.game.ability.AbilityUtils.resolveApiAbility(AbilityUtils.java:1397)
at forge.game.ability.AbilityUtils.resolve(AbilityUtils.java:1333)
at forge.game.zone.MagicStack.resolveStack(MagicStack.java:647)
Just before resolving, the spell on the stack reported targets: [Grizzly Bears], isDividedAsYouChoose() == true and getDividedValue(Grizzly Bears) == null.
Verification: Reproduced on 2026-10-26 with the installed stock desktop JAR. A small headless harness built a two-player game, ran GameState.parse and applyGameOnThread on the [state] section above on the game thread, then called MagicStack.resolveStack(); the trace above is its output. I first tried the same file as a puzzle in the desktop app, but could not finish that run, so the GUI path itself is unconfirmed. Puzzle Mode applies puzzles through the same GameState code. No stock puzzle in res/puzzle pre-casts or puts on the stack a DividedAsYouChoose spell, so the shipped puzzles are not affected.
Root cause: precastSpellFromCard and executeScript parse only the target after ->. handleScriptedTargetingForSA adds it with sa.getTargets().add(...) and never records a divided allocation. DamageDealEffect.resolve then unboxes sa.getDividedValue(o) into an int for each target, and the null value throws. The precast path (humanprecast=...->id) resolves the spell directly and appears to reach the same line; I did not run it.
Suggested fix: In handleScriptedTargetingForSA, when sa.isDividedAsYouChoose(), give the scripted target an allocation along with the target. With one scripted target, that is the whole amount, sa.addDividedAllocation(target, ...). The divided total may need computing first, since getStillToDivide() returns 0 while dividedValue is unset. Alternatively, extend the -> syntax to carry an amount. Hardening DamageDealEffect against a null allocation would avoid the crash, but would hide the missing allocation.
Duplicate search: Searched open and closed issues and PRs (2026-10-26) for Fire Covenant, getDividedValue, DividedAsYouChoose, GameState putonstack, puzzle divided damage, DamageDealEffect NullPointerException, divided damage NullPointerException, puzzle NullPointerException. Related but different: #654 (open: "exile until leaves" broken in GameState / Puzzle Mode), #12378 (merged: random retarget of a divided ability). None describes this crash.
🤖 Investigated and written in collaboration with Claude Code.
Activity