feat(simulation): error handling and recovery — panic supervision, state hash, structured errors (#85)
Protocol v17: add state_hash (desync detection) and sim_errors (structured error reporting) to ObserverSnapshot. Add SimError, SimErrorKind, SimErrorBuffer types. Wrap main loop app.update() in catch_unwind — on panic, send a final SimError snapshot before exit. Report recoverable deserialization errors to client via SimErrorBuffer. Compute per-tick state hash from player position + NPC count + tick. Update all test fixtures and golden files for protocol v17. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,8 @@
|
||||
use bevy_ecs::prelude::*;
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
use crate::bridge::types::*;
|
||||
use crate::knowledge::graph::filter_by_access;
|
||||
use crate::knowledge::types::{AccessRule, KnowledgeState};
|
||||
@@ -103,6 +105,8 @@ pub fn compute_observer_snapshot(
|
||||
mut crisis_queue: ResMut<TriangleCrisisEventQueue>,
|
||||
sim_rng: Option<Res<SimRng>>,
|
||||
pressure_query: Query<&crate::simulation::pressure::CharacterPressure, With<PlayerCharacter>>,
|
||||
error_buffer: Option<ResMut<SimErrorBuffer>>,
|
||||
npc_count_query: Query<Entity, With<crate::npc::Npc>>,
|
||||
) {
|
||||
let Ok((
|
||||
observer_entity,
|
||||
@@ -396,6 +400,25 @@ pub fn compute_observer_snapshot(
|
||||
.map(TriangleCrisisEventWire::from)
|
||||
.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.
|
||||
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 npc_count = npc_count_query.iter().count() as u64;
|
||||
npc_count.hash(&mut hasher);
|
||||
Some(hasher.finish())
|
||||
};
|
||||
|
||||
// Drain sim errors collected this tick (#85)
|
||||
let sim_errors = error_buffer
|
||||
.map(|mut buf| buf.drain())
|
||||
.unwrap_or_default();
|
||||
|
||||
buffer.snapshot = Some(ObserverSnapshot {
|
||||
version: crate::bridge::types::PROTOCOL_VERSION,
|
||||
tick: time.tick,
|
||||
@@ -424,6 +447,8 @@ pub fn compute_observer_snapshot(
|
||||
player_knowledge,
|
||||
save_result,
|
||||
triangle_crisis_events,
|
||||
state_hash,
|
||||
sim_errors,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user