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
+96
View File
@@ -24,6 +24,7 @@ use crate::simulation::interaction::NearbyInteractionBuffer;
use crate::simulation::inventory::{CarriedBy, InventorySlot, ItemName};
use crate::simulation::monologue::{MonologueBuffer, SprintAnomalyQueue};
use crate::simulation::movement::{PlayerCharacter, TilePosition, WalkabilityMap};
use crate::simulation::poi::PointOfInterest;
use crate::simulation::rng::SimRng;
use crate::simulation::sound::SoundEventQueue;
use crate::simulation::stance::Stance;
@@ -94,6 +95,7 @@ pub fn compute_observer_snapshot(
Option<&crate::npc::tell_state::DerivedTellState>,
)>,
inventory_items: Query<(Entity, &CarriedBy, &ItemName, &InventorySlot)>,
poi_query: Query<&PointOfInterest>,
mut buffer: ResMut<SnapshotBuffer>,
sim_rng: Option<Res<SimRng>>,
) {
@@ -272,6 +274,97 @@ pub fn compute_observer_snapshot(
})
});
// Collect discovered POIs for minimap (#151, D-013)
// A POI is "discovered" if the observer's KG contains fact "poi.{poi_id}".
let poi_list: Vec<PoiWire> = poi_query
.iter()
.filter(|poi| observer_kg.knows_fact(&poi.fact_id()))
.map(|poi| PoiWire {
poi_id: poi.poi_id.clone(),
name: poi.name.clone(),
x: poi.position.x,
y: poi.position.y,
z: poi.position.z,
category: poi.category,
})
.collect();
// Build player knowledge dump for journal panel (#264, D-041).
// Sends full KG state — client-side filtering for display grouping.
let player_knowledge = {
let kg_entities: Vec<KnownEntityWire> = observer_kg
.known_entities_iter()
.map(|(sid, ek)| {
let name = ek
.known_attributes
.get("name")
.cloned()
.unwrap_or_else(|| "Unknown".to_string());
let source = match &ek.source {
crate::knowledge::types::KnowledgeSource::DirectObservation { .. } => {
"DirectObservation".to_string()
}
crate::knowledge::types::KnowledgeSource::Heard { .. } => "Heard".to_string(),
crate::knowledge::types::KnowledgeSource::ToldBy { source_id, .. } => {
format!("ToldBy({})", source_id.0)
}
crate::knowledge::types::KnowledgeSource::Inferred { .. } => {
"Inferred".to_string()
}
crate::knowledge::types::KnowledgeSource::Background => {
"Background".to_string()
}
};
KnownEntityWire {
entity_id: sid.0,
name,
confidence: ek.confidence,
source,
state: ek.state,
relationship: ek.relationship,
last_observed_tick: ek.last_observed_tick,
}
})
.collect();
let kg_facts: Vec<KnownFactWire> = observer_kg
.known_facts_iter()
.map(|(fid, fk)| {
let source = match &fk.source {
crate::knowledge::types::KnowledgeSource::DirectObservation { .. } => {
"DirectObservation".to_string()
}
crate::knowledge::types::KnowledgeSource::Heard { .. } => "Heard".to_string(),
crate::knowledge::types::KnowledgeSource::ToldBy { source_id, .. } => {
format!("ToldBy({})", source_id.0)
}
crate::knowledge::types::KnowledgeSource::Inferred { .. } => {
"Inferred".to_string()
}
crate::knowledge::types::KnowledgeSource::Background => {
"Background".to_string()
}
};
KnownFactWire {
fact_id: fid.0.clone(),
confidence: fk.confidence,
source,
state: fk.state,
acquired_tick: fk.acquired_tick,
}
})
.collect();
if kg_entities.is_empty() && kg_facts.is_empty() {
None
} else {
Some(PlayerKnowledgeWire {
entities: kg_entities,
facts: kg_facts,
})
}
};
buffer.snapshot = Some(ObserverSnapshot {
version: crate::bridge::types::PROTOCOL_VERSION,
tick: time.tick,
@@ -292,6 +385,9 @@ pub fn compute_observer_snapshot(
follow_state,
sound_events,
rng_seed: sim_rng.as_deref().map(|r| r.seed()),
poi_list,
examine_result: None, // Populated by examine system when #242 lands
player_knowledge,
});
}