extends SceneTree const PathServiceScript = preload("res://scripts/world/path_service.gd") var _checks := 0 var _failures := 0 func _initialize() -> void: call_deferred("_run") func _run() -> void: print("S1: deterministic PathService") _test_deterministic_tie_break() _test_layer_separation_and_connector() _test_connector_access_and_direction() _test_cache_invalidation_and_revision() _test_connector_only_snapshot() _test_cross_layer_reason_and_live_changes() if _failures == 0: print("PASS: %d checks" % _checks) else: push_error("FAIL: %d of %d checks failed" % [_failures, _checks]) quit(_failures) func _test_deterministic_tie_break() -> void: var service = PathServiceScript.new() var passable := [ Vector3i(0, 0, 0), Vector3i(1, 0, 0), Vector3i(2, 0, 0), Vector3i(0, 1, 0), Vector3i(2, 1, 0), Vector3i(0, 2, 0), Vector3i(1, 2, 0), Vector3i(2, 2, 0), ] _expect(service.rebuild_graph(passable), "array of Vector3i cells rebuilds the path graph") var expected: Array[Vector3i] = [ Vector3i(0, 1, 0), Vector3i(0, 0, 0), Vector3i(1, 0, 0), Vector3i(2, 0, 0), Vector3i(2, 1, 0), ] var first: Array[Vector3i] = service.find_path(Vector3i(0, 1, 0), Vector3i(2, 1, 0), service.FRONT) var second: Array[Vector3i] = service.find_path(Vector3i(0, 1, 0), Vector3i(2, 1, 0), service.FRONT) _expect(first == expected, "equal-cost routes use coordinate dictionary order as the tie break") _expect(second == first, "the cached route exactly reproduces the calculated route") _expect(service.cache_hits == 1 and service.cache_misses == 1, "path cache records one miss followed by one hit") func _test_layer_separation_and_connector() -> void: var service = PathServiceScript.new() var passable_set := {} for layer in [service.FRONT, service.BACK]: for x in range(3): passable_set[Vector3i(x, 0, layer)] = true _expect(service.rebuild_graph(passable_set), "Dictionary set can supply passable cells without a WorldService dependency") _expect( service.find_path(Vector3i(0, 0, 0), Vector3i(2, 0, 1)).is_empty(), "FRONT and BACK never connect through ordinary cardinal edges" ) _expect( service.configure_connector( &"service_door", Vector3i(2, 0, 0), Vector3i(0, 0, 1), 120, true ), "an explicit layer connector is accepted" ) var connected_path: Array[Vector3i] = service.find_path(Vector3i(0, 0, 0), Vector3i(2, 0, 1)) _expect( connected_path.has(Vector3i(2, 0, 0)) and connected_path.has(Vector3i(0, 0, 1)), "an explicit connector joins the two isolated layers" ) _expect( service.find_path(Vector3i(0, 0, 0), Vector3i(2, 0, 1), service.FRONT).is_empty(), "an access-layer restriction rejects a route that enters BACK" ) func _test_connector_access_and_direction() -> void: var service = PathServiceScript.new() var cells := [Vector3i(0, 0, 0), Vector3i(0, 2, 0)] service.rebuild_graph(cells) service.configure_connector( &"staff_stairs", Vector3i(0, 0, 0), Vector3i(0, 2, 0), 150, false, &"staff" ) _expect( service.find_path(Vector3i(0, 0, 0), Vector3i(0, 2, 0)).is_empty(), "a restricted connector requires its access id" ) var staff_profile := { &"allowed_layers": [service.FRONT], &"connector_access_ids": [&"staff"], } _expect( service.find_path(Vector3i(0, 0, 0), Vector3i(0, 2, 0), staff_profile).size() == 2, "an access profile enables its matching connector" ) _expect( service.find_path(Vector3i(0, 2, 0), Vector3i(0, 0, 0), staff_profile).is_empty(), "a one-way connector cannot be traversed in reverse" ) func _test_cache_invalidation_and_revision() -> void: var service = PathServiceScript.new() var passable := [ Vector3i(0, 0, 0), Vector3i(1, 0, 0), Vector3i(2, 0, 0), Vector3i(0, 1, 0), Vector3i(2, 1, 0), Vector3i(0, 2, 0), Vector3i(1, 2, 0), Vector3i(2, 2, 0), ] service.rebuild_graph(passable) var initial_revision: int = service.graph_revision service.find_path(Vector3i(0, 1, 0), Vector3i(2, 1, 0)) _expect(service.cached_path_count() == 1, "a calculated path is cached") _expect( service.invalidate_cache_for_cells([Vector3i(1, 0, 0)]) == 1, "local invalidation removes a cached path that crosses an affected cell" ) _expect(service.cached_path_count() == 0, "local invalidation leaves no matching stale cache entry") service.find_path(Vector3i(0, 1, 0), Vector3i(2, 1, 0)) _expect(service.invalidate_all_cache() == 1, "full invalidation reports and removes every cache entry") passable.erase(Vector3i(1, 0, 0)) service.rebuild_graph(passable) _expect(service.graph_revision == initial_revision + 1, "a changed topology advances graph_revision exactly once") var rebuilt_path: Array[Vector3i] = service.find_path(Vector3i(0, 1, 0), Vector3i(2, 1, 0)) _expect(rebuilt_path.has(Vector3i(1, 2, 0)), "rebuild discards the old route and uses the remaining corridor") func _test_connector_only_snapshot() -> void: var cells := [Vector3i(0, 0, 0), Vector3i(0, 0, 1)] var original = PathServiceScript.new() original.rebuild_graph(cells) original.configure_connector( &"lift_a", Vector3i(0, 0, 0), Vector3i(0, 0, 1), 250, true, &"lift_pass" ) original.find_path( Vector3i(0, 0, 0), Vector3i(0, 0, 1), null, [&"lift_pass"] ) var snapshot: Dictionary = original.create_snapshot() _expect( snapshot.has(&"connectors") and not snapshot.has(&"graph") and not snapshot.has(&"cache") and not snapshot.has(&"passable_cells"), "snapshot persists connector configuration but no derived graph or cache" ) var restored = PathServiceScript.new() _expect(restored.restore_snapshot(snapshot), "connector configuration restores independently of world cells") _expect(restored.rebuild_graph(cells), "restored service rebuilds its graph from authoritative passable cells") _expect( restored.find_path( Vector3i(0, 0, 0), Vector3i(0, 0, 1), null, [&"lift_pass"] ).size() == 2, "restored connector produces the same cross-layer route after rebuild" ) func _test_cross_layer_reason_and_live_changes() -> void: var service = PathServiceScript.new() var start := Vector3i(0, 0, 0) var bridge_front := Vector3i(1, 0, 0) var bridge_back := Vector3i(1, 0, 1) var goal := Vector3i(2, 0, 1) service.rebuild_graph([start,bridge_front,bridge_back,goal]) service.configure_connector(&"staff_door",bridge_front,bridge_back,120,false,&"staff") _expect(service.find_path_report(start,goal)[&"reason"] == service.ROUTE_GOAL_UNREACHABLE,"guest cannot cross a staff-only layer door") var forward: Array[Vector3i] = service.find_path(start,goal,null,[&"staff"]) _expect(forward == [start,bridge_front,bridge_back,goal],"staff retains the exact forward cross-layer path") _expect(service.find_path_report(goal,start,null,[&"staff"])[&"reason"] == service.ROUTE_GOAL_UNREACHABLE,"one-way layer door remains unreachable in reverse") service.set_connector_enabled(&"staff_door",false) _expect(service.find_path_report(start,goal,null,[&"staff"])[&"reason"] == service.ROUTE_CONNECTOR_UNAVAILABLE,"disabled permitted crossing retains connector-unavailable diagnosis") service.set_connector_enabled(&"staff_door",true) service.set_cell_temporary_block(bridge_front,true) _expect(service.find_path_report(start,goal,null,[&"staff"])[&"reason"] == service.ROUTE_TEMPORARILY_BLOCKED,"blocked permitted crossing retains temporary-block diagnosis") service.set_cell_temporary_block(start,true) _expect(service.find_path_report(start,goal,null,[&"staff"])[&"reason"] == service.ROUTE_START_BLOCKED,"blocked endpoint precedence is preserved") _expect(service.find_path_report(start,goal,service.FRONT,[&"staff"])[&"reason"] == service.ROUTE_ACCESS_LAYER_DENIED,"layer restriction still precedes blocked endpoint") service.set_cell_temporary_block(start,false) service.set_cell_temporary_block(bridge_front,false) _expect(service.find_path(start,goal,null,[&"staff"]) == forward,"reenabled and unblocked crossing restores exact path") service.configure_connector(&"staff_door",bridge_front,bridge_back,120,true,&"staff") _expect(service.find_path(goal,start,null,[&"staff"]) == [goal,bridge_back,bridge_front,start],"current bidirectional configuration restores the reverse path") service.remove_connector(&"staff_door") _expect(service.find_path_report(start,goal,null,[&"staff"])[&"reason"] == service.ROUTE_GOAL_UNREACHABLE,"connector removal immediately restores unreachable result") func _expect(condition: bool, label: String) -> void: _checks += 1 if condition: print(" ✓ %s" % label) return _failures += 1 push_error(" ✗ %s" % label)