feat(simulation): NPC-to-NPC conversation system (#247, D-078)

Implements server-authoritative per-word occlusion for NPC conversations.
ConversationEventBuffer drains into ObserverSnapshot each tick so the client
receives only words audible from the player's position. Updates test fixtures
to include the new conversation_events and conversation_ended fields.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-20 18:41:44 +01:00
co-authored by Claude Sonnet 4.6
parent 4e53a2e979
commit aa0d8ced37
9 changed files with 926 additions and 2 deletions
+11
View File
@@ -17,6 +17,7 @@ use crate::perception::cognitive_delay::CognitiveDelay;
use crate::perception::query::{ActivePerceptionMode, VisibilityGeometry};
use crate::perception::vision_cone::Facing;
use crate::simulation::contraband::ScanEventBuffer;
use crate::simulation::conversation::ConversationEventBuffer;
use crate::simulation::dialogue::DialogueResponseBuffer;
use crate::simulation::interaction::NearbyInteractionBuffer;
use crate::simulation::inventory::{CarriedBy, InventorySlot, ItemName};
@@ -78,6 +79,7 @@ pub fn compute_observer_snapshot(
Option<&CognitiveDelay>,
Option<&mut DialogueResponseBuffer>,
Option<&mut ScanEventBuffer>,
Option<&mut ConversationEventBuffer>,
),
With<PlayerCharacter>,
>,
@@ -105,6 +107,7 @@ pub fn compute_observer_snapshot(
cognitive_delay_opt,
mut dialogue_response_opt,
mut scan_event_buffer_opt,
mut conversation_buffer_opt,
)) = observer_query.single_mut()
else {
tracing::error!("compute_observer_snapshot: PlayerCharacter query failed");
@@ -191,6 +194,12 @@ pub fn compute_observer_snapshot(
.map(|buf| buf.take())
.unwrap_or_default();
// Drain NPC-to-NPC conversation events (#247, D-078)
let (conversation_events, conversation_ended) = conversation_buffer_opt
.as_mut()
.map(|buf| (buf.take_events(), buf.take_ended()))
.unwrap_or_default();
// Collect sound events audible to the observer (D-038, #124).
// Filter by D-018 range: only events the player can hear based on distance.
let sound_events = if let Some(ref queue) = sound_queue {
@@ -251,6 +260,8 @@ pub fn compute_observer_snapshot(
dialogue_response,
blocked_entities,
scan_events,
conversation_events,
conversation_ended,
sound_events,
rng_seed: sim_rng.as_deref().map(|r| r.seed()),
});