204 lines
16 KiB
GDScript
204 lines
16 KiB
GDScript
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)
|