fix(simulation): address PR #75 review — vision components, triangle validation, FNV-1a, KG gating
1. CRITICAL: spawn_template_npcs now inserts NpcVisionState, NpcMemory, PlayerAwareness on template-spawned NPCs — matches spawn_npc() pattern from PR #66. Without these, template NPCs were invisible to vision and awareness systems. 2. WARNING: generate_intra_template_triangles now calls validate_triangle_def before generating TriangleState — mirrors cross-template path. Updates test TriangleDefs and logistics-hub.yaml to pass all three quality checks (conflict viability, relationship coherence, interest divergence). 3. WARNING: state_hash in compute_observer_snapshot now uses FNV-1a instead of DefaultHasher — consistent with D-010 principle 4 and the pattern in TemplateId/TriangleId. Updates golden file for new hash value. 4. WARNING: TriangleCrisisEventWire.role_assignments now filtered against observer KnowledgeGraph — unknown NPCs redacted from wire event per D-010 principle 2 (information boundaries). 5. WARNING: ActiveTemplateInstances::insert now despawns previous instance entities before overwriting — prevents orphaned ECS entities. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -9,7 +9,6 @@
|
||||
use bevy_ecs::prelude::*;
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
use crate::bridge::types::*;
|
||||
use crate::knowledge::graph::filter_by_access;
|
||||
@@ -394,24 +393,40 @@ pub fn compute_observer_snapshot(
|
||||
let save_result = buffer.pending_save_result.take();
|
||||
|
||||
// Drain triangle crisis events (#250) and convert to wire format.
|
||||
let triangle_crisis_events = crisis_queue
|
||||
// Drain triangle crisis events (#250) and filter role_assignments against
|
||||
// observer KG (D-010 principle 2: information boundaries are universal).
|
||||
// NPCs unknown to the observer are redacted from the wire event.
|
||||
let triangle_crisis_events: Vec<TriangleCrisisEventWire> = crisis_queue
|
||||
.drain()
|
||||
.into_iter()
|
||||
.map(TriangleCrisisEventWire::from)
|
||||
.map(|e| {
|
||||
let mut wire = TriangleCrisisEventWire::from(e);
|
||||
wire.role_assignments.retain(|(_, npc_id)| {
|
||||
observer_kg.knows_entity(&crate::knowledge::types::StableId(*npc_id))
|
||||
});
|
||||
wire
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Compute state hash for desync detection (#85).
|
||||
// Hash inputs: player position (x, y, z), NPC count, tick number.
|
||||
// Uses DefaultHasher for speed — not cryptographic, just comparison.
|
||||
// Uses FNV-1a (64-bit) for determinism across Rust versions — DefaultHasher
|
||||
// is explicitly prohibited by D-010 principle 4 (see template.rs module docs).
|
||||
let state_hash = {
|
||||
let mut hasher = std::collections::hash_map::DefaultHasher::new();
|
||||
time.tick.hash(&mut hasher);
|
||||
observer_pos.x.hash(&mut hasher);
|
||||
observer_pos.y.hash(&mut hasher);
|
||||
observer_pos.z.hash(&mut hasher);
|
||||
let mut hash: u64 = 0xcbf29ce484222325; // FNV-1a offset basis
|
||||
let fnv_fold = |h: &mut u64, bytes: &[u8]| {
|
||||
for &b in bytes {
|
||||
*h ^= b as u64;
|
||||
*h = h.wrapping_mul(0x100000001b3); // FNV-1a prime
|
||||
}
|
||||
};
|
||||
fnv_fold(&mut hash, &time.tick.to_le_bytes());
|
||||
fnv_fold(&mut hash, &observer_pos.x.to_le_bytes());
|
||||
fnv_fold(&mut hash, &observer_pos.y.to_le_bytes());
|
||||
fnv_fold(&mut hash, &observer_pos.z.to_le_bytes());
|
||||
let npc_count = npc_count_query.iter().count() as u64;
|
||||
npc_count.hash(&mut hasher);
|
||||
Some(hasher.finish())
|
||||
fnv_fold(&mut hash, &npc_count.to_le_bytes());
|
||||
Some(hash)
|
||||
};
|
||||
|
||||
// Drain sim errors collected this tick (#85)
|
||||
|
||||
Reference in New Issue
Block a user