Skip to main content

encounter/resolution/
single.rs

1//! CiF-style one-shot resolution protocol.
2
3use crate::scoring::{AcceptanceEval, ScoredAffordance};
4use crate::types::{Beat, EncounterResult};
5
6/// Single-exchange resolution: initiator picks highest-scored action,
7/// responder accepts or rejects, effects fire, one beat is recorded.
8pub struct SingleExchange;
9
10impl SingleExchange {
11    /// Generic over P (precondition type). Protocol never inspects P.
12    pub fn resolve<P>(
13        &self,
14        initiator: &str,
15        responder: &str,
16        available: &[ScoredAffordance<P>],
17        acceptance: &dyn AcceptanceEval<P>,
18    ) -> EncounterResult {
19        let mut result = EncounterResult::new(vec![initiator.into(), responder.into()], None);
20
21        let Some(best) = available.iter().max_by(|a, b| {
22            a.score
23                .partial_cmp(&b.score)
24                .unwrap_or(std::cmp::Ordering::Equal)
25        }) else {
26            return result;
27        };
28
29        let accepted = acceptance.evaluate(responder, best);
30        let effects = if accepted {
31            best.entry.spec.effects_on_accept.clone()
32        } else {
33            best.entry.spec.effects_on_reject.clone()
34        };
35
36        let beat = Beat {
37            actor: initiator.into(),
38            action: best.entry.spec.name.clone(),
39            accepted,
40            effects,
41        };
42        result.push_beat(beat);
43        if let Some(esc) = crate::escalation::check_escalation(
44            result.beats.last().unwrap(),
45            result.beats.len() - 1,
46        ) {
47            result.escalation_requested = true;
48            result.escalation_requests.push(esc);
49        }
50        result
51    }
52}