214 lines
13 KiB
GDScript
214 lines
13 KiB
GDScript
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)
|