feat(client): atlas request/response transport on the bridge (#960, D-225)

Client transport half of the layer-stream protocol.
- SimBridge.request_atlas_layers(body_id) sends an AtlasLayerRequest frame
  (live mode only; no-op in test mode); responses arrive via a new
  atlas_layers_received signal.
- receive_bytes now decodes each frame ONCE via Protocol.decode_inbound and
  branches by shape (snapshot vs atlas response) — avoids double-decoding the
  20 Hz snapshot path. decode_snapshot is split into decode_raw +
  _decode_snapshot_from_raw (public decode_snapshot unchanged, so the 70 protocol
  tests stay the regression guard); decode_inbound returns {kind, value}.

70/70 protocol tests pass, including the new decode_inbound classifier test.
(Pre-existing client-suite failures in server-dependent e2e/roundtrip + unrelated
audio/fog/dialogue suites are unchanged — verified identical at baseline.)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-24 18:36:39 +02:00
co-authored by Claude Opus 4.7
parent a9ba8ba90c
commit 03d0264eb0
3 changed files with 77 additions and 10 deletions
+11
View File
@@ -511,3 +511,14 @@ func test_encode_atlas_request_shape() -> void:
assert_that(raw.value["up_to"]).is_equal("Topography")
# A request is a map with no "status" — must not be mistaken for a response.
assert_that(Protocol.decode_atlas_layer_response(bytes)).is_null()
func test_decode_inbound_classifies_frames() -> void:
# The receive-side classifier: snapshot vs atlas response, decoded once.
var snap = Protocol.decode_inbound(_load_fixture("snapshot_empty"))
assert_that(snap.kind).is_equal("snapshot")
assert_that(snap.value).is_not_null()
var atlas = Protocol.decode_inbound(_load_fixture("atlas_response_ready"))
assert_that(atlas.kind).is_equal("atlas")
assert_that(atlas.value.status).is_equal("Ready")