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:
+124
-65
@@ -8,16 +8,14 @@
|
||||
|
||||
use bevy_ecs::prelude::*;
|
||||
|
||||
use crate::bridge::types::{
|
||||
DebugCommandKind, DebugEnabled, DebugResponsePayload, SnapshotBuffer,
|
||||
};
|
||||
use crate::simulation::triangle::TriangleState;
|
||||
use crate::bridge::types::{DebugCommandKind, DebugEnabled, DebugResponsePayload, SnapshotBuffer};
|
||||
use crate::knowledge::EntityRegistry;
|
||||
use crate::npc::Npc;
|
||||
use crate::simulation::conversation::NpcName;
|
||||
use crate::simulation::movement::{PlayerCharacter, TilePosition, WalkabilityMap};
|
||||
use crate::simulation::tier::ActiveSim;
|
||||
use crate::simulation::time::SimulationTime;
|
||||
use crate::simulation::triangle::TriangleState;
|
||||
use crate::storyteller::{ContaminationActive, ContaminationEventQueue, CONTAMINATION_DELAY_TICKS};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -67,10 +65,13 @@ pub fn handle_debug_commands(
|
||||
registry: Res<EntityRegistry>,
|
||||
mut player_query: Query<(Entity, &mut TilePosition), With<PlayerCharacter>>,
|
||||
triangles: Query<(Entity, &TriangleState), With<ActiveSim>>,
|
||||
npcs: Query<(Entity, &TilePosition, Option<&NpcName>), (With<Npc>, With<ActiveSim>, Without<PlayerCharacter>)>,
|
||||
npcs: Query<
|
||||
(Entity, &TilePosition, Option<&NpcName>),
|
||||
(With<Npc>, With<ActiveSim>, Without<PlayerCharacter>),
|
||||
>,
|
||||
) {
|
||||
// Gate: debug must be enabled
|
||||
let enabled = debug_enabled.as_ref().map_or(false, |d| d.0);
|
||||
let enabled = debug_enabled.as_ref().is_some_and(|d| d.0);
|
||||
let commands = cmd_buffer.drain();
|
||||
if commands.is_empty() {
|
||||
return;
|
||||
@@ -91,10 +92,7 @@ pub fn handle_debug_commands(
|
||||
time.tick = time.tick.saturating_add(n);
|
||||
DebugResponsePayload {
|
||||
command: format!("AdvanceTicks({})", n),
|
||||
text: format!(
|
||||
"Advanced {} ticks: {} -> {}",
|
||||
n, old_tick, time.tick
|
||||
),
|
||||
text: format!("Advanced {} ticks: {} -> {}", n, old_tick, time.tick),
|
||||
success: true,
|
||||
}
|
||||
}
|
||||
@@ -145,7 +143,7 @@ pub fn handle_debug_commands(
|
||||
// Validate target is walkable (if WalkabilityMap available)
|
||||
let walkable = walkability
|
||||
.as_ref()
|
||||
.map_or(true, |wm| wm.can_move_to(&target));
|
||||
.is_none_or(|wm| wm.can_move_to(&target));
|
||||
if !walkable {
|
||||
DebugResponsePayload {
|
||||
command: format!("TeleportToPosition({}, {}, {})", x, y, z),
|
||||
@@ -181,42 +179,42 @@ pub fn handle_debug_commands(
|
||||
// center from tile_bounds). For now, report unimplemented.
|
||||
DebugResponsePayload {
|
||||
command: format!("TeleportToLocation({})", name),
|
||||
text: format!(
|
||||
"TeleportToLocation not yet implemented (needs location tile_bounds from ContentStore). Use TeleportToPosition instead."
|
||||
),
|
||||
text: "TeleportToLocation not yet implemented (needs location tile_bounds from ContentStore). Use TeleportToPosition instead.".to_string(),
|
||||
success: false,
|
||||
}
|
||||
}
|
||||
DebugCommandKind::ForceContaminationActivate => {
|
||||
if contamination.is_none() {
|
||||
if let Some(ref mut cont) = contamination {
|
||||
if cont.0 {
|
||||
DebugResponsePayload {
|
||||
command: "ForceContaminationActivate".to_string(),
|
||||
text: "Contamination already active.".to_string(),
|
||||
success: true,
|
||||
}
|
||||
} else {
|
||||
cont.0 = true;
|
||||
// Also push an event so downstream systems react
|
||||
if let Some(ref mut queue) = contamination_queue {
|
||||
queue.push(crate::storyteller::ContaminationEvent {
|
||||
tick: time.tick,
|
||||
triangles_affected: 0, // no pressure delta applied — use SkipToContamination for that
|
||||
});
|
||||
}
|
||||
DebugResponsePayload {
|
||||
command: "ForceContaminationActivate".to_string(),
|
||||
text: format!(
|
||||
"Contamination force-activated at tick {}. Note: no tension delta applied (use SkipToContamination for full effect).",
|
||||
time.tick
|
||||
),
|
||||
success: true,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
DebugResponsePayload {
|
||||
command: "ForceContaminationActivate".to_string(),
|
||||
text: "Contamination system not available.".to_string(),
|
||||
success: false,
|
||||
}
|
||||
} else if contamination.as_ref().unwrap().0 {
|
||||
DebugResponsePayload {
|
||||
command: "ForceContaminationActivate".to_string(),
|
||||
text: "Contamination already active.".to_string(),
|
||||
success: true,
|
||||
}
|
||||
} else {
|
||||
contamination.as_mut().unwrap().0 = true;
|
||||
// Also push an event so downstream systems react
|
||||
if let Some(ref mut queue) = contamination_queue {
|
||||
queue.push(crate::storyteller::ContaminationEvent {
|
||||
tick: time.tick,
|
||||
triangles_affected: 0, // no pressure delta applied — use SkipToContamination for that
|
||||
});
|
||||
}
|
||||
DebugResponsePayload {
|
||||
command: "ForceContaminationActivate".to_string(),
|
||||
text: format!(
|
||||
"Contamination force-activated at tick {}. Note: no tension delta applied (use SkipToContamination for full effect).",
|
||||
time.tick
|
||||
),
|
||||
success: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
DebugCommandKind::ForceTriangleActivation(ref slug) => {
|
||||
@@ -244,7 +242,10 @@ pub fn handle_debug_commands(
|
||||
} else {
|
||||
DebugResponsePayload {
|
||||
command: format!("InspectNpc({})", wire_id),
|
||||
text: format!("Entity {} exists but is not an Active-tier NPC.", wire_id),
|
||||
text: format!(
|
||||
"Entity {} exists but is not an Active-tier NPC.",
|
||||
wire_id
|
||||
),
|
||||
success: false,
|
||||
}
|
||||
}
|
||||
@@ -290,8 +291,12 @@ pub fn handle_debug_commands(
|
||||
lines.push(format!(
|
||||
" {} | sid={} | pos=({},{},{}) | {}",
|
||||
entity,
|
||||
stable.map(|s| s.0.to_string()).unwrap_or_else(|| "?".to_string()),
|
||||
pos.x, pos.y, pos.z,
|
||||
stable
|
||||
.map(|s| s.0.to_string())
|
||||
.unwrap_or_else(|| "?".to_string()),
|
||||
pos.x,
|
||||
pos.y,
|
||||
pos.z,
|
||||
name_str,
|
||||
));
|
||||
}
|
||||
@@ -366,12 +371,18 @@ mod tests {
|
||||
fn advance_ticks_updates_simulation_time() {
|
||||
let (mut world, mut schedule) = setup_debug_world();
|
||||
world.resource_mut::<SimulationTime>().tick = 100;
|
||||
world.resource_mut::<DebugCommandBuffer>().push(DebugCommandKind::AdvanceTicks(50));
|
||||
world
|
||||
.resource_mut::<DebugCommandBuffer>()
|
||||
.push(DebugCommandKind::AdvanceTicks(50));
|
||||
|
||||
schedule.run(&mut world);
|
||||
|
||||
assert_eq!(world.resource::<SimulationTime>().tick, 150);
|
||||
let resp = world.resource::<SnapshotBuffer>().pending_debug_response.as_ref().unwrap();
|
||||
let resp = world
|
||||
.resource::<SnapshotBuffer>()
|
||||
.pending_debug_response
|
||||
.as_ref()
|
||||
.unwrap();
|
||||
assert!(resp.success);
|
||||
assert!(resp.text.contains("150"));
|
||||
}
|
||||
@@ -380,21 +391,30 @@ mod tests {
|
||||
fn skip_to_contamination_sets_tick() {
|
||||
let (mut world, mut schedule) = setup_debug_world();
|
||||
world.resource_mut::<SimulationTime>().tick = 10;
|
||||
world.resource_mut::<DebugCommandBuffer>().push(DebugCommandKind::SkipToContamination);
|
||||
world
|
||||
.resource_mut::<DebugCommandBuffer>()
|
||||
.push(DebugCommandKind::SkipToContamination);
|
||||
|
||||
schedule.run(&mut world);
|
||||
|
||||
assert_eq!(world.resource::<SimulationTime>().tick, CONTAMINATION_DELAY_TICKS);
|
||||
let resp = world.resource::<SnapshotBuffer>().pending_debug_response.as_ref().unwrap();
|
||||
assert_eq!(
|
||||
world.resource::<SimulationTime>().tick,
|
||||
CONTAMINATION_DELAY_TICKS
|
||||
);
|
||||
let resp = world
|
||||
.resource::<SnapshotBuffer>()
|
||||
.pending_debug_response
|
||||
.as_ref()
|
||||
.unwrap();
|
||||
assert!(resp.success);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn teleport_moves_player() {
|
||||
let (mut world, mut schedule) = setup_debug_world();
|
||||
world.resource_mut::<DebugCommandBuffer>().push(
|
||||
DebugCommandKind::TeleportToPosition { x: 50, y: 60, z: 1 },
|
||||
);
|
||||
world
|
||||
.resource_mut::<DebugCommandBuffer>()
|
||||
.push(DebugCommandKind::TeleportToPosition { x: 50, y: 60, z: 1 });
|
||||
|
||||
schedule.run(&mut world);
|
||||
|
||||
@@ -402,7 +422,11 @@ mod tests {
|
||||
let pos = q.single(&world).unwrap();
|
||||
assert_eq!((pos.x, pos.y, pos.z), (50, 60, 1));
|
||||
|
||||
let resp = world.resource::<SnapshotBuffer>().pending_debug_response.as_ref().unwrap();
|
||||
let resp = world
|
||||
.resource::<SnapshotBuffer>()
|
||||
.pending_debug_response
|
||||
.as_ref()
|
||||
.unwrap();
|
||||
assert!(resp.success);
|
||||
}
|
||||
|
||||
@@ -411,12 +435,18 @@ mod tests {
|
||||
let (mut world, mut schedule) = setup_debug_world();
|
||||
assert!(!world.resource::<ContaminationActive>().0);
|
||||
|
||||
world.resource_mut::<DebugCommandBuffer>().push(DebugCommandKind::ForceContaminationActivate);
|
||||
world
|
||||
.resource_mut::<DebugCommandBuffer>()
|
||||
.push(DebugCommandKind::ForceContaminationActivate);
|
||||
schedule.run(&mut world);
|
||||
|
||||
assert!(world.resource::<ContaminationActive>().0);
|
||||
assert!(!world.resource::<ContaminationEventQueue>().is_empty());
|
||||
let resp = world.resource::<SnapshotBuffer>().pending_debug_response.as_ref().unwrap();
|
||||
let resp = world
|
||||
.resource::<SnapshotBuffer>()
|
||||
.pending_debug_response
|
||||
.as_ref()
|
||||
.unwrap();
|
||||
assert!(resp.success);
|
||||
}
|
||||
|
||||
@@ -424,11 +454,17 @@ mod tests {
|
||||
fn debug_disabled_rejects_commands() {
|
||||
let (mut world, mut schedule) = setup_debug_world();
|
||||
world.insert_resource(DebugEnabled(false));
|
||||
world.resource_mut::<DebugCommandBuffer>().push(DebugCommandKind::GetContaminationStatus);
|
||||
world
|
||||
.resource_mut::<DebugCommandBuffer>()
|
||||
.push(DebugCommandKind::GetContaminationStatus);
|
||||
|
||||
schedule.run(&mut world);
|
||||
|
||||
let resp = world.resource::<SnapshotBuffer>().pending_debug_response.as_ref().unwrap();
|
||||
let resp = world
|
||||
.resource::<SnapshotBuffer>()
|
||||
.pending_debug_response
|
||||
.as_ref()
|
||||
.unwrap();
|
||||
assert!(!resp.success);
|
||||
assert!(resp.text.contains("disabled"));
|
||||
}
|
||||
@@ -437,21 +473,30 @@ mod tests {
|
||||
fn get_contamination_status_reports_state() {
|
||||
let (mut world, mut schedule) = setup_debug_world();
|
||||
world.resource_mut::<SimulationTime>().tick = 42;
|
||||
world.resource_mut::<DebugCommandBuffer>().push(DebugCommandKind::GetContaminationStatus);
|
||||
world
|
||||
.resource_mut::<DebugCommandBuffer>()
|
||||
.push(DebugCommandKind::GetContaminationStatus);
|
||||
|
||||
schedule.run(&mut world);
|
||||
|
||||
let resp = world.resource::<SnapshotBuffer>().pending_debug_response.as_ref().unwrap();
|
||||
let resp = world
|
||||
.resource::<SnapshotBuffer>()
|
||||
.pending_debug_response
|
||||
.as_ref()
|
||||
.unwrap();
|
||||
assert!(resp.success);
|
||||
assert!(resp.text.contains("false")); // not yet active
|
||||
assert!(resp.text.contains("42")); // current tick
|
||||
assert!(resp.text.contains("42")); // current tick
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_commands_produces_no_response() {
|
||||
let (mut world, mut schedule) = setup_debug_world();
|
||||
schedule.run(&mut world);
|
||||
assert!(world.resource::<SnapshotBuffer>().pending_debug_response.is_none());
|
||||
assert!(world
|
||||
.resource::<SnapshotBuffer>()
|
||||
.pending_debug_response
|
||||
.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -461,17 +506,25 @@ mod tests {
|
||||
map.set_walkable(&TilePosition::new(5, 5, 0), false);
|
||||
world.insert_resource(map);
|
||||
|
||||
world.resource_mut::<DebugCommandBuffer>().push(
|
||||
DebugCommandKind::TeleportToPosition { x: 5, y: 5, z: 0 },
|
||||
);
|
||||
world
|
||||
.resource_mut::<DebugCommandBuffer>()
|
||||
.push(DebugCommandKind::TeleportToPosition { x: 5, y: 5, z: 0 });
|
||||
schedule.run(&mut world);
|
||||
|
||||
// Player should NOT have moved
|
||||
let mut q = world.query_filtered::<&TilePosition, With<PlayerCharacter>>();
|
||||
let pos = q.single(&world).unwrap();
|
||||
assert_eq!((pos.x, pos.y, pos.z), (10, 20, 0), "player must not teleport to unwalkable tile");
|
||||
assert_eq!(
|
||||
(pos.x, pos.y, pos.z),
|
||||
(10, 20, 0),
|
||||
"player must not teleport to unwalkable tile"
|
||||
);
|
||||
|
||||
let resp = world.resource::<SnapshotBuffer>().pending_debug_response.as_ref().unwrap();
|
||||
let resp = world
|
||||
.resource::<SnapshotBuffer>()
|
||||
.pending_debug_response
|
||||
.as_ref()
|
||||
.unwrap();
|
||||
assert!(!resp.success);
|
||||
assert!(resp.text.contains("not walkable"));
|
||||
}
|
||||
@@ -482,7 +535,9 @@ mod tests {
|
||||
// Set tick past the delay but contamination not yet active
|
||||
world.resource_mut::<SimulationTime>().tick = CONTAMINATION_DELAY_TICKS + 100;
|
||||
|
||||
world.resource_mut::<DebugCommandBuffer>().push(DebugCommandKind::SkipToContamination);
|
||||
world
|
||||
.resource_mut::<DebugCommandBuffer>()
|
||||
.push(DebugCommandKind::SkipToContamination);
|
||||
schedule.run(&mut world);
|
||||
|
||||
// Tick should NOT have changed (no rewind)
|
||||
@@ -492,7 +547,11 @@ mod tests {
|
||||
"tick must not rewind"
|
||||
);
|
||||
|
||||
let resp = world.resource::<SnapshotBuffer>().pending_debug_response.as_ref().unwrap();
|
||||
let resp = world
|
||||
.resource::<SnapshotBuffer>()
|
||||
.pending_debug_response
|
||||
.as_ref()
|
||||
.unwrap();
|
||||
assert!(!resp.success);
|
||||
assert!(resp.text.contains("already past"));
|
||||
}
|
||||
|
||||
@@ -87,20 +87,15 @@ impl BridgeResource {
|
||||
/// Inserted by BridgePlugin as Pending. Set to Complete in main.rs after
|
||||
/// `send_handshake()` succeeds. `receive_bridge_inputs` logs a warning
|
||||
/// if inputs arrive while still Pending.
|
||||
#[derive(Resource, Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Resource, Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub enum HandshakeState {
|
||||
/// Handshake not yet sent. Inputs arriving in this state trigger a warning.
|
||||
#[default]
|
||||
Pending,
|
||||
/// Handshake sent. Normal operation.
|
||||
Complete,
|
||||
}
|
||||
|
||||
impl Default for HandshakeState {
|
||||
fn default() -> Self {
|
||||
Self::Pending
|
||||
}
|
||||
}
|
||||
|
||||
/// Receive inputs from bridge and push to InputQueue.
|
||||
/// Protocol errors (malformed input) are recoverable: the frame is skipped
|
||||
/// and a SimError is pushed to the SimErrorBuffer for client reporting (#85).
|
||||
|
||||
@@ -137,10 +137,16 @@ impl SimBridge for TcpBridge {
|
||||
// Toggle to blocking for reliable startup message read.
|
||||
// The client sends StartupMessage immediately after handshake validation,
|
||||
// so this read should complete quickly.
|
||||
reader.get_mut().set_nonblocking(false).map_err(BridgeError::Io)?;
|
||||
reader
|
||||
.get_mut()
|
||||
.set_nonblocking(false)
|
||||
.map_err(BridgeError::Io)?;
|
||||
let result = read_framed(reader.get_mut());
|
||||
// Restore non-blocking for the tick loop
|
||||
reader.get_mut().set_nonblocking(true).map_err(BridgeError::Io)?;
|
||||
reader
|
||||
.get_mut()
|
||||
.set_nonblocking(true)
|
||||
.map_err(BridgeError::Io)?;
|
||||
match result? {
|
||||
Some(payload) => {
|
||||
let msg: super::StartupMessage = rmp_serde::from_slice(&payload)?;
|
||||
|
||||
@@ -351,6 +351,7 @@ pub struct VisibleTile {
|
||||
/// Server-authoritative zone assignment. Client maps zone_id to:
|
||||
/// - Audio crossfade target (D-073)
|
||||
/// - Deep fog temperature tint (D-059 layer 3)
|
||||
///
|
||||
/// None for tiles outside any defined zone (corridors, transition spaces).
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub zone_id: Option<u16>,
|
||||
@@ -506,7 +507,9 @@ pub enum PlayerAction {
|
||||
ToggleStanceDown,
|
||||
/// Update player facing without movement (D-054). Client sends when
|
||||
/// the player turns in place (e.g. mouse aim, turn keys).
|
||||
SetFacing { facing: String },
|
||||
SetFacing {
|
||||
facing: String,
|
||||
},
|
||||
/// Teleport player to the Gauntlet hub spawn point (#491).
|
||||
/// Clears dialogue, monologue, and interaction buffers.
|
||||
/// Rejected with a log warning on non-Gauntlet maps.
|
||||
@@ -521,11 +524,15 @@ pub enum PlayerAction {
|
||||
/// Save the current game state to `path` (#553, D-085).
|
||||
/// Client sends this when the player activates the save UI.
|
||||
/// Server executes save_to_file and sends SaveLoadResultWire confirmation.
|
||||
SaveGame { path: String },
|
||||
SaveGame {
|
||||
path: String,
|
||||
},
|
||||
/// Load a previously saved game from `path` (#553, D-085).
|
||||
/// Client sends this when the player selects a save file to load.
|
||||
/// Server executes load_from_file and sends SaveLoadResultWire confirmation.
|
||||
LoadGame { path: String },
|
||||
LoadGame {
|
||||
path: String,
|
||||
},
|
||||
/// Debug console command (#580). Only processed when `DebugEnabled` is true.
|
||||
/// Response delivered via `ObserverSnapshot.debug_response`.
|
||||
DebugCommand(DebugCommandKind),
|
||||
@@ -540,7 +547,9 @@ pub enum PlayerAction {
|
||||
RequestAllSettings,
|
||||
/// Delete a single setting (#627). Restores the key to its default
|
||||
/// (absent from the database). Confirmation via `settings_response`.
|
||||
DeleteSetting { key: String },
|
||||
DeleteSetting {
|
||||
key: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl PlayerAction {
|
||||
@@ -1032,6 +1041,9 @@ mod tests {
|
||||
};
|
||||
let bytes = rmp_serde::to_vec_named(&msg).expect("serialize");
|
||||
let result = rmp_serde::from_slice::<ObserverSnapshot>(&bytes);
|
||||
assert!(result.is_err(), "HandshakeMessage must not deserialize as ObserverSnapshot");
|
||||
assert!(
|
||||
result.is_err(),
|
||||
"HandshakeMessage must not deserialize as ObserverSnapshot"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user