feat(simulation): voice pipeline integration — Factual bypass, observer wiring, tell iteration (#650 #652 #651)
ContentType::Factual bypasses LLM for fact-bearing lines (numbers, denials). Observer enrichment systems rewrite dialogue/conversation text with voiced versions before snapshot assembly. Friendly and RoutineDeviation tells get concrete surface-pattern examples. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,17 +1,25 @@
|
||||
//! Voice cache lookup for behavior text (D-138, Phase 3 stub).
|
||||
//! Voice cache lookup for behavior text (D-138, Phase 3).
|
||||
//!
|
||||
//! Provides the integration point between the voice cache and any system
|
||||
//! that serves NPC text to the client. Not yet wired into a runtime system —
|
||||
//! `observable_behaviors` on `NpcBlueprint` is generator output only.
|
||||
//!
|
||||
//! Wire `voiced_behavior()` into the behavior-serving path once it exists.
|
||||
//! that serves NPC text to the client. Wired into the observer pipeline
|
||||
//! via `voice::integration` enrichment systems.
|
||||
|
||||
use bevy_ecs::prelude::*;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use crate::npc::tell_state::TellCategory;
|
||||
use crate::voice::cache::{CacheKey, VoiceCacheStore};
|
||||
use crate::voice::prompt_builder::ContentType;
|
||||
|
||||
/// ECS resource wrapping the voice cache store.
|
||||
///
|
||||
/// Optional — systems that use it gracefully degrade to base text when absent.
|
||||
/// Populated at startup by the voice subsystem when the pipeline is enabled.
|
||||
#[derive(Resource)]
|
||||
pub struct VoiceCacheResource {
|
||||
pub cache: Arc<Mutex<VoiceCacheStore>>,
|
||||
}
|
||||
|
||||
/// Look up a voiced behavior from cache, falling back to base text.
|
||||
///
|
||||
/// Tell behaviors (from `NpcBlueprint.tell_behaviors`) are NEVER re-voiced —
|
||||
@@ -33,6 +41,11 @@ pub fn voiced_behavior(
|
||||
return base_text.to_string();
|
||||
}
|
||||
|
||||
// Factual content is always base text — no LLM, no cache lookup.
|
||||
if content_type == ContentType::Factual {
|
||||
return base_text.to_string();
|
||||
}
|
||||
|
||||
let key = CacheKey {
|
||||
culture_id: culture_id.to_string(),
|
||||
npc_stable_id,
|
||||
@@ -99,6 +112,36 @@ mod tests {
|
||||
assert_eq!(result, "Base line.");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn factual_content_always_passthrough() {
|
||||
let cache = test_cache();
|
||||
// Store a voiced version under the Factual key — it must never be returned.
|
||||
let key = CacheKey {
|
||||
culture_id: "krenn".into(),
|
||||
npc_stable_id: 42,
|
||||
content_type: ContentType::Factual,
|
||||
content_index: 0,
|
||||
tell_state: None,
|
||||
};
|
||||
cache
|
||||
.lock()
|
||||
.unwrap()
|
||||
.store(100, key, "Voiced factual (should be ignored).".into());
|
||||
|
||||
let result = voiced_behavior(
|
||||
&cache,
|
||||
100,
|
||||
"krenn",
|
||||
42,
|
||||
ContentType::Factual,
|
||||
0,
|
||||
None,
|
||||
"14 crates in bay seven.",
|
||||
false,
|
||||
);
|
||||
assert_eq!(result, "14 crates in bay seven.");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tell_behavior_always_passthrough() {
|
||||
let cache = test_cache();
|
||||
|
||||
Reference in New Issue
Block a user