feat(simulation): Sprint 19 — 7 server systems

Protocol handshake (#555): HandshakeMessage as first IPC frame,
HandshakeState resource, forward-compatible input handling.

State serialization (#96): serialize_npc_to_frozen/deserialize with
full D-024 axis coverage (10 new optional fields on NpcSaveState).

Scope tags (#98): ScopeTagKind enum, ScopePinned marker, automatic
assignment from KnowledgeGraph and RelationshipGraph.

Timestamp eviction (#97): LastInteractionTick, SimSpacePressure,
BinaryHeap LRU eviction respecting ScopePinned entities.

Save/load (#553): save_to_file/load_from_file via MessagePack,
SaveGame/LoadGame IPC commands, SaveLoadResultWire on snapshot.

Test infrastructure (#200): Layer 3 integration test entry point,
three-layer architecture documented per D-030.

Information boundary tests (#272): 4 negative tests proving no
passive KG leakage, LOS fog holds, tier boundary holds, save
isolation per NPC.

1063 tests passing, 0 failures.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-25 12:13:03 +01:00
co-authored by Claude Opus 4.6
parent 0dd33690f7
commit 6ed8d11502
22 changed files with 2561 additions and 35 deletions
+35
View File
@@ -35,6 +35,11 @@ pub enum BridgeError {
/// Abstracts transport layer (D-020)
/// Implemented by LocalBridge (stdio) and future NetworkBridge
pub trait SimBridge: Send + Sync {
/// Send the protocol handshake as the first framed message (#555).
/// Must be called exactly once, immediately after connection, before
/// any ObserverSnapshot is sent.
fn send_handshake(&self) -> Result<(), BridgeError>;
/// Send an observer snapshot to the client
fn send_snapshot(&self, snapshot: &ObserverSnapshot) -> Result<(), BridgeError>;
@@ -55,6 +60,10 @@ impl BridgeResource {
}
}
pub fn send_handshake(&self) -> Result<(), BridgeError> {
self.inner.send_handshake()
}
pub fn send_snapshot(&self, snapshot: &ObserverSnapshot) -> Result<(), BridgeError> {
self.inner.send_snapshot(snapshot)
}
@@ -64,15 +73,40 @@ impl BridgeResource {
}
}
/// Tracks whether the protocol handshake has been sent (#555).
/// Inserted by BridgePlugin as Pending. Set to Complete in main.rs after
/// `send_handshake()` succeeds. `receive_bridge_inputs` logs a warning
/// if inputs arrive while still Pending.
#[derive(Resource, Debug, Clone, Copy, PartialEq, Eq)]
pub enum HandshakeState {
/// Handshake not yet sent. Inputs arriving in this state trigger a warning.
Pending,
/// Handshake sent. Normal operation.
Complete,
}
impl Default for HandshakeState {
fn default() -> Self {
Self::Pending
}
}
/// Receive inputs from bridge and push to InputQueue
pub fn receive_bridge_inputs(
bridge: Option<Res<BridgeResource>>,
mut input_queue: ResMut<crate::simulation::input::InputQueue>,
mut running: ResMut<ServerRunning>,
handshake: Res<HandshakeState>,
) {
let Some(bridge) = bridge else { return };
match bridge.receive_inputs() {
Ok(inputs) => {
if !inputs.is_empty() && *handshake == HandshakeState::Pending {
tracing::warn!(
"Received {} input(s) before handshake completed — processing anyway (forward-compatible)",
inputs.len()
);
}
for input in &inputs {
tracing::trace!(
"Received input: tick={} action={:?}",
@@ -156,6 +190,7 @@ impl Plugin for BridgePlugin {
fn build(&self, app: &mut App) {
app.init_resource::<SnapshotBuffer>()
.init_resource::<ServerRunning>()
.init_resource::<HandshakeState>()
.init_resource::<crate::perception::query::VisibilityGeometry>()
.init_resource::<crate::perception::query::ActivePerceptionMode>()
.add_systems(