## D-030 Layer 2: LocalBridge framing and transport tests ## Tests 4-byte BE length-prefix framing and diagonal movement wire mapping. class_name TestLocalBridge extends GdUnitTestSuite # -- Framing encode/decode roundtrip ------------------------------------------- func test_frame_encode_decode_roundtrip() -> void: var payload := PackedByteArray([0xDE, 0xAD, 0xBE, 0xEF, 0x42]) var framed := LocalBridge.frame_encode(payload) # Should be 4-byte header + 5-byte payload = 9 bytes assert_that(framed.size()).is_equal(9) # Header should be big-endian length 5 assert_that(framed[0]).is_equal(0) assert_that(framed[1]).is_equal(0) assert_that(framed[2]).is_equal(0) assert_that(framed[3]).is_equal(5) var decoded: Variant = LocalBridge.frame_decode(framed) assert_that(decoded).is_not_null() assert_that(decoded.payload).is_equal(payload) assert_that(decoded.remainder.size()).is_equal(0) func test_frame_encode_empty_payload() -> void: var payload := PackedByteArray() var framed := LocalBridge.frame_encode(payload) # 4-byte header + 0-byte payload assert_that(framed.size()).is_equal(4) assert_that(framed[0]).is_equal(0) assert_that(framed[1]).is_equal(0) assert_that(framed[2]).is_equal(0) assert_that(framed[3]).is_equal(0) var decoded: Variant = LocalBridge.frame_decode(framed) assert_that(decoded).is_not_null() assert_that(decoded.payload.size()).is_equal(0) func test_frame_decode_with_remainder() -> void: var payload1 := PackedByteArray([0x01, 0x02, 0x03]) var payload2 := PackedByteArray([0x04, 0x05]) var framed := LocalBridge.frame_encode(payload1) framed.append_array(LocalBridge.frame_encode(payload2)) # Decode first message var decoded: Variant = LocalBridge.frame_decode(framed) assert_that(decoded).is_not_null() assert_that(decoded.payload).is_equal(payload1) assert_that(decoded.remainder.size()).is_greater(0) # Decode second message from remainder var decoded2: Variant = LocalBridge.frame_decode(decoded.remainder) assert_that(decoded2).is_not_null() assert_that(decoded2.payload).is_equal(payload2) assert_that(decoded2.remainder.size()).is_equal(0) func test_frame_decode_incomplete_header() -> void: var data := PackedByteArray([0x00, 0x00]) # Only 2 bytes, need 4 var decoded: Variant = LocalBridge.frame_decode(data) assert_that(decoded).is_null() func test_frame_decode_incomplete_payload() -> void: # Header says 10 bytes but only 3 bytes of payload var data := PackedByteArray([0x00, 0x00, 0x00, 0x0A, 0x01, 0x02, 0x03]) var decoded: Variant = LocalBridge.frame_decode(data) assert_that(decoded).is_null() func test_frame_encode_large_payload_length() -> void: # Verify big-endian encoding of a length > 255 var payload := PackedByteArray() payload.resize(300) payload.fill(0xAB) var framed := LocalBridge.frame_encode(payload) # Length 300 = 0x0000012C in big-endian assert_that(framed[0]).is_equal(0x00) assert_that(framed[1]).is_equal(0x00) assert_that(framed[2]).is_equal(0x01) assert_that(framed[3]).is_equal(0x2C) var decoded: Variant = LocalBridge.frame_decode(framed) assert_that(decoded).is_not_null() assert_that(decoded.payload.size()).is_equal(300) # -- Framing with Protocol messages (cross-layer) ----------------------------- func test_framed_protocol_snapshot_roundtrip() -> void: # Encode a snapshot with Protocol, frame it, decode the frame, decode the snapshot var snapshot_data := {"tick": 42, "version": Protocol.PROTOCOL_VERSION, "entities": []} var encoded: Variant = Messagepack.encode(snapshot_data) assert_that(encoded.status).is_null() var framed := LocalBridge.frame_encode(encoded.value) var decoded_frame: Variant = LocalBridge.frame_decode(framed) assert_that(decoded_frame).is_not_null() var snapshot: Variant = Protocol.decode_snapshot(decoded_frame.payload) assert_that(snapshot).is_not_null() assert_that(snapshot.tick).is_equal(42) assert_that(snapshot.entities.size()).is_equal(0) func test_framed_protocol_input_roundtrip() -> void: var encoded := Protocol.encode_player_input(99, "MoveNorth") assert_that(encoded.size()).is_greater(0) var framed := LocalBridge.frame_encode(encoded) var decoded_frame: Variant = LocalBridge.frame_decode(framed) assert_that(decoded_frame).is_not_null() var input: Variant = Protocol.decode_player_input(decoded_frame.payload) assert_that(input).is_not_null() assert_that(input.tick).is_equal(99) assert_that(input.action.variant).is_equal("MoveNorth") func test_framed_protocol_batch_input_roundtrip() -> void: # Encode a batch of inputs (Vec), frame it, decode frame, verify wire format var inputs: Array = [ {"tick": 0, "action_name": "MoveNorth"}, {"tick": 0, "action_name": "Interact"}, ] var encoded := Protocol.encode_player_inputs(inputs) assert_that(encoded.size()).is_greater(0) var framed := LocalBridge.frame_encode(encoded) var decoded_frame: Variant = LocalBridge.frame_decode(framed) assert_that(decoded_frame).is_not_null() # Verify the payload is a valid msgpack array matching server expectations var raw: Variant = Messagepack.decode(decoded_frame.payload) assert_that(raw.status).is_null() assert_that(raw.value is Array).is_true() assert_that(raw.value.size()).is_equal(2) assert_that(raw.value[0]["action"]).is_equal("MoveNorth") # Interact is a struct variant: {"Interact": {"target_entity_id": null, "verb": null}} assert_that(raw.value[1]["action"] is Dictionary).is_true() assert_that(raw.value[1]["action"].has("Interact")).is_true() # -- Diagonal movement wire mapping -------------------------------------------- func test_action_enum_to_wire_all_directions_clockwise() -> void: # All 8 directions in clockwise order (N, NE, E, SE, S, SW, W, NW) var expected: Array = [ [InputMapper.Action.MOVE_NORTH, "MoveNorth"], [InputMapper.Action.MOVE_NORTHEAST, "MoveNortheast"], [InputMapper.Action.MOVE_EAST, "MoveEast"], [InputMapper.Action.MOVE_SOUTHEAST, "MoveSoutheast"], [InputMapper.Action.MOVE_SOUTH, "MoveSouth"], [InputMapper.Action.MOVE_SOUTHWEST, "MoveSouthwest"], [InputMapper.Action.MOVE_WEST, "MoveWest"], [InputMapper.Action.MOVE_NORTHWEST, "MoveNorthwest"], ] for pair in expected: var wire_name := SimBridge.action_enum_to_wire(pair[0]) assert_that(wire_name).is_equal(pair[1]) func test_action_enum_to_wire_non_movement() -> void: assert_that(SimBridge.action_enum_to_wire(InputMapper.Action.INTERACT)).is_equal("Interact") assert_that(SimBridge.action_enum_to_wire(InputMapper.Action.USE_PERCEPTION_MODE)).is_equal("UsePerceptionMode") assert_that(SimBridge.action_enum_to_wire(InputMapper.Action.PAUSE)).is_equal("Pause") func test_action_enum_to_wire_open_menu_returns_empty() -> void: var wire_name := SimBridge.action_enum_to_wire(InputMapper.Action.OPEN_MENU) assert_that(wire_name).is_equal("") func test_action_enum_to_wire_unknown_returns_empty() -> void: var wire_name := SimBridge.action_enum_to_wire(9999) assert_that(wire_name).is_equal("") # -- Partial read and state machine tests -------------------------------------- func test_frame_decode_partial_then_complete() -> void: # Simulate chunked TCP delivery: header arrives first, payload arrives later var payload := PackedByteArray([0xCA, 0xFE, 0xBA, 0xBE]) var framed := LocalBridge.frame_encode(payload) # Split at byte 6 (header + 2 bytes of payload) var chunk1 := framed.slice(0, 6) var chunk2 := framed.slice(6) # First chunk: incomplete message var decoded: Variant = LocalBridge.frame_decode(chunk1) assert_that(decoded).is_null() # Reassemble and decode var full := PackedByteArray() full.append_array(chunk1) full.append_array(chunk2) decoded = LocalBridge.frame_decode(full) assert_that(decoded).is_not_null() assert_that(decoded.payload).is_equal(payload) func test_frame_decode_multiple_messages_sequential() -> void: # Three messages concatenated — decode all sequentially via remainder var p1 := PackedByteArray([0x01]) var p2 := PackedByteArray([0x02, 0x03]) var p3 := PackedByteArray([0x04, 0x05, 0x06]) var buffer := PackedByteArray() buffer.append_array(LocalBridge.frame_encode(p1)) buffer.append_array(LocalBridge.frame_encode(p2)) buffer.append_array(LocalBridge.frame_encode(p3)) # Decode message 1 var d1: Variant = LocalBridge.frame_decode(buffer) assert_that(d1).is_not_null() assert_that(d1.payload).is_equal(p1) # Decode message 2 from remainder var d2: Variant = LocalBridge.frame_decode(d1.remainder) assert_that(d2).is_not_null() assert_that(d2.payload).is_equal(p2) # Decode message 3 from remainder var d3: Variant = LocalBridge.frame_decode(d2.remainder) assert_that(d3).is_not_null() assert_that(d3.payload).is_equal(p3) assert_that(d3.remainder.size()).is_equal(0) func test_send_input_returns_error_on_invalid_action() -> void: # send_input returns ERR_INVALID_PARAMETER for unknown actions # SimBridge is in test_mode=true and CONNECTED, so we need to temporarily # disable test_mode to exercise the encode path var original_test_mode: bool = SimBridge.test_mode var original_state: SimBridge.ConnectionState = SimBridge.state SimBridge.test_mode = false SimBridge.state = SimBridge.ConnectionState.CONNECTED var err := SimBridge.send_input({"action": 9999, "timestamp_msec": 0}) assert_that(err).is_equal(ERR_INVALID_PARAMETER) # Restore SimBridge.test_mode = original_test_mode SimBridge.state = original_state