fix(engine): enforce event-bound forced blocks#6613
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughThe change adds exact attacker and object-incarnation tracking for force-block effects, introduces global blocker-declaration validation and tax snapshot restoration, updates AI completion, and changes the client to support multiple attacker pairs per blocker. ChangesForce-block parsing and provenance
Blocker declaration flow
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
Suggested labels: Sequence Diagram(s)sequenceDiagram
participant OracleParser
participant TriggerSystem
participant CombatEngine
participant BlockerUI
OracleParser->>TriggerSystem: parse force-block attacker and duration
TriggerSystem->>CombatEngine: bind exact attacker incarnation
CombatEngine->>BlockerUI: publish blocker requirements and attacker IDs
BlockerUI->>CombatEngine: submit blocker-attacker pairs
CombatEngine->>BlockerUI: accept declaration or rebuild blocker prompt
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
Parse changes introduced by this PR · 27 card(s), 6 signature(s) (baseline: main
|
There was a problem hiding this comment.
Actionable comments posted: 13
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
client/src/components/board/ActionButton.tsx (1)
176-179: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winReset the pending blocker on prompt replacement.
This effect only tracks
mode; consecutiveDeclareBlockersprompts keep"combat-blockers", so a stalependingBlockercan carry into the next prompt and make the next attacker click assign unexpectedly. Reset against a blocker-prompt identity (for example,waitingFor) as well.🤖 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 `@client/src/components/board/ActionButton.tsx` around lines 176 - 179, Update the pending-blocker reset effect in ActionButton to also depend on the blocker prompt identity, such as waitingFor, not only mode. Ensure pendingBlocker is cleared whenever a new DeclareBlockers prompt replaces the previous one, while preserving the existing reset on mode changes.Source: Path instructions
crates/engine/src/parser/swallow_check.rs (1)
4092-4108: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winMisplaced exemption breaks the file's own documented
*ThisTurninvariant.
TriggerCondition::SourceAttackedThisCombatis added todetect_duration_this_turn's exemption set, but the surrounding comment (lines 3990-3995, 4046-4047) explicitly states the checkable rule for this arm: every variant named here must containThisTurn— this one containsThisCombatinstead. It's also unreachable for the very card this PR targets (Tolsimir's text has no" this turn"substring at all, so the function returns at line 3824 before reaching this arm), andEffect::ForceBlock { .. }(line 4197) already exempts the actual force-block-with-combat-history shape. As written, this creates a latent false-negative: a future card combining"attacked this combat"with an unrelated, genuinely swallowed"this turn"elsewhere would have that real bug masked.🐛 Proposed fix: remove the misplaced arm
if evidence.any::<TriggerCondition>(|x| { matches!( x, TriggerCondition::SourceEnteredThisTurn - | TriggerCondition::SourceAttackedThisCombat | TriggerCondition::DealtDamageBySourceThisTurn | TriggerCondition::DealtDamageThisTurnBySource { .. } | TriggerCondition::FirstTimeObjectTappedThisTurn | TriggerCondition::FirstTimeObjectCountersAddedThisTurn | TriggerCondition::AttackedThisTurn | TriggerCondition::CastSpellThisTurn { .. } | TriggerCondition::SpellCastWithVariantThisTurn { .. } | TriggerCondition::CounterAddedThisTurn ) }) { return; }🤖 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/src/parser/swallow_check.rs` around lines 4092 - 4108, Remove TriggerCondition::SourceAttackedThisCombat from the exemption matches in detect_duration_this_turn, leaving only variants whose names contain ThisTurn. Preserve the existing exemptions and the separate Effect::ForceBlock handling.crates/engine/src/game/ability_scan.rs (1)
1146-1150: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winDo not hide the new
ForceBlockpayloads behind...This scanner now visits only
target, while the PR adds attacker-provenance and duration fields to force-block effects. Any read-bearing attacker or duration payload is therefore omitted, allowing false-negative axis results and unsafe ordering/loop classifications. Destructure every field and scan each applicable payload, especially throughscan_duration, to preserve fail-closed behavior.🤖 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/src/game/ability_scan.rs` around lines 1146 - 1150, Update the ForceBlock arm in the effect scanner to explicitly destructure every payload field instead of using .., and scan each read-bearing attacker and duration value with the appropriate scanner, including scan_duration. Preserve scanning target while combining all applicable axes so ordering and loop classification remains fail-closed.
🧹 Nitpick comments (2)
crates/engine/src/types/ability.rs (1)
11518-11529: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsider a serde round-trip test for the new
ForceBlockshape.This file otherwise adds a dedicated round-trip test for nearly every new/changed serialized field shape (
enters_under_*,duration_roundtrip,zone_owner_serde_round_trips_each_variant, etc.). A small test coveringEffect::ForceBlock { attacker: Some(ForceBlockAttackerRef::EventSource), duration: Duration::UntilEndOfCombat, .. }(plus the legacy/no-attacker/no-durationdeserialize path) would guard the new backward-compat defaults the same way the rest of the file does.🤖 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/src/types/ability.rs` around lines 11518 - 11529, Add a serde round-trip test near the existing serialized-field tests for the ForceBlock variant, covering attacker set to ForceBlockAttackerRef::EventSource and duration set to Duration::UntilEndOfCombat. Also deserialize a legacy ForceBlock payload without attacker or duration and assert the defaults preserve the generic attacker behavior and UntilEndOfTurn lifetime.crates/engine/src/game/layers.rs (1)
4606-4615: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd a CR citation for the new incarnation-liveness guard.
The new
affected_recipientmismatch check is rules-touching (it determines whether a "locked-in" continuous effect still applies to its bound recipient across zone changes/incarnation changes), but it has no CR annotation, unlike the adjacentUntilHostLeavesPlaycheck right below it. CR 400.7 ("if an effect causes an object to move to a public zone, other parts of that effect can find that object" — with the corollary that a locked-in effect stops applying once the referenced object is no longer the same object, absent a specific 400.7a-k carve-out) is the applicable rule for this guard.As per path instructions,
crates/engine/**code implementing MTG game rules must carry a verifiedCR <number>: <description>annotation: "Verify every implementation of MTG game rules against the relevant Comprehensive Rules section before completion, and annotate rules-related code with a verified CR citation and description."📝 Suggested comment
pub(crate) fn transient_effect_is_live(state: &GameState, tce: &TransientContinuousEffect) -> bool { + // CR 400.7: a continuous effect "locked in" to a specific object stops applying + // once that object is no longer the same object (e.g. it left and re-entered the + // battlefield as a new incarnation), absent a specific 400.7a-k carve-out. if let Some(recipient) = tce.affected_recipient {🤖 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/src/game/layers.rs` around lines 4606 - 4615, Add a verified CR 400.7 annotation describing the affected_recipient incarnation-liveness guard in transient_effect_is_live, placing it alongside the guard and before or adjacent to the existing UntilHostLeavesPlay rule check. Keep the implementation unchanged and ensure the citation explains that the locked-in effect no longer applies when the referenced object is a different incarnation.Source: Path instructions
🤖 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 `@client/src/components/board/__tests__/BlockAssignmentLines.test.ts`:
- Around line 7-20: Update the test data in the filterVisibleBlockerPairs test
to include two pairs sharing blocker ID 10, such as [10, 100] and [10, 200], and
ensure blocker 10 is visible in the selected IDs. Assert that both pairs remain
in the filtered result, while preserving coverage of the existing
distinct-blocker cases.
In `@client/src/components/board/blockAssignmentVisibility.ts`:
- Around line 11-13: Remove the client-side controller-based filtering from the
assignment-visibility logic around the blocker-pair filter. Add or use an
engine/adapter API that returns blocker pairs already filtered for the viewing
player, and have the frontend render that engine-provided collection directly
without inspecting objects, controllers, or visiblePlayerIds.
In `@client/src/components/combat/useBlockerConstraints.ts`:
- Around line 51-64: Update the MustBlock handling in the constraint annotation
logic around blockerAssignments so it no longer derives satisfaction from
selectedAttackers or requiredAttackers. Reuse the engine-provided progress or
validation result for each constraint, ensuring the displayed status matches the
engine’s global solver and cannot report satisfied when declaration is rejected.
In `@client/src/components/combat/useBlockRequirements.ts`:
- Around line 49-64: Replace the local assignedPerAttacker counting and status
derivation in the useBlockRequirements flow with the engine-provided
preview/status for the currently selected blocker pairs. Use the existing engine
API or helper that evaluates those assignments, and pass through its statuses
without calculating, filtering, or reinterpreting requirement state in the
frontend.
In `@crates/engine/src/database/synthesis.rs`:
- Around line 5171-5172: Update the Provoke effect using
ForceBlockAttackerRef::Source to use Duration::UntilEndOfCombat instead of
Duration::UntilEndOfTurn, and revise its related description and tests to assert
the requirement ends at combat.
In `@crates/engine/src/game/ability_rw.rs`:
- Line 2834: Update the Effect::ForceBlock match arm to bind duration explicitly
instead of hiding it behind .., then invoke legacy_duration(duration) within its
legacy-effect classification. Keep the existing ForceBlock handling unchanged
otherwise and align it with the neighboring ForceAttack logic.
In `@crates/engine/src/game/combat.rs`:
- Around line 1327-1630: Bound BlockDeclarationConstraints::search_free with a
deterministic node budget so wide blocker-choice Cartesian products cannot
exhaustively stall declaration. Track visited search nodes across
best_free_declaration and stop exploring once the budget is reached, while
preserving score/length/lexicographic ordering for fully explored results. Add a
fallback that returns the best legal, tax-free declaration found so far (or the
existing empty declaration when none is found), including MustBeBlockedByAll and
lure requirements.
In `@crates/engine/src/game/coverage.rs`:
- Line 2356: Update the Effect::ForceBlock coverage handling to destructure its
named attacker and duration fields instead of discarding them with `..`. Include
both non-default attacker and duration details in the coverage output alongside
target, preserving the existing output behavior for default values.
In `@crates/engine/src/game/triggers.rs`:
- Around line 12198-12239: Add positive reach guards to the no_tolsimir_attack
test: verify parsed.condition is
Some(TriggerCondition::SourceAttackedThisCombat) and parsed.execute is present,
then confirm the same fixture produces a pending trigger after
attacking_incarnations_this_combat includes Tolsimir. Keep the existing empty
assertion so it specifically validates the intervening-if condition rather than
an upstream parser, matcher, or setup failure.
- Around line 12282-12308: The test currently assigns targets directly in the
pending-trigger loop, bypassing production target-selection. Replace the manual
TargetRef assignment with begin_pending_trigger_target_selection and apply the
resulting ChooseTarget/SelectTargets actions, asserting each trigger exposes its
own legal opponent-controlled-creature target prompt before pushing it onto the
stack. Ensure the scenario exercises the failure path through WaitingFor and the
normal apply pipeline, while preserving the attacker-to-blocker target mapping.
- Around line 1482-1502: Remove the unreachable! guard in the YouAttack branch
of the trigger processing flow. Reuse the result of matching_you_attack_pairs
directly, allowing non-AttackersDeclared events to produce an empty result
instead of panicking; preserve singleton_attack_events behavior for valid
attack-declaration events.
In `@crates/engine/src/parser/oracle_effect/imperative.rs`:
- Around line 11849-11861: Update the local parse_duration closure to accept an
optional trailing period after either supported duration phrase while remaining
all-consuming. Preserve the existing Duration mappings for “this turn if able”
and “this combat if able” so punctuated final clauses avoid the scan_contains
fallback and retain the attacker binding.
In `@crates/engine/src/parser/oracle_effect/subject.rs`:
- Around line 6806-6808: Strengthen the ForceBlock regression assertions: in
crates/engine/src/parser/oracle_effect/subject.rs lines 6806-6808, destructure
and assert the parsed duration and intended attacker value in addition to
target; in crates/engine/src/database/synthesis.rs lines 13579-13582, assert
attacker provenance is ForceBlockAttackerRef::Source and duration is
Duration::UntilEndOfCombat. Preserve each test’s existing target assertion.
---
Outside diff comments:
In `@client/src/components/board/ActionButton.tsx`:
- Around line 176-179: Update the pending-blocker reset effect in ActionButton
to also depend on the blocker prompt identity, such as waitingFor, not only
mode. Ensure pendingBlocker is cleared whenever a new DeclareBlockers prompt
replaces the previous one, while preserving the existing reset on mode changes.
In `@crates/engine/src/game/ability_scan.rs`:
- Around line 1146-1150: Update the ForceBlock arm in the effect scanner to
explicitly destructure every payload field instead of using .., and scan each
read-bearing attacker and duration value with the appropriate scanner, including
scan_duration. Preserve scanning target while combining all applicable axes so
ordering and loop classification remains fail-closed.
In `@crates/engine/src/parser/swallow_check.rs`:
- Around line 4092-4108: Remove TriggerCondition::SourceAttackedThisCombat from
the exemption matches in detect_duration_this_turn, leaving only variants whose
names contain ThisTurn. Preserve the existing exemptions and the separate
Effect::ForceBlock handling.
---
Nitpick comments:
In `@crates/engine/src/game/layers.rs`:
- Around line 4606-4615: Add a verified CR 400.7 annotation describing the
affected_recipient incarnation-liveness guard in transient_effect_is_live,
placing it alongside the guard and before or adjacent to the existing
UntilHostLeavesPlay rule check. Keep the implementation unchanged and ensure the
citation explains that the locked-in effect no longer applies when the
referenced object is a different incarnation.
In `@crates/engine/src/types/ability.rs`:
- Around line 11518-11529: Add a serde round-trip test near the existing
serialized-field tests for the ForceBlock variant, covering attacker set to
ForceBlockAttackerRef::EventSource and duration set to
Duration::UntilEndOfCombat. Also deserialize a legacy ForceBlock payload without
attacker or duration and assert the defaults preserve the generic attacker
behavior and UntilEndOfTurn lifetime.
🪄 Autofix (Beta)
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: 0067480f-bb18-47bd-8af1-f4fa856facb8
📒 Files selected for processing (60)
client/src/adapter/types.tsclient/src/components/board/ActionButton.tsxclient/src/components/board/BlockAssignmentLines.tsxclient/src/components/board/GroupedPermanent.tsxclient/src/components/board/__tests__/ActionButton.test.tsxclient/src/components/board/__tests__/BlockAssignmentLines.test.tsclient/src/components/board/blockAssignmentVisibility.tsclient/src/components/combat/BlockerConstraintBadges.tsxclient/src/components/combat/__tests__/combatRequirements.test.tsxclient/src/components/combat/useBlockRequirements.tsclient/src/components/combat/useBlockerConstraints.tsclient/src/hooks/useRafPositions.tsclient/src/i18n/locales/de/game.jsonclient/src/i18n/locales/en/game.jsonclient/src/i18n/locales/es/game.jsonclient/src/i18n/locales/fr/game.jsonclient/src/i18n/locales/it/game.jsonclient/src/i18n/locales/pl/game.jsonclient/src/i18n/locales/pt/game.jsonclient/src/stores/__tests__/uiStore.test.tsclient/src/stores/uiStore.tsclient/src/viewmodel/__tests__/blockerSorting.test.tsclient/src/viewmodel/blockerSorting.tscrates/engine/src/ai_support/candidates.rscrates/engine/src/database/synthesis.rscrates/engine/src/game/ability_rw.rscrates/engine/src/game/ability_scan.rscrates/engine/src/game/ability_utils.rscrates/engine/src/game/combat.rscrates/engine/src/game/combat_damage.rscrates/engine/src/game/coverage.rscrates/engine/src/game/effects/additional_phase.rscrates/engine/src/game/effects/double.rscrates/engine/src/game/effects/extra_turn.rscrates/engine/src/game/effects/force_block.rscrates/engine/src/game/effects/grant_extra_loyalty_activations.rscrates/engine/src/game/effects/player_counter.rscrates/engine/src/game/effects/reverse_turn_order.rscrates/engine/src/game/effects/skip_next_step.rscrates/engine/src/game/effects/skip_next_turn.rscrates/engine/src/game/effects/vote.rscrates/engine/src/game/engine_combat.rscrates/engine/src/game/layers.rscrates/engine/src/game/meld_tests.rscrates/engine/src/game/stack.rscrates/engine/src/game/triggers.rscrates/engine/src/parser/oracle_effect/imperative.rscrates/engine/src/parser/oracle_effect/mod.rscrates/engine/src/parser/oracle_effect/subject.rscrates/engine/src/parser/oracle_effect/tests.rscrates/engine/src/parser/oracle_ir/ast.rscrates/engine/src/parser/oracle_nom/target.rscrates/engine/src/parser/oracle_trigger.rscrates/engine/src/parser/oracle_trigger_tests.rscrates/engine/src/parser/swallow_check.rscrates/engine/src/types/ability.rscrates/engine/src/types/game_state.rscrates/engine/src/types/statics.rscrates/engine/tests/integration/the_chain_veil_loyalty_grants.rscrates/phase-ai/src/search.rs
💤 Files with no reviewable changes (1)
- client/src/components/board/GroupedPermanent.tsx
| return pairs.filter(([blockerId]) => { | ||
| const blockerController = objects[String(blockerId)]?.controller; | ||
| return blockerController == null || visiblePlayerIds.has(blockerController); |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
Move assignment-visibility filtering into the engine.
Lines 11-13 derive which blocker assignments are visible from game-object controller data in the client. Expose viewer-filtered blocker pairs through the engine/adapters instead, so every transport renders the same authorized state.
As per coding guidelines and path instructions, the frontend must only render engine-provided state and must not calculate, filter, derive, or reinterpret game data.
🤖 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 `@client/src/components/board/blockAssignmentVisibility.ts` around lines 11 -
13, Remove the client-side controller-based filtering from the
assignment-visibility logic around the blocker-pair filter. Add or use an
engine/adapter API that returns blocker pairs already filtered for the viewing
player, and have the frontend render that engine-provided collection directly
without inspecting objects, controllers, or visiblePlayerIds.
Sources: Coding guidelines, Path instructions
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
crates/engine/src/database/synthesis.rs (2)
5039-5043: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winMatch the complete Provoke force-block shape.
Using
..ignores bothattackerandduration. Because this predicate is used for Provoke idempotency, an unrelated optional attack trigger withParentTargetforce-blocking could be mistaken for Provoke and prevent the real Provoke trigger from being synthesized.Match
SourceandUntilEndOfCombatexplicitly.As per path instructions, typed exact-attacker axes must remain explicit and composable.
Proposed fix
- Effect::ForceBlock { - target: TargetFilter::ParentTarget, - .. - } + Effect::ForceBlock { + target: TargetFilter::ParentTarget, + attacker: Some(crate::types::ability::ForceBlockAttackerRef::Source), + duration: Duration::UntilEndOfCombat, + }🤖 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/src/database/synthesis.rs` around lines 5039 - 5043, Update the ForceBlock pattern in the Provoke idempotency predicate to match attacker as Source and duration as UntilEndOfCombat explicitly, while retaining TargetFilter::ParentTarget. Do not use `..`; preserve the existing typed exact-attacker axes so only the complete Provoke force-block shape is recognized.Source: Path instructions
13903-13918: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winAssert the complete forced-block effect, not independent fields.
Comparing only
attacker.object_iddoes not validate the incarnation, and the separate.any()duration check can pass because of an unrelated transient effect. Locate the effect containingMustBlockAttacker, then assert its fullObjectIncarnationRefandUntilEndOfCombatduration.As per path instructions, the force-block regression test must verify exact attacker/event-bound behavior.
🤖 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/src/database/synthesis.rs` around lines 13903 - 13918, The force-block regression assertions must validate one complete effect rather than independent properties. Update the test around the `forced` assertion to locate the transient continuous effect containing `MustBlockAttacker`, then assert that effect’s full `ObjectIncarnationRef` matches the provoking attacker and that its duration is `UntilEndOfCombat`; remove the separate unrelated duration check.Source: Path instructions
🤖 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.
Outside diff comments:
In `@crates/engine/src/database/synthesis.rs`:
- Around line 5039-5043: Update the ForceBlock pattern in the Provoke
idempotency predicate to match attacker as Source and duration as
UntilEndOfCombat explicitly, while retaining TargetFilter::ParentTarget. Do not
use `..`; preserve the existing typed exact-attacker axes so only the complete
Provoke force-block shape is recognized.
- Around line 13903-13918: The force-block regression assertions must validate
one complete effect rather than independent properties. Update the test around
the `forced` assertion to locate the transient continuous effect containing
`MustBlockAttacker`, then assert that effect’s full `ObjectIncarnationRef`
matches the provoking attacker and that its duration is `UntilEndOfCombat`;
remove the separate unrelated duration check.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 8baa41d2-ee63-4da4-a6b1-d38e0d2cb304
📒 Files selected for processing (18)
client/src/components/board/ActionButton.tsxclient/src/components/board/__tests__/ActionButton.test.tsxclient/src/components/board/__tests__/BlockAssignmentLines.test.tsclient/src/components/combat/BlockRequirementBadges.tsxclient/src/components/combat/BlockerConstraintBadges.tsxclient/src/components/combat/__tests__/combatRequirements.test.tsxclient/src/components/combat/useBlockRequirements.tsclient/src/components/combat/useBlockerConstraints.tscrates/engine/src/database/synthesis.rscrates/engine/src/game/ability_rw.rscrates/engine/src/game/ability_scan.rscrates/engine/src/game/coverage.rscrates/engine/src/game/layers.rscrates/engine/src/game/triggers.rscrates/engine/src/parser/oracle_effect/imperative.rscrates/engine/src/parser/oracle_effect/subject.rscrates/engine/src/parser/oracle_effect/tests.rscrates/engine/src/types/ability.rs
💤 Files with no reviewable changes (1)
- client/src/components/combat/BlockerConstraintBadges.tsx
🚧 Files skipped from review as they are similar to previous changes (10)
- client/src/components/board/tests/BlockAssignmentLines.test.ts
- crates/engine/src/game/layers.rs
- crates/engine/src/parser/oracle_effect/imperative.rs
- client/src/components/combat/tests/combatRequirements.test.tsx
- crates/engine/src/game/triggers.rs
- crates/engine/src/parser/oracle_effect/tests.rs
- crates/engine/src/game/coverage.rs
- crates/engine/src/types/ability.rs
- crates/engine/src/game/ability_rw.rs
- client/src/components/board/ActionButton.tsx
Summary by CodeRabbit
New Features
Bug Fixes