KnowledgeGrant untagged enum (Fact + Entity variants), ContentEntityRegistry resource, KnowledgeGranted event processing, ContradictionClaim struct with 600-tick window detection in observe_entity. Wires knowledge_grant field in dialogue line selection. Implements D-079, D-083. Closes Q-026. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -20,6 +20,8 @@ use bevy_app::prelude::*;
|
||||
use bevy_ecs::prelude::*;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::knowledge::ContentEntityRegistry;
|
||||
|
||||
/// Configuration for the content loader.
|
||||
/// Set the content root path before adding ContentPlugin.
|
||||
#[derive(Resource, Debug, Clone)]
|
||||
@@ -52,6 +54,10 @@ impl Plugin for ContentPlugin {
|
||||
app.insert_resource(ContentConfig::default());
|
||||
}
|
||||
|
||||
// ContentEntityRegistry is required by spawn_npc (D-079).
|
||||
// Init here so ContentPlugin works standalone without KnowledgePlugin.
|
||||
app.init_resource::<ContentEntityRegistry>();
|
||||
|
||||
app.add_systems(Startup, load_and_spawn_content);
|
||||
app.add_systems(PostUpdate, hot_reload::hot_reload_content);
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ use std::collections::BTreeMap;
|
||||
|
||||
use crate::content::loader::{ContentStore, DistrictContent};
|
||||
use crate::content::types;
|
||||
use crate::knowledge::content_registry::ContentEntityRegistry;
|
||||
use crate::knowledge::graph::KnowledgeGraph;
|
||||
use crate::knowledge::registry::{EntityRegistry, StableEntityId};
|
||||
use crate::knowledge::types::{
|
||||
@@ -213,6 +214,12 @@ fn spawn_npc(world: &mut World, profile: &types::NpcProfile, result: &mut SpawnR
|
||||
ContentSlug(profile.canonical_id.clone()),
|
||||
));
|
||||
|
||||
// Register in ContentEntityRegistry so KnowledgeGrant::Entity can resolve entity_ref strings
|
||||
// (D-079: ContentEntityRegistry populated at NPC spawn time)
|
||||
world
|
||||
.resource_mut::<ContentEntityRegistry>()
|
||||
.register(profile.canonical_id.clone(), stable_id);
|
||||
|
||||
result
|
||||
.npc_ids
|
||||
.insert(profile.canonical_id.clone(), stable_id);
|
||||
@@ -296,6 +303,7 @@ fn resolve_information(
|
||||
source: KnowledgeSource::Background,
|
||||
state: KnowledgeState::Active,
|
||||
acquired_tick: 0,
|
||||
disclosure_blocked: false,
|
||||
},
|
||||
)
|
||||
})
|
||||
@@ -648,6 +656,7 @@ mod tests {
|
||||
fn create_test_world() -> World {
|
||||
let mut world = World::new();
|
||||
world.init_resource::<EntityRegistry>();
|
||||
world.init_resource::<ContentEntityRegistry>();
|
||||
world
|
||||
}
|
||||
|
||||
|
||||
@@ -491,10 +491,30 @@ pub struct DialogueLine {
|
||||
pub knowledge_grant: Option<KnowledgeGrant>,
|
||||
}
|
||||
|
||||
/// Knowledge grant attached to a dialogue line (D-079).
|
||||
///
|
||||
/// Untagged enum — serde tries each variant in order:
|
||||
/// `Fact` matches YAML with `fact_id` field.
|
||||
/// `Entity` matches YAML with `entity_ref` field.
|
||||
/// `Compound` variant deferred to Sprint 18.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct KnowledgeGrant {
|
||||
pub fact_id: String,
|
||||
pub confidence: String,
|
||||
#[serde(untagged)]
|
||||
pub enum KnowledgeGrant {
|
||||
/// Grant knowledge of a non-entity fact.
|
||||
/// Format: fact_id "category.topic", confidence string.
|
||||
Fact {
|
||||
fact_id: String,
|
||||
confidence: String,
|
||||
},
|
||||
/// Grant knowledge of an entity (creates EntityKnowledge entry in observer's KG).
|
||||
/// Required for contradiction detection: testimony must create ToldBy EntityKnowledge
|
||||
/// so a subsequent DirectObservation can detect a discrepancy (D-079, D-083).
|
||||
Entity {
|
||||
entity_ref: String,
|
||||
#[serde(default)]
|
||||
attributes: BTreeMap<String, String>,
|
||||
confidence: String,
|
||||
},
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user