fix(simulation): PR #66 review — dedup vision, spawn components, doc fixes
- vision.rs: deduplicate own-tile entity iteration, add same-tile test - spawn.rs: add NpcVisionState, NpcMemory, PlayerAwareness to content- spawned NPCs (matching generate_npc) - pressure.rs: update who_knows_full_scan doc to reflect actual call frequency, document O(N) acceptability at call site - types.rs: fix stale protocol version comment (13→14) - generate.rs: format!() → .to_string() (clippy) - vision.rs: hardcoded 10 → TICKS_PER_GAME_MINUTE - relationships.rs: fix comment "15%" → "20%" to match code - pressure.rs: document total() floor-truncation - save_state.rs: document NpcMemory exclusion as intentional Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -82,9 +82,12 @@ pub struct CharacterPressure {
|
||||
}
|
||||
|
||||
impl CharacterPressure {
|
||||
/// Total pressure as a weighted average of all axes (0–100).
|
||||
/// Total pressure as a simple average of all axes (0–100).
|
||||
///
|
||||
/// Uses integer division — remainders are floor-truncated (D-010, no floats).
|
||||
/// Maximum rounding error is 2 units (e.g. axis sum 101 → 33 instead of 33.67).
|
||||
pub fn total(&self) -> i32 {
|
||||
// Simple average, clamped. Integer arithmetic only (D-010).
|
||||
// Simple average, clamped. Integer division truncates toward zero (D-010).
|
||||
((self.exposure + self.institutional + self.relationship) / 3).clamp(0, 100)
|
||||
}
|
||||
|
||||
@@ -178,6 +181,8 @@ pub fn update_character_pressure(
|
||||
// --- Relationship pressure ---
|
||||
let player_stable = registry.to_stable(player_entity);
|
||||
if let Some(player_sid) = player_stable {
|
||||
// O(N) over all relationship edges — called once per game-minute, not every tick.
|
||||
// Acceptable at v0.1 NPC counts (<100 NPCs = <100 edge iterations).
|
||||
let hostile_edges = relationship_graph
|
||||
.who_knows_full_scan(&player_sid)
|
||||
.iter()
|
||||
|
||||
@@ -31,6 +31,8 @@
|
||||
//! - Full ECS world extraction/injection (system not yet written)
|
||||
//! - Pathfinding state (reconstructed from position + routine)
|
||||
//! - Tier transitions in-flight (dropped to background state on load)
|
||||
//! - `NpcMemory` (intentionally excluded — stale inferences would be wrong after
|
||||
//! reload; memory degrades naturally over time so reset-on-load is acceptable)
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
||||
@@ -21,6 +21,15 @@ pub trait SpatialIndex: Send + Sync {
|
||||
/// Return all entities at the exact `position`.
|
||||
fn entities_at(&self, position: &TilePosition) -> Vec<Entity>;
|
||||
|
||||
/// Return all entities within Manhattan distance `radius` of `position`,
|
||||
/// **including** entities exactly at `position`. Single-pass alternative to
|
||||
/// `entities_in_range` + `entities_at`.
|
||||
fn entities_within(&self, position: &TilePosition, radius: u32) -> Vec<Entity> {
|
||||
let mut result = self.entities_in_range(position, radius);
|
||||
result.extend(self.entities_at(position));
|
||||
result
|
||||
}
|
||||
|
||||
/// Insert or update an entity's position in the index.
|
||||
fn update(&mut self, entity: Entity, position: TilePosition);
|
||||
|
||||
@@ -78,6 +87,19 @@ impl SpatialIndex for NaiveSpatialIndex {
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn entities_within(&self, position: &TilePosition, radius: u32) -> Vec<Entity> {
|
||||
self.entries
|
||||
.iter()
|
||||
.filter(|(_, pos)| {
|
||||
pos == position
|
||||
|| pos
|
||||
.manhattan_distance(position)
|
||||
.is_some_and(|d| d <= radius)
|
||||
})
|
||||
.map(|(entity, _)| *entity)
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn update(&mut self, entity: Entity, position: TilePosition) {
|
||||
if let Some(entry) = self.entries.iter_mut().find(|(e, _)| *e == entity) {
|
||||
entry.1 = position;
|
||||
|
||||
Reference in New Issue
Block a user