Encounter integration
The encounter-argumentation crate bridges formal argumentation into a trait-inverted scene engine. It translates the formalisms into an interface a scene engine can consume — two trait impls the engine asks every beat.
The two-question architecture
Scene engines like encounter are trait-inverted — they don't know how to score actions or decide acceptance. They delegate via traits:
ActionScorer<P>::score_actions— how much does actor X want to take action Y right now?AcceptanceEval<P>::evaluate— does the responder accept Y's proposal?
Our bridge provides one impl of each:
StateActionScorer<'a, S>wraps any inner scorer and boosts affordances whose backing argument is credulously accepted at current β. Proposer-side salience.StateAcceptanceEval<'a>rejects when the responder has put forward a credulously-accepted counter-argument. Responder-side gate.
These ask genuinely different questions. The scorer is "what's in the proposer's vocabulary right now?" — a global-β credulous check. The eval is "does the responder have ammunition?" — a per-responder check using has_accepted_counter_by.
Lifecycle
- Build an
EncounterArgumentationStatewith your scheme catalog. - Set β via
set_intensity. - For each (actor, affordance) pair in the scene, call
add_scheme_instance_for_affordance— this seeds the forward index that the bridge uses at resolve time. - Construct a
StateActionScorerandStateAcceptanceEval, both borrowing the state. - Hand them to
encounter::resolution::MultiBeat::resolve(orSingleExchange). - After resolve returns, call
drain_errorsto collect any latched errors.
See the first-scene guide for a complete worked example.
Why the error latch?
Bridge traits return bool, not Result<bool, Error>. If has_accepted_counter_by fails internally (e.g., framework exceeds the weighted-bipolar enumeration limit), we default to accept — permissive — and append the error to a per-state buffer. Consumers drain via drain_errors() after the scene. This keeps scenes flowing even under internal failure, and exposes failures on the normal drain path.
A common latch entry is Error::MissingProposerBinding: the bridge uses "self" as the proposer slot by convention, and will surface this error when an affordance's bindings don't contain one.
Zero encounter changes
The bridge was designed with the constraint that the sibling encounter crate could not be modified. Every state feature — forward index, β dial, error latch — lives on this side of the trait boundary. Sync is required by encounter's trait bounds, so the state uses Mutex<Budget> and Mutex<Vec<Error>> internally rather than the more common Cell / RefCell.
Societas-modulated weights
Attack weights can be derived from live societas-relations state via societas_encounter::SocietasRelationshipSource, which lives in the sibling societas-encounter crate under the argumentation feature. It implements WeightSource<ArgumentId>, resolving an ArgumentId back to its asserting actors via actors_by_argument() and a NameResolver, then querying five relationship dimensions.
encounter-argumentation itself stays focused on the encounter↔argumentation bridge — the societas adapter is composed in by consumers who want it. See the societas-modulated weights how-to.
Further reading
- encounter crate docs — the scene engine.
- Migration v0.4 → v0.5 — what changed in the bridge surface.
- First scene guide — practical walkthrough.