190 lines
7.4 KiB
GDScript
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 |