feat(simulation): add proximity detection and interaction verbs

Implement compute_nearby_interactions system that detects entities
within close (≤2) and mid (≤5) Manhattan distance, computes
available verbs per D-060 spec. NPCs get Talk+Observe at close
range, Observe-only at mid range; PersonOfInterest flips priority.
Objects get Examine. Results populate nearby_interactions[] on
ObserverSnapshot v4. Bump protocol version 3→4. Implements #404.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-12 19:48:55 +01:00
co-authored by Claude Opus 4.6
parent 5afea99622
commit 2de413dd87
12 changed files with 438 additions and 29 deletions
+4 -2
View File
@@ -102,7 +102,8 @@ pub fn generate_snapshot(
day: time.day(),
time_of_day: time.time_of_day_minutes(),
day_phase: time.day_phase(),
paused: time.paused,
paused: time.paused(),
tick_rate: time.tick_rate,
};
tracing::trace!(
@@ -111,12 +112,13 @@ pub fn generate_snapshot(
visible.len()
);
buffer.snapshot = Some(ObserverSnapshot {
version: 3,
version: 4,
tick: time.tick,
game_time,
player_facing: FacingDirection::default(),
entities: visible,
visible_tiles: Vec::new(), // Empty until #112 adds LOS filtering
nearby_interactions: Vec::new(),
});
}
+50 -3
View File
@@ -6,18 +6,19 @@ use bevy_ecs::prelude::*;
use serde::{Deserialize, Serialize};
pub use crate::knowledge::types::{EntityVisibility, KnowledgeConfidence, RelationshipState};
pub use crate::simulation::time::DayPhase;
pub use crate::simulation::time::{DayPhase, TickRate};
/// The ONLY data structure crossing the client-server boundary (D-020)
/// Contains all information visible to the observer at a given tick.
///
/// v2 adds: game_time, player_facing, visible_tiles, visibility sectors.
/// v3 adds: relationship (D-033 entity color), observation (Visible/Remembered).
/// v4 adds: nearby_interactions (D-060, #404 proximity + verbs[]).
/// Future fields: ambient sound events, internal monologue triggers,
/// HUD state (D-020 expansion).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ObserverSnapshot {
/// Protocol version for forward compatibility. Current: 3.
/// Protocol version for forward compatibility. Current: 4.
pub version: u8,
/// Simulation tick when this snapshot was produced
pub tick: u64,
@@ -29,6 +30,10 @@ pub struct ObserverSnapshot {
pub entities: Vec<VisibleEntity>,
/// Tiles visible to the observer for fog rendering
pub visible_tiles: Vec<VisibleTile>,
/// Entities within interaction range with available verbs (D-060, #404).
/// Sorted by distance (nearest first). v0.1 client reads verbs[0] on the nearest entity.
#[serde(default)]
pub nearby_interactions: Vec<NearbyInteraction>,
}
/// Game time data for client display (D-031)
@@ -41,8 +46,11 @@ pub struct GameTime {
pub time_of_day: u64,
/// Current day phase (Morning/Afternoon/Evening/Night)
pub day_phase: DayPhase,
/// Whether simulation is paused
/// Whether simulation is paused (tick_rate == Paused)
pub paused: bool,
/// Current tick rate state (D-052)
#[serde(default)]
pub tick_rate: TickRate,
}
/// 8-directional facing direction, matching movement system.
@@ -136,6 +144,45 @@ pub enum PlayerAction {
UsePerceptionMode(String),
Pause,
Unpause,
/// Set tick rate: Full (1.0), Half (0.5), or Paused (0.0) per D-052
SetTickRate(TickRate),
}
/// Available interaction verbs for a nearby entity (D-060, #404)
/// Embedded in ObserverSnapshot.nearby_interactions.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NearbyInteraction {
/// Wire-format entity identifier
pub entity_id: u64,
/// Entity type for client-side verb display
pub entity_type: EntityKind,
/// Manhattan distance from player
pub distance: f32,
/// Available verbs sorted by priority (index 0 = highest priority)
pub verbs: Vec<VerbOption>,
}
/// A single available verb on a nearby entity
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VerbOption {
/// Verb type
pub kind: VerbKind,
/// Display label for the context prompt (e.g. "Talk", "Observe", "Examine")
pub label: String,
/// Priority rank (lower = higher priority). v0.1 client reads only priority 1.
pub priority: u8,
/// Whether this verb is currently available (false = greyed out in v0.2)
pub available: bool,
}
/// Verb types for the interaction system (D-060)
/// Only active verbs appear in verbs[]. Passive (Look, Overhear) and
/// reactive (Monologue) verbs fire independently.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum VerbKind {
ExamineObject,
ExamineNpc,
Talk,
}
/// Snapshot buffer resource for staging outgoing ObserverSnapshots