feat(simulation): sprint anomaly double-take monologue (#428, D-055)
When sprinting past a Contradicted entity, queue a delayed retroactive monologue that fires after ~1.5s: "Wait — something wasn't right back there." SprintAnomalyQueue component with first-in-wins semantics. Detection in observer pipeline, processing in monologue system. Wire MovementProfile, Stance, and SprintAnomalyQueue into player spawn. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -16,7 +16,7 @@ use crate::perception::query::{ActivePerceptionMode, VisibilityGeometry};
|
||||
use crate::perception::vision_cone::Facing;
|
||||
use crate::simulation::interaction::NearbyInteractionBuffer;
|
||||
use crate::simulation::inventory::{CarriedBy, InventorySlot, ItemName};
|
||||
use crate::simulation::monologue::MonologueBuffer;
|
||||
use crate::simulation::monologue::{MonologueBuffer, SprintAnomalyQueue};
|
||||
use crate::simulation::movement::{PlayerCharacter, TilePosition, WalkabilityMap};
|
||||
use crate::simulation::stance::Stance;
|
||||
use crate::simulation::time::SimulationTime;
|
||||
@@ -53,7 +53,7 @@ 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>),
|
||||
(Entity, &TilePosition, Option<&Facing>, &KnowledgeGraph, &mut NearbyInteractionBuffer, &mut MonologueBuffer, Option<&Stance>, Option<&CharacterArchetype>, Option<&mut SprintAnomalyQueue>),
|
||||
With<PlayerCharacter>,
|
||||
>,
|
||||
all_entities: Query<(
|
||||
@@ -65,7 +65,7 @@ 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)) =
|
||||
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()
|
||||
else {
|
||||
return;
|
||||
@@ -101,6 +101,25 @@ pub fn compute_observer_snapshot(
|
||||
&mut entities,
|
||||
);
|
||||
|
||||
// Sprint anomaly detection (#428, D-055)
|
||||
// When sprinting, scan visible entities for Contradicted KG state.
|
||||
// Queue the first match for delayed "double-take" monologue.
|
||||
if stance_opt.map(|s| s.0) == Some(MovementStance::Sprint) {
|
||||
if let Some(anomaly_queue) = anomaly_queue_opt.as_mut() {
|
||||
if !anomaly_queue.has_pending() {
|
||||
for &wire_id in &visible_ids {
|
||||
let stable_id = StableId(wire_id);
|
||||
if let Some(knowledge) = observer_kg.entity_knowledge(&stable_id) {
|
||||
if knowledge.state == KnowledgeState::Contradicted {
|
||||
anomaly_queue.push_anomaly(wire_id, time.tick);
|
||||
break; // First-in wins
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let game_time = GameTime {
|
||||
day: time.day(),
|
||||
time_of_day: time.time_of_day_minutes(),
|
||||
|
||||
Reference in New Issue
Block a user