feat(simulation): character archetype, tell escalation, and news ticker (#587, #589, #591)

- Add character_archetype to StartupMessage with serde default (Detective)
- Bump PROTOCOL_VERSION to 19
- Add escalate_tells_on_activation() and expire_routine_deviations() systems
- RoutineDeviation inserted on triangle NPCs with 300-tick TTL
- Add TickerPool resource with deterministic SimRng rotation (200 ticks)
- Emit current_ticker in ObserverSnapshot when player is in bar zone
- Load ticker YAML from district content directories

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-05 09:13:03 +01:00
co-authored by Claude Opus 4.6
parent fc24de6128
commit 3194a6e491
16 changed files with 567 additions and 22 deletions
+20 -2
View File
@@ -30,6 +30,8 @@ pub struct DistrictContent {
pub routines: Option<RoutineFile>,
pub dialogue_pools: Vec<DialoguePool>,
pub monologue_pools: Vec<MonologuePool>,
/// Ticker headlines loaded from `ticker/*.yaml` files (#591).
pub ticker_headlines: Vec<crate::content::types::TickerHeadline>,
}
/// Errors that can occur during content loading.
@@ -234,16 +236,32 @@ fn load_district(district_dir: &Path) -> Result<DistrictContent, ContentError> {
content.monologue_pools = load_yaml_recursive::<MonologuePool>(&monologue_dir);
}
// Ticker headlines (#591): scan ticker/*.yaml in this district
let ticker_dir = district_dir.join("ticker");
if ticker_dir.is_dir() {
let ticker_files = load_yaml_dir::<crate::content::types::TickerFile>(&ticker_dir);
for tf in ticker_files {
tracing::info!(
"Ticker loaded: {} headlines from '{}' (feed: {})",
tf.headlines.len(),
tf.location,
tf.feed,
);
content.ticker_headlines.extend(tf.headlines);
}
}
let npc_count = content.npc_profiles.len();
let triangle_count = content.triangles.len();
let template_count = content.templates.len();
let pool_count = content.pools.len();
let dialogue_count = content.dialogue_pools.len();
let monologue_count = content.monologue_pools.len();
let ticker_count = content.ticker_headlines.len();
tracing::info!(
"District loaded: {} NPCs, {} triangles, {} templates, {} pools, {} dialogue pools, {} monologue pools",
npc_count, triangle_count, template_count, pool_count, dialogue_count, monologue_count
"District loaded: {} NPCs, {} triangles, {} templates, {} pools, {} dialogue pools, {} monologue pools, {} ticker headlines",
npc_count, triangle_count, template_count, pool_count, dialogue_count, monologue_count, ticker_count
);
Ok(content)