diff --git a/server/src/simulation/dialogue.rs b/server/src/simulation/dialogue.rs index d8df9b1b3..ff0f129cb 100644 --- a/server/src/simulation/dialogue.rs +++ b/server/src/simulation/dialogue.rs @@ -614,9 +614,14 @@ pub fn process_confrontation_response( let target = confrontation.target; // Effect 1: Shift target NPC to Tier 2 animation (D-047) - commands - .entity(target) - .insert(crate::npc::AnimationTier::Tier2); + // + record routine deviation (symmetric with walk-away path) + commands.entity(target).insert(( + crate::npc::AnimationTier::Tier2, + crate::npc::RoutineDeviation { + trigger: crate::npc::DeviationTrigger::Confrontation, + tick: time.tick, + }, + )); // Effect 2: Decrement observer's relationship with the target (D-033 color fade) if let Some(target_sid) = registry.to_stable(target) { @@ -1651,6 +1656,15 @@ mod tests { Some(&crate::npc::AnimationTier::Tier2), "NPC should shift to Tier2 after confrontation" ); + + // RoutineDeviation should be recorded (symmetric with walk-away) + let deviation = world.get::(npc); + assert!(deviation.is_some(), "NPC should get RoutineDeviation after confrontation"); + assert_eq!( + deviation.unwrap().trigger, + crate::npc::DeviationTrigger::Confrontation, + "Deviation trigger should be Confrontation" + ); } #[test] diff --git a/server/src/simulation/input.rs b/server/src/simulation/input.rs index 8586b5b9c..9c8e2ab2b 100644 --- a/server/src/simulation/input.rs +++ b/server/src/simulation/input.rs @@ -1860,7 +1860,8 @@ mod tests { #[cfg(feature = "gauntlet")] #[test] fn teleport_to_hub_clears_dialogue_markers() { - // #491: TeleportToHub removes ActiveDialogue and TalkRequest. + // #491: TeleportToHub removes ActiveDialogue, TalkRequest, + // WalkAwayRequest, and ConfrontationDelivered. let mut world = bevy_ecs::world::World::new(); world.insert_resource(InputQueue::default()); world.insert_resource(SimulationTime::default()); @@ -1869,7 +1870,7 @@ mod tests { // Spawn a fake NPC target let npc = world.spawn(TilePosition::new(10, 10, 0)).id(); - // Spawn player with active dialogue state + // Spawn player with active dialogue state + mid-confrontation marker let player = world .spawn(( PlayerCharacter, @@ -1880,6 +1881,8 @@ mod tests { interaction_type: crate::knowledge::events::InteractionType::Talk, started_tick: 0, }, + crate::simulation::dialogue::WalkAwayRequest, + crate::simulation::dialogue::ConfrontationDelivered { target: npc }, )) .id(); @@ -1904,6 +1907,18 @@ mod tests { .is_none(), "ActiveDialogue cleared after teleport" ); + assert!( + world + .get::(player) + .is_none(), + "WalkAwayRequest cleared after teleport" + ); + assert!( + world + .get::(player) + .is_none(), + "ConfrontationDelivered cleared after teleport" + ); } #[cfg(feature = "gauntlet")] diff --git a/server/tests/content_scaling.rs b/server/tests/content_scaling.rs index bdc3249a4..3ca5bfa5b 100644 --- a/server/tests/content_scaling.rs +++ b/server/tests/content_scaling.rs @@ -182,8 +182,9 @@ fn scaling_tick_timing_within_budget() { } /// Determinism test: baseline entities produce identical snapshots regardless -/// of extra NPCs being present. The original Gauntlet entities (StableId 0-51) -/// should have the same positions and visibility after the same number of ticks. +/// of extra NPCs being present. The original Gauntlet entities (StableId 0 +/// through RESET_PLATE_STABLE_IDS.1) should have the same positions and +/// visibility after the same number of ticks. #[test] #[cfg(feature = "gauntlet")] fn extra_npcs_dont_affect_baseline_behavior() { @@ -253,6 +254,6 @@ fn extra_npcs_dont_affect_baseline_behavior() { assert_eq!( baseline_original_ids, scaled_original_ids, - "Original Gauntlet entities (id<=51) should be identical in both runs" + "Original Gauntlet entities (id <= max_baseline_id) should be identical in both runs" ); }