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>
87 lines
2.7 KiB
Rust
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);
|
|
}
|
|
}
|