extends SceneTree ## P14-03 route choice with real waiting. Two guest shafts with real guests at ## real cells; a registered worker physically walks the chosen route. Covers a ## whole-shaft rush detour, shared cabins within capacity with every rider's ## arrival cell, cancellation, route loss on demolition, service-elevator and ## stair congestion pricing, warp-exit congestion, legacy v1 save migration and ## v2 tamper rejection. "-- writer"/"-- reader" add a new-PID rush save check. const START := Vector3i(1, 0, 0) const GOAL := Vector3i(2, 1, 0) const LEGACY_FIXTURE := "res://tests/fixtures/p14-03/legacy_rush_v1" const LEGACY_COPY := "user://tests/p14_03_legacy_rush_copy" const PID_SAVE := "user://saves/p14_03_route_new_pid" const PID_EXPECTED := "user://saves/p14_03_route_new_pid_expected.json" const PID_CONTINUE := 60 var _checks := 0 var _failures := 0 var _serial := 0 var _clock: Variant var _state: Variant var _runtime: Variant func _initialize() -> void: call_deferred("_run") func _run() -> void: var args := OS.get_cmdline_user_args() var mode := "reader" if "reader" in args else ("writer" if "writer" in args else "full") print("Route wait choice (P14-03) %s pid %d" % [mode, OS.get_process_id()]) _new_runtime() if mode == "reader": _read_new_pid() else: _layout() if mode == "writer": _write_new_pid() else: _test_shaft_rush() _test_worker_walks_detour() _test_shared_cabin() _test_route_loss() _test_service_elevator_wait() _test_stair_congestion() _test_warp_exit_congestion() _test_legacy_v1_migration() _free_runtime() print("%s: %d checks" % ["PASS" if _failures == 0 else "FAIL", _checks]) quit(_failures) func _new_runtime() -> void: _clock = load("res://autoload/game_clock.gd").new() _state = load("res://autoload/game_state.gd").new() _runtime = load("res://autoload/simulation_runtime.gd").new() _clock.reset(1, 6, 0) _state.reset() _runtime.initialize(_clock, _state, 1403) _clock.set_paused(true) func _free_runtime() -> void: _runtime.free(); _clock.free(); _state.free() func _cmd(kind: StringName, payload: Dictionary) -> CommandResult: _serial += 1 return _runtime.submit_command(kind, payload, &"player", &"", 0, _clock.absolute_minute, StringName("p1403.%d" % _serial)) func _build(id: StringName, origin: Vector3i) -> void: var result := _cmd(&"build_facility", {&"definition_id": id, &"origin": origin}) _expect(result.is_success(), "build %s at %s %s" % [id, origin, result.message]) func _next_evaluation() -> void: _clock.advance_minutes(10 - int(_clock.absolute_minute) % 10 + 1) func _layout() -> void: _expect(_cmd(&"change_resources", {&"deltas": {&"gold": 40000, &"materials": 400}}).is_success(), "fund the fixture") # FRONT: warps x-4, shaft A x0 (floors 0-3), shaft B x4 (floors 0-1), floor-1 link x2-3. # BACK: service shaft x6, security station x-2 for the worker's shift. _build(&"guest_elevator", Vector3i(0, 0, 0)) _build(&"guest_elevator", Vector3i(4, 0, 0)) _build(&"mana_warp", Vector3i(-4, 0, 0)) _build(&"service_elevator", Vector3i(6, 0, 1)) _build(&"security_station", Vector3i(-2, 0, 1)) _clock.advance_minutes(360) _build(&"guest_elevator", Vector3i(0, 2, 0)) _build(&"mana_warp", Vector3i(-4, 2, 0)) for x: int in [2, 3]: _build(&"guest_corridor", Vector3i(x, 1, 0)) _clock.advance_minutes(360) _next_evaluation() _expect(_runtime.elevators.get_report()[&"shafts"].size() == 3, "two guest shafts and one service shaft") func _spawn(guest_id: StringName, cell: Vector3i) -> bool: var result := _cmd(&"spawn_guest", {&"guest_id": guest_id, &"guest_type_id": &"traveler", &"options": {&"current_cell": cell}}) _expect(result.is_success(), "spawn real guest %s at %s %s" % [guest_id, cell, result.message]) return result.is_success() func _route() -> Array: return _runtime.path_service.find_path(START, GOAL) func _uses_shaft_a(path: Array) -> bool: return path.size() > 1 and path[1] == Vector3i(0, 0, 0) func _queue_rush(count: int) -> Array[StringName]: var riders: Array[StringName] = [] for i: int in count: var guest_id := StringName("rush_%02d" % i) if _spawn(guest_id, Vector3i(0, 0, 0)): var actor_id := StringName("guest:%s" % guest_id) var trip: Dictionary = _runtime.elevators.request_trip(actor_id, Vector3i(0, 0, 0), Vector3i(0, 3, 0), _clock.absolute_minute) _expect(bool(trip.get(&"ok", false)), "real guest %s queues for floor 3" % guest_id) riders.append(actor_id) return riders func _shaft_a_link() -> StringName: return _runtime.path_service.get_step_connector(Vector3i(0, 0, 0), Vector3i(0, 1, 0), []) func _test_shaft_rush() -> void: var idle := _route() var idle_cost: int = _runtime.path_service.last_path_cost _expect(_uses_shaft_a(idle), "idle tower routes through the nearer shaft A") var riders := _queue_rush(12) var rush := _route() _expect(not _uses_shaft_a(rush) and rush.has(Vector3i(4, 0, 0)), "whole-shaft rush diverts the 0->1 trip to idle shaft B") _expect(_runtime.path_service.last_path_cost > idle_cost, "diverted route is dearer than the idle one") _expect(int(_runtime.path_service.get_connector(_shaft_a_link())[&"dynamic_cost"]) == 12 * PathService.ORTHOGONAL_MOVE_COST, "shaft A 0->1 prices all 12 riders waiting elsewhere in the shaft") var saved: Dictionary = _runtime.create_snapshot() _expect(int(saved[&"systems"][&"runtime"][&"elevators"][&"version"]) == ElevatorService.SNAPSHOT_VERSION, "new saves carry elevator snapshot v2") _expect(_runtime.restore_snapshot(saved.duplicate(true)) and _runtime.create_snapshot() == saved, "rush wait costs and queues survive save restore exactly") for bad: int in [0, 300]: var tampered: Dictionary = saved.duplicate(true) for row: Dictionary in tampered[&"systems"][&"runtime"][&"path"][&"connectors"]: if row[&"id"] == _shaft_a_link(): row[&"dynamic_cost"] = bad var live: Dictionary = _runtime.create_snapshot() _expect(not _runtime.restore_snapshot(tampered) and _runtime.create_snapshot() == live, "v2 save with shaft wait %d (disagreeing with its queue) is rejected atomically" % bad) var a_route_during_rush: int = _runtime.path_service.calculate_path_cost(idle) for actor_id: StringName in riders: _runtime.elevators.cancel_actor(actor_id) _expect(int(_runtime.path_service.get_connector(_shaft_a_link())[&"dynamic_cost"]) == 0, "cancelled rush clears the shaft wait") _expect(_runtime.path_service.calculate_path_cost(idle) == a_route_during_rush - 12 * PathService.ORTHOGONAL_MOVE_COST, "cleared shaft wait removes exactly the queued riders' cost from shaft A's route") _expect(_runtime.restore_snapshot(saved), "rewind to the rush state") func _register_walker(staff_id: StringName, cell: Vector3i) -> bool: var registered := _cmd(&"register_staff", {&"staff_id": staff_id, &"role_id": &"porter", &"options": { &"current_cell": cell, &"state": StaffRuntime.State.OFF_DUTY, &"role_tags": [&"porter"], &"permitted_task_tags": [&"work"], &"skills": {&"operation": 50}}}) var station: FacilityInstance = null for facility: FacilityInstance in _runtime.facility_service.get_instances(): if facility.definition_id == &"security_station": station = facility var minute: int = posmod(_clock.absolute_minute, 1440) var shift := _cmd(&"create_staff_shift", {&"staff_id": staff_id, &"template_id": &"FLEX", &"start_day": _clock.absolute_minute / 1440, &"start_minute_of_day": minute, &"end_minute_of_day": mini(minute + 600, 1439), &"facility_id": station.instance_id}) _expect(registered.is_success() and shift.is_success(), "register real walker %s at %s %s %s" % [staff_id, cell, registered.message, shift.message]) return registered.is_success() and shift.is_success() ## Sends the walker to GOAL through an ordinary Staff task; returns visited cells. func _walk(staff_id: StringName, task_id: StringName) -> Array: var created := _cmd(&"create_staff_task", {&"task_id": task_id, &"task_type": &"inspect", &"priority": 90, &"options": { &"target_cell": GOAL, &"required_role_tags": [&"porter"], &"task_tags": [&"work"], &"estimated_minutes": 1}}) _expect(created.is_success(), "walk task %s created %s" % [task_id, created.message]) var visited: Array = [] for minute: int in 180: _clock.advance_minutes(1) var worker: StaffRuntime = _runtime.staff_service.get_staff(staff_id) if worker == null: break if visited.is_empty() or visited.back() != worker.current_cell: visited.append(worker.current_cell) if worker.current_cell == GOAL: break return visited func _test_worker_walks_detour() -> void: # Still in the rush state saved above: shaft A has 12 riders queued. var before: Dictionary = _runtime.create_snapshot() if not _register_walker(&"walker_rush", START): return var visited := _walk(&"walker_rush", &"walk_rush") _expect(not visited.is_empty() and visited.back() == GOAL, "real walker reaches the goal during the rush") _expect(visited.has(Vector3i(4, 0, 0)) and visited.has(Vector3i(4, 1, 0)), "walker physically rides shaft B (%s)" % [visited]) _expect(not visited.has(Vector3i(0, 1, 0)), "walker never rides the congested shaft A") _expect(_runtime.restore_snapshot(before), "rewind walker drill") func _shaft_id_at(x: int, layer: int) -> StringName: for shaft: Dictionary in _runtime.elevators.get_report()[&"shafts"]: var bottom: Vector3i = (shaft[&"stops"] as Array).front() if bottom.x == x and bottom.z == layer: return shaft[&"shaft_id"] return &"" func _test_shared_cabin() -> void: var before: Dictionary = _runtime.create_snapshot() for trip: Dictionary in _runtime.elevators.get_report()[&"trips"]: _runtime.elevators.cancel_actor(StringName(trip[&"actor_id"])) var riders: Array[StringName] = [] for i: int in 6: var guest_id := StringName("share_%02d" % i) if not _spawn(guest_id, Vector3i(4, 0, 0)): continue # Real guest with a floor-1 destination; the Runtime's own path travel # requests the elevator ride (no test-side trip injection). var guest: GuestRuntime = _runtime.guest_service.get_guest(guest_id) guest.path_handle = {&"target_cell": Vector3i(4, 1, 0)} _expect(bool(_runtime.guest_service.transition_guest(guest_id, GuestRuntime.State.ENTERING, _clock.absolute_minute, &"TEST_ENTRY").get(&"ok", false)) and bool(_runtime.guest_service.transition_guest(guest_id, GuestRuntime.State.MOVING, _clock.absolute_minute, &"TEST_DESTINATION").get(&"ok", false)), "rider %s heads for floor 1" % guest_id) riders.append(StringName("guest:%s" % guest_id)) var requested := {} var leaver: StringName = &"" var shaft_b := _shaft_id_at(4, 0) var max_boarded := 0 var arrival_cell := {} for minute: int in 60: _clock.advance_minutes(1) var boarded := 0 for trip: Dictionary in _runtime.elevators.get_report()[&"trips"]: if trip[&"shaft_id"] == shaft_b and trip[&"state"] == PathService.ELEVATOR_REQUEST_BOARDED: boarded += 1 if leaver.is_empty(): # A boarded guest told to leave (patience/closing) must ride on. leaver = StringName(trip[&"actor_id"]) _expect(bool(_runtime.guest_service.begin_leaving(StringName(String(leaver).trim_prefix("guest:")), _clock.absolute_minute, &"TEST_LEAVE_WHILE_ABOARD").get(&"ok", false)), "boarded rider %s starts leaving mid-ride" % leaver) max_boarded = maxi(max_boarded, boarded) for actor_id: StringName in riders: if not _runtime.elevators.get_actor_trip(actor_id).is_empty(): requested[actor_id] = true continue if arrival_cell.has(actor_id) or not requested.has(actor_id): continue var guest: GuestRuntime = _runtime.guest_service.get_guest(StringName(String(actor_id).trim_prefix("guest:"))) arrival_cell[actor_id] = guest.current_cell if guest != null else null _expect(max_boarded > 1 and max_boarded <= 4, "riders share the cabin up to its capacity of 4 (max %d)" % max_boarded) _expect(requested.size() == riders.size(), "the Runtime requested a ride for every rider") var arrived_at := -1 var despawned_at := -1 for event: SimulationDomainEvent in _runtime.domain_events.recent_events(): if not (leaver in event.entity_ids or StringName(String(leaver).trim_prefix("guest:")) in event.entity_ids): continue if event.event_type == &"ELEVATOR_ARRIVED" and event.payload.get(&"cell") == Vector3i(4, 1, 0): arrived_at = event.absolute_minute if event.event_type == &"GUEST_DESPAWNED": despawned_at = event.absolute_minute _expect(not leaver.is_empty() and arrived_at >= 0 and (despawned_at < 0 or despawned_at >= arrived_at), "a rider leaving mid-ride rides on and alights at the floor-1 landing before it can leave (arrived %d, left %d)" % [arrived_at, despawned_at]) _expect(arrival_cell.size() == riders.size(), "every rider's trip ends") for actor_id: StringName in riders: if actor_id == leaver: continue _expect(arrival_cell.get(actor_id) == Vector3i(4, 1, 0), "%s exists and stands on the floor-1 landing when the ride ends (%s)" % [actor_id, arrival_cell.get(actor_id)]) _expect(_runtime.restore_snapshot(before), "rewind shared-cabin drill") func _test_route_loss() -> void: var before: Dictionary = _runtime.create_snapshot() for trip: Dictionary in _runtime.elevators.get_report()[&"trips"]: _runtime.elevators.cancel_actor(StringName(trip[&"actor_id"])) var shaft_b_facility: StringName = &"" for facility: Dictionary in _runtime.construction_service.get_facilities(): if facility[&"definition_id"] == &"guest_elevator" and facility[&"origin"] == Vector3i(4, 0, 0): shaft_b_facility = facility[&"instance_id"] _expect(_cmd(&"demolish_facility", {&"instance_id": shaft_b_facility}).is_success(), "demolish shaft B") _expect(_uses_shaft_a(_route()), "route loss falls back to the remaining shaft") if _register_walker(&"walker_loss", START): var visited := _walk(&"walker_loss", &"walk_loss") _expect(not visited.is_empty() and visited.back() == GOAL and visited.has(Vector3i(0, 1, 0)), "real walker reaches the goal through shaft A after the loss (%s)" % [visited]) _expect(_runtime.restore_snapshot(_runtime.create_snapshot()), "post-loss state restores") _expect(_runtime.restore_snapshot(before), "rewind route-loss drill") func _test_service_elevator_wait() -> void: var ps: PathService = _runtime.path_service var lower := Vector3i(6, 0, 1) var upper := Vector3i(6, 1, 1) var staff_only: Array[StringName] = [&"staff"] var layers: Array[int] = [WorldService.LAYER_FRONT, WorldService.LAYER_BACK] _expect(ps.find_path(lower, upper).is_empty() and not ps.find_path(lower, upper, layers, staff_only).is_empty(), "service shaft is staff-only") var idle_cost: int = ps.last_path_cost var before: Dictionary = _runtime.create_snapshot() for i: int in 4: var staff_id := StringName("svc_rider_%d" % i) _expect(_cmd(&"register_staff", {&"staff_id": staff_id, &"role_id": &"porter", &"options": {&"current_cell": lower, &"state": StaffRuntime.State.OFF_DUTY, &"role_tags": [&"porter"]}}).is_success(), "register real worker %s on the service landing" % staff_id) _expect(bool(_runtime.elevators.request_trip(StringName("staff:%s" % staff_id), lower, upper, _clock.absolute_minute, staff_only).get(&"ok", false)), "real worker %s queues on the service shaft" % staff_id) ps.find_path(lower, upper, layers, staff_only) _expect(ps.last_path_cost > idle_cost, "queued workers raise the service elevator's A* cost") _expect(_runtime.restore_snapshot(before), "rewind service drill") func _test_stair_congestion() -> void: var ps: PathService = _runtime.path_service var before: Dictionary = _runtime.create_snapshot() _expect(_cmd(&"build_facility", {&"definition_id": &"guest_corridor", &"origin": Vector3i(-1, 1, 0)}).is_success(), "build a floor-1 corridor beside shaft A") _expect(_cmd(&"build_facility", {&"definition_id": &"guest_stairs", &"origin": Vector3i(-2, 1, 0)}).is_success(), "build stairs to floor 1") _clock.advance_minutes(20) var landing := Vector3i(-2, 1, 0) var foot := Vector3i(-2, 0, 0) var quiet: Array = ps.find_path(foot, landing) var quiet_cost: int = ps.last_path_cost _expect(quiet == [foot, landing], "stairs connect the two floors") for i: int in 6: _spawn(StringName("stair_crowd_%d" % i), landing) ps.find_path(foot, landing) _expect(ps.last_path_cost > quiet_cost, "a crowded stair landing raises the stair route's A* cost") _expect(_runtime.restore_snapshot(before), "rewind stair drill") func _test_warp_exit_congestion() -> void: var ps: PathService = _runtime.path_service var lower := Vector3i(-4, 0, 0) var upper := Vector3i(-4, 2, 0) var quiet: Array = ps.find_path(lower, upper) var quiet_cost: int = ps.last_path_cost _expect(quiet == [lower, upper], "idle warp is the cheapest way up") var crowd := 0 for i: int in 6: if _spawn(StringName("crowd_%02d" % i), upper): crowd += 1 ps.find_path(lower, upper) _expect(crowd > 0 and ps.last_path_cost > quiet_cost, "a crowded warp exit raises the warp route's A* cost") ## A genuine save written by 50ceca0 (pre-P14-03, elevator snapshot v1) with 12 ## riders queued for floor 3 and every elevator dynamic_cost 0. func _test_legacy_v1_migration() -> void: DirAccess.make_dir_recursive_absolute(ProjectSettings.globalize_path(LEGACY_COPY)) for member: String in ["world.bin", "manifest.json", "thumbnail.png", "journal.log"]: var bytes := FileAccess.get_file_as_bytes(LEGACY_FIXTURE.path_join(member)) var file := FileAccess.open(LEGACY_COPY.path_join(member), FileAccess.WRITE) file.store_buffer(bytes) file.close() var loaded := SaveService.load_snapshot(LEGACY_COPY) _expect(loaded[&"ok"], "legacy v1 package loads from disk") if not loaded[&"ok"]: return var legacy: Dictionary = loaded[&"snapshot"] _expect(int(legacy[&"systems"][&"runtime"][&"elevators"][&"version"]) == 1, "fixture really is elevator snapshot v1") var live: Dictionary = _runtime.create_snapshot() var bad: Dictionary = legacy.duplicate(true) for row: Dictionary in bad[&"systems"][&"runtime"][&"path"][&"connectors"]: if String(row[&"id"]).begins_with("transport.") and int(row[&"dynamic_cost"]) == 0: row[&"dynamic_cost"] = 500 break _expect(not _runtime.restore_snapshot(bad) and _runtime.create_snapshot() == live, "v1 save with a nonzero wait (never written by v1) is rejected atomically") _expect(_runtime.restore_snapshot(legacy.duplicate(true)), "legacy v1 rush save migrates") _expect(int(_runtime.path_service.get_connector(_shaft_a_link())[&"dynamic_cost"]) == 12 * PathService.ORTHOGONAL_MOVE_COST, "migration derives the shaft-wide wait from the legacy queue") _expect(not _uses_shaft_a(_route()), "migrated rush diverts like a fresh one") var migrated: Dictionary = _runtime.create_snapshot() _expect(int(migrated[&"systems"][&"runtime"][&"elevators"][&"version"]) == ElevatorService.SNAPSHOT_VERSION and _runtime.restore_snapshot(migrated) and _runtime.create_snapshot() == migrated, "migrated state saves as v2 and round-trips exactly") SaveService.delete_save(LEGACY_COPY) func _write_new_pid() -> void: _queue_rush(12) _register_walker(&"walker_pid", START) var snapshot: Dictionary = _runtime.create_snapshot() var serial_at_save := _serial _expect(SaveService.save_snapshot(PID_SAVE, snapshot)[&"ok"], "writer saves the rush state") var visited := _walk(&"walker_pid", &"walk_pid") _clock.advance_minutes(maxi(PID_CONTINUE - visited.size(), 0)) var file := FileAccess.open(PID_EXPECTED, FileAccess.WRITE) file.store_string(JSON.stringify({"writer_pid": OS.get_process_id(), "serial_at_save": serial_at_save, "visited": var_to_str(visited), "minute": _clock.absolute_minute, "digest": SimulationDigest.compute(_runtime.create_snapshot())})) file.close() _expect(not visited.is_empty() and visited.back() == GOAL and visited.has(Vector3i(4, 1, 0)), "writer walker reaches the goal through shaft B") func _read_new_pid() -> void: var expected: Variant = JSON.parse_string(FileAccess.get_file_as_string(PID_EXPECTED)) _expect(expected is Dictionary, "reader finds writer record") if not expected is Dictionary: return _expect(int(expected["writer_pid"]) != OS.get_process_id(), "reader runs in a different process") var loaded := SaveService.load_snapshot(PID_SAVE) _expect(loaded[&"ok"] and _runtime.restore_snapshot(loaded[&"snapshot"]), "reader restores the rush save") _expect(int(_runtime.path_service.get_connector(_shaft_a_link())[&"dynamic_cost"]) == 12 * PathService.ORTHOGONAL_MOVE_COST, "restored shaft wait matches the queue") # Same player command sequence as the writer after its save. _serial = int(expected["serial_at_save"]) var visited := _walk(&"walker_pid", &"walk_pid") _clock.advance_minutes(maxi(PID_CONTINUE - visited.size(), 0)) _expect(var_to_str(visited) == String(expected["visited"]), "walker takes the same physical route after restart") _expect(_clock.absolute_minute == int(expected["minute"]) and SimulationDigest.compute(_runtime.create_snapshot()) == String(expected["digest"]), "whole continued state digest matches the writer") SaveService.delete_save(PID_SAVE) DirAccess.remove_absolute(ProjectSettings.globalize_path(PID_EXPECTED)) func _expect(condition: bool, label: String) -> void: _checks += 1 if condition: print(" ✓ %s" % label) else: _failures += 1 push_error(" ✗ %s" % label)