feat(perception): anomaly detection and recognition monologue during delay

Add AnomalyMarker component and detect_anomalies() system that flags
entities with KG relationship PersonOfInterest or Contradicted state
for urgent cognitive delay (0.3s vs 0.6s normal). Add
trigger_recognition_monologue() that fires monologue at delay START
(when grey blob appears), not at completion — the monologue IS the
recognition process per D-060. Includes v0.1 fallback recognition
lines and cooldown tracking. Fixes #450, #451.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-17 17:41:14 +01:00
co-authored by Claude Opus 4.6
parent ed7498cb24
commit 695d2ac843
5 changed files with 745 additions and 5 deletions
+13 -5
View File
@@ -28,6 +28,7 @@ pub fn emit_observation_events(
>,
mut event_queue: ResMut<KnowledgeEventQueue>,
entity_positions: Query<&TilePosition>,
anomaly_markers: Query<(), With<crate::perception::anomaly::AnomalyMarker>>,
) {
let Some(snapshot) = &buffer.snapshot else {
return;
@@ -78,22 +79,29 @@ pub fn emit_observation_events(
} else if let Some(ref mut delay) = cognitive_delay {
// New entity + cognitive delay available: buffer recognition
if !delay.is_pending(&stable_id) {
// TODO(#450): wire RecognitionTrigger::Urgent for observe_anomaly triggers
let trigger = RecognitionTrigger::Normal;
// #450: Urgent trigger for anomalous entities (D-060)
let trigger = if anomaly_markers.get(entity).is_ok() {
RecognitionTrigger::Urgent
} else {
RecognitionTrigger::Normal
};
let delay_until = time.tick + trigger.delay_ticks();
delay.push(PendingRecognition {
target: entity,
stable_id,
position: *pos,
delay_until_tick: time.tick + trigger.delay_ticks(),
delay_until_tick: delay_until,
trigger,
monologue_fired: false,
});
tracing::debug!(
"Cognitive delay queued: stable_id={}, position=({},{},{}), delay_until={}",
"Cognitive delay queued: stable_id={}, position=({},{},{}), trigger={:?}, delay_until={}",
stable_id.0,
pos.x,
pos.y,
pos.z,
time.tick + trigger.delay_ticks(),
trigger,
delay_until,
);
}
} else {