Skip to content

Fix Black Bolt, Inhuman King - #7011

Merged
matthewevans merged 3 commits into
phase-rs:mainfrom
JacobWoodson:card/black-bolt-inhuman-king
Aug 9, 2026
Merged

Fix Black Bolt, Inhuman King#7011
matthewevans merged 3 commits into
phase-rs:mainfrom
JacobWoodson:card/black-bolt-inhuman-king

Conversation

@JacobWoodson

@JacobWoodson JacobWoodson commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes a parse-fidelity defect on Black Bolt, Inhuman King.

Issue: Lethal Voice: destroy target's controller parsed as You instead of the triggering opponent; "that player controls" (the opponent controlling the spell/ability) anaphor defaulted to ControllerRef::You, so it targets your own nonland permanent rather than the opponent's.

Files changed

  • C:\Users\jacob\source\repos\phase-card-runs\crates\engine\src\parser\oracle_trigger.rs
  • C:\Users\jacob\source\repos\phase-card-runs\crates\engine\src\parser\oracle_trigger_tests.rs
  • C:\Users\jacob\source\repos\phase-card-runs\crates\engine\tests\integration\black_bolt_lethal_voice_destroys_triggering_opponents_permanent.rs
  • C:\Users\jacob\source\repos\phase-card-runs\crates\engine\tests\integration\main.rs
  • C:\Users\jacob\source\repos\phase-card-runs\crates\engine\src\parser\oracle_ir\snapshots\engine__parser__oracle_ir__snapshot_tests__bonecrusher_giant_ir.snap

CR references

  • CR 115.1
  • CR 603.2e
  • CR 109.4b

Track

Developer

LLM

Model: claude-opus-4-8
Thinking: high

Tier: Frontier

