feat(simulation): add YAML content loader with hot-reload (#326)

LinePool system parses dialogue.yaml and monologue.yaml into indexed
BTreeMap structures. Supports 4-layer query filtering (access >
situation > trust > topic+mood) per D-028. Hot-reload via timestamp
polling every 20 ticks (dev-only). Graceful failure preserves
previous content on reload error.

30+ tests covering enum parsing, index building, query filtering,
monologue fallback, and content watching.

Ref: D-028, D-032, D-035, D-041

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-16 00:41:29 +01:00
co-authored by Claude Opus 4.6
parent ea0a3f0b35
commit 91d6b1b1cb
6 changed files with 1382 additions and 37 deletions
+9 -10
View File
@@ -291,15 +291,13 @@ fn load_yaml_dir<T: serde::de::DeserializeOwned>(dir: &Path) -> Vec<T> {
/// Load all YAML files recursively under a directory, skipping stubs.
fn load_yaml_recursive<T: serde::de::DeserializeOwned>(dir: &Path) -> Vec<T> {
let mut results = Vec::new();
walk_yaml_files(dir, &mut |path| {
match load_yaml::<T>(path) {
Ok(item) => results.push(item),
Err(e) => {
if is_comment_only_file(path) {
tracing::debug!("Skipping stub: {:?}", path);
} else {
tracing::warn!("Failed to parse {:?}: {}", path, e);
}
walk_yaml_files(dir, &mut |path| match load_yaml::<T>(path) {
Ok(item) => results.push(item),
Err(e) => {
if is_comment_only_file(path) {
tracing::debug!("Skipping stub: {:?}", path);
} else {
tracing::warn!("Failed to parse {:?}: {}", path, e);
}
}
});
@@ -525,7 +523,8 @@ motivation: "HANDLER"
#[test]
fn derive_district_id_from_path() {
let root = Path::new("/content");
let district = Path::new("/content/campaigns/main/systems/krenn/stations/sova/districts/transit");
let district =
Path::new("/content/campaigns/main/systems/krenn/stations/sova/districts/transit");
let id = derive_district_id(root, district);
assert_eq!(id, "krenn.sova.transit");
}