Add explicit stairs and measure elevator congestion with real actors
This commit is contained in:
@@ -101,7 +101,7 @@ func _test_snapshot_validation_and_legacy() -> void:
|
||||
path.set_vertical_walk_blockers(&"shaft.a", [cells[0], cells[1]])
|
||||
path.set_vertical_walk_blockers(&"shaft.b", [cells[2]])
|
||||
var snapshot := path.create_snapshot()
|
||||
_expect(snapshot[&"schema_version"] == 4, "Path v4 declares shaft ownership")
|
||||
_expect(snapshot[&"schema_version"] == 5, "Path v5 retains shaft ownership and explicit floor policy")
|
||||
var restored := PathType.new()
|
||||
_expect(restored.restore_snapshot(snapshot) and restored.rebuild_graph(cells), "restore shaft topology before authoritative graph materialization")
|
||||
_expect(restored.create_snapshot() == snapshot and restored.find_path(cells[0], cells[1]).is_empty(), "round trip restores exact restrictions and revision")
|
||||
@@ -138,6 +138,8 @@ func _test_snapshot_validation_and_legacy() -> void:
|
||||
legacy[&"schema_version"] = version
|
||||
_expect(not restored.restore_snapshot(legacy), "old version with retained shaft field cannot disable safety")
|
||||
legacy.erase(&"vertical_walk_blockers")
|
||||
legacy.erase(&"vertical_walk_requires_connector")
|
||||
legacy.erase(&"transit_actor_ids")
|
||||
_expect(restored.restore_snapshot(legacy), "true legacy snapshot lacking shaft field migrates")
|
||||
_expect(restored.get_vertical_walk_blockers().is_empty() and not restored.find_path(cells[0], cells[1]).is_empty(), "legacy plain topology remains compatible")
|
||||
_expect(restored.restore_snapshot(snapshot), "return to current shaft snapshot")
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
extends SceneTree
|
||||
|
||||
const PathType = preload("res://scripts/world/path_service.gd")
|
||||
const LegacyType = preload("res://scripts/world/legacy_stair_service.gd")
|
||||
|
||||
class ControlledPath:
|
||||
extends PathService
|
||||
var fail_restore_once := false
|
||||
var fail_restore_always := false
|
||||
var reenter: LegacyStairService
|
||||
var nested_rejected := false
|
||||
func restore_snapshot(snapshot: Dictionary) -> bool:
|
||||
var result := super.restore_snapshot(snapshot)
|
||||
if reenter != null:
|
||||
var target := reenter
|
||||
reenter = null
|
||||
nested_rejected = not target.migrate_existing_vertical_walks()
|
||||
if fail_restore_always:
|
||||
set_vertical_walk_requires_connector(not get_vertical_walk_requires_connector())
|
||||
return false
|
||||
if fail_restore_once:
|
||||
fail_restore_once = false
|
||||
return false
|
||||
return result
|
||||
|
||||
var _checks := 0
|
||||
var _failures := 0
|
||||
|
||||
func _initialize() -> void:
|
||||
call_deferred("_run")
|
||||
|
||||
func _run() -> void:
|
||||
_test_one_time_migration_and_access()
|
||||
_test_prune_and_no_regeneration()
|
||||
_test_snapshot_ownership()
|
||||
_test_atomic_failure_and_reentry()
|
||||
print("Legacy stairs: %d checks, %d failures" % [_checks, _failures])
|
||||
quit(_failures)
|
||||
|
||||
func _test_one_time_migration_and_access() -> void:
|
||||
var path := PathType.new()
|
||||
var cells: Array[Vector3i] = [Vector3i(0, 0, 0), Vector3i(0, 1, 0), Vector3i(0, 2, 0), Vector3i(1, 0, 0), Vector3i(1, 1, 0), Vector3i(2, 0, 0), Vector3i(2, 1, 0), Vector3i(0, 0, 1), Vector3i(0, 1, 1)]
|
||||
_expect(path.rebuild_graph(cells), "materialize legacy floors")
|
||||
_expect(path.configure_connector(&"staff.door", Vector3i(0, 0, 0), Vector3i(0, 0, 1), 120, true, &"staff"), "existing staff door remains separately owned")
|
||||
_expect(path.configure_connector(&"external.express", Vector3i(0, 0, 0), Vector3i(0, 1, 0), 50, false, &"permit"), "existing cheaper one-way connector coexists with WALK")
|
||||
var shaft_cells: Array[Vector3i] = [Vector3i(2, 0, 0), Vector3i(2, 1, 0)]
|
||||
_expect(path.set_vertical_walk_blockers(&"infra.shaft:lift", shaft_cells), "existing elevator shaft suppresses ordinary climbing")
|
||||
_expect(path.set_cell_temporary_block(Vector3i(1, 1, 0), true), "temporary traffic block does not remove structural connection")
|
||||
_expect(path.project_occupancy([{&"actor_id": &"staff:porter", &"cell": Vector3i(0, 0, 1)}]), "preserve existing occupancy during migration")
|
||||
var before := path.create_snapshot()
|
||||
var legacy := LegacyType.new()
|
||||
_expect(legacy.configure(path), "bind legacy owner")
|
||||
_expect(legacy.migrate_existing_vertical_walks(), "one migration materializes ordinary vertical WALK edges")
|
||||
var snapshot := legacy.create_snapshot()
|
||||
_expect(snapshot[&"stairs"].size() == 4, "both FRONT and BACK WALK edges convert once, without shaft bypass")
|
||||
_expect(path.get_vertical_walk_requires_connector() and legacy.validate_invariants()[&"ok"], "migration enables strict floor policy")
|
||||
_expect(path.has_connector(&"legacy.stairs:0:0:0") and path.has_connector(&"legacy.stairs:0:0:1"), "stable IDs encode layer, x and lower floor")
|
||||
_expect(not path.has_connector(&"legacy.stairs:0:2:0"), "shaft has no free compatibility stairs")
|
||||
_expect(path.get_connector(&"legacy.stairs:0:1:0")[&"cost"] == 100, "temporary blocked route still migrates at original WALK score")
|
||||
_expect(path.get_connector(&"legacy.stairs:1:0:0")[&"access_id"] == &"", "legacy BACK route keeps its previous access semantics")
|
||||
_expect(path.get_connector(&"staff.door") == _connector(before, &"staff.door"), "staff door cost and permission are unchanged")
|
||||
_expect(path.get_connector(&"external.express") == _connector(before, &"external.express"), "one-way custom connector retains exact configuration")
|
||||
var after := path.create_snapshot()
|
||||
for key: StringName in [&"cell_traffic", &"actor_cells", &"actor_progress", &"vertical_walk_blockers", &"active_elevator_units", &"known_elevator_request_ids", &"transit_actor_ids", &"traffic_revision"]:
|
||||
_expect(after[key] == before[key], "migration preserves unrelated Path field %s" % key)
|
||||
_expect(path.find_path(Vector3i(0, 0, 0), Vector3i(0, 2, 0), [0], []).size() == 3, "ordinary guests retain their old multi-floor route")
|
||||
_expect(path.find_path(Vector3i(0, 0, 0), Vector3i(0, 1, 1), [0, 1], []).is_empty(), "migration does not bypass staff-door access")
|
||||
_expect(not path.find_path(Vector3i(0, 0, 0), Vector3i(0, 1, 1), [0, 1], [&"staff"]).is_empty(), "staff keep cross-layer commute via original door")
|
||||
_expect(legacy.migrate_existing_vertical_walks() and legacy.create_snapshot() == snapshot and path.create_snapshot() == after, "repeat migration is an exact no-op")
|
||||
cells.append(Vector3i(0, 3, 0))
|
||||
_expect(path.rebuild_graph(cells), "append newly built upper corridor after migration")
|
||||
_expect(legacy.migrate_existing_vertical_walks() and legacy.create_snapshot() == snapshot, "new vertical adjacency never receives compatibility stairs")
|
||||
_expect(path.find_path(Vector3i(0, 2, 0), Vector3i(0, 3, 0)).is_empty(), "new floors require a real stair or elevator")
|
||||
snapshot[&"stairs"].clear()
|
||||
_expect(legacy.create_snapshot()[&"stairs"].size() == 4, "snapshot returns defensive metadata")
|
||||
|
||||
func _test_prune_and_no_regeneration() -> void:
|
||||
var path := PathType.new()
|
||||
var cells: Array[Vector3i] = [Vector3i(0, 0, 0), Vector3i(0, 1, 0), Vector3i(0, 2, 0), Vector3i(1, 0, 0), Vector3i(1, 1, 0)]
|
||||
path.rebuild_graph(cells)
|
||||
var legacy := LegacyType.new()
|
||||
legacy.configure(path)
|
||||
_expect(legacy.migrate_existing_vertical_walks(), "migrate prune fixture")
|
||||
cells.erase(Vector3i(0, 1, 0))
|
||||
_expect(path.rebuild_graph(cells), "demolish middle landing")
|
||||
_expect(not legacy.validate_invariants()[&"ok"], "missing landing marks legacy ownership stale until prune")
|
||||
_expect(legacy.prune_invalid(), "prune both incident stair edges atomically")
|
||||
_expect(legacy.create_snapshot()[&"stairs"].size() == 1 and not path.has_connector(&"legacy.stairs:0:0:0") and not path.has_connector(&"legacy.stairs:0:0:1"), "removed landing retires both owned connector records")
|
||||
cells.append(Vector3i(0, 1, 0))
|
||||
_expect(path.rebuild_graph(cells) and legacy.prune_invalid(), "rebuild same coordinates after demolition")
|
||||
_expect(path.find_path(Vector3i(0, 0, 0), Vector3i(0, 2, 0)).is_empty(), "retired links never resurrect at reused coordinates")
|
||||
var blocked: Array[Vector3i] = [Vector3i(1, 1, 0)]
|
||||
_expect(path.set_vertical_walk_blockers(&"new.shaft", blocked), "new elevator shaft covers remaining landing")
|
||||
_expect(legacy.prune_invalid() and legacy.create_snapshot()[&"stairs"].is_empty(), "new shaft removes touching compatibility connector")
|
||||
_expect(path.remove_vertical_walk_blockers(&"new.shaft") and legacy.prune_invalid(), "remove elevator shaft later")
|
||||
_expect(not path.has_connector(&"legacy.stairs:0:1:0"), "removing a shaft does not restore compatibility stairs")
|
||||
var before := path.create_snapshot()
|
||||
_expect(legacy.prune_invalid() and path.create_snapshot() == before, "unchanged prune does not advance graph revision")
|
||||
path.set_vertical_walk_requires_connector(false)
|
||||
before = path.create_snapshot()
|
||||
_expect(not legacy.migrate_existing_vertical_walks() and not legacy.prune_invalid(), "policy tampering cannot restart closed migration")
|
||||
_expect(path.create_snapshot() == before, "rejected repeated migration does not mutate tampered Path")
|
||||
|
||||
func _test_snapshot_ownership() -> void:
|
||||
var path := PathType.new()
|
||||
var cells: Array[Vector3i] = [Vector3i(-1, -1, 0), Vector3i(-1, 0, 0), Vector3i(2, 0, 1), Vector3i(2, 1, 1)]
|
||||
path.rebuild_graph(cells)
|
||||
var legacy := LegacyType.new()
|
||||
legacy.configure(path)
|
||||
_expect(legacy.migrate_existing_vertical_walks(), "migrate negative floor and BACK snapshot fixture")
|
||||
var saved_path := path.create_snapshot()
|
||||
var saved := legacy.create_snapshot()
|
||||
var restored_path := PathType.new()
|
||||
var restored := LegacyType.new()
|
||||
_expect(restored.configure(restored_path), "bind destination before restoring Path")
|
||||
_expect(restored_path.restore_snapshot(saved_path) and restored_path.rebuild_graph(cells), "restore and materialize saved Path")
|
||||
_expect(restored.restore_snapshot(saved) and restored.create_snapshot() == saved, "strict current legacy snapshot roundtrip")
|
||||
var malformed: Array[Dictionary] = []
|
||||
var row := saved.duplicate(true)
|
||||
row[&"extra"] = true
|
||||
malformed.append(row)
|
||||
row = saved.duplicate(true)
|
||||
row[&"schema_version"] = 2
|
||||
malformed.append(row)
|
||||
row = saved.duplicate(true)
|
||||
row[&"schema_version"] = "1"
|
||||
malformed.append(row)
|
||||
row = saved.duplicate(true)
|
||||
row[&"stairs"].reverse()
|
||||
malformed.append(row)
|
||||
row = saved.duplicate(true)
|
||||
row[&"stairs"].append(row[&"stairs"][0].duplicate(true))
|
||||
malformed.append(row)
|
||||
row = saved.duplicate(true)
|
||||
row[&"stairs"][0][&"id"] = "legacy.stairs:forged"
|
||||
malformed.append(row)
|
||||
row = saved.duplicate(true)
|
||||
row[&"stairs"][0][&"to"] = Vector3i(-1, 1, 0)
|
||||
malformed.append(row)
|
||||
row = saved.duplicate(true)
|
||||
row[&"stairs"][0][&"to"] = Vector3i(-1, 0, 1)
|
||||
malformed.append(row)
|
||||
row = saved.duplicate(true)
|
||||
row[&"stairs"][0][&"cost"] = 100
|
||||
malformed.append(row)
|
||||
row = saved.duplicate(true)
|
||||
row[&"stairs"].clear()
|
||||
malformed.append(row)
|
||||
for bad: Dictionary in malformed:
|
||||
_expect(not restored.restore_snapshot(bad), "reject malformed or incomplete compatibility ownership")
|
||||
_expect(restored.create_snapshot() == saved and restored_path.create_snapshot() == saved_path, "legacy snapshot rejection preserves metadata and Path exactly")
|
||||
restored_path.configure_connector(&"legacy.stairs:orphan", cells[0], cells[1])
|
||||
_expect(not restored.restore_snapshot(saved) and not restored.prune_invalid(), "untracked managed connector blocks restore and prune")
|
||||
restored_path.remove_connector(&"legacy.stairs:orphan")
|
||||
restored_path.configure_connector(&"legacy.stairs:0:-1:-1", cells[0], cells[1], 100, true, &"staff")
|
||||
_expect(not restored.restore_snapshot(saved) and not restored.prune_invalid(), "owned connector permission tampering is rejected")
|
||||
_expect(restored_path.restore_snapshot(saved_path), "repair exact connector configuration")
|
||||
restored_path.set_vertical_walk_requires_connector(false)
|
||||
_expect(not restored.restore_snapshot(saved), "current legacy snapshot cannot restore with free vertical walking")
|
||||
var fresh_path := PathType.new()
|
||||
fresh_path.rebuild_graph(cells)
|
||||
fresh_path.set_vertical_walk_requires_connector(true)
|
||||
var fresh := LegacyType.new()
|
||||
fresh.configure(fresh_path)
|
||||
_expect(fresh.migrate_existing_vertical_walks() and fresh.create_snapshot()[&"stairs"].is_empty(), "new strict game never gains migration stairs")
|
||||
_expect(fresh.restore_snapshot({&"schema_version": 1, &"stairs": []}), "new game empty legacy snapshot is valid")
|
||||
var out_of_bounds_path := PathType.new()
|
||||
out_of_bounds_path.rebuild_graph([Vector3i(100_001, 0, 0), Vector3i(100_001, 1, 0)])
|
||||
var out_of_bounds := LegacyType.new()
|
||||
out_of_bounds.configure(out_of_bounds_path)
|
||||
var out_of_bounds_before := out_of_bounds_path.create_snapshot()
|
||||
_expect(not out_of_bounds.migrate_existing_vertical_walks() and out_of_bounds_path.create_snapshot() == out_of_bounds_before, "unsupported migration coordinates cannot create unrestorable records")
|
||||
|
||||
func _test_atomic_failure_and_reentry() -> void:
|
||||
var path := ControlledPath.new()
|
||||
var cells: Array[Vector3i] = [Vector3i(0, 0, 0), Vector3i(0, 1, 0), Vector3i(0, 2, 0)]
|
||||
path.rebuild_graph(cells)
|
||||
var legacy := LegacyType.new()
|
||||
legacy.configure(path)
|
||||
var before := path.create_snapshot()
|
||||
var metadata := legacy.create_snapshot()
|
||||
path.fail_restore_once = true
|
||||
_expect(not legacy.migrate_existing_vertical_walks(), "injected partial Path apply failure rejects migration")
|
||||
_expect(path.create_snapshot() == before and legacy.create_snapshot() == metadata, "failed migration restores Path and owned metadata exactly")
|
||||
path.reenter = legacy
|
||||
_expect(legacy.migrate_existing_vertical_walks() and path.nested_rejected, "outer migration succeeds while nested migration is rejected")
|
||||
cells.erase(Vector3i(0, 1, 0))
|
||||
path.rebuild_graph(cells)
|
||||
before = path.create_snapshot()
|
||||
metadata = legacy.create_snapshot()
|
||||
path.fail_restore_once = true
|
||||
_expect(not legacy.prune_invalid(), "injected partial prune failure rejects ownership removal")
|
||||
_expect(path.create_snapshot() == before and legacy.create_snapshot() == metadata, "failed prune restores both dangling records and original Path for caller rollback")
|
||||
_expect(legacy.prune_invalid(), "prune can retry after exact rollback")
|
||||
var broken_path := ControlledPath.new()
|
||||
broken_path.rebuild_graph([Vector3i(0, 0, 0), Vector3i(0, 1, 0)])
|
||||
var broken := LegacyType.new()
|
||||
broken.configure(broken_path)
|
||||
broken_path.fail_restore_always = true
|
||||
_expect(not broken.migrate_existing_vertical_walks(), "repeated rollback failure rejects migration")
|
||||
_expect(broken.validate_invariants()[&"quarantined"] and not broken.migrate_existing_vertical_walks(), "uncertain rollback quarantines future mutation")
|
||||
|
||||
func _connector(snapshot: Dictionary, connector_id: StringName) -> Dictionary:
|
||||
for connector: Dictionary in snapshot[&"connectors"]:
|
||||
if StringName(connector[&"id"]) == connector_id:
|
||||
return connector
|
||||
return {}
|
||||
|
||||
func _expect(ok: bool, label: String) -> void:
|
||||
_checks += 1
|
||||
if not ok:
|
||||
_failures += 1
|
||||
push_error(label)
|
||||
@@ -0,0 +1 @@
|
||||
uid://c6pvi4t0vy41s
|
||||
@@ -0,0 +1,120 @@
|
||||
extends SceneTree
|
||||
|
||||
var _checks := 0
|
||||
var _failures := 0
|
||||
const ORIGIN := Vector3i.ZERO
|
||||
const DESTINATION := Vector3i(0, 1, 0)
|
||||
|
||||
func _initialize() -> void:
|
||||
call_deferred("_run")
|
||||
|
||||
func _run() -> void:
|
||||
_test_occupancy_lifecycle()
|
||||
_test_projection_and_despawn()
|
||||
_test_save_and_migration()
|
||||
print("Path transit density: %d checks, %d failures" % [_checks, _failures])
|
||||
quit(_failures)
|
||||
|
||||
func _path() -> PathService:
|
||||
var path := PathService.new()
|
||||
path.rebuild_graph([ORIGIN, DESTINATION, Vector3i(1, 0, 0)])
|
||||
path.configure_elevator(&"lift", ORIGIN, DESTINATION, {&"cabin_capacity": 4})
|
||||
path.set_cell_base_capacity(ORIGIN, 1)
|
||||
return path
|
||||
|
||||
func _queue(path: PathService, actor: StringName, id: StringName) -> void:
|
||||
_expect(path.enqueue_elevator_request(&"lift", {&"request_id": id, &"unit_id": actor, &"floor_from": 0, &"floor_to": 1, &"enqueued_at": 10})[&"ok"], "elevator request registers: %s" % actor)
|
||||
|
||||
func _populate(path: PathService) -> void:
|
||||
for id: StringName in [&"guest:a", &"guest:b", &"staff:c"]:
|
||||
path.project_actor_position(id, ORIGIN, 10)
|
||||
|
||||
func _test_occupancy_lifecycle() -> void:
|
||||
var path := _path()
|
||||
_populate(path)
|
||||
_expect(path.get_current_density(ORIGIN) == 3 and is_equal_approx(path.get_speed_multiplier(ORIGIN), 1.0 / 3.0), "all three floor actors initially contribute to congestion")
|
||||
_queue(path, &"guest:a", &"ride.a")
|
||||
_expect(path.get_current_density(ORIGIN) == 3 and path.get_transit_actor_ids().is_empty(), "queued passengers still occupy the floor")
|
||||
var before := path.create_snapshot()
|
||||
var graph_before := path.graph_revision
|
||||
var boarded := path.dequeue_elevator_boarding(&"lift", 0, 11, 1)
|
||||
_expect(boarded.size() == 1 and path.get_transit_actor_ids() == [&"guest:a"], "boarding derives transit occupancy from the existing Path owner")
|
||||
_expect(path.get_current_density(ORIGIN) == 2 and is_equal_approx(path.get_speed_multiplier(ORIGIN), 0.5), "a boarded passenger no longer slows the remaining floor actors")
|
||||
_expect(path.get_actor_projected_cell(&"guest:a") == ORIGIN and path.create_snapshot()[&"actor_progress"] == before[&"actor_progress"], "boarding retains logical origin and progress records")
|
||||
_expect(path.graph_revision == graph_before and path.traffic_revision > before[&"traffic_revision"], "boarding changes traffic costs without changing structural topology")
|
||||
var stable := path.create_snapshot()
|
||||
_expect(path.dequeue_elevator_boarding(&"lift", 0, 11, 1).is_empty() and path.get_current_density(ORIGIN) == 2 \
|
||||
and path.get_transit_actor_ids() == [&"guest:a"] and path.traffic_revision == stable[&"traffic_revision"], "repeated empty boarding does not change occupancy or traffic revision")
|
||||
_expect(path.complete_elevator_request(&"lift", &"ride.a"), "completion releases the existing boarded owner")
|
||||
_expect(path.get_current_density(ORIGIN) == 3 and not path.is_actor_in_transit(&"guest:a"), "completed trip restores the anchor's floor contribution before Runtime relocates it")
|
||||
_expect(path.project_actor_position(&"guest:a", DESTINATION, 12), "Runtime arrival projects the actor at the real destination")
|
||||
_expect(path.get_current_density(ORIGIN) == 2 and path.get_current_density(DESTINATION) == 1, "arrival moves exactly one floor occupant from origin to destination")
|
||||
stable = path.create_snapshot()
|
||||
_expect(not path.complete_elevator_request(&"lift", &"ride.a") and path.create_snapshot() == stable, "duplicate completion cannot release density twice")
|
||||
_queue(path, &"guest:b", &"ride.b")
|
||||
_expect(path.cancel_elevator_request(&"lift", &"ride.b") and path.get_current_density(ORIGIN) == 2, "cancelling a queued ride keeps its waiting actor on the floor")
|
||||
|
||||
func _test_projection_and_despawn() -> void:
|
||||
var path := _path()
|
||||
_populate(path)
|
||||
_queue(path, &"staff:c", &"ride.c")
|
||||
path.dequeue_elevator_boarding(&"lift", 0, 11, 1)
|
||||
_expect(path.project_occupancy([{&"actor_id": &"guest:a", &"cell": ORIGIN}, {&"actor_id": &"staff:c", &"cell": ORIGIN}]), "batch projection retains existing boarded ownership")
|
||||
_expect(path.get_current_density(ORIGIN) == 1 and path.get_transit_actor_ids() == [&"staff:c"], "batch occupancy excludes the passenger exactly once")
|
||||
_expect(path.rebuild_graph([ORIGIN, DESTINATION, Vector3i(1, 0, 0)]) and path.get_current_density(ORIGIN) == 1, "topology refresh does not reintroduce cabin passengers into density")
|
||||
var revision := path.traffic_revision
|
||||
_expect(path.project_actor_position(&"staff:c", DESTINATION, 12), "transit projection can update its logical anchor without materializing an occupant")
|
||||
_expect(path.get_current_density(ORIGIN) == 1 and path.get_current_density(DESTINATION) == 0 and path.traffic_revision == revision, "moving an excluded anchor changes no floor density or route costs")
|
||||
_expect(path.despawn_actor(&"staff:c") and path.get_current_density(DESTINATION) == 0 and path.get_transit_actor_ids().is_empty(), "despawning a passenger does not decrement a zero-density floor")
|
||||
_expect(path.complete_elevator_request(&"lift", &"ride.c") and path.get_current_density(DESTINATION) == 0, "later owner cleanup cannot resurrect a despawned passenger")
|
||||
_queue(path, &"unprojected_unit", &"ride.unit")
|
||||
path.dequeue_elevator_boarding(&"lift", 0, 13, 1)
|
||||
_expect(path.get_transit_actor_ids().is_empty() and path.get_current_density(ORIGIN) == 1, "standalone elevator units without actor projections do not affect floor density")
|
||||
_expect(path.project_actor_position(&"unprojected_unit", ORIGIN, 14) and path.is_actor_in_transit(&"unprojected_unit") and path.get_current_density(ORIGIN) == 1, "a projection added after boarding inherits its existing transit ownership")
|
||||
_expect(path.actor_leave_cell(&"unprojected_unit", false) and not path.is_actor_in_transit(&"unprojected_unit"), "leaving the actor graph clears transit membership even when retaining progress")
|
||||
|
||||
func _test_save_and_migration() -> void:
|
||||
var path := _path()
|
||||
_populate(path)
|
||||
_queue(path, &"guest:a", &"ride.a")
|
||||
_queue(path, &"staff:c", &"ride.c")
|
||||
path.dequeue_elevator_boarding(&"lift", 0, 11, 2)
|
||||
var saved := path.create_snapshot()
|
||||
_expect(saved[&"transit_actor_ids"] == [&"guest:a", &"staff:c"] and path.get_current_density(ORIGIN) == 1, "v5 saves canonical excluded actors and physical floor density")
|
||||
var restored := _path()
|
||||
_expect(restored.restore_snapshot(saved) and restored.create_snapshot() == saved, "boarded Path v5 snapshot restores exactly")
|
||||
for raw: Variant in [null, [], ["missing"], ["guest:a", "guest:a"], ["staff:c", "guest:a"], [17, "staff:c"]]:
|
||||
var bad := saved.duplicate(true)
|
||||
bad[&"transit_actor_ids"] = raw
|
||||
_expect(not restored.restore_snapshot(bad) and restored.create_snapshot() == saved, "invalid transit ownership rejects atomically: %s" % str(raw))
|
||||
var wrong_density := saved.duplicate(true)
|
||||
for row: Dictionary in wrong_density[&"cell_traffic"]:
|
||||
if row[&"cell"] == ORIGIN: row[&"current_density"] += 1
|
||||
_expect(not restored.restore_snapshot(wrong_density) and restored.create_snapshot() == saved, "v5 cannot count an excluded passenger back into origin density")
|
||||
var legacy := saved.duplicate(true)
|
||||
legacy[&"schema_version"] = 4
|
||||
legacy.erase(&"transit_actor_ids")
|
||||
legacy.erase(&"vertical_walk_requires_connector")
|
||||
_expect(not restored.restore_snapshot(legacy) and restored.create_snapshot() == saved, "legacy snapshots must first satisfy their original full-occupancy density rule")
|
||||
for row: Dictionary in legacy[&"cell_traffic"]:
|
||||
if row[&"cell"] == ORIGIN: row[&"current_density"] = 3
|
||||
_expect(restored.restore_snapshot(legacy), "genuine v4 boarded save migrates")
|
||||
var migrated := restored.create_snapshot()
|
||||
_expect(restored.get_current_density(ORIGIN) == 1 and migrated[&"transit_actor_ids"] == saved[&"transit_actor_ids"], "legacy migration removes only projected boarded actors from density")
|
||||
_expect(migrated[&"actor_cells"] == legacy[&"actor_cells"] and migrated[&"actor_progress"] == legacy[&"actor_progress"], "migration preserves every logical actor location and progress timestamp")
|
||||
_expect(migrated[&"traffic_revision"] == legacy[&"traffic_revision"] + 1 and migrated[&"graph_revision"] == legacy[&"graph_revision"], "migration advances traffic revision once without rewriting topology")
|
||||
_expect(restored.restore_snapshot(migrated) and restored.create_snapshot() == migrated, "canonical reload does not repeat legacy density subtraction")
|
||||
var replay := _path()
|
||||
_expect(replay.restore_snapshot(migrated), "a second runtime starts from the same boarded snapshot")
|
||||
for service: PathService in [restored, replay]:
|
||||
service.complete_elevator_request(&"lift", &"ride.a")
|
||||
service.project_actor_position(&"guest:a", DESTINATION, 12)
|
||||
service.complete_elevator_request(&"lift", &"ride.c")
|
||||
service.project_actor_position(&"staff:c", DESTINATION, 12)
|
||||
_expect(restored.create_snapshot() == replay.create_snapshot() and restored.get_current_density(DESTINATION) == 2, "arrival replay preserves exact ownership, density and timestamps after migration")
|
||||
|
||||
func _expect(ok: bool, label: String) -> void:
|
||||
_checks += 1
|
||||
if not ok:
|
||||
_failures += 1
|
||||
push_error(label)
|
||||
@@ -0,0 +1 @@
|
||||
uid://bwopfir6rudpp
|
||||
@@ -0,0 +1,105 @@
|
||||
extends SceneTree
|
||||
|
||||
var _checks := 0
|
||||
var _failures := 0
|
||||
|
||||
func _initialize() -> void:
|
||||
call_deferred("_run")
|
||||
|
||||
func _run() -> void:
|
||||
_test_policy_and_connectors()
|
||||
_test_recovery_and_diagnostics()
|
||||
_test_snapshots()
|
||||
print("Path vertical policy: %d checks, %d failures" % [_checks, _failures])
|
||||
quit(_failures)
|
||||
|
||||
func _path() -> PathService:
|
||||
var path := PathService.new()
|
||||
path.rebuild_graph([Vector3i.ZERO, Vector3i(1, 0, 0), Vector3i(0, 1, 0), Vector3i(1, 1, 0), Vector3i(0, 0, 1), Vector3i(0, 1, 1)])
|
||||
return path
|
||||
|
||||
func _test_policy_and_connectors() -> void:
|
||||
var path := _path()
|
||||
var bottom := Vector3i.ZERO
|
||||
var top := Vector3i(0, 1, 0)
|
||||
_expect(not path.get_vertical_walk_requires_connector(), "standalone Path retains legacy cardinal movement by default")
|
||||
var old_route := path.find_path(bottom, top)
|
||||
_expect(old_route == [bottom, top] and path.cached_path_count() == 1, "legacy upper-floor route can be cached")
|
||||
var graph_before := path.graph_revision
|
||||
_expect(path.set_vertical_walk_requires_connector(true), "strict vertical policy enables")
|
||||
_expect(path.graph_revision == graph_before + 1 and path.cached_path_count() == 0, "policy change invalidates previously cached floor climbing")
|
||||
_expect(path.find_path(bottom, top).is_empty() and path.last_no_route_reason == PathService.ROUTE_GOAL_UNREACHABLE, "adjacent floors require an explicit vertical connection")
|
||||
_expect(path.calculate_path_cost(old_route) == -1 and path.get_edge_report(bottom, top).is_empty(), "cost and movement edge resolution cannot bypass strict routing")
|
||||
_expect(path.find_path(bottom, Vector3i(1, 0, 0)).size() == 2, "horizontal walking remains available")
|
||||
var raw_vertical := false
|
||||
for edge: Dictionary in path.get_neighbors(bottom):
|
||||
if edge[&"to"] == top and edge[&"movement_type"] == PathService.MOVEMENT_WALK: raw_vertical = true
|
||||
_expect(raw_vertical, "raw structural neighbors remain available to legacy-stair migration")
|
||||
var snapshot := path.create_snapshot()
|
||||
_expect(path.set_vertical_walk_requires_connector(true) and path.create_snapshot() == snapshot, "reapplying the same policy is an exact no-op")
|
||||
_expect(path.configure_connector(&"stairs", bottom, top, 150), "explicit public stairs register")
|
||||
_expect(path.find_path(bottom, top) == [bottom, top] and path.get_step_connector(bottom, top) == &"stairs", "explicit stairs work through strict policy")
|
||||
_expect(path.calculate_path_cost([bottom, top]) == 150, "stairs keep their defined movement cost")
|
||||
path.set_connector_enabled(&"stairs", false)
|
||||
_expect(path.find_path(bottom, top).is_empty() and path.last_no_route_reason == PathService.ROUTE_CONNECTOR_UNAVAILABLE, "closed stairs cannot fall back to implicit floor climbing")
|
||||
path.configure_connector(&"service_stairs", Vector3i(0, 0, 1), Vector3i(0, 1, 1), 150, true, &"staff")
|
||||
_expect(path.find_path(Vector3i(0, 0, 1), Vector3i(0, 1, 1), [1], []).is_empty(), "service stairs retain staff-only access")
|
||||
_expect(path.find_path(Vector3i(0, 0, 1), Vector3i(0, 1, 1), [1], [&"staff"]).size() == 2, "authorized staff use explicit BACK stairs")
|
||||
path.configure_elevator(&"lift", bottom, top)
|
||||
_expect(path.get_step_connector(bottom, top) == &"lift", "an operational elevator remains an explicit vertical route")
|
||||
path.set_connector_enabled(&"lift", false)
|
||||
_expect(path.find_path(bottom, top).is_empty(), "a stopped lift never exposes an ordinary vertical edge")
|
||||
path.set_vertical_walk_blockers(&"shaft", [bottom, top])
|
||||
path.set_vertical_walk_requires_connector(false)
|
||||
_expect(path.get_edge_report(bottom, top).is_empty(), "physical shaft blockers survive opting out of the global floor policy")
|
||||
|
||||
func _test_recovery_and_diagnostics() -> void:
|
||||
var path := PathService.new()
|
||||
var bottom := Vector3i.ZERO
|
||||
var top := Vector3i(0, 1, 0)
|
||||
path.rebuild_graph([bottom, top])
|
||||
path.set_vertical_walk_requires_connector(true)
|
||||
path.set_cell_temporary_block(bottom, true)
|
||||
_expect(not path.find_nearest_walkable_recovery(bottom).get(&"found", false), "recovery cannot choose an unreachable floor solely because it is nearby")
|
||||
path.configure_connector(&"stairs", bottom, top, 150)
|
||||
_expect(path.find_nearest_walkable_recovery(bottom).get(&"cell") == top, "a real public stair makes the upper recovery proposal structurally reachable")
|
||||
path.set_connector_enabled(&"stairs", false)
|
||||
_expect(not path.find_nearest_walkable_recovery(bottom).get(&"found", false), "recovery respects an unavailable vertical connector")
|
||||
path.set_cell_temporary_block(bottom, false)
|
||||
path.remove_connector(&"stairs")
|
||||
path.rebuild_graph([bottom, top, Vector3i(1, 0, 0), Vector3i(1, 1, 0)])
|
||||
path.set_cell_temporary_block(Vector3i(1, 0, 0), true)
|
||||
_expect(path.find_path(bottom, top).is_empty() and path.last_no_route_reason == PathService.ROUTE_GOAL_UNREACHABLE, "diagnostics do not invent a blocked walking route between floors")
|
||||
path.set_vertical_walk_requires_connector(false)
|
||||
_expect(path.find_path(bottom, top) == [bottom, top], "explicit legacy opt-out restores standalone vertical walking")
|
||||
|
||||
func _test_snapshots() -> void:
|
||||
var path := _path()
|
||||
path.set_vertical_walk_requires_connector(true)
|
||||
path.set_vertical_walk_blockers(&"shaft", [Vector3i.ZERO, Vector3i(0, 1, 0)])
|
||||
var saved := path.create_snapshot()
|
||||
_expect(saved[&"schema_version"] == 5 and saved[&"vertical_walk_requires_connector"] == true and saved[&"transit_actor_ids"] == [], "Path v5 saves explicit policy and transit occupancy")
|
||||
var restored := _path()
|
||||
_expect(restored.restore_snapshot(saved) and restored.create_snapshot() == saved, "strict policy and shaft ownership restore exactly")
|
||||
for value: Variant in [0, 1, "true", null]:
|
||||
var bad := saved.duplicate(true)
|
||||
bad[&"vertical_walk_requires_connector"] = value
|
||||
_expect(not restored.restore_snapshot(bad) and restored.create_snapshot() == saved, "non-bool vertical policy rejects atomically: %s" % str(value))
|
||||
var missing := saved.duplicate(true)
|
||||
missing.erase(&"vertical_walk_requires_connector")
|
||||
_expect(not restored.restore_snapshot(missing), "v5 cannot silently default a missing policy")
|
||||
var legacy := saved.duplicate(true)
|
||||
legacy[&"schema_version"] = 4
|
||||
_expect(not restored.restore_snapshot(legacy), "outer-only legacy downgrade cannot hide new policy fields")
|
||||
legacy.erase(&"vertical_walk_requires_connector")
|
||||
legacy.erase(&"transit_actor_ids")
|
||||
_expect(restored.restore_snapshot(legacy) and not restored.get_vertical_walk_requires_connector(), "v4 defaults to its original vertical rules for Runtime-managed migration")
|
||||
_expect(restored.get_vertical_walk_blockers() == path.get_vertical_walk_blockers(), "v4 keeps existing shaft ownership independently of v5 policy")
|
||||
var migrated := restored.create_snapshot()
|
||||
_expect(restored.restore_snapshot(migrated) and restored.create_snapshot() == migrated, "legacy migration is canonical and does not repeat on second restore")
|
||||
|
||||
func _expect(ok: bool, label: String) -> void:
|
||||
_checks += 1
|
||||
if not ok:
|
||||
_failures += 1
|
||||
push_error(label)
|
||||
@@ -0,0 +1 @@
|
||||
uid://bttq5agd5o82p
|
||||
@@ -0,0 +1,203 @@
|
||||
extends SceneTree
|
||||
|
||||
const PathType = preload("res://scripts/world/path_service.gd")
|
||||
const ProjectorType = preload("res://scripts/world/facility_infrastructure_projector.gd")
|
||||
const AdapterType = preload("res://scripts/world/route_subslot_path_adapter.gd")
|
||||
const SpatialType = preload("res://scripts/world/spatial_subslot_service.gd")
|
||||
const CatalogType = preload("res://scripts/data/facility_catalog.gd")
|
||||
const RegistryType = preload("res://scripts/core/definition_registry.gd")
|
||||
const GUEST = preload("res://data/facilities/infrastructure/guest_stairs.tres")
|
||||
const SERVICE = preload("res://data/facilities/infrastructure/service_stairs.tres")
|
||||
const CORRIDOR = preload("res://data/facilities/guest_corridor.tres")
|
||||
|
||||
class ControlledPath:
|
||||
extends PathService
|
||||
var fail_configure := false
|
||||
var fail_remove := false
|
||||
func configure_connector(connector_id: StringName, from: Vector3i, to: Vector3i, cost: int = 150, bidirectional: bool = true, access_id: StringName = &"", enabled: bool = true, options: Dictionary = {}) -> bool:
|
||||
var result := super.configure_connector(connector_id, from, to, cost, bidirectional, access_id, enabled, options)
|
||||
if fail_configure:
|
||||
fail_configure = false
|
||||
return false
|
||||
return result
|
||||
func remove_connector(connector_id: StringName) -> bool:
|
||||
var result := super.remove_connector(connector_id)
|
||||
if fail_remove:
|
||||
fail_remove = false
|
||||
return false
|
||||
return result
|
||||
|
||||
var _checks := 0
|
||||
var _failures := 0
|
||||
|
||||
func _initialize() -> void:
|
||||
call_deferred("_run")
|
||||
|
||||
func _run() -> void:
|
||||
_test_catalog_compatibility()
|
||||
_test_guest_stairs_and_stacking()
|
||||
_test_service_permissions()
|
||||
_test_lower_landing_and_atomic_demolition()
|
||||
_test_snapshot_and_schema_validation()
|
||||
_test_injected_connector_failures()
|
||||
print("Stair infrastructure: %d checks, %d failures" % [_checks, _failures])
|
||||
quit(_failures)
|
||||
|
||||
func _test_catalog_compatibility() -> void:
|
||||
_expect(CatalogType.load_all().size() == 16 and CatalogType.FACILITY_PATHS.size() == 16, "baseline sixteen remains unchanged")
|
||||
_expect(CatalogType.load_expansion().size() == 16 and CatalogType.EXPANSION_FACILITY_PATHS.size() == 16, "optional expansion sixteen remains unchanged")
|
||||
_expect(CatalogType.load_available().size() == 16 and CatalogType.load_available(true).size() == 32, "existing catalog loading contract remains unchanged")
|
||||
var infrastructure := CatalogType.load_infrastructure()
|
||||
_expect(infrastructure.size() == 2 and CatalogType.INFRASTRUCTURE_FACILITY_IDS == [&"guest_stairs", &"service_stairs"], "required stair infrastructure is explicitly listed")
|
||||
for definition: FacilityDefinition in infrastructure:
|
||||
_expect(definition.is_valid(), "stair definition validates")
|
||||
_expect(CatalogType.by_id(definition.id) == definition, "global stable ID lookup resolves stairs")
|
||||
_expect(definition.footprint == Vector2i.ONE and definition.allowed_rotations == [0], "stairs use an unrotated upper landing")
|
||||
_expect(definition.build_minutes == 10 and definition.maintenance_mana == 0 and definition.weight_per_cell == 1, "stairs preserve low-cost unpowered construction")
|
||||
_expect(int(definition.construction_cost[&"materials"]) == 0 and definition.pass_through, "stairs require no materials and provide floor access")
|
||||
_expect(GUEST.construction_cost[&"gold"] == 40 and SERVICE.construction_cost[&"gold"] == 50, "guest and service stair prices match approved plan")
|
||||
var registry := RegistryType.new()
|
||||
registry.rebuild()
|
||||
_expect(registry.is_valid() and registry.facility_count() == 16, "default nonrecursive registry remains baseline compatible")
|
||||
var required_paths: Array[String] = CatalogType.FACILITY_PATHS.duplicate()
|
||||
required_paths.append_array(CatalogType.INFRASTRUCTURE_FACILITY_PATHS)
|
||||
registry.rebuild_from_paths(required_paths)
|
||||
_expect(registry.is_valid() and registry.facility_count() == 18, "Runtime can explicitly load all eighteen required facilities")
|
||||
|
||||
func _test_guest_stairs_and_stacking() -> void:
|
||||
var bottom := Vector3i(0, 0, 0)
|
||||
var middle := Vector3i(0, 1, 0)
|
||||
var upper := Vector3i(0, 2, 0)
|
||||
var context := _context([bottom, Vector3i(-1, 1, 0), Vector3i(1, 2, 0)])
|
||||
_expect(context.projector.project_facility(&"build.first", GUEST, _instance(GUEST, &"first", middle)).is_success(), "guest stairs project onto existing lower landing")
|
||||
_expect(context.source.get_entity_cells(&"infra.route:first") == [middle], "only the upper landing consumes ROUTE subslot")
|
||||
var connector: Dictionary = context.path.get_connector(&"infra.connector:first")
|
||||
_expect(connector[&"from"] == middle and connector[&"to"] == bottom and connector[&"cost"] == 150, "stair connector joins one floor at approved route cost")
|
||||
_expect(connector[&"movement_type"] == PathType.MOVEMENT_CONNECTOR and connector[&"bidirectional"] and connector[&"access_id"] == &"", "guest stairs work in both directions without elevator dispatch")
|
||||
_expect(context.path.find_path(bottom, Vector3i(-1, 1, 0), [0], []) == [bottom, middle, Vector3i(-1, 1, 0)], "strict floor policy still permits explicit guest stairs and floor exit")
|
||||
_expect(context.path.find_path(middle, bottom, [0], []) == [middle, bottom], "descending stairs is valid")
|
||||
_expect(context.projector.project_facility(&"build.second", GUEST, _instance(GUEST, &"second", upper)).is_success(), "stacked upper landings connect consecutive floors")
|
||||
_expect(context.path.find_path(bottom, Vector3i(1, 2, 0)) == [bottom, middle, upper, Vector3i(1, 2, 0)], "two modules reach second floor without overlapping footprints")
|
||||
_expect(context.path.get_vertical_walk_blockers().is_empty(), "stairs do not create elevator shaft blockers")
|
||||
var queried_cells: Array[Vector3i] = [middle, bottom, bottom]
|
||||
var dependents: Array[StringName] = context.projector.get_stair_dependents(queried_cells)
|
||||
_expect(dependents == [&"first", &"second"], "lower-landing dependency query is unique and deterministic")
|
||||
dependents.clear()
|
||||
queried_cells.assign([bottom])
|
||||
_expect(context.projector.get_stair_dependents(queried_cells) == [&"first"], "dependency report cannot mutate owned records")
|
||||
_expect(context.projector.validate_invariants()[&"ok"], "stacked stair topology validates")
|
||||
var before := _snapshots(context)
|
||||
_expect(not context.projector.unproject_facility(&"remove.first", &"first").is_success(), "removing lower stair cannot strand upper stair")
|
||||
_expect(_snapshots(context) == before, "dependent stair rejection restores all authoritative state")
|
||||
_expect(context.projector.unproject_facility(&"remove.second", &"second").is_success(), "remove top stair first")
|
||||
_expect(context.projector.unproject_facility(&"remove.first", &"first").is_success(), "lower stair demolition remains retryable after dependent removed")
|
||||
_expect(context.path.get_connector(&"infra.connector:first").is_empty() and not context.path.has_cell(middle), "demolition removes connector and landing together")
|
||||
|
||||
func _test_service_permissions() -> void:
|
||||
var bottom := Vector3i(0, 0, 1)
|
||||
var upper := Vector3i(0, 1, 1)
|
||||
var context := _context([bottom, Vector3i(0, 0, 0), Vector3i(0, 1, 0), Vector3i(1, 1, 1)])
|
||||
_expect(context.projector.project_facility(&"build.service", SERVICE, _instance(SERVICE, &"service", upper)).is_success(), "service stairs project on BACK landings")
|
||||
_expect(context.path.find_path(bottom, upper, [0, 1], []).is_empty(), "guest without staff permission cannot climb service stairs")
|
||||
_expect(context.path.find_path(bottom, upper, [0, 1], [&"staff"]) == [bottom, upper], "staff can climb service stairs")
|
||||
_expect(context.path.find_path(upper, bottom, [1], [&"staff"]) == [upper, bottom], "staff can descend service stairs")
|
||||
_expect(context.path.find_path(Vector3i(0, 0, 0), upper, [0, 1], [&"staff"]).is_empty(), "service stairs do not invent a FRONT to BACK transfer")
|
||||
_expect(context.path.find_path(upper, Vector3i(1, 1, 1), [1], []) == [upper, Vector3i(1, 1, 1)], "stair landing preserves existing same-floor geometry")
|
||||
_expect(context.path.get_connector(&"infra.connector:service")[&"access_id"] == &"staff", "service access is stored in authoritative connector")
|
||||
|
||||
func _test_lower_landing_and_atomic_demolition() -> void:
|
||||
var bottom := Vector3i(0, 0, 0)
|
||||
var upper := Vector3i(0, 1, 0)
|
||||
var context := _context([])
|
||||
var before := _snapshots(context)
|
||||
var stairs := _instance(GUEST, &"stairs", upper)
|
||||
_expect(not context.projector.project_facility(&"build.stairs", GUEST, stairs).is_success(), "missing lower landing rejects construction projection")
|
||||
_expect(_snapshots(context) == before, "missing landing failure does not consume request or create route")
|
||||
_expect(context.projector.project_facility(&"build.lower", CORRIDOR, _instance(CORRIDOR, &"lower", bottom)).is_success(), "create lower corridor")
|
||||
_expect(context.projector.project_facility(&"build.stairs", GUEST, stairs).is_success(), "same failed request can succeed after lower landing exists")
|
||||
before = _snapshots(context)
|
||||
_expect(not context.projector.unproject_facility(&"remove.lower", &"lower").is_success(), "direct projector demolition protects dependent stair landing")
|
||||
_expect(_snapshots(context) == before, "lower corridor rejection has exact rollback")
|
||||
_expect(context.adapter.remove_route(&"external.remove", &"infra.route:lower").is_success(), "external source mutation can remove landing outside projector boundary")
|
||||
_expect(context.projector.is_projection_stale(), "externally missing lower landing invalidates stair crosslinks")
|
||||
_expect(not context.projector.restore_snapshot(before.projector), "metadata restore cannot mask a missing lower landing")
|
||||
_expect(context.source.restore_snapshot(before.source), "repair lower landing source")
|
||||
_expect(context.path.rebuild_graph([bottom, upper]), "materialize repaired landing graph before snapshot restore")
|
||||
_expect(context.path.restore_snapshot(before.path), "repair connector and floor policy")
|
||||
_expect(context.adapter.restore_snapshot(before.adapter), "repair exact route projection")
|
||||
_expect(context.projector.validate_invariants()[&"ok"], "landing repair restores valid crosslinks")
|
||||
_expect(context.projector.unproject_facility(&"remove.stairs", &"stairs").is_success(), "remove dependent staircase")
|
||||
_expect(context.projector.unproject_facility(&"remove.lower", &"lower").is_success(), "previously rejected lower demolition succeeds once safe")
|
||||
var overlap := _context([bottom])
|
||||
_expect(overlap.projector.project_facility(&"overlap.lower", CORRIDOR, _instance(CORRIDOR, &"lower", bottom)).is_success(), "project lower corridor over existing base landing")
|
||||
_expect(overlap.projector.project_facility(&"overlap.stairs", GUEST, stairs).is_success(), "project stairs over overlapping lower source")
|
||||
_expect(overlap.projector.unproject_facility(&"overlap.remove", &"lower").is_success(), "removing route is safe when base keeps lower landing")
|
||||
_expect(overlap.projector.validate_invariants()[&"ok"] and overlap.path.find_path(bottom, upper) == [bottom, upper], "remaining base landing preserves usable stairs")
|
||||
|
||||
func _test_snapshot_and_schema_validation() -> void:
|
||||
var bottom := Vector3i(0, 0, 0)
|
||||
var context := _context([bottom])
|
||||
var stairs := _instance(GUEST, &"stairs", Vector3i(0, 1, 0))
|
||||
_expect(context.projector.project_facility(&"build", GUEST, stairs).is_success(), "create snapshot fixture")
|
||||
var saved := _snapshots(context)
|
||||
_expect(context.projector.restore_snapshot(saved.projector) and _snapshots(context) == saved, "snapshot roundtrip preserves exact connector and history")
|
||||
for key: StringName in [&"connector_id", &"kind", &"definition_tags", &"rotation", &"footprint"]:
|
||||
var malformed: Dictionary = saved.projector.duplicate(true)
|
||||
match key:
|
||||
&"connector_id": malformed[&"projections"][0][key] = ""
|
||||
&"kind": malformed[&"projections"][0][key] = "guest_corridor"
|
||||
&"definition_tags": malformed[&"projections"][0][key] = ["movement"]
|
||||
&"rotation": malformed[&"projections"][0][key] = 90
|
||||
&"footprint": malformed[&"projections"][0][key] = Vector2i(1, 2)
|
||||
_expect(not context.projector.restore_snapshot(malformed), "reject malformed stair record %s" % key)
|
||||
_expect(_snapshots(context) == saved, "malformed stair restore is atomic")
|
||||
var changed_definition := GUEST.duplicate(true) as FacilityDefinition
|
||||
changed_definition.allowed_rotations = [0, 90]
|
||||
_expect(not context.projector.project_facility(&"bad.rotation", changed_definition, _instance(changed_definition, &"other", Vector3i(0, 1, 0))).is_success(), "definition cannot widen fixed stair orientation")
|
||||
var rotated := _instance(GUEST, &"rotated", Vector3i(0, 1, 0))
|
||||
rotated[&"rotation"] = 90
|
||||
_expect(not context.projector.project_facility(&"bad.instance.rotation", GUEST, rotated).is_success(), "completed stair cannot use rotation with unchanged footprint")
|
||||
_expect(context.path.configure_connector(&"infra.connector:orphan", bottom, Vector3i(0, 1, 0)), "create untracked managed connector fixture")
|
||||
_expect(context.projector.is_projection_stale() and not context.projector.restore_snapshot(saved.projector), "managed orphan connector cannot hide behind valid stair metadata")
|
||||
_expect(context.path.remove_connector(&"infra.connector:orphan"), "remove orphan")
|
||||
_expect(context.path.configure_connector(&"legacy.stairs:external", bottom, Vector3i(0, 1, 0), 100, true, &"permit"), "create separately owned legacy connector")
|
||||
_expect(context.projector.validate_invariants()[&"ok"], "external connector ownership and permissions remain independent")
|
||||
_expect(context.projector.unproject_facility(&"remove", &"stairs").is_success(), "remove owned stairs without removing external connector")
|
||||
_expect(context.path.has_connector(&"legacy.stairs:external") and context.path.get_connector(&"legacy.stairs:external")[&"access_id"] == &"permit", "demolition preserves external connector details")
|
||||
|
||||
func _test_injected_connector_failures() -> void:
|
||||
var controlled := ControlledPath.new()
|
||||
var context := _context([Vector3i(0, 0, 0)], controlled)
|
||||
var stairs := _instance(GUEST, &"stairs", Vector3i(0, 1, 0))
|
||||
var before := _snapshots(context)
|
||||
controlled.fail_configure = true
|
||||
_expect(not context.projector.project_facility(&"build", GUEST, stairs).is_success(), "failed connector write rejects whole projection")
|
||||
_expect(_snapshots(context) == before, "connector write failure rolls back source, route, path and metadata")
|
||||
_expect(context.projector.project_facility(&"build", GUEST, stairs).is_success(), "failed projection request remains retryable")
|
||||
before = _snapshots(context)
|
||||
controlled.fail_remove = true
|
||||
_expect(not context.projector.unproject_facility(&"remove", &"stairs").is_success(), "failed connector removal rejects whole demolition")
|
||||
_expect(_snapshots(context) == before, "connector removal failure restores complete staircase")
|
||||
_expect(context.projector.unproject_facility(&"remove", &"stairs").is_success(), "failed demolition request remains retryable")
|
||||
|
||||
func _context(base_cells: Array[Vector3i], path_value: PathService = null) -> Dictionary:
|
||||
var source := SpatialType.new()
|
||||
var path := path_value if path_value != null else PathType.new()
|
||||
_expect(path.set_vertical_walk_requires_connector(true), "enable explicit vertical connectors")
|
||||
var adapter := AdapterType.new()
|
||||
_expect(adapter.configure(source, path, base_cells), "configure route adapter")
|
||||
var projector := ProjectorType.new()
|
||||
_expect(projector.configure(adapter, source, path), "configure infrastructure projector")
|
||||
return {&"source": source, &"path": path, &"adapter": adapter, &"projector": projector}
|
||||
|
||||
func _instance(definition: FacilityDefinition, instance_id: StringName, origin: Vector3i) -> Dictionary:
|
||||
return {&"instance_id": instance_id, &"definition_id": definition.id, &"origin": origin, &"rotation": 0, &"layer": definition.layer, &"occupied_cells": [origin], &"construction_cost": definition.construction_cost.duplicate(true), &"source_job_id": StringName("build." + String(instance_id)), &"state": &"ACTIVE", &"completed_at": 300}
|
||||
|
||||
func _snapshots(context: Dictionary) -> Dictionary:
|
||||
return {&"source": context.source.create_snapshot(), &"path": context.path.create_snapshot(), &"adapter": context.adapter.create_snapshot(), &"projector": context.projector.create_snapshot()}
|
||||
|
||||
func _expect(ok: bool, label: String) -> void:
|
||||
_checks += 1
|
||||
if not ok:
|
||||
_failures += 1
|
||||
push_error(label)
|
||||
@@ -0,0 +1 @@
|
||||
uid://cw0y2pyvn04ob
|
||||
Reference in New Issue
Block a user