extends GutTest # ============================================================================= # Test: Gauntlet Growth Tick System (v2) [Gauntlet #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 GauntletManager = preload("res://scripts/managers/gauntlet_manager.gd") var gauntlet_manager: Node var main_mock: Node var gridmap_mock: Node func before_all(): gut.p("=== Feature Tests [Gauntlet #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) gauntlet_manager = GauntletManager.new() main_mock.add_child(gauntlet_manager) gauntlet_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(gauntlet_manager.growth_timer, 0.0, "Growth timer starts at 0.0") func test_growth_interval_default(): assert_eq(gauntlet_manager.growth_interval, 3.0, "Growth interval defaults to 3.0s") func test_telegraph_duration_default(): assert_eq(gauntlet_manager.telegraph_duration, 1.0, "Telegraph duration defaults to 1.0s") # ============================================================================= # Phase Growth Config # ============================================================================= func test_phase_growth_config_has_three_phases(): assert_eq(gauntlet_manager.phase_growth_config.size(), 3, "Three phase growth configs") func test_phase1_cells_range(): var cfg = gauntlet_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 = gauntlet_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 = gauntlet_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): gauntlet_manager.current_phase = phase var cfg = gauntlet_manager.phase_growth_config[phase] # Sample several times since the count is randomized. for _i in range(20): var n = gauntlet_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(): gauntlet_manager.current_phase = 0 var candidates = gauntlet_manager._generate_candidates() # Fresh arena: every playable cell is SAFE. assert_eq(candidates.size(), gauntlet_manager.playable_cell_count(), "All playable cells are candidates on a fresh arena") func test_candidates_exclude_sticky(): gauntlet_manager.sticky_cells[Vector2i(3, 3)] = true var candidates = gauntlet_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(): gauntlet_manager.mark_cleansed(Vector2i(4, 4)) var candidates = gauntlet_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 = gauntlet_manager._generate_candidates() for c in candidates: var p = c["pos"] assert_false(gauntlet_manager._is_npc_zone(p), "No NPC-zone candidates") assert_false(gauntlet_manager._is_boundary(p), "No boundary candidates") func test_candidates_have_scores(): var candidates = gauntlet_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 = gauntlet_manager._generate_candidates() var picked = gauntlet_manager._select_cells_weighted(candidates, 5) assert_eq(picked.size(), 5, "Selects exactly the requested count") func test_select_no_duplicates(): var candidates = gauntlet_manager._generate_candidates() var picked = gauntlet_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 = gauntlet_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(gauntlet_manager._layer_of(Vector2i(9, 9)), "inner", "Center is inner") assert_eq(gauntlet_manager._layer_of(Vector2i(1, 1)), "outer", "Corner is outer") func test_sticky_neighbor_count(): gauntlet_manager.sticky_cells[Vector2i(5, 5)] = true gauntlet_manager.sticky_cells[Vector2i(5, 6)] = true assert_eq(gauntlet_manager._sticky_neighbor_count(Vector2i(6, 5)), 2, "Counts 8-directional sticky neighbors") func test_chebyshev(): assert_eq(gauntlet_manager._chebyshev(Vector2i(0, 0), Vector2i(3, 1)), 3, "Chebyshev distance")