use crate::types::{Beat, Effect, EncounterResult};
use serde::Serialize;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
pub enum SchemePhase {
Preparation,
Execution,
Resolved,
}
#[derive(Debug, Clone, Serialize)]
pub struct BackgroundScheme {
pub initiator: String,
pub target: String,
pub scheme_type: String,
pub progress: f64,
pub threshold: f64,
pub phase: SchemePhase,
pub advantages: Vec<String>,
}
impl BackgroundScheme {
pub fn new(initiator: String, target: String, scheme_type: String, threshold: f64) -> Self {
Self {
initiator,
target,
scheme_type,
progress: 0.0,
threshold,
phase: SchemePhase::Preparation,
advantages: Vec::new(),
}
}
pub fn advance(&mut self, delta: f64) -> bool {
if self.phase == SchemePhase::Resolved {
return false;
}
self.progress = (self.progress + delta).max(0.0);
if self.phase == SchemePhase::Preparation && self.progress > 0.0 {
self.phase = SchemePhase::Execution;
}
if self.progress >= self.threshold {
self.phase = SchemePhase::Resolved;
return true;
}
false
}
pub fn add_advantage(&mut self, label: String) {
self.advantages.push(label);
}
pub fn to_result(
&self,
success_effects: Vec<Effect>,
failure_effects: Vec<Effect>,
) -> EncounterResult {
let success = self.phase == SchemePhase::Resolved;
let effects = if success {
success_effects
} else {
failure_effects
};
let mut result = EncounterResult::new(
vec![self.initiator.clone(), self.target.clone()],
Some(self.scheme_type.clone()),
);
let beat = Beat {
actor: self.initiator.clone(),
action: format!("{}_resolution", self.scheme_type),
accepted: success,
effects,
};
result.push_beat(beat);
if let Some(esc) = crate::escalation::check_escalation(
result.beats.last().unwrap(),
result.beats.len() - 1,
) {
result.escalation_requested = true;
result.escalation_requests.push(esc);
}
result
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::escalation::FidelityHint;
use crate::types::Effect;
#[test]
fn scheme_starts_in_preparation() {
let state = BackgroundScheme::new(
"alice".to_string(),
"bob".to_string(),
"assassination".to_string(),
10.0,
);
assert_eq!(state.phase, SchemePhase::Preparation);
assert_eq!(state.progress, 0.0);
}
#[test]
fn advance_transitions_to_execution() {
let mut state = BackgroundScheme::new(
"alice".to_string(),
"bob".to_string(),
"assassination".to_string(),
10.0,
);
let resolved = state.advance(2.0);
assert!(!resolved);
assert_eq!(state.phase, SchemePhase::Execution);
assert!((state.progress - 2.0).abs() < f64::EPSILON);
}
#[test]
fn advance_resolves_at_threshold() {
let mut state = BackgroundScheme::new(
"alice".to_string(),
"bob".to_string(),
"blackmail".to_string(),
10.0,
);
let first = state.advance(5.0);
assert!(!first);
assert_eq!(state.phase, SchemePhase::Execution);
let second = state.advance(5.0);
assert!(second);
assert_eq!(state.phase, SchemePhase::Resolved);
}
#[test]
fn setback_cannot_go_below_zero() {
let mut state = BackgroundScheme::new(
"alice".to_string(),
"bob".to_string(),
"seduction".to_string(),
10.0,
);
state.advance(3.0);
state.advance(-5.0);
assert_eq!(state.progress, 0.0);
}
#[test]
fn to_result_produces_one_beat_and_escalates() {
let mut state = BackgroundScheme::new(
"alice".to_string(),
"bob".to_string(),
"spy_ring".to_string(),
5.0,
);
state.advance(5.0);
assert_eq!(state.phase, SchemePhase::Resolved);
let success_effects = vec![Effect::RelationshipDelta {
axis: "trust".to_string(),
from: "alice".to_string(),
to: "bob".to_string(),
delta: -0.5,
}];
let failure_effects = vec![];
let result = state.to_result(success_effects, failure_effects);
assert_eq!(result.beats.len(), 1);
assert!(result.beats[0].accepted);
assert_eq!(result.beats[0].effects.len(), 1);
assert_eq!(result.relationship_deltas.len(), 1);
assert!(result.escalation_requested);
assert_eq!(result.escalation_requests.len(), 1);
assert_eq!(
result.escalation_requests[0].suggested_fidelity,
FidelityHint::Active
);
}
#[test]
fn to_result_does_not_escalate_on_mild_effects() {
let mut state = BackgroundScheme::new(
"alice".to_string(),
"bob".to_string(),
"courtship".to_string(),
1.0,
);
state.advance(1.0);
let success_effects = vec![Effect::RelationshipDelta {
axis: "affection".to_string(),
from: "bob".to_string(),
to: "alice".to_string(),
delta: 0.1,
}];
let result = state.to_result(success_effects, vec![]);
assert!(!result.escalation_requested);
assert!(result.escalation_requests.is_empty());
}
}