Preserve approved pixel tower prototype as development baseline
This commit is contained in:
@@ -0,0 +1,182 @@
|
||||
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()
|
||||
|
||||
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 _expect(condition: bool, label: String) -> void:
|
||||
_checks += 1
|
||||
if condition:
|
||||
print(" ✓ %s" % label)
|
||||
return
|
||||
_failures += 1
|
||||
push_error(" ✗ %s" % label)
|
||||
Reference in New Issue
Block a user