feat(simulation): demux inbound bridge stream — receive() + atlas routing (#969, D-225)

Replaces the fixed-type SimBridge::receive_inputs() with a tagged
receive() -> Option<Inbound>, where Inbound is Inputs(Vec<PlayerInput>) or
AtlasRequest(AtlasLayerRequest). A shared decode_inbound() demuxes a frame by
shape (msgpack array = inputs, map = atlas request) — additive, no wire change
to existing input/snapshot frames. Adds send_atlas_response() to the trait
(both TcpBridge + LocalBridge impls). receive_bridge_inputs routes inputs to
the InputQueue as before; atlas requests to a new AtlasRequestBuffer (drained
by the serve system next). Integration tests (bridge_tcp/bridge_ipc) updated to
the tagged receive(); a demux unit test covers all three branches.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-24 18:00:30 +02:00
co-authored by Claude Opus 4.7
parent edbc0f4ff1
commit dfdc578429
5 changed files with 138 additions and 62 deletions
+14 -23
View File
@@ -2,7 +2,8 @@
// Implements D-020 subprocess/IPC architecture
// Deterministic client-server communication via Unix domain sockets
use super::{BridgeError, ObserverSnapshot, PlayerInput, SimBridge};
use super::{decode_inbound, BridgeError, Inbound, ObserverSnapshot, SimBridge};
use crate::atlas::layer_proxy::AtlasLayerResponse;
use crate::bridge::framing::{read_framed, write_framed};
use std::fs;
use std::io::{BufReader, BufWriter};
@@ -123,37 +124,27 @@ impl SimBridge for LocalBridge {
Ok(())
}
fn receive_inputs(&self) -> Result<Vec<PlayerInput>, BridgeError> {
fn receive(&self) -> Result<Option<Inbound>, BridgeError> {
let mut reader = self
.reader
.lock()
.map_err(|e| BridgeError::MutexPoisoned(format!("reader: {}", e)))?;
match read_framed(reader.get_mut())? {
Some(payload) => match rmp_serde::from_slice::<Vec<PlayerInput>>(&payload) {
Ok(inputs) => {
tracing::trace!("received {} inputs", inputs.len());
Ok(inputs)
}
Err(e) => {
let dump_len = payload.len().min(256);
tracing::error!(
"deserialization failed: {}. Raw bytes ({} of {} total): {:02x?}",
e,
dump_len,
payload.len(),
&payload[..dump_len]
);
Err(BridgeError::DeserializationWithDump(format!(
"{} (payload {} bytes)",
e,
payload.len()
)))
}
},
Some(payload) => decode_inbound(&payload).map(Some),
None => Err(BridgeError::Disconnected),
}
}
fn send_atlas_response(&self, resp: &AtlasLayerResponse) -> Result<(), BridgeError> {
let payload = rmp_serde::to_vec_named(resp)?;
let mut writer = self
.writer
.lock()
.map_err(|e| BridgeError::MutexPoisoned(format!("writer: {}", e)))?;
write_framed(writer.get_mut(), &payload)?;
Ok(())
}
}
impl Drop for LocalBridge {