feat(ci): ban HashMap in simulation crate via clippy (#343)
Add clippy::disallowed_types for std::collections::HashMap scoped to the simulation crate. Replace HashMap with BTreeMap in movement.rs for deterministic iteration order. Allow exception in perception/query.rs where iteration order is irrelevant (per-frame scratch buffer). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
# Clippy configuration for settled-reach-server
|
||||
# Enforces determinism-safe collection types in simulation code (D-030).
|
||||
|
||||
# Ban std::collections::HashMap — non-deterministic iteration order breaks replay.
|
||||
# Use BTreeMap (ordered by key) or IndexMap (insertion-ordered) instead.
|
||||
disallowed-types = [
|
||||
{ path = "std::collections::HashMap", reason = "HashMap iteration order is non-deterministic. Use BTreeMap or IndexMap for deterministic simulation." },
|
||||
{ path = "std::collections::HashSet", reason = "HashSet iteration order is non-deterministic. Use BTreeSet or IndexSet." },
|
||||
]
|
||||
@@ -4,6 +4,11 @@
|
||||
//! (natural vision, thermal, EM, etc.) implements PerceptionQuery to
|
||||
//! provide mode-specific FOV and visibility sector computation.
|
||||
//! v0.1 implements only NaturalVision.
|
||||
//!
|
||||
//! Note: HashMap is used for `sector_lookup` — a per-frame scratch buffer
|
||||
//! looked up only by key. Iteration order is irrelevant here. Not subject to
|
||||
//! the simulation determinism constraint (see server/.clippy.toml).
|
||||
#![allow(clippy::disallowed_types)]
|
||||
|
||||
use std::collections::{BTreeSet, HashMap};
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
use bevy_ecs::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
/// Chunk size in tiles (32x32 per chunk)
|
||||
pub const CHUNK_SIZE: i32 = 32;
|
||||
@@ -22,7 +22,10 @@ pub struct PlayerCharacter;
|
||||
///
|
||||
/// Examples: a Standing character can walk past a Seated NPC at a console,
|
||||
/// a Fixture (terminal) shares a tile with someone Seated at it.
|
||||
#[derive(Component, Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
|
||||
#[derive(
|
||||
Component, Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default, Serialize,
|
||||
Deserialize,
|
||||
)]
|
||||
pub enum TilePresence {
|
||||
/// Upright position — walking, standing, sprinting. Default for all entities.
|
||||
#[default]
|
||||
@@ -39,7 +42,7 @@ pub enum TilePresence {
|
||||
/// Tile position component for grid-based movement.
|
||||
/// Discrete integer coordinates used in simulation; converted to f32
|
||||
/// at the bridge boundary for VisibleEntity wire format.
|
||||
#[derive(Component, Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
#[derive(Component, Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub struct TilePosition {
|
||||
pub x: i32,
|
||||
pub y: i32,
|
||||
@@ -116,7 +119,7 @@ impl TilePosition {
|
||||
}
|
||||
|
||||
/// Chunk coordinate for chunk-based map storage (D-012).
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct ChunkCoord {
|
||||
pub cx: i32,
|
||||
pub cy: i32,
|
||||
@@ -161,14 +164,14 @@ impl ChunkData {
|
||||
/// Unloaded chunks are treated as unwalkable.
|
||||
#[derive(Resource, Debug, Clone)]
|
||||
pub struct WalkabilityMap {
|
||||
chunks: HashMap<ChunkCoord, ChunkData>,
|
||||
chunks: BTreeMap<ChunkCoord, ChunkData>,
|
||||
}
|
||||
|
||||
impl WalkabilityMap {
|
||||
/// Create a walkability map covering a rectangular area with all tiles walkable.
|
||||
/// Generates chunks to cover the specified dimensions on z-level 0..z_levels.
|
||||
pub fn new(width: i32, height: i32, z_levels: i32) -> Self {
|
||||
let mut chunks = HashMap::new();
|
||||
let mut chunks = BTreeMap::new();
|
||||
let cx_max = (width + CHUNK_SIZE - 1) / CHUNK_SIZE;
|
||||
let cy_max = (height + CHUNK_SIZE - 1) / CHUNK_SIZE;
|
||||
for z in 0..z_levels {
|
||||
@@ -183,7 +186,7 @@ impl WalkabilityMap {
|
||||
|
||||
/// Create a walkability map covering a rectangular area with all tiles blocked.
|
||||
pub fn new_blocked(width: i32, height: i32, z_levels: i32) -> Self {
|
||||
let mut chunks = HashMap::new();
|
||||
let mut chunks = BTreeMap::new();
|
||||
let cx_max = (width + CHUNK_SIZE - 1) / CHUNK_SIZE;
|
||||
let cy_max = (height + CHUNK_SIZE - 1) / CHUNK_SIZE;
|
||||
for z in 0..z_levels {
|
||||
@@ -279,7 +282,7 @@ pub fn validate_movement(
|
||||
|
||||
// Collect layer slots occupied by stationary entities (no MoveIntent).
|
||||
// Key: (position, layer) — two entities can share a tile if different layers.
|
||||
let mut occupied: HashMap<(TilePosition, TilePresence), Entity> = HashMap::new();
|
||||
let mut occupied: BTreeMap<(TilePosition, TilePresence), Entity> = BTreeMap::new();
|
||||
for (entity, pos, presence) in stationary.iter() {
|
||||
let layer = presence.copied().unwrap_or_default();
|
||||
occupied.insert((*pos, layer), entity);
|
||||
|
||||
Reference in New Issue
Block a user