feat(simulation): dialogue pipeline, ContentSlug, and walk-away KG recording
Implement full dialogue selection pipeline (D-028): 4-layer filtering engine with access tier, situation derivation, trust tier, and weighted topic+mood scoring via SimRng. Add ContentSlug component for stable content identity across save/load. Add walk-away KG recording with IncompleteInteraction events per D-064 three-phase consequences. Bump protocol to v8 with DialogueResponseEvent. Fixes #305, #427, #452. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -29,6 +29,25 @@ pub enum KnowledgeEventType {
|
||||
},
|
||||
/// Entity left observer's LOS (downgrades from Direct).
|
||||
LeftLOS { target: Entity },
|
||||
/// Observer walked away from an active interaction (D-064).
|
||||
/// Records incompleteness in the target's known_attributes for future
|
||||
/// dialogue/monologue consequences.
|
||||
IncompleteInteraction {
|
||||
target: Entity,
|
||||
interaction_type: InteractionType,
|
||||
},
|
||||
}
|
||||
|
||||
/// Type of interaction for walk-away recording (D-064).
|
||||
/// Differentiates casual conversation from confrontation —
|
||||
/// future dialogue may react differently.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum InteractionType {
|
||||
/// Normal Talk conversation.
|
||||
Talk,
|
||||
/// Confrontation (D-063). Walking away from confrontation
|
||||
/// carries heavier consequences than casual talk.
|
||||
Confront,
|
||||
}
|
||||
|
||||
/// Resource: queue of pending knowledge events.
|
||||
@@ -98,6 +117,24 @@ pub fn process_knowledge_events(
|
||||
tracing::error!("LeftLOS target {:?} not in EntityRegistry", target);
|
||||
}
|
||||
}
|
||||
KnowledgeEventType::IncompleteInteraction {
|
||||
target,
|
||||
interaction_type,
|
||||
} => {
|
||||
if let Some(stable_id) = registry.to_stable(target) {
|
||||
observer_kg.record_incomplete_interaction(
|
||||
&stable_id,
|
||||
interaction_type,
|
||||
event.tick,
|
||||
);
|
||||
tracing::debug!(
|
||||
"Recorded incomplete {:?} interaction with {:?} at tick {}",
|
||||
interaction_type,
|
||||
stable_id,
|
||||
event.tick,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,6 +145,61 @@ impl KnowledgeGraph {
|
||||
}
|
||||
}
|
||||
|
||||
/// Record an incomplete interaction with an entity (D-064 walk-away).
|
||||
///
|
||||
/// Appends to known_attributes["incomplete_interactions"] as a
|
||||
/// comma-separated list of "tick:type" entries. Creates the entity
|
||||
/// entry if it doesn't exist (at Suspects confidence).
|
||||
pub fn record_incomplete_interaction(
|
||||
&mut self,
|
||||
target: &StableId,
|
||||
interaction_type: super::events::InteractionType,
|
||||
tick: u64,
|
||||
) {
|
||||
let entry = self
|
||||
.entities
|
||||
.entry(*target)
|
||||
.or_insert_with(|| EntityKnowledge {
|
||||
last_known_position: None,
|
||||
last_observed_tick: 0,
|
||||
last_updated_tick: 0,
|
||||
confidence: KnowledgeConfidence::Suspects,
|
||||
source: KnowledgeSource::DirectObservation { tick },
|
||||
state: KnowledgeState::Active,
|
||||
relationship: RelationshipState::Unknown,
|
||||
known_attributes: BTreeMap::new(),
|
||||
});
|
||||
|
||||
let type_str = match interaction_type {
|
||||
super::events::InteractionType::Talk => "talk",
|
||||
super::events::InteractionType::Confront => "confront",
|
||||
};
|
||||
let record = format!("{}:{}", tick, type_str);
|
||||
|
||||
entry
|
||||
.known_attributes
|
||||
.entry("incomplete_interactions".to_string())
|
||||
.and_modify(|v| {
|
||||
v.push(',');
|
||||
v.push_str(&record);
|
||||
})
|
||||
.or_insert(record);
|
||||
|
||||
entry.last_updated_tick = tick;
|
||||
}
|
||||
|
||||
/// Check if the observer has any incomplete interactions with an entity.
|
||||
///
|
||||
/// Returns true if known_attributes["incomplete_interactions"] exists
|
||||
/// and is non-empty. Used by dialogue/monologue systems to gate
|
||||
/// post-conversation reactions (D-064 phase 3).
|
||||
pub fn has_incomplete_interaction(&self, target: &StableId) -> bool {
|
||||
self.entities
|
||||
.get(target)
|
||||
.and_then(|e| e.known_attributes.get("incomplete_interactions"))
|
||||
.is_some_and(|v| !v.is_empty())
|
||||
}
|
||||
|
||||
/// Set relationship state for an entity.
|
||||
pub fn set_relationship(&mut self, target: &StableId, state: RelationshipState) {
|
||||
if let Some(entry) = self.entities.get_mut(target) {
|
||||
|
||||
@@ -12,7 +12,7 @@ pub mod graph;
|
||||
pub mod registry;
|
||||
pub mod types;
|
||||
|
||||
pub use events::{KnowledgeEvent, KnowledgeEventQueue, KnowledgeEventType};
|
||||
pub use events::{InteractionType, KnowledgeEvent, KnowledgeEventQueue, KnowledgeEventType};
|
||||
pub use graph::KnowledgeGraph;
|
||||
pub use registry::{EntityRegistry, StableEntityId};
|
||||
pub use types::*;
|
||||
|
||||
Reference in New Issue
Block a user