diff --git a/client/scripts/autoloads/sim_bridge.gd b/client/scripts/autoloads/sim_bridge.gd index d0629fa0e..1a2991111 100644 --- a/client/scripts/autoloads/sim_bridge.gd +++ b/client/scripts/autoloads/sim_bridge.gd @@ -264,8 +264,7 @@ func _process(delta: float) -> void: # gdlint:disable=max-returns # Send startup message with world_seed and character appearance (#175, D-010/D-029, #718). # Server blocks waiting for this before entering the tick loop. var startup_bytes := Protocol.encode_startup_message( - GameState.world_seed, - GameState.character_visual_descriptor + GameState.world_seed, GameState.character_visual_descriptor ) if startup_bytes.size() > 0: var send_err: int = _bridge.send_message(startup_bytes) @@ -400,7 +399,10 @@ func request_atlas_layers(body_id: String, up_to: String = "Topography") -> void var err: int = _bridge.send_message(bytes) if err != OK: push_error( - "SimBridge: failed to send atlas layer request for %s: %s" % [body_id, error_string(err)] + ( + "SimBridge: failed to send atlas layer request for %s: %s" + % [body_id, error_string(err)] + ) ) diff --git a/client/scripts/protocol/protocol.gd b/client/scripts/protocol/protocol.gd index f95d12934..a627ce3f5 100644 --- a/client/scripts/protocol/protocol.gd +++ b/client/scripts/protocol/protocol.gd @@ -9,9 +9,11 @@ extends Node ## Unit enum variants (no data) → bare strings ("MoveNorth", "Npc") ## Data enum variants → single-element maps ({"UsePerceptionMode": "thermal"}) + static func _mp(): return load("res://addons/messagepack/messagepack.gd") + # -- Decode: bytes from server → GDScript types -------------------------------- @@ -411,18 +413,21 @@ static func _decode_snapshot_from_raw(raw: Variant) -> Variant: if raw_alc is Array: for cul in raw_alc: alc.append(str(cul)) - bm_entries.append( - { - "id": str(raw_bm["id"]), - "title": str(raw_bm.get("title", "")), - "subtitle": str(raw_bm.get("subtitle", "")), - "flavor": str(raw_bm.get("flavor", "")), - "default_location": str(raw_bm.get("default_location", "")), - "allowed_locations": al, - "allowed_locations_cultures": alc, - "career": str(raw_bm.get("career", "")), - "starting_capital_tractus": int(raw_bm.get("starting_capital_tractus", 0)), - } + ( + bm_entries + . append( + { + "id": str(raw_bm["id"]), + "title": str(raw_bm.get("title", "")), + "subtitle": str(raw_bm.get("subtitle", "")), + "flavor": str(raw_bm.get("flavor", "")), + "default_location": str(raw_bm.get("default_location", "")), + "allowed_locations": al, + "allowed_locations_cultures": alc, + "career": str(raw_bm.get("career", "")), + "starting_capital_tractus": int(raw_bm.get("starting_capital_tractus", 0)), + } + ) ) bookmark_catalog = {"bookmarks": bm_entries} @@ -744,7 +749,9 @@ static func encode_request_bookmark_catalog() -> PackedByteArray: ## A bare map {body_id, up_to} — NOT the Vec array — so the server's ## frame demux routes it to the atlas proxy. up_to is a CascadeLayer unit variant ## (bare string: "Heightmap" | "Topography"). -static func encode_atlas_layer_request(body_id: String, up_to: String = "Topography") -> PackedByteArray: +static func encode_atlas_layer_request( + body_id: String, up_to: String = "Topography" +) -> PackedByteArray: var msg := {"body_id": body_id, "up_to": up_to} var result = _mp().encode(msg) if result.status != null: @@ -799,12 +806,15 @@ static func decode_inbound(bytes: PackedByteArray) -> Dictionary: ## Encode a ConfirmBookmark action (#614, #680). ## Struct variant with bookmark_id and starting_location_id. -static func encode_confirm_bookmark(bookmark_id: String, starting_location_id: String) -> PackedByteArray: +static func encode_confirm_bookmark( + bookmark_id: String, starting_location_id: String +) -> PackedByteArray: var entries: Array = [ { "tick": 0, "action_name": "ConfirmBookmark", - "action_data": {"bookmark_id": bookmark_id, "starting_location_id": starting_location_id}, + "action_data": + {"bookmark_id": bookmark_id, "starting_location_id": starting_location_id}, } ] var result = _mp().encode(entries) diff --git a/client/tests/test_protocol.gd b/client/tests/test_protocol.gd index 915d15a04..5c3428437 100644 --- a/client/tests/test_protocol.gd +++ b/client/tests/test_protocol.gd @@ -16,6 +16,7 @@ func _load_fixture(name: String) -> PackedByteArray: # -- Snapshot decoding ---------------------------------------------------------- + func test_decode_snapshot_one_npc() -> void: var bytes = _load_fixture("snapshot_one_npc") var snapshot = Protocol.decode_snapshot(bytes) @@ -102,6 +103,7 @@ func test_decode_snapshot_multi_entity() -> void: # -- PlayerInput decoding ------------------------------------------------------- + func test_decode_input_move_north() -> void: var bytes = _load_fixture("input_move_north") var input = Protocol.decode_player_input(bytes) @@ -124,6 +126,7 @@ func test_decode_input_perception_mode() -> void: # -- PlayerInput encoding ------------------------------------------------------- + func test_encode_decode_roundtrip_unit_variant() -> void: var bytes = Protocol.encode_player_input(50, "MoveEast") assert_that(bytes.size()).is_greater(0) @@ -148,6 +151,7 @@ func test_encode_decode_roundtrip_data_variant() -> void: # -- Cross-language roundtrip: GDScript encode matches Rust decode --------------- + func test_gdscript_encode_matches_rust_fixture() -> void: # Encode the same MoveNorth input as the Rust fixture var bytes = Protocol.encode_player_input(100, "MoveNorth") @@ -164,6 +168,7 @@ func test_gdscript_encode_matches_rust_fixture() -> void: # -- Negative tests: malformed/truncated input ----------------------------------- + func test_decode_snapshot_truncated_bytes() -> void: var truncated := PackedByteArray([0x82, 0xa4]) # Incomplete msgpack map var result = Protocol.decode_snapshot(truncated) @@ -189,7 +194,8 @@ func test_decode_snapshot_malformed_entities_counted() -> void: var raw := { "tick": 7, "version": 23, - "entities": [ + "entities": + [ {"entity_id": 1, "x": 5.0, "y": 10.0, "z": 0, "kind": "Npc"}, {"entity_id": 2, "broken": true}, # Missing required fields {"x": 1.0}, # Missing entity_id, y, z, kind @@ -217,6 +223,7 @@ func test_encode_produces_nonempty_bytes() -> void: # -- Batch input encoding (Vec wire format) ----------------------- + func test_encode_player_inputs_single() -> void: var inputs: Array = [{"tick": 10, "action_name": "MoveNorth"}] var bytes := Protocol.encode_player_inputs(inputs) @@ -276,6 +283,7 @@ func test_encode_player_inputs_empty() -> void: # -- v2 snapshot decoding ------------------------------------------------------- + func test_decode_snapshot_v2_full() -> void: var bytes = _load_fixture("snapshot_v2_full") var snapshot = Protocol.decode_snapshot(bytes) @@ -304,7 +312,9 @@ func test_decode_snapshot_v2_full() -> void: func test_existing_fixtures_have_v2_fields() -> void: # All fixtures are generated by fixture_snapshot() — verify decoder extracts v2+ fields - for fixture_name in ["snapshot_one_npc", "snapshot_empty", "snapshot_player", "snapshot_multi_entity"]: + for fixture_name in [ + "snapshot_one_npc", "snapshot_empty", "snapshot_player", "snapshot_multi_entity" + ]: var bytes = _load_fixture(fixture_name) var snapshot = Protocol.decode_snapshot(bytes) assert_that(snapshot).is_not_null() @@ -324,6 +334,7 @@ func test_multi_entity_visibility_sectors() -> void: # -- Batch input fixture (D-030 Layer 1 bidirectional symmetry) ---------------- + func test_decode_batch_input_fixture() -> void: # Rust-generated Vec fixture — verifies bidirectional Layer 1 compatibility var bytes = _load_fixture("input_batch_two") @@ -341,6 +352,7 @@ func test_decode_batch_input_fixture() -> void: # -- 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 = [ @@ -361,14 +373,17 @@ func test_decode_diagonal_fixtures() -> void: # -- v23: BookmarkCatalog decode ----------------------------------------------- + func test_decode_snapshot_with_bookmark_catalog() -> void: # Hand-built dict — fixture generation requires server work, skip round-trip (#614). var raw := { "tick": 1, "version": 23, "entities": [], - "bookmark_catalog": { - "bookmarks": [ + "bookmark_catalog": + { + "bookmarks": + [ { "id": "bm_tycoon_arion", "title": "The Arion Run", @@ -436,6 +451,7 @@ func test_decode_snapshot_no_bookmark_catalog_is_null() -> void: # -- v23: RequestBookmarkCatalog + ConfirmBookmark encoding -------------------- + func test_encode_request_bookmark_catalog_roundtrip() -> void: var bytes := Protocol.encode_request_bookmark_catalog() assert_that(bytes.size()).is_greater(0) @@ -467,6 +483,7 @@ func test_encode_confirm_bookmark_roundtrip() -> void: # -- Atlas layer-stream protocol (#969, D-225) --------------------------------- + func test_decode_atlas_response_ready() -> void: var bytes := _load_fixture("atlas_response_ready") var resp = Protocol.decode_atlas_layer_response(bytes)