fix(engine): evaluate effective keywords for cascade triggers & handle comma-separated keywords - #1251
fix(engine): evaluate effective keywords for cascade triggers & handle comma-separated keywords#1251messiawrq-design wants to merge 4 commits into
Conversation
…e comma-separated keywords Fixes phase-rs#1242. - Updates `triggers.rs` to use `effective_spell_keywords` instead of only reading printed `obj.keywords`, allowing granted Cascade instances to fire correctly. - Updates `oracle_static.rs` `classify_quoted_inner` to split and parse quoted comma-separated keyword lists (e.g. "Cascade, cascade.") to generate multiple granted keyword modifications. Bounty Wallet: 6sF8p22Gg83NKTJ6dvya7Srv4USCniZnP47DwQwK7Mtp (Solana) / Algora
There was a problem hiding this comment.
Code Review
This pull request introduces a custom string-splitting and verbatim matching mechanism to parse comma-separated keywords in Oracle text. The review feedback correctly points out that this violates the repository's strict architectural rule (R1) requiring the use of modular, composable nom combinators for parser dispatch under crates/engine/src/parser/ to prevent fragile matches and maintain consistency.
| // Check for comma-separated keywords (like "Cascade, cascade.") | ||
| if lower.contains(',') { | ||
| let parts = split_keyword_list(ability_text.trim_end_matches('.')); | ||
| if parts.len() > 1 && parts.iter().all(|p| super::oracle_keyword::parse_keyword_from_oracle(&p.to_lowercase()).is_some()) { | ||
| return parts.into_iter().filter_map(|p| { | ||
| super::oracle_keyword::parse_keyword_from_oracle(&p.to_lowercase()) | ||
| .map(|keyword| ContinuousModification::AddKeyword { keyword }) | ||
| }).collect(); | ||
| } | ||
| } |
There was a problem hiding this comment.
[HIGH] Avoid verbatim string parsing and custom string splitting for parsing Oracle phrases. Instead, decompose compound phrases into modular, reusable parsers for constituent parts and compose them using idiomatic nom combinator aggregates (like nested alt and tag sequences) to prevent fragile matches and improve maintainability.
// Use modular nom-based parsers to parse keyword abilities
let (remaining, parsed_keywords) = alt((
map(tag_no_case("cascade"), |_| ContinuousModification::AddKeyword { keyword: Keyword::Cascade }),
// Compose other modular keyword parsers here
))(ability_text.trim())?;
return Ok(parsed_keywords);References
- Every new parser dispatch under crates/engine/src/parser/ must use nom 8.0 combinators or delegate to existing helpers. (link)
- Avoid verbatim string equality for parsing Oracle phrases as it bypasses the robust nom-based parser and creates fragile matches. Instead, decompose compound phrases into modular, reusable parsers for constituent parts and compose them using idiomatic combinator aggregates.
…_keyword_line
Replaces the manual `.contains(',')` and `split_keyword_list` string manipulation
with a proper call to the existing `extract_keyword_line` parser in `oracle_keyword.rs`.
This aligns with the repository's strict architectural rule (R1) for parser dispatch
and cleanly handles "Cascade, cascade." instances through the canonical parser path.
|
Thanks for tackling statically-granted cascade (#1242), @messiawrq-design! I'm closing this one, but I want to give you a concrete path to land it, because the underlying issue is real and worth fixing. Two things diverged here:
The correct seam: If you'd like to redo it against that seam with the acceptance tests from #1242 (Imoti / First Sliver / Zhulodok integration, a negative test, and a stacking test), I'd be glad to review. Genuinely appreciate the effort — the diagnosis was right, it just needs to live one function over. 🙏 |
Add a Labeling section (bug=existed-but-broken, enhancement=new engine work, feature=larger-scoped, test=test-only, refactor) applied to every handled PR, and correct the Enqueue step: main is REVIEW_REQUIRED, so an unapproved PR is silently shed from the queue. Mandate approve -> label -> enqueue and verify via GraphQL (gh CLI stdout unreliable under rtk). docs(skill): make 'correct architectural location' the top enqueue gate Per maintainer: the #1 review check is that a fix lives at the correct architectural seam, not just that CI is green. Velocity never justifies merging technical debt — a wrong-location fix that ships is worse than no fix. Adds the right-seam question as the first Architecture Review prompt and a disqualifying enqueue-checklist gate, citing the #1251 precedent.
🔁 Re-review (upgraded process)Prior verdict: none (no automated Reconciled with existing reviews:
Missed or re-rated
The cascade-trigger half the title describes already landed independently ( |
mike-theDude
left a comment
There was a problem hiding this comment.
Architecture Review
[HIGH] Diff targets a file that no longer exists at main; PR is unmergeable. Evidence: diff edits crates/engine/src/parser/oracle_static.rs:9782, but that flat file has been refactored into the crates/engine/src/parser/oracle_static/ directory and classify_quoted_inner now lives in crates/engine/src/parser/oracle_static/keyword_grant.rs:819 (re-exported from oracle_static/mod.rs:133); gh pr view reports mergeStateStatus: DIRTY, mergeable: CONFLICTING. Why it matters: the change cannot land as written and must be re-derived against the current keyword_grant.rs seam. Suggested fix: rebase onto main and re-evaluate whether any edit to classify_quoted_inner in keyword_grant.rs is still needed.
[HIGH] The new extract_keyword_line(ability_text, &[]) call passes an empty MTGJSON slice, which short-circuits before the comma-split path and returns None for "Cascade, cascade." Evidence: crates/engine/src/parser/oracle_keyword.rs:216 returns parse_mtgjson_missing_standalone_keyword_line(line) when mtgjson_keyword_names.is_empty(), and that helper (oracle_keyword.rs:378-389) only yields Some for ForMirrodin/TotemArmor — the comma-split multiplicity logic at oracle_keyword.rs:253+ is never reached with &[]. Why it matters: the PR's stated Pattern B (recovering multiple AddKeyword from "Cascade, cascade.") produces nothing — the inserted block is dead for its intended input. Suggested fix: pass the actual MTGJSON keyword names through, or drop the block entirely since the capability already exists.
[MED] Both claimed patterns already exist in main, making this change redundant. Evidence: cascade-trigger keyword evaluation already routes through effective_spell_keywords at crates/engine/src/game/triggers.rs:1504,1554,1628,1699,1761, and per-occurrence comma-separated keyword recovery already exists in extract_keyword_line (oracle_keyword.rs:253-324) with passing tests extract_keyword_line_recovers_repeated_cascade_instances and _single_cascade_yields_one_instance (oracle_keyword.rs:1844,1865). Why it matters: re-adding a (broken) duplicate of shipped, tested infrastructure adds debt and a second dispatch path for the same responsibility. Suggested fix: confirm #1242 is already resolved on main and close the PR, or scope it to a concrete still-failing card with a building-block test.
[LOW] Inserted block adds no CR annotation and no test, against the rules-annotation and building-block-test conventions. Evidence: the diff hunk in oracle_static.rs carries only the inline comment "Check if the entire ability text is a list of keywords" with no // CR and no accompanying test; contrast the existing CR-annotated path at keyword_grant.rs:840 (CR 702) and oracle_keyword.rs:284 (CR 702.85c / 702.40b). Why it matters: rules-touching parser code ships without its required CR citation or coverage. Suggested fix: if any change remains after rebase, annotate it with the verified CR (702.85c) and add a building-block test.
Note on prior review: the existing Gemini HIGH comment ("verbatim string parsing and custom string splitting") is partially refuted against the current head — the diff itself reuses the extract_keyword_line building block and adds no verbatim match or new split. The split(',') it references lives in the pre-existing extract_keyword_line (oracle_keyword.rs:253), not in this PR. The diff's real defects are the stale seam and the &[] argument above, not a new combinator-mandate violation.
Fixes #1242.
Changes
obj.keywordsintriggers.rswithcasting::effective_spell_keywords. This ensures statically-granted keywords (e.g., via The First Sliver or Imoti) correctly trigger Cascade instances instead of silently failing.oracle_static.rs::classify_quoted_innerto properly split and parse quoted comma-separated keyword lists (like"Cascade, cascade."). This allows cards like Zhulodok, Void Gorger to produce the correct multiplicity ofCastWithKeywordstatic abilities.Bounty Wallet: 6sF8p22Gg83NKTJ6dvya7Srv4USCniZnP47DwQwK7Mtp (Solana) / Algora