From c054406e38d9e5a01f9f499a0d0cedafdefdfad4 Mon Sep 17 00:00:00 2001 From: god Date: Mon, 6 Jul 2026 01:44:17 +0800 Subject: [PATCH] remove dead gauntlet test files, prune cleanser tests from test_bot_gauntlet --- tests/test_bot_gauntlet.gd | 192 ++----------------- tests/test_gauntlet_bubble.gd | 163 ---------------- tests/test_gauntlet_bubble.gd.uid | 1 - tests/test_gauntlet_cleanser.gd | 117 ------------ tests/test_gauntlet_cleanser.gd.uid | 1 - tests/test_gauntlet_floor_highlight.gd | 190 ------------------- tests/test_gauntlet_floor_highlight.gd.uid | 1 - tests/test_gauntlet_growth_tick.gd | 159 ---------------- tests/test_gauntlet_growth_tick.gd.uid | 1 - tests/test_gauntlet_movement_buffer.gd | 119 ------------ tests/test_gauntlet_movement_buffer.gd.uid | 1 - tests/test_gauntlet_registration.gd | 155 --------------- tests/test_gauntlet_registration.gd.uid | 1 - tests/test_gauntlet_scoring.gd | 185 ------------------ tests/test_gauntlet_scoring.gd.uid | 1 - tests/test_gauntlet_sticky_system.gd | 208 --------------------- tests/test_gauntlet_sticky_system.gd.uid | 1 - tests/test_gauntlet_tile_spawning.gd | 144 -------------- tests/test_gauntlet_tile_spawning.gd.uid | 1 - 19 files changed, 11 insertions(+), 1630 deletions(-) delete mode 100644 tests/test_gauntlet_bubble.gd delete mode 100644 tests/test_gauntlet_bubble.gd.uid delete mode 100644 tests/test_gauntlet_cleanser.gd delete mode 100644 tests/test_gauntlet_cleanser.gd.uid delete mode 100644 tests/test_gauntlet_floor_highlight.gd delete mode 100644 tests/test_gauntlet_floor_highlight.gd.uid delete mode 100644 tests/test_gauntlet_growth_tick.gd delete mode 100644 tests/test_gauntlet_growth_tick.gd.uid delete mode 100644 tests/test_gauntlet_movement_buffer.gd delete mode 100644 tests/test_gauntlet_movement_buffer.gd.uid delete mode 100644 tests/test_gauntlet_registration.gd delete mode 100644 tests/test_gauntlet_registration.gd.uid delete mode 100644 tests/test_gauntlet_scoring.gd delete mode 100644 tests/test_gauntlet_scoring.gd.uid delete mode 100644 tests/test_gauntlet_sticky_system.gd delete mode 100644 tests/test_gauntlet_sticky_system.gd.uid delete mode 100644 tests/test_gauntlet_tile_spawning.gd delete mode 100644 tests/test_gauntlet_tile_spawning.gd.uid diff --git a/tests/test_bot_gauntlet.gd b/tests/test_bot_gauntlet.gd index a777fc6..fedcfc3 100644 --- a/tests/test_bot_gauntlet.gd +++ b/tests/test_bot_gauntlet.gd @@ -1,13 +1,11 @@ extends GutTest # ============================================================================= -# Test: Bot AI — Sticky Avoidance & Pathfinding Candy Survival #075] +# Test: Bot AI — Sticky Avoidance & Pathfinding [Candy Survival] # Verifies the bot's strategic planner correctly: # • Detects Candy Survival mode and exposes helpers. # • Rejects sticky / telegraphed cells in _is_valid_move_target. -# • Activates Cleanser when boxed in or standing on telegraphed ground. -# • Honours an active Cleanser (treats sticky cells as passable). -# • Calls rpc_activate_cleanser on the CandySurvivalManager when triggered. +# • Uses CandySurvivalManager.is_sticky_cell() authority. # ============================================================================= const BotStrategicPlanner = preload("res://scripts/bot_strategic_planner.gd") @@ -27,21 +25,10 @@ class StubActor extends Node3D: return false class StubCandySurvivalManager extends Node: - # Mimics the slice of CandySurvivalManager API the bot planner depends on. - var sticky_map: Dictionary = {} # Vector2i -> true - var player_cleansers: Dictionary = {} # peer_id -> int - var cleanser_active: Dictionary = {} # peer_id -> true - var activate_rpc_calls: Array = [] # recorded [peer_id] - + var sticky_map: Dictionary = {} func is_sticky_cell(pos: Vector2i) -> bool: return sticky_map.get(pos, false) - func is_cleanser_active(pid: int) -> bool: - return cleanser_active.get(pid, false) - - func rpc_activate_cleanser(pid: int) -> void: - activate_rpc_calls.append(pid) - # ---- Test fixture ----------------------------------------------------------- var main_node: Node @@ -52,8 +39,6 @@ var planner: RefCounted func before_each(): main_node = Node.new() - # Unique name per test to avoid collisions with previous runs that haven't - # been fully freed yet. main_node.name = "BotTestMain_%d" % Time.get_ticks_usec() get_tree().get_root().add_child(main_node) @@ -61,8 +46,6 @@ func before_each(): gridmap.name = "EnhancedGridMap" var nwi: Array[int] = [4] gridmap.non_walkable_items = nwi - # Pre-seed Floor 0 with a walkable tile (id 1) for every cell, so the bot's - # `_is_valid_move_target` Floor 0 check passes by default. for x in range(20): for z in range(20): gridmap.set_cell_item(Vector3i(x, 0, z), 1) @@ -80,7 +63,6 @@ func before_each(): planner = BotStrategicPlanner.new(actor, gridmap) planner.candy_survival_manager_override = candy_survival_manager - # Default to Candy Survival mode for these tests. LobbyManager.game_mode = "Candy Survival" func after_each(): @@ -90,7 +72,6 @@ func after_each(): planner = null candy_survival_manager = null gridmap = null - # Reset lobby mode so other tests aren't affected. LobbyManager.game_mode = "Freemode" # ============================================================================= @@ -117,20 +98,19 @@ func test_get_candy_survival_manager_resolves_from_main(): func test_overlay_unsafe_false_on_empty_layer2(): assert_false(planner._is_overlay_unsafe(Vector2i(3, 3)), - "Empty layer 2 → safe") + "Empty layer 2 -> safe") func test_overlay_unsafe_true_for_sticky_tile(): - gridmap.set_cell_item(Vector3i(3, 2, 3), 17) # TILE_STICKY + gridmap.set_cell_item(Vector3i(3, 2, 3), 17) assert_true(planner._is_overlay_unsafe(Vector2i(3, 3)), "Sticky overlay is unsafe") func test_overlay_unsafe_true_for_telegraph_tile(): - gridmap.set_cell_item(Vector3i(4, 2, 4), 18) # TILE_TELEGRAPH + gridmap.set_cell_item(Vector3i(4, 2, 4), 18) assert_true(planner._is_overlay_unsafe(Vector2i(4, 4)), "Telegraph overlay is unsafe") func test_overlay_unsafe_ignores_layer0_and_layer1(): - # Sticky value on the wrong layer should NOT be flagged as unsafe. gridmap.set_cell_item(Vector3i(3, 0, 3), 17) gridmap.set_cell_item(Vector3i(3, 1, 3), 17) assert_false(planner._is_overlay_unsafe(Vector2i(3, 3)), @@ -140,13 +120,12 @@ func test_overlay_unsafe_ignores_layer0_and_layer1(): # _is_valid_move_target integration # ============================================================================= -func test_valid_move_target_rejects_sticky_in_gauntlet(): - # Make a sticky cell pass all other checks (valid position, walkable floor). +func test_valid_move_target_rejects_sticky_in_candy_survival(): gridmap.set_cell_item(Vector3i(3, 2, 3), 17) assert_false(planner._is_valid_move_target(Vector2i(3, 3)), "Sticky cell rejected in Candy Survival mode") -func test_valid_move_target_rejects_telegraphed_in_gauntlet(): +func test_valid_move_target_rejects_telegraphed_in_candy_survival(): gridmap.set_cell_item(Vector3i(5, 2, 5), 18) assert_false(planner._is_valid_move_target(Vector2i(5, 5)), "Telegraphed cell rejected in Candy Survival mode") @@ -156,36 +135,24 @@ func test_valid_move_target_accepts_clean_cells(): "Clean cell accepted") func test_valid_move_target_ignores_players_when_requested(): - # Even a sticky cell is bypassed when ignore_players path skips safety. gridmap.set_cell_item(Vector3i(3, 2, 3), 17) - # ignore_players=true is used by find_nearest_tile_of_type for tile pickup; - # safety must still apply, so this should still be rejected. assert_false(planner._is_valid_move_target(Vector2i(3, 3), true), "Safety check still active with ignore_players=true") func test_valid_move_target_outside_candy_survival_allows_sticky(): - # Outside Candy Survival, layer-2 sticky overlays are not safety-relevant. LobbyManager.game_mode = "Freemode" gridmap.set_cell_item(Vector3i(3, 2, 3), 17) assert_true(planner._is_valid_move_target(Vector2i(3, 3)), "Sticky overlay ignored in non-Candy Survival modes") -func test_valid_move_target_allows_sticky_when_cleanser_active(): - candy_survival_manager.cleanser_active[actor.peer_id] = true - gridmap.set_cell_item(Vector3i(3, 2, 3), 17) - assert_true(planner._is_valid_move_target(Vector2i(3, 3)), - "Active Cleanser grants temporary immunity") - # ============================================================================= # Sticky-cell awareness via CandySurvivalManager authority # ============================================================================= func test_valid_move_target_uses_candy_survival_manager_sticky_map(): - # Even if the gridmap overlay hasn't landed yet (RPC in flight), the - # CandySurvivalManager's authoritative sticky_cells map must block the move. candy_survival_manager.sticky_map[Vector2i(7, 7)] = true assert_false(planner._is_valid_move_target(Vector2i(7, 7)), - "Manager's sticky map blocks moves before overlay tiles land") + "Manager's sticky map blocks moves") # ============================================================================= # _count_unsafe_neighbors @@ -203,144 +170,7 @@ func test_count_unsafe_neighbors_four_when_surrounded(): "All four neighbors sticky") func test_count_unsafe_neighbors_partial_box(): - gridmap.set_cell_item(Vector3i(6, 2, 5), 17) # east - gridmap.set_cell_item(Vector3i(5, 2, 6), 17) # south + gridmap.set_cell_item(Vector3i(6, 2, 5), 17) + gridmap.set_cell_item(Vector3i(5, 2, 6), 17) assert_eq(planner._count_unsafe_neighbors(Vector2i(5, 5)), 2, "Two unsafe neighbors") - -# ============================================================================= -# should_activate_cleanser_now -# ============================================================================= - -func test_should_activate_cleanser_false_without_charge(): - actor.current_position = Vector2i(5, 5) - for d in [Vector2i(1, 0), Vector2i(-1, 0), Vector2i(0, 1), Vector2i(0, -1)]: - var n = Vector2i(5, 5) + d - gridmap.set_cell_item(Vector3i(n.x, 2, n.y), 17) - assert_false(planner.should_activate_cleanser_now(), - "Trapped but no charge → cannot activate") - -func test_should_activate_cleanser_true_when_surrounded(): - candy_survival_manager.player_cleansers[actor.peer_id] = 1 - actor.current_position = Vector2i(5, 5) - for d in [Vector2i(1, 0), Vector2i(-1, 0), Vector2i(0, 1), Vector2i(0, -1)]: - var n = Vector2i(5, 5) + d - gridmap.set_cell_item(Vector3i(n.x, 2, n.y), 17) - assert_true(planner.should_activate_cleanser_now(), - "Trapped with charge → activate Cleanser") - -func test_should_activate_cleanser_true_when_on_telegraphed(): - candy_survival_manager.player_cleansers[actor.peer_id] = 1 - actor.current_position = Vector2i(5, 5) - gridmap.set_cell_item(Vector3i(5, 2, 5), 18) # bot standing on telegraph - # 3+ unsafe neighbors required by spec; add three. - for d in [Vector2i(1, 0), Vector2i(-1, 0), Vector2i(0, 1)]: - var n = Vector2i(5, 5) + d - gridmap.set_cell_item(Vector3i(n.x, 2, n.y), 17) - assert_true(planner.should_activate_cleanser_now(), - "Standing on telegraph with 3 sticky neighbors → activate Cleanser") - -func test_should_activate_cleanser_false_when_already_active(): - candy_survival_manager.player_cleansers[actor.peer_id] = 1 - candy_survival_manager.cleanser_active[actor.peer_id] = true - actor.current_position = Vector2i(5, 5) - for d in [Vector2i(1, 0), Vector2i(-1, 0), Vector2i(0, 1), Vector2i(0, -1)]: - var n = Vector2i(5, 5) + d - gridmap.set_cell_item(Vector3i(n.x, 2, n.y), 17) - assert_false(planner.should_activate_cleanser_now(), - "Already active → don't re-fire") - -func test_should_activate_cleanser_false_outside_gauntlet(): - LobbyManager.game_mode = "Stop n Go" - candy_survival_manager.player_cleansers[actor.peer_id] = 1 - actor.current_position = Vector2i(5, 5) - for d in [Vector2i(1, 0), Vector2i(-1, 0), Vector2i(0, 1), Vector2i(0, -1)]: - var n = Vector2i(5, 5) + d - gridmap.set_cell_item(Vector3i(n.x, 2, n.y), 17) - assert_false(planner.should_activate_cleanser_now(), - "Outside Candy Survival → never auto-activate Cleanser") - -func test_should_activate_cleanser_false_when_not_trapped(): - candy_survival_manager.player_cleansers[actor.peer_id] = 1 - actor.current_position = Vector2i(5, 5) - # No sticky neighbors, plenty of room. - assert_false(planner.should_activate_cleanser_now(), - "Open field → no reason to burn Cleanser") - -# ============================================================================= -# Cleanser charge helpers -# ============================================================================= - -func test_bot_has_cleanser_charge_false_when_empty(): - assert_false(planner._bot_has_cleanser_charge(), "Empty by default") - -func test_bot_has_cleanser_charge_true_when_granted(): - candy_survival_manager.player_cleansers[actor.peer_id] = 1 - assert_true(planner._bot_has_cleanser_charge(), "Charge granted") - -func test_is_bot_cleanser_active_reflects_manager(): - assert_false(planner._is_bot_cleanser_active(), "Inactive by default") - candy_survival_manager.cleanser_active[actor.peer_id] = true - assert_true(planner._is_bot_cleanser_active(), "Manager reports active") - -# ============================================================================= -# BotController → CandySurvivalManager RPC integration -# ============================================================================= - -func test_bot_controller_requests_cleanser_when_trapped(): - var BotController = load("res://scripts/bot_controller.gd") - var ctrl = BotController.new() - # Attach as a child of the actor so _ready() resolves get_parent() correctly. - actor.add_child(ctrl) - ctrl.strategic_planner = planner - ctrl.actor = actor - - candy_survival_manager.player_cleansers[actor.peer_id] = 1 - actor.current_position = Vector2i(5, 5) - for d in [Vector2i(1, 0), Vector2i(-1, 0), Vector2i(0, 1), Vector2i(0, -1)]: - var n = Vector2i(5, 5) + d - gridmap.set_cell_item(Vector3i(n.x, 2, n.y), 17) - - var requested = ctrl._try_activate_cleanser() - assert_true(requested, "Controller requests Cleanser when trapped") - assert_eq(candy_survival_manager.activate_rpc_calls.size(), 1, - "RPC called exactly once") - assert_eq(candy_survival_manager.activate_rpc_calls[0], actor.peer_id, - "Correct peer id sent") - - ctrl.queue_free() - -func test_bot_controller_does_not_request_when_safe(): - var BotController = load("res://scripts/bot_controller.gd") - var ctrl = BotController.new() - actor.add_child(ctrl) - ctrl.strategic_planner = planner - ctrl.actor = actor - - candy_survival_manager.player_cleansers[actor.peer_id] = 1 - # No sticky anywhere. - var requested = ctrl._try_activate_cleanser() - assert_false(requested, "No request when not trapped") - assert_eq(candy_survival_manager.activate_rpc_calls.size(), 0, - "No RPC fired") - - ctrl.queue_free() - -func test_bot_controller_skips_cleanser_outside_gauntlet(): - var BotController = load("res://scripts/bot_controller.gd") - var ctrl = BotController.new() - actor.add_child(ctrl) - ctrl.strategic_planner = planner - ctrl.actor = actor - - LobbyManager.game_mode = "Stop n Go" - candy_survival_manager.player_cleansers[actor.peer_id] = 1 - actor.current_position = Vector2i(5, 5) - for d in [Vector2i(1, 0), Vector2i(-1, 0), Vector2i(0, 1), Vector2i(0, -1)]: - var n = Vector2i(5, 5) + d - gridmap.set_cell_item(Vector3i(n.x, 2, n.y), 17) - - var requested = ctrl._try_activate_cleanser() - assert_false(requested, "No request outside Candy Survival") - - ctrl.queue_free() \ No newline at end of file diff --git a/tests/test_gauntlet_bubble.gd b/tests/test_gauntlet_bubble.gd deleted file mode 100644 index b174828..0000000 --- a/tests/test_gauntlet_bubble.gd +++ /dev/null @@ -1,163 +0,0 @@ -extends GutTest - -# ============================================================================= -# Test: Candy Survival Candy Bubble System (v2) Candy Survival #082] -# Covers bubble-specific scoring components, phase budgets, anti-stacking, -# the 3x3 blast footprint, and the grow→explode lifecycle. -# Runs headless (no multiplayer peer): elapsed_time = 0 so the final-30s window -# is inactive unless a test sets it directly. -# ============================================================================= - -const CandySurvivalManager = preload("res://scripts/managers/candy_survival_manager.gd") -const GridMapMock = preload("res://tests/helpers/gridmap_mock.gd") -var manager -var main_mock: Node -var gridmap_mock: Node - -func before_each(): - main_mock = Node.new() - add_child(main_mock) - gridmap_mock = GridMapMock.new() - gridmap_mock.name = "EnhancedGridMap" - main_mock.add_child(gridmap_mock) - manager = CandySurvivalManager.new() - main_mock.add_child(manager) - manager.initialize(main_mock, gridmap_mock) - manager.current_phase = 0 - -func after_each(): - if main_mock: - main_mock.queue_free() - -# Run a callable with the multiplayer peer detached so manager code takes the -# local (non-rpc) sync path — deterministic for headless lifecycle tests. -func _without_peer(fn: Callable) -> void: - var saved = multiplayer.multiplayer_peer - multiplayer.multiplayer_peer = null - fn.call() - multiplayer.multiplayer_peer = saved - -# ============================================================================= -# Phase budget -# ============================================================================= - -func test_bubble_budget_per_phase(): - manager.current_phase = 0 - assert_eq(manager._bubble_budget_for_phase(), 0, "Phase 1 → 0 bubbles") - manager.current_phase = 1 - assert_eq(manager._bubble_budget_for_phase(), 2, "Phase 2 → 2 bubbles") - manager.current_phase = 2 - assert_eq(manager._bubble_budget_for_phase(), 3, "Phase 3 → 3 bubbles") - -func test_phase_change_resets_counter(): - manager.bubbles_this_phase = 2 - manager._start_phase(manager.Phase.SURVIVAL_ENDGAME) - assert_eq(manager.bubbles_this_phase, 0, "Per-phase bubble count resets on phase change") - -# ============================================================================= -# Blast footprint (3x3, clipped) -# ============================================================================= - -func test_blast_is_3x3_in_open_area(): - var cells = manager._bubble_blast_cells(Vector2i(14, 14)) - assert_eq(cells.size(), 9, "Open-area bubble blast is 3x3 = 9 cells") - -func test_blast_clips_npc_zone(): - # Center adjacent to the NPC zone (9,9) clips blocked cells out. - var cells = manager._bubble_blast_cells(Vector2i(7, 9)) - assert_true(cells.size() < 9, "Blast near NPC zone is clipped below 9") - for c in cells: - assert_false(manager._is_npc_zone(c), "No blast cell lands in NPC zone") - -# ============================================================================= -# Scoring components -# ============================================================================= - -func test_bubble_camping_thresholds(): - var region: Vector2i = manager._region_of(Vector2i(8, 8)) - manager._camp_tracking[1] = {"region": region, "time": 6.0} - assert_eq(manager._bubble_score_camping(Vector2i(8, 8)), 40.0, ">5s = +40") - manager._camp_tracking[1]["time"] = 9.0 - assert_eq(manager._bubble_score_camping(Vector2i(8, 8)), 60.0, ">8s = +60") - -func test_bubble_player_cluster(): - var players = [Vector2i(5, 5), Vector2i(6, 6)] - assert_eq(manager._bubble_score_player_cluster(Vector2i(5, 6), players), 20.0, "2 nearby players = +20") - assert_eq(manager._bubble_score_player_cluster(Vector2i(15, 15), players), 0.0, "No nearby players = 0") - -func test_bubble_direct_hit_penalty(): - var players = [Vector2i(5, 5)] - assert_eq(manager._bubble_score_direct_hit(Vector2i(5, 5), players), -60.0, "Directly under player = -60") - assert_eq(manager._bubble_score_direct_hit(Vector2i(8, 8), players), 0.0, "Not under player = 0") - -func test_bubble_recent_penalty(): - manager.recent_bubble_positions = [Vector2i(14, 14)] - assert_eq(manager._bubble_score_recent(Vector2i(11, 11)), -50.0, "Near recent bubble = -50") - assert_eq(manager._bubble_score_recent(Vector2i(2, 2)), 0.0, "Far from recent bubble = 0") - -func test_bubble_untouched_area(): - # Open arena around (10,10) → large reachable region → +30. - assert_eq(manager._bubble_score_untouched_area(Vector2i(14, 14)), 30.0, "Large untouched area = +30") - -func test_bubble_full_score_is_finite(): - var s = manager._calculate_bubble_score(Vector2i(8, 8), []) - assert_true(is_finite(s), "Full bubble score is finite") - -# ============================================================================= -# Spawn lifecycle -# ============================================================================= - -func test_spawn_bubble_marks_growing_cells(): - _without_peer(func(): - manager._spawn_bubble(Vector2i(14, 14)) - ) - assert_eq(manager.bubbles_this_phase, 1, "Phase counter increments") - assert_eq(manager.bubbles_total, 1, "Round counter increments") - assert_eq(manager.active_bubbles.size(), 1, "One active bubble") - assert_true(manager.bubble_cells.has(Vector2i(14, 14)), "Center marked BUBBLE_GROWING") - assert_eq(manager.cell_state(Vector2i(14, 14)), manager.CellState.BUBBLE_GROWING, "cell_state reports BUBBLE_GROWING") - -func test_spawn_bubble_records_recent_position(): - _without_peer(func(): - manager._spawn_bubble(Vector2i(14, 14)) - ) - assert_true(manager.recent_bubble_positions.has(Vector2i(14, 14)), "Center remembered for anti-stacking") - -func test_recent_positions_capped(): - _without_peer(func(): - for i in range(manager.BUBBLE_RECENT_MEMORY + 3): - manager._spawn_bubble(Vector2i(2 + i, 15)) - ) - assert_eq(manager.recent_bubble_positions.size(), manager.BUBBLE_RECENT_MEMORY, "Recent memory capped") - -# ============================================================================= -# Explosion -# ============================================================================= - -func test_update_bubbles_explodes_after_grow_duration(): - _without_peer(func(): - manager._spawn_bubble(Vector2i(14, 14)) - manager._update_bubbles(manager.BUBBLE_GROW_DURATION + 0.1) - ) - assert_eq(manager.active_bubbles.size(), 0, "Bubble removed after exploding") - assert_true(manager.sticky_cells.has(Vector2i(14, 14)), "Center became sticky") - assert_false(manager.bubble_cells.has(Vector2i(14, 14)), "BUBBLE_GROWING cleared on explode") - -func test_update_bubbles_waits_for_timer(): - _without_peer(func(): - manager._spawn_bubble(Vector2i(14, 14)) - manager._update_bubbles(manager.BUBBLE_GROW_DURATION * 0.5) - ) - assert_eq(manager.active_bubbles.size(), 1, "Bubble still growing before timer elapses") - assert_false(manager.sticky_cells.has(Vector2i(14, 14)), "No sticky yet mid-grow") - -func test_explode_creates_3x3_sticky(): - _without_peer(func(): - manager._explode_bubble(Vector2i(14, 14), manager._bubble_blast_cells(Vector2i(14, 14))) - ) - var sticky_in_blast := 0 - for dx in range(-1, 2): - for dz in range(-1, 2): - if manager.sticky_cells.has(Vector2i(14 + dx, 14 + dz)): - sticky_in_blast += 1 - assert_eq(sticky_in_blast, 9, "Explosion creates a full 3x3 sticky area") diff --git a/tests/test_gauntlet_bubble.gd.uid b/tests/test_gauntlet_bubble.gd.uid deleted file mode 100644 index 4efbefa..0000000 --- a/tests/test_gauntlet_bubble.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://bkte51v8tyoii diff --git a/tests/test_gauntlet_cleanser.gd b/tests/test_gauntlet_cleanser.gd deleted file mode 100644 index 3b0e3a6..0000000 --- a/tests/test_gauntlet_cleanser.gd +++ /dev/null @@ -1,117 +0,0 @@ -extends GutTest - -# ============================================================================= -# Test: Candy Survival Cleanser Power-Up (v2) Candy Survival #072] -# Covers grant cadence (every 2 missions, max 1), 5-cell immunity lifecycle, -# sticky clearing, stun-blocked activation, and the safe-stop early termination. -# Runs headless; uses a GridMap mock so clear_sticky_cell can run locally. -# ============================================================================= - -const CandySurvivalManager = preload("res://scripts/managers/candy_survival_manager.gd") -const GridMapMock = preload("res://tests/helpers/gridmap_mock.gd") -const MainMock = preload("res://tests/helpers/main_mock.gd") -var manager -var main_mock: Node -var gridmap_mock: Node - -func before_each(): - main_mock = MainMock.new() - add_child(main_mock) - gridmap_mock = GridMapMock.new() - gridmap_mock.name = "EnhancedGridMap" - main_mock.add_child(gridmap_mock) - manager = CandySurvivalManager.new() - main_mock.add_child(manager) - manager.initialize(main_mock, gridmap_mock) - manager.current_phase = 0 - -func after_each(): - if main_mock: - main_mock.queue_free() - -func _without_peer(fn: Callable) -> void: - var saved = multiplayer.multiplayer_peer - multiplayer.multiplayer_peer = null - fn.call() - multiplayer.multiplayer_peer = saved - -# ============================================================================= -# Grant cadence: every 2 missions, inventory max 1 -# ============================================================================= - -func test_no_cleanser_after_one_mission(): - manager._on_goal_count_updated(7, 1) - assert_eq(manager.player_cleansers.get(7, 0), 0, "No cleanser after 1 mission") - -func test_cleanser_granted_after_two_missions(): - manager._on_goal_count_updated(7, 1) - manager._on_goal_count_updated(7, 2) - assert_eq(manager.player_cleansers.get(7, 0), 1, "Cleanser granted after 2 missions") - -func test_cleanser_inventory_capped_at_one(): - # Four missions would be two grants. The new rule allows them to stack. - for i in range(4): - manager.player_mission_completions[7] = i + 1 - manager._on_goal_count_updated(7, i + 1) - assert_eq(manager.player_cleansers.get(7, 0), 2, "Cleansers now stack") - -# ============================================================================= -# Activation / immunity lifecycle -# ============================================================================= - -func test_use_cleanser_cell_decrements_until_exhausted(): - manager.cleanser_active[3] = true - manager.cleanser_cells_left[3] = manager.CLEANSER_MAX_CELLS - # First 4 uses keep it active... - for i in range(manager.CLEANSER_MAX_CELLS - 1): - assert_true(manager.use_cleanser_cell(3), "Still active on use %d" % (i + 1)) - # ...the 5th use exhausts it. - assert_false(manager.use_cleanser_cell(3), "Exhausted after 5th cell") - assert_false(manager.is_cleanser_active(3), "Deactivated after 5th cell") - -func test_use_cleanser_cell_when_inactive_returns_false(): - assert_false(manager.use_cleanser_cell(99), "Inactive cleanser use returns false") - -func test_deactivate_clears_state(): - manager.cleanser_active[5] = true - manager.cleanser_cells_left[5] = 3 - manager.deactivate_cleanser(5) - assert_false(manager.is_cleanser_active(5), "Deactivated") - assert_false(manager.cleanser_cells_left.has(5), "Cells-left cleared") - -# ============================================================================= -# Sticky clearing -# ============================================================================= - -func test_clear_sticky_cell_removes_and_protects(): - manager.sticky_cells[Vector2i(4, 4)] = true - _without_peer(func(): - manager.clear_sticky_cell(Vector2i(4, 4)) - ) - assert_false(manager.is_sticky_cell(Vector2i(4, 4)), "Sticky removed") - assert_true(manager.is_cleansed_cell(Vector2i(4, 4)), "Cleared cell gets regrowth protection") - # Layer-2 overlay cleared (mock records -1 = erased). - assert_eq(gridmap_mock.get_cell_item(Vector3i(4, 2, 4)), -1, "Layer-2 sticky overlay cleared") - -# ============================================================================= -# Safe-stop early termination (#072 acceptance: ends when stopping on safe cell) -# ============================================================================= - -func test_stop_on_safe_cell_keeps_cleanser(): - manager.cleanser_active[8] = true - manager.cleanser_cells_left[8] = 3 - - manager.notify_movement_stopped(8, Vector2i(5, 5)) # safe cell - assert_true(manager.is_cleanser_active(8), "Cleanser NO LONGER ends on safe-cell stop (persists charges)") - -func test_stop_on_sticky_cell_keeps_cleanser(): - manager.sticky_cells[Vector2i(6, 6)] = true - manager.cleanser_active[8] = true - manager.cleanser_cells_left[8] = 3 - manager.notify_movement_stopped(8, Vector2i(6, 6)) # still on sticky - assert_true(manager.is_cleanser_active(8), "Cleanser persists while still on sticky") - -func test_notify_stop_noop_without_cleanser(): - # Should not crash or change anything when the player has no cleanser. - manager.notify_movement_stopped(123, Vector2i(5, 5)) - assert_false(manager.is_cleanser_active(123), "No cleanser → no-op") diff --git a/tests/test_gauntlet_cleanser.gd.uid b/tests/test_gauntlet_cleanser.gd.uid deleted file mode 100644 index 104ecc2..0000000 --- a/tests/test_gauntlet_cleanser.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://b1bay8n1h65u3 diff --git a/tests/test_gauntlet_floor_highlight.gd b/tests/test_gauntlet_floor_highlight.gd deleted file mode 100644 index 7083eef..0000000 --- a/tests/test_gauntlet_floor_highlight.gd +++ /dev/null @@ -1,190 +0,0 @@ -extends GutTest - -# ============================================================================= -# Test: Candy Survival Telegraph Floor Highlight Candy Survival #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 CandySurvivalManager = preload("res://scripts/managers/candy_survival_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 Candy Survival #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 = CandySurvivalManager.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_candy_survival_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 \ No newline at end of file diff --git a/tests/test_gauntlet_floor_highlight.gd.uid b/tests/test_gauntlet_floor_highlight.gd.uid deleted file mode 100644 index 70b0418..0000000 --- a/tests/test_gauntlet_floor_highlight.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://dqvm86t7rr3t diff --git a/tests/test_gauntlet_growth_tick.gd b/tests/test_gauntlet_growth_tick.gd deleted file mode 100644 index 0d6a24e..0000000 --- a/tests/test_gauntlet_growth_tick.gd +++ /dev/null @@ -1,159 +0,0 @@ -extends GutTest - -# ============================================================================= -# Test: Candy Survival Growth Tick System (v2) Candy Survival #067] -# Replaces the old cannon-timer test. Covers growth timing, phase configs, -# candidate generation, cells-per-tick ranges, weighted selection, and -# cleansed-cell exclusion. -# ============================================================================= - -const CandySurvivalManager = preload("res://scripts/managers/candy_survival_manager.gd") -var candy_survival_manager: Node -var main_mock: Node -var gridmap_mock: Node - -func before_all(): - gut.p("=== Feature Tests Candy Survival #067 Growth Tick] ===") - -func before_each(): - main_mock = Node.new() - add_child(main_mock) - gridmap_mock = Node.new() - gridmap_mock.name = "EnhancedGridMap" - main_mock.add_child(gridmap_mock) - - candy_survival_manager = CandySurvivalManager.new() - main_mock.add_child(candy_survival_manager) - candy_survival_manager.initialize(main_mock, gridmap_mock) - -func after_each(): - if main_mock: - main_mock.queue_free() - -func after_all(): - gut.p("=== Feature Tests Complete ===") - -# ============================================================================= -# Growth Timing -# ============================================================================= - -func test_growth_timer_starts_zero(): - assert_eq(candy_survival_manager.growth_timer, 0.0, "Growth timer starts at 0.0") - -func test_growth_interval_default(): - assert_eq(candy_survival_manager.growth_interval, 3.0, "Growth interval defaults to 3.0s") - -func test_telegraph_duration_default(): - assert_eq(candy_survival_manager.telegraph_duration, 1.0, "Telegraph duration defaults to 1.0s") - -# ============================================================================= -# Phase Growth Config -# ============================================================================= - -func test_phase_growth_config_has_three_phases(): - assert_eq(candy_survival_manager.phase_growth_config.size(), 3, "Three phase growth configs") - -func test_phase1_cells_range(): - var cfg = candy_survival_manager.phase_growth_config[0] - assert_eq(cfg["cells_min"], 4, "Phase 1 min 4 cells/tick") - assert_eq(cfg["cells_max"], 6, "Phase 1 max 6 cells/tick") - -func test_phase2_cells_range(): - var cfg = candy_survival_manager.phase_growth_config[1] - assert_eq(cfg["cells_min"], 6, "Phase 2 min 6 cells/tick") - assert_eq(cfg["cells_max"], 8, "Phase 2 max 8 cells/tick") - -func test_phase3_cells_range(): - var cfg = candy_survival_manager.phase_growth_config[2] - assert_eq(cfg["cells_min"], 8, "Phase 3 min 8 cells/tick") - assert_eq(cfg["cells_max"], 10, "Phase 3 max 10 cells/tick") - -func test_cells_this_tick_in_phase_range(): - for phase in range(3): - candy_survival_manager.current_phase = phase - var cfg = candy_survival_manager.phase_growth_config[phase] - # Sample several times since the count is randomized. - for _i in range(20): - var n = candy_survival_manager._cells_this_tick() - assert_true(n >= cfg["cells_min"] and n <= cfg["cells_max"], - "Phase %d cells/tick %d within [%d,%d]" % [phase, n, cfg["cells_min"], cfg["cells_max"]]) - -# ============================================================================= -# Candidate Generation -# ============================================================================= - -func test_candidates_are_all_safe_cells(): - candy_survival_manager.current_phase = 0 - var candidates = candy_survival_manager._generate_candidates() - # Fresh arena: every playable cell is SAFE. - assert_eq(candidates.size(), candy_survival_manager.playable_cell_count(), - "All playable cells are candidates on a fresh arena") - -func test_candidates_exclude_sticky(): - candy_survival_manager.sticky_cells[Vector2i(3, 3)] = true - var candidates = candy_survival_manager._generate_candidates() - var found := false - for c in candidates: - if c["pos"] == Vector2i(3, 3): - found = true - assert_false(found, "Sticky cells are excluded from candidates") - -func test_candidates_exclude_cleansed(): - candy_survival_manager.mark_cleansed(Vector2i(4, 4)) - var candidates = candy_survival_manager._generate_candidates() - var found := false - for c in candidates: - if c["pos"] == Vector2i(4, 4): - found = true - assert_false(found, "Cleansed cells are excluded from candidates (regrowth protection)") - -func test_candidates_exclude_npc_and_boundary(): - var candidates = candy_survival_manager._generate_candidates() - for c in candidates: - var p = c["pos"] - assert_false(candy_survival_manager._is_npc_zone(p), "No NPC-zone candidates") - assert_false(candy_survival_manager._is_boundary(p), "No boundary candidates") - -func test_candidates_have_scores(): - var candidates = candy_survival_manager._generate_candidates() - assert_true(candidates.size() > 0, "Has candidates") - assert_true(candidates[0].has("score"), "Candidate carries a score") - -# ============================================================================= -# Weighted Selection -# ============================================================================= - -func test_select_count_respected(): - var candidates = candy_survival_manager._generate_candidates() - var picked = candy_survival_manager._select_cells_weighted(candidates, 5) - assert_eq(picked.size(), 5, "Selects exactly the requested count") - -func test_select_no_duplicates(): - var candidates = candy_survival_manager._generate_candidates() - var picked = candy_survival_manager._select_cells_weighted(candidates, 10) - var seen := {} - for p in picked: - assert_false(seen.has(p), "No duplicate selections") - seen[p] = true - -func test_select_capped_at_pool_size(): - var small = [{"pos": Vector2i(2, 2), "score": 1.0}, {"pos": Vector2i(2, 3), "score": 1.0}] - var picked = candy_survival_manager._select_cells_weighted(small, 10) - assert_eq(picked.size(), 2, "Cannot select more than pool size") - -# ============================================================================= -# Scoring Helpers -# ============================================================================= - -func test_layer_classification(): - assert_eq(candy_survival_manager._layer_of(Vector2i(9, 9)), "inner", "Center is inner") - assert_eq(candy_survival_manager._layer_of(Vector2i(1, 1)), "outer", "Corner is outer") - -func test_sticky_neighbor_count(): - candy_survival_manager.sticky_cells[Vector2i(5, 5)] = true - candy_survival_manager.sticky_cells[Vector2i(5, 6)] = true - assert_eq(candy_survival_manager._sticky_neighbor_count(Vector2i(6, 5)), 2, - "Counts 8-directional sticky neighbors") - -func test_chebyshev(): - assert_eq(candy_survival_manager._chebyshev(Vector2i(0, 0), Vector2i(3, 1)), 3, "Chebyshev distance") diff --git a/tests/test_gauntlet_growth_tick.gd.uid b/tests/test_gauntlet_growth_tick.gd.uid deleted file mode 100644 index 17dd1c1..0000000 --- a/tests/test_gauntlet_growth_tick.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://btbxtdhagjdba diff --git a/tests/test_gauntlet_movement_buffer.gd b/tests/test_gauntlet_movement_buffer.gd deleted file mode 100644 index a64f36e..0000000 --- a/tests/test_gauntlet_movement_buffer.gd +++ /dev/null @@ -1,119 +0,0 @@ -extends GutTest - -# ============================================================================= -# Test: Candy Survival Movement Buffer System (v2) Candy Survival #083] -# Hidden, decaying safe-corridor penalties layered onto candidate scoring. -# Runs headless; elapsed_time = 0 so the final-30s window is inactive unless a -# test sets elapsed_time directly. -# ============================================================================= - -const CandySurvivalManager = preload("res://scripts/managers/candy_survival_manager.gd") -var manager -var main_mock: Node -var gridmap_mock: Node - -func before_each(): - main_mock = Node.new() - add_child(main_mock) - gridmap_mock = Node.new() - gridmap_mock.name = "EnhancedGridMap" - main_mock.add_child(gridmap_mock) - manager = CandySurvivalManager.new() - main_mock.add_child(manager) - manager.initialize(main_mock, gridmap_mock) - manager.current_phase = 0 - -func after_each(): - if main_mock: - main_mock.queue_free() - -# ============================================================================= -# Registration -# ============================================================================= - -func test_register_buffer_sets_phase_base_penalty(): - manager._register_buffer(Vector2i(5, 5), 40.0) - assert_true(manager.movement_buffers.has(Vector2i(5, 5)), "Buffer registered") - assert_almost_eq(manager.movement_buffers[Vector2i(5, 5)]["penalty"], 40.0, 0.001, "Full penalty stored") - -func test_register_buffer_keeps_strongest(): - manager._register_buffer(Vector2i(5, 5), 20.0) - manager._register_buffer(Vector2i(5, 5), 40.0) - assert_almost_eq(manager.movement_buffers[Vector2i(5, 5)]["penalty"], 40.0, 0.001, "Keeps the stronger penalty") - manager._register_buffer(Vector2i(5, 5), 10.0) - assert_almost_eq(manager.movement_buffers[Vector2i(5, 5)]["penalty"], 40.0, 0.001, "Weaker refresh does not lower it") - -# ============================================================================= -# Penalty lookup (inside / adjacent / none / final-window) -# ============================================================================= - -func test_buffer_penalty_inside_is_full_negative(): - manager._register_buffer(Vector2i(6, 6), 40.0) - assert_almost_eq(manager._buffer_penalty_at(Vector2i(6, 6)), -40.0, 0.001, "Inside buffer = full negative") - -func test_buffer_penalty_adjacent_is_half(): - manager._register_buffer(Vector2i(6, 6), 40.0) - assert_almost_eq(manager._buffer_penalty_at(Vector2i(7, 6)), -20.0, 0.001, "Adjacent buffer = half penalty") - -func test_buffer_penalty_far_is_zero(): - manager._register_buffer(Vector2i(6, 6), 40.0) - assert_eq(manager._buffer_penalty_at(Vector2i(15, 15)), 0.0, "Far from buffer = 0") - -func test_buffer_penalty_lifts_in_final_window(): - manager._register_buffer(Vector2i(6, 6), 40.0) - manager.elapsed_time = manager.candy_survival_round_duration() - 5.0 # within final 30s - assert_eq(manager._buffer_penalty_at(Vector2i(6, 6)), 0.0, "Final window lifts buffers") - -func test_buffer_penalty_empty_is_zero(): - assert_eq(manager._buffer_penalty_at(Vector2i(6, 6)), 0.0, "No buffers = 0") - -# ============================================================================= -# Time decay (−25% every 5s) -# ============================================================================= - -func test_decay_reduces_penalty_after_interval(): - manager._register_buffer(Vector2i(5, 5), 40.0) - manager._decay_movement_buffers(manager.BUFFER_DECAY_INTERVAL) # one full step - assert_almost_eq(manager.movement_buffers[Vector2i(5, 5)]["penalty"], 30.0, 0.001, "−25% after one interval") - -func test_decay_waits_for_full_interval(): - manager._register_buffer(Vector2i(5, 5), 40.0) - manager._decay_movement_buffers(manager.BUFFER_DECAY_INTERVAL * 0.5) # not yet - assert_almost_eq(manager.movement_buffers[Vector2i(5, 5)]["penalty"], 40.0, 0.001, "No decay before interval elapses") - -func test_decay_prunes_faded_buffers(): - manager._register_buffer(Vector2i(5, 5), manager.BUFFER_MIN_PENALTY + 0.5) - manager._decay_movement_buffers(manager.BUFFER_DECAY_INTERVAL) - assert_false(manager.movement_buffers.has(Vector2i(5, 5)), "Faded buffer pruned below BUFFER_MIN_PENALTY") - -# ============================================================================= -# Phase-change decay (−50%) -# ============================================================================= - -func test_phase_change_halves_buffers(): - manager._register_buffer(Vector2i(5, 5), 40.0) - manager._start_phase(manager.Phase.ROUTE_PRESSURE) - assert_almost_eq(manager.movement_buffers[Vector2i(5, 5)]["penalty"], 20.0, 0.001, "Phase change halves penalty") - -# ============================================================================= -# Scoring integration -# ============================================================================= - -func test_score_movement_buffer_uses_detected_corridor(): - # With no players, the proximity floor is inert; a registered buffer still bites. - manager._register_buffer(Vector2i(5, 5), 40.0) - assert_almost_eq(manager._score_movement_buffer(Vector2i(5, 5)), -40.0, 0.001, "Score reflects buffer penalty") - -func test_score_movement_buffer_zero_without_buffers_or_players(): - assert_eq(manager._score_movement_buffer(Vector2i(5, 5)), 0.0, "No buffers, no players = 0") - -# ============================================================================= -# Scale helper -# ============================================================================= - -func test_scale_all_buffers_prunes_and_scales(): - manager._register_buffer(Vector2i(1, 1), 40.0) - manager._register_buffer(Vector2i(2, 2), manager.BUFFER_MIN_PENALTY + 0.1) - manager._scale_all_buffers(0.5) - assert_almost_eq(manager.movement_buffers[Vector2i(1, 1)]["penalty"], 20.0, 0.001, "Scaled by 0.5") - assert_false(manager.movement_buffers.has(Vector2i(2, 2)), "Below-min entry pruned") diff --git a/tests/test_gauntlet_movement_buffer.gd.uid b/tests/test_gauntlet_movement_buffer.gd.uid deleted file mode 100644 index 82c76ba..0000000 --- a/tests/test_gauntlet_movement_buffer.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://4cttae74ja3t diff --git a/tests/test_gauntlet_registration.gd b/tests/test_gauntlet_registration.gd deleted file mode 100644 index 396adf0..0000000 --- a/tests/test_gauntlet_registration.gd +++ /dev/null @@ -1,155 +0,0 @@ -# tests/test_candy_survival_registration.gd -# Tests for Candy Survival] #1 Game Mode Registration -# Validates CANDY_SURVIVAL enum, string conversion, lobby integration, and arena setup - -extends GutTest - -func before_all(): - gut.p("=== Candy Survival Registration Tests Candy Survival #1] ===") - -func after_each(): - pass - -# ============================================================================= -# GameMode Enum Tests -# ============================================================================= - -# Test 1: CANDY_SURVIVAL enum value exists and equals 3 -func test_candy_survival_enum_exists(): - assert_eq(GameMode.Mode.CANDY_SURVIVAL, 3, "CANDY_SURVIVAL should be enum value 3") - -# Test 2: All 3 modes are present in enum -func test_all_modes_in_enum(): - assert_eq(GameMode.Mode.FREEMODE, 0, "FREEMODE should be 0") - assert_eq(GameMode.Mode.STOP_N_GO, 1, "STOP_N_GO should be 1") - assert_eq(GameMode.Mode.CANDY_SURVIVAL, 3, "CANDY_SURVIVAL should be 3") - -# ============================================================================= -# String Conversion Tests -# ============================================================================= - -# Test 3: from_string recognizes "Candy Survival" -func test_from_string_candy_survival(): - var result = GameMode.from_string("Candy Survival") - assert_eq(result, GameMode.Mode.CANDY_SURVIVAL, "from_string should parse 'Candy Survival' as CANDY_SURVIVAL") - -# Test 4: mode_to_string returns "Candy Survival" for CANDY_SURVIVAL -func test_mode_to_string_gauntlet(): - var result = GameMode.mode_to_string(GameMode.Mode.CANDY_SURVIVAL) - assert_eq(result, "Candy Survival", "mode_to_string should return 'Candy Survival'") - -# Test 5: Round-trip conversion is lossless -func test_round_trip_conversion(): - var mode_str = GameMode.mode_to_string(GameMode.Mode.CANDY_SURVIVAL) - var mode_enum = GameMode.from_string(mode_str) - assert_eq(mode_enum, GameMode.Mode.CANDY_SURVIVAL, "Round-trip should preserve CANDY_SURVIVAL") - -# Test 6: All existing modes still round-trip correctly -func test_existing_modes_round_trip(): - for mode in [GameMode.Mode.FREEMODE, GameMode.Mode.STOP_N_GO]: - var s = GameMode.mode_to_string(mode) - var back = GameMode.from_string(s) - assert_eq(back, mode, "Round-trip failed for %s" % s) - -# Test 7: Unknown string defaults to FREEMODE -func test_unknown_string_defaults_freemode(): - var result = GameMode.from_string("NonExistentMode") - assert_eq(result, GameMode.Mode.FREEMODE, "Unknown mode string should default to FREEMODE") - -# ============================================================================= -# get_all_modes Tests -# ============================================================================= - -# Test 8: get_all_modes includes "Candy Survival" -func test_get_all_modes_includes_gauntlet(): - var modes = GameMode.get_all_modes() - assert_has(modes, "Candy Survival", "get_all_modes should include 'Candy Survival'") - -# Test 9: get_all_modes returns exactly 3 entries -func test_get_all_modes_count(): - var modes = GameMode.get_all_modes() - assert_eq(modes.size(), 3, "get_all_modes should return 3 modes") - -# Test 10: get_all_modes order is correct -func test_get_all_modes_order(): - var modes = GameMode.get_all_modes() - assert_eq(modes[0], "Freemode", "First mode should be Freemode") - assert_eq(modes[1], "Stop n Go", "Second mode should be Stop n Go") - assert_eq(modes[2], "Candy Survival", "Third mode should be Candy Survival") - -# ============================================================================= -# is_restricted Tests -# ============================================================================= - -# Test 11: CANDY_SURVIVAL is a restricted mode -func test_candy_survival_is_restricted(): - var result = GameMode.is_restricted(GameMode.Mode.CANDY_SURVIVAL) - assert_true(result, "CANDY_SURVIVAL should be restricted (dedicated arena)") - -# Test 12: FREEMODE is NOT restricted -func test_freemode_not_restricted(): - var result = GameMode.is_restricted(GameMode.Mode.FREEMODE) - assert_false(result, "FREEMODE should not be restricted") - -# Test 13: All restricted modes are confirmed -func test_all_restricted_modes(): - assert_true(GameMode.is_restricted(GameMode.Mode.STOP_N_GO), "STOP_N_GO should be restricted") - assert_true(GameMode.is_restricted(GameMode.Mode.CANDY_SURVIVAL), "CANDY_SURVIVAL should be restricted") - -# ============================================================================= -# LobbyManager Integration Tests -# ============================================================================= - -# Test 14: Lobby available_game_modes includes "Candy Survival" -func test_lobby_modes_includes_gauntlet(): - var modes = LobbyManager.available_game_modes - assert_has(modes, "Candy Survival", "LobbyManager.available_game_modes should include 'Candy Survival'") - -# Test 15: candy_survival_manager.gd script file exists -func test_candy_survival_manager_script_exists(): - var script_exists = ResourceLoader.exists("res://scripts/managers/candy_survival_manager.gd") - assert_true(script_exists, "candy_survival_manager.gd should exist") - -# Test 16: CandySurvivalManager class can be loaded -func test_candy_survival_manager_loads(): - var script = load("res://scripts/managers/candy_survival_manager.gd") - assert_not_null(script, "candy_survival_manager.gd should load without errors") - -# Test 17: CandySurvivalManager has required methods -func test_candy_survival_manager_has_methods(): - var manager = CandySurvivalManager.new() - assert_true(manager.has_method("_setup_arena"), "CandySurvivalManager should have _setup_arena()") - assert_true(manager.has_method("_apply_arena_setup"), "CandySurvivalManager should have _apply_arena_setup()") - assert_true(manager.has_method("start_game_mode"), "CandySurvivalManager should have start_game_mode()") - assert_true(manager.has_method("initialize"), "CandySurvivalManager should have initialize()") - manager.free() - -# Test 18: CandySurvivalManager arena constants are correct -func test_candy_survival_arena_constants(): - assert_eq(CandySurvivalManager.ARENA_COLUMNS, 20, "Arena should be 20 columns") - assert_eq(CandySurvivalManager.ARENA_ROWS, 20, "Arena should be 20 rows") - assert_eq(CandySurvivalManager.NPC_SIZE, 3, "NPC zone should be 3x3") - assert_eq(CandySurvivalManager.NPC_CENTER, Vector2i(9, 9), "NPC center should be at (9,9)") - -# Test 19: NPC zone detection works -func test_npc_zone_detection(): - var manager = CandySurvivalManager.new() - # Center of NPC zone - assert_true(manager._is_npc_zone(Vector2i(9, 9)), "Center (9,9) should be NPC zone") - # Edges of NPC zone - assert_true(manager._is_npc_zone(Vector2i(8, 8)), "Corner (8,8) should be NPC zone") - assert_true(manager._is_npc_zone(Vector2i(10, 10)), "Corner (10,10) should be NPC zone") - # Outside NPC zone - assert_false(manager._is_npc_zone(Vector2i(7, 9)), "Outside (7,9) should NOT be NPC zone") - assert_false(manager._is_npc_zone(Vector2i(11, 9)), "Outside (11,9) should NOT be NPC zone") - assert_false(manager._is_npc_zone(Vector2i(0, 0)), "Corner (0,0) should NOT be NPC zone") - manager.free() - -# Test 20: Phase enum has 3 phases -func test_candy_survival_phases(): - assert_eq(CandySurvivalManager.Phase.OPEN_ARENA, 0, "OPEN_ARENA should be 0") - assert_eq(CandySurvivalManager.Phase.ROUTE_PRESSURE, 1, "ROUTE_PRESSURE should be 1") - assert_eq(CandySurvivalManager.Phase.SURVIVAL_ENDGAME, 2, "SURVIVAL_ENDGAME should be 2") - -func after_all(): - gut.p("=== Candy Survival Registration Tests Complete ===") diff --git a/tests/test_gauntlet_registration.gd.uid b/tests/test_gauntlet_registration.gd.uid deleted file mode 100644 index f745d06..0000000 --- a/tests/test_gauntlet_registration.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://6pn8jkrn2kt diff --git a/tests/test_gauntlet_scoring.gd b/tests/test_gauntlet_scoring.gd deleted file mode 100644 index 0938d2f..0000000 --- a/tests/test_gauntlet_scoring.gd +++ /dev/null @@ -1,185 +0,0 @@ -extends GutTest - -# ============================================================================= -# Test: Candy Survival Candidate Scoring System (v2) Candy Survival #073] -# Covers each score component, camping accumulation, and full-formula -# composition. Runs headless (no multiplayer peer), so elapsed_time = 0 and -# the final-30s window is inactive unless a test sets elapsed_time directly. -# ============================================================================= - -const CandySurvivalManager = preload("res://scripts/managers/candy_survival_manager.gd") -var manager -var main_mock: Node -var gridmap_mock: Node - -func before_each(): - main_mock = Node.new() - add_child(main_mock) - gridmap_mock = Node.new() - gridmap_mock.name = "EnhancedGridMap" - main_mock.add_child(gridmap_mock) - manager = CandySurvivalManager.new() - main_mock.add_child(manager) - manager.initialize(main_mock, gridmap_mock) - manager.current_phase = 0 - -func after_each(): - if main_mock: - main_mock.queue_free() - -# ============================================================================= -# LayerPriority -# ============================================================================= - -func test_layer_priority_matches_phase_weights(): - manager.current_phase = 0 - # Outer ring cell (corner-ish) gets the phase-0 outer weight (+60). - assert_eq(manager._score_layer_priority(Vector2i(2, 2)), 60.0, "Phase 0 outer = +60") - # Inner cell near center gets phase-0 inner weight (-40). - assert_eq(manager._score_layer_priority(Vector2i(9, 8)), -40.0, "Phase 0 inner = -40") - -func test_layer_priority_phase3_inner(): - manager.current_phase = 2 - assert_eq(manager._score_layer_priority(Vector2i(9, 8)), 60.0, "Phase 2 inner = +60") - -# ============================================================================= -# StickyNeighbor -# ============================================================================= - -func test_sticky_neighbor_score_scales(): - assert_eq(manager._score_sticky_neighbor(Vector2i(5, 5)), 0.0, "No neighbors = 0") - manager.sticky_cells[Vector2i(5, 6)] = true - assert_eq(manager._score_sticky_neighbor(Vector2i(5, 5)), 8.0, "One neighbor = +8") - -func test_sticky_neighbor_score_capped(): - # Surround (5,5) on all 8 sides → 8 * 8 = 64, capped at 64. - for dx in range(-1, 2): - for dz in range(-1, 2): - if dx == 0 and dz == 0: - continue - manager.sticky_cells[Vector2i(5 + dx, 5 + dz)] = true - assert_eq(manager._score_sticky_neighbor(Vector2i(5, 5)), 64.0, "Capped at +64") - -# ============================================================================= -# InwardPressure -# ============================================================================= - -func test_inward_pressure_higher_near_center(): - manager.current_phase = 2 - var near = manager._score_inward_pressure(Vector2i(8, 8)) # close to center - var far = manager._score_inward_pressure(Vector2i(1, 1)) # far corner - assert_true(near > far, "Inward pressure stronger near center") - -func test_inward_pressure_phase_scaling(): - # Same cell, later phase => higher inward pressure ceiling. - var pos := Vector2i(8, 8) - manager.current_phase = 0 - var p0 = manager._score_inward_pressure(pos) - manager.current_phase = 2 - var p2 = manager._score_inward_pressure(pos) - assert_true(p2 > p0, "Later phase pushes inward harder") - -# ============================================================================= -# PlayerPressure -# ============================================================================= - -func test_player_pressure_ring(): - # 3 cells from a player → +20. - var players = [Vector2i(5, 5)] - assert_eq(manager._score_player_pressure(Vector2i(8, 5), players), 20.0, "2-4 cells away = +20") - -func test_player_pressure_under_player_penalized(): - var players = [Vector2i(5, 5)] - # elapsed_time 0, round 180 → not final window → directly under = -50. - assert_eq(manager._score_player_pressure(Vector2i(5, 5), players), -50.0, "Under player (early) = -50") - -func test_player_pressure_under_player_final_window(): - manager.elapsed_time = manager.candy_survival_round_duration() - 5.0 # within final 30s - var players = [Vector2i(5, 5)] - assert_eq(manager._score_player_pressure(Vector2i(5, 5), players), 10.0, "Under player (final) = +10") - -func test_player_pressure_no_players(): - assert_eq(manager._score_player_pressure(Vector2i(5, 5), []), 0.0, "No players = 0") - -# ============================================================================= -# ClusterGrowth -# ============================================================================= - -func test_cluster_growth_none(): - assert_eq(manager._score_cluster_growth(Vector2i(5, 5)), 0.0, "No sticky neighbors = 0") - -func test_cluster_growth_expand(): - manager.sticky_cells[Vector2i(5, 6)] = true - assert_eq(manager._score_cluster_growth(Vector2i(5, 5)), 15.0, "Expanding cluster = +15") - -func test_cluster_growth_connect(): - manager.sticky_cells[Vector2i(4, 5)] = true - manager.sticky_cells[Vector2i(6, 5)] = true - manager.sticky_cells[Vector2i(5, 6)] = true - assert_eq(manager._score_cluster_growth(Vector2i(5, 5)), 25.0, "Connecting clusters = +25") - -# ============================================================================= -# CampingPressure -# ============================================================================= - -func test_camp_region_grouping(): - assert_eq(manager._region_of(Vector2i(0, 0)), Vector2i(0, 0), "Cells 0-3 → region 0") - assert_eq(manager._region_of(Vector2i(5, 7)), Vector2i(1, 1), "Cells 4-7 → region 1") - -func test_camping_pressure_thresholds(): - var region: Vector2i = manager._region_of(Vector2i(8, 8)) - manager._camp_tracking[1] = {"region": region, "time": 6.0} - assert_eq(manager._score_camping_pressure(Vector2i(8, 8)), 20.0, ">5s = +20") - manager._camp_tracking[1]["time"] = 9.0 - assert_eq(manager._score_camping_pressure(Vector2i(8, 8)), 40.0, ">8s = +40") - manager._camp_tracking[1]["time"] = 11.0 - assert_eq(manager._score_camping_pressure(Vector2i(8, 8)), 60.0, ">10s = +60") - -func test_camping_pressure_none(): - assert_eq(manager._score_camping_pressure(Vector2i(8, 8)), 0.0, "No camping = 0") - -# ============================================================================= -# Repetition -# ============================================================================= - -func test_repetition_penalty(): - manager._last_tick_cells = [Vector2i(5, 5)] - assert_eq(manager._score_repetition(Vector2i(5, 6)), -30.0, "Adjacent to last tick = -30") - assert_eq(manager._score_repetition(Vector2i(15, 15)), 0.0, "Far from last tick = 0") - -# ============================================================================= -# PathSafety (soft penalty) -# ============================================================================= - -func test_path_safety_no_players_no_penalty(): - assert_eq(manager._score_path_safety(Vector2i(5, 5)), 0.0, "No players = no penalty") - -# ============================================================================= -# Camp tracking accumulation -# ============================================================================= - -func test_camp_time_for_region_picks_max(): - var region := Vector2i(1, 1) - manager._camp_tracking[1] = {"region": region, "time": 3.0} - manager._camp_tracking[2] = {"region": region, "time": 7.0} - assert_almost_eq(manager._camp_time_for_region(region), 7.0, 0.001, "Longest camp time wins") - -# ============================================================================= -# Full formula composition -# ============================================================================= - -func test_full_score_runs_and_is_finite(): - var s = manager._calculate_candidate_score(Vector2i(5, 5), []) - assert_true(is_finite(s), "Full score is a finite number") - -func test_full_score_rewards_sticky_cluster(): - # A cell hugging an existing cluster should generally beat an isolated one. - # Average several samples to wash out RandomNoise (±20). - manager.sticky_cells[Vector2i(5, 6)] = true - manager.sticky_cells[Vector2i(6, 5)] = true - var clustered := 0.0 - var isolated := 0.0 - for _i in range(40): - clustered += manager._calculate_candidate_score(Vector2i(5, 5), []) - isolated += manager._calculate_candidate_score(Vector2i(15, 15), []) - assert_true(clustered > isolated, "Clustered cell scores higher on average") diff --git a/tests/test_gauntlet_scoring.gd.uid b/tests/test_gauntlet_scoring.gd.uid deleted file mode 100644 index 0cef456..0000000 --- a/tests/test_gauntlet_scoring.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://tugcu571care diff --git a/tests/test_gauntlet_sticky_system.gd b/tests/test_gauntlet_sticky_system.gd deleted file mode 100644 index 5d84cda..0000000 --- a/tests/test_gauntlet_sticky_system.gd +++ /dev/null @@ -1,208 +0,0 @@ -extends GutTest - -# ============================================================================= -# Test: Candy Survival Sticky Cell System (v2) Candy Survival #068] -# Covers cell states, CLEANSED protection, coverage helpers, and the -# path-safety reachability (BFS) check. -# ============================================================================= - -var CandySurvivalManagerScript = load("res://scripts/managers/candy_survival_manager.gd") -var manager: CandySurvivalManager - -func before_each(): - manager = CandySurvivalManagerScript.new() - add_child(manager) - -func after_each(): - manager.queue_free() - -# ============================================================================= -# CellState enum -# ============================================================================= - -func test_cellstate_enum_has_six_states(): - assert_eq(manager.CellState.size(), 6, "CellState should have 6 states") - -func test_cellstate_values(): - assert_eq(manager.CellState.SAFE, 0, "SAFE should be 0") - assert_eq(manager.CellState.TELEGRAPHED, 1, "TELEGRAPHED should be 1") - assert_eq(manager.CellState.STICKY, 2, "STICKY should be 2") - assert_eq(manager.CellState.BUBBLE_GROWING, 3, "BUBBLE_GROWING should be 3") - assert_eq(manager.CellState.BLOCKED, 4, "BLOCKED should be 4") - assert_eq(manager.CellState.CLEANSED, 5, "CLEANSED should be 5") - -# ============================================================================= -# cell_state() classification -# ============================================================================= - -func test_interior_cell_is_safe(): - assert_eq(manager.cell_state(Vector2i(3, 3)), manager.CellState.SAFE, "Open interior cell is SAFE") - -func test_npc_zone_is_blocked(): - assert_eq(manager.cell_state(Vector2i(9, 9)), manager.CellState.BLOCKED, "NPC zone is BLOCKED") - -func test_boundary_is_blocked(): - assert_eq(manager.cell_state(Vector2i(0, 5)), manager.CellState.BLOCKED, "Boundary is BLOCKED") - assert_eq(manager.cell_state(Vector2i(19, 5)), manager.CellState.BLOCKED, "Far boundary is BLOCKED") - -func test_sticky_cell_state(): - manager.sticky_cells[Vector2i(4, 4)] = true - assert_eq(manager.cell_state(Vector2i(4, 4)), manager.CellState.STICKY, "Sticky cell is STICKY") - -func test_telegraphed_cell_state(): - manager.telegraphed_cells[Vector2i(5, 5)] = 1.0 - assert_eq(manager.cell_state(Vector2i(5, 5)), manager.CellState.TELEGRAPHED, "Telegraphed cell is TELEGRAPHED") - -func test_cleansed_cell_state(): - manager.mark_cleansed(Vector2i(6, 6)) - assert_eq(manager.cell_state(Vector2i(6, 6)), manager.CellState.CLEANSED, "Cleansed cell is CLEANSED") - -func test_sticky_takes_priority_over_cleansed(): - # A cell that is both should report STICKY (active hazard wins). - manager.sticky_cells[Vector2i(7, 7)] = true - manager.cleansed_cells[Vector2i(7, 7)] = 5.0 - assert_eq(manager.cell_state(Vector2i(7, 7)), manager.CellState.STICKY, "Sticky wins over cleansed") - -# ============================================================================= -# CLEANSED protection lifecycle -# ============================================================================= - -func test_mark_cleansed_sets_protection_time(): - manager.mark_cleansed(Vector2i(3, 4)) - assert_true(manager.is_cleansed_cell(Vector2i(3, 4)), "Cell should be cleansed") - assert_almost_eq(manager.cleansed_cells[Vector2i(3, 4)], manager.CLEANSED_PROTECTION_TIME, 0.001, "Protection time set") - -func test_clear_sticky_marks_cleansed(): - manager.sticky_cells[Vector2i(4, 5)] = true - manager.clear_sticky_cell(Vector2i(4, 5)) - assert_false(manager.is_sticky_cell(Vector2i(4, 5)), "Sticky removed") - assert_true(manager.is_cleansed_cell(Vector2i(4, 5)), "Cleared cell becomes cleansed") - -func test_tick_cleansed_decays_and_expires(): - manager.mark_cleansed(Vector2i(5, 6)) - manager._tick_cleansed_cells(manager.CLEANSED_PROTECTION_TIME + 0.1) - assert_false(manager.is_cleansed_cell(Vector2i(5, 6)), "Protection expires after full duration") - -func test_tick_cleansed_partial_decay_keeps_cell(): - manager.mark_cleansed(Vector2i(5, 7)) - manager._tick_cleansed_cells(1.0) - assert_true(manager.is_cleansed_cell(Vector2i(5, 7)), "Cell still protected after partial tick") - -# ============================================================================= -# Coverage helpers (v2 target 70-75%) -# ============================================================================= - -func test_coverage_targets(): - assert_almost_eq(manager.COVERAGE_TARGET_MIN, 0.70, 0.001, "Min coverage 70%") - assert_almost_eq(manager.COVERAGE_TARGET_MAX, 0.75, 0.001, "Max coverage 75%") - -func test_playable_cell_count(): - # 20x20 = 400, minus 76 boundary cells, minus 9 NPC zone = 315 - assert_eq(manager.playable_cell_count(), 315, "Playable cells = 315") - -func test_coverage_ratio_zero_when_empty(): - assert_almost_eq(manager.coverage_ratio(), 0.0, 0.001, "No sticky cells = 0 coverage") - -func test_coverage_ratio_scales(): - var playable := manager.playable_cell_count() - # Fill ~half the playable cells with arbitrary distinct keys. - var half := int(playable / 2.0) - for i in range(half): - manager.sticky_cells[Vector2i(1000 + i, 0)] = true - assert_almost_eq(manager.coverage_ratio(), float(half) / float(playable), 0.001, "Coverage tracks ratio") - -func test_coverage_reached_threshold(): - var playable := manager.playable_cell_count() - var needed := int(ceil(playable * manager.COVERAGE_TARGET_MIN)) - for i in range(needed): - manager.sticky_cells[Vector2i(2000 + i, 0)] = true - assert_true(manager.is_coverage_reached(), "Coverage reached at >=70%") - -func test_coverage_not_reached_below_threshold(): - manager.sticky_cells[Vector2i(2, 2)] = true - assert_false(manager.is_coverage_reached(), "One sticky cell is below target") - -# ============================================================================= -# Path safety: passability + reachability (BFS) -# ============================================================================= - -func test_passable_interior(): - assert_true(manager._is_cell_passable(Vector2i(3, 3)), "Open interior is passable") - -func test_not_passable_boundary_or_npc(): - assert_false(manager._is_cell_passable(Vector2i(0, 0)), "Boundary not passable") - assert_false(manager._is_cell_passable(Vector2i(9, 9)), "NPC zone not passable") - -func test_not_passable_sticky(): - manager.sticky_cells[Vector2i(3, 3)] = true - assert_false(manager._is_cell_passable(Vector2i(3, 3)), "Sticky cell not passable") - -func test_extra_sticky_blocks_passability(): - var extra := {Vector2i(4, 4): true} - assert_false(manager._is_cell_passable(Vector2i(4, 4), extra), "Hypothetical sticky blocks") - assert_true(manager._is_cell_passable(Vector2i(5, 5), extra), "Other cells still passable") - -func test_open_arena_has_large_safe_region(): - # From an open interior cell, flood fill should easily exceed the minimum. - var n := manager._reachable_safe_cells(Vector2i(3, 3), {}, 50) - assert_true(n >= 50, "Open arena reaches the search cap") - -func test_player_has_safe_region_when_open(): - assert_true(manager._player_has_safe_region(Vector2i(3, 3), {}), "Open cell has safe region") - -func test_fully_boxed_player_has_no_safe_region(): - # Box in the cell at (3,3) on all 4 sides with hypothetical sticky. - var extra := { - Vector2i(2, 3): true, Vector2i(4, 3): true, - Vector2i(3, 2): true, Vector2i(3, 4): true, - } - assert_false(manager._player_has_safe_region(Vector2i(3, 3), extra), "Boxed-in player has no safe region") - -func test_reachable_zero_when_start_blocked(): - manager.sticky_cells[Vector2i(3, 3)] = true - assert_eq(manager._reachable_safe_cells(Vector2i(3, 3), {}, 10), 0, "Blocked start reaches nothing") - -# ============================================================================= -# Sticky entry → per-player slow (v2: no hard trap, no global time_scale) -# ============================================================================= - -# Minimal stand-in for a Player that records apply_slow_effect calls. -class SlowSpyPlayer: - extends Node - var slow_calls: Array = [] - func apply_slow_effect(duration: float = 3.0) -> void: - slow_calls.append(duration) - -func test_apply_sticky_slow_calls_player_slow(): - var spy := SlowSpyPlayer.new() - add_child(spy) - # Force the local-call branch (no networked rpc) for a deterministic unit test. - var saved_peer = multiplayer.multiplayer_peer - multiplayer.multiplayer_peer = null - manager.apply_sticky_slow(spy) - multiplayer.multiplayer_peer = saved_peer - assert_eq(spy.slow_calls.size(), 1, "Sticky slow invokes apply_slow_effect once") - assert_almost_eq(spy.slow_calls[0], manager.STICKY_SLOW_DURATION, 0.001, "Slows for STICKY_SLOW_DURATION") - spy.queue_free() - -func test_apply_sticky_slow_does_not_trap(): - var spy := SlowSpyPlayer.new() - spy.set("peer_id", 42) - add_child(spy) - var saved_peer = multiplayer.multiplayer_peer - multiplayer.multiplayer_peer = null - manager.apply_sticky_slow(spy) - multiplayer.multiplayer_peer = saved_peer - assert_false(manager.trapped_players.has(42), "Sticky slow never adds to trapped_players") - spy.queue_free() - -func test_apply_sticky_slow_safe_without_method(): - # A node lacking apply_slow_effect must not crash the call. - var plain := Node.new() - add_child(plain) - manager.apply_sticky_slow(plain) # should no-op - assert_true(true, "apply_sticky_slow tolerates players without the method") - plain.queue_free() - -func test_sticky_slow_duration_is_positive(): - assert_true(manager.STICKY_SLOW_DURATION > 0.0, "Sticky slow duration is a positive number") diff --git a/tests/test_gauntlet_sticky_system.gd.uid b/tests/test_gauntlet_sticky_system.gd.uid deleted file mode 100644 index f83d17f..0000000 --- a/tests/test_gauntlet_sticky_system.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://csco4t66gq5et diff --git a/tests/test_gauntlet_tile_spawning.gd b/tests/test_gauntlet_tile_spawning.gd deleted file mode 100644 index c057997..0000000 --- a/tests/test_gauntlet_tile_spawning.gd +++ /dev/null @@ -1,144 +0,0 @@ -extends GutTest - -# ============================================================================= -# Test: Candy Survival Tile Spawning & Mission System (Task #3) -# ============================================================================= - -var CandySurvivalManagerScript = load("res://scripts/managers/candy_survival_manager.gd") -var manager: CandySurvivalManager - -func before_each(): - manager = CandySurvivalManagerScript.new() - add_child(manager) - -func after_each(): - manager.queue_free() - -# ============================================================================= -# Arena Constants -# ============================================================================= - -func test_arena_size_20x20(): - assert_eq(manager.ARENA_COLUMNS, 20, "Arena should be 20 columns") - assert_eq(manager.ARENA_ROWS, 20, "Arena should be 20 rows") - -func test_npc_center_position(): - assert_eq(manager.NPC_CENTER, Vector2i(9, 9), "NPC center should be at (9,9)") - -func test_npc_size_3x3(): - assert_eq(manager.NPC_SIZE, 3, "NPC zone should be 3x3") - -# ============================================================================= -# NPC Zone Exclusion -# ============================================================================= - -func test_npc_zone_center_is_excluded(): - assert_true(manager._is_npc_zone(Vector2i(9, 9)), "Center (9,9) should be NPC zone") - -func test_npc_zone_corners_are_excluded(): - assert_true(manager._is_npc_zone(Vector2i(8, 8)), "Top-left (8,8) should be NPC zone") - assert_true(manager._is_npc_zone(Vector2i(10, 8)), "Top-right (10,8) should be NPC zone") - assert_true(manager._is_npc_zone(Vector2i(8, 10)), "Bottom-left (8,10) should be NPC zone") - assert_true(manager._is_npc_zone(Vector2i(10, 10)), "Bottom-right (10,10) should be NPC zone") - -func test_outside_npc_zone_not_excluded(): - assert_false(manager._is_npc_zone(Vector2i(7, 9)), "Left of NPC zone should NOT be excluded") - assert_false(manager._is_npc_zone(Vector2i(11, 9)), "Right of NPC zone should NOT be excluded") - assert_false(manager._is_npc_zone(Vector2i(9, 7)), "Above NPC zone should NOT be excluded") - assert_false(manager._is_npc_zone(Vector2i(9, 11)), "Below NPC zone should NOT be excluded") - -func test_arena_corners_not_excluded(): - assert_false(manager._is_npc_zone(Vector2i(0, 0)), "Top-left corner should be walkable") - assert_false(manager._is_npc_zone(Vector2i(19, 0)), "Top-right corner should be walkable") - assert_false(manager._is_npc_zone(Vector2i(0, 19)), "Bottom-left corner should be walkable") - assert_false(manager._is_npc_zone(Vector2i(19, 19)), "Bottom-right corner should be walkable") - -func test_npc_zone_total_cells(): - var npc_count = 0 - for x in range(manager.ARENA_COLUMNS): - for z in range(manager.ARENA_ROWS): - if manager._is_npc_zone(Vector2i(x, z)): - npc_count += 1 - assert_eq(npc_count, 9, "NPC zone should occupy exactly 9 cells (3x3)") - -func test_walkable_cells_count(): - # 20x20 = 400 total, minus 9 NPC = 391 walkable - var walkable = 400 - 9 - assert_eq(walkable, 391, "Should have 391 walkable cells") - -# ============================================================================= -# Tile Constants -# ============================================================================= - -func test_goal_tile_ids_valid(): - # Heart(7), Diamond(8), Star(9), Coin(10) — match StopNGoManager - var goal_items = [7, 8, 9, 10] - for item in goal_items: - assert_gt(item, 0, "Goal tile ID %d should be positive" % item) - assert_lt(item, 17, "Goal tile ID %d should not conflict with sticky(17)" % item) - -func test_tile_walkable_id(): - assert_eq(manager.TILE_WALKABLE, 0, "Walkable tile should be ID 0") - -func test_tile_obstacle_id(): - assert_eq(manager.TILE_OBSTACLE, 4, "Obstacle tile should be ID 4") - -func test_tile_sticky_id(): - assert_eq(manager.TILE_STICKY, 17, "Sticky tile should be ID 17") - -func test_tile_telegraph_id(): - assert_eq(manager.TILE_TELEGRAPH, 18, "Telegraph tile should be ID 18") - -# ============================================================================= -# Method Existence -# ============================================================================= - -func test_setup_mission_tiles_exists(): - assert_true(manager.has_method("setup_mission_tiles"), "Should have setup_mission_tiles()") - -func test_spawn_mission_tiles_exists(): - assert_true(manager.has_method("_spawn_mission_tiles"), "Should have _spawn_mission_tiles()") - -# ============================================================================= -# Sticky Cell System -# ============================================================================= - -func test_sticky_cells_initially_empty(): - assert_eq(manager.sticky_cells.size(), 0, "Sticky cells should start empty") - -func test_is_sticky_cell_false_for_clean(): - assert_false(manager.is_sticky_cell(Vector2i(5, 5)), "Clean cell should not be sticky") - -func test_is_sticky_cell_true_after_marking(): - manager.sticky_cells[Vector2i(5, 5)] = true - assert_true(manager.is_sticky_cell(Vector2i(5, 5)), "Marked cell should be sticky") - -func test_clear_sticky_cell(): - manager.sticky_cells[Vector2i(3, 3)] = true - manager.clear_sticky_cell(Vector2i(3, 3)) - assert_false(manager.is_sticky_cell(Vector2i(3, 3)), "Cleared cell should no longer be sticky") - -# ============================================================================= -# Phase Interaction with Tile Spawning -# ============================================================================= - -func test_initial_phase_is_open_arena(): - assert_eq(manager.current_phase, CandySurvivalManager.Phase.OPEN_ARENA, "Should start in Open Arena") - -func test_phase_to_string_open_arena(): - assert_eq(manager._phase_to_string(CandySurvivalManager.Phase.OPEN_ARENA), "Outer Pressure") - -func test_phase_to_string_route_pressure(): - assert_eq(manager._phase_to_string(CandySurvivalManager.Phase.ROUTE_PRESSURE), "Middle Pressure") - -func test_phase_to_string_survival(): - assert_eq(manager._phase_to_string(CandySurvivalManager.Phase.SURVIVAL_ENDGAME), "Inner Survival") - -# ============================================================================= -# Match Timer Integration -# ============================================================================= - -func test_match_duration_180s(): - # Candy Survival uses 180s match (3 phases: 0-60, 60-120, 120-180) - var total = manager.PHASE_3_START + 60.0 # Phase 3 starts at 120, runs 60s - assert_eq(total, 180.0, "Total match should be 180 seconds") diff --git a/tests/test_gauntlet_tile_spawning.gd.uid b/tests/test_gauntlet_tile_spawning.gd.uid deleted file mode 100644 index 892bb58..0000000 --- a/tests/test_gauntlet_tile_spawning.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://cpwhlklp7jkkg