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:
2026-02-27 17:46:27 +01:00
co-authored by Claude Opus 4.6
parent c2cae1f618
commit d31ac1cc70
23 changed files with 557 additions and 7 deletions
+7 -1
View File
@@ -37,6 +37,8 @@ fn test_snapshot(tick: u64, entities: Vec<VisibleEntity>) -> ObserverSnapshot {
player_knowledge: None,
save_result: None,
triangle_crisis_events: vec![],
state_hash: None,
sim_errors: vec![],
}
}
@@ -296,6 +298,8 @@ fn snapshot_v2_fields_roundtrip() {
player_knowledge: None,
save_result: None,
triangle_crisis_events: vec![],
state_hash: None,
sim_errors: vec![],
};
let bytes = rmp_serde::to_vec_named(&snapshot).expect("serialize");
@@ -350,7 +354,7 @@ fn protocol_version_constant_matches_snapshot() {
let snapshot = test_snapshot(0, vec![]);
assert_eq!(snapshot.version, PROTOCOL_VERSION);
assert_eq!(
PROTOCOL_VERSION, 16,
PROTOCOL_VERSION, 17,
"bump this assertion when protocol version changes"
);
}
@@ -401,6 +405,8 @@ fn all_facing_direction_variants_roundtrip() {
player_knowledge: None,
save_result: None,
triangle_crisis_events: vec![],
state_hash: None,
sim_errors: vec![],
};
let bytes = rmp_serde::to_vec_named(&snapshot).expect("serialize");
let decoded: ObserverSnapshot = rmp_serde::from_slice(&bytes).expect("deserialize");