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:
2026-03-17 10:33:15 +01:00
co-authored by Claude Opus 4.6
parent 7f7706442a
commit aa79dd97e7
84 changed files with 2409 additions and 1339 deletions
+124 -65
View File
@@ -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"));
}
+2 -7
View File
@@ -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).
+8 -2
View File
@@ -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)?;
+17 -5
View File
@@ -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"
);
}
}