Skip to main content

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

  1. Build an EncounterArgumentationState with your scheme catalog.
  2. Set β via set_intensity.
  3. 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.
  4. Construct a StateActionScorer and StateAcceptanceEval, both borrowing the state.
  5. Hand them to encounter::resolution::MultiBeat::resolve (or SingleExchange).
  6. After resolve returns, call drain_errors to 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