diff --git a/server/src/bridge/mod.rs b/server/src/bridge/mod.rs index 98663e6fa..f1803b5bc 100644 --- a/server/src/bridge/mod.rs +++ b/server/src/bridge/mod.rs @@ -73,7 +73,11 @@ pub fn receive_bridge_inputs( match bridge.receive_inputs() { Ok(inputs) => { for input in &inputs { - tracing::debug!("Received input: tick={} action={:?}", input.tick, input.action); + tracing::debug!( + "Received input: tick={} action={:?}", + input.tick, + input.action + ); } for input in inputs { input_queue.push(input); @@ -153,8 +157,7 @@ impl Plugin for BridgePlugin { .add_systems( Update, ( - receive_bridge_inputs - .before(crate::simulation::input::process_player_input), + receive_bridge_inputs.before(crate::simulation::input::process_player_input), crate::perception::observer::compute_visibility_geometry .after(crate::simulation::movement::validate_movement), crate::simulation::interaction::compute_nearby_interactions diff --git a/server/src/content/spawn.rs b/server/src/content/spawn.rs index 9d65283f5..7f805874f 100644 --- a/server/src/content/spawn.rs +++ b/server/src/content/spawn.rs @@ -184,11 +184,11 @@ fn spawn_npc(world: &mut World, profile: &types::NpcProfile, result: &mut SpawnR // Register in EntityRegistry for StableId mapping let stable_id = world.resource_mut::().register(entity); - world - .entity_mut(entity) - .insert(StableEntityId(stable_id)); + world.entity_mut(entity).insert(StableEntityId(stable_id)); - result.npc_ids.insert(profile.canonical_id.clone(), stable_id); + result + .npc_ids + .insert(profile.canonical_id.clone(), stable_id); result.npcs_spawned += 1; tracing::debug!( @@ -418,10 +418,7 @@ fn resolve_routines( for schedule in &routine_file.schedules { let Some(&stable_id) = npc_ids.get(&schedule.npc) else { - tracing::debug!( - "Skipping routine for {}: not in npc_ids map", - schedule.npc - ); + tracing::debug!("Skipping routine for {}: not in npc_ids map", schedule.npc); continue; }; let Some(entity) = world.resource::().to_entity(&stable_id) else { @@ -523,7 +520,10 @@ fn parse_relationship_kind(s: &str) -> npc::RelationshipKind { "superior" => npc::RelationshipKind::Superior, "subordinate" => npc::RelationshipKind::Subordinate, other => { - tracing::warn!("Unknown relationship kind '{}', defaulting to Colleague", other); + tracing::warn!( + "Unknown relationship kind '{}', defaulting to Colleague", + other + ); npc::RelationshipKind::Colleague } } @@ -594,10 +594,7 @@ fn parse_secret_severity(description: &str) -> npc::SecretSeverity { } // Minor: social embarrassment, mild secrets, ambiguous situations - if lower.contains("ambiguous") - || lower.contains("embarrassment") - || lower.contains("gossip") - { + if lower.contains("ambiguous") || lower.contains("embarrassment") || lower.contains("gossip") { return npc::SecretSeverity::Minor; } @@ -715,7 +712,10 @@ mod tests { let tells = world.get::(entity).unwrap(); assert_eq!(tells.tells.len(), 1); - assert_eq!(tells.tells[0].trigger, npc::TellTrigger::StressAboveThreshold); + assert_eq!( + tells.tells[0].trigger, + npc::TellTrigger::StressAboveThreshold + ); let skills = world.get::(entity).unwrap(); assert_eq!(skills.skills.len(), 2); @@ -771,7 +771,10 @@ mod tests { assert_eq!(parse_want_kind("Wealth"), Some(npc::WantKind::Wealth)); assert_eq!(parse_want_kind("safety"), Some(npc::WantKind::Safety)); assert_eq!(parse_want_kind("KNOWLEDGE"), Some(npc::WantKind::Knowledge)); - assert_eq!(parse_want_kind("Connection"), Some(npc::WantKind::Connection)); + assert_eq!( + parse_want_kind("Connection"), + Some(npc::WantKind::Connection) + ); assert_eq!(parse_want_kind("Power"), Some(npc::WantKind::Power)); assert_eq!(parse_want_kind("Freedom"), Some(npc::WantKind::Freedom)); assert_eq!(parse_want_kind("Justice"), Some(npc::WantKind::Justice)); @@ -851,7 +854,9 @@ mod tests { .to_entity(&stable_id) .unwrap(); - let kg = world.get::(entity).expect("KnowledgeGraph should be attached"); + let kg = world + .get::(entity) + .expect("KnowledgeGraph should be attached"); assert!(kg.knows_fact(&FactId("contraband.ring_exists".to_string()))); assert!(kg.knows_fact(&FactId("relationship.kael_trust".to_string()))); assert!(kg.fact_at_least( @@ -886,7 +891,9 @@ mod tests { .to_entity(&stable_id) .unwrap(); - let kg = world.get::(entity).expect("Phase 2 should attach KnowledgeGraph"); + let kg = world + .get::(entity) + .expect("Phase 2 should attach KnowledgeGraph"); assert!(kg.knows_fact(&FactId("investigation.inspection_lapses".to_string()))); } @@ -939,15 +946,39 @@ mod tests { #[test] fn parse_relationship_kinds() { - assert_eq!(parse_relationship_kind("friend"), npc::RelationshipKind::Friend); - assert_eq!(parse_relationship_kind("colleague"), npc::RelationshipKind::Colleague); - assert_eq!(parse_relationship_kind("family"), npc::RelationshipKind::Family); - assert_eq!(parse_relationship_kind("romantic"), npc::RelationshipKind::Romantic); - assert_eq!(parse_relationship_kind("superior"), npc::RelationshipKind::Superior); - assert_eq!(parse_relationship_kind("subordinate"), npc::RelationshipKind::Subordinate); - assert_eq!(parse_relationship_kind("rival"), npc::RelationshipKind::Rival); + assert_eq!( + parse_relationship_kind("friend"), + npc::RelationshipKind::Friend + ); + assert_eq!( + parse_relationship_kind("colleague"), + npc::RelationshipKind::Colleague + ); + assert_eq!( + parse_relationship_kind("family"), + npc::RelationshipKind::Family + ); + assert_eq!( + parse_relationship_kind("romantic"), + npc::RelationshipKind::Romantic + ); + assert_eq!( + parse_relationship_kind("superior"), + npc::RelationshipKind::Superior + ); + assert_eq!( + parse_relationship_kind("subordinate"), + npc::RelationshipKind::Subordinate + ); + assert_eq!( + parse_relationship_kind("rival"), + npc::RelationshipKind::Rival + ); // Unknown defaults to Colleague - assert_eq!(parse_relationship_kind("acquaintance"), npc::RelationshipKind::Colleague); + assert_eq!( + parse_relationship_kind("acquaintance"), + npc::RelationshipKind::Colleague + ); } #[test] diff --git a/server/src/knowledge/graph.rs b/server/src/knowledge/graph.rs index b65a21f64..bbdb16c87 100644 --- a/server/src/knowledge/graph.rs +++ b/server/src/knowledge/graph.rs @@ -108,22 +108,20 @@ impl KnowledgeGraph { // --- Write Operations --- /// Record a direct observation of another entity (entity is in LOS). - pub fn observe_entity( - &mut self, - target: StableId, - position: TilePosition, - tick: u64, - ) { - let entry = self.entities.entry(target).or_insert_with(|| EntityKnowledge { - last_known_position: None, - last_observed_tick: 0, - last_updated_tick: 0, - confidence: KnowledgeConfidence::Direct, - source: KnowledgeSource::DirectObservation { tick }, - state: KnowledgeState::Active, - relationship: RelationshipState::Unknown, - known_attributes: BTreeMap::new(), - }); + pub fn observe_entity(&mut self, target: StableId, position: TilePosition, tick: u64) { + let entry = self + .entities + .entry(target) + .or_insert_with(|| EntityKnowledge { + last_known_position: None, + last_observed_tick: 0, + last_updated_tick: 0, + confidence: KnowledgeConfidence::Direct, + source: KnowledgeSource::DirectObservation { tick }, + state: KnowledgeState::Active, + relationship: RelationshipState::Unknown, + known_attributes: BTreeMap::new(), + }); entry.last_known_position = Some(position); entry.last_observed_tick = tick; entry.last_updated_tick = tick; @@ -251,10 +249,7 @@ mod tests { g.observe_entity(target, make_position(5, 10), 100); g.set_relationship(&target, RelationshipState::Hostile); - assert_eq!( - g.relationship_with(&target), - RelationshipState::Hostile - ); + assert_eq!(g.relationship_with(&target), RelationshipState::Hostile); } #[test] @@ -330,10 +325,7 @@ mod tests { }; // Age = 200 - 100 = 100, which is > decay_after (10) g.decay(200, &thresholds); - assert_eq!( - g.confidence_of(&target), - Some(KnowledgeConfidence::KnowsOf) - ); + assert_eq!(g.confidence_of(&target), Some(KnowledgeConfidence::KnowsOf)); } #[test] diff --git a/server/src/knowledge/mod.rs b/server/src/knowledge/mod.rs index 209350220..4683109b3 100644 --- a/server/src/knowledge/mod.rs +++ b/server/src/knowledge/mod.rs @@ -12,9 +12,7 @@ pub mod graph; pub mod registry; pub mod types; -pub use events::{ - KnowledgeEvent, KnowledgeEventQueue, KnowledgeEventType, -}; +pub use events::{KnowledgeEvent, KnowledgeEventQueue, KnowledgeEventType}; pub use graph::KnowledgeGraph; pub use registry::{EntityRegistry, StableEntityId}; pub use types::*; @@ -33,8 +31,7 @@ impl Plugin for KnowledgePlugin { ( events::process_knowledge_events .after(crate::perception::observer::compute_observer_snapshot), - events::decay_knowledge - .after(events::process_knowledge_events), + events::decay_knowledge.after(events::process_knowledge_events), ), ); tracing::debug!("KnowledgePlugin initialized"); diff --git a/server/src/npc/relationships.rs b/server/src/npc/relationships.rs index 44334e48c..7ed764ec8 100644 --- a/server/src/npc/relationships.rs +++ b/server/src/npc/relationships.rs @@ -141,7 +141,11 @@ mod tests { graph.set_relationship(a, StableId(20), make_edge(RelationshipKind::Colleague, 2)); graph.set_relationship(a, StableId(30), make_edge(RelationshipKind::Rival, -3)); // Different subject — should not appear - graph.set_relationship(StableId(2), StableId(10), make_edge(RelationshipKind::Family, 8)); + graph.set_relationship( + StableId(2), + StableId(10), + make_edge(RelationshipKind::Family, 8), + ); let rels = graph.relationships_of(&a); assert_eq!(rels.len(), 3); @@ -158,9 +162,17 @@ mod tests { graph.set_relationship(StableId(1), target, make_edge(RelationshipKind::Friend, 5)); graph.set_relationship(StableId(2), target, make_edge(RelationshipKind::Rival, -2)); - graph.set_relationship(StableId(3), target, make_edge(RelationshipKind::Colleague, 0)); + graph.set_relationship( + StableId(3), + target, + make_edge(RelationshipKind::Colleague, 0), + ); // Edge to different target — should not appear - graph.set_relationship(StableId(1), StableId(99), make_edge(RelationshipKind::Family, 8)); + graph.set_relationship( + StableId(1), + StableId(99), + make_edge(RelationshipKind::Family, 8), + ); let knowers = graph.who_knows(&target); assert_eq!(knowers.len(), 3); @@ -199,9 +211,21 @@ mod tests { fn deterministic_iteration() { let mut graph = RelationshipGraph::new(); // Insert in arbitrary order - graph.set_relationship(StableId(3), StableId(1), make_edge(RelationshipKind::Rival, -1)); - graph.set_relationship(StableId(1), StableId(2), make_edge(RelationshipKind::Friend, 5)); - graph.set_relationship(StableId(2), StableId(3), make_edge(RelationshipKind::Colleague, 0)); + graph.set_relationship( + StableId(3), + StableId(1), + make_edge(RelationshipKind::Rival, -1), + ); + graph.set_relationship( + StableId(1), + StableId(2), + make_edge(RelationshipKind::Friend, 5), + ); + graph.set_relationship( + StableId(2), + StableId(3), + make_edge(RelationshipKind::Colleague, 0), + ); // Iteration order should be deterministic (sorted by (subject, target)) let keys: Vec<_> = graph.edges.keys().collect(); diff --git a/server/src/npc/routine.rs b/server/src/npc/routine.rs index ee157d967..8952c04d9 100644 --- a/server/src/npc/routine.rs +++ b/server/src/npc/routine.rs @@ -55,11 +55,9 @@ pub fn check_phase_transition( for (entity, current_pos, routine) in npcs.iter() { if let Some(expected_location) = routine.expected_location(current_phase) { if *current_pos != expected_location { - commands - .entity(entity) - .insert(PathRequest { - goal: expected_location, - }); + commands.entity(entity).insert(PathRequest { + goal: expected_location, + }); tracing::trace!( "Entity {:?}: routine path request to {:?} for {:?}", entity, @@ -105,8 +103,7 @@ mod tests { .id(); // Advance time to Afternoon boundary - world.resource_mut::().tick = - MINUTES_PER_PHASE * TICKS_PER_GAME_MINUTE; + world.resource_mut::().tick = MINUTES_PER_PHASE * TICKS_PER_GAME_MINUTE; let mut schedule = bevy_ecs::schedule::Schedule::default(); schedule.add_systems(check_phase_transition); @@ -163,8 +160,7 @@ mod tests { )) .id(); - world.resource_mut::().tick = - MINUTES_PER_PHASE * TICKS_PER_GAME_MINUTE; + world.resource_mut::().tick = MINUTES_PER_PHASE * TICKS_PER_GAME_MINUTE; let mut schedule = bevy_ecs::schedule::Schedule::default(); schedule.add_systems(check_phase_transition); @@ -193,8 +189,7 @@ mod tests { .id(); // Transition to Afternoon, but NPC only has Morning entry - world.resource_mut::().tick = - MINUTES_PER_PHASE * TICKS_PER_GAME_MINUTE; + world.resource_mut::().tick = MINUTES_PER_PHASE * TICKS_PER_GAME_MINUTE; let mut schedule = bevy_ecs::schedule::Schedule::default(); schedule.add_systems(check_phase_transition); diff --git a/server/src/perception/interpretation.rs b/server/src/perception/interpretation.rs index 100be305b..95fd52300 100644 --- a/server/src/perception/interpretation.rs +++ b/server/src/perception/interpretation.rs @@ -210,14 +210,12 @@ mod tests { let mut schedule = bevy_ecs::schedule::Schedule::default(); schedule.add_systems(( compute_visibility_geometry, - compute_observer_snapshot - .after(compute_visibility_geometry), + compute_observer_snapshot.after(compute_visibility_geometry), crate::perception::observation::emit_observation_events .after(compute_observer_snapshot), generate_observation_events .after(crate::perception::observation::emit_observation_events), - crate::knowledge::events::process_knowledge_events - .after(generate_observation_events), + crate::knowledge::events::process_knowledge_events.after(generate_observation_events), )); schedule.run(world); } @@ -228,8 +226,7 @@ mod tests { let mut registry = EntityRegistry::new(0); // Set time to Afternoon - world.resource_mut::().tick = - MINUTES_PER_PHASE * TICKS_PER_GAME_MINUTE; + world.resource_mut::().tick = MINUTES_PER_PHASE * TICKS_PER_GAME_MINUTE; let player = world .spawn(( @@ -381,7 +378,11 @@ mod tests { ) }) .collect(); - assert_eq!(absences.len(), 1, "should detect absence at visible location"); + assert_eq!( + absences.len(), + 1, + "should detect absence at visible location" + ); } #[test] @@ -401,9 +402,7 @@ mod tests { .id(); registry.register(player); - let npc = world - .spawn((Npc, TilePosition::new(16, 14, 0))) - .id(); + let npc = world.spawn((Npc, TilePosition::new(16, 14, 0))).id(); let npc_sid = registry.register(npc); world.insert_resource(registry); @@ -428,9 +427,7 @@ mod tests { let mut world = setup_world(); let mut registry = EntityRegistry::new(0); - let npc = world - .spawn((Npc, TilePosition::new(16, 14, 0))) - .id(); + let npc = world.spawn((Npc, TilePosition::new(16, 14, 0))).id(); let npc_sid = registry.register(npc); // Player already knows about the NPC diff --git a/server/src/perception/query.rs b/server/src/perception/query.rs index 92114bd0e..078253dc8 100644 --- a/server/src/perception/query.rs +++ b/server/src/perception/query.rs @@ -62,8 +62,7 @@ impl PerceptionQuery for NaturalVision { z, ); - let cone_tiles = - apply_vision_cone(&fov, observer_pos.x, observer_pos.y, facing, &config); + let cone_tiles = apply_vision_cone(&fov, observer_pos.x, observer_pos.y, facing, &config); let visible_tiles = cone_tiles .iter() diff --git a/server/src/perception/shadowcast.rs b/server/src/perception/shadowcast.rs index 7da3779bf..e89659e05 100644 --- a/server/src/perception/shadowcast.rs +++ b/server/src/perception/shadowcast.rs @@ -101,7 +101,12 @@ pub fn symmetric_shadowcast( visible.insert((origin_x, origin_y)); // Origin is always visible // Process 4 cardinal quadrants - for &cardinal in &[Cardinal::North, Cardinal::East, Cardinal::South, Cardinal::West] { + for &cardinal in &[ + Cardinal::North, + Cardinal::East, + Cardinal::South, + Cardinal::West, + ] { scan_quadrant(&mut visible, is_opaque, origin_x, origin_y, range, cardinal); } diff --git a/server/src/perception/vision_cone.rs b/server/src/perception/vision_cone.rs index 982709a39..283b9a5ac 100644 --- a/server/src/perception/vision_cone.rs +++ b/server/src/perception/vision_cone.rs @@ -138,8 +138,7 @@ pub fn apply_vision_cone( ) -> Vec<(i32, i32, VisibilitySector)> { fov.visible_tiles() .filter_map(|(x, y)| { - classify_tile(observer_x, observer_y, x, y, facing, config) - .map(|sector| (x, y, sector)) + classify_tile(observer_x, observer_y, x, y, facing, config).map(|sector| (x, y, sector)) }) .collect() } diff --git a/server/src/simulation/input.rs b/server/src/simulation/input.rs index b3a9fee05..65542deb5 100644 --- a/server/src/simulation/input.rs +++ b/server/src/simulation/input.rs @@ -68,7 +68,12 @@ pub fn process_player_input( mut commands: Commands, registry: Res, mut player_query: Query< - (Entity, &TilePosition, Option<&mut Stance>, Option<&mut PlayerMoveCooldown>), + ( + Entity, + &TilePosition, + Option<&mut Stance>, + Option<&mut PlayerMoveCooldown>, + ), With, >, inventory_items: Query<(Entity, &CarriedBy, &ItemName, &InventorySlot)>, @@ -143,34 +148,30 @@ pub fn process_player_input( time.tick_rate = rate; tracing::debug!("Tick rate set to {:?} by player input", rate); } - PlayerAction::Interact { target_entity_id, ref verb } => { - match verb.as_deref() { - Some("Take") => { - handle_take( - &mut commands, - ®istry, - &player_query, - &inventory_items, - target_entity_id, - ); - } - Some("Place") => { - handle_place( - &mut commands, - ®istry, - &player_query, - target_entity_id, - ); - } - _ => { - tracing::info!( + PlayerAction::Interact { + target_entity_id, + ref verb, + } => match verb.as_deref() { + Some("Take") => { + handle_take( + &mut commands, + ®istry, + &player_query, + &inventory_items, + target_entity_id, + ); + } + Some("Place") => { + handle_place(&mut commands, ®istry, &player_query, target_entity_id); + } + _ => { + tracing::info!( "Interact: target={:?}, verb={:?} — logged only, dialogue dispatch future scope (#415)", target_entity_id, verb, ); - } } - } + }, PlayerAction::UsePerceptionMode(ref mode) => { tracing::trace!("UsePerceptionMode({}) — no-op for Sprint 1", mode); } @@ -192,7 +193,12 @@ pub fn process_player_input( #[allow(clippy::type_complexity)] fn apply_move( player_query: &mut Query< - (Entity, &TilePosition, Option<&mut Stance>, Option<&mut PlayerMoveCooldown>), + ( + Entity, + &TilePosition, + Option<&mut Stance>, + Option<&mut PlayerMoveCooldown>, + ), With, >, commands: &mut Commands, @@ -230,7 +236,12 @@ fn handle_take( commands: &mut Commands, registry: &EntityRegistry, player_query: &Query< - (Entity, &TilePosition, Option<&mut Stance>, Option<&mut PlayerMoveCooldown>), + ( + Entity, + &TilePosition, + Option<&mut Stance>, + Option<&mut PlayerMoveCooldown>, + ), With, >, inventory_items: &Query<(Entity, &CarriedBy, &ItemName, &InventorySlot)>, @@ -260,12 +271,16 @@ fn handle_take( // Check inventory capacity let occupied = occupied_slots_for(player_sid, inventory_items); let Some(slot) = find_next_slot(&occupied) else { - tracing::info!("Inventory full ({} slots), cannot take item", MAX_INVENTORY_SLOTS); + tracing::info!( + "Inventory full ({} slots), cannot take item", + MAX_INVENTORY_SLOTS + ); return; }; // Remove TilePosition (item leaves the ground), add CarriedBy + InventorySlot - commands.entity(target_entity) + commands + .entity(target_entity) .remove::() .insert((CarriedBy(player_sid), InventorySlot(slot))); @@ -284,7 +299,12 @@ fn handle_place( commands: &mut Commands, registry: &EntityRegistry, player_query: &Query< - (Entity, &TilePosition, Option<&mut Stance>, Option<&mut PlayerMoveCooldown>), + ( + Entity, + &TilePosition, + Option<&mut Stance>, + Option<&mut PlayerMoveCooldown>, + ), With, >, target_entity_id: Option, @@ -307,7 +327,8 @@ fn handle_place( let place_pos = *player_pos; // Remove inventory components, place item at player's tile - commands.entity(target_entity) + commands + .entity(target_entity) .remove::() .remove::() .insert(place_pos); @@ -338,7 +359,10 @@ mod tests { }); queue.push(PlayerInput { tick: 5, - action: PlayerAction::Interact { target_entity_id: None, verb: None }, + action: PlayerAction::Interact { + target_entity_id: None, + verb: None, + }, }); let inputs = queue.drain_for_tick(3); assert_eq!(inputs.len(), 2); @@ -406,7 +430,10 @@ mod tests { schedule.add_systems(process_player_input); schedule.run(&mut world); - assert_eq!(world.resource::().tick_rate, TickRate::Paused); + assert_eq!( + world.resource::().tick_rate, + TickRate::Paused + ); } #[test] @@ -553,7 +580,10 @@ mod tests { action: PlayerAction::MoveNorth, }); schedule.run(&mut world); - assert!(world.get::(player).is_some(), "first move should succeed"); + assert!( + world.get::(player).is_some(), + "first move should succeed" + ); // Remove MoveIntent (simulating validate_movement consuming it) world.entity_mut(player).remove::(); @@ -564,7 +594,10 @@ mod tests { action: PlayerAction::MoveNorth, }); schedule.run(&mut world); - assert!(world.get::(player).is_none(), "second move should be throttled"); + assert!( + world.get::(player).is_none(), + "second move should be throttled" + ); // Tick 0 again: move north — should succeed (cooldown elapsed) world.resource_mut::().push(PlayerInput { @@ -572,7 +605,10 @@ mod tests { action: PlayerAction::MoveNorth, }); schedule.run(&mut world); - assert!(world.get::(player).is_some(), "third move should succeed after cooldown"); + assert!( + world.get::(player).is_some(), + "third move should succeed after cooldown" + ); } #[test] @@ -609,7 +645,10 @@ mod tests { action: PlayerAction::MoveNorth, }); schedule.run(&mut world); - assert!(world.get::(player).is_some(), "sprint should allow every tick"); + assert!( + world.get::(player).is_some(), + "sprint should allow every tick" + ); } #[test] @@ -655,16 +694,17 @@ mod tests { let player = world .spawn((PlayerCharacter, TilePosition::new(5, 5, 0))) .id(); - let player_sid = world.resource_mut::().register(player); + let player_sid = world + .resource_mut::() + .register(player); // Spawn item near player let item = world - .spawn(( - TilePosition::new(5, 4, 0), - ItemName("Manifest Copy".into()), - )) + .spawn((TilePosition::new(5, 4, 0), ItemName("Manifest Copy".into()))) .id(); - let item_sid = world.resource_mut::().register(item); + let item_sid = world + .resource_mut::() + .register(item); // Issue Take verb world.resource_mut::().push(PlayerInput { @@ -680,10 +720,17 @@ mod tests { schedule.run(&mut world); // Item should have CarriedBy + InventorySlot, no TilePosition - assert!(world.get::(item).is_none(), "item should leave the ground"); - let carried = world.get::(item).expect("item should have CarriedBy"); + assert!( + world.get::(item).is_none(), + "item should leave the ground" + ); + let carried = world + .get::(item) + .expect("item should have CarriedBy"); assert_eq!(carried.0, player_sid); - let slot = world.get::(item).expect("item should have slot"); + let slot = world + .get::(item) + .expect("item should have slot"); assert_eq!(slot.0, 0, "first item goes to slot 0"); } @@ -697,7 +744,9 @@ mod tests { let player = world .spawn((PlayerCharacter, TilePosition::new(5, 5, 0))) .id(); - let player_sid = world.resource_mut::().register(player); + let player_sid = world + .resource_mut::() + .register(player); // Spawn item already in inventory (no TilePosition) let item = world @@ -707,7 +756,9 @@ mod tests { InventorySlot(0), )) .id(); - let item_sid = world.resource_mut::().register(item); + let item_sid = world + .resource_mut::() + .register(item); // Issue Place verb world.resource_mut::().push(PlayerInput { @@ -723,10 +774,19 @@ mod tests { schedule.run(&mut world); // Item should have TilePosition at player's location, no CarriedBy/InventorySlot - let pos = world.get::(item).expect("item should be on ground"); - assert_eq!(*pos, TilePosition::new(5, 5, 0), "placed at player position"); + let pos = world + .get::(item) + .expect("item should be on ground"); + assert_eq!( + *pos, + TilePosition::new(5, 5, 0), + "placed at player position" + ); assert!(world.get::(item).is_none(), "CarriedBy removed"); - assert!(world.get::(item).is_none(), "InventorySlot removed"); + assert!( + world.get::(item).is_none(), + "InventorySlot removed" + ); } #[test] @@ -739,7 +799,9 @@ mod tests { let player = world .spawn((PlayerCharacter, TilePosition::new(5, 5, 0))) .id(); - let player_sid = world.resource_mut::().register(player); + let player_sid = world + .resource_mut::() + .register(player); // Item already in slot 0 world.spawn(( @@ -750,12 +812,11 @@ mod tests { // New item on the ground let item2 = world - .spawn(( - TilePosition::new(5, 4, 0), - ItemName("Token".into()), - )) + .spawn((TilePosition::new(5, 4, 0), ItemName("Token".into()))) .id(); - let item2_sid = world.resource_mut::().register(item2); + let item2_sid = world + .resource_mut::() + .register(item2); world.resource_mut::().push(PlayerInput { tick: 0, @@ -769,7 +830,9 @@ mod tests { schedule.add_systems(process_player_input); schedule.run(&mut world); - let slot = world.get::(item2).expect("item should have slot"); + let slot = world + .get::(item2) + .expect("item should have slot"); assert_eq!(slot.0, 1, "second item goes to slot 1"); } @@ -783,7 +846,9 @@ mod tests { let player = world .spawn((PlayerCharacter, TilePosition::new(5, 5, 0))) .id(); - let player_sid = world.resource_mut::().register(player); + let player_sid = world + .resource_mut::() + .register(player); // Fill all 9 slots for slot in 0..MAX_INVENTORY_SLOTS { @@ -796,12 +861,11 @@ mod tests { // Try to take another item let item = world - .spawn(( - TilePosition::new(5, 4, 0), - ItemName("Overflow".into()), - )) + .spawn((TilePosition::new(5, 4, 0), ItemName("Overflow".into()))) .id(); - let item_sid = world.resource_mut::().register(item); + let item_sid = world + .resource_mut::() + .register(item); world.resource_mut::().push(PlayerInput { tick: 0, @@ -816,8 +880,14 @@ mod tests { schedule.run(&mut world); // Item should still be on the ground - assert!(world.get::(item).is_some(), "item stays on ground"); - assert!(world.get::(item).is_none(), "no CarriedBy when full"); + assert!( + world.get::(item).is_some(), + "item stays on ground" + ); + assert!( + world.get::(item).is_none(), + "no CarriedBy when full" + ); } #[test] @@ -831,15 +901,16 @@ mod tests { let player = world .spawn((PlayerCharacter, TilePosition::new(5, 5, 0))) .id(); - let player_sid = world.resource_mut::().register(player); + let player_sid = world + .resource_mut::() + .register(player); let item = world - .spawn(( - TilePosition::new(5, 4, 0), - ItemName("Manifest Copy".into()), - )) + .spawn((TilePosition::new(5, 4, 0), ItemName("Manifest Copy".into()))) .id(); - let item_sid = world.resource_mut::().register(item); + let item_sid = world + .resource_mut::() + .register(item); let mut schedule = bevy_ecs::schedule::Schedule::default(); schedule.add_systems(process_player_input); @@ -854,7 +925,10 @@ mod tests { }); schedule.run(&mut world); - assert!(world.get::(item).is_none(), "item off ground after Take"); + assert!( + world.get::(item).is_none(), + "item off ground after Take" + ); assert_eq!(world.get::(item).unwrap().0, player_sid); assert_eq!(world.get::(item).unwrap().0, 0); @@ -869,10 +943,22 @@ mod tests { world.resource_mut::().tick = 1; schedule.run(&mut world); - let pos = world.get::(item).expect("item back on ground after Place"); - assert_eq!(*pos, TilePosition::new(5, 5, 0), "placed at player position"); - assert!(world.get::(item).is_none(), "CarriedBy removed after Place"); - assert!(world.get::(item).is_none(), "InventorySlot removed after Place"); + let pos = world + .get::(item) + .expect("item back on ground after Place"); + assert_eq!( + *pos, + TilePosition::new(5, 5, 0), + "placed at player position" + ); + assert!( + world.get::(item).is_none(), + "CarriedBy removed after Place" + ); + assert!( + world.get::(item).is_none(), + "InventorySlot removed after Place" + ); } #[test] diff --git a/server/src/simulation/interaction.rs b/server/src/simulation/interaction.rs index e802a5303..c431c55d6 100644 --- a/server/src/simulation/interaction.rs +++ b/server/src/simulation/interaction.rs @@ -52,30 +52,100 @@ impl ObjectType { pub fn verb_set(&self) -> &'static [VerbDef] { match self { Self::Readable => &[ - VerbDef { kind: VerbKind::Read, label: "Read", priority: 1, close_only: true }, - VerbDef { kind: VerbKind::Observe, label: "Observe", priority: 2, close_only: false }, + VerbDef { + kind: VerbKind::Read, + label: "Read", + priority: 1, + close_only: true, + }, + VerbDef { + kind: VerbKind::Observe, + label: "Observe", + priority: 2, + close_only: false, + }, ], Self::Container => &[ - VerbDef { kind: VerbKind::Open, label: "Open", priority: 1, close_only: true }, - VerbDef { kind: VerbKind::Search, label: "Search", priority: 2, close_only: true }, - VerbDef { kind: VerbKind::Observe, label: "Observe", priority: 3, close_only: false }, + VerbDef { + kind: VerbKind::Open, + label: "Open", + priority: 1, + close_only: true, + }, + VerbDef { + kind: VerbKind::Search, + label: "Search", + priority: 2, + close_only: true, + }, + VerbDef { + kind: VerbKind::Observe, + label: "Observe", + priority: 3, + close_only: false, + }, ], Self::Terminal => &[ - VerbDef { kind: VerbKind::Use, label: "Use", priority: 1, close_only: true }, - VerbDef { kind: VerbKind::Observe, label: "Observe", priority: 2, close_only: false }, + VerbDef { + kind: VerbKind::Use, + label: "Use", + priority: 1, + close_only: true, + }, + VerbDef { + kind: VerbKind::Observe, + label: "Observe", + priority: 2, + close_only: false, + }, ], Self::Door => &[ - VerbDef { kind: VerbKind::Open, label: "Open", priority: 1, close_only: true }, - VerbDef { kind: VerbKind::Close, label: "Close", priority: 2, close_only: true }, - VerbDef { kind: VerbKind::Observe, label: "Observe", priority: 3, close_only: false }, + VerbDef { + kind: VerbKind::Open, + label: "Open", + priority: 1, + close_only: true, + }, + VerbDef { + kind: VerbKind::Close, + label: "Close", + priority: 2, + close_only: true, + }, + VerbDef { + kind: VerbKind::Observe, + label: "Observe", + priority: 3, + close_only: false, + }, ], Self::Pickup => &[ - VerbDef { kind: VerbKind::Take, label: "Take", priority: 1, close_only: true }, - VerbDef { kind: VerbKind::Observe, label: "Observe", priority: 2, close_only: false }, + VerbDef { + kind: VerbKind::Take, + label: "Take", + priority: 1, + close_only: true, + }, + VerbDef { + kind: VerbKind::Observe, + label: "Observe", + priority: 2, + close_only: false, + }, ], Self::Furniture => &[ - VerbDef { kind: VerbKind::Sit, label: "Sit", priority: 1, close_only: true }, - VerbDef { kind: VerbKind::Observe, label: "Observe", priority: 2, close_only: false }, + VerbDef { + kind: VerbKind::Sit, + label: "Sit", + priority: 1, + close_only: true, + }, + VerbDef { + kind: VerbKind::Observe, + label: "Observe", + priority: 2, + close_only: false, + }, ], } } @@ -201,7 +271,10 @@ pub fn compute_nearby_interactions( .to_stable(entity) .map(|sid| sid.0) .unwrap_or_else(|| { - tracing::error!(?entity, "entity in interaction range but not in EntityRegistry"); + tracing::error!( + ?entity, + "entity in interaction range but not in EntityRegistry" + ); entity.to_bits() }); @@ -216,9 +289,7 @@ pub fn compute_nearby_interactions( } // Sort interactions by distance (nearest first) - buffer - .interactions - .sort_by_key(|a| a.distance); + buffer.interactions.sort_by_key(|a| a.distance); } /// Buffer for nearby interaction results, consumed by snapshot generation. @@ -417,11 +488,7 @@ mod tests { fn door_close_range_gets_open_close_observe() { let mut world = setup_world(); spawn_player(&mut world, 5, 5); - world.spawn(( - TilePosition::new(5, 6, 0), - Interactable, - ObjectType::Door, - )); + world.spawn((TilePosition::new(5, 6, 0), Interactable, ObjectType::Door)); let mut schedule = bevy_ecs::schedule::Schedule::default(); schedule.add_systems(compute_nearby_interactions); @@ -439,11 +506,7 @@ mod tests { fn pickup_close_range_gets_take_and_observe() { let mut world = setup_world(); spawn_player(&mut world, 5, 5); - world.spawn(( - TilePosition::new(5, 6, 0), - Interactable, - ObjectType::Pickup, - )); + world.spawn((TilePosition::new(5, 6, 0), Interactable, ObjectType::Pickup)); let mut schedule = bevy_ecs::schedule::Schedule::default(); schedule.add_systems(compute_nearby_interactions); @@ -514,7 +577,10 @@ mod tests { let buffer = read_buffer(&mut world); assert_eq!(buffer.interactions.len(), 1); assert_eq!(buffer.interactions[0].verbs.len(), 1); - assert_eq!(buffer.interactions[0].verbs[0].kind, VerbKind::ExamineObject); + assert_eq!( + buffer.interactions[0].verbs[0].kind, + VerbKind::ExamineObject + ); } #[test] @@ -593,7 +659,10 @@ mod tests { let buffer = read_buffer(&mut world); assert_eq!(buffer.interactions.len(), 2); - assert_eq!(buffer.interactions[0].distance, buffer.interactions[1].distance); + assert_eq!( + buffer.interactions[0].distance, + buffer.interactions[1].distance + ); } #[test] @@ -708,11 +777,7 @@ mod tests { let mut world = setup_world(); spawn_player(&mut world, 5, 5); // Distance 4 = mid range (> CLOSE_RANGE=2, <= MID_RANGE=5) - world.spawn(( - TilePosition::new(5, 9, 0), - Interactable, - obj_type, - )); + world.spawn((TilePosition::new(5, 9, 0), Interactable, obj_type)); let mut schedule = bevy_ecs::schedule::Schedule::default(); schedule.add_systems(compute_nearby_interactions); @@ -720,16 +785,22 @@ mod tests { let buffer = read_buffer(&mut world); assert_eq!( - buffer.interactions.len(), 1, - "{:?} at mid range should produce 1 interaction", obj_type + buffer.interactions.len(), + 1, + "{:?} at mid range should produce 1 interaction", + obj_type ); assert_eq!( - buffer.interactions[0].verbs.len(), 1, - "{:?} at mid range should have exactly 1 verb (Observe)", obj_type + buffer.interactions[0].verbs.len(), + 1, + "{:?} at mid range should have exactly 1 verb (Observe)", + obj_type ); assert_eq!( - buffer.interactions[0].verbs[0].kind, VerbKind::Observe, - "{:?} at mid range verb should be Observe", obj_type + buffer.interactions[0].verbs[0].kind, + VerbKind::Observe, + "{:?} at mid range verb should be Observe", + obj_type ); } } @@ -795,11 +866,16 @@ mod tests { for obj_type in types { for def in obj_type.verb_set() { if def.kind == VerbKind::Observe { - assert!(!def.close_only, "{:?} Observe should be mid-range", obj_type); + assert!( + !def.close_only, + "{:?} Observe should be mid-range", + obj_type + ); } else { assert!( def.close_only, - "{:?} {:?} should be close-only", obj_type, def.kind + "{:?} {:?} should be close-only", + obj_type, def.kind ); } } @@ -822,7 +898,9 @@ mod tests { let verbs = obj_type.verb_set(); assert!( verbs.len() <= 4, - "{:?} has {} verbs, D-057 max is 4", obj_type, verbs.len() + "{:?} has {} verbs, D-057 max is 4", + obj_type, + verbs.len() ); } } @@ -832,7 +910,12 @@ mod tests { // ----------------------------------------------------------------------- /// Spawn player with Stance component for sprint suppression tests. - fn spawn_player_with_stance(world: &mut World, x: i32, y: i32, stance: MovementStance) -> Entity { + fn spawn_player_with_stance( + world: &mut World, + x: i32, + y: i32, + stance: MovementStance, + ) -> Entity { world .spawn(( PlayerCharacter, @@ -854,21 +937,31 @@ mod tests { schedule.run(&mut world); let buffer = read_buffer(&mut world); - assert!(buffer.interactions.is_empty(), "sprint should suppress all interactions"); + assert!( + buffer.interactions.is_empty(), + "sprint should suppress all interactions" + ); } #[test] fn sprint_suppresses_object_interactions() { let mut world = setup_world(); spawn_player_with_stance(&mut world, 5, 5, MovementStance::Sprint); - world.spawn((TilePosition::new(5, 6, 0), Interactable, ObjectType::Terminal)); + world.spawn(( + TilePosition::new(5, 6, 0), + Interactable, + ObjectType::Terminal, + )); let mut schedule = bevy_ecs::schedule::Schedule::default(); schedule.add_systems(compute_nearby_interactions); schedule.run(&mut world); let buffer = read_buffer(&mut world); - assert!(buffer.interactions.is_empty(), "sprint should suppress object interactions"); + assert!( + buffer.interactions.is_empty(), + "sprint should suppress object interactions" + ); } #[test] @@ -882,7 +975,11 @@ mod tests { schedule.run(&mut world); let buffer = read_buffer(&mut world); - assert_eq!(buffer.interactions.len(), 1, "Walk should allow interactions"); + assert_eq!( + buffer.interactions.len(), + 1, + "Walk should allow interactions" + ); } #[test] @@ -896,7 +993,11 @@ mod tests { schedule.run(&mut world); let buffer = read_buffer(&mut world); - assert_eq!(buffer.interactions.len(), 1, "Careful should allow interactions"); + assert_eq!( + buffer.interactions.len(), + 1, + "Careful should allow interactions" + ); } #[test] @@ -910,7 +1011,11 @@ mod tests { schedule.run(&mut world); let buffer = read_buffer(&mut world); - assert_eq!(buffer.interactions.len(), 1, "Crouch should allow interactions"); + assert_eq!( + buffer.interactions.len(), + 1, + "Crouch should allow interactions" + ); } #[test] @@ -925,7 +1030,11 @@ mod tests { schedule.run(&mut world); let buffer = read_buffer(&mut world); - assert_eq!(buffer.interactions.len(), 1, "no Stance component should allow interactions"); + assert_eq!( + buffer.interactions.len(), + 1, + "no Stance component should allow interactions" + ); } #[test] @@ -933,7 +1042,11 @@ mod tests { let mut world = setup_world(); spawn_player_with_stance(&mut world, 5, 5, MovementStance::Sprint); world.spawn((Npc, TilePosition::new(5, 6, 0), Interactable)); - world.spawn((TilePosition::new(6, 5, 0), Interactable, ObjectType::Container)); + world.spawn(( + TilePosition::new(6, 5, 0), + Interactable, + ObjectType::Container, + )); world.spawn((TilePosition::new(4, 5, 0), Interactable)); let mut schedule = bevy_ecs::schedule::Schedule::default(); @@ -941,6 +1054,9 @@ mod tests { schedule.run(&mut world); let buffer = read_buffer(&mut world); - assert!(buffer.interactions.is_empty(), "sprint should suppress all 3 nearby entities"); + assert!( + buffer.interactions.is_empty(), + "sprint should suppress all 3 nearby entities" + ); } } diff --git a/server/src/simulation/inventory.rs b/server/src/simulation/inventory.rs index 675eb6a49..01b68b34d 100644 --- a/server/src/simulation/inventory.rs +++ b/server/src/simulation/inventory.rs @@ -38,12 +38,7 @@ pub struct InventorySlot(pub u8); /// Find the next available inventory slot for a carrier. /// Returns None if all 9 slots are occupied. pub fn find_next_slot(occupied: &[u8]) -> Option { - for slot in 0..MAX_INVENTORY_SLOTS { - if !occupied.contains(&slot) { - return Some(slot); - } - } - None + (0..MAX_INVENTORY_SLOTS).find(|slot| !occupied.contains(slot)) } /// Collect inventory items for a specific carrier (by StableId). @@ -126,8 +121,7 @@ mod tests { let player = world.spawn_empty().id(); let player_sid = world.resource_mut::().register(player); - let mut query_state = - world.query::<(Entity, &CarriedBy, &ItemName, &InventorySlot)>(); + let mut query_state = world.query::<(Entity, &CarriedBy, &ItemName, &InventorySlot)>(); // Can't use system params directly in tests — use world query // Instead, verify the logic by spawning items and checking diff --git a/server/src/simulation/monologue.rs b/server/src/simulation/monologue.rs index 3f53c8b19..c0eabd33c 100644 --- a/server/src/simulation/monologue.rs +++ b/server/src/simulation/monologue.rs @@ -36,9 +36,18 @@ pub(crate) const ANOMALY_DELAY_TICKS: u64 = 90; /// Hardcoded v0.1 sprint anomaly "double-take" lines. /// Future: move to content pools with trigger="sprint_anomaly". const ANOMALY_LINES: &[(&str, &str)] = &[ - ("sprint_anomaly_01", "Wait \u{2014} something wasn't right back there."), - ("sprint_anomaly_02", "Hold on. That face... why were they there?"), - ("sprint_anomaly_03", "Something's off. That wasn't where they should be."), + ( + "sprint_anomaly_01", + "Wait \u{2014} something wasn't right back there.", + ), + ( + "sprint_anomaly_02", + "Hold on. That face... why were they there?", + ), + ( + "sprint_anomaly_03", + "Something's off. That wasn't where they should be.", + ), ]; /// Tracks monologue state for cooldown and trigger detection. @@ -148,7 +157,11 @@ pub fn process_sprint_anomaly_monologue( time: Res, mut rng: ResMut, mut query: Query< - (&mut SprintAnomalyQueue, &mut MonologueBuffer, &mut MonologueState), + ( + &mut SprintAnomalyQueue, + &mut MonologueBuffer, + &mut MonologueState, + ), With, >, ) { @@ -236,7 +249,7 @@ pub fn trigger_monologue( let character = state.character.as_str(); let mut candidates: Vec<(&str, &str)> = Vec::new(); // (id, text) - for (_district_id, district) in &content.0.districts { + for district in content.0.districts.values() { for pool in &district.monologue_pools { if pool.character != character { continue; @@ -255,7 +268,7 @@ pub fn trigger_monologue( if candidates.is_empty() { // All lines for this trigger have been shown; allow repeats - for (_district_id, district) in &content.0.districts { + for district in content.0.districts.values() { for pool in &district.monologue_pools { if pool.character != character { continue; @@ -688,7 +701,11 @@ mod tests { // All hardcoded v0.1 lines should have id prefix and non-empty text assert!(!ANOMALY_LINES.is_empty()); for (id, text) in ANOMALY_LINES { - assert!(id.starts_with("sprint_anomaly_"), "id={} should start with sprint_anomaly_", id); + assert!( + id.starts_with("sprint_anomaly_"), + "id={} should start with sprint_anomaly_", + id + ); assert!(!text.is_empty(), "text for {} should be non-empty", id); } } @@ -716,10 +733,16 @@ mod tests { schedule.run(&mut world); let mut buf_query = world.query::<&MonologueBuffer>(); - assert!(buf_query.single(&world).unwrap().event.is_none(), "should not fire before delay"); + assert!( + buf_query.single(&world).unwrap().event.is_none(), + "should not fire before delay" + ); let mut q_query = world.query::<&SprintAnomalyQueue>(); - assert!(q_query.single(&world).unwrap().has_pending(), "still pending before delay"); + assert!( + q_query.single(&world).unwrap().has_pending(), + "still pending before delay" + ); // Tick 90: delay elapsed — should fire world.resource_mut::().tick = ANOMALY_DELAY_TICKS; @@ -734,7 +757,10 @@ mod tests { // Queue should be cleared let mut q_query = world.query::<&SprintAnomalyQueue>(); - assert!(!q_query.single(&world).unwrap().has_pending(), "queue cleared after fire"); + assert!( + !q_query.single(&world).unwrap().has_pending(), + "queue cleared after fire" + ); // last_fired_tick should be updated let mut state_query = world.query::<&MonologueState>(); diff --git a/server/src/simulation/movement.rs b/server/src/simulation/movement.rs index 13ba4a7a6..7265b1e59 100644 --- a/server/src/simulation/movement.rs +++ b/server/src/simulation/movement.rs @@ -261,7 +261,12 @@ pub struct MoveIntent { pub fn validate_movement( mut commands: Commands, walkability: Option>, - mut movers: Query<(Entity, &MoveIntent, &mut TilePosition, Option<&TilePresence>)>, + mut movers: Query<( + Entity, + &MoveIntent, + &mut TilePosition, + Option<&TilePresence>, + )>, stationary: Query<(Entity, &TilePosition, Option<&TilePresence>), Without>, ) { let Some(map) = walkability else { @@ -290,12 +295,17 @@ pub fn validate_movement( } else if occupied.contains_key(&slot) { tracing::trace!( "Entity {:?} blocked by entity at {:?} (layer {:?})", - entity, target, layer + entity, + target, + layer ); } else { tracing::trace!( "Entity {:?} moving from {:?} to {:?} (layer {:?})", - entity, *position, target, layer + entity, + *position, + target, + layer ); // Free old layer slot, claim new one occupied.remove(&(*position, layer)); diff --git a/server/src/simulation/path_follow.rs b/server/src/simulation/path_follow.rs index 2e0d6bd88..64a11c3fe 100644 --- a/server/src/simulation/path_follow.rs +++ b/server/src/simulation/path_follow.rs @@ -151,10 +151,7 @@ mod tests { Npc, TilePosition::new(0, 0, 0), ComputedPath { - steps: vec![ - TilePosition::new(1, 0, 0), - TilePosition::new(2, 0, 0), - ], + steps: vec![TilePosition::new(1, 0, 0), TilePosition::new(2, 0, 0)], current_index: 0, }, MovementSpeed::new(3), diff --git a/server/src/simulation/pathfinding.rs b/server/src/simulation/pathfinding.rs index 6a64ef11c..cc1b1a365 100644 --- a/server/src/simulation/pathfinding.rs +++ b/server/src/simulation/pathfinding.rs @@ -102,7 +102,12 @@ pub fn compute_paths( Some((path, _cost)) => { // path includes start position; skip it let steps: Vec = path.into_iter().skip(1).collect(); - tracing::trace!("Entity {:?}: path to {:?}, {} steps", entity, goal, steps.len()); + tracing::trace!( + "Entity {:?}: path to {:?}, {} steps", + entity, + goal, + steps.len() + ); commands.entity(entity).insert(ComputedPath { steps, current_index: 0, @@ -208,10 +213,7 @@ mod tests { world.insert_resource(map); let entity = world - .spawn(( - TilePosition::new(5, 5, 0), - PathRequest { goal }, - )) + .spawn((TilePosition::new(5, 5, 0), PathRequest { goal })) .id(); let mut schedule = bevy_ecs::schedule::Schedule::default(); diff --git a/server/src/simulation/stance.rs b/server/src/simulation/stance.rs index 302318172..0109f405b 100644 --- a/server/src/simulation/stance.rs +++ b/server/src/simulation/stance.rs @@ -193,7 +193,7 @@ mod tests { fn cooldown_stance_switch_mid_cooldown() { let mut cd = PlayerMoveCooldown::default(); assert!(cd.try_move(MovementStance::Crouch)); // move at crouch speed - // Switch to sprint mid-cooldown + // Switch to sprint mid-cooldown assert!(cd.try_move(MovementStance::Sprint)); // sprint allows every tick } @@ -239,7 +239,13 @@ mod tests { fn movement_profile_as_ecs_component() { let mut world = bevy_ecs::world::World::new(); let profile = MovementProfile::smuggler(); - let entity = world.spawn((profile, profile.initial_stance(), PlayerMoveCooldown::default())).id(); + let entity = world + .spawn(( + profile, + profile.initial_stance(), + PlayerMoveCooldown::default(), + )) + .id(); let stored = world.get::(entity).unwrap(); assert_eq!(stored.default_stance, MovementStance::Walk); diff --git a/server/src/simulation/time.rs b/server/src/simulation/time.rs index b743cda98..e985316ca 100644 --- a/server/src/simulation/time.rs +++ b/server/src/simulation/time.rs @@ -115,7 +115,10 @@ mod tests { #[test] fn tick_to_minute_conversion() { - let time = SimulationTime { tick: 10, ..Default::default() }; + let time = SimulationTime { + tick: 10, + ..Default::default() + }; assert_eq!(time.game_minutes(), 1); } @@ -123,18 +126,30 @@ mod tests { fn day_phase_boundaries() { let time = SimulationTime::default(); assert_eq!(time.day_phase(), DayPhase::Morning); - let time = SimulationTime { tick: 360 * TICKS_PER_GAME_MINUTE, ..Default::default() }; + let time = SimulationTime { + tick: 360 * TICKS_PER_GAME_MINUTE, + ..Default::default() + }; assert_eq!(time.day_phase(), DayPhase::Afternoon); - let time = SimulationTime { tick: 720 * TICKS_PER_GAME_MINUTE, ..Default::default() }; + let time = SimulationTime { + tick: 720 * TICKS_PER_GAME_MINUTE, + ..Default::default() + }; assert_eq!(time.day_phase(), DayPhase::Evening); - let time = SimulationTime { tick: 1080 * TICKS_PER_GAME_MINUTE, ..Default::default() }; + let time = SimulationTime { + tick: 1080 * TICKS_PER_GAME_MINUTE, + ..Default::default() + }; assert_eq!(time.day_phase(), DayPhase::Night); } #[test] fn paused_prevents_tick_advance() { let mut world = bevy_ecs::world::World::new(); - world.insert_resource(SimulationTime { tick_rate: TickRate::Paused, ..Default::default() }); + world.insert_resource(SimulationTime { + tick_rate: TickRate::Paused, + ..Default::default() + }); let mut schedule = bevy_ecs::schedule::Schedule::default(); schedule.add_systems(advance_tick); schedule.run(&mut world); @@ -154,7 +169,10 @@ mod tests { #[test] fn half_rate_advances_every_two_frames() { let mut world = bevy_ecs::world::World::new(); - world.insert_resource(SimulationTime { tick_rate: TickRate::Half, ..Default::default() }); + world.insert_resource(SimulationTime { + tick_rate: TickRate::Half, + ..Default::default() + }); let mut schedule = bevy_ecs::schedule::Schedule::default(); schedule.add_systems(advance_tick); @@ -179,15 +197,24 @@ mod tests { fn paused_helper_method() { let time = SimulationTime::default(); assert!(!time.paused()); - let time = SimulationTime { tick_rate: TickRate::Paused, ..Default::default() }; + let time = SimulationTime { + tick_rate: TickRate::Paused, + ..Default::default() + }; assert!(time.paused()); - let time = SimulationTime { tick_rate: TickRate::Half, ..Default::default() }; + let time = SimulationTime { + tick_rate: TickRate::Half, + ..Default::default() + }; assert!(!time.paused()); } #[test] fn day_wraparound_at_midnight() { - let time = SimulationTime { tick: MINUTES_PER_DAY * TICKS_PER_GAME_MINUTE, ..Default::default() }; + let time = SimulationTime { + tick: MINUTES_PER_DAY * TICKS_PER_GAME_MINUTE, + ..Default::default() + }; assert_eq!(time.day_phase(), DayPhase::Morning); assert_eq!(time.time_of_day_minutes(), 0); assert_eq!(time.day(), 1); @@ -195,7 +222,10 @@ mod tests { #[test] fn day_calculation() { - let time = SimulationTime { tick: 3 * MINUTES_PER_DAY * TICKS_PER_GAME_MINUTE + 100, ..Default::default() }; + let time = SimulationTime { + tick: 3 * MINUTES_PER_DAY * TICKS_PER_GAME_MINUTE + 100, + ..Default::default() + }; assert_eq!(time.day(), 3); } @@ -203,7 +233,10 @@ mod tests { fn tick_rate_switch_mid_accumulation() { // Half->Full with 0.5 remainder: Full should tick immediately (0.5 + 1.0 >= 1.0) let mut world = bevy_ecs::world::World::new(); - world.insert_resource(SimulationTime { tick_rate: TickRate::Half, ..Default::default() }); + world.insert_resource(SimulationTime { + tick_rate: TickRate::Half, + ..Default::default() + }); let mut schedule = bevy_ecs::schedule::Schedule::default(); schedule.add_systems(advance_tick); @@ -233,7 +266,10 @@ mod tests { #[test] fn half_rate_no_drift_over_10000_frames() { let mut world = bevy_ecs::world::World::new(); - world.insert_resource(SimulationTime { tick_rate: TickRate::Half, ..Default::default() }); + world.insert_resource(SimulationTime { + tick_rate: TickRate::Half, + ..Default::default() + }); let mut schedule = bevy_ecs::schedule::Schedule::default(); schedule.add_systems(advance_tick); diff --git a/server/tests/game_loop.rs b/server/tests/game_loop.rs index 58df823a2..1959757b3 100644 --- a/server/tests/game_loop.rs +++ b/server/tests/game_loop.rs @@ -66,14 +66,20 @@ fn player_moves_north_through_full_pipeline() { rmp_serde::from_slice(&response).expect("deserialize snapshot"); // Snapshot captures state at end of tick 0 (before advance_tick increments to 1) - assert_eq!(snapshot.version, 6); + assert_eq!(snapshot.version, PROTOCOL_VERSION); assert_eq!(snapshot.tick, 0); assert_eq!(snapshot.entities.len(), 1); // v2 fields populated assert_eq!(snapshot.game_time.day, 0); - assert_eq!(snapshot.game_time.day_phase, settled_reach_server::simulation::time::DayPhase::Morning); - assert_eq!(snapshot.game_time.tick_rate, settled_reach_server::simulation::time::TickRate::Full); + assert_eq!( + snapshot.game_time.day_phase, + settled_reach_server::simulation::time::DayPhase::Morning + ); + assert_eq!( + snapshot.game_time.tick_rate, + settled_reach_server::simulation::time::TickRate::Full + ); let player_entity = &snapshot.entities[0]; // Player started at (16, 16, 0), moved north (y-1) to (16, 15, 0) @@ -82,7 +88,10 @@ fn player_moves_north_through_full_pipeline() { assert_eq!(player_entity.y, 15.5); assert_eq!(player_entity.z, 0); assert!(matches!(player_entity.kind, EntityKind::Player)); - assert!(matches!(player_entity.visibility, VisibilitySector::Forward)); + assert!(matches!( + player_entity.visibility, + VisibilitySector::Forward + )); // Clean up drop(reader); diff --git a/server/tests/shadowcast_bench.rs b/server/tests/shadowcast_bench.rs index 9852b7e5b..91ec6b660 100644 --- a/server/tests/shadowcast_bench.rs +++ b/server/tests/shadowcast_bench.rs @@ -4,9 +4,9 @@ //! Run with: cargo test --test shadowcast_bench -- --ignored --nocapture use rand::Rng; -use rand_chacha::ChaCha8Rng; use rand::SeedableRng; -use settled_reach_server::perception::shadowcast::{symmetric_shadowcast, recursive_shadowcast}; +use rand_chacha::ChaCha8Rng; +use settled_reach_server::perception::shadowcast::{recursive_shadowcast, symmetric_shadowcast}; use std::collections::HashSet; use std::time::Instant; @@ -173,12 +173,14 @@ fn benchmark_symmetric_vs_recursive() { let results = bench_config(&config); - println!(" Symmetric: {:.2}ms total, {:.2}µs/call, {:.1} tiles avg", + println!( + " Symmetric: {:.2}ms total, {:.2}µs/call, {:.1} tiles avg", results.symmetric_ms, results.symmetric_ms * 1000.0 / config.iterations as f64, results.symmetric_avg_tiles ); - println!(" Recursive: {:.2}ms total, {:.2}µs/call, {:.1} tiles avg", + println!( + " Recursive: {:.2}ms total, {:.2}µs/call, {:.1} tiles avg", results.recursive_ms, results.recursive_ms * 1000.0 / config.iterations as f64, results.recursive_avg_tiles