rabukasim / engine_rust_src /src /response_flow_tests.rs
trioskosmos's picture
Upload folder using huggingface_hub
463f868 verified
//! Tests for Response phase flow, ability pausing, and resumption.
//! These tests verify that the engine correctly captures state and resumes
//! execution after player input.
use crate::core::logic::card_db::LOGIC_ID_MASK;
use crate::test_helpers::create_test_state;
use crate::core::logic::*;
fn create_test_db() -> CardDatabase {
let mut db = CardDatabase::default();
// Member 500 with O_RECOVER_MEMBER ability
let m = MemberCard {
card_id: 500,
abilities: vec![Ability {
trigger: TriggerType::Activated, // or whatever triggers manually
bytecode: vec![O_RECOVER_MEMBER, 1, 0, 0, 0, O_RETURN, 0, 0, 0, 0],
..Default::default()
}],
..Default::default()
};
db.members.insert(500, m.clone());
db.members.insert(
99,
MemberCard {
card_id: 99,
..Default::default()
},
);
db.members_vec.resize(8431, None);
db.members_vec[(500 as usize) & LOGIC_ID_MASK as usize] = Some(db.members[&500].clone());
db.members_vec[(99 as usize) & LOGIC_ID_MASK as usize] = Some(db.members[&99].clone());
db
}
/// Verifies that an ability requiring a target selection correctly pauses and enters Response phase.
#[test]
fn test_ability_pause_for_selection() {
let db = create_test_db();
let mut state = create_test_state();
state.players[0].stage[0] = 500;
state.players[0].discard = vec![99, 99].into(); // ID 99 is in our test DB
println!(
"DEBUG: Discard before recovery: {:?}",
state.players[0].discard
);
// Activate ability 0 on slot 0
state.activate_ability(&db, 0, 0).unwrap();
// Should be in Response phase, waiting for choice
assert_eq!(state.phase, Phase::Response);
assert_eq!(
state
.interaction_stack
.last()
.map(|p| p.effect_opcode)
.unwrap_or(0),
O_RECOVER_MEMBER
);
assert!(!state.interaction_stack.is_empty());
assert_eq!(state.players[0].looked_cards.len(), 2);
}
/// Verifies that providing a choice correctly resumes the ability and transitions back to Main phase.
#[test]
fn test_ability_resumption_after_choice() {
let db = create_test_db();
let mut state = create_test_state();
state.players[0].stage[0] = 500;
state.players[0].discard = vec![99, 99].into();
// 1. Pause
state.activate_ability(&db, 0, 0).unwrap();
assert_eq!(state.phase, Phase::Response);
// 2. Resume with choice (Select Card 99 at index 0)
state.activate_ability_with_choice(&db, 0, 0, 0, 0).unwrap();
// 3. Verify results
assert_eq!(state.phase, Phase::Main);
assert!(state.players[0].hand.contains(&99));
assert_eq!(state.players[0].discard.len(), 1);
assert!(state.interaction_stack.is_empty());
}
/// Verifies that multiple activations or nested triggers correctly manage the pending context.
#[test]
fn test_nested_trigger_flow_simple() {
let db = create_test_db();
let mut state = create_test_state();
// Trigger depth acts as a recursion counter. It should return to its initial state
// after the trigger chain finishes.
let initial_depth = state.trigger_depth;
let ctx = AbilityContext {
player_id: 0,
..Default::default()
};
state.trigger_abilities(&db, TriggerType::TurnStart, &ctx);
// Verify that depth returned to its starting value
assert_eq!(state.trigger_depth, initial_depth);
}
#[test]
fn test_nested_suspension_preserves_original_phase() {
let db = CardDatabase::default();
let mut state = create_test_state();
// 1. Initial State: Performance results are being processed
state.phase = Phase::LiveResult;
let ctx = AbilityContext {
player_id: 0,
..Default::default()
};
// 2. First suspension (e.g., an optional cost prompt)
// We'll use O_MOVE_TO_DISCARD to simulate an optional discard cost
crate::core::logic::interpreter::suspension::suspend_interaction(
&mut state,
&db,
&ctx,
10,
O_MOVE_TO_DISCARD,
0,
crate::core::enums::ChoiceType::Optional,
"",
0,
-1,
Vec::new(),
Vec::new(),
);
assert_eq!(state.phase, Phase::Response);
assert_eq!(state.interaction_stack.len(), 1);
assert_eq!(state.interaction_stack[0].original_phase, Phase::LiveResult);
// 3. Second suspension (nested, e.g., the effect after cost is paid)
// While still in Phase::Response, another suspension occurs.
crate::core::logic::interpreter::suspension::suspend_interaction(
&mut state,
&db,
&ctx,
20,
O_LOOK_AND_CHOOSE,
0,
crate::core::enums::ChoiceType::LookAndChoose,
"",
0,
1,
Vec::new(),
Vec::new(),
);
assert_eq!(state.phase, Phase::Response);
assert_eq!(state.interaction_stack.len(), 2);
// CRITICAL CHECK: The second suspension must have captured LiveResult, NOT Response.
assert_eq!(
state.interaction_stack[1].original_phase,
Phase::LiveResult,
"Nested suspension failed to propagate the true original phase (LiveResult)!"
);
}
#[test]
fn test_nested_suspension_real_flow() {
let mut db = create_test_db();
// Member 501 with nested suspension ability:
// Opcode 22 (O_TAP_MEMBER) with attr 2 (Optional)
// Then Opcode 17 (O_RECOVER_MEMBER)
db.members.insert(
501,
MemberCard {
card_id: 501,
abilities: vec![Ability {
trigger: TriggerType::Activated,
bytecode: vec![
O_TAP_MEMBER,
0,
2,
0,
0,
O_RECOVER_MEMBER,
1,
0,
0,
0,
O_RETURN,
0,
0,
0,
0,
],
..Default::default()
}],
..Default::default()
},
);
let logic_id = (501 as usize) & LOGIC_ID_MASK as usize;
db.members_vec.resize(logic_id + 1, None);
db.members_vec[logic_id] = Some(db.members[&501].clone());
let mut state = create_test_state();
state.players[0].stage[0] = 501;
state.players[0].discard = vec![99, 99].into();
state.phase = Phase::LiveResult;
// 1. Initial activation -> First suspension (Optional Tap)
state.activate_ability(&db, 0, 0).unwrap();
assert_eq!(state.phase, Phase::Response);
assert_eq!(state.interaction_stack.len(), 1);
assert_eq!(state.interaction_stack[0].original_phase, Phase::LiveResult);
// 2. Resume first suspension -> This triggers the second suspension (Recover Selection)
// Choice 0 = "Yes" for the optional tap
state.activate_ability_with_choice(&db, 0, 0, 0, 0).unwrap();
// Should still be in Response phase, waiting for recover choice
assert_eq!(state.phase, Phase::Response);
assert_eq!(
state.interaction_stack.len(),
1,
"Should have 1 pending interaction (the nested one)"
);
// THE CRITICAL CHECK:
assert_eq!(
state.interaction_stack[0].original_phase,
Phase::LiveResult,
"Nested suspension lost the original LiveResult phase!"
);
}