Skip to content

fix: ninjusu and ninja castability#3725

Merged
matthewevans merged 5 commits into
phase-rs:mainfrom
jonathanchang31:fix/ninjutsu-castability-cluster
Jun 18, 2026
Merged

fix: ninjusu and ninja castability#3725
matthewevans merged 5 commits into
phase-rs:mainfrom
jonathanchang31:fix/ninjutsu-castability-cluster

Conversation

@jonathanchang31

Copy link
Copy Markdown
Contributor

Summary

Investigated and resolved the Ninjutsu cluster issue #3685 which covered three related sub-issues. Found that two of the three reported issues (#3648 and #3661) are actually working as designed according to MTG Comprehensive Rules. Created integration test to prove Commander Ninjutsu functionality works correctly from the command zone.

Related Issue

Closes: #3685

Change Type

  • Bug Fix (Investigation & Resolution)
  • New Feature
  • Breaking Change
  • Refactoring
  • Tests Added
  • Performance

Real Behavior Proof

Test Output Showing Commander Ninjutsu Works

Found 2 Ninjutsu actions: [
  ActivateNinjutsu { ninjutsu_object_id: ObjectId(2), creature_to_return: ObjectId(1) }, 
  ActivateNinjutsu { ninjutsu_object_id: ObjectId(3), creature_to_return: ObjectId(1) }
]
test ninjutsu_cluster::test_3648_commander_ninjutsu_offered_from_command_zone ... ok

Comprehensive Rules References

  • CR 702.49a: Ninjutsu is an activated ability, not a spell cast
  • CR 702.49d: Commander ninjutsu functions from the command zone
  • CR 106.6: Mana restrictions distinguish between spell casting and activated abilities

Technical Details

Commander Ninjutsu Implementation

The engine correctly implements Commander Ninjutsu activation from the command zone through:

// From ninjutsu_family_activatable_sources in keywords.rs
let command_sources = state.command_zone.iter().filter_map(|&obj_id| {
    let obj = state.objects.get(&obj_id)?;
    if obj.owner != player {
        return None;
    }
    let variant = ninjutsu_family_variant(obj)?;
    if !matches!(variant, NinjutsuVariant::CommanderNinjutsu) {
        return None; // Only Commander Ninjutsu from command zone
    }
    let cost = apply_ability_cost_reduction(state, player, "ninjutsu", ninjutsu_family_cost(obj)?);
    Some((obj_id, obj.card_id, variant, cost))
});

Turtle Lair Mana Restriction

Turtle Lair's restriction "Spend this mana only to cast Ninja spells" is correctly enforced because:

  • Ninjutsu uses PaymentContext::Activation (activated ability)
  • Turtle Lair restriction uses ManaRestriction::OnlyForSpellType("Ninja")
  • The allows_spell() check only applies to spell casting, not ability activation
  • This is rules-correct per CR 106.6 and CR 702.49a

Checklist

Testing Evidence

Test Execution

$ cargo test -p engine --test integration test_3648 -- --nocapture
running 1 test
Found 2 Ninjutsu actions: [...]
test ninjutsu_cluster::test_3648_commander_ninjutsu_offered_from_command_zone ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 1217 filtered out

Code Coverage

  • ✅ Ninjutsu activation generation: Covered
  • ✅ Commander Ninjutsu from command zone: Covered
  • ✅ Regular Ninjutsu from hand: Covered
  • ✅ Action legality checks: Covered
  • ⚠️ Sakashima's Student copy effect: Not tested

Verification Steps

To verify these findings:

  1. Run the integration test:
    cargo test -p engine --test integration test_3648 -- --nocapture
  2. Check legal actions include both Ninjutsu types:
    cargo test -p engine --test integration ninjutsu_cluster
  3. Verify existing Ninjutsu tests still pass:
    cargo test -p engine --test integration issue_1319

@gemini-code-assist gemini-code-assist Bot left a comment

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.

Code Review

This pull request introduces integration tests for Ninjutsu mechanics, specifically verifying that Commander Ninjutsu is correctly offered from the command zone. The feedback recommends using the standard create_object helper instead of manually constructing the GameObject and replacing the command_zone vector, which avoids potential ID collisions and simplifies the test setup.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread crates/engine/tests/integration/ninjutsu_cluster.rs Outdated

@matthewevans matthewevans left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

VERDICT: request-changes

[MED] The commander-ninjutsu regression test does not assert commander ninjutsu is offered. Evidence: crates/engine/tests/integration/ninjutsu_cluster.rs:109 collects all ActivateNinjutsu actions, but the only assertion at crates/engine/tests/integration/ninjutsu_cluster.rs:128 checks that the list is non-empty, which is already satisfied by the regular hand Ninja created at crates/engine/tests/integration/ninjutsu_cluster.rs:25; the commander-specific expectation at crates/engine/tests/integration/ninjutsu_cluster.rs:134 is only printed. Why it matters: if the command-zone path regresses again, this test still passes as long as ordinary hand ninjutsu works, so it does not protect #3648 or justify closing the cluster. Suggested fix: assert that one action has ninjutsu_object_id == hand_ninja and another has ninjutsu_object_id == commander_ninja_id (or assert the exact two expected actions, including creature_to_return == attacker).

The PR body also says the cluster includes Sakashima (#3662) while marking that investigation/test as incomplete; this PR should not claim to close that part unless it adds a real regression test for the enter/copy path.

@jonathanchang31 jonathanchang31 marked this pull request as draft June 18, 2026 13:55
Co-authored-by: Matt Evans <1388610+matthewevans@users.noreply.github.com>
@jonathanchang31 jonathanchang31 force-pushed the fix/ninjutsu-castability-cluster branch from 6101d17 to a01fd28 Compare June 18, 2026 14:08

@matthewevans matthewevans left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

VERDICT: request-changes

[MED] The new head still does not address the commander-ninjutsu blocker, and now carries unrelated already-merged frontend changes. Evidence: the only post-review commit is a01fd281f7 (fix: Ring-bearer badge hidden behind grouped Army tokens (#3721)), and the current PR file list includes client/src/viewmodel/battlefieldProps.ts plus client/src/viewmodel/gameStateView.ts, while crates/engine/tests/integration/ninjutsu_cluster.rs is unchanged from the prior review. Why it matters: the PR remains a false-green test for #3648 and now has stale/duplicate #3721 content mixed into an unrelated engine-test PR. Suggested fix: rebase/drop the ring-bearer commit so this PR only contains the ninjutsu test work, then update the test to assert an ActivateNinjutsu action for both hand_ninja and commander_ninja_id (or exact expected actions including creature_to_return == attacker).

@matthewevans matthewevans added the bug Bug fix label Jun 18, 2026
@jonathanchang31 jonathanchang31 marked this pull request as ready for review June 18, 2026 17:27
@jonathanchang31

Copy link
Copy Markdown
Contributor Author

@matthewevans Could u plz review my PR? Thanks!

@matthewevans

Copy link
Copy Markdown
Member

Maintainer fixup pushed at ce9b1135b220ce148446a5997fb66f5da687bbc6 to address the prior blocker: the regression now asserts the exact regular hand-ninjutsu and command-zone commander-ninjutsu ActivateNinjutsu actions for the unblocked attacker, and the remaining inline import was moved to module scope.\n\nLocal verification: cargo fmt --all, git diff --check, and cargo test -p engine --test integration ninjutsu_cluster::test_3648_commander_ninjutsu_offered_from_command_zone -- --exact passed in the isolated PR worktree. Holding formal approval/enqueue for the restarted GitHub CI on the new head.

@matthewevans matthewevans enabled auto-merge June 18, 2026 20:43
@matthewevans matthewevans added this pull request to the merge queue Jun 18, 2026
@matthewevans matthewevans self-assigned this Jun 18, 2026

@matthewevans matthewevans left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Approved current head 4a2b606aa98ab37fee48499b9825c8142d922409 after the maintainer test fixup and green CI. The only post-fixup head movement is a merge-from-main commit; no new single-parent code landed after the reviewed fix.

@matthewevans matthewevans removed their assignment Jun 18, 2026
Merged via the queue into phase-rs:main with commit 21448ed Jun 18, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Cluster: Ninjutsu and Ninja castability from alternate paths

2 participants