Add Dazzling Beauty#5074
Conversation
Adds a general Effect::BecomeBlocked { target: TargetFilter } primitive so an
effect can make a target unblocked attacking creature become blocked with no
blockers (CR 509.1h), covering the ~21-card 'becomes blocked' effect class.
- New Effect::BecomeBlocked variant + resolver (game/effects/become_blocked.rs)
- New GameEvent::AttackerBecameBlockedByEffect for CR 509.3c triggers (fires the
bare 'becomes blocked' trigger only if the attacker was unblocked; CR 509.3d
keeps blocker-side 'whenever ~ blocks' from firing)
- combat::mark_attacker_blocked helper (sets blocked flag, no blocker_assignments)
- Fix is_source_blocked and activate_ninjutsu to read the blocked flag rather
than blocker_assignments non-emptiness (CR 509.1h / 702.49a)
- Parser: build_become_clause 'blocked' arm (nom all_consuming(tag))
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Warning You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again! |
Parse changes introduced by this PR · 5 card(s), 8 signature(s) (baseline: main
|
matthewevans
left a comment
There was a problem hiding this comment.
Reviewed current head db72f0b. This is a parameterized BecomeBlocked engine primitive for the Dazzling Beauty/Curtain of Light/Fog Patch/Trap Runner/Choking Vines class, not a card-specific special case. I traced resolution through combat blocked state, trigger matching, ninjutsu/restriction predicates, parser output, AI classifiers, and the targeted runtime tests; live checks are green.
Merge of upstream/main brought in the CR 603.3b read/write conflict profiler (ability_rw.rs). Add BecomeBlocked to both exhaustive Effect matches, mirroring the sibling combat-state mutator RemoveFromCombat (a TurnStructure write, idempotent on a non-attacker/already-blocked target; CR 509.1h). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Status: CI red is fixed and pushed. Cause: merging current Fix (commit in On the parse-diff: the changes are the intended class generalization — the general 🤖 Addressed by Claude Code |
matthewevans
left a comment
There was a problem hiding this comment.
Reviewed current head c0cc33d740de8358786a031eabe83a16e58cea58 after the merge-main fix.
The only PR-local change since the previously approved implementation is the ability_rw.rs classification for Effect::BecomeBlocked. It is threaded through both exhaustive matches: the legacy-tag visitor descends the target filter, and the read/write profiler mirrors RemoveFromCombat as a combat/turn-structure write while preserving target flagging. That is the right seam for the new CR 603.3b profiler interaction and does not change the previously reviewed BecomeBlocked runtime/parser surface.
Summary
Adds engine support for Dazzling Beauty by building the general
Effect::BecomeBlocked { target: TargetFilter }primitive — an effect that makesa target unblocked attacking creature become blocked with no blockers assigned
(CR 509.1h; works even on creatures that can't be blocked). This is built for
the class, not the card: ~21 unsupported cards share the "target … becomes
blocked" effect (Curtain of Light, Trap Runner, Dazzling Beauty, …). Dazzling
Beauty then composes it with the already-supported delayed-draw and
declare-blockers-step casting restriction (both left untouched).
Key design points:
GameEvent::AttackerBecameBlockedByEffect { attacker }so aneffect-driven block fires the bare "Whenever ~ becomes blocked" trigger
only if the attacker was unblocked at that instant (CR 509.3c), while
blocker-side "whenever ~ blocks" and "becomes blocked by a creature"
triggers do not fire (CR 509.3d). A dedicated event — rather than a
synthetic
BlockersDeclaredwith a fake blocker — is what keeps the 509.3dexclusion correct.
combat::mark_attacker_blockedsets theAttackerInfo.blockedflag with noblocker_assignmentsentry (contrastplace_blocking, which needs a legalblocker and is deliberately not reused). The existing combat-damage seam
already assigns no damage for a blocked-with-zero-blockers creature
(CR 510.1c), so no change there.
is_source_blockedandactivate_ninjutsunow read theauthoritative
blockedflag (CR 509.1h, CR 702.49a) instead ofblocker_assignmentsnon-emptiness, matchingunblocked_attackersandFilterProp::Unblocked. Without this, an effect-blocked creature would read as"not blocked" for "if ~ is blocked" conditions and would remain ninjutsu-eligible.
Files changed
CR references
CR 509.1h— an effect can make an attacking creature become blocked; it remains blocked even if all blockers are removed (authorizing rule).CR 510.1c— a blocked creature with no creatures blocking it assigns no combat damage.CR 509.3c— the "becomes blocked" trigger also fires when a creature becomes blocked by an effect, but only if it was unblocked at that time.CR 509.3d— it won't fire "becomes blocked by a creature" / blocker-side triggers when the block is caused by an effect.CR 509.1b,CR 509.1g— declare-blockers turn-based action / a blocking creature is declared (contrast: the place-a-blocker case).CR 702.49a— Ninjutsu returns an unblocked attacking creature.CR 608.2b— illegal-target fizzle (a non-attacking target no-ops).CR 613.1f— Layer 6 re-evaluation on a combat-state change.Gate A
./scripts/check-parser-combinators.sh— exit 0, no violations (the script emitsno output on success). No
.contains/.starts_with/.split_once/match-arm stringliterals introduced; parser dispatch is a nom
all_consuming(tag("blocked"))arm.Anchored on
crates/engine/src/parser/oracle_effect/subject.rs:3416— the existingall_consuming(tag("saddled"))BecomeSaddledarm inbuild_become_clause;the new
"blocked"arm (subject.rs:3432) mirrors the same nom-combinator idiomand the same
target = application.target … unwrap_or_else(affected)threading.crates/engine/src/game/effects/saddle.rs:78—resolve_object_targets(SelfRef → source / event-context ref / announced targets);
become_blocked.rsis a structural clone of this resolver shape.
crates/engine/src/game/combat.rs:391—place_blocking, the siblingcombat-state mutator;
mark_attacker_blocked(combat.rs:438) follows the sameshape (deliberately not reused —
place_blockingrequires a legal blocker,which an unblockable creature has none of).
crates/engine/src/types/ability.rs:11143—RemoveFromCombat, the siblingcombat-state
EffectvariantBecomeBlockedis placed alongside and mirrorsfor every registration site (
target_filter,EffectKind,From<&Effect>).Track
Non-developer — this environment cannot build
client/public/card-data.json(the per-set MTGJSON fetch fails under Git Bash on Windows), so
cargo coverageand
cargo semantic-auditcould not run locally. CI runs them on this PR.Substantial local verification did run (below).
LLM
Model: claude-opus-4-8
Thinking: high
Verification
Local (Non-developer — coverage/semantic-audit deferred to CI):
cargo fmt --all— clean./scripts/check-parser-combinators.sh— clean (exit 0)cargo clippy --workspace --exclude phase-tauri --all-targets --features engine/proptest -- -D warnings— clean (engine, phase-ai, engine-wasm, server-core, …)cargo test -p engine --lib— 14941 passed / 0 failed (incl. the new resolver,is_source_blocked, ninjutsu, and CR 509.3d block-side-guard unit tests)cargo test -p engine --test dazzling_beauty_become_blocked— 5 passed / 0 failedcargo test -p phase-ai— all greencargo check --package engine-wasm --target wasm32-unknown-unknown— cleancargo engine-inventory— regenerated;BecomeBlockedpresent, no collisionupstream/main.Discriminating runtime tests (drive the real cast pipeline; fail on revert):
becomes_blocked_deals_no_damage(+control_without_spell_takes_full_damagereach-guard) — target deals 0 to the defending player once made blocked.becomes_blocked_trigger_fires_and_block_side_trigger_does_not— CR 509.3c positive paired with the CR 509.3d negative in one scenario.works_on_unblockable— a "can't be blocked" attacker still becomes blocked (proves the effect does not route throughplace_blocking).is_source_blocked_reads_blocked_flag_not_assignments,ninjutsu_fails_if_attacker_blocked_by_effect_without_assignments— each with a positive reach-guard.Scope Expansion
This
Addships a general building block (as its size reflects): a newEffect::BecomeBlockedvariant + resolver, a new typedGameEvent::AttackerBecameBlockedByEffectfor CR 509.3c/509.3d trigger fidelity,the
combat::mark_attacker_blockedhelper, and rules-read corrections tois_source_blockedandactivate_ninjutsu(both previously keyed offblocker_assignmentsnon-emptiness, which is wrong for effect-driven blocks). Theprimitive covers the whole ~21-card "becomes blocked" effect class, not just
Dazzling Beauty.
Validation Failures
None.
CI Failures
None.
Tier: Frontier