Skip to content
Merged
83 changes: 83 additions & 0 deletions crates/engine/src/game/effects/explore.rs
Original file line number Diff line number Diff line change
Expand Up @@ -394,11 +394,13 @@ pub fn handle_choice(
#[cfg(test)]
mod tests {
use super::*;
use crate::game::engine;
use crate::game::zones::create_object;
use crate::types::ability::{
AbilityDefinition, AbilityKind, ControllerRef, Effect, QuantityExpr, ReplacementDefinition,
TargetFilter, TargetRef, TypedFilter,
};
use crate::types::actions::GameAction;
use crate::types::identifiers::{CardId, ObjectId};
use crate::types::keywords::Keyword;
use crate::types::player::PlayerId;
Expand Down Expand Up @@ -946,4 +948,85 @@ mod tests {
other => panic!("expected ExploreChoice, got {other:?}"),
}
}

/// CR 701.44a (issue #1151): Jadelight Ranger explores twice — the second
/// explore must resume after the first explore's nonland DigChoice completes.
#[test]
fn chained_explore_resumes_after_nonland_dig_choice() {
let mut state = GameState::new_two_player(42);
let ranger = create_object(
&mut state,
CardId(1),
PlayerId(0),
"Jadelight Ranger".to_string(),
Zone::Battlefield,
);
state
.objects
.get_mut(&ranger)
.unwrap()
.card_types
.core_types
.push(CoreType::Creature);

let bolt_a = create_object(
&mut state,
CardId(2),
PlayerId(0),
"Lightning Bolt".to_string(),
Zone::Library,
);
let bolt_b = create_object(
&mut state,
CardId(3),
PlayerId(0),
"Shock".to_string(),
Zone::Library,
);
state.players[0].library = vec![bolt_a, bolt_b].into();

let second_explore = ResolvedAbility::new(Effect::Explore, vec![], ranger, PlayerId(0));
let ability = ResolvedAbility::new(Effect::Explore, vec![], ranger, PlayerId(0))
.sub_ability(second_explore);
let mut events = Vec::new();
resolve_ability_chain(&mut state, &ability, &mut events, 0).unwrap();

assert!(
matches!(state.waiting_for, WaitingFor::DigChoice { .. }),
"first explore should pause on DigChoice, got {:?}",
state.waiting_for
);
assert!(
state.pending_continuation.is_some(),
"second explore must be stashed while first explore waits for DigChoice"
);
assert_eq!(
state.objects[&ranger]
.counters
.get(&CounterType::Plus1Plus1)
.copied(),
Some(1),
"first explore should add one +1/+1 counter"
);

let WaitingFor::DigChoice { cards, .. } = state.waiting_for.clone() else {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

[MEDIUM] Avoid unnecessary cloning of WaitingFor by matching on a reference.

Why it matters: WaitingFor is a large enum containing heap-allocated collections (like Vec<ObjectId> and Vec<String>). Cloning it just to extract a single ObjectId from the cards vector is inefficient.

Suggested fix: Match on &state.waiting_for instead of state.waiting_for.clone().

Suggested change
let WaitingFor::DigChoice { cards, .. } = state.waiting_for.clone() else {
let WaitingFor::DigChoice { cards, .. } = &state.waiting_for else {

unreachable!();
};
engine::apply_as_current(
&mut state,
GameAction::SelectCards {
cards: vec![cards[0]],
},
)
.unwrap();

assert_eq!(
state.objects[&ranger]
.counters
.get(&CounterType::Plus1Plus1)
.copied(),
Some(2),
"second explore should add another +1/+1 counter after DigChoice resolves"
);
}
}
Loading