Skip to content

Fix Talion, the Kindly Lord chosen-number spell trigger (#1999) - #2295

Merged
matthewevans merged 5 commits into
phase-rs:mainfrom
kiannidev:fix/1999-talion-chosen-number-trigger
Jun 5, 2026
Merged

Fix Talion, the Kindly Lord chosen-number spell trigger (#1999)#2295
matthewevans merged 5 commits into
phase-rs:mainfrom
kiannidev:fix/1999-talion-chosen-number-trigger

Conversation

@kiannidev

Copy link
Copy Markdown
Contributor

Summary

Fixes #1999: Talion, the Kindly Lord was triggering on every opponent spell cast but never applying life loss or draw.

Root cause was twofold:

  1. Condition/effect splitfind_effect_boundary treated the comma in mana value, power, or toughness as the effect boundary, truncating the trigger condition and breaking spell-quality parsing.
  2. Spell filter — The opponent-cast parser only recognized with mana value equal to the chosen number, not Talion’s full mana value, power, or toughness disjunction, so valid_card was never set and the trigger fired unconditionally.

Changes

  • Teach find_effect_boundary to skip commas that continue a spell-stat disjunction (continues_spell_quality_disjunction).
  • Add parse_spell_chosen_number_quality producing FilterProp::AnyOf over CMC / power / toughness == ChosenNumber.
  • Stop re-splitting the opponent-cast spell clause on , (condition text is already effect-free).
  • Parser and boundary tests for Talion’s oracle line.

Test plan

  • cargo test -p engine --lib talion
  • cargo test -p engine --lib find_effect_boundary_skips_spell_quality_comma
  • Manual: ETB choose a number; opponent casts a spell matching MV, power, or toughness → 2 life loss + draw; non-matching spell → no trigger

Parse mana value/power/toughness vs chosen number and skip commas
inside that spell-quality phrase when splitting condition from effect.
@kiannidev
kiannidev requested a review from matthewevans as a code owner June 4, 2026 18:49
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

Run rustfmt on oracle_trigger imports and tests. Narrow
continues_spell_quality_disjunction so unrelated ", power, or …"
phrases do not skip the effect boundary (swallowed-clause ratchet).
@kiannidev
kiannidev force-pushed the fix/1999-talion-chosen-number-trigger branch from a3ad7fd to 4c89a1e Compare June 4, 2026 19:24
@matthewevans

Copy link
Copy Markdown
Member

🤖 Architecture Review (automated)

Verdict: ⚠️ Changes requested

Seam: PASS — Correct layer (trigger parser), reuses existing FilterProp::{Cmc,PtComparison,AnyOf}, QuantityRef::ChosenNumber, PtStat/PtValueScope (all already in engine-inventory.json); no new variants, runtime consumers in game/filter.rs + game/quantity.rs confirmed (not parsed-but-unused).
Idiomatic: CONCERN — New continues_spell_quality_disjunction dispatches with raw starts_with("…")/contains("…") string literals, unlike its three sibling continuation detectors in the same block which all use nom tag().
Value: Class-level — suffix-table + disjunction generalizes the prior single-CMC path to the "mana value[, power, or toughness] equal to the chosen/that number" spell-quality class, not just Talion.
Reconciled with existing reviews: none — no Gemini review or inline comments present on this PR.

CR check: CR 202.3 (mana value) and CR 208.1 (creature P/T numbers) both verified present in docs/MagicCompRules.txt. Annotations accurate.

Findings

  • [HIGH] crates/engine/src/parser/oracle_trigger.rs:22-29 (continues_spell_quality_disjunction) — Dispatches via trimmed.starts_with("power, or "), .starts_with("toughness, or "), .starts_with("or "), .contains("equal to the chosen number"), .contains("equal to that number"). These are string-literal .starts_with("/.contains(" forms with no allow-noncombinator marker, so they will trip scripts/check-parser-combinators.sh (SCOPE = crates/engine/src/parser; FORBIDDEN_METHODS matches \.starts_with\(" and \.contains\(", filtering only lines tagged allow-noncombinator). This is also the repo's chore: update coverage stats and badges #1 idiomatic rule (nom combinators for parsing dispatch) and is inconsistent with the three sibling detectors (continues_player_action_list, continues_disjunctive_zone_change_condition, continues_serial_event_condition) that all use tag::<_,_,OracleError>("or "). Fix: rewrite the connector dispatch with nom tag() mirroring the siblings; for the clause-content test, prefer matching the single clause segment (truncate at next , like continues_disjunctive_zone_change_condition does) rather than .contains() over the whole remainder — .contains() can match a later "chosen number" mention in the effect clause. (Note: the strip_suffix calls in parse_spell_chosen_number_quality are correctly marked allow-noncombinator — that sanctioned structural-suffix exception is fine; only the new continuation detector is the problem.)

No correctness or CR defects found beyond the above; the boundary trace for the Talion line is sound (comma-1 → power, or, comma-2 → or …equal to the chosen number, comma-3 → effect boundary), and the suffix table is class-level.

Replace starts_with/contains dispatch in continues_spell_quality_disjunction
with tag/preceded/alt matching Talion disjunction segments; verify
continuation via trailing comma tag instead of clause splitting.
@kiannidev
kiannidev force-pushed the fix/1999-talion-chosen-number-trigger branch from fc5dd65 to 98f511a Compare June 4, 2026 19:45
@kiannidev

Copy link
Copy Markdown
Contributor Author

🤖 Architecture Review (automated)

Verdict: ⚠️ Changes requested

Seam: PASS — Correct layer (trigger parser), reuses existing FilterProp::{Cmc,PtComparison,AnyOf}, QuantityRef::ChosenNumber, PtStat/PtValueScope (all already in engine-inventory.json); no new variants, runtime consumers in game/filter.rs + game/quantity.rs confirmed (not parsed-but-unused). Idiomatic: CONCERN — New continues_spell_quality_disjunction dispatches with raw starts_with("…")/contains("…") string literals, unlike its three sibling continuation detectors in the same block which all use nom tag(). Value: Class-level — suffix-table + disjunction generalizes the prior single-CMC path to the "mana value[, power, or toughness] equal to the chosen/that number" spell-quality class, not just Talion. Reconciled with existing reviews: none — no Gemini review or inline comments present on this PR.

CR check: CR 202.3 (mana value) and CR 208.1 (creature P/T numbers) both verified present in docs/MagicCompRules.txt. Annotations accurate.

Findings

  • [HIGH] crates/engine/src/parser/oracle_trigger.rs:22-29 (continues_spell_quality_disjunction) — Dispatches via trimmed.starts_with("power, or "), .starts_with("toughness, or "), .starts_with("or "), .contains("equal to the chosen number"), .contains("equal to that number"). These are string-literal .starts_with("/.contains(" forms with no allow-noncombinator marker, so they will trip scripts/check-parser-combinators.sh (SCOPE = crates/engine/src/parser; FORBIDDEN_METHODS matches \.starts_with\(" and \.contains\(", filtering only lines tagged allow-noncombinator). This is also the repo's chore: update coverage stats and badges #1 idiomatic rule (nom combinators for parsing dispatch) and is inconsistent with the three sibling detectors (continues_player_action_list, continues_disjunctive_zone_change_condition, continues_serial_event_condition) that all use tag::<_,_,OracleError>("or "). Fix: rewrite the connector dispatch with nom tag() mirroring the siblings; for the clause-content test, prefer matching the single clause segment (truncate at next , like continues_disjunctive_zone_change_condition does) rather than .contains() over the whole remainder — .contains() can match a later "chosen number" mention in the effect clause. (Note: the strip_suffix calls in parse_spell_chosen_number_quality are correctly marked allow-noncombinator — that sanctioned structural-suffix exception is fine; only the new continuation detector is the problem.)

No correctness or CR defects found beyond the above; the boundary trace for the Talion line is sound (comma-1 → power, or, comma-2 → or …equal to the chosen number, comma-3 → effect boundary), and the suffix table is class-level.

I updated code from the review

@matthewevans

Copy link
Copy Markdown
Member

Maintainer follow-up pushed.

Changes made:

  • Merged current origin/main into the PR branch.
  • Kept the author’s nom-combinator cleanup for the Talion spell-quality comma boundary.
  • Added a runtime cast/trigger regression that sets Talion’s chosen number and verifies the trigger fires for matching mana value, matching power, and matching toughness, but not a non-matching spell.

Verification run:

  • cargo fmt --all
  • ./scripts/check-parser-combinators.sh
  • cargo test -p engine --lib talion -- --nocapture (6/6 focused tests)

I also rechecked the final diff for the handler security/instruction-path exclusions before pushing.

@matthewevans matthewevans added bug Bug fix area:parser Oracle text parser area:engine Core rules engine mechanic:triggers rust Pull requests that update rust code labels Jun 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.

Maintainer review: approved after nom-parser cleanup, runtime trigger regression coverage, and final architecture pass.

@matthewevans
matthewevans enabled auto-merge June 5, 2026 00:23
@matthewevans
matthewevans added this pull request to the merge queue Jun 5, 2026
Merged via the queue into phase-rs:main with commit a04088e Jun 5, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:engine Core rules engine area:parser Oracle text parser bug Bug fix mechanic:triggers rust Pull requests that update rust code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Talion, The Kindly Lord is triggering but doing nothing — [[Talion, The Kindly Lord]] is triggering but doing nothing,…

2 participants