fix(simulation): Clippy cleanup and CI enforcement (#635)
Fix all Clippy warnings across the server codebase (2411 insertions, 1341 deletions). Raise type-complexity-threshold to 750 and too-many-arguments to 12 in .clippy.toml for idiomatic Bevy ECS system signatures. The server now passes `cargo clippy -- --deny warnings` cleanly. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -205,11 +205,7 @@ pub const CONFRONTATION_STAGE: GauntletRoom = GauntletRoom {
|
||||
/// distances from the observer: Close (2 tiles), Medium (6 tiles), Long (12 tiles).
|
||||
pub const SOUND_LAB: GauntletRoom = GauntletRoom {
|
||||
name: "sound_lab",
|
||||
origin: TilePosition {
|
||||
x: 0,
|
||||
y: 104,
|
||||
z: 0,
|
||||
},
|
||||
origin: TilePosition { x: 0, y: 104, z: 0 },
|
||||
size: (34, 20),
|
||||
spawn: TilePosition {
|
||||
x: 10,
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
//! - Perception (5): LOS symmetry, sound range boundaries
|
||||
//! - Population (8): NPC count limits, tier assignment correctness
|
||||
//! - Simulation (8): no entity at blocked tile, determinism, pathfinder
|
||||
//! termination, interaction buffer cleared on sprint
|
||||
//! termination, interaction buffer cleared on sprint
|
||||
//!
|
||||
//! The `run_invariants(world: &mut World)` function covers the 29 structural,
|
||||
//! perception, population, and simulation invariants checkable via pure
|
||||
@@ -24,6 +24,12 @@ use crate::knowledge::registry::{EntityRegistry, StableEntityId};
|
||||
#[cfg(feature = "gauntlet")]
|
||||
use crate::knowledge::types::SoundRange;
|
||||
#[cfg(feature = "gauntlet")]
|
||||
use crate::npc::interaction::InteractionMemory;
|
||||
#[cfg(feature = "gauntlet")]
|
||||
use crate::npc::mood::MoodState;
|
||||
#[cfg(feature = "gauntlet")]
|
||||
use crate::npc::routine::ActivityState;
|
||||
#[cfg(feature = "gauntlet")]
|
||||
use crate::npc::{Npc, Want};
|
||||
#[cfg(feature = "gauntlet")]
|
||||
use crate::perception::shadowcast::compute_fov;
|
||||
@@ -38,12 +44,6 @@ use crate::simulation::pathfinding::{ComputedPath, PathBlocked};
|
||||
#[cfg(feature = "gauntlet")]
|
||||
use crate::simulation::sound::SoundEvent;
|
||||
#[cfg(feature = "gauntlet")]
|
||||
use crate::npc::interaction::InteractionMemory;
|
||||
#[cfg(feature = "gauntlet")]
|
||||
use crate::npc::mood::MoodState;
|
||||
#[cfg(feature = "gauntlet")]
|
||||
use crate::npc::routine::ActivityState;
|
||||
#[cfg(feature = "gauntlet")]
|
||||
use crate::simulation::tier::{ActiveSim, BackgroundSim, StateSaved};
|
||||
|
||||
#[cfg(feature = "gauntlet")]
|
||||
@@ -164,8 +164,10 @@ fn inv_s2_walkable_count_in_map_bounds(world: &mut World) {
|
||||
#[cfg(feature = "gauntlet")]
|
||||
fn inv_s3_player_spawn_walkable(world: &mut World) {
|
||||
let player_pos = {
|
||||
let mut q = world.query_filtered::<&TilePosition, bevy_ecs::prelude::With<PlayerCharacter>>();
|
||||
*q.single(world).expect("S3: exactly one PlayerCharacter must exist")
|
||||
let mut q =
|
||||
world.query_filtered::<&TilePosition, bevy_ecs::prelude::With<PlayerCharacter>>();
|
||||
*q.single(world)
|
||||
.expect("S3: exactly one PlayerCharacter must exist")
|
||||
};
|
||||
let wm = world
|
||||
.get_resource::<WalkabilityMap>()
|
||||
@@ -259,11 +261,7 @@ fn inv_s7_room_interiors_have_walkable_tiles(world: &mut World) {
|
||||
assert!(
|
||||
found,
|
||||
"S7: room '{}' (interior x={}..{}, y={}..{}) must have at least one walkable tile",
|
||||
room.name,
|
||||
x_start,
|
||||
x_end,
|
||||
y_start,
|
||||
y_end
|
||||
room.name, x_start, x_end, y_start, y_end
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -334,8 +332,8 @@ fn inv_p4_los_symmetry_at_hub(world: &mut World) {
|
||||
let (ax, ay, bx, by) = (50i32, 58i32, 54i32, 58i32);
|
||||
let range = 12;
|
||||
|
||||
let fov_a = compute_fov(&is_opaque, ax, ay, range, 0);
|
||||
let fov_b = compute_fov(&is_opaque, bx, by, range, 0);
|
||||
let fov_a = compute_fov(is_opaque, ax, ay, range, 0);
|
||||
let fov_b = compute_fov(is_opaque, bx, by, range, 0);
|
||||
|
||||
// A sees B → B must see A (symmetric shadowcasting guarantee, D-035)
|
||||
if fov_a.is_visible(bx, by) {
|
||||
@@ -375,7 +373,7 @@ fn inv_p5_los_range_bounded(world: &mut World) {
|
||||
let range = 6;
|
||||
let (ox, oy) = (50i32, 58i32);
|
||||
|
||||
let fov = compute_fov(&is_opaque, ox, oy, range, 0);
|
||||
let fov = compute_fov(is_opaque, ox, oy, range, 0);
|
||||
|
||||
// Tile at Chebyshev distance = range+2 must not be visible.
|
||||
let (out_x, out_y) = (ox + range + 2, oy);
|
||||
@@ -399,7 +397,10 @@ fn inv_p5_los_range_bounded(world: &mut World) {
|
||||
/// Pop1: Active-tier NPC count does not exceed the D-026 tick-budget limit (80).
|
||||
#[cfg(feature = "gauntlet")]
|
||||
fn inv_pop1_active_npc_count_within_limit(world: &mut World) {
|
||||
let mut q = world.query_filtered::<(), (bevy_ecs::prelude::With<Npc>, bevy_ecs::prelude::With<ActiveSim>)>();
|
||||
let mut q = world.query_filtered::<(), (
|
||||
bevy_ecs::prelude::With<Npc>,
|
||||
bevy_ecs::prelude::With<ActiveSim>,
|
||||
)>();
|
||||
let count = q.iter(world).count();
|
||||
assert!(
|
||||
count <= 80,
|
||||
@@ -417,15 +418,16 @@ fn inv_pop2_all_npcs_have_tile_position(world: &mut World) {
|
||||
q.iter(world).count()
|
||||
};
|
||||
let with_pos = {
|
||||
let mut q = world.query_filtered::<(), (bevy_ecs::prelude::With<Npc>, bevy_ecs::prelude::With<TilePosition>)>();
|
||||
let mut q = world.query_filtered::<(), (
|
||||
bevy_ecs::prelude::With<Npc>,
|
||||
bevy_ecs::prelude::With<TilePosition>,
|
||||
)>();
|
||||
q.iter(world).count()
|
||||
};
|
||||
assert_eq!(
|
||||
with_pos,
|
||||
total,
|
||||
with_pos, total,
|
||||
"Pop2: all {} NPCs must have TilePosition; only {} do",
|
||||
total,
|
||||
with_pos
|
||||
total, with_pos
|
||||
);
|
||||
}
|
||||
|
||||
@@ -438,29 +440,32 @@ fn inv_pop3_all_npcs_have_exactly_one_tier(world: &mut World) {
|
||||
q.iter(world).count()
|
||||
};
|
||||
let active = {
|
||||
let mut q = world.query_filtered::<(), (bevy_ecs::prelude::With<Npc>, bevy_ecs::prelude::With<ActiveSim>)>();
|
||||
let mut q = world.query_filtered::<(), (
|
||||
bevy_ecs::prelude::With<Npc>,
|
||||
bevy_ecs::prelude::With<ActiveSim>,
|
||||
)>();
|
||||
q.iter(world).count()
|
||||
};
|
||||
let bg = {
|
||||
let mut q = world.query_filtered::<(), (bevy_ecs::prelude::With<Npc>, bevy_ecs::prelude::With<BackgroundSim>)>();
|
||||
let mut q = world.query_filtered::<(), (
|
||||
bevy_ecs::prelude::With<Npc>,
|
||||
bevy_ecs::prelude::With<BackgroundSim>,
|
||||
)>();
|
||||
q.iter(world).count()
|
||||
};
|
||||
let ss = {
|
||||
let mut q = world.query_filtered::<(), (bevy_ecs::prelude::With<Npc>, bevy_ecs::prelude::With<StateSaved>)>();
|
||||
let mut q = world.query_filtered::<(), (
|
||||
bevy_ecs::prelude::With<Npc>,
|
||||
bevy_ecs::prelude::With<StateSaved>,
|
||||
)>();
|
||||
q.iter(world).count()
|
||||
};
|
||||
let tier_sum = active + bg + ss;
|
||||
assert_eq!(
|
||||
tier_sum,
|
||||
total,
|
||||
tier_sum, total,
|
||||
"Pop3: each of {} NPCs must have exactly one tier marker; \
|
||||
found {} Active + {} Background + {} StateSaved = {} (should equal {})",
|
||||
total,
|
||||
active,
|
||||
bg,
|
||||
ss,
|
||||
tier_sum,
|
||||
total
|
||||
total, active, bg, ss, tier_sum, total
|
||||
);
|
||||
}
|
||||
|
||||
@@ -468,9 +473,9 @@ fn inv_pop3_all_npcs_have_exactly_one_tier(world: &mut World) {
|
||||
/// Same-layer collision on spawn is a world-setup error.
|
||||
#[cfg(feature = "gauntlet")]
|
||||
fn inv_pop4_no_same_layer_collision(world: &mut World) {
|
||||
use std::collections::BTreeSet;
|
||||
use crate::simulation::movement::TilePresence;
|
||||
use bevy_ecs::prelude::Entity;
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
let mut q = world.query::<(Entity, &TilePosition, Option<&TilePresence>)>();
|
||||
let occupied: Vec<(TilePosition, TilePresence, Entity)> = q
|
||||
@@ -516,15 +521,14 @@ fn inv_pop6_all_npcs_have_want(world: &mut World) {
|
||||
q.iter(world).count()
|
||||
};
|
||||
let with_want = {
|
||||
let mut q = world.query_filtered::<(), (bevy_ecs::prelude::With<Npc>, bevy_ecs::prelude::With<Want>)>();
|
||||
let mut q = world
|
||||
.query_filtered::<(), (bevy_ecs::prelude::With<Npc>, bevy_ecs::prelude::With<Want>)>();
|
||||
q.iter(world).count()
|
||||
};
|
||||
assert_eq!(
|
||||
with_want,
|
||||
total,
|
||||
with_want, total,
|
||||
"Pop6: all {} NPCs must have a Want component; only {} do (D-024 axis 1)",
|
||||
total,
|
||||
with_want
|
||||
total, with_want
|
||||
);
|
||||
}
|
||||
|
||||
@@ -545,8 +549,7 @@ fn inv_pop7_crowd_plaza_npc_count(world: &mut World) {
|
||||
})
|
||||
.count();
|
||||
assert_eq!(
|
||||
count,
|
||||
15,
|
||||
count, 15,
|
||||
"Pop7: Crowd Plaza must contain exactly 15 NPCs; found {}",
|
||||
count
|
||||
);
|
||||
@@ -556,15 +559,12 @@ fn inv_pop7_crowd_plaza_npc_count(world: &mut World) {
|
||||
/// Duplicate StableIds corrupt knowledge-graph references and snapshot output.
|
||||
#[cfg(feature = "gauntlet")]
|
||||
fn inv_pop8_stable_ids_unique(world: &mut World) {
|
||||
use std::collections::BTreeSet;
|
||||
use crate::knowledge::types::StableId;
|
||||
use bevy_ecs::prelude::Entity;
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
let mut q = world.query::<(Entity, &StableEntityId)>();
|
||||
let ids: Vec<(StableId, Entity)> = q
|
||||
.iter(world)
|
||||
.map(|(e, sid)| (sid.0, e))
|
||||
.collect();
|
||||
let ids: Vec<(StableId, Entity)> = q.iter(world).map(|(e, sid)| (sid.0, e)).collect();
|
||||
|
||||
let mut seen: BTreeSet<StableId> = BTreeSet::new();
|
||||
for (id, entity) in ids {
|
||||
@@ -593,7 +593,8 @@ fn inv_sim1_no_entity_at_blocked_tile(world: &mut World) {
|
||||
// Only check movable entities: Npc + PlayerCharacter. Fixtures/signs/reset plates
|
||||
// may legitimately sit in wall tiles (interactable from range, not traversed).
|
||||
let positions: Vec<(Entity, TilePosition)> = {
|
||||
let mut q = world.query_filtered::<(Entity, &TilePosition), Or<(With<Npc>, With<PlayerCharacter>)>>();
|
||||
let mut q = world
|
||||
.query_filtered::<(Entity, &TilePosition), Or<(With<Npc>, With<PlayerCharacter>)>>();
|
||||
q.iter(world).map(|(e, p)| (e, *p)).collect()
|
||||
};
|
||||
let wm = world
|
||||
@@ -619,9 +620,7 @@ fn inv_sim2_computed_path_steps_walkable(world: &mut World) {
|
||||
|
||||
let paths: Vec<(Entity, Vec<TilePosition>)> = {
|
||||
let mut q = world.query::<(Entity, &ComputedPath)>();
|
||||
q.iter(world)
|
||||
.map(|(e, p)| (e, p.steps.clone()))
|
||||
.collect()
|
||||
q.iter(world).map(|(e, p)| (e, p.steps.clone())).collect()
|
||||
};
|
||||
let wm = world
|
||||
.get_resource::<WalkabilityMap>()
|
||||
@@ -667,8 +666,7 @@ fn inv_sim4_player_entity_present(world: &mut World) {
|
||||
let mut q = world.query_filtered::<(), With<PlayerCharacter>>();
|
||||
let count = q.iter(world).count();
|
||||
assert_eq!(
|
||||
count,
|
||||
1,
|
||||
count, 1,
|
||||
"Sim4: exactly one PlayerCharacter must exist; found {}",
|
||||
count
|
||||
);
|
||||
@@ -704,7 +702,7 @@ fn inv_sim6_registry_has_entities(world: &mut World) {
|
||||
.get_resource::<EntityRegistry>()
|
||||
.expect("Sim6: EntityRegistry resource must exist");
|
||||
assert!(
|
||||
registry.len() > 0,
|
||||
!registry.is_empty(),
|
||||
"Sim6: EntityRegistry must contain at least one entity (the player)"
|
||||
);
|
||||
}
|
||||
@@ -750,19 +748,24 @@ fn inv_sim8_player_has_interaction_buffer(world: &mut World) {
|
||||
#[cfg(feature = "gauntlet")]
|
||||
fn inv_s14_active_npcs_have_mood_state(world: &mut World) {
|
||||
let active = {
|
||||
let mut q = world.query_filtered::<(), (bevy_ecs::prelude::With<Npc>, bevy_ecs::prelude::With<ActiveSim>)>();
|
||||
let mut q = world.query_filtered::<(), (
|
||||
bevy_ecs::prelude::With<Npc>,
|
||||
bevy_ecs::prelude::With<ActiveSim>,
|
||||
)>();
|
||||
q.iter(world).count()
|
||||
};
|
||||
let with_mood = {
|
||||
let mut q = world.query_filtered::<(), (bevy_ecs::prelude::With<Npc>, bevy_ecs::prelude::With<ActiveSim>, bevy_ecs::prelude::With<MoodState>)>();
|
||||
let mut q = world.query_filtered::<(), (
|
||||
bevy_ecs::prelude::With<Npc>,
|
||||
bevy_ecs::prelude::With<ActiveSim>,
|
||||
bevy_ecs::prelude::With<MoodState>,
|
||||
)>();
|
||||
q.iter(world).count()
|
||||
};
|
||||
assert_eq!(
|
||||
with_mood,
|
||||
active,
|
||||
with_mood, active,
|
||||
"S14-1: all {} Active NPCs must have MoodState; only {} do",
|
||||
active,
|
||||
with_mood
|
||||
active, with_mood
|
||||
);
|
||||
}
|
||||
|
||||
@@ -775,15 +778,16 @@ fn inv_s14_npcs_have_interaction_memory(world: &mut World) {
|
||||
q.iter(world).count()
|
||||
};
|
||||
let with_mem = {
|
||||
let mut q = world.query_filtered::<(), (bevy_ecs::prelude::With<Npc>, bevy_ecs::prelude::With<InteractionMemory>)>();
|
||||
let mut q = world.query_filtered::<(), (
|
||||
bevy_ecs::prelude::With<Npc>,
|
||||
bevy_ecs::prelude::With<InteractionMemory>,
|
||||
)>();
|
||||
q.iter(world).count()
|
||||
};
|
||||
assert_eq!(
|
||||
with_mem,
|
||||
total,
|
||||
with_mem, total,
|
||||
"S14-2: all {} NPCs must have InteractionMemory; only {} do",
|
||||
total,
|
||||
with_mem
|
||||
total, with_mem
|
||||
);
|
||||
}
|
||||
|
||||
@@ -792,8 +796,8 @@ fn inv_s14_npcs_have_interaction_memory(world: &mut World) {
|
||||
/// "needs to move somewhere". Both at once is contradictory.
|
||||
#[cfg(feature = "gauntlet")]
|
||||
fn inv_s14_no_activity_state_with_path_request(world: &mut World) {
|
||||
use bevy_ecs::prelude::{Entity, With};
|
||||
use crate::simulation::pathfinding::PathRequest;
|
||||
use bevy_ecs::prelude::{Entity, With};
|
||||
|
||||
let with_activity: Vec<Entity> = {
|
||||
let mut q = world.query_filtered::<Entity, With<ActivityState>>();
|
||||
@@ -816,7 +820,8 @@ fn inv_pop3b_no_double_tagged_tiers(world: &mut World) {
|
||||
use bevy_ecs::prelude::{Entity, With};
|
||||
|
||||
let active_and_bg: Vec<Entity> = {
|
||||
let mut q = world.query_filtered::<Entity, (With<Npc>, With<ActiveSim>, With<BackgroundSim>)>();
|
||||
let mut q =
|
||||
world.query_filtered::<Entity, (With<Npc>, With<ActiveSim>, With<BackgroundSim>)>();
|
||||
q.iter(world).collect()
|
||||
};
|
||||
assert!(
|
||||
@@ -826,7 +831,8 @@ fn inv_pop3b_no_double_tagged_tiers(world: &mut World) {
|
||||
);
|
||||
|
||||
let active_and_ss: Vec<Entity> = {
|
||||
let mut q = world.query_filtered::<Entity, (With<Npc>, With<ActiveSim>, With<StateSaved>)>();
|
||||
let mut q =
|
||||
world.query_filtered::<Entity, (With<Npc>, With<ActiveSim>, With<StateSaved>)>();
|
||||
q.iter(world).collect()
|
||||
};
|
||||
assert!(
|
||||
@@ -836,7 +842,8 @@ fn inv_pop3b_no_double_tagged_tiers(world: &mut World) {
|
||||
);
|
||||
|
||||
let bg_and_ss: Vec<Entity> = {
|
||||
let mut q = world.query_filtered::<Entity, (With<Npc>, With<BackgroundSim>, With<StateSaved>)>();
|
||||
let mut q =
|
||||
world.query_filtered::<Entity, (With<Npc>, With<BackgroundSim>, With<StateSaved>)>();
|
||||
q.iter(world).collect()
|
||||
};
|
||||
assert!(
|
||||
@@ -857,14 +864,16 @@ mod system_tests {
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_ecs::schedule::Schedule;
|
||||
|
||||
use crate::bridge::types::MovementStance;
|
||||
use crate::knowledge::registry::EntityRegistry;
|
||||
use crate::npc::{Contentment, Npc, ToleranceThreshold, Want, WantKind};
|
||||
use crate::simulation::interaction::{compute_nearby_interactions, Interactable, NearbyInteractionBuffer};
|
||||
use crate::simulation::interaction::{
|
||||
compute_nearby_interactions, Interactable, NearbyInteractionBuffer,
|
||||
};
|
||||
use crate::simulation::movement::{PlayerCharacter, TilePosition, WalkabilityMap};
|
||||
use crate::simulation::pathfinding::{compute_paths, ComputedPath, PathBlocked, PathRequest};
|
||||
use crate::simulation::stance::Stance;
|
||||
use crate::simulation::tier::ActiveSim;
|
||||
use crate::bridge::types::MovementStance;
|
||||
use crate::knowledge::registry::EntityRegistry;
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Invariant 30-31: Pathfinder terminates on adjacent tile
|
||||
@@ -1042,8 +1051,8 @@ mod system_tests {
|
||||
schedule.add_systems(compute_nearby_interactions);
|
||||
schedule.run(&mut world);
|
||||
|
||||
let mut q = world
|
||||
.query_filtered::<&mut NearbyInteractionBuffer, With<PlayerCharacter>>();
|
||||
let mut q =
|
||||
world.query_filtered::<&mut NearbyInteractionBuffer, With<PlayerCharacter>>();
|
||||
let mut buf = q.single_mut(&mut world).expect("player must exist");
|
||||
let interactions = buf.take();
|
||||
assert!(
|
||||
@@ -1074,8 +1083,8 @@ mod system_tests {
|
||||
schedule.add_systems(compute_nearby_interactions);
|
||||
schedule.run(&mut world);
|
||||
|
||||
let mut q = world
|
||||
.query_filtered::<&mut NearbyInteractionBuffer, With<PlayerCharacter>>();
|
||||
let mut q =
|
||||
world.query_filtered::<&mut NearbyInteractionBuffer, With<PlayerCharacter>>();
|
||||
let mut buf = q.single_mut(&mut world).expect("player must exist");
|
||||
let interactions = buf.take();
|
||||
assert!(
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
//!
|
||||
//! NOT production content. This module provides deterministic room layouts
|
||||
//! with precise entity placement for golden file testing, regression testing,
|
||||
//! and manual QA sessions. Loaded instead of content/ when the server runs
|
||||
//! and manual QA sessions. Loaded instead of server/content/ when the server runs
|
||||
//! the Gauntlet map.
|
||||
//!
|
||||
//! Module structure:
|
||||
@@ -111,11 +111,11 @@ pub fn setup_gauntlet(app: &mut App, archetype: crate::bridge::types::CharacterA
|
||||
carve_room_interior(&mut walkability, 0, 2, 28, 20); // Sprint Gauntlet
|
||||
carve_room_interior(&mut walkability, 74, 26, 24, 16); // Eavesdrop Alcove
|
||||
carve_room_interior(&mut walkability, 84, 2, 32, 24); // Confrontation Stage
|
||||
// Sprint 13 rooms
|
||||
// Sprint 13 rooms
|
||||
carve_room_interior(&mut walkability, 0, 104, 34, 20); // Sound Lab
|
||||
carve_room_interior(&mut walkability, 0, 24, 24, 14); // Decay Observatory
|
||||
carve_room_interior(&mut walkability, 64, 78, 16, 24); // Shift Change
|
||||
// Sprint 22 rooms
|
||||
// Sprint 22 rooms
|
||||
carve_room_interior(&mut walkability, 64, 102, 16, 22); // Zone Gate
|
||||
|
||||
// Carve corridors between hub and rooms
|
||||
@@ -192,8 +192,10 @@ pub fn setup_gauntlet(app: &mut App, archetype: crate::bridge::types::CharacterA
|
||||
// Spawn at Hub center: absolute (50, 58)
|
||||
let profile = MovementProfile::smuggler();
|
||||
let player_pos = TilePosition::new(50, 58, 0);
|
||||
let mut monologue_state = MonologueState::default();
|
||||
monologue_state.character = archetype.as_monologue_key().to_string();
|
||||
let monologue_state = MonologueState {
|
||||
character: archetype.as_monologue_key().to_string(),
|
||||
..Default::default()
|
||||
};
|
||||
let player = app
|
||||
.world_mut()
|
||||
.spawn((
|
||||
@@ -536,8 +538,7 @@ pub fn setup_gauntlet(app: &mut App, archetype: crate::bridge::types::CharacterA
|
||||
}
|
||||
|
||||
// Decay Observatory entities (StableId 69): NPC only, no floor items
|
||||
for id in
|
||||
constants::DECAY_OBSERVATORY_STABLE_IDS.0..=constants::DECAY_OBSERVATORY_STABLE_IDS.1
|
||||
for id in constants::DECAY_OBSERVATORY_STABLE_IDS.0..=constants::DECAY_OBSERVATORY_STABLE_IDS.1
|
||||
{
|
||||
if let Some(entity) = registry.to_entity(&StableId(id)) {
|
||||
if let Some(pos) = app.world().get::<TilePosition>(entity) {
|
||||
@@ -616,7 +617,7 @@ pub fn setup_gauntlet(app: &mut App, archetype: crate::bridge::types::CharacterA
|
||||
use crate::simulation::conversation::NpcColorIndex;
|
||||
use crate::simulation::dialogue::{CurrentMood, DialogueProfile};
|
||||
|
||||
// (location, role) pairs matching content/campaigns/.../dialogue/ YAML pools.
|
||||
// (location, role) pairs matching server/content/campaigns/.../dialogue/ YAML pools.
|
||||
// Cycling through these gives NPC variety across rooms.
|
||||
const DIALOGUE_ROLES: &[(&str, &str)] = &[
|
||||
("the-terminal", "dock-worker"),
|
||||
@@ -698,7 +699,10 @@ mod tests {
|
||||
app.add_plugins(crate::knowledge::KnowledgePlugin);
|
||||
app.add_plugins(crate::npc::NpcPlugin);
|
||||
|
||||
setup_gauntlet(&mut app, crate::bridge::types::CharacterArchetype::default());
|
||||
setup_gauntlet(
|
||||
&mut app,
|
||||
crate::bridge::types::CharacterArchetype::default(),
|
||||
);
|
||||
|
||||
// Run all 29 world-query invariants against the fully-initialized gauntlet world.
|
||||
invariants::run_invariants(app.world_mut());
|
||||
@@ -718,7 +722,10 @@ mod tests {
|
||||
app.add_plugins(crate::knowledge::KnowledgePlugin);
|
||||
app.add_plugins(crate::npc::NpcPlugin);
|
||||
|
||||
setup_gauntlet(&mut app, crate::bridge::types::CharacterArchetype::default());
|
||||
setup_gauntlet(
|
||||
&mut app,
|
||||
crate::bridge::types::CharacterArchetype::default(),
|
||||
);
|
||||
|
||||
let wm = app.world().resource::<WalkabilityMap>();
|
||||
// Hub center at (50, 58) must be walkable
|
||||
@@ -732,7 +739,10 @@ mod tests {
|
||||
app.add_plugins(crate::knowledge::KnowledgePlugin);
|
||||
app.add_plugins(crate::npc::NpcPlugin);
|
||||
|
||||
setup_gauntlet(&mut app, crate::bridge::types::CharacterArchetype::default());
|
||||
setup_gauntlet(
|
||||
&mut app,
|
||||
crate::bridge::types::CharacterArchetype::default(),
|
||||
);
|
||||
|
||||
let wm = app.world().resource::<WalkabilityMap>();
|
||||
// North wall segment at absolute (90, 54) should be blocked
|
||||
@@ -748,7 +758,10 @@ mod tests {
|
||||
app.add_plugins(crate::knowledge::KnowledgePlugin);
|
||||
app.add_plugins(crate::npc::NpcPlugin);
|
||||
|
||||
setup_gauntlet(&mut app, crate::bridge::types::CharacterArchetype::default());
|
||||
setup_gauntlet(
|
||||
&mut app,
|
||||
crate::bridge::types::CharacterArchetype::default(),
|
||||
);
|
||||
|
||||
let wm = app.world().resource::<WalkabilityMap>();
|
||||
// corridor-E center should be walkable
|
||||
@@ -762,7 +775,10 @@ mod tests {
|
||||
app.add_plugins(crate::knowledge::KnowledgePlugin);
|
||||
app.add_plugins(crate::npc::NpcPlugin);
|
||||
|
||||
setup_gauntlet(&mut app, crate::bridge::types::CharacterArchetype::default());
|
||||
setup_gauntlet(
|
||||
&mut app,
|
||||
crate::bridge::types::CharacterArchetype::default(),
|
||||
);
|
||||
|
||||
let registry = app.world().resource::<EntityRegistry>();
|
||||
|
||||
@@ -904,8 +920,8 @@ mod tests {
|
||||
}
|
||||
|
||||
// Decay Observatory at 69
|
||||
for id in constants::DECAY_OBSERVATORY_STABLE_IDS.0
|
||||
..=constants::DECAY_OBSERVATORY_STABLE_IDS.1
|
||||
for id in
|
||||
constants::DECAY_OBSERVATORY_STABLE_IDS.0..=constants::DECAY_OBSERVATORY_STABLE_IDS.1
|
||||
{
|
||||
assert!(
|
||||
registry.to_entity(&StableId(id)).is_some(),
|
||||
|
||||
@@ -17,7 +17,9 @@
|
||||
use bevy_app::prelude::*;
|
||||
|
||||
use crate::knowledge::registry::{EntityRegistry, StableEntityId};
|
||||
use crate::npc::{Contentment, DailyRoutine, Npc, RoutineEntry, ToleranceThreshold, Want, WantKind};
|
||||
use crate::npc::{
|
||||
Contentment, DailyRoutine, Npc, RoutineEntry, ToleranceThreshold, Want, WantKind,
|
||||
};
|
||||
use crate::simulation::interaction::Interactable;
|
||||
use crate::simulation::movement::TilePosition;
|
||||
use crate::simulation::path_follow::MovementSpeed;
|
||||
@@ -177,8 +179,7 @@ mod tests {
|
||||
.id();
|
||||
|
||||
// Advance time to Afternoon boundary (Morning→Afternoon).
|
||||
world.resource_mut::<SimulationTime>().tick =
|
||||
MINUTES_PER_PHASE * TICKS_PER_GAME_MINUTE;
|
||||
world.resource_mut::<SimulationTime>().tick = MINUTES_PER_PHASE * TICKS_PER_GAME_MINUTE;
|
||||
|
||||
let mut schedule = bevy_ecs::schedule::Schedule::default();
|
||||
schedule.add_systems(check_phase_transition);
|
||||
|
||||
@@ -30,22 +30,38 @@ const ORIGIN_X: i32 = 0;
|
||||
const ORIGIN_Y: i32 = 104;
|
||||
|
||||
/// Observer position for Sound Lab tests (absolute).
|
||||
pub const OBSERVER_POS: TilePosition = TilePosition { x: 10, y: 114, z: 0 };
|
||||
pub const OBSERVER_POS: TilePosition = TilePosition {
|
||||
x: 10,
|
||||
y: 114,
|
||||
z: 0,
|
||||
};
|
||||
|
||||
/// Close-range emitter position — 2 tiles east of observer (dist=2, Close ≤3).
|
||||
pub const CLOSE_EMITTER_POS: TilePosition = TilePosition { x: 12, y: 114, z: 0 };
|
||||
pub const CLOSE_EMITTER_POS: TilePosition = TilePosition {
|
||||
x: 12,
|
||||
y: 114,
|
||||
z: 0,
|
||||
};
|
||||
|
||||
/// Medium-range emitter position — 6 tiles east of observer (dist=6, Medium ≤8, outside Close).
|
||||
pub const MEDIUM_EMITTER_POS: TilePosition = TilePosition { x: 16, y: 114, z: 0 };
|
||||
pub const MEDIUM_EMITTER_POS: TilePosition = TilePosition {
|
||||
x: 16,
|
||||
y: 114,
|
||||
z: 0,
|
||||
};
|
||||
|
||||
/// Long-range emitter position — 12 tiles east of observer (dist=12, Long ≤20, outside Medium).
|
||||
pub const LONG_EMITTER_POS: TilePosition = TilePosition { x: 22, y: 114, z: 0 };
|
||||
pub const LONG_EMITTER_POS: TilePosition = TilePosition {
|
||||
x: 22,
|
||||
y: 114,
|
||||
z: 0,
|
||||
};
|
||||
|
||||
/// NPC definitions: (relative_x, relative_y, want_kind, intensity).
|
||||
const NPCS: &[(i32, i32, WantKind, u8)] = &[
|
||||
(12, 10, WantKind::Safety, 3), // npc_sound_close — StableId 66
|
||||
(12, 10, WantKind::Safety, 3), // npc_sound_close — StableId 66
|
||||
(16, 10, WantKind::Knowledge, 4), // npc_sound_medium — StableId 67
|
||||
(22, 10, WantKind::Freedom, 5), // npc_sound_long — StableId 68
|
||||
(22, 10, WantKind::Freedom, 5), // npc_sound_long — StableId 68
|
||||
];
|
||||
|
||||
/// Spawn Sound Lab entities in canonical order (StableId 66-68).
|
||||
|
||||
@@ -25,16 +25,32 @@ use crate::simulation::interaction::{DoorState, Interactable, ObjectType};
|
||||
use crate::simulation::movement::TilePosition;
|
||||
|
||||
/// Observer start position — Terminal side of the zone boundary.
|
||||
pub const OBSERVER_POS: TilePosition = TilePosition { x: 70, y: 112, z: 0 };
|
||||
pub const OBSERVER_POS: TilePosition = TilePosition {
|
||||
x: 70,
|
||||
y: 112,
|
||||
z: 0,
|
||||
};
|
||||
|
||||
/// Door position — first tile of the Corridor zone (zone boundary).
|
||||
pub const DOOR_POS: TilePosition = TilePosition { x: 72, y: 112, z: 0 };
|
||||
pub const DOOR_POS: TilePosition = TilePosition {
|
||||
x: 72,
|
||||
y: 112,
|
||||
z: 0,
|
||||
};
|
||||
|
||||
/// Last walkable tile of the Terminal zone before the boundary.
|
||||
pub const TERMINAL_SIDE_POS: TilePosition = TilePosition { x: 71, y: 112, z: 0 };
|
||||
pub const TERMINAL_SIDE_POS: TilePosition = TilePosition {
|
||||
x: 71,
|
||||
y: 112,
|
||||
z: 0,
|
||||
};
|
||||
|
||||
/// First walkable tile of the Corridor zone after the boundary.
|
||||
pub const CORRIDOR_SIDE_POS: TilePosition = TilePosition { x: 73, y: 112, z: 0 };
|
||||
pub const CORRIDOR_SIDE_POS: TilePosition = TilePosition {
|
||||
x: 73,
|
||||
y: 112,
|
||||
z: 0,
|
||||
};
|
||||
|
||||
/// Spawn Zone Gate entities in canonical order (StableId 75).
|
||||
pub fn spawn_entities(app: &mut App, registry: &mut EntityRegistry) {
|
||||
@@ -149,7 +165,11 @@ mod tests {
|
||||
ZONE_GATE_TERMINAL_ZONE_ID
|
||||
);
|
||||
assert_eq!(
|
||||
zone_map.zone_at(TERMINAL_SIDE_POS.x, TERMINAL_SIDE_POS.y, TERMINAL_SIDE_POS.z),
|
||||
zone_map.zone_at(
|
||||
TERMINAL_SIDE_POS.x,
|
||||
TERMINAL_SIDE_POS.y,
|
||||
TERMINAL_SIDE_POS.z
|
||||
),
|
||||
Some(ZONE_GATE_TERMINAL_ZONE_ID),
|
||||
"Tile (71,112) must be in Terminal zone"
|
||||
);
|
||||
@@ -267,7 +287,9 @@ mod tests {
|
||||
world.resource_mut::<ZoneCrossEventQueue>().drain();
|
||||
|
||||
// Move within Terminal zone (same zone, different tile).
|
||||
world.entity_mut(player).insert(TilePosition::new(68, 112, 0));
|
||||
world
|
||||
.entity_mut(player)
|
||||
.insert(TilePosition::new(68, 112, 0));
|
||||
sched.run(&mut world);
|
||||
|
||||
let events = world.resource_mut::<ZoneCrossEventQueue>().drain();
|
||||
|
||||
Reference in New Issue
Block a user