1use crate::types::{Beat, Effect, EncounterResult};
8use serde::Serialize;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
12pub enum SchemePhase {
13 Preparation,
15 Execution,
17 Resolved,
19}
20
21#[derive(Debug, Clone, Serialize)]
28pub struct BackgroundScheme {
29 pub initiator: String,
31 pub target: String,
33 pub scheme_type: String,
35 pub progress: f64,
37 pub threshold: f64,
39 pub phase: SchemePhase,
41 pub advantages: Vec<String>,
43}
44
45impl BackgroundScheme {
46 pub fn new(initiator: String, target: String, scheme_type: String, threshold: f64) -> Self {
48 Self {
49 initiator,
50 target,
51 scheme_type,
52 progress: 0.0,
53 threshold,
54 phase: SchemePhase::Preparation,
55 advantages: Vec::new(),
56 }
57 }
58
59 pub fn advance(&mut self, delta: f64) -> bool {
65 if self.phase == SchemePhase::Resolved {
66 return false;
67 }
68 self.progress = (self.progress + delta).max(0.0);
69 if self.phase == SchemePhase::Preparation && self.progress > 0.0 {
70 self.phase = SchemePhase::Execution;
71 }
72 if self.progress >= self.threshold {
73 self.phase = SchemePhase::Resolved;
74 return true;
75 }
76 false
77 }
78
79 pub fn add_advantage(&mut self, label: String) {
81 self.advantages.push(label);
82 }
83
84 pub fn to_result(
94 &self,
95 success_effects: Vec<Effect>,
96 failure_effects: Vec<Effect>,
97 ) -> EncounterResult {
98 let success = self.phase == SchemePhase::Resolved;
99 let effects = if success {
100 success_effects
101 } else {
102 failure_effects
103 };
104 let mut result = EncounterResult::new(
105 vec![self.initiator.clone(), self.target.clone()],
106 Some(self.scheme_type.clone()),
107 );
108 let beat = Beat {
109 actor: self.initiator.clone(),
110 action: format!("{}_resolution", self.scheme_type),
111 accepted: success,
112 effects,
113 };
114 result.push_beat(beat);
115
116 if let Some(esc) = crate::escalation::check_escalation(
120 result.beats.last().unwrap(),
121 result.beats.len() - 1,
122 ) {
123 result.escalation_requested = true;
124 result.escalation_requests.push(esc);
125 }
126
127 result
128 }
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134 use crate::escalation::FidelityHint;
135 use crate::types::Effect;
136
137 #[test]
138 fn scheme_starts_in_preparation() {
139 let state = BackgroundScheme::new(
140 "alice".to_string(),
141 "bob".to_string(),
142 "assassination".to_string(),
143 10.0,
144 );
145 assert_eq!(state.phase, SchemePhase::Preparation);
146 assert_eq!(state.progress, 0.0);
147 }
148
149 #[test]
150 fn advance_transitions_to_execution() {
151 let mut state = BackgroundScheme::new(
152 "alice".to_string(),
153 "bob".to_string(),
154 "assassination".to_string(),
155 10.0,
156 );
157 let resolved = state.advance(2.0);
158 assert!(!resolved);
159 assert_eq!(state.phase, SchemePhase::Execution);
160 assert!((state.progress - 2.0).abs() < f64::EPSILON);
161 }
162
163 #[test]
164 fn advance_resolves_at_threshold() {
165 let mut state = BackgroundScheme::new(
166 "alice".to_string(),
167 "bob".to_string(),
168 "blackmail".to_string(),
169 10.0,
170 );
171 let first = state.advance(5.0);
172 assert!(!first);
173 assert_eq!(state.phase, SchemePhase::Execution);
174
175 let second = state.advance(5.0);
176 assert!(second);
177 assert_eq!(state.phase, SchemePhase::Resolved);
178 }
179
180 #[test]
181 fn setback_cannot_go_below_zero() {
182 let mut state = BackgroundScheme::new(
183 "alice".to_string(),
184 "bob".to_string(),
185 "seduction".to_string(),
186 10.0,
187 );
188 state.advance(3.0);
189 state.advance(-5.0);
190 assert_eq!(state.progress, 0.0);
191 }
192
193 #[test]
194 fn to_result_produces_one_beat_and_escalates() {
195 let mut state = BackgroundScheme::new(
196 "alice".to_string(),
197 "bob".to_string(),
198 "spy_ring".to_string(),
199 5.0,
200 );
201 state.advance(5.0);
202 assert_eq!(state.phase, SchemePhase::Resolved);
203
204 let success_effects = vec![Effect::RelationshipDelta {
205 axis: "trust".to_string(),
206 from: "alice".to_string(),
207 to: "bob".to_string(),
208 delta: -0.5,
209 }];
210 let failure_effects = vec![];
211
212 let result = state.to_result(success_effects, failure_effects);
213 assert_eq!(result.beats.len(), 1);
214 assert!(result.beats[0].accepted);
215 assert_eq!(result.beats[0].effects.len(), 1);
216 assert_eq!(result.relationship_deltas.len(), 1);
217
218 assert!(result.escalation_requested);
220 assert_eq!(result.escalation_requests.len(), 1);
221 assert_eq!(
222 result.escalation_requests[0].suggested_fidelity,
223 FidelityHint::Active
224 );
225 }
226
227 #[test]
228 fn to_result_does_not_escalate_on_mild_effects() {
229 let mut state = BackgroundScheme::new(
230 "alice".to_string(),
231 "bob".to_string(),
232 "courtship".to_string(),
233 1.0,
234 );
235 state.advance(1.0);
236
237 let success_effects = vec![Effect::RelationshipDelta {
238 axis: "affection".to_string(),
239 from: "bob".to_string(),
240 to: "alice".to_string(),
241 delta: 0.1,
242 }];
243 let result = state.to_result(success_effects, vec![]);
244
245 assert!(!result.escalation_requested);
246 assert!(result.escalation_requests.is_empty());
247 }
248}