feat(simulation): complete interaction and monologue pipelines (#413, #414, #415)

Three fixes to make the gameplay loop functional end-to-end:

- Add Interactable component to NPC spawn so E-prompt detection works
- Build monologue trigger system (enter_location + time_idle) with
  MonologueBuffer/MonologueState components, wire through ObserverSnapshot
  as current_monologue field, decode on client and display via HUD
- Change PlayerAction::Interact from unit to struct variant carrying
  optional target_entity_id and verb fields

Bumps protocol version from 4 to 5. Regenerates MessagePack fixtures.
All 200 tests pass (170 unit + 30 integration).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-13 19:23:47 +01:00
co-authored by Claude Opus 4.6
parent 8228ef3c1c
commit 3c106aa2d0
27 changed files with 480 additions and 30 deletions
+3
View File
@@ -159,9 +159,12 @@ impl Plugin for BridgePlugin {
.after(crate::simulation::movement::validate_movement),
crate::simulation::interaction::compute_nearby_interactions
.after(crate::simulation::movement::validate_movement),
crate::simulation::monologue::trigger_monologue
.after(crate::simulation::movement::validate_movement),
crate::perception::observer::compute_observer_snapshot
.after(crate::perception::observer::compute_visibility_geometry)
.after(crate::simulation::interaction::compute_nearby_interactions)
.after(crate::simulation::monologue::trigger_monologue)
.before(crate::simulation::time::advance_tick),
crate::perception::observation::emit_observation_events
.after(crate::perception::observer::compute_observer_snapshot),
+25 -4
View File
@@ -15,7 +15,7 @@ pub use crate::simulation::time::{DayPhase, TickRate};
/// negotiation is unnecessary. Client should reject snapshots with version !=
/// PROTOCOL_VERSION. New fields use #[serde(default)] only during the migration
/// period, then the default is removed once both sides are updated.
pub const PROTOCOL_VERSION: u8 = 4;
pub const PROTOCOL_VERSION: u8 = 5;
/// The ONLY data structure crossing the client-server boundary (D-020)
/// Contains all information visible to the observer at a given tick.
@@ -23,8 +23,8 @@ pub const PROTOCOL_VERSION: u8 = 4;
/// 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).
/// v5 adds: current_monologue (#414 internal monologue pipeline).
/// Future fields: ambient sound events, HUD state (D-020 expansion).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ObserverSnapshot {
/// Protocol version for forward compatibility. Current: 4.
@@ -42,6 +42,10 @@ pub struct ObserverSnapshot {
/// 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.
pub nearby_interactions: Vec<NearbyInteraction>,
/// Internal monologue line to display this tick, if any (#414).
/// None when no monologue is triggered. Client shows text and auto-fades.
#[serde(default)]
pub current_monologue: Option<MonologueEvent>,
}
/// Game time data for client display (D-031)
@@ -159,7 +163,12 @@ pub enum PlayerAction {
MoveNorthwest,
MoveSoutheast,
MoveSouthwest,
Interact,
/// Player pressed E on a nearby entity. Carries target + verb from client.
/// v0.1: logged only — full dialogue dispatch is future scope (#415).
Interact {
target_entity_id: Option<u64>,
verb: Option<String>,
},
UsePerceptionMode(String),
Pause,
Unpause,
@@ -204,6 +213,18 @@ pub enum VerbKind {
Talk,
}
/// Internal monologue event sent to the client for display (#414).
/// Contains the text and display duration. Client auto-fades after duration.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MonologueEvent {
/// Monologue line ID (for dedup and cooldown tracking)
pub id: String,
/// The monologue text to display
pub text: String,
/// Display duration in seconds before fade
pub duration_seconds: f32,
}
/// Snapshot buffer resource for staging outgoing ObserverSnapshots
#[derive(Resource, Debug, Default)]
pub struct SnapshotBuffer {