Merge remote-tracking branch 'origin/server'

# Conflicts:
#	CHANGELOG.md
#	CLAUDE.md
This commit is contained in:
2026-02-24 13:00:39 +01:00
27 changed files with 4764 additions and 23 deletions
+6
View File
@@ -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);
+9
View File
@@ -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
}
+23 -3
View File
@@ -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,
},
}
// ---------------------------------------------------------------------------