feat(simulation): protocol v14 — POI list, examine result, player knowledge wire types

Add three new ObserverSnapshot fields for client Sprint 18 tickets:
poi_list (Vec<PoiWire>) for minimap #151, examine_result
(Option<ExamineResultWire>) for #174, player_knowledge
(Option<PlayerKnowledgeWire>) for journal #264. POI list populated
live from KG-discovered PointOfInterest components. KG dump
serializes entity/fact knowledge with confidence, source, and state.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-25 02:29:29 +01:00
co-authored by Claude Opus 4.6
parent 2eca8e961b
commit dd3ead7a1b
8 changed files with 240 additions and 6 deletions
+6
View File
@@ -308,6 +308,9 @@ mod tests {
follow_state: None,
sound_events: vec![],
rng_seed: None,
poi_list: vec![],
examine_result: None,
player_knowledge: None,
}
}
@@ -438,6 +441,9 @@ mod tests {
follow_state: None,
sound_events: vec![],
rng_seed: None,
poi_list: vec![],
examine_result: None,
player_knowledge: None,
};
let text = format_snapshot_text(&snap);
assert!(text.contains("Tick 0"));
+99 -2
View File
@@ -5,7 +5,9 @@
use bevy_ecs::prelude::*;
use serde::{Deserialize, Serialize};
pub use crate::knowledge::types::{EntityVisibility, KnowledgeConfidence, RelationshipState};
pub use crate::knowledge::types::{
EntityVisibility, KnowledgeConfidence, KnowledgeState, RelationshipState,
};
pub use crate::simulation::time::{DayPhase, TickRate};
/// Wire protocol version for ObserverSnapshot.
@@ -15,7 +17,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 = 13;
pub const PROTOCOL_VERSION: u8 = 14;
/// The ONLY data structure crossing the client-server boundary (D-020)
/// Contains all information visible to the observer at a given tick.
@@ -34,6 +36,9 @@ pub const PROTOCOL_VERSION: u8 = 13;
/// v12 adds: conversation_events, conversation_ended (#247, D-078 NPC-to-NPC conversations).
/// v13 adds: tell_state on VisibleEntity (#90, D-024 tell system — for future client use),
/// follow_state (#241, follow mechanic HUD state).
/// v14 adds: poi_list (#151, discovered POIs for minimap rendering),
/// examine_result (#174, character-filtered observation text from Examine verb),
/// player_knowledge (#264, partial KG dump for journal/knowledge panel).
/// Future fields: ambient sound events, HUD state (D-020 expansion).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ObserverSnapshot {
@@ -110,6 +115,22 @@ pub struct ObserverSnapshot {
/// None when the RNG resource is unavailable (should not occur in practice).
#[serde(default, skip_serializing_if = "Option::is_none")]
pub rng_seed: Option<u64>,
/// Discovered POIs for minimap rendering (#151, D-013).
/// Contains all POIs the observer has discovered (fact in KG).
/// Client renders nearby POIs as dots, distant POIs as directional arrows.
/// Empty when no POIs have been discovered.
#[serde(default)]
pub poi_list: Vec<PoiWire>,
/// Character-filtered observation text from Examine verb (#174, #242).
/// Present when an examine interaction completed this tick.
/// Client displays as non-interactive overlay, auto-dismisses after 4-6 seconds.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub examine_result: Option<ExamineResultWire>,
/// Partial knowledge graph dump for journal/knowledge panel (#264, D-041).
/// Updated periodically (not every tick — only when KG changes).
/// Client renders as a read-only journal grouped by entity.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub player_knowledge: Option<PlayerKnowledgeWire>,
}
/// Game time data for client display (D-031)
@@ -536,6 +557,82 @@ pub struct DialogueResponseEvent {
pub speaker_name: String,
}
/// A discovered POI crossing the wire for minimap rendering (#151).
/// Derived from PointOfInterest component + KG fact lookup.
/// Client converts position to player-relative vector for minimap display.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PoiWire {
/// POI identifier (matches poi_id in PointOfInterest component).
pub poi_id: String,
/// Display name for minimap label.
pub name: String,
/// World position in simulation tile coordinates.
/// Client converts to player-relative vector for compass placement.
pub x: i32,
pub y: i32,
pub z: i32,
/// Category for icon/color selection on minimap.
pub category: crate::simulation::poi::PoiCategory,
}
/// Character-filtered observation text from Examine verb (#174, #242).
/// The server runs the examine through the observer's KG to produce
/// text appropriate to what the character knows/sees.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExamineResultWire {
/// Wire-format entity ID of the examined entity.
pub entity_id: u64,
/// Character-filtered observation text.
pub text: String,
/// Observer's confidence level about this entity at time of examine.
pub confidence: KnowledgeConfidence,
}
/// Partial knowledge graph dump for the journal panel (#264, D-041).
/// Sent when KG state changes. Contains entity knowledge and fact knowledge
/// that the observer has accumulated.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PlayerKnowledgeWire {
/// Known entities with their knowledge metadata.
pub entities: Vec<KnownEntityWire>,
/// Known facts (non-entity knowledge: POIs, events, abstract info).
pub facts: Vec<KnownFactWire>,
}
/// A single entity knowledge entry for the journal wire format (#264).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KnownEntityWire {
/// Wire-format entity ID.
pub entity_id: u64,
/// Display name (from known_attributes if available, else "Unknown").
pub name: String,
/// Confidence level (Suspects / KnowsOf / KnowsDetails / Direct).
pub confidence: KnowledgeConfidence,
/// How the knowledge was acquired.
pub source: String,
/// Logical state (Active / Contradicted / Stale).
pub state: KnowledgeState,
/// Relationship assessment for color rendering.
pub relationship: RelationshipState,
/// Last tick this entity was observed.
pub last_observed_tick: u64,
}
/// A single fact knowledge entry for the journal wire format (#264).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KnownFactWire {
/// Fact identifier (e.g. "poi.docking_bay_7", "contraband.ring_exists").
pub fact_id: String,
/// Confidence level.
pub confidence: KnowledgeConfidence,
/// How the fact was acquired.
pub source: String,
/// Logical state.
pub state: KnowledgeState,
/// Tick when this fact was learned.
pub acquired_tick: u64,
}
/// Snapshot buffer resource for staging outgoing ObserverSnapshots
#[derive(Resource, Debug, Default)]
pub struct SnapshotBuffer {