Files
tekton/tests/test_gauntlet_floor_highlight.gd
T
2026-06-26 18:31:17 +08:00

190 lines
7.4 KiB
GDScript

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