176 lines
12 KiB
GDScript
176 lines
12 KiB
GDScript
extends SceneTree
|
|
|
|
var _checks := 0
|
|
var _failures := 0
|
|
var _runtime: Variant
|
|
var _clock: Variant
|
|
var _state: Variant
|
|
var _serial := 0
|
|
const STAFF := &"staff_akane"
|
|
const ACTOR := &"staff:staff_akane"
|
|
|
|
|
|
class LateFailElevators extends ElevatorService:
|
|
var fail_next_sync := false
|
|
|
|
func synchronize(configurations: Array, minute: int) -> Dictionary:
|
|
var result := super.synchronize(configurations, minute)
|
|
if fail_next_sync and result[&"ok"]:
|
|
fail_next_sync = false
|
|
last_error = "Injected failure after actual transport mutation."
|
|
return {&"ok": false, &"reason": &"INJECTED_LATE_FAILURE"}
|
|
return result
|
|
|
|
|
|
func _initialize() -> void:
|
|
call_deferred("_run")
|
|
|
|
|
|
func _run() -> void:
|
|
_runtime = load("res://autoload/simulation_runtime.gd").new()
|
|
_clock = load("res://autoload/game_clock.gd").new()
|
|
_state = load("res://autoload/game_state.gd").new()
|
|
_clock.reset(1, 6, 0)
|
|
_state.reset()
|
|
_expect(_runtime.initialize(_clock, _state, 81662)[&"valid"], "boundary fixture initializes")
|
|
_runtime.configure_save_policy({&"enabled": false, &"daily_checkpoint_enabled": false})
|
|
_clock.set_paused(true)
|
|
_expect(_command(&"build_facility", {&"definition_id": &"guest_elevator", &"origin": Vector3i.ZERO}).is_success(), "build lower lift from real starting budget")
|
|
_clock.advance_minutes(310)
|
|
_expect(_car().get(&"stops", []).size() == 2, "completed lower lift has two real stops")
|
|
_expect(_command(&"hire_staff_candidate", {&"candidate_id": STAFF}).is_success(), "hire actual ground-floor actor")
|
|
var idle: Dictionary = _runtime.create_snapshot()
|
|
var lower_id: StringName = _car()[&"module_ids"][0]
|
|
var extension := _command(&"build_facility", {&"definition_id": &"guest_elevator", &"origin": Vector3i(0, 2, 0)})
|
|
_expect(extension.is_success(), "idle shaft accepts contiguous extension")
|
|
var extension_id: StringName
|
|
for job: ConstructionJob in _runtime.construction_service.get_jobs():
|
|
if job.origin == Vector3i(0, 2, 0): extension_id = job.job_id
|
|
var freeze: Dictionary = _runtime.advance_staff_commute(STAFF, Vector3i(0, 1, 0), _clock.absolute_minute)
|
|
_expect(freeze.get(&"reason") == &"ELEVATOR_WAIT" and _runtime.elevators.get_report()[&"trips"].is_empty(), "in-progress extension freezes new boarding at Runtime movement boundary")
|
|
_expect(_runtime.staff_service.get_staff(STAFF).current_cell == Vector3i.ZERO, "extension freeze never walks actor vertically")
|
|
_clock.advance_minutes(5)
|
|
_expect(_runtime.restore_snapshot(_runtime.create_snapshot()), "in-progress extension and frozen empty car survive save restore")
|
|
_expect(_command(&"cancel_construction", {&"job_id": extension_id}).is_success(), "public cancellation releases extension freeze")
|
|
var resumed: Dictionary = _runtime.advance_staff_commute(STAFF, Vector3i(0, 1, 0), _clock.absolute_minute)
|
|
_expect(resumed.get(&"reason") == &"ELEVATOR_WAIT" and _runtime.elevators.get_actor_trip(ACTOR).get(&"state") == PathService.ELEVATOR_REQUEST_QUEUED, "after cancellation the same real actor queues for a cabin")
|
|
_check_busy_commands(lower_id, "queued")
|
|
_check_operations_rollback("queued")
|
|
_clock.advance_minutes(1)
|
|
_expect(_runtime.elevators.get_actor_trip(ACTOR).get(&"state") == PathService.ELEVATOR_REQUEST_BOARDED, "queued actor boards through next real minute")
|
|
_check_busy_commands(lower_id, "boarded")
|
|
_check_operations_rollback("boarded")
|
|
var reassigned := _command(&"set_staff_work_assignment", {&"staff_id": STAFF, &"facility_id": &"", &"template_id": &"DAY"})
|
|
_expect(reassigned.is_success() and _runtime.elevators.get_actor_trip(ACTOR).get(&"state") == PathService.ELEVATOR_REQUEST_BOARDED, "unassigning boarded worker preserves cabin ride until safe arrival")
|
|
_expect(_runtime.restore_snapshot(_runtime.create_snapshot()), "reassigned boarded worker remains a valid Runtime save")
|
|
_clock.advance_minutes(20)
|
|
_expect(_runtime.elevators.get_actor_trip(ACTOR).is_empty() and _runtime.staff_service.get_staff(STAFF).current_cell == Vector3i(0, 1, 0), "unassigned worker still physically finishes the existing ride")
|
|
_expect(_runtime.restore_snapshot(idle), "restore original empty shaft for topology completion cases")
|
|
var shaft_id: StringName = _car()[&"shaft_id"]
|
|
_expect(_command(&"set_elevator_operation", {&"shaft_id": shaft_id, &"operator_open": false}).is_success(), "player stops idle shaft before extension")
|
|
_expect(_command(&"build_facility", {&"definition_id": &"guest_elevator", &"origin": Vector3i(0, 2, 0)}).is_success(), "stopped idle shaft can extend")
|
|
_clock.advance_minutes(310)
|
|
_expect(_car()[&"stops"].size() == 4 and _car()[&"module_ids"].size() == 2 and not _car()[&"operator_open"], "successful extension keeps one cabin and persists player-stopped policy")
|
|
_expect(_runtime.restore_snapshot(_runtime.create_snapshot()), "completed extended shaft has exact save crosslinks")
|
|
var upper_id: StringName = _car()[&"module_ids"][1]
|
|
_expect(_command(&"demolish_facility", {&"instance_id": upper_id}).is_success(), "empty upper module demolition succeeds")
|
|
_expect(_car()[&"stops"].size() == 2 and _car()[&"module_ids"] == [lower_id] and not _car()[&"operator_open"], "upper demolition removes its stops without re-enabling lower car")
|
|
_expect(_runtime.restore_snapshot(_runtime.create_snapshot()), "partially demolished shaft remains saveable")
|
|
var injected := LateFailElevators.new()
|
|
injected.configure(_runtime.path_service)
|
|
_expect(injected.restore_snapshot(_runtime.elevators.create_snapshot()), "late-failure injector starts from exact live cabin state")
|
|
_runtime.elevators = injected
|
|
injected.fail_next_sync = true
|
|
var before := _participants()
|
|
var failure := _command(&"demolish_facility", {&"instance_id": lower_id})
|
|
_expect(not failure.is_success() and failure.details.get(&"rolled_back", false), "post-demolition transport failure rolls back the public transaction")
|
|
_expect(_participants() == before, "late rollback restores world, money, projections, construction, power and physical car exactly")
|
|
_expect(_runtime.restore_snapshot(_runtime.create_snapshot()), "late demolition rollback produces a valid canonical save")
|
|
_expect(_command(&"demolish_facility", {&"instance_id": lower_id}).is_success(), "empty final shaft demolition succeeds on retry")
|
|
_expect(_runtime.elevators.get_report()[&"shafts"].is_empty() and _runtime.path_service.get_vertical_walk_blockers().is_empty(), "final demolition clears cabin and all shaft vertical blockers")
|
|
_expect(_runtime.restore_snapshot(_runtime.create_snapshot()), "fully demolished world remains exactly saveable")
|
|
_test_shift_end(idle)
|
|
_runtime.free()
|
|
_clock.free()
|
|
_state.free()
|
|
print("Transport boundaries: %d checks, %d failures" % [_checks, _failures])
|
|
quit(0 if _failures == 0 else 1)
|
|
|
|
|
|
func _test_shift_end(idle: Dictionary) -> void:
|
|
_expect(_runtime.restore_snapshot(idle), "shift-boundary fixture restores ordinary lower elevator")
|
|
_expect(_command(&"build_facility", {&"definition_id": &"cafeteria", &"origin": Vector3i(2, 1, 0)}).is_success(), "real upper-floor cafeteria accepts commuting staff")
|
|
_clock.advance_minutes(190)
|
|
_clock.advance_minutes(1078 - _clock.absolute_minute)
|
|
var cafeteria: StringName
|
|
for facility: FacilityInstance in _runtime.facility_service.get_instances():
|
|
if facility.definition_id == &"cafeteria": cafeteria = facility.instance_id
|
|
_expect(not cafeteria.is_empty() and _command(&"hire_staff_candidate", {&"candidate_id": &"staff_chika"}).is_success(), "hire real cook just before DAY shift ends")
|
|
var assignment := {&"staff_id": &"staff_chika", &"facility_id": cafeteria, &"template_id": &"DAY"}
|
|
_expect(_command(&"set_staff_work_assignment", assignment).is_success(), "cook receives real persistent DAY work plan")
|
|
var before_commute: Dictionary = _runtime.create_snapshot()
|
|
_clock.advance_minutes(1)
|
|
var actor_id := &"staff:staff_chika"
|
|
var queued: Dictionary = _runtime.elevators.get_actor_trip(actor_id)
|
|
_expect(queued.get(&"state") == PathService.ELEVATOR_REQUEST_QUEUED, "17:59 cook naturally queues during actual commute")
|
|
var unchanged := _command(&"set_staff_work_assignment", assignment)
|
|
_expect(unchanged.is_success() and unchanged.details.get(&"reason") == &"ASSIGNMENT_UNCHANGED" and _runtime.elevators.get_actor_trip(actor_id) == queued, "unchanged work plan preserves the same FIFO request")
|
|
_clock.advance_minutes(1)
|
|
var worker: StaffRuntime = _runtime.staff_service.get_staff(&"staff_chika")
|
|
_expect(worker.state == StaffRuntime.State.OFF_DUTY and _runtime.elevators.get_actor_trip(actor_id).is_empty(), "18:00 shift end cancels stale commute before the available cabin can board")
|
|
_expect(worker.current_cell == Vector3i.ZERO and _runtime.path_service.get_active_elevator_request(actor_id).is_empty(), "expired commute leaves cook at original landing without orphaned Path ownership")
|
|
_expect(_runtime.restore_snapshot(_runtime.create_snapshot()), "cancelled shift-boundary wait is a valid Runtime save")
|
|
_expect(_runtime.restore_snapshot(before_commute), "restore 17:58 for already-boarded shift-boundary case")
|
|
_runtime.advance_staff_commute(&"staff_chika", Vector3i(0, 1, 0), _clock.absolute_minute)
|
|
_clock.advance_minutes(1)
|
|
_expect(_runtime.elevators.get_actor_trip(actor_id).get(&"state") == PathService.ELEVATOR_REQUEST_BOARDED, "cook queued a minute earlier is already aboard before shift end")
|
|
_clock.advance_minutes(1)
|
|
worker = _runtime.staff_service.get_staff(&"staff_chika")
|
|
_expect(worker.state == StaffRuntime.State.OFF_DUTY and _runtime.elevators.get_actor_trip(actor_id).get(&"state") == PathService.ELEVATOR_REQUEST_BOARDED, "shift end retains already-boarded rider until physical alighting")
|
|
_expect(_runtime.restore_snapshot(_runtime.create_snapshot()), "off-duty boarded employee remains a valid save")
|
|
_clock.advance_minutes(20)
|
|
worker = _runtime.staff_service.get_staff(&"staff_chika")
|
|
_expect(worker.current_cell == Vector3i(0, 1, 0) and _runtime.elevators.get_actor_trip(actor_id).is_empty(), "off-duty boarded cook completes exactly one physical arrival")
|
|
|
|
|
|
func _check_busy_commands(module_id: StringName, label: String) -> void:
|
|
var before := _participants()
|
|
var build := _command(&"build_facility", {&"definition_id": &"guest_elevator", &"origin": Vector3i(0, 2, 0)})
|
|
_expect(not build.is_success() and build.details.get(&"reason") == &"ELEVATOR_BUSY" and _participants() == before, "%s passengers reject extension before any participant changes" % label)
|
|
var demolition := _command(&"demolish_facility", {&"instance_id": module_id})
|
|
_expect(not demolition.is_success() and demolition.details.get(&"reason") == &"ELEVATOR_BUSY" and _participants() == before, "%s passengers reject demolition before any participant changes" % label)
|
|
_expect(_runtime.restore_snapshot(_runtime.create_snapshot()), "%s rejected topology commands preserve a valid save" % label)
|
|
|
|
|
|
func _check_operations_rollback(label: String) -> void:
|
|
var before: Dictionary = _runtime.create_snapshot()
|
|
var token: Dictionary = _runtime._capture_operations_external_state()
|
|
_expect(not token.is_empty(), "%s ride can participate in Operations snapshot boundary" % label)
|
|
var stopped := _command(&"set_elevator_operation", {&"shaft_id": _car()[&"shaft_id"], &"operator_open": false})
|
|
_expect(stopped.is_success() and _runtime.elevators.get_report()[&"trips"].is_empty(), "%s rollback fixture actually mutates car and Path ownership through public command" % label)
|
|
_expect(_runtime._restore_operations_external_state(token), "%s Operations rollback restores external participants" % label)
|
|
_expect(_runtime.create_snapshot() == before, "%s Operations rollback restores full Runtime including command IDs and event state exactly" % label)
|
|
|
|
|
|
func _participants() -> Dictionary:
|
|
var snapshot: Dictionary = _runtime._capture_infrastructure_demolition_state()
|
|
snapshot.erase(&"domain_events")
|
|
snapshot.erase(&"id_generator")
|
|
return snapshot
|
|
|
|
|
|
func _car() -> Dictionary:
|
|
var shafts: Array = _runtime.elevators.get_report()[&"shafts"]
|
|
return shafts[0] if not shafts.is_empty() else {}
|
|
|
|
|
|
func _command(kind: StringName, payload: Dictionary) -> CommandResult:
|
|
_serial += 1
|
|
return _runtime.submit_command(kind, payload, &"player", &"", 0, _clock.absolute_minute, StringName("transport.boundary.%d" % _serial))
|
|
|
|
|
|
func _expect(condition: bool, label: String) -> void:
|
|
_checks += 1
|
|
if not condition: _failures += 1
|
|
print("%s: %s" % ["PASS" if condition else "FAIL", label])
|