feat(engine): retire D-078 overheard conversation system (#848)
Per R-012: delete conversation.rs, both overheard content files, and remove all 6 wire-up points (social_plugin, bridge/types, monologue, voice/integration). Protocol version 22 → 23. Scope confirmed by #842 audit — npc/ and content/global/ untouched. Surviving NPC components (NpcName, NpcColorIndex, NpcConversation) migrated to simulation/npc_components.rs for use by D-080 knowledge propagation. Also applies pre-existing cargo fmt debt (names.rs and 4 others). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,15 +1,13 @@
|
||||
//! Observer integration for the voice pipeline (D-138, Phase 3).
|
||||
//!
|
||||
//! Two enrichment systems run before `compute_observer_snapshot` and rewrite
|
||||
//! NPC text in the dialogue and conversation buffers with voiced variants
|
||||
//! looked up from the cache. Cache miss → base text (never blocks).
|
||||
//! One enrichment system runs before `compute_observer_snapshot` and rewrites
|
||||
//! NPC dialogue text with voiced variants looked up from the cache.
|
||||
//! Cache miss → base text (never blocks).
|
||||
//!
|
||||
//! ## System ordering
|
||||
//!
|
||||
//! ```text
|
||||
//! process_talk_interaction ─┐
|
||||
//! run_npc_conversations ─┤─► voice_enrich_dialogue_response ─┐
|
||||
//! └─► voice_enrich_conversation_events ─► compute_observer_snapshot
|
||||
//! process_talk_interaction ─► voice_enrich_dialogue_response ─► compute_observer_snapshot
|
||||
//! ```
|
||||
//!
|
||||
//! ## Content index derivation
|
||||
@@ -24,7 +22,6 @@ use bevy_ecs::prelude::*;
|
||||
use crate::knowledge::{EntityRegistry, StableId};
|
||||
use crate::npc::tell_state::DerivedTellState;
|
||||
use crate::npc::{Npc, NpcVoiceProfile};
|
||||
use crate::simulation::conversation::ConversationEventBuffer;
|
||||
use crate::simulation::dialogue::DialogueResponseBuffer;
|
||||
use crate::simulation::movement::{PlayerCharacter, TilePosition};
|
||||
use crate::simulation::zone::ZoneMap;
|
||||
@@ -127,85 +124,6 @@ pub fn voice_enrich_dialogue_response(
|
||||
response.text = voiced;
|
||||
}
|
||||
|
||||
/// Enrich the conversation event buffer with voiced lines from the cache.
|
||||
///
|
||||
/// Must run after `run_npc_conversations` and before
|
||||
/// `compute_observer_snapshot`. No-op when the voice cache resource is absent.
|
||||
///
|
||||
/// Each event in the buffer is processed: the pre-occlusion base text is
|
||||
/// not available at this point (occlusion has already been applied), so the
|
||||
/// `occluded_line` is treated as the base text for the voice lookup.
|
||||
/// This means the voice register wraps the already-occluded line.
|
||||
///
|
||||
/// ## Accepted risk: re-voicing of heavily occluded text
|
||||
///
|
||||
/// When many words are dropped by D-078 occlusion, the remaining text may
|
||||
/// be fragmentary ("... the ... came in ..."). Re-voicing such fragments can
|
||||
/// produce incoherent output. This is acceptable for two reasons:
|
||||
/// 1. Cache misses are common for conversation text (no pre-baking pipeline),
|
||||
/// so the base text fallback in `voiced_behavior()` fires most of the time.
|
||||
/// 2. Even incoherent voiced output is no worse than the already-degraded
|
||||
/// overheard line — the occlusion itself has already broken coherence.
|
||||
/// The player's inability to fully parse overheard speech is the mechanic.
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn voice_enrich_conversation_events(
|
||||
voice_cache: Option<Res<VoiceCacheResource>>,
|
||||
registry: Res<EntityRegistry>,
|
||||
zone_map: Option<Res<ZoneMap>>,
|
||||
player_query: Query<&TilePosition, With<PlayerCharacter>>,
|
||||
npc_voice_query: Query<(&NpcVoiceProfile, Option<&DerivedTellState>), With<Npc>>,
|
||||
mut conversation_buffer: Query<&mut ConversationEventBuffer, With<PlayerCharacter>>,
|
||||
) {
|
||||
let Some(voice_cache) = voice_cache else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Ok(mut buffer) = conversation_buffer.single_mut() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if buffer.events.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let zone_id = player_zone_id(&player_query, zone_map.as_deref());
|
||||
|
||||
for event in &mut buffer.events {
|
||||
let speaker_entity = registry.to_entity(&StableId(event.speaker_id));
|
||||
let Some(entity) = speaker_entity else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let Ok((voice_profile, tell_state_opt)) = npc_voice_query.get(entity) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let tell_state = tell_state_opt.and_then(|t| t.category);
|
||||
// Conversation lines don't have a stable line_id; derive content_index
|
||||
// from the occluded line text. The full cache key is
|
||||
// (culture_id, npc_stable_id, content_type, content_index, tell_state),
|
||||
// so a u16 hash collision requires two different lines from the same
|
||||
// speaker with the same tell state to hash identically — at ~65K
|
||||
// possible values this is extremely rare, and the worst outcome is
|
||||
// a stale voiced line being served instead of base text. Acceptable.
|
||||
let content_index = content_index_from_line_id(&event.occluded_line);
|
||||
|
||||
let voiced = voiced_behavior(
|
||||
&voice_cache.cache,
|
||||
zone_id,
|
||||
&voice_profile.culture_id,
|
||||
event.speaker_id,
|
||||
ContentType::Dialogue,
|
||||
content_index,
|
||||
tell_state,
|
||||
&event.occluded_line,
|
||||
false,
|
||||
);
|
||||
|
||||
event.occluded_line = voiced;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tests
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user