Files
settled-reach/server/src/content/mod.rs
T
jpmschweitzerandClaude Opus 4.6 0e8ac56fc1 feat(simulation): add social site template schema types
Implements #163 (RoleSchema), #164 (SpaceSpec), #165 (TemplateOwnership
+ TemplateReferenceMap), #106 (TriangleDef), #107 (intra-template
triangle generation), and #250 (triangle escalation system) as the
foundational Tier 2 template system per D-025.

New content/template module with YAML-deserializable schema types,
ECS components for ownership/triangle state, escalation system
running on game-minute boundaries, and TriangleCrisisEvent emission.
Sample YAML templates at server/data/templates/.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 22:33:06 +01:00

120 lines
4.2 KiB
Rust

//! Content loading, indexing, and entity spawning system.
//!
//! Architecture (per Tyre's D-020 guidance):
//! 1. Deserialize YAML -> intermediate content types (types.rs)
//! 2. Content discovery + loading (loader.rs) -> ContentStore resource
//! 3. ContentStore -> ECS entity spawning (spawn.rs)
//! 4. ContentStore -> indexed line pools (line_pool.rs) -> LinePoolIndex resource
//! 5. Optional hot-reload (hot_reload.rs) for dev/authoring workflow
//!
//! Content schema is decoupled from ECS components. The spawn module
//! handles the mapping between the two representations.
pub mod hot_reload;
pub mod line_pool;
pub mod loader;
pub mod spawn;
pub mod template;
pub mod types;
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)]
pub struct ContentConfig {
/// Root directory containing content.yaml and campaign directories.
pub content_root: PathBuf,
/// Enable hot-reload (timestamp polling). Dev-only, not for production.
pub hot_reload: bool,
}
impl Default for ContentConfig {
fn default() -> Self {
Self {
content_root: PathBuf::from("content"),
hot_reload: false,
}
}
}
/// Content loading plugin.
///
/// Loads content from YAML files at startup, spawns ECS entities,
/// and builds the indexed line pools for dialogue/monologue queries.
/// Optionally enables hot-reload for the authoring workflow.
pub struct ContentPlugin;
impl Plugin for ContentPlugin {
fn build(&self, app: &mut App) {
if !app.world().contains_resource::<ContentConfig>() {
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);
tracing::debug!("ContentPlugin initialized");
}
}
/// Startup system: load content from disk, spawn entities, and build line pool index.
fn load_and_spawn_content(world: &mut World) {
let config = world.resource::<ContentConfig>().clone();
tracing::info!("Loading content from: {:?}", config.content_root);
match loader::load_content(&config.content_root) {
Ok(store) => {
let result = spawn::spawn_content(world, &store);
tracing::info!("Content loaded and spawned: {} NPCs", result.npcs_spawned);
// Build line pool index
let index = line_pool::LinePoolIndex::build(&store);
tracing::info!(
"Line pool index built: {} dialogue lines, {} monologue lines",
index.dialogue_line_count(),
index.monologue_line_count()
);
world.insert_resource(ContentStoreResource(store));
world.insert_resource(LinePoolIndexResource(index));
}
Err(e) => {
tracing::error!("Failed to load content: {}", e);
world.insert_resource(ContentStoreResource(loader::ContentStore::default()));
world.insert_resource(LinePoolIndexResource(line_pool::LinePoolIndex::default()));
}
}
// Set up hot-reload if enabled
if config.hot_reload {
let watcher = hot_reload::ContentWatcher::new(&config.content_root);
tracing::info!(
"Content hot-reload enabled — tracking {} files, polling every ~2s",
watcher.tracked_file_count()
);
world.insert_resource(watcher);
}
}
/// Wrapper resource holding the loaded content store.
/// Available for runtime systems that need to query content data
/// (e.g., dialogue selection, triangle fork evaluation).
#[derive(Resource, Debug)]
pub struct ContentStoreResource(pub loader::ContentStore);
/// Wrapper resource holding the indexed line pools.
/// Available for runtime systems that need to query dialogue/monologue lines
/// through the D-028 four-layer filtering pipeline.
#[derive(Resource, Debug)]
pub struct LinePoolIndexResource(pub line_pool::LinePoolIndex);