fix(ai): address community tactical decision reports#6637
Conversation
|
Warning Review limit reached
Next review available in: 38 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (17)
📝 WalkthroughWalkthroughThis PR adds printed-loyalty tracking and X-loyalty counter handling, read-only combat and counter previews, new combat and crew AI policies, MDFC-aware mulligan logic, candidate-aware decision scoring, and recent-thread filtering for Discord exports. ChangesEngine state and combat analysis
AI policy behavior
Supporting updates
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
Parse changes introduced by this PR✓ No card-parse changes detected. |
There was a problem hiding this comment.
Actionable comments posted: 16
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
crates/engine/src/game/effects/become_copy.rs (1)
145-150: 📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy liftAdd production-pipeline coverage for
SetStartingLoyalty.The changed tests only update
BackFaceDataliterals; they do not verify that this path seeds the overridden loyalty counters on entry or that a later copy observes the fixed value instead of the source’sXprovenance. Add an integration test undercrates/engine/tests/integration/and register it intests/integration/main.rs.🤖 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/effects/become_copy.rs` around lines 145 - 150, Add an integration test under crates/engine/tests/integration/ covering SetStartingLoyalty through the production copy pipeline: verify the override initializes loyalty counters on entry and that a subsequent copy observes the fixed loyalty value rather than inheriting the source’s X provenance. Register the new test module or case in tests/integration/main.rs, using existing integration-test patterns and symbols.Source: Path instructions
crates/engine/src/game/augment.rs (1)
383-392: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick winAdd a local CR citation for
printed_loyalty. This rules-sensitive assignment should carry aCR <number>: <description>note explaining why the merged permanent inherits the host’sprinted_loyalty.🤖 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/augment.rs` around lines 383 - 392, Add a local rules citation comment immediately above the printed_loyalty assignment in the CopiableValues construction, using the required “CR <number>: <description>” format and explaining why the merged permanent inherits the host’s printed_loyalty. Leave the assignment and surrounding fields unchanged.crates/engine/src/game/game_object.rs (1)
2327-2333: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winPreserve printed-loyalty provenance during base synchronization.
revert_layered_characteristics_to_basecallssync_missing_base_characteristics, but that helper does not backfillbase_printed_loyalty. A reachable object withprinted_loyalty = Some(...)andbase_printed_loyalty = Noneis therefore reset toNonehere, losing the X-loyalty/printed provenance during layer or zone reversion.Add the same fallback used for
loyaltyinsync_missing_base_characteristics.Proposed fix
if self.base_loyalty.is_none() && self.loyalty.is_some() { self.base_loyalty = self.loyalty; } + if self.base_printed_loyalty.is_none() && self.printed_loyalty.is_some() { + self.base_printed_loyalty = self.printed_loyalty.clone(); + }🤖 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/game_object.rs` around lines 2327 - 2333, Update sync_missing_base_characteristics to backfill base_printed_loyalty from printed_loyalty when the base value is absent, matching the existing loyalty fallback. Ensure revert_layered_characteristics_to_base preserves printed-loyalty provenance for reachable objects during synchronization.
🧹 Nitpick comments (6)
crates/phase-ai/tests/community_scenarios.rs (1)
49-54: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueHardcoded scenario id duplicates the data-driven spec mechanism.
The fixture already carries
expected_action_typeper scenario; a special-casedspec.idbranch inside the generic loop means the next scenario needing a stricter assertion adds anotherif. Sinceaction_type(action)on Line 55 already checksCastSpellfor this spec viaexpected_action_type, this assertion is largely redundant — if the intent is the stronger "must not be the land fast path" claim, encode it as an optional field onCommunityScenario(e.g.forbid_action_type) and drop the id branch. At minimum give the bareassert!a message namingspec.id.🤖 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/phase-ai/tests/community_scenarios.rs` around lines 49 - 54, The generic community-scenario loop should not special-case the "saheeli-legend-loop" identifier. Extend CommunityScenario with an optional forbidden action-type field, populate it for this fixture, and validate it alongside the existing action_type(action) expectation so future scenarios reuse the same mechanism; remove the hardcoded id branch.crates/phase-ai/src/policies/payment_selection.rs (1)
112-125: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueRedundant
kindcheck; the parameter is already destructured fromctx.state.Line 115 already binds
kind: PayCostKind::Discardfromctx.state.waiting_for, andcards != selectedpins the candidate;!matches!(kind, PayCostKind::Discard)on Line 123 re-tests the caller-passedkind, which the caller obtained fromctx.decision.waiting_for. Either drop thekindparameter entirely (derive it from state) or drop the destructure — carrying both invites the two to silently disagree.🤖 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/phase-ai/src/policies/payment_selection.rs` around lines 112 - 125, Remove the redundant kind validation in the payment-selection logic around the PayCostKind::Discard destructuring. Use a single source of truth: either remove the kind parameter and derive it from ctx.state.waiting_for, or stop destructuring kind there; ensure candidate, player, and discard-state validation remain consistent without allowing two kind values to diverge.crates/phase-ai/src/search.rs (1)
594-599: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value
actions.containsis a linear scan per candidate, and the baseline only coversCastSpell.Two smaller points on the same expression chain: the membership test is O(candidates × actions) on the path that exists precisely because the board is large, and
PlayLandnow carries a0.0baseline where the previous heuristic explicitly prioritized the land drop — so a high-mana-value spell can outrank the land drop on this path with nothing butLandSequencingPolicy's0.0/-1.5to differentiate. Confirm that is intended, sinceprefer_land_dropno longer participates here.🤖 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/phase-ai/src/search.rs` around lines 594 - 599, Update the candidate filtering around the actions collection to use constant-time membership checks, such as a set, instead of repeatedly scanning actions. Extend the baseline calculation in the map closure to preserve the previous land-drop heuristic for PlayLand, and verify the resulting ordering remains intentional now that prefer_land_drop is not used on this path.crates/phase-ai/src/policies/crew_timing.rs (1)
67-91: 🚀 Performance & Scalability | 🔵 Trivial | 🏗️ Heavy liftEnumerates and re-simulates every legal crew subset instead of short-circuiting.
crew_has_exact_combat_usecollects all engine-legalCrewVehiclesubset actions for this vehicle and clone+simulates each one viacrew_subset_has_exact_combat_useuntil it finds a working subset. On boards with several creatures able to satisfy the crew cost, the number of legal subsets can grow combinatorially, and the worst case — no subset yields combat use, which is precisely the scenario this policy exists to flag (e.g.real_vehicle_postcombat_crew_is_not_treated_as_an_attack_use) — pays the full enumeration + per-subset clone/fast-forward cost with no early exit.Since whether the crewed Vehicle can attack/block afterward generally depends only on it being crewed (not on which specific creatures supplied the power), a single arbitrary legal subset should be sufficient to answer the question, avoiding the combinatorial blowup.
♻️ Proposed short-circuit using a single legal subset
legal_actions_full(&selection_state) .0 .iter() - .filter(|action| { + .find(|action| { matches!( action, GameAction::CrewVehicle { vehicle_id: candidate_vehicle, creature_ids, } if *candidate_vehicle == vehicle_id && !creature_ids.is_empty() ) }) - .any(|subset| crew_subset_has_exact_combat_use(&selection_state, actor, vehicle_id, subset)) + .is_some_and(|subset| crew_subset_has_exact_combat_use(&selection_state, actor, vehicle_id, subset))As per path instructions, "Combination generators should short-circuit infeasible cases before enumerating."
🤖 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/phase-ai/src/policies/crew_timing.rs` around lines 67 - 91, Update crew_has_exact_combat_use to select one matching legal CrewVehicle action for vehicle_id with a non-empty creature_ids list, then pass only that subset to crew_subset_has_exact_combat_use. Remove the all-subsets iteration so the function avoids enumerating and re-simulating every legal crew combination while preserving the existing false result when no legal subset exists.Source: Path instructions
crates/engine/src/ai_support/combat_withdrawal.rs (1)
58-75: 🎯 Functional Correctness | 🔵 Trivial | ⚖️ Poor tradeoffOnly
WaitingFor::TargetSelectionis handled; triggered remove-from-combat prompts fall through.The sibling accessor added in
ai_support/mod.rsdeliberately covers bothTargetSelectionandTriggerTargetSelection, but this facade destructures only the former, so any triggered "remove target creature from combat" (a whole card class — e.g. ETB/attack triggers that fog a blocker) always yieldsNoneand the new combat-withdrawal policy never evaluates it. It fails closed, so this is a coverage gap rather than a bug, but the pattern-matching asymmetry withcurrent_target_selection_targetswill read as an oversight later.
TriggerTargetSelectionhas nopending_cast, so extending it means sourcing the ability/source from the trigger fields — worth doing deliberately or documenting the exclusion in the module doc comment.🤖 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/ai_support/combat_withdrawal.rs` around lines 58 - 75, Extend the combat-withdrawal matching around the current WaitingFor::TargetSelection destructure to deliberately handle TriggerTargetSelection as well, sourcing the ability and source identity from its trigger fields so the same validation applies to triggered remove-from-combat prompts. Keep compatibility with current_target_selection_targets, and ensure unrelated waiting states still return None.crates/engine/src/game/effects/counters.rs (1)
2814-2954: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winNo test covers the
Unsupportedarm.Applied / Transformed / Prevented / ChoiceRequired / stale-incarnation are all exercised, but the event-class-rewrite path (
ReplacementResult::Execute(_)→Unsupported) is never reached. That arm is the one whose contract consumers are explicitly told not to conflate withNone, so it's the arm most likely to regress silently — a replacement that redirectsAddCounterinto anotherProposedEventwould start returningApplied-shaped nonsense if the arm ordering ever changed.🤖 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/effects/counters.rs` around lines 2814 - 2954, Add a focused test for preview_counter_addition that installs a replacement for AddCounter rewriting it to a different ProposedEvent, then assert it returns Some(CounterAdditionPreview::Unsupported) rather than None or an Applied-shaped result. Keep the test focused on the event-class rewrite path represented by ReplacementResult::Execute(_) and verify the target/state setup required to reach that arm.
🤖 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 `@crates/engine/src/ai_support/combat_withdrawal.rs`:
- Around line 439-456: Update the combat state setup in the test around
combat_withdrawal_fact_for_current_target to represent a genuinely unblocked
attacker: clear blocker_to_attacker alongside blocker_assignments and set the
combatant’s blocked flag to false before installing target selection. Keep the
existing NoCombatPair assertion unchanged.
In `@crates/engine/src/game/effects/change_zone.rs`:
- Line 9498: Update the regression fixture’s printed-loyalty field from None to
Some(3) to represent the back face’s printed loyalty and ensure the test
exercises printed-loyalty propagation rather than the legacy loyalty fallback.
In `@crates/engine/src/game/effects/token_copy.rs`:
- Around line 492-496: Update the nearby copy-token regression tests to assert
both printed_loyalty and base_printed_loyalty equal
Some(PrintedLoyalty::Fixed(1)), covering the standard and liminal paths
identified by the related test sections. Ensure the assertions execute through
the production token-copy pipeline and fail if printed-loyalty propagation or
its base value regresses.
In `@crates/engine/src/game/game_object.rs`:
- Around line 188-189: Document the printed-loyalty fields in BackFaceData,
GameObject, and base_printed_loyalty with comments referencing CR 306.5b and
306.5c: printed loyalty is the face value that seeds loyalty counters on entry,
while live loyalty is the counter-derived battlefield value. Keep the existing
field types and serde attributes unchanged.
In `@crates/engine/tests/integration/tamiyo_inquisitive_student_flip.rs`:
- Around line 91-95: Update the planeswalker back-face fixture in the relevant
transform test so its `printed_loyalty` field is populated with 2, matching
`loyalty`. Keep the fixture production-shaped and ensure the test exercises the
printed-loyalty validation path rather than allowing an absent value.
In `@crates/phase-ai/src/policies/land_sequencing.rs`:
- Around line 322-336: Update
unavailable_non_bounce_land_in_hand_is_not_an_alternative to obtain its play
candidates through the production engine::ai_support candidate-generation path,
using a real play restriction or an exhausted
lands_played_this_turn/max_lands_per_turn state. Avoid manually passing a
candidate list that omits restricted; assert the verdict using the
engine-derived legal actions so the regression verifies production legality
filtering.
In `@crates/phase-ai/src/policies/mulligan/cedh_keepables.rs`:
- Around line 143-146: Exclude land sources that also have a spell face from the
land counts used by the mulligan thresholds, mirroring land_only_count and
has_spell_face. Update count_lands_in_hand in
crates/phase-ai/src/policies/mulligan/cedh_keepables.rs (lines 143-146) before
the land_count > 4 ForceMulligan check; apply the same filtering in
crates/phase-ai/src/policies/mulligan/aggro_keepables.rs (lines 58-63) for the
lands >= 5 flooded penalty and
crates/phase-ai/src/policies/mulligan/ramp_keepables.rs (lines 64-69) for the
ramp_count == 0 && land_count >= 3 penalty.
In `@crates/phase-ai/src/policies/payment_selection.rs`:
- Around line 176-191: Restrict playable_lands_after_stack_clears to clear the
forecast stack only when the sole stack entry is controlled by the specified
player; otherwise return no playable lands. Preserve the existing bounded
forecast behavior for an empty stack or a single player-controlled entry, while
continuing to reject stacks with multiple entries.
- Around line 783-813: Add positive reach-guards to the negative assertions in
production_discard_probe_does_not_treat_one_of_two_retained_lands_as_final,
multi_card_discard_does_not_penalize_a_land_when_another_land_survives_the_candidate,
and the related tests through the indicated range. Before asserting the detector
is false, verify the sibling replay reaches the tested path by asserting
playable_lands_after_stack_clears is non-empty; initialize phase and
active_player consistently with the production scenario when required.
- Around line 127-165: Reorder discard_spends_last_playable_land so it returns
false before the selected-payment and sibling probes when selected contains no
land, moving the existing is_land check ahead of apply_as_current_for_simulation
and playable_lands_after_stack_clears. Also reuse cached sibling-outcome results
for candidates sharing the same waiting_for context, rather than recomputing
full state clones for every candidate.
In `@crates/phase-ai/src/policies/self_cost_value.rs`:
- Around line 122-142: The doc comment for counter_replenishment_verdict
contradicts its match behavior. Update the comment to state that Prevented and
Unsupported receive the bounded penalty, while Applied, ChoiceRequired, and
Transformed remain neutral; describe Unsupported as the redirected or
unsupported event-class case rather than a completely rewritten counter event.
- Around line 1343-1386: Strengthen the table-driven test around the installed
replacement functions applied, transformed, and choice_required: invoke
self_counter_cost_preview directly for each corresponding setup and assert the
expected Applied, Transformed, or ChoiceRequired preview outcome, rather than
only checking the self_cost_value_na verdict reason. Keep the prevented row’s
penalized verdict assertion, and ensure the test still obtains each replacement
through the normal production setup so it exercises the previously unverified
preview-mismatch failure path.
In `@crates/phase-ai/src/policies/self_cost.rs`:
- Around line 112-120: The self-cost detection logic around the AbilityCost
match must also handle a single RemoveCounter component wrapped in
AbilityCost::Composite. Unwrap Composite only when it contains exactly one cost,
recursively apply the existing RemoveCounter matching logic to that component,
and continue rejecting composites with multiple costs or unrelated cost shapes.
In `@crates/phase-ai/src/search.rs`:
- Around line 4241-4248: The test
large_board_main_phase_fast_action_uses_bounded_policy_scoring must explicitly
validate the large-board gate. Assert after make_state and creature setup that
state.objects.len() meets LARGE_BOARD_FAST_PRIORITY_OBJECTS, and add a sibling
case with fewer objects that invokes the same fast-action path and asserts None,
isolating behavior below the threshold.
- Around line 591-619: Update fast_priority_action’s candidate-selection
pipeline to apply the same gate_candidates safety filtering used by
score_candidates_core before scoring or selecting an action. Ensure
cancelled_casts, pending_activations, MAX_ACTIVATIONS_PER_SOURCE_PER_TURN, and
MAX_CASTS_OF_SAME_CARD_PER_TURN are enforced on this bounded path, preferably by
reusing gate_candidates or extracting a shared predicate used by both paths.
- Around line 90-94: The large-board fast-path threshold currently counts all
state objects, causing ordinary games to bypass normal search; change it to use
a battlefield-sized measure or restore a genuinely pathological threshold, and
apply that same measure at both call sites in search.rs (lines 305 and 574). In
the bounded candidate-selection path around the fast-priority logic, reuse the
shared gating/filter predicate from score_candidates_core before selecting an
action so cancelled casts, pending activations, and per-turn activation or
same-card cast caps remain enforced.
---
Outside diff comments:
In `@crates/engine/src/game/augment.rs`:
- Around line 383-392: Add a local rules citation comment immediately above the
printed_loyalty assignment in the CopiableValues construction, using the
required “CR <number>: <description>” format and explaining why the merged
permanent inherits the host’s printed_loyalty. Leave the assignment and
surrounding fields unchanged.
In `@crates/engine/src/game/effects/become_copy.rs`:
- Around line 145-150: Add an integration test under
crates/engine/tests/integration/ covering SetStartingLoyalty through the
production copy pipeline: verify the override initializes loyalty counters on
entry and that a subsequent copy observes the fixed loyalty value rather than
inheriting the source’s X provenance. Register the new test module or case in
tests/integration/main.rs, using existing integration-test patterns and symbols.
In `@crates/engine/src/game/game_object.rs`:
- Around line 2327-2333: Update sync_missing_base_characteristics to backfill
base_printed_loyalty from printed_loyalty when the base value is absent,
matching the existing loyalty fallback. Ensure
revert_layered_characteristics_to_base preserves printed-loyalty provenance for
reachable objects during synchronization.
---
Nitpick comments:
In `@crates/engine/src/ai_support/combat_withdrawal.rs`:
- Around line 58-75: Extend the combat-withdrawal matching around the current
WaitingFor::TargetSelection destructure to deliberately handle
TriggerTargetSelection as well, sourcing the ability and source identity from
its trigger fields so the same validation applies to triggered
remove-from-combat prompts. Keep compatibility with
current_target_selection_targets, and ensure unrelated waiting states still
return None.
In `@crates/engine/src/game/effects/counters.rs`:
- Around line 2814-2954: Add a focused test for preview_counter_addition that
installs a replacement for AddCounter rewriting it to a different ProposedEvent,
then assert it returns Some(CounterAdditionPreview::Unsupported) rather than
None or an Applied-shaped result. Keep the test focused on the event-class
rewrite path represented by ReplacementResult::Execute(_) and verify the
target/state setup required to reach that arm.
In `@crates/phase-ai/src/policies/crew_timing.rs`:
- Around line 67-91: Update crew_has_exact_combat_use to select one matching
legal CrewVehicle action for vehicle_id with a non-empty creature_ids list, then
pass only that subset to crew_subset_has_exact_combat_use. Remove the
all-subsets iteration so the function avoids enumerating and re-simulating every
legal crew combination while preserving the existing false result when no legal
subset exists.
In `@crates/phase-ai/src/policies/payment_selection.rs`:
- Around line 112-125: Remove the redundant kind validation in the
payment-selection logic around the PayCostKind::Discard destructuring. Use a
single source of truth: either remove the kind parameter and derive it from
ctx.state.waiting_for, or stop destructuring kind there; ensure candidate,
player, and discard-state validation remain consistent without allowing two kind
values to diverge.
In `@crates/phase-ai/src/search.rs`:
- Around line 594-599: Update the candidate filtering around the actions
collection to use constant-time membership checks, such as a set, instead of
repeatedly scanning actions. Extend the baseline calculation in the map closure
to preserve the previous land-drop heuristic for PlayLand, and verify the
resulting ordering remains intentional now that prefer_land_drop is not used on
this path.
In `@crates/phase-ai/tests/community_scenarios.rs`:
- Around line 49-54: The generic community-scenario loop should not special-case
the "saheeli-legend-loop" identifier. Extend CommunityScenario with an optional
forbidden action-type field, populate it for this fixture, and validate it
alongside the existing action_type(action) expectation so future scenarios reuse
the same mechanism; remove the hardcoded id branch.
🪄 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: 6a998eb5-6c87-472d-a3ac-34449307b83f
📒 Files selected for processing (77)
crates/engine/src/ai_support/candidates.rscrates/engine/src/ai_support/combat_withdrawal.rscrates/engine/src/ai_support/mod.rscrates/engine/src/game/augment.rscrates/engine/src/game/casting_tests.rscrates/engine/src/game/combat_damage.rscrates/engine/src/game/day_night.rscrates/engine/src/game/effects/become_copy.rscrates/engine/src/game/effects/change_zone.rscrates/engine/src/game/effects/counters.rscrates/engine/src/game/effects/flip_coin.rscrates/engine/src/game/effects/flip_permanent.rscrates/engine/src/game/effects/prepare.rscrates/engine/src/game/effects/set_room_door_lock.rscrates/engine/src/game/effects/token.rscrates/engine/src/game/effects/token_copy.rscrates/engine/src/game/effects/transform_effect.rscrates/engine/src/game/engine.rscrates/engine/src/game/engine_debug.rscrates/engine/src/game/engine_mdfc_land_tests.rscrates/engine/src/game/engine_tests.rscrates/engine/src/game/flip.rscrates/engine/src/game/game_object.rscrates/engine/src/game/layers.rscrates/engine/src/game/printed_cards.rscrates/engine/src/game/replacement.rscrates/engine/src/game/specialize.rscrates/engine/src/game/stack.rscrates/engine/src/game/transform.rscrates/engine/src/game/triggers.rscrates/engine/src/game/zone_pipeline.rscrates/engine/src/game/zones.rscrates/engine/src/types/ability.rscrates/engine/src/types/card.rscrates/engine/src/types/game_state.rscrates/engine/src/types/layers.rscrates/engine/tests/integration/azors_gateway_transform_condition.rscrates/engine/tests/integration/combo_infinite_pile.rscrates/engine/tests/integration/craft_tithing_blade_transform.rscrates/engine/tests/integration/integration_adventure.rscrates/engine/tests/integration/issue_2425_fable_chapter_iii_transform.rscrates/engine/tests/integration/issue_4001_frolicking_familiar_adventure_instant.rscrates/engine/tests/integration/issue_4239_nissa_steward_x_loyalty.rscrates/engine/tests/integration/issue_5326_avatar_aang_transform.rscrates/engine/tests/integration/issue_691_sheoldred_saga_lore.rscrates/engine/tests/integration/kamigawa_flip_cards.rscrates/engine/tests/integration/main.rscrates/engine/tests/integration/room_door_lock_unlock.rscrates/engine/tests/integration/rules/battle.rscrates/engine/tests/integration/specialize_runtime.rscrates/engine/tests/integration/std_s07_batch5b.rscrates/engine/tests/integration/stolen_goodies_zero_targets.rscrates/engine/tests/integration/tamiyo_inquisitive_student_flip.rscrates/engine/tests/integration/wedding_announcement_transform.rscrates/phase-ai/fixtures/scenarios/community-scenarios.jsoncrates/phase-ai/src/config.rscrates/phase-ai/src/policies/combat_withdrawal.rscrates/phase-ai/src/policies/crew_timing.rscrates/phase-ai/src/policies/land_sequencing.rscrates/phase-ai/src/policies/mod.rscrates/phase-ai/src/policies/mulligan/aggro_keepables.rscrates/phase-ai/src/policies/mulligan/aristocrats_keepables.rscrates/phase-ai/src/policies/mulligan/cedh_keepables.rscrates/phase-ai/src/policies/mulligan/keepables_by_land_count.rscrates/phase-ai/src/policies/mulligan/landfall_keepables.rscrates/phase-ai/src/policies/mulligan/mod.rscrates/phase-ai/src/policies/mulligan/plus_one_counters_keepables.rscrates/phase-ai/src/policies/mulligan/ramp_keepables.rscrates/phase-ai/src/policies/mulligan/spellslinger_keepables.rscrates/phase-ai/src/policies/mulligan/tokens_wide_keepables.rscrates/phase-ai/src/policies/payment_selection.rscrates/phase-ai/src/policies/registry.rscrates/phase-ai/src/policies/self_cost.rscrates/phase-ai/src/policies/self_cost_value.rscrates/phase-ai/src/search.rscrates/phase-ai/tests/community_scenarios.rsscripts/export-discord-threads.mjs
Summary by CodeRabbit
New Features
Bug Fixes