Files
settled-reach/server/src/knowledge/content_registry.rs
T
jpmschweitzerandClaude Opus 4.6 3d636fd4bb feat(simulation): knowledge grant schema, events, and contradiction detection (#545, #546, #547)
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>
2026-02-24 12:14:53 +01:00

87 lines
2.7 KiB
Rust

//! ContentEntityRegistry resource (D-079).
//!
//! Maps NPC canonical_id strings (e.g., "kael-davan") to their runtime StableIds.
//! Populated at NPC spawn time; queried at KnowledgeGrant processing time to
//! resolve `entity_ref` strings in `KnowledgeGrant::Entity` variants.
//!
//! BTreeMap for deterministic iteration (D-010 principle 4).
use bevy_ecs::prelude::*;
use std::collections::BTreeMap;
use super::types::StableId;
/// Content ID → StableId registry.
///
/// Populated by `spawn_npc` for every authored NPC. Read by the
/// `KnowledgeGranted` event handler to resolve `entity_ref` strings at
/// grant processing time (D-079).
#[derive(Resource, Debug, Default)]
pub struct ContentEntityRegistry {
entries: BTreeMap<String, StableId>,
}
impl ContentEntityRegistry {
/// Register a content_id → StableId mapping.
///
/// Idempotent for the same (content_id, stable_id) pair.
/// If the same content_id is registered twice with different StableIds,
/// the latest call wins (last-write semantics; warn in caller if this is unexpected).
pub fn register(&mut self, content_id: impl Into<String>, stable_id: StableId) {
self.entries.insert(content_id.into(), stable_id);
}
/// Resolve a content_id string to a StableId.
///
/// Returns `None` if the entity_ref is not registered. Callers should
/// emit `tracing::warn!` and drop the grant when `None` is returned.
pub fn resolve(&self, entity_ref: &str) -> Option<StableId> {
self.entries.get(entity_ref).copied()
}
/// Number of registered entries.
pub fn len(&self) -> usize {
self.entries.len()
}
/// Whether the registry is empty.
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn register_and_resolve() {
let mut registry = ContentEntityRegistry::default();
let sid = StableId(42);
registry.register("kael-davan", sid);
assert_eq!(registry.resolve("kael-davan"), Some(sid));
assert_eq!(registry.resolve("unknown-npc"), None);
assert_eq!(registry.len(), 1);
}
#[test]
fn register_overwrites() {
let mut registry = ContentEntityRegistry::default();
let sid_a = StableId(1);
let sid_b = StableId(2);
registry.register("npc-x", sid_a);
registry.register("npc-x", sid_b);
assert_eq!(registry.resolve("npc-x"), Some(sid_b));
assert_eq!(registry.len(), 1);
}
#[test]
fn empty_registry() {
let registry = ContentEntityRegistry::default();
assert!(registry.is_empty());
assert_eq!(registry.resolve("any"), None);
}
}