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:
2026-04-19 14:08:26 +02:00
co-authored by Claude Sonnet 4.6
parent e11a9c308a
commit e86e53ec06
29 changed files with 645 additions and 1818 deletions
+6 -103
View File
@@ -16,8 +16,8 @@ use rand::Rng;
use crate::bridge::types::MonologueEvent;
use crate::knowledge::{ContradictionDetectedQueue, EntityRegistry};
use crate::perception::interpretation::ObservationTrigger;
use crate::simulation::conversation::NpcName;
use crate::simulation::movement::{PlayerCharacter, TilePosition};
use crate::simulation::npc_components::NpcName;
use crate::simulation::rng::EntityRng;
use crate::simulation::time::SimulationTime;
use crate::storyteller::EngagementRecord;
@@ -84,15 +84,6 @@ const HEAR_SOUND_LINES: &[(&str, &str)] = &[
("hear_sound_03", "Something just happened nearby."),
];
/// Hardcoded v0.1 witness_interaction monologue lines.
/// Fire when the player overhears an NPC-to-NPC conversation (D-078).
/// Future: move to content pools with trigger="witness_interaction".
const WITNESS_INTERACTION_LINES: &[(&str, &str)] = &[
("witness_01", "Interesting. Wonder what that was about."),
("witness_02", "I should remember what they just said."),
("witness_03", "They didn't know I was listening."),
];
/// Hardcoded v0.1 post_conversation monologue lines.
/// Fire after a player-NPC dialogue concludes (walk-away or natural end).
/// Future: move to content pools with trigger="post_conversation".
@@ -375,7 +366,6 @@ fn select_hardcoded_fallback(trigger: &str, rng: &mut impl Rng) -> (String, Stri
let lines = match trigger {
"observe_npc" => OBSERVE_NPC_LINES,
"hear_sound" => HEAR_SOUND_LINES,
"witness_interaction" => WITNESS_INTERACTION_LINES,
"post_conversation" => POST_CONVERSATION_LINES,
unknown => {
tracing::warn!(
@@ -413,8 +403,7 @@ fn sound_range_tiles(range: &crate::knowledge::types::SoundRange) -> u32 {
/// Priority order (first match wins):
/// 1. observe_npc (new entity spotted — uses previous-tick observation events)
/// 2. hear_sound (non-routine sound: Machinery, Alert)
/// 3. witness_interaction (overheard NPC-to-NPC conversation, D-078)
/// 4. post_conversation (player-NPC dialogue concluded)
/// 3. post_conversation (player-NPC dialogue concluded)
///
/// System ordering: after all event producers + recognition/anomaly monologue
/// systems, before compute_observer_snapshot.
@@ -429,7 +418,6 @@ pub fn trigger_event_monologue(
&TilePosition,
&mut MonologueState,
&mut MonologueBuffer,
Option<&crate::simulation::conversation::ConversationEventBuffer>,
&mut EntityRng,
),
With<PlayerCharacter>,
@@ -441,9 +429,7 @@ pub fn trigger_event_monologue(
// Saved for NPC attribution (engagement tracking #570) and trigger detection.
let post_conv_npcs: Vec<Entity> = post_conv_queue.drain();
let Ok((player_pos, mut state, mut buffer, conv_buffer_opt, mut entity_rng)) =
query.single_mut()
else {
let Ok((player_pos, mut state, mut buffer, mut entity_rng)) = query.single_mut() else {
return;
};
@@ -468,11 +454,6 @@ pub fn trigger_event_monologue(
.unwrap_or(false)
{
Some("hear_sound")
} else if conv_buffer_opt
.map(|b| !b.events.is_empty())
.unwrap_or(false)
{
Some("witness_interaction")
} else if !post_conv_npcs.is_empty() {
Some("post_conversation")
} else {
@@ -511,7 +492,7 @@ pub fn trigger_event_monologue(
);
// Engagement tracking (#570): attribute monologue_trigger_count to specific NPCs.
// Only NPC-context triggers are attributed — hear_sound/witness_interaction are not NPC-specific.
// Only NPC-context triggers are attributed — hear_sound is not NPC-specific.
match trigger {
"observe_npc" => {
// Attribute to all NPCs whose NewEntity event triggered this monologue
@@ -537,7 +518,7 @@ pub fn trigger_event_monologue(
}
}
}
_ => {} // hear_sound, witness_interaction: no NPC-specific attribution
_ => {} // hear_sound: no NPC-specific attribution
}
}
@@ -1345,7 +1326,6 @@ mod tests {
use crate::perception::interpretation::{
ObservationEvent, ObservationEventQueue, ObservationTrigger,
};
use crate::simulation::conversation::ConversationEventBuffer;
use crate::simulation::sound::{SoundEvent, SoundEventKind, SoundEventQueue};
fn setup_event_world() -> World {
@@ -1366,7 +1346,6 @@ mod tests {
TilePosition::new(10, 10, 0),
MonologueState::default(),
MonologueBuffer::default(),
ConversationEventBuffer::default(),
))
.id()
}
@@ -1521,36 +1500,6 @@ mod tests {
);
}
#[test]
fn witness_interaction_fires_on_conversation_event() {
let mut world = setup_event_world();
let player = spawn_event_player(&mut world);
// Pre-fill ConversationEventBuffer with an overheard conversation
world
.get_mut::<ConversationEventBuffer>(player)
.unwrap()
.events
.push(crate::simulation::conversation::ConversationEvent {
occluded_line: "Keep your head down today.".to_string(),
speaker_id: 100,
target_id: 101,
speaker_name: "Worker".to_string(),
target_name: "Courier".to_string(),
speaker_color_index: 0,
target_color_index: 1,
});
run_event_system(&mut world);
let buf = world.get::<MonologueBuffer>(player).unwrap();
assert!(
buf.event.is_some(),
"witness_interaction should fire when conversation overheard"
);
assert!(buf.event.as_ref().unwrap().id.starts_with("witness_"));
}
#[test]
fn post_conversation_fires_on_queue_entry() {
let mut world = setup_event_world();
@@ -1668,47 +1617,6 @@ mod tests {
);
}
#[test]
fn priority_hear_sound_over_witness_interaction() {
let mut world = setup_event_world();
let player = spawn_event_player(&mut world);
// Sound event
world
.resource_mut::<SoundEventQueue>()
.events
.push(SoundEvent::at(
&TilePosition::new(11, 10, 0),
SoundEventKind::Alert,
1.0,
crate::knowledge::types::SoundRange::Medium,
None,
));
// Conversation event
world
.get_mut::<ConversationEventBuffer>(player)
.unwrap()
.events
.push(crate::simulation::conversation::ConversationEvent {
occluded_line: "Test".to_string(),
speaker_id: 100,
target_id: 101,
speaker_name: "A".to_string(),
target_name: "B".to_string(),
speaker_color_index: 0,
target_color_index: 1,
});
run_event_system(&mut world);
let buf = world.get::<MonologueBuffer>(player).unwrap();
assert!(
buf.event.as_ref().unwrap().id.starts_with("hear_sound_"),
"hear_sound should have priority over witness_interaction"
);
}
#[test]
fn event_trigger_updates_last_fired_tick() {
let mut world = setup_event_world();
@@ -1813,12 +1721,7 @@ mod tests {
#[test]
fn hardcoded_lines_all_valid() {
for lines in &[
OBSERVE_NPC_LINES,
HEAR_SOUND_LINES,
WITNESS_INTERACTION_LINES,
POST_CONVERSATION_LINES,
] {
for lines in &[OBSERVE_NPC_LINES, HEAR_SOUND_LINES, POST_CONVERSATION_LINES] {
assert!(!lines.is_empty());
for (id, text) in *lines {
assert!(!id.is_empty(), "line id should not be empty");