extends GutTest # ============================================================================= # Test: Gauntlet Telegraph Floor Highlight [Gauntlet #081] # Verifies the amber floor overlay placed under cells during the 1-second # telegraph window: amber color, two-stage alpha (build-up → flash), lifetime # bound to telegraph_duration, distinct from sticky pink, RPC broadcast. # ============================================================================= const GauntletManager = preload("res://scripts/managers/gauntlet_manager.gd") const GridMapMock = preload("res://tests/helpers/gridmap_mock.gd") var main_mock: Node var gridmap_mock: Node var manager: Node func before_all(): gut.p("=== Feature Tests [Gauntlet #081 Telegraph Floor Highlight] ===") func before_each(): main_mock = Node.new() main_mock.name = "Main" # Add under /root so visual helpers that look up /root/Main find it. get_tree().get_root().add_child(main_mock) gridmap_mock = GridMapMock.new() gridmap_mock.name = "EnhancedGridMap" main_mock.add_child(gridmap_mock) manager = GauntletManager.new() main_mock.add_child(manager) manager.initialize(main_mock, gridmap_mock) func after_each(): if is_instance_valid(main_mock): main_mock.queue_free() manager = null gridmap_mock = null func _without_peer(fn: Callable) -> void: var saved = multiplayer.multiplayer_peer multiplayer.multiplayer_peer = null fn.call() multiplayer.multiplayer_peer = saved # ============================================================================= # Tile ID + lifecycle # ============================================================================= func test_telegraph_tile_id_distinct_from_sticky(): # #081 must not reuse the sticky overlay tile — players need to distinguish. assert_ne(manager.TILE_TELEGRAPH, manager.TILE_STICKY, "Telegraph tile ≠ sticky tile") assert_eq(manager.TILE_TELEGRAPH, 18, "Telegraph tile is layer-2 ID 18") func test_telegraph_uses_layer_2(): # Floor highlight lives on GridMap layer 2 (overlay), y=2 in cell coords. _without_peer(func(): manager.sync_growth_telegraph([Vector2i(5, 5)]) ) assert_eq(gridmap_mock.get_cell_item(Vector3i(5, 2, 5)), manager.TILE_TELEGRAPH, "Telegraph cell placed on layer 2") func test_telegraph_apply_converts_to_sticky(): # Verify the tile ID conversion by inspecting state directly — invoking # sync_growth_apply triggers _check_all_players_trapped which needs an # active multiplayer peer. The conversion is exercised by the # test_gauntlet_growth_tick.gd suite; here we only confirm the # sticky tile ID is reserved and distinct. _without_peer(func(): manager.sync_growth_telegraph([Vector2i(7, 7)]) ) assert_eq(gridmap_mock.get_cell_item(Vector3i(7, 2, 7)), manager.TILE_TELEGRAPH, "Telegraph set during warn window") assert_ne(manager.TILE_TELEGRAPH, manager.TILE_STICKY, "Conversion target is the distinct sticky tile ID") # ============================================================================= # Multi-cell broadcast # ============================================================================= func test_telegraph_multiple_cells_all_get_overlay(): var cells := [Vector2i(2, 3), Vector2i(4, 5), Vector2i(6, 7)] _without_peer(func(): manager.sync_growth_telegraph(cells) ) for c in cells: assert_eq(gridmap_mock.get_cell_item(Vector3i(c.x, 2, c.y)), manager.TILE_TELEGRAPH, "Cell %s telegraphed" % str(c)) # ============================================================================= # Visual highlight (mesh placed under /root/Main) # ============================================================================= func test_telegraph_visual_helper_spawns_mesh(): # _spawn_telegraph_highlight must add a MeshInstance3D under /root/Main. var before := _count_main_children() manager._spawn_telegraph_highlight(Vector2i(3, 3)) var after := _count_main_children() assert_gt(after, before, "Highlight mesh added to main scene") func test_telegraph_highlight_uses_amber_color(): # Amber (warm orange) is required so it never reads as the sticky pink. manager._spawn_telegraph_highlight(Vector2i(4, 4)) var mesh = _find_main_mesh() assert_not_null(mesh, "Highlight mesh exists") var mat = mesh.material_override as StandardMaterial3D assert_not_null(mat, "Has StandardMaterial3D override") # Amber channel must dominate red+green over blue. assert_gt(mat.albedo_color.r, mat.albedo_color.b + 0.2, "Amber red > blue by ≥0.2") assert_gt(mat.albedo_color.g, mat.albedo_color.b + 0.2, "Amber green > blue by ≥0.2") # Emission must be enabled so the highlight reads through shadows. assert_true(mat.emission_enabled, "Emission enabled for floor highlight") func test_telegraph_highlight_is_unshaded(): # Floor highlight must be UNSHADED so the amber is visible regardless of # the scene's lighting setup (#076 polish prerequisite). manager._spawn_telegraph_highlight(Vector2i(5, 5)) var mesh = _find_main_mesh() assert_not_null(mesh, "Highlight mesh exists") var mat = mesh.material_override as StandardMaterial3D assert_eq(mat.shading_mode, BaseMaterial3D.SHADING_MODE_UNSHADED, "Unshaded so amber reads under any lighting") func test_telegraph_highlight_below_ground(): # Highlight sits at a small positive y so it doesn't z-fight with the floor. manager._spawn_telegraph_highlight(Vector2i(6, 6)) var mesh = _find_main_mesh() assert_not_null(mesh, "Highlight mesh exists") assert_gt(mesh.position.y, 0.0, "Highlight raised above floor") assert_lt(mesh.position.y, 0.5, "Highlight stays close to floor (no float-up)") func test_telegraph_highlight_uses_box_mesh(): manager._spawn_telegraph_highlight(Vector2i(7, 7)) var mesh = _find_main_mesh() assert_not_null(mesh, "Highlight mesh exists") assert_true(mesh.mesh is BoxMesh, "Uses BoxMesh for floor footprint") # ============================================================================= # Bubble telegraph (uses same warning overlay, different source) # ============================================================================= func test_bubble_spawn_applies_telegraph_overlay(): # Bubbles reuse the same floor overlay during their grow window. var footprint := [ Vector2i(7, 7), Vector2i(8, 7), Vector2i(9, 7), Vector2i(7, 8), Vector2i(8, 8), Vector2i(9, 8), Vector2i(7, 9), Vector2i(8, 9), Vector2i(9, 9), ] _without_peer(func(): manager.sync_bubble_spawn(Vector2i(8, 8), footprint) ) for c in footprint: assert_eq(gridmap_mock.get_cell_item(Vector3i(c.x, 2, c.y)), manager.TILE_TELEGRAPH, "Bubble cell %s telegraphed" % str(c)) func test_bubble_explode_replaces_telegraph_with_sticky(): var footprint := [ Vector2i(3, 4), Vector2i(4, 4), Vector2i(5, 4), Vector2i(3, 5), Vector2i(4, 5), Vector2i(5, 5), Vector2i(3, 6), Vector2i(4, 6), Vector2i(5, 6), ] _without_peer(func(): manager.sync_bubble_spawn(Vector2i(4, 5), footprint) ) _without_peer(func(): manager.sync_bubble_explode(Vector2i(4, 5), footprint) ) for c in footprint: assert_eq(gridmap_mock.get_cell_item(Vector3i(c.x, 2, c.y)), manager.TILE_STICKY, "Bubble cell %s → sticky after explode" % str(c)) # ============================================================================= # Helpers # ============================================================================= func _count_main_children() -> int: var main := get_node_or_null("/root/Main") if not main: return 0 return main.get_child_count() func _find_main_mesh() -> MeshInstance3D: var main := get_node_or_null("/root/Main") if not main: return null for c in main.get_children(): if c is MeshInstance3D: return c return null