Skip to main content

encounter/
lib.rs

1//! # encounter
2//!
3//! `encounter` resolves what happens when several characters interact in a
4//! scene. Give it the *what could happen* (a catalog of possible actions)
5//! and the *who is present* (the characters); it picks one of three
6//! protocols (one-shot exchange, turn-by-turn scene, or long-running
7//! scheme) and returns a structured *what happened* — beats and typed
8//! effects, replayable and testable. It does not generate prose, run a
9//! drama manager, or decide policy — those live in the layer above.
10//!
11//! ## Architecture
12//!
13//! encounter is **standalone**: it depends on `serde`, `toml`, and
14//! `thiserror`, nothing else. Scoring and pattern evaluation are delegated
15//! to consumers via traits:
16//!
17//! - [`ActionScorer`] — scores available actions for an actor. This is
18//!   where your scoring policy lives — a utility/salience model, a GOAP
19//!   planner, or an LLM call.
20//! - [`AcceptanceEval`] — determines whether a responder accepts a chosen
21//!   action. This is where your fabula evaluator, reaction model, or
22//!   argumentation backend lives. *Fabula* here means the precondition
23//!   language encounter uses for action availability — typically a small
24//!   DSL the bridge crate parses.
25//!
26//! On each beat, the protocol asks [`ActionScorer`] for ranked actions and
27//! then asks [`AcceptanceEval`] whether the chosen action lands.
28//!
29//! ## Pluggable backends
30//!
31//! The canonical reasoning backend is the
32//! [`argumentation`](https://crates.io/crates/argumentation) crate, via the
33//! [`encounter-argumentation`](https://crates.io/crates/encounter-argumentation)
34//! bridge. It implements both consumer traits using a Dung-framework-style
35//! argument graph with weighted-bipolar attacks and a β-budget acceptance
36//! dial. If you need more than the built-in [`AlwaysAccept`] / [`AlwaysReject`]
37//! test helpers, start with that bridge.
38//!
39//! ## Quick example: SingleExchange
40//!
41//! ```
42//! use encounter::resolution::SingleExchange;
43//! use encounter::scoring::{AlwaysAccept, ScoredAffordance};
44//! use encounter::affordance::{AffordanceSpec, CatalogEntry};
45//! use encounter::types::Effect;
46//!
47//! let entry = CatalogEntry {
48//!     spec: AffordanceSpec {
49//!         name: "greet".into(),
50//!         domain: "social".into(),
51//!         bindings: vec!["self".into(), "target".into()],
52//!         considerations: Vec::new(),
53//!         effects_on_accept: vec![Effect::RelationshipDelta {
54//!             axis: "friendship".into(),
55//!             from: "target".into(),
56//!             to: "self".into(),
57//!             delta: 0.05,
58//!         }],
59//!         effects_on_reject: Vec::new(),
60//!         drive_alignment: Vec::new(),
61//!     },
62//!     precondition: String::new(),
63//! };
64//! let scored = ScoredAffordance {
65//!     entry,
66//!     score: 0.8,
67//!     bindings: [("self".into(), "alice".into()), ("target".into(), "bob".into())]
68//!         .into_iter()
69//!         .collect(),
70//! };
71//!
72//! let protocol = SingleExchange;
73//! let result = protocol.resolve("alice", "bob", &[scored], &AlwaysAccept);
74//! assert_eq!(result.beats.len(), 1);
75//! assert!(result.beats[0].accepted);
76//! ```
77//!
78//! Runnable versions of all three protocols live in the `examples/` directory
79//! of the source repository.
80//!
81//! ## Catalog loading
82//!
83//! ```no_run
84//! use encounter::catalog::load_catalog_dir;
85//! use std::path::Path;
86//!
87//! let entries = load_catalog_dir(Path::new("path/to/catalog")).unwrap();
88//! println!("loaded {} affordances", entries.len());
89//! ```
90//!
91//! ## Inspirations
92//!
93//! The protocols are small, opinionated reductions of shapes that have
94//! shipped or been published. Each one is named for what it borrows, not
95//! for what it reproduces faithfully:
96//!
97//! - **`SingleExchange`** reduces the intent/reaction step from McCoy et
98//!   al., *Comme il Faut* (Game AI Pro 3, ch. 43). Full CiF social-games are
99//!   out of scope.
100//! - **`MultiBeat`** takes the speaker-rotation loop from Evans & Short,
101//!   *Versu* (IEEE TCIAIG 2014). Full social-practice goal stacks, role
102//!   tableaux, and obligations are out of scope.
103//! - **`BackgroundScheme`** takes the progress-bar shape from the scheme
104//!   system in Crusader Kings III (CK3). Agents, discovery rolls, and
105//!   counter-actions are out of scope.
106//! - **`TurnPolicy::AdjacencyPair`** is the adjacency-pair model from
107//!   Sacks, Schegloff & Jefferson, *Lectures on Conversation* (1992).
108
109#![deny(missing_docs)]
110#![warn(clippy::all)]
111
112pub mod affordance;
113pub mod catalog;
114pub mod error;
115pub mod escalation;
116pub mod practice;
117pub mod resolution;
118pub mod scoring;
119pub mod types;
120pub mod value_argument;
121
122pub use affordance::{AffordanceSpec, CatalogEntry};
123pub use error::Error;
124pub use escalation::{EscalationRequest, FidelityHint, check_escalation};
125pub use practice::{DurationPolicy, PracticeSpec, TurnPolicy};
126pub use resolution::MultiBeat;
127pub use resolution::SingleExchange;
128pub use resolution::background::{BackgroundScheme, SchemePhase};
129pub use scoring::{AcceptanceEval, ActionScorer, AlwaysAccept, AlwaysReject, ScoredAffordance};
130pub use types::{Beat, ConsiderationSpec, DriveAlignment, Effect, EncounterResult};
131pub use value_argument::{ValueArgumentInput, ValueArgumentResult, resolve_value_argument};