Add Talruum Piper#5131
Conversation
Parameterize StaticMode::MustBeBlockedByAll with an optional blocker TargetFilter so the engine supports filtered mass-forced-block (filtered Lure): "All <creature-filter> able to block <subject> do so". None preserves the existing unfiltered Lure; Some(filter) forces only matching creatures. Covers Talruum Piper (flying), Marble Priest (Walls), and You Look Upon the Tarrasque (opponents-control). CR 509.1c. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request implements filtered lures in accordance with CR 509.1c by parameterizing the StaticMode::MustBeBlockedByAll enum variant with an optional TargetFilter. The parser has been updated to extract blocker filters (such as 'creatures with flying' for Talruum Piper or 'Walls' for Marble Priest), and the blocker validation logic in combat.rs now correctly enforces that only qualifying creatures are compelled to block. Comprehensive unit tests have been added to verify the new functionality. No review comments were provided, and the implementation is highly idiomatic and conforms to the repository's architectural guidelines, so I have no feedback to provide.
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.
matthewevans
left a comment
There was a problem hiding this comment.
[MED] One-shot filtered lures lose the spell controller before combat evaluates controller-relative blocker filters. Evidence: crates/engine/src/parser/oracle_effect/tests.rs:17556 asserts the one-shot form parses creatures your opponents control as ControllerRef::Opponent, crates/engine/src/game/layers.rs:5023 installs the AddStaticMode as a recipient StaticDefinition, and crates/engine/src/game/combat.rs:1647 later rebuilds the filter context from that recipient source id. Why it matters: if the spell targets a creature controlled by someone other than the spell controller, your opponents is evaluated relative to the target creature's controller, so the wrong creatures are compelled to block. Suggested fix: preserve/snapshot the originating ability controller for controller-relative blocker filters when materializing AddStaticMode, and add a runtime test that targets an opponent-controlled creature.
[LOW] Required parse-diff evidence is not present yet for this engine/parser PR. Evidence: the PR currently changes parser seams at crates/engine/src/parser/oracle_static/evasion.rs:611 and crates/engine/src/parser/oracle_effect/mod.rs:7979, but the issue comments do not yet contain a <!-- coverage-parse-diff --> sticky for head 6b680a33c01c41fb8be19202d11f130b2ec592ad. Why it matters: the card-level gained/lost/changed parse coverage cannot be audited against the claimed Talruum Piper/Marble Priest scope. Suggested fix: let the card-data/parse-diff job complete and address any unexpected card-level changes before approval.
Parse changes introduced by this PR · 3 card(s), 6 signature(s) (baseline: main
|
|
The parse-diff sticky has now posted for head That resolves the parse-diff evidence gap from my review. The remaining blocker is still the MED controller-provenance issue in the formal review: the one-shot |
One-shot filtered mass-forced-block effects (You Look Upon the
Tarrasque) graft the MustBeBlockedByAll static onto the target creature
via AddStaticMode, which dropped the spell controller so combat resolved
a controller-relative blocker filter ("creatures your opponents control")
relative to the target's controller instead of the caster's. Snapshot the
originating ability controller onto StaticDefinition.source_controller at
graft time (mirrors ReplacementDefinition.source_controller) and anchor the
combat filter context via from_source_with_controller. Covers the sibling
MustBeBlocked { by } seam too. CR 611.2c, CR 109.5, CR 509.1c.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Thanks for the review. Both points addressed in [MED] one-shot controller-relative lures lost the spell controller — fixed. Fix — snapshot the originating ability controller at graft time:
Multiplayer-correct: New runtime test (your requested case): [LOW] parse-diff evidence — thanks, confirmed resolved on your follow-up: the sticky shows only the three intended cards (Talruum Piper / Marble Priest / You Look Upon the Tarrasque) gaining the filtered Verification (Non-developer track, Windows — coverage/semantic-audit deferred to CI): merged current 🤖 Addressed by Claude Code |
…terals CI builds the full workspace; phase-ai constructs full StaticDefinition literals in its feature/policy tests that need the new field. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
matthewevans
left a comment
There was a problem hiding this comment.
[MED] Distinct one-shot lure anchors can still collapse when the same mode is already installed on the target. Evidence: crates/engine/src/game/layers.rs:5081 snapshots the installing player into def.source_controller, but the install guard at crates/engine/src/game/layers.rs:5084 only checks sd.mode == resolved_mode; StaticDefinition::source_controller is the field combat later consumes at crates/engine/src/game/combat.rs:1024 and crates/engine/src/game/combat.rs:1043. Why it matters: in multiplayer, if two players cast the same controller-relative one-shot lure on the same creature, the second effect has the same MustBeBlockedByAll { blockers: ... } mode but a different caster anchor, and it is silently dropped, so one player’s required-blocker set is never enforced. Suggested fix: include the full installed static definition, including source_controller, in the idempotency check for AddStaticMode, and add a runtime test with two different casters applying the same controller-relative lure to one target.
The AddStaticMode graft deduped on mode only, so two different casters installing the same controller-relative lure mode on one permanent collapsed to a single static — the second caster's source_controller anchor (and thus its CR 509.1c required-blocker set) was silently dropped. Compare the full grafted StaticDefinition (including source_controller) so distinct-caster anchors coexist; same-caster re-application across layer passes still dedups. CR 611.2c, CR 509.1c. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Addressed in [MED] distinct one-shot lure anchors collapsing in the
Test (your requested case): Verification (merged current 🤖 Addressed by Claude Code |
Summary
Adds engine support for Talruum Piper ("All creatures with flying able to block this creature do so.").
Built for the class, not the single card: parameterizes the existing
StaticMode::MustBeBlockedByAllunit variant intoMustBeBlockedByAll { blockers: Option<TargetFilter> }— the filtered mass-forced-block (filtered Lure) class.Nonepreserves the existing unfiltered Lure behavior byte-for-byte (Lure, Ochran Assassin, Prized Unicorn, Breaker of Armies);Some(filter)forces only creatures matching the filter to block. This mirrors the already-shipped siblingStaticMode::MustBeBlocked { by: Option<TargetFilter> }(the one-blocker requirement) exactly.Cards unlocked by the class primitive:
Some(creatures with flying)(permanent static)Some(Walls)(permanent static)Some(creatures your opponents control)(one-shot modal effect)Both parser seams (one-shot effect + permanent static) share a single combinator-based two-slot grammar helper. Runtime enforcement gates the CR 509.1c "must block if able" requirement by the blocker filter, live-evaluated per candidate blocker.
Files changed
crates/engine/src/types/statics.rscrates/engine/src/game/combat.rscrates/engine/src/game/coverage.rscrates/engine/src/game/static_abilities.rscrates/engine/src/parser/oracle_effect/mod.rscrates/engine/src/parser/oracle_effect/tests.rscrates/engine/src/parser/oracle_static/evasion.rscrates/engine/src/parser/oracle_static/mod.rscrates/engine/src/parser/oracle_tests.rscrates/engine/src/ai_support/filter.rscrates/engine/tests/squirrel_perf_probe.rsCR references
CR 509.1c— blocking requirements ("the defending player checks each creature they control" against must-block requirements). The blocker filter narrows which of the defending player's creatures carry the requirement; it does not change the requirement-maximization rule.Track
Non-developer
This box (Windows) cannot generate
client/public/card-data.json— the per-set MTGJSON fetches fail under Git Bash (curl: (3) URL rejected: Malformed input to a URL function), socargo coverage,cargo semantic-audit, and the card-data integration suite are unavailable locally and are deferred to CI. To compensate, the fullcargo test -p enginesuite and the parser-combinator gate were run locally (results below).LLM
Model: claude-opus-4-8
Thinking: high
Tier: Frontier
Gate A
Exit 0 — no combinator-purity violations. (The script emits output only on violation; a clean run is silent.)
Anchored on
crates/engine/src/types/statics.rs:1556—StaticMode::MustBeBlocked { by: Option<TargetFilter> }, the shipped one-blocker sibling; this PR applies the identicalOption<TargetFilter>parameterization toMustBeBlockedByAll.crates/engine/src/types/statics.rs:2753— theMustBeBlocked:By({filter:?})two-armDisplaysplit; the newMustBeBlockedByAllDisplay mirrors it (None → unchanged label, Some → Debug form).crates/engine/src/game/combat.rs:648— the existingMustBeBlockedfiltered enforcement loop'smatches_target_filter(state, id, filter, &FilterContext::from_source(state, src_id))conjunct; the newMustBeBlockedByAllenforcement reuses this exact filter-eval pattern.crates/engine/src/game/coverage.rs:130— theis_data_carrying_staticarm forMustBeBlocked { .. };MustBeBlockedByAll { .. }is added adjacent (both are now data-carrying, non-registry-keyed).Verification
Non-developer track —
cargo coverage/cargo semantic-audit/gen-card-data.shdeferred to CI (card-data.json cannot be generated on this box; see Track). Compensating local checks, all clean:cargo fmt --all— clean./scripts/check-parser-combinators.sh— exit 0, no violationscargo check -p engineandcargo check -p engine --tests— clean, 0 warningscargo test -p engine— 15175 lib + 1758 parser pass / 0 fail (6 ignored); all other engine test binaries 0 failis_none_or(matches_target_filter)conjunct fails the runtime discriminatorparsed_talruum_piper_flying_lure_forces_only_fliers; reverting the parser filter slot fails theSome(filter)reach-guards.Discriminating tests added:
parsed_talruum_piper_flying_lure_forces_only_fliers(combat.rs) — parses the real Oracle line, then drivesvalidate_blockers: with a flying and a non-flying defender both able,[]→Err,[(non_flier, attacker)]→Err,[(flier, attacker)]→Ok (the non-flier is not forced). Reach-guarded by asserting the extracted filter isSome(flying)matching the flier but not the non-flier.mass_forced_block_filtered_opponents_control(oracle_effect/tests.rs) — one-shot seam; assertsSome(creatures your opponents control)at both theStaticDefinitionand theAddStaticModeconstruction sites.blockers: None; two-disjoint-lures, tapped-only-match, non-matching-idle, and both-shapes-data-carrying tests.Scope Expansion
None.
Validation Failures
None.
CI Failures
None.