- ElevatorService snapshot v2 carries the shaft-wide wait; v1 saves (written
with every elevator dynamic_cost 0) migrate by deriving it on load, and any
v2 mismatch, including all zeros, is rejected atomically. A genuine save
from 50ceca0 is kept as tests/fixtures/p14-03/legacy_rush_v1.
- A guest aboard a cabin no longer decides, leaves or despawns until the
elevator sets it down at a real landing; previously a rider told to leave
vanished from a moving cabin.
- test_route_wait_choice now proves the rush detour and route loss with a
worker who actually walks, every shared rider's arrival cell, service
elevator and stair congestion, legacy migration and a new-PID restart.
- Register completion markers for the seven suites added this session and
bring the P14 ledger in line with P14-02's completion.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
407 lines
21 KiB
GDScript
407 lines
21 KiB
GDScript
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)
|