Files
TheTree_mock/tests/supply/test_supply_logistics_bridge.gd
T

361 lines
23 KiB
GDScript

extends SceneTree
## Real construction, hired porter and purchased stock; failure injection is
## limited to route closures, capacity pressure and expired cargo.
var _checks := 0
var _failures := 0
var _runtime: Variant
var _clock: Variant
var _state: Variant
var _source: StringName
var _destination: StringName
var _loaded: Dictionary
var _reserved: Dictionary
const REQUEST := &"supply.bridge_test"
const PORTER := &"staff_gina"
class LateFailInventory extends InventoryService:
var fail_delivery := true
func transfer_available(source_inventory_id: StringName, destination_inventory_id: StringName,
item_id: StringName, quantity: int, operation_id: StringName = &"", absolute_minute: int = -1) -> Dictionary:
var result := super.transfer_available(source_inventory_id, destination_inventory_id, item_id, quantity, operation_id, absolute_minute)
if fail_delivery and String(source_inventory_id).begins_with("cargo:") and result[&"ok"]:
return {&"ok": false, &"code": &"INJECTED_LATE_FAILURE"}
return result
func _initialize() -> void:
call_deferred("_run")
func _run() -> void:
print("Supply bridge: real movement, cargo conservation and recovery")
_runtime = root.get_node("SimulationRuntime")
_clock = root.get_node("GameClock")
_state = root.get_node("GameState")
_clock.reset(1, 6, 0)
_state.reset()
_expect(_runtime.initialize(_clock, _state, 653322)[&"valid"], "Runtime initializes")
_clock.set_paused(true)
_runtime.configure_save_policy({&"enabled": false, &"daily_checkpoint_enabled": false})
for row: Array in [[&"warehouse", Vector3i(-8, 0, 1)], [&"kitchen", Vector3i(2, 0, 1)], [&"staff_door", Vector3i(0, 0, 1)]]:
_expect(_command(&"build_facility", {&"definition_id": row[0], &"origin": row[1]}), "build %s" % row[0])
_clock.advance_minutes(180)
for facility: FacilityInstance in _runtime.facility_service.get_instances():
if facility.definition_id == &"warehouse": _source = facility.instance_id
if facility.definition_id == &"kitchen": _destination = facility.instance_id
if _source.is_empty() or _destination.is_empty():
_expect(false, "normal construction completes")
_finish()
return
_expect(_command(&"hire_staff_candidate", {&"candidate_id": PORTER}), "hire the catalogue porter")
_expect(_command(&"set_staff_work_assignment", {&"staff_id": PORTER, &"facility_id": _source, &"template_id": &"DAY"}), "assign porter to a repeating real shift")
_expect(_worker().state == StaffRuntime.State.COMMUTING, "porter assignment begins a real warehouse commute")
for minute in range(40):
if _worker().state == StaffRuntime.State.AVAILABLE: break
_clock.advance_minutes(1)
_expect(_worker().state == StaffRuntime.State.AVAILABLE and StaffCommuteService.is_at_facility(_worker(), _runtime.facility_service.get_instance(_source)), "porter checks in physically before taking a supply job")
_expect(_command(&"purchase_supplies", {&"warehouse_id": _source, &"item_id": SupplyCatalog.RAW_FOOD, &"quantity": 8}), "purchase the source stock through the ledger")
_expect(_command(&"create_logistics_request", {&"request_id": REQUEST, &"source_inventory_id": _inventory(_source), &"destination_inventory_id": _inventory(_destination), &"item_requirements": {SupplyCatalog.RAW_FOOD: 8}}), "create the reserved supply request")
_expect(_quantity(_inventory(_destination)) == 0, "request creation does not deliver anything")
_test_manual_dispatch_boundary()
_reserved = _runtime.create_snapshot()
var check_in_cell := _worker().current_cell
_clock.advance_minutes(1)
_expect(_request().assigned_carrier_id == PORTER and _worker().current_cell.distance_to(check_in_cell) <= 1.0, "dispatcher selects the checked-in employee without teleporting")
_wait_for(LogisticsRequest.State.DELIVERING, 40)
_expect(_request().state == LogisticsRequest.State.DELIVERING and _worker().current_cell.distance_to(check_in_cell) <= 1.0, "physical pickup starts delivery from the checked-in warehouse access without teleporting")
if _request().state != LogisticsRequest.State.DELIVERING:
_finish()
return
_expect(_quantity(_inventory(_source)) == 0 and _quantity(_cargo()) == 8 and _quantity(_inventory(_destination)) == 0, "pickup moves all eight units into owned cargo only")
_expect(_crosslinks(), "loaded Request, Task, reservations and cargo agree")
_loaded = _runtime.create_snapshot()
_expect(_runtime.restore_snapshot(_loaded), "mid-delivery Runtime snapshot restores")
_test_same_floor_access()
_test_ownership_and_replay()
_test_capacity_wait()
_test_route_recovery()
_test_unavailable_and_expiry()
_test_restore_guards()
_test_pickup_retry()
_test_source_closes_after_assignment()
_test_source_closes_after_loading()
_test_unavailable_before_pickup()
_test_partial_expiry_return()
_test_shift_boundary()
_test_late_delivery_failure()
_finish()
func _test_same_floor_access() -> void:
var upper := FacilityInstance.new().configure(&"upper", &"warehouse", {&"origin": Vector3i(2, 1, 1), &"occupied_cells": [Vector3i(2, 1, 1)]})
_expect(not SupplyLogisticsBridge._cell_touches_facility(Vector3i(2, 0, 1), upper), "goods cannot be picked up from the floor below a warehouse")
_expect(SupplyLogisticsBridge._cell_touches_facility(Vector3i(1, 1, 1), upper), "same-floor doorway remains a valid transfer boundary")
_expect(not _runtime.supply_logistics.find_facility_access_path(Vector3i(2, 0, 1), upper).get(&"reachable", false), "source access routing cannot substitute a corridor below the destination floor")
func _test_ownership_and_replay() -> void:
var owned := _owned_snapshot()
var manual: CommandResult = _runtime.submit_command(&"advance_logistics_request", {&"request_id": REQUEST})
_expect(not manual.is_success() and manual.details.get(&"reason") == &"SUPPLY_DRIVER_REQUIRED", "external advance cannot bypass real arrival")
_expect(_owned_snapshot() == owned, "rejected manual advance leaves inventory, Task and carrier ownership unchanged")
_expect(_runtime.restore_snapshot(_loaded), "rewind the rejected command audit before deterministic replay")
var task_id := _worker().current_task_id
_expect(not _runtime.staff_service.cancel_task(task_id).is_success(), "ordinary cancellation cannot orphan loaded cargo")
_expect(not _runtime.staff_service.remove_staff(PORTER).is_success(), "loaded porter cannot be removed")
_expect(not _runtime.staff_service.retire_staff(PORTER).is_success() and not _worker().is_retiring, "retirement rejects atomically while hauling")
_expect(not _runtime.staff_service.progress_task(task_id, 1, _clock.absolute_minute + 2).is_success(), "generic Task progress cannot bypass delivery")
_wait_for(LogisticsRequest.State.COMPLETED, 40)
_expect(_request().state == LogisticsRequest.State.COMPLETED and _quantity(_inventory(_destination)) == 8, "arrival transfers the purchased stock into kitchen storage")
_expect(_quantity(_cargo()) == 0 and _worker().current_task_id.is_empty() and _worker().carried_inventory_id.is_empty(), "completion clears cargo and employee ownership together")
_expect(_runtime.logistics_dispatcher.get_reserved_load(PORTER) == 0 and _crosslinks(), "completion releases exact dispatcher weight")
var completed: Dictionary = _runtime.create_snapshot()
var end_minute: int = _clock.absolute_minute
_expect(_runtime.restore_snapshot(_loaded), "rewind to a loaded employee")
_clock.advance_minutes(end_minute - _clock.absolute_minute)
_expect(_runtime.create_snapshot() == completed, "loaded save replay produces identical inventory, Tasks, positions and accounts")
func _test_manual_dispatch_boundary() -> void:
var owned := _owned_snapshot()
var manual: CommandResult = _runtime.submit_command(&"dispatch_logistics", {&"carriers": {&"synthetic_porter": {&"capacity": 100, &"current_load": 0, &"path_minutes": 0, &"role": &"logistics"}}})
_expect(manual.is_success() and manual.details[&"assignments"].is_empty(), "external synthetic carrier cannot claim a managed supply request")
_expect(_owned_snapshot() == owned, "manual dispatch does not change reserved stock, Staff Task or request ownership")
func _owned_snapshot() -> Dictionary:
return {&"inventory": _runtime.inventory_service.create_snapshot(), &"staff": _runtime.staff_service.create_snapshot(), &"dispatcher": _runtime.logistics_dispatcher.create_snapshot(), &"request": _request().create_snapshot()}
func _test_capacity_wait() -> void:
_expect(_runtime.restore_snapshot(_loaded), "restore capacity scenario")
_runtime.inventory_service.add_stack(_inventory(_destination), SupplyCatalog.BASIC_MEAL, 40, 100, -1, _clock.absolute_minute)
_clock.advance_minutes(30)
_expect(_request().state == LogisticsRequest.State.DELIVERING and _request().state_reason == "DESTINATION_FULL", "capacity becoming full after pickup pauses delivery")
_expect(_quantity(_cargo()) == 8 and _runtime.logistics_dispatcher.get_reserved_load(PORTER) == 8, "full destination retains all cargo and weight ownership")
_expect(_runtime.restore_snapshot(_runtime.create_snapshot()), "waiting loaded cargo is restorable")
_runtime.inventory_service.transfer_available(_inventory(_destination), &"test.capacity_sink", SupplyCatalog.BASIC_MEAL, 40, &"", _clock.absolute_minute)
_wait_for(LogisticsRequest.State.COMPLETED, 10)
_expect(_quantity(_inventory(_destination)) == 8 and _quantity(_cargo()) == 0, "freeing storage resumes once without losing or duplicating stock")
func _test_route_recovery() -> void:
_expect(_runtime.restore_snapshot(_loaded), "restore route scenario")
var target := _task().target_cell
_runtime.path_service.set_cell_temporary_block(target, true)
_clock.advance_minutes(40)
_expect(_request().state == LogisticsRequest.State.DELIVERING and _quantity(_cargo()) == 8, "blocked delivery route never releases carried stock")
_expect(_worker().current_task_id == _task().task_id and _crosslinks(), "stuck recovery keeps employee and Task linked")
_expect(_runtime.restore_snapshot(_runtime.create_snapshot()), "route-blocked cargo saves and restores")
_runtime.path_service.set_cell_temporary_block(target, false)
_wait_for(LogisticsRequest.State.COMPLETED, 40)
_expect(_request().state == LogisticsRequest.State.COMPLETED and _quantity(_inventory(_destination)) == 8, "restoring the real route resumes delivery")
func _test_unavailable_and_expiry() -> void:
_expect(_runtime.restore_snapshot(_loaded), "restore unavailable scenario")
_worker().health = 0
_worker().transition_to(StaffRuntime.State.INCAPACITATED, &"TEST", _clock.absolute_minute)
var cell := _worker().current_cell
_clock.advance_minutes(3)
_expect(_worker().current_cell == cell and _quantity(_cargo()) == 8, "incapacitated porter does not move or lose cargo")
_expect(_runtime.restore_snapshot(_runtime.create_snapshot()), "incapacitated cargo owner is restorable")
_worker().health = 100
_worker().transition_to(StaffRuntime.State.MOVING_TO_TASK, &"TEST_RECOVERED", _clock.absolute_minute)
_wait_for(LogisticsRequest.State.COMPLETED, 40)
_expect(_request().state == LogisticsRequest.State.COMPLETED, "employee recovery resumes the retained delivery")
_expect(_runtime.restore_snapshot(_loaded), "restore spoilage scenario")
_worker().health = 0
_worker().transition_to(StaffRuntime.State.INCAPACITATED, &"TEST", _clock.absolute_minute)
var inventory_snapshot: Dictionary = _runtime.inventory_service.create_snapshot()
for owner: Dictionary in inventory_snapshot[&"inventories"]:
if owner[&"owner_inventory_id"] == String(_cargo()):
for stack: Dictionary in owner[&"stacks"]:
stack[&"expires_at"] = _clock.absolute_minute + 1
_expect(_runtime.inventory_service.restore_snapshot(inventory_snapshot), "inject a cargo expiry boundary")
_clock.advance_minutes(1)
_expect(_request().state == LogisticsRequest.State.CANCELLED and _quantity(_cargo()) == 0, "expired cargo enters inventory waste and closes the impossible delivery")
_expect(_worker().state == StaffRuntime.State.INCAPACITATED and _worker().current_task_id.is_empty(), "spoilage cleanup preserves incapacitation while releasing the Task")
_expect(_runtime.logistics_dispatcher.get_reserved_load(PORTER) == 0 and _crosslinks(), "spoilage releases weight without a ghost assignment")
func _test_restore_guards() -> void:
_expect(_runtime.restore_snapshot(_loaded), "restore crosslink scenario")
var stable: Dictionary = _runtime.create_snapshot()
var bad := stable.duplicate(true)
for task: Dictionary in bad[&"systems"][&"runtime"][&"staff"][&"tasks"]:
if task[&"task_type"] == String(SupplyLogisticsBridge.DELIVERY_TYPE):
task[&"target_cell"] = [99, 99, 1]
_expect(not _runtime.restore_snapshot(bad) and _runtime.create_snapshot() == stable, "restore rejects a remote delivery target atomically")
var worker := _worker()
worker.carried_inventory_id = &"cargo:somebody_else"
_expect(not _crosslinks(), "crosslink validator rejects stolen cargo ownership")
worker.carried_inventory_id = _cargo()
var task := _task()
var saved_type := task.task_type
task.task_type = SupplyLogisticsBridge.PICKUP_TYPE
_expect(not _crosslinks(), "crosslink validator rejects pickup Task paired with loaded request")
task.task_type = saved_type
_expect(_crosslinks(), "valid ownership survives rejected candidates")
func _test_pickup_retry() -> void:
_expect(_runtime.restore_snapshot(_reserved), "restore before pickup dispatch")
_clock.advance_minutes(1)
var old_task := _worker().current_task_id
var stopped: Dictionary = _runtime.supply_logistics.handle_path_stuck(old_task, _clock.absolute_minute)
_expect(stopped[&"ok"] and _request().state == LogisticsRequest.State.BLOCKED, "unloaded route failure blocks the request and cancels the approach")
_expect(_runtime.inventory_service.available_quantity(_inventory(_source), SupplyCatalog.RAW_FOOD) == 8 and _worker().current_task_id.is_empty(), "failed approach releases source reservation and employee together")
_expect(_runtime.logistics_dispatcher.get_reserved_load(PORTER) == 0 and _crosslinks(), "failed approach retains no dispatcher load")
_expect(_runtime.restore_snapshot(_runtime.create_snapshot()), "blocked unloaded request restores")
_wait_for(LogisticsRequest.State.DELIVERING, 40)
_expect(_request().state == LogisticsRequest.State.DELIVERING and _worker().current_task_id != old_task, "retries reserve stock again and create a fresh attempt Task")
_wait_for(LogisticsRequest.State.COMPLETED, 40)
_expect(_quantity(_inventory(_destination)) == 8 and _quantity(_cargo()) == 0, "retry delivers the same stock exactly once")
func _test_source_closes_after_assignment() -> void:
_expect(_runtime.restore_snapshot(_reserved), "restore source-closure approach scenario")
_clock.advance_minutes(1)
var old_task := _worker().current_task_id
_expect(_request().state == LogisticsRequest.State.ASSIGNED and not old_task.is_empty(), "source closure begins after assigning the real pickup Task")
_expect(_command(&"set_facility_policy", {&"facility_id": _source, &"patch": {&"business_open": false}}), "close the source while its porter approaches")
_wait_for(LogisticsRequest.State.BLOCKED, 40)
_expect(_request().state == LogisticsRequest.State.BLOCKED and _request().state_reason == "SOURCE_CLOSED", "arrival at a newly closed source blocks before loading")
_expect(_quantity(_inventory(_source)) == 8 and _quantity(_cargo()) == 0 and _quantity(_inventory(_destination)) == 0, "closed source retains every purchased unit without transfer")
_expect(_runtime.inventory_service.available_quantity(_inventory(_source), SupplyCatalog.RAW_FOOD) == 8 and _worker().current_task_id.is_empty(), "blocked pickup releases both source stock and employee reservations")
_expect(_runtime.logistics_dispatcher.get_reserved_load(PORTER) == 0 and _crosslinks(), "closed-source cleanup retains no dispatcher or Task ghost")
_expect(_runtime.restore_snapshot(_runtime.create_snapshot()), "blocked closed-source state passes strict Runtime restore")
_expect(_command(&"set_facility_policy", {&"facility_id": _source, &"patch": {&"business_open": true}}), "reopen the source for a retry")
_wait_for(LogisticsRequest.State.DELIVERING, 40)
_expect(_request().state == LogisticsRequest.State.DELIVERING and _worker().current_task_id != old_task and _quantity(_cargo()) == 8, "reopening retries with a fresh Task and physical pickup")
var loaded_cell := _worker().current_cell
_wait_for(LogisticsRequest.State.COMPLETED, 40)
_expect(_request().state == LogisticsRequest.State.COMPLETED and _worker().current_cell != loaded_cell and _quantity(_inventory(_destination)) == 8, "reopened source stock reaches the kitchen by actual porter movement")
_expect(_quantity(_inventory(_source)) == 0 and _quantity(_cargo()) == 0 and _runtime.logistics_dispatcher.get_reserved_load(PORTER) == 0 and _crosslinks(), "reopened source delivery consumes and releases ownership exactly once")
func _test_source_closes_after_loading() -> void:
_expect(_runtime.restore_snapshot(_loaded), "restore loaded source-closure scenario")
_expect(_command(&"set_facility_policy", {&"facility_id": _source, &"patch": {&"business_open": false}}), "close the source after loading")
var cell := _worker().current_cell
_wait_for(LogisticsRequest.State.COMPLETED, 40)
_expect(_request().state == LogisticsRequest.State.COMPLETED and _worker().current_cell != cell and _quantity(_inventory(_destination)) == 8, "already-loaded porter can finish delivery to an open destination")
_expect(_quantity(_cargo()) == 0 and _runtime.logistics_dispatcher.get_reserved_load(PORTER) == 0 and _crosslinks(), "delivery after source closure clears cargo and carrier ownership")
func _test_unavailable_before_pickup() -> void:
_expect(_runtime.restore_snapshot(_reserved), "restore unloaded health failure")
_clock.advance_minutes(1)
_worker().health = 0
_worker().transition_to(StaffRuntime.State.INCAPACITATED, &"TEST", _clock.absolute_minute)
_clock.advance_minutes(1)
_expect(_request().state == LogisticsRequest.State.BLOCKED and _worker().current_task_id.is_empty(), "incapacitation before pickup releases approach ownership")
_expect(_worker().state == StaffRuntime.State.INCAPACITATED and _runtime.inventory_service.available_quantity(_inventory(_source), SupplyCatalog.RAW_FOOD) == 8, "unloaded health failure preserves health state and releases reserved food")
_expect(_runtime.restore_snapshot(_runtime.create_snapshot()) and _crosslinks(), "unloaded health failure saves without expired reservation links")
func _test_partial_expiry_return() -> void:
_expect(_runtime.restore_snapshot(_loaded), "restore partial-spoilage scenario")
var inventory: Dictionary = _runtime.inventory_service.create_snapshot()
for owner: Dictionary in inventory[&"inventories"]:
if owner[&"owner_inventory_id"] == String(_cargo()):
owner[&"stacks"][0][&"quantity"] = 4
_expect(_runtime.inventory_service.restore_snapshot(inventory), "split loaded cargo into two expiry lots")
_runtime.inventory_service.add_stack(_cargo(), SupplyCatalog.RAW_FOOD, 4, 100, _clock.absolute_minute + 1, _clock.absolute_minute)
_clock.advance_minutes(1)
_expect(_request().state == LogisticsRequest.State.DELIVERING and _task().task_type == SupplyLogisticsBridge.RETURN_TYPE and _quantity(_cargo()) == 4, "partial expiry retargets the surviving cargo to its source")
_expect(_runtime.restore_snapshot(_runtime.create_snapshot()), "returning cargo saves its destination in the existing Task")
_worker().health = 0
_worker().transition_to(StaffRuntime.State.INCAPACITATED, &"TEST", _clock.absolute_minute)
_clock.advance_minutes(1)
_expect(_crosslinks(), "unavailability reason cannot erase return destination")
_worker().health = 100
_worker().transition_to(StaffRuntime.State.MOVING_TO_TASK, &"RECOVERED", _clock.absolute_minute)
_wait_for(LogisticsRequest.State.CANCELLED, 40)
_expect(_request().state_reason == "CARGO_EXPIRED_RETURNED" and _quantity(_inventory(_source)) == 4 and _quantity(_inventory(_destination)) == 0, "return unloads only unspoiled stock at the original warehouse")
_expect(_quantity(_cargo()) == 0 and _runtime.logistics_dispatcher.get_reserved_load(PORTER) == 0 and _crosslinks(), "partial-expiry return releases all cargo ownership")
func _test_shift_boundary() -> void:
_expect(_runtime.restore_snapshot(_loaded), "restore end-of-shift scenario")
var target := _task().target_cell
_runtime.path_service.set_cell_temporary_block(target, true)
_clock.advance_minutes(1081 - _clock.absolute_minute)
_expect(_worker().is_overtime and not _worker().current_task_id.is_empty() and _quantity(_cargo()) == 8, "18:00 shift end retains loaded non-interruptible work")
_expect(_runtime.restore_snapshot(_runtime.create_snapshot()), "overtime delivery is restorable")
_runtime.path_service.set_cell_temporary_block(target, false)
_wait_for(LogisticsRequest.State.COMPLETED, 40)
_clock.advance_minutes(1)
_expect(_worker().state == StaffRuntime.State.OFF_DUTY and _worker().current_task_id.is_empty() and _quantity(_inventory(_destination)) == 8, "employee ends the shift after safe unloading")
func _test_late_delivery_failure() -> void:
_expect(_runtime.restore_snapshot(_loaded), "restore delivery transaction failure")
var fault := LateFailInventory.new()
_expect(fault.restore_snapshot(_runtime.inventory_service.create_snapshot()), "install failure after inventory transfer has already mutated")
_runtime.inventory_service = fault
_clock.advance_minutes(30)
_expect(_request().state == LogisticsRequest.State.DELIVERING and _request().state_reason == "DELIVERY_RETRY", "late inventory failure rolls the request back to owned delivery")
_expect(_quantity(_cargo()) == 8 and _quantity(_inventory(_destination)) == 0 and _runtime.logistics_dispatcher.get_reserved_load(PORTER) == 8, "failed delivery reverses all physical transfer and dispatcher changes")
_expect(_crosslinks() and not _worker().current_task_id.is_empty(), "failed delivery preserves the employee and Task pair")
fault.fail_delivery = false
_wait_for(LogisticsRequest.State.COMPLETED, 10)
_expect(_request().state == LogisticsRequest.State.COMPLETED and _quantity(_inventory(_destination)) == 8 and _quantity(_cargo()) == 0, "clearing transient failure retries the same operation exactly once")
_expect(_runtime.restore_snapshot(_runtime.create_snapshot()), "transaction retry leaves a restorable Runtime")
func _wait_for(state: int, minutes: int) -> void:
for index: int in minutes:
if _request().state == state: return
_clock.advance_minutes(1)
func _command(type: StringName, payload: Dictionary) -> bool:
var result: CommandResult = _runtime.submit_command(type, payload)
if not result.is_success(): print(result.message, result.details)
return result.is_success()
func _inventory(id: StringName) -> StringName:
return _runtime.facility_service.get_instance(id).local_inventory_id
func _request() -> LogisticsRequest:
return _runtime.logistics_requests[REQUEST]
func _worker() -> StaffRuntime:
return _runtime.staff_service.get_staff(PORTER)
func _task() -> StaffTask:
return _runtime.staff_service.get_task(_worker().current_task_id)
func _cargo() -> StringName:
return StringName("cargo:%s" % PORTER)
func _quantity(inventory_id: StringName) -> int:
return _runtime.inventory_service.total_quantity(inventory_id, SupplyCatalog.RAW_FOOD)
func _crosslinks() -> bool:
return SupplyLogisticsBridge.validate_crosslinks(_runtime.logistics_requests, _runtime.staff_service, _runtime.inventory_service, _runtime.facility_service)
func _expect(condition: bool, message: String) -> void:
_checks += 1
if not condition:
_failures += 1
push_error(message)
func _finish() -> void:
print("%s: %d checks / %d failures" % ["PASS" if _failures == 0 else "FAIL", _checks, _failures])
quit(_failures)