From f6ce7d4046ab7d5faf88454b90b6e6629fa79548 Mon Sep 17 00:00:00 2001 From: Jeroen Schweitzer Date: Wed, 11 Feb 2026 21:01:15 +0100 Subject: [PATCH] fix(bridge): use named MessagePack format for wire compatibility Client-side Protocol.gd expects rmp_serde::to_vec_named() (maps with string keys), but LocalBridge was using to_vec() (compact positional arrays). Fix send_snapshot and update all test serialization calls to match actual wire format. Also change EOF from Ok(vec![]) to BridgeError::Transport so bridge systems can detect disconnects. Co-Authored-By: Claude Opus 4.6 --- server/src/bridge/local.rs | 7 ++----- server/tests/bridge_ipc.rs | 2 +- server/tests/serialization.rs | 21 +++++++++++++-------- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/server/src/bridge/local.rs b/server/src/bridge/local.rs index 6669ea773..62c36f873 100644 --- a/server/src/bridge/local.rs +++ b/server/src/bridge/local.rs @@ -81,7 +81,7 @@ impl LocalBridge { impl SimBridge for LocalBridge { fn send_snapshot(&self, snapshot: &ObserverSnapshot) -> Result<(), BridgeError> { - let payload = rmp_serde::to_vec(snapshot)?; + let payload = rmp_serde::to_vec_named(snapshot)?; let mut writer = self.writer.lock().expect("writer mutex poisoned"); write_framed(writer.get_mut(), &payload)?; @@ -99,10 +99,7 @@ impl SimBridge for LocalBridge { tracing::trace!("received {} inputs", inputs.len()); Ok(inputs) } - None => { - tracing::trace!("received EOF, returning empty input vec"); - Ok(Vec::new()) - } + None => Err(BridgeError::Transport("client disconnected (EOF)".into())), } } } diff --git a/server/tests/bridge_ipc.rs b/server/tests/bridge_ipc.rs index 448144f57..3d110b8bd 100644 --- a/server/tests/bridge_ipc.rs +++ b/server/tests/bridge_ipc.rs @@ -106,7 +106,7 @@ fn input_roundtrip_over_unix_socket() { }, ]; - let payload = rmp_serde::to_vec(&inputs).expect("failed to serialize"); + let payload = rmp_serde::to_vec_named(&inputs).expect("failed to serialize"); write_framed(&mut writer, &payload).expect("failed to write frame"); // Drop writer to close connection and signal EOF to server diff --git a/server/tests/serialization.rs b/server/tests/serialization.rs index abe61f56a..af60a0d75 100644 --- a/server/tests/serialization.rs +++ b/server/tests/serialization.rs @@ -15,7 +15,7 @@ fn observer_snapshot_roundtrip() { }], }; - let bytes = rmp_serde::to_vec(&snapshot).expect("serialize"); + let bytes = rmp_serde::to_vec_named(&snapshot).expect("serialize"); let decoded: ObserverSnapshot = rmp_serde::from_slice(&bytes).expect("deserialize"); assert_eq!(decoded.tick, 42); @@ -30,7 +30,7 @@ fn player_input_roundtrip() { action: PlayerAction::MoveNorth, }; - let bytes = rmp_serde::to_vec(&input).expect("serialize"); + let bytes = rmp_serde::to_vec_named(&input).expect("serialize"); let decoded: PlayerInput = rmp_serde::from_slice(&bytes).expect("deserialize"); assert_eq!(decoded.tick, 100); @@ -43,7 +43,7 @@ fn empty_snapshot_roundtrip() { entities: vec![], }; - let bytes = rmp_serde::to_vec(&snapshot).expect("serialize"); + let bytes = rmp_serde::to_vec_named(&snapshot).expect("serialize"); let decoded: ObserverSnapshot = rmp_serde::from_slice(&bytes).expect("deserialize"); assert_eq!(decoded.tick, 0); @@ -73,11 +73,11 @@ fn all_player_action_variants_roundtrip() { tick: 1, action: action.clone(), }; - let bytes = rmp_serde::to_vec(&input).expect("serialize"); + let bytes = rmp_serde::to_vec_named(&input).expect("serialize"); let decoded: PlayerInput = rmp_serde::from_slice(&bytes).expect("deserialize"); assert_eq!(decoded.tick, 1); // Verify the variant survived by re-serializing and comparing bytes - let re_bytes = rmp_serde::to_vec(&decoded).expect("re-serialize"); + let re_bytes = rmp_serde::to_vec_named(&decoded).expect("re-serialize"); assert_eq!(bytes, re_bytes, "round-trip mismatch for action variant"); } } @@ -85,7 +85,12 @@ fn all_player_action_variants_roundtrip() { /// All EntityKind variants must survive MessagePack round-trip (D-030 Layer 1) #[test] fn all_entity_kind_variants_roundtrip() { - let kinds = vec![EntityKind::Npc, EntityKind::Object, EntityKind::Terrain]; + let kinds = vec![ + EntityKind::Player, + EntityKind::Npc, + EntityKind::Object, + EntityKind::Terrain, + ]; for (i, kind) in kinds.into_iter().enumerate() { let entity = VisibleEntity { @@ -99,9 +104,9 @@ fn all_entity_kind_variants_roundtrip() { tick: 0, entities: vec![entity], }; - let bytes = rmp_serde::to_vec(&snapshot).expect("serialize"); + let bytes = rmp_serde::to_vec_named(&snapshot).expect("serialize"); let decoded: ObserverSnapshot = rmp_serde::from_slice(&bytes).expect("deserialize"); - let re_bytes = rmp_serde::to_vec(&decoded).expect("re-serialize"); + let re_bytes = rmp_serde::to_vec_named(&decoded).expect("re-serialize"); assert_eq!( bytes, re_bytes, "round-trip mismatch for EntityKind variant"