diff --git a/client/addons/messagepack/messagepack.gd b/client/addons/messagepack/messagepack.gd index 5ada09950..a0cfc9deb 100755 --- a/client/addons/messagepack/messagepack.gd +++ b/client/addons/messagepack/messagepack.gd @@ -88,6 +88,7 @@ static func _encode_message(buffer: StreamPeerBuffer, value): buffer.put_u8(types["uint_32"]) buffer.put_u32(value) elif value < 0: + # Negative beyond int_32 range — encode as int_64 (0xd3) buffer.put_u8(types["int_64"]) buffer.put_64(value) else: @@ -133,7 +134,9 @@ static func _encode_message(buffer: StreamPeerBuffer, value): return ERR_INVALID_DATA for obj in value: - _encode_message(buffer, obj) + var inner_err = _encode_message(buffer, obj) + if inner_err: + return inner_err TYPE_DICTIONARY: var size = value.size() @@ -150,8 +153,12 @@ static func _encode_message(buffer: StreamPeerBuffer, value): return ERR_INVALID_DATA for key in value: - _encode_message(buffer, key) - _encode_message(buffer, value[key]) + var key_err = _encode_message(buffer, key) + if key_err: + return key_err + var val_err = _encode_message(buffer, value[key]) + if val_err: + return val_err TYPE_PACKED_BYTE_ARRAY: var size = value.size() diff --git a/client/tests/test_anti_tedium.gd b/client/tests/test_anti_tedium.gd index fab341bec..a97703a32 100644 --- a/client/tests/test_anti_tedium.gd +++ b/client/tests/test_anti_tedium.gd @@ -214,8 +214,11 @@ func test_snapshot_without_room_id_shows_no_gauntlet_ui() -> void: # Apply to GameState — gauntlet-related state should not exist GameState.apply_snapshot(snapshot) - # GameState should not have gauntlet fields set (they don't exist yet, - # and when added, they must default to null/false) + # Guard: when #496 adds GameState.room_id / gauntlet_mode properties, + # these assertions become falsifiable — they'll catch any code path that + # sets gauntlet state from a non-gauntlet snapshot. Currently Object.get() + # returns null for nonexistent properties, so this passes trivially until + # the properties are defined. assert_that(GameState.get("room_id")).override_failure_message( "GameState.room_id should not exist or be null in non-gauntlet mode" ).is_null() diff --git a/client/tests/test_client_p2.gd b/client/tests/test_client_p2.gd index b64e8d81e..c0bca3a5f 100644 --- a/client/tests/test_client_p2.gd +++ b/client/tests/test_client_p2.gd @@ -80,7 +80,10 @@ func test_camera_smoothing_convergence() -> void: for i in 5: inst._process(0.016) var expected := GameState.player_position * Constants.TILE_SIZE - assert_that(camera.global_position).is_equal(expected) + var dist := camera.global_position.distance_to(expected) + assert_that(dist < 0.1).override_failure_message( + "Camera should converge to player position (dist: %.4f)" % dist + ).is_true() func test_camera_viewport_tracks_player_position() -> void: @@ -191,6 +194,10 @@ func test_entity_player_color_regardless_of_sector() -> void: func test_monologue_display_visible_hidden() -> void: # P2-U01: MonologueDisplay starts hidden, becomes visible after show_monologue. + # Note: mono.is_visible is a custom bool property on MonologueDisplay + # (monologue_display.gd:11), not the built-in CanvasItem.is_visible() method. + # The monologue uses tween alpha for visual hide/show, so the built-in + # .visible stays true — we test the script's own state tracking. var inst := _make_scene() var mono = inst.get_node("UILayer/MonologueDisplay") assert_that(mono.is_visible).override_failure_message( diff --git a/client/tests/test_client_p3.gd b/client/tests/test_client_p3.gd index ec6fdda02..1b802a599 100644 --- a/client/tests/test_client_p3.gd +++ b/client/tests/test_client_p3.gd @@ -13,6 +13,7 @@ var _instance: Node = null func before_test() -> void: SimBridge.reset_test_state() + SimBridge._last_snapshot = null GameState.current_tick = 0 GameState.player_position = Vector2.ZERO GameState.visible_entities = [] @@ -96,7 +97,13 @@ func test_z_fog_above_world_content() -> void: var inst := _make_scene() var fog = inst.get_node("World/FogOverlay") var fog_entities = inst.get_node("World/FogEntities") + var fog_group = inst.get_node("World/FogGroup") var overhead = inst.get_node("World/FogGroup/Overhead") + # FogOverlay must be a sibling of FogGroup (both children of World), + # not a child of FogGroup — fog renders OVER the composited group. + assert_that(fog.get_parent()).override_failure_message( + "FogOverlay must be sibling of FogGroup (both under World)" + ).is_equal(fog_group.get_parent()) assert_that(fog.z_index).override_failure_message( "FogOverlay z_index must be Z_FOG (%d) per D-049" % Constants.Z_FOG ).is_equal(Constants.Z_FOG) @@ -117,15 +124,22 @@ func test_z_ui_layer_above_world() -> void: var inst := _make_scene() var ui_layer = inst.get_node("UILayer") as CanvasLayer var insert_layer = inst.get_node("InsertOverlay") as CanvasLayer - assert_that(ui_layer.layer).override_failure_message( - "UILayer must be CanvasLayer %d" % Constants.CANVAS_UI - ).is_equal(Constants.CANVAS_UI) + var modal_layer = inst.get_node("ModalLayer") as CanvasLayer assert_that(insert_layer.layer).override_failure_message( "InsertOverlay must be CanvasLayer %d" % Constants.CANVAS_INSERT ).is_equal(Constants.CANVAS_INSERT) + assert_that(ui_layer.layer).override_failure_message( + "UILayer must be CanvasLayer %d" % Constants.CANVAS_UI + ).is_equal(Constants.CANVAS_UI) + assert_that(modal_layer.layer).override_failure_message( + "ModalLayer must be CanvasLayer %d" % Constants.CANVAS_MODAL + ).is_equal(Constants.CANVAS_MODAL) assert_that(ui_layer.layer > insert_layer.layer).override_failure_message( "UILayer must render above InsertOverlay" ).is_true() + assert_that(modal_layer.layer > ui_layer.layer).override_failure_message( + "ModalLayer must render above UILayer" + ).is_true() # -- Entity lerp (3) ---------------------------------------------------------- @@ -318,8 +332,8 @@ func test_recognition_blob_removed_when_absent() -> void: func test_entity_renderer_lerp_speed_constant() -> void: - # P3-T05: LERP_SPEED is tuned at 12.0 per D-053 stance discussion. - # Ensures no accidental changes to the feel of entity movement. + # P3-T05: LERP_SPEED is tuned at 12.0 — exponential smoothing constant + # for entity visual interpolation. Pin value to prevent accidental changes. assert_that(EntityRenderer.LERP_SPEED).override_failure_message( - "LERP_SPEED must be 12.0 per D-053 tuning" + "LERP_SPEED must be 12.0 (entity movement feel constant)" ).is_equal(12.0)