Files
settled-reach/client/tests/test_local_bridge.gd
T
jpmschweitzerandClaude Sonnet 4.6 d72fcc7847 fix(protocol): drop PROTOCOL_VERSION lockstep — D-192 (#875)
Removes the version-mismatch guard from Protocol.decode_snapshot() and the
PROTOCOL_VERSION constant from the client (server side done in #874).

Core changes:
- protocol.gd: remove const PROTOCOL_VERSION, remove version mismatch guard,
  remove "version" from return dict, add gauntlet_mode/room_id decode
- sim_bridge.gd: remove handshake version check; relax handshake guard to
  require only a valid Dictionary (server no longer sends protocol_version);
  emit handshake_complete(0) for API compat
- loading_screen.gd: drop "· protocol N" suffix from version label
- test_harness.gd: replace Protocol.PROTOCOL_VERSION with literal 23

Test updates (21 files): replace "version": Protocol.PROTOCOL_VERSION with
"version": 23 in all snapshot bytes dicts; remove snapshot.version == N
assertions; remove version-rejection tests (test_rejects_version_6,
test_decode_snapshot_rejects_missing_version, test_decode_snapshot_rejects_old_version,
test_protocol_rejects_version_mismatch, test_sim_bridge_test_snapshot_uses_current_protocol_version).

Also includes: #872 bookmark_catalog carry-forward regression test, and
#873 merge-path flow tests (test_merge_path_flows_sprint37.gd).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 17:29:49 +02:00

253 lines
9.2 KiB
GDScript

## 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": 23, "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<PlayerInput>), 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