feat(perception): add cognitive delay system for fog recognition (#423)
New CognitiveDelay component buffers perception events before emitting KnowledgeEvents. Normal delay: 6 ticks (0.6s), urgent: 3 ticks (0.3s). Drain system runs after emit_observation_events, before process_knowledge_events. Adds pending_recognitions to ObserverSnapshot (protocol v7) for client fog entity visualization. Includes cancellation when entity leaves LOS. Monologue fires during delay (not after) per D-060 — cross-system wiring deferred to follow-up ticket #451. Ref: D-060 (cognitive delay), D-031 (10 tps), D-059 (fog layers) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,7 @@ use std::collections::HashSet;
|
||||
use crate::bridge::types::*;
|
||||
use crate::knowledge::types::KnowledgeState;
|
||||
use crate::knowledge::{EntityRegistry, KnowledgeGraph, StableId};
|
||||
use crate::perception::cognitive_delay::CognitiveDelay;
|
||||
use crate::perception::query::{ActivePerceptionMode, VisibilityGeometry};
|
||||
use crate::perception::vision_cone::Facing;
|
||||
use crate::simulation::interaction::NearbyInteractionBuffer;
|
||||
@@ -53,7 +54,18 @@ pub fn compute_observer_snapshot(
|
||||
geometry: Res<VisibilityGeometry>,
|
||||
registry: Res<EntityRegistry>,
|
||||
mut observer_query: Query<
|
||||
(Entity, &TilePosition, Option<&Facing>, &KnowledgeGraph, &mut NearbyInteractionBuffer, &mut MonologueBuffer, Option<&Stance>, Option<&CharacterArchetype>, Option<&mut SprintAnomalyQueue>),
|
||||
(
|
||||
Entity,
|
||||
&TilePosition,
|
||||
Option<&Facing>,
|
||||
&KnowledgeGraph,
|
||||
&mut NearbyInteractionBuffer,
|
||||
&mut MonologueBuffer,
|
||||
Option<&Stance>,
|
||||
Option<&CharacterArchetype>,
|
||||
Option<&mut SprintAnomalyQueue>,
|
||||
Option<&CognitiveDelay>,
|
||||
),
|
||||
With<PlayerCharacter>,
|
||||
>,
|
||||
all_entities: Query<(
|
||||
@@ -65,8 +77,18 @@ pub fn compute_observer_snapshot(
|
||||
inventory_items: Query<(Entity, &CarriedBy, &ItemName, &InventorySlot)>,
|
||||
mut buffer: ResMut<SnapshotBuffer>,
|
||||
) {
|
||||
let Ok((observer_entity, _observer_pos, facing_opt, observer_kg, mut interaction_buffer, mut monologue_buffer, stance_opt, archetype_opt, mut anomaly_queue_opt)) =
|
||||
observer_query.single_mut()
|
||||
let Ok((
|
||||
observer_entity,
|
||||
_observer_pos,
|
||||
facing_opt,
|
||||
observer_kg,
|
||||
mut interaction_buffer,
|
||||
mut monologue_buffer,
|
||||
stance_opt,
|
||||
archetype_opt,
|
||||
mut anomaly_queue_opt,
|
||||
cognitive_delay_opt,
|
||||
)) = observer_query.single_mut()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
@@ -141,6 +163,27 @@ pub fn compute_observer_snapshot(
|
||||
|
||||
let current_monologue = monologue_buffer.take();
|
||||
|
||||
// Build pending recognitions from CognitiveDelay (#423, D-060)
|
||||
let pending_recognitions = cognitive_delay_opt
|
||||
.map(|delay| {
|
||||
delay
|
||||
.pending()
|
||||
.iter()
|
||||
.map(|p| {
|
||||
let (rx, ry, rz) = p.position.to_render_coords();
|
||||
PendingRecognitionWire {
|
||||
entity_id: p.stable_id.0,
|
||||
x: rx,
|
||||
y: ry,
|
||||
z: rz,
|
||||
remaining_ticks: p.delay_until_tick.saturating_sub(time.tick),
|
||||
total_delay_ticks: p.trigger.delay_ticks(),
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
buffer.snapshot = Some(ObserverSnapshot {
|
||||
version: crate::bridge::types::PROTOCOL_VERSION,
|
||||
tick: time.tick,
|
||||
@@ -152,6 +195,7 @@ pub fn compute_observer_snapshot(
|
||||
visible_tiles: geometry.visible_tiles.clone(),
|
||||
nearby_interactions,
|
||||
current_monologue,
|
||||
pending_recognitions,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -360,13 +404,16 @@ fn apply_phase2_verb_filter(
|
||||
// This implements D-057: "Character differentiation via Phase 2 observer
|
||||
// filter, not separate verb systems."
|
||||
for verb in &mut interaction.verbs {
|
||||
if let Some(label) = archetype_verb_label(archetype, interaction.object_type, verb.kind) {
|
||||
if let Some(label) = archetype_verb_label(archetype, interaction.object_type, verb.kind)
|
||||
{
|
||||
verb.label = label.into();
|
||||
}
|
||||
}
|
||||
|
||||
// Re-sort after priority changes and verb additions
|
||||
interaction.verbs.sort_by_key(|v| (v.priority, v.kind as u8));
|
||||
interaction
|
||||
.verbs
|
||||
.sort_by_key(|v| (v.priority, v.kind as u8));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -385,11 +432,17 @@ fn archetype_verb_label(
|
||||
match (archetype, object_type, kind) {
|
||||
// Smuggler: Container verbs — physical manipulation vocabulary
|
||||
(CharacterArchetype::Smuggler, Some(ObjectType::Container), VerbKind::Open) => Some("Move"),
|
||||
(CharacterArchetype::Smuggler, Some(ObjectType::Container), VerbKind::Search) => Some("Stash"),
|
||||
(CharacterArchetype::Smuggler, Some(ObjectType::Container), VerbKind::Search) => {
|
||||
Some("Stash")
|
||||
}
|
||||
|
||||
// Detective: Container verbs — investigation vocabulary
|
||||
(CharacterArchetype::Detective, Some(ObjectType::Container), VerbKind::Open) => Some("Scan"),
|
||||
(CharacterArchetype::Detective, Some(ObjectType::Container), VerbKind::Search) => Some("Flag"),
|
||||
(CharacterArchetype::Detective, Some(ObjectType::Container), VerbKind::Open) => {
|
||||
Some("Scan")
|
||||
}
|
||||
(CharacterArchetype::Detective, Some(ObjectType::Container), VerbKind::Search) => {
|
||||
Some("Flag")
|
||||
}
|
||||
|
||||
// All other combinations: keep Phase 1 default label
|
||||
_ => None,
|
||||
|
||||
Reference in New Issue
Block a user