Verification

  • cargo fmt --all — clean (exit 0)
  • ./scripts/check-parser-combinators.sh — clean - Gate G PASS + Gate A PASS. WindowsApps python3 stub falsely hard-fails D0 pre-check; re-ran with working msys64 python3 on PATH so Family D actually ran and passed (10/10 detector tests). Not merely skipped.
  • cargo clippy-strict — clean (exit 0, -D warnings); re-verified after the census-test edit
  • cargo test -p phase-engine — clean (exit 0) after in-loop fix: lib 18483 passed/0 failed, integration 4489 passed/0 failed. Fixed a Windows path-separator artifact in the unrelated CR-603.5 census pin test (test-only .replace('\','/') in game/engine.rs; no-op on Linux CI, pins untouched).
  • cargo export-cards data --stats --output client/public/card-data.json --sidecar-dir client/public + cp to data/card-data.json — clean (exit 0). Used --output because the literal recipe (no --output) streams main export to stdout and --sidecar-dir writes only localized card-data..json; the cp would have copied the stale sidecar. Fresh card-data.json regenerated against this branch's engine.
  • cargo coverage — clean (exit 0); Black Bolt, Inhuman King supported:true gap_count:0
  • cargo semantic-audit — clean (exit 0); Black Bolt, Inhuman King absent from all 295 flagged_cards -> 0 findings

Scope Expansion

Also updated the Bonecrusher Giant IR snapshot (metadata-only relative_player_scope, another becomes-target trigger; effect unchanged); card-data.json not hand-regenerated (gitignored, Tilt-owned).

Validation Failures

None.

CI Failures

None.

Summary by CodeRabbit

  • Bug Fixes

    • Improved handling of effects that trigger when a permanent becomes the target of a spell or ability.
    • “That player” references now correctly resolve to the player who controlled the triggering spell or ability, even if control changes afterward.
    • Improved platform-independent path matching.
  • Tests

    • Added coverage for targeting triggers, life-loss effects, attack and damage triggers, and Black Bolt’s Lethal Voice ability.

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The PR normalizes relative census paths to forward slashes. It records the source controller in BecomesTarget events and resolves related “that player” references to the triggering player. Parser, unit, and integration tests cover the behavior.

Changes

Census path normalization

Layer / File(s) Summary
Normalize relative census paths
crates/engine/src/game/engine.rs
Relative source paths replace backslash separators with forward slashes after string conversion.

Becomes-target trigger resolution

Layer / File(s) Summary
Preserve source-controller provenance
crates/engine/src/types/events.rs, crates/engine/src/game/casting.rs, crates/engine/src/game/targeting.rs, crates/engine/src/game/log.rs, crates/engine/src/game/trigger_matchers.rs, crates/engine/src/game/triggers.rs, crates/engine/src/game/stack.rs, crates/engine/src/game/casting_tests.rs
BecomesTarget events now store the announcement-time source controller. Target resolution uses this value after control changes. Existing matchers, logs, fixtures, and tests accept the expanded event shape.
Parse triggering-player references
crates/engine/src/parser/oracle_trigger.rs, crates/engine/src/parser/oracle_trigger_tests.rs
The parser detects singular and plural becomes-target conditions and maps their source controller to ControllerRef::TriggeringPlayer. Tests cover destroy, attack, damage, and life-loss conditions.
Validate Black Bolt resolution
crates/engine/tests/integration/black_bolt_lethal_voice_destroys_triggering_opponents_permanent.rs, crates/engine/tests/integration/main.rs
The integration test verifies that Black Bolt selects and destroys only the triggering opponent’s nonland permanent, including after the spell controller changes.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Casting
  participant GameEvent
  participant TriggerParser
  participant TriggeredAbility
  participant Battlefield
  Casting->>GameEvent: emit BecomesTarget with source_controller
  GameEvent->>TriggerParser: match becomes-target condition
  TriggerParser->>TriggeredAbility: resolve "that player" as TriggeringPlayer
  TriggeredAbility->>Battlefield: select and destroy opponent permanent
Loading

Possibly related PRs

Suggested reviewers: matthewevans

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely identifies the Black Bolt, Inhuman King defect addressed by the pull request.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ast-grep (0.45.0)
crates/engine/src/game/casting_tests.rs

ast-grep timed out on this file

crates/engine/src/game/triggers.rs

ast-grep timed out on this file


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added the needs-maintainer AI-contribution PR requires human triage (Non-dev track or unresolved gaps) label Aug 4, 2026
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

Generated for head 11a445f4621725f97b235b8dbb7a799a81261427.

Parse changes introduced by this PR · 2 card(s), 1 signature(s) (baseline: main de0b04ecbe76)

🟡 Modified fields (1 signature)

  • 2 cards · 🔄 ability/Destroy · changed field target: you control permanent non-landtriggering player controls permanent non-land
    • Affected (first 3): Black Bolt, Inhuman King, Scalelord Reckoner

11 card(s) had Oracle-text changes (errata/reprint) — excluded as non-parser.
New cards in head: 138.

@matthewevans matthewevans added the bug Bug fix label Aug 5, 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.

[MED] Snapshot the targeting spell or ability's controller in GameEvent::BecomesTarget. Evidence: crates/engine/src/parser/oracle_trigger.rs:1045-1055,1219-1227 maps the targeter to ControllerRef::TriggeringPlayer, while crates/engine/src/game/targeting.rs:1662-1665 later derives that player from the current state.objects[source_id].controller; the event emitted at crates/engine/src/game/casting.rs:621-636 carries only source_id despite already receiving controller. Why it matters: if an activated ability targets Black Bolt and its source changes controllers before Lethal Voice resolves, the trigger can select the new controller's permanent instead of the player who activated the targeting ability. CR 602.2a and CR 115.1c (verified in docs/MagicCompRules.txt) establish that the activated ability's controller and targets are set during activation. Suggested fix: capture the controller in GameEvent::BecomesTarget at emit_targeting_events, resolve this anaphor from that snapshot, and add a runtime regression test for an activated ability followed by source control change; the existing test covers only a spell without control change.

@matthewevans matthewevans removed the needs-maintainer AI-contribution PR requires human triage (Non-dev track or unresolved gaps) label Aug 6, 2026
@matthewevans matthewevans self-assigned this Aug 9, 2026
@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@crates/engine/tests/integration/black_bolt_lethal_voice_destroys_triggering_opponents_permanent.rs`:
- Around line 1-18: Correct the rules annotation in the test documentation:
replace the CR 115.1 attribution for spell target declaration and
source-controller provenance with CR 601.2c for target selection during casting
and CR 405.4 for the spell controller being its caster. Ensure every cited CR
directly describes the annotated behavior, while preserving the test’s existing
rules explanation and pipeline scenario.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: fea38331-ad82-4e7d-887d-74457e928a1b

📥 Commits

Reviewing files that changed from the base of the PR and between de0b04e and 11a445f.

⛔ Files ignored due to path filters (1)
  • crates/engine/src/parser/oracle_ir/snapshots/engine__parser__oracle_ir__snapshot_tests__bonecrusher_giant_ir.snap is excluded by !**/*.snap, !**/snapshots/**
📒 Files selected for processing (13)
  • crates/engine/src/game/casting.rs
  • crates/engine/src/game/casting_tests.rs
  • crates/engine/src/game/engine.rs
  • crates/engine/src/game/log.rs
  • crates/engine/src/game/stack.rs
  • crates/engine/src/game/targeting.rs
  • crates/engine/src/game/trigger_matchers.rs
  • crates/engine/src/game/triggers.rs
  • crates/engine/src/parser/oracle_trigger.rs
  • crates/engine/src/parser/oracle_trigger_tests.rs
  • crates/engine/src/types/events.rs
  • crates/engine/tests/integration/black_bolt_lethal_voice_destroys_triggering_opponents_permanent.rs
  • crates/engine/tests/integration/main.rs
🚧 Files skipped from review as they are similar to previous changes (4)
  • crates/engine/src/game/engine.rs
  • crates/engine/tests/integration/main.rs
  • crates/engine/src/parser/oracle_trigger.rs
  • crates/engine/src/parser/oracle_trigger_tests.rs

Comment on lines +1 to +18
//! CR 115.1 + CR 603.2e + CR 608.2c — Black Bolt, Inhuman King (Lethal Voice).
//!
//! Verbatim Oracle text:
//! Flying
//! Whenever you cast a noncreature spell, Black Bolt gets +2/+2 until end of turn.
//! Lethal Voice — Whenever Black Bolt becomes the target of a spell or ability an
//! opponent controls, destroy target nonland permanent that player controls.
//!
//! "That player" is the controller of the *targeting* source — the opponent
//! (CR 608.2c, "apply the rules of English to the text," reads the effect's "that
//! player" anaphor as referring back to the player named by the trigger
//! condition; CR 603.2e fires on the "becomes the target" event; CR 115.1 fixes
//! the source's target when it is put on the stack). The parser previously
//! lowered "that player controls" to `ControllerRef::You`, so Lethal Voice
//! offered/destroyed one of BLACK BOLT'S OWN permanents. This test drives the
//! real pipeline: an opponent (P1) targets Black Bolt with a spell P1 controls,
//! and Lethal Voice must be able to destroy a nonland permanent P1 controls —
//! never one of P0's own.

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.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Correct the CR annotation for target declaration and source-controller provenance.

The statement that CR 115.1 fixes the source's target when it is put on the stack is incorrect. CR 601.2c defines spell target selection during casting. CR 405.4 defines a spell controller as the player who cast it. Update the annotation so it does not attribute announcement-time source_controller preservation to CR 115.1. (blogs.magicjudges.org)

As per path instructions, each CR citation must describe the code it annotates.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@crates/engine/tests/integration/black_bolt_lethal_voice_destroys_triggering_opponents_permanent.rs`
around lines 1 - 18, Correct the rules annotation in the test documentation:
replace the CR 115.1 attribution for spell target declaration and
source-controller provenance with CR 601.2c for target selection during casting
and CR 405.4 for the spell controller being its caster. Ensure every cited CR
directly describes the annotated behavior, while preserving the test’s existing
rules explanation and pipeline scenario.

Source: Path instructions

@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 at 11a445f4621725f97b235b8dbb7a799a81261427.

The targeting-source controller is now snapshotted in GameEvent::BecomesTarget, the hostile controller-change integration path covers the prior bug, the exact-head parse-diff is limited to Black Bolt and Scalelord Reckoner, and required checks are green.

@matthewevans
matthewevans added this pull request to the merge queue Aug 9, 2026
@matthewevans matthewevans removed their assignment Aug 9, 2026
Merged via the queue into phase-rs:main with commit 0784597 Aug 9, 2026
12 checks passed

@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.

Verified current head: BecomesTarget now snapshots the announcement-time controller, with a control-change regression; CI is green.

@JacobWoodson
JacobWoodson deleted the card/black-bolt-inhuman-king branch August 10, 2026 01:18
@coderabbitai coderabbitai Bot mentioned this pull request Aug 13, 2026
4 tasks
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.

2 participants