Capture current source, content data, tests, asset licenses, and original-specification traceability. Original implementation remains incomplete. The accepted matching UI generation passed 154 suites and 12,253 checks, with eight rendered staff-contract screens reviewed. Latest native Windows and full-original acceptance remain outstanding.
104 lines
6.4 KiB
GDScript
104 lines
6.4 KiB
GDScript
extends RefCounted
|
|
|
|
## Compare actual guest/staff access queries on two isolated Runtime states.
|
|
## Reachability alone is not a relocation or in-flight transport migration.
|
|
static func inspect(before: Variant, after: Variant, facility_id: StringName) -> Dictionary:
|
|
var result := {&"ok": false, &"errors": [], &"guest_replans": [], &"checked_routes": 0}
|
|
var old_room: FacilityInstance = before.facility_service.get_instance(facility_id)
|
|
var new_room: FacilityInstance = after.facility_service.get_instance(facility_id)
|
|
if old_room == null or new_room == null:
|
|
result[&"errors"].append(&"FACILITY_REQUIRED")
|
|
return result
|
|
_check(result, before.get_facility_access_report(facility_id), after.get_facility_access_report(facility_id), &"ENTRANCE_ACCESS_LOST", &"")
|
|
for guest: GuestRuntime in before.guest_service.get_guests():
|
|
if guest.target_facility_id != facility_id: continue
|
|
var restored: GuestRuntime = after.guest_service.get_guest(guest.guest_id)
|
|
if restored == null:
|
|
result[&"errors"].append({&"code": &"GUEST_MISSING", &"id": guest.guest_id})
|
|
continue
|
|
var old_route: Dictionary = before._find_facility_access_path(guest.current_cell, old_room)
|
|
var new_route: Dictionary = after._find_facility_access_path(restored.current_cell, new_room)
|
|
_check(result, old_route, new_route, &"GUEST_ACCESS_LOST", guest.guest_id)
|
|
if restored.state == GuestRuntime.State.MOVING and new_route[&"reachable"]:
|
|
var target: Vector3i = after._path_handle_target(restored.path_handle, restored.current_cell)
|
|
# A new best destination must be applied explicitly to the saved route.
|
|
if target != new_route[&"target_cell"]:
|
|
result[&"guest_replans"].append({&"id": guest.guest_id, &"old_target": target, &"target": new_route[&"target_cell"]})
|
|
for worker: StaffRuntime in before.staff_service.get_staff_members():
|
|
var assignment: Dictionary = before.staff_service.get_work_assignment(worker.staff_id)
|
|
var rest: Dictionary = before.get_staff_rest_report(worker.staff_id)
|
|
var resting_here := StringName(rest.get(&"facility_id", &"")) == facility_id
|
|
if worker.assigned_facility_id != facility_id and StringName(assignment.get(&"facility_id", &"")) != facility_id and not resting_here: continue
|
|
var restored: StaffRuntime = after.staff_service.get_staff(worker.staff_id)
|
|
if restored == null:
|
|
result[&"errors"].append({&"code": &"STAFF_MISSING", &"id": worker.staff_id})
|
|
continue
|
|
_check(result, before.staff_commute.find_facility_access_path(worker.current_cell, old_room), after.staff_commute.find_facility_access_path(restored.current_cell, new_room), &"STAFF_ACCESS_LOST", worker.staff_id)
|
|
var access_cells := {}
|
|
for cell: Vector3i in new_room.occupied_cells:
|
|
for offset: Vector3i in [Vector3i.ZERO, Vector3i.LEFT, Vector3i.RIGHT]: access_cells[cell + offset] = true
|
|
for task: StaffTask in before.staff_service.get_tasks():
|
|
if task.is_terminal() or (task.target_entity_id != facility_id and task.rescue_facility_id != facility_id): continue
|
|
var restored: StaffTask = after.staff_service.get_task(task.task_id)
|
|
if restored == null or restored.create_snapshot() != task.create_snapshot():
|
|
result[&"errors"].append({&"code": &"TASK_STATE_CHANGED", &"id": task.task_id})
|
|
continue
|
|
if not access_cells.has(task.target_cell):
|
|
result[&"errors"].append({&"code": &"TASK_DESTINATION_CHANGED", &"id": task.task_id})
|
|
continue
|
|
if task.assigned_staff_id.is_empty(): continue
|
|
var old_worker: StaffRuntime = before.staff_service.get_staff(task.assigned_staff_id)
|
|
var new_worker: StaffRuntime = after.staff_service.get_staff(task.assigned_staff_id)
|
|
if old_worker == null or new_worker == null:
|
|
result[&"errors"].append({&"code": &"STAFF_MISSING", &"id": task.assigned_staff_id})
|
|
continue
|
|
_check(result, {&"reachable":_task_reachable(before, old_worker, task)}, {&"reachable":_task_reachable(after, new_worker, restored)}, &"TASK_ACCESS_LOST", task.task_id)
|
|
result[&"ok"] = result[&"errors"].is_empty() and result[&"guest_replans"].is_empty()
|
|
return result
|
|
|
|
static func _check(result: Dictionary, old_route: Dictionary, new_route: Dictionary, code: StringName, actor_id: StringName) -> void:
|
|
result[&"checked_routes"] += 1
|
|
if old_route.get(&"reachable", false) and not new_route.get(&"reachable", false):
|
|
result[&"errors"].append({&"code": code, &"id": actor_id})
|
|
|
|
## Apply only to an isolated candidate. Validate the entire batch before changes.
|
|
## Position, movement credit, service request, wallet and transport stay intact.
|
|
static func retarget_guests(candidate: Variant, report: Dictionary) -> Dictionary:
|
|
var result := {&"ok": false, &"errors": [], &"changed": []}
|
|
if not report.get(&"errors", []).is_empty():
|
|
result[&"errors"].append(&"ACCESS_LOSS")
|
|
return result
|
|
var updates: Array = []
|
|
var seen := {}
|
|
for plan: Dictionary in report.get(&"guest_replans", []):
|
|
var id := StringName(plan.get(&"id", &""))
|
|
var guest: GuestRuntime = candidate.guest_service.get_guest(id)
|
|
if guest == null or seen.has(id) or guest.state != GuestRuntime.State.MOVING or not guest.path_handle is Dictionary or not plan.get(&"target") is Vector3i:
|
|
result[&"errors"].append(&"INVALID_REPLAN")
|
|
return result
|
|
seen[id] = true
|
|
if candidate._path_handle_target(guest.path_handle, guest.current_cell) != plan.get(&"old_target"):
|
|
result[&"errors"].append(&"STALE_REPLAN")
|
|
return result
|
|
if not candidate.elevators.get_actor_trip(candidate._guest_path_actor_id(id)).is_empty():
|
|
result[&"errors"].append(&"TRANSPORT_REPLAN_REQUIRED")
|
|
return result
|
|
if candidate.path_service.find_path(guest.current_cell, plan[&"target"]).is_empty():
|
|
result[&"errors"].append(&"TARGET_UNREACHABLE")
|
|
return result
|
|
updates.append({&"guest": guest, &"target": plan[&"target"]})
|
|
for update: Dictionary in updates:
|
|
var guest: GuestRuntime = update[&"guest"]
|
|
var handle: Dictionary = guest.path_handle.duplicate(true)
|
|
handle[&"target_cell"] = update[&"target"]
|
|
handle[&"graph_revision"] = candidate.path_service.graph_revision
|
|
guest.path_handle = handle
|
|
result[&"changed"].append(guest.guest_id)
|
|
result[&"ok"] = true
|
|
return result
|
|
|
|
static func _task_reachable(runtime: Variant, worker: StaffRuntime, task: StaffTask) -> bool:
|
|
if task.task_type in [&"rescue_pickup", &"rescue_transport"]:
|
|
return not runtime.path_service.find_backyard_path(worker.current_cell, task.target_cell, true).is_empty()
|
|
return not runtime.path_service.find_path(worker.current_cell, task.target_cell, [WorldService.LAYER_FRONT, WorldService.LAYER_BACK], [&"staff"]).is_empty()
|