feat(client): add LocalBridge framing tests and diagonal fixtures

12 new tests covering framing roundtrips, cross-layer Protocol+framing
integration, and diagonal wire mapping. 4 new diagonal movement
fixtures generated from Rust for D-030 Layer 1 cross-language
verification. 33/33 tests pass.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-11 20:59:46 +01:00
co-authored by Claude Opus 4.6
parent 2ecafff2ed
commit 0f75c3194d
8 changed files with 195 additions and 0 deletions
@@ -0,0 +1 @@
うtickdヲactionュMoveNortheast
@@ -0,0 +1 @@
うtickdヲactionュMoveNorthwest
@@ -0,0 +1 @@
うtickdヲactionュMoveSoutheast
@@ -0,0 +1 @@
うtickdヲactionュMoveSouthwest
+159
View File
@@ -0,0 +1,159 @@
## 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, "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")
# -- 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("")
+1
View File
@@ -0,0 +1 @@
uid://xpl0rsixyh3y
+20
View File
@@ -167,3 +167,23 @@ func test_decode_player_input_empty_bytes() -> void:
func test_encode_produces_nonempty_bytes() -> void:
var bytes = Protocol.encode_player_input(1, "MoveNorth")
assert_that(bytes.size()).is_greater(0)
# -- Diagonal movement fixtures (D-030 Layer 1 cross-language) -----------------
func test_decode_diagonal_fixtures() -> void:
# All 4 diagonal fixtures: clockwise NE, SE, SW, NW
var diagonals: Array = [
["input_move_northeast", "MoveNortheast"],
["input_move_southeast", "MoveSoutheast"],
["input_move_southwest", "MoveSouthwest"],
["input_move_northwest", "MoveNorthwest"],
]
for pair in diagonals:
var bytes = _load_fixture(pair[0])
var input: Variant = Protocol.decode_player_input(bytes)
assert_that(input).is_not_null()
assert_that(input.tick).is_equal(100)
assert_that(input.action.variant).is_equal(pair[1])
assert_that(input.action.data).is_null()
+11
View File
@@ -61,4 +61,15 @@ fn generate_msgpack_fixtures() {
],
};
write_fixture("snapshot_multi_entity", &rmp_serde::to_vec_named(&snapshot_multi).unwrap());
// Diagonal movement fixtures (clockwise: NE, SE, SW, NW)
for (name, action) in [
("input_move_northeast", PlayerAction::MoveNortheast),
("input_move_southeast", PlayerAction::MoveSoutheast),
("input_move_southwest", PlayerAction::MoveSouthwest),
("input_move_northwest", PlayerAction::MoveNorthwest),
] {
let input = PlayerInput { tick: 100, action };
write_fixture(name, &rmp_serde::to_vec_named(&input).unwrap());
}
}