Connect physical elevators and staff commutes to construction, saves, and cutaway UI
This commit is contained in:
@@ -0,0 +1,258 @@
|
||||
extends SceneTree
|
||||
|
||||
var _checks := 0
|
||||
var _failures := 0
|
||||
var _path: PathService
|
||||
var _elevators: ElevatorService
|
||||
var _config: Dictionary
|
||||
var _minute := 0
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
call_deferred("_run")
|
||||
|
||||
|
||||
func _run() -> void:
|
||||
var clock := root.get_node_or_null("GameClock")
|
||||
if clock != null: clock.set_paused(true)
|
||||
_test_grouping()
|
||||
_test_capacity_and_time()
|
||||
_test_access_and_shared_cabin()
|
||||
_test_restore_and_emergency()
|
||||
_test_mutations_and_cancellation()
|
||||
_test_snapshot_rejections()
|
||||
print("Elevator service: %d checks, %d failures" % [_checks, _failures])
|
||||
quit(0 if _failures == 0 else 1)
|
||||
|
||||
|
||||
func _setup(layer := 0, floors := 4) -> void:
|
||||
_path = PathService.new()
|
||||
var cells: Array[Vector3i] = []
|
||||
for y: int in range(floors + 2):
|
||||
cells.append(Vector3i(0, y, layer))
|
||||
_path.rebuild_graph(cells)
|
||||
_elevators = ElevatorService.new()
|
||||
_elevators.configure(_path)
|
||||
_config = {&"shaft_id": StringName("transport.shaft.%d.0.0.%d" % [layer, 2 if layer == 0 else 3]),
|
||||
&"module_ids": [&"module.0", &"module.1"], &"stops": [], &"width": 2 if layer == 0 else 3,
|
||||
&"capacity": 4 if layer == 0 else 8, &"travel_minutes_per_floor": 10 if layer == 0 else 5,
|
||||
&"access_id": &"" if layer == 0 else &"staff"}
|
||||
for y: int in range(floors): (_config[&"stops"] as Array).append(Vector3i(0, y, layer))
|
||||
_expect(_elevators.synchronize([_config], 0)[&"ok"], "shaft initializes")
|
||||
_minute = 0
|
||||
|
||||
|
||||
func _request(actor: StringName, from: int, to: int, layer := 0) -> Dictionary:
|
||||
var access: Array[StringName] = []
|
||||
if layer == 1: access.append(&"staff")
|
||||
return _elevators.request_trip(actor, Vector3i(0, from, layer), Vector3i(0, to, layer), _minute, access)
|
||||
|
||||
|
||||
func _tick(count := 1) -> Array[Dictionary]:
|
||||
var arrivals: Array[Dictionary] = []
|
||||
for i: int in range(count):
|
||||
_minute += 1
|
||||
arrivals.append_array(_elevators.advance_one_minute(_minute))
|
||||
return arrivals
|
||||
|
||||
|
||||
func _car() -> Dictionary:
|
||||
return _elevators.get_report()[&"shafts"][0]
|
||||
|
||||
|
||||
func _test_grouping() -> void:
|
||||
var registry := DefinitionRegistry.new()
|
||||
registry.rebuild()
|
||||
var records: Array = []
|
||||
for row: Array in [[&"g2", &"guest_elevator", Vector3i(0, 2, 0)], [&"g0", &"guest_elevator", Vector3i(0, 0, 0)], [&"g6", &"guest_elevator", Vector3i(0, 6, 0)], [&"b0", &"service_elevator", Vector3i(0, 0, 1)], [&"other", &"reception", Vector3i(6, 0, 0)]]:
|
||||
records.append({&"instance_id": row[0], &"definition_id": row[1], &"origin": row[2]})
|
||||
var configs := ElevatorService.configurations_from_facilities(records, registry)
|
||||
_expect(configs.size() == 3, "adjacent modules form one shaft, gaps and layers form separate shafts")
|
||||
_expect(configs[0][&"stops"] == [Vector3i(0, 0, 0), Vector3i(0, 1, 0), Vector3i(0, 2, 0), Vector3i(0, 3, 0)], "each occupied integer floor gets a landing")
|
||||
_expect(configs[0][&"module_ids"] == [&"g0", &"g2"], "construction ordering does not change module/stop ordering")
|
||||
_expect(configs[2][&"capacity"] == 8 and configs[2][&"travel_minutes_per_floor"] == 5 and configs[2][&"access_id"] == &"staff", "BACK shaft has eight seats, five minutes per floor and staff access")
|
||||
records.reverse()
|
||||
_expect(ElevatorService.configurations_from_facilities(records, registry) == configs, "configuration generation is deterministic")
|
||||
|
||||
|
||||
func _test_capacity_and_time() -> void:
|
||||
_setup()
|
||||
_expect(_path.create_snapshot()[&"connectors"].size() == 6, "four stops expose six bidirectional direct links")
|
||||
for i: int in range(5): _expect(_request(StringName("guest/%d" % i), 0, 3)[&"ok"], "guest queues without moving")
|
||||
_expect(_car()[&"passenger_count"] == 0 and _car()[&"waiting_count"] == 5, "requests do not board before simulation advances")
|
||||
_expect(_tick().is_empty() and _car()[&"passenger_count"] == 4 and _car()[&"waiting_count"] == 1, "shared cabin boards at most four FRONT actors")
|
||||
_expect(_car()[&"phase"] == &"BOARDING" and _car()[&"remaining_minutes"] == 6, "doors take two minutes plus one per boarding actor")
|
||||
_expect(_tick(6).is_empty() and _car()[&"phase"] == &"TRAVEL", "boarding duration elapses before cabin travels")
|
||||
_expect(_tick(15).is_empty() and is_equal_approx(_car()[&"visual_floor"], 1.5), "cabin interpolates actual half journey without arriving")
|
||||
_expect(_tick(15).is_empty() and _car()[&"phase"] == &"ALIGHTING", "three FRONT floors take thirty minutes before alighting")
|
||||
_expect(_tick(5).is_empty(), "passengers remain owned while doors are unloading")
|
||||
var arrivals := _tick()
|
||||
_expect(arrivals.size() == 4 and _minute == 43, "first four arrivals happen only after both door intervals and travel")
|
||||
for arrival: Dictionary in arrivals:
|
||||
_expect(arrival[&"cell"] == Vector3i(0, 3, 0) and not arrival[&"emergency"] and _path.get_active_elevator_request(arrival[&"actor_id"]).is_empty(), "arrival transfers actor once and releases Path ownership")
|
||||
_expect(_car()[&"phase"] == &"PICKUP" and _car()[&"waiting_count"] == 1, "remaining guest waits for same cabin to return")
|
||||
arrivals = _tick(66)
|
||||
_expect(arrivals.size() == 1 and arrivals[0][&"actor_id"] == &"guest/4" and _car()[&"phase"] == &"IDLE", "fifth guest waits for real return and completes without starvation")
|
||||
_expect(_tick(10).is_empty(), "completed request never arrives twice")
|
||||
var read := _elevators.get_report()
|
||||
read[&"shafts"][0][&"boarded"].append(&"tamper")
|
||||
_expect(_car()[&"boarded"].is_empty(), "report returns defensive copies")
|
||||
|
||||
|
||||
func _test_access_and_shared_cabin() -> void:
|
||||
_setup(1)
|
||||
_expect(not _elevators.request_trip(&"guest/no_access", Vector3i(0, 0, 1), Vector3i(0, 3, 1), 0)[&"ok"], "BACK elevator rejects actor without staff access")
|
||||
for i: int in range(9): _expect(_request(StringName("staff/%d" % i), 0, 3, 1)[&"ok"], "authorized staff queues")
|
||||
_tick()
|
||||
_expect(_car()[&"passenger_count"] == 8 and _car()[&"waiting_count"] == 1, "BACK cabin shares eight seats across requests")
|
||||
_tick(10)
|
||||
_expect(_car()[&"phase"] == &"TRAVEL" and _car()[&"remaining_minutes"] == 15, "BACK shaft uses five minutes per floor")
|
||||
_tick(15)
|
||||
_expect(_car()[&"phase"] == &"ALIGHTING", "BACK travel duration is physical")
|
||||
_expect(_tick(10).size() == 8, "BACK eight passengers alight together after paid time")
|
||||
_setup()
|
||||
_request(&"a", 0, 3)
|
||||
_request(&"b", 1, 2)
|
||||
_tick()
|
||||
_expect(_car()[&"passenger_count"] == 1 and _car()[&"waiting_count"] == 1, "different floor pair cannot create a second cabin")
|
||||
_expect(_elevators.get_actor_trip(&"b")[&"state"] == PathService.ELEVATOR_REQUEST_QUEUED, "other connector actor remains waiting while shared car moves")
|
||||
_expect(_elevators.owns_connector(_elevators.get_actor_trip(&"a")[&"connector_id"]), "transport identifies its owned path step")
|
||||
_expect(not _elevators.owns_connector(&"door.other"), "unrelated door connector is not transport-owned")
|
||||
var all := _tick(100)
|
||||
_expect(all.size() == 2 and all[0][&"actor_id"] == &"a" and all[1][&"actor_id"] == &"b", "FIFO serves different floor pair after first journey")
|
||||
|
||||
|
||||
func _test_restore_and_emergency() -> void:
|
||||
_setup()
|
||||
_request(&"rider", 0, 3)
|
||||
_tick(20)
|
||||
_expect(_car()[&"phase"] == &"TRAVEL" and _car()[&"visual_floor"] > 1.0, "midride fixture is physically between floors")
|
||||
var state := _elevators.create_snapshot()
|
||||
var path_state := _path.create_snapshot()
|
||||
var future := _tick(40)
|
||||
var final_state := _elevators.create_snapshot()
|
||||
_expect(_path.restore_snapshot(path_state), "Path queue/boarded state restores")
|
||||
_expect(_elevators.restore_snapshot(state), "midride save restores against matching Path ownership: %s" % _elevators.last_error)
|
||||
_minute = state[&"current_minute"]
|
||||
_expect(_tick(40) == future and _elevators.create_snapshot() == final_state, "restored trip produces identical arrivals and final snapshot")
|
||||
_setup()
|
||||
_request(&"aboard", 0, 3)
|
||||
_request(&"waiting", 2, 3)
|
||||
_tick(20)
|
||||
var released := _elevators.set_powered(_config[&"shaft_id"], false, _minute)
|
||||
_expect(released.size() == 2, "power failure releases both waiting and boarded owners")
|
||||
_expect(released[0][&"actor_id"] == &"aboard" and released[0][&"cell"] == Vector3i(0, 2, 0) and released[0][&"emergency"], "power failure unloads boarded actor at nearest valid floor")
|
||||
_expect(released[1][&"actor_id"] == &"waiting" and released[1][&"cell"] == Vector3i(0, 2, 0) and released[1][&"cancelled"], "waiting cancellation stays on original landing for replan")
|
||||
_expect(_car()[&"operator_open"] and not _car()[&"powered"] and not _car()[&"operational"], "power loss does not alter player operation policy")
|
||||
_expect(_path.get_active_elevator_request(&"aboard").is_empty() and _elevators.get_actor_trip(&"waiting").is_empty(), "emergency leaves no orphan owner")
|
||||
_expect(not _request(&"stopped", 0, 3)[&"ok"], "stopped elevator rejects new boarding")
|
||||
_expect(_elevators.restore_snapshot(_elevators.create_snapshot()), "stopped elevator restores with Path outage state")
|
||||
_elevators.set_operator_open(_config[&"shaft_id"], false, _minute)
|
||||
_elevators.set_powered(_config[&"shaft_id"], true, _minute)
|
||||
_expect(not _car()[&"operator_open"] and _car()[&"powered"] and not _car()[&"operational"], "power restoration preserves player-requested stop")
|
||||
_expect(_elevators.restore_snapshot(_elevators.create_snapshot()), "player-stopped powered elevator state restores")
|
||||
_elevators.set_operator_open(_config[&"shaft_id"], true, _minute)
|
||||
_expect(_car()[&"operational"] and _request(&"resumed", 2, 0)[&"ok"], "player reopens restored elevator")
|
||||
_expect(_tick(27).size() == 1, "reopened cabin resumes from emergency landing with real travel")
|
||||
_setup()
|
||||
_request(&"blocked_destination", 0, 3)
|
||||
_tick(20)
|
||||
_path.configure_cell_traffic(Vector3i(0, 3, 0), 4, 0, true)
|
||||
released = _tick()
|
||||
_expect(released.size() == 1 and released[0][&"emergency"] and released[0][&"cell"] != Vector3i(0, 3, 0), "blocked destination causes emergency at a valid landing")
|
||||
_setup()
|
||||
_request(&"held", 0, 3)
|
||||
_request(&"waiting_held", 2, 3)
|
||||
_tick(15)
|
||||
for cell: Vector3i in _config[&"stops"]: _path.configure_cell_traffic(cell, 4, 0, true)
|
||||
released = _elevators.set_operator_open(_config[&"shaft_id"], false, _minute)
|
||||
_expect(released.size() == 1 and released[0][&"actor_id"] == &"waiting_held" and released[0][&"cancelled"], "all-landings closure still cancels waiting actors at their original position")
|
||||
_expect(_car()[&"passenger_count"] == 1 and not _car()[&"operational"] and not _path.get_active_elevator_request(&"held").is_empty(), "no valid landing holds boarded actor safely with ownership")
|
||||
var held_remaining: int = _car()[&"remaining_minutes"]
|
||||
_tick(20)
|
||||
_expect(_car()[&"remaining_minutes"] == held_remaining and _elevators.restore_snapshot(_elevators.create_snapshot()), "held cabin survives elapsed time and save restore without advancing or losing its rider")
|
||||
for cell: Vector3i in _config[&"stops"]: _path.configure_cell_traffic(cell)
|
||||
_elevators.set_operator_open(_config[&"shaft_id"], true, _minute)
|
||||
_expect(_elevators.restore_snapshot(_elevators.create_snapshot()), "repaired cabin resumes with an adjusted physical phase clock")
|
||||
_expect(_tick(40).size() == 1, "held actor arrives exactly once after landing repair")
|
||||
|
||||
|
||||
func _test_mutations_and_cancellation() -> void:
|
||||
_setup()
|
||||
_request(&"waiter", 0, 3)
|
||||
var before := _elevators.create_snapshot()
|
||||
_expect(not _elevators.synchronize([], _minute)[&"ok"] and _elevators.create_snapshot() == before, "queued rider prevents destructive shaft modification atomically")
|
||||
var larger := _config.duplicate(true)
|
||||
larger[&"module_ids"].append(&"module.2")
|
||||
larger[&"stops"].append(Vector3i(0, 4, 0))
|
||||
larger[&"stops"].append(Vector3i(0, 5, 0))
|
||||
_expect(not _elevators.synchronize([larger], _minute)[&"ok"], "extension waits until cabin and queues are idle")
|
||||
_expect(_elevators.cancel_actor(&"waiter") and _path.get_active_elevator_request(&"waiter").is_empty(), "waiting actor removal cancels Path queue ownership")
|
||||
_expect(_elevators.synchronize([larger], _minute)[&"ok"] and _car()[&"stops"].size() == 6, "idle upward extension joins existing physical cabin")
|
||||
_expect(_request(&"despawn", 0, 3)[&"ok"], "new request uses fresh request ID after cancellation")
|
||||
_tick(2)
|
||||
_expect(_elevators.cancel_actor(&"despawn") and _path.get_active_elevator_request(&"despawn").is_empty() and _car()[&"passenger_count"] == 0, "boarded actor despawn removes only seat and ownership")
|
||||
_expect(_elevators.restore_snapshot(_elevators.create_snapshot()), "saved doors remain valid after only passenger despawns")
|
||||
_expect(_tick(50).is_empty() and _car()[&"phase"] == &"IDLE", "empty cabin safely finishes its motion without resurrecting despawned actor")
|
||||
_expect(_elevators.synchronize([], _minute)[&"ok"] and _path.create_snapshot()[&"connectors"].is_empty(), "idle demolition removes all owned connectors")
|
||||
_setup()
|
||||
_request(&"duplicate", 0, 3)
|
||||
_expect(_elevators.restore_snapshot(_elevators.create_snapshot()), "queued snapshot restores with matching floor, request, and Path unit")
|
||||
var replay := _request(&"duplicate", 0, 3)
|
||||
_expect(replay[&"ok"] and replay[&"replay"] and _car()[&"waiting_count"] == 1, "same actor route request replays without duplicate queue")
|
||||
_expect(not _request(&"duplicate", 0, 2)[&"ok"], "actor cannot own another simultaneous destination")
|
||||
_expect(_elevators.advance_one_minute(8).is_empty() and _elevators.current_minute == 0, "skipped simulation minutes are rejected")
|
||||
_expect(_tick().is_empty() and _elevators.advance_one_minute(1).is_empty() and _car()[&"remaining_minutes"] == 3, "duplicate minute cannot advance cabin twice")
|
||||
|
||||
|
||||
func _test_snapshot_rejections() -> void:
|
||||
_setup()
|
||||
_request(&"saved", 0, 3)
|
||||
_tick(10)
|
||||
var saved := _elevators.create_snapshot()
|
||||
_expect(_elevators.restore_snapshot(saved), "valid active snapshot restores")
|
||||
var bad := saved.duplicate(true)
|
||||
bad[&"shafts"][0][&"config"][&"capacity"] = 5
|
||||
_reject(bad, saved, "tampered FRONT capacity")
|
||||
bad = saved.duplicate(true)
|
||||
bad[&"shafts"][0][&"config"][&"travel_minutes_per_floor"] = 1
|
||||
_reject(bad, saved, "tampered floor speed")
|
||||
bad = saved.duplicate(true)
|
||||
bad[&"shafts"][0][&"remaining_minutes"] -= 1
|
||||
_reject(bad, saved, "tampered cabin countdown")
|
||||
bad = saved.duplicate(true)
|
||||
bad[&"trips"][0][&"to"] = Vector3i(0, 2, 0)
|
||||
_reject(bad, saved, "tampered destination")
|
||||
bad = saved.duplicate(true)
|
||||
bad[&"shafts"][0][&"boarded"] = []
|
||||
_reject(bad, saved, "missing passenger seat")
|
||||
bad = saved.duplicate(true)
|
||||
bad[&"trips"] = []
|
||||
_reject(bad, saved, "missing active trip")
|
||||
bad = saved.duplicate(true)
|
||||
bad[&"request_sequence"] = 0
|
||||
_reject(bad, saved, "active request sequence reuse")
|
||||
bad = saved.duplicate(true)
|
||||
bad[&"shafts"][0][&"powered"] = false
|
||||
_reject(bad, saved, "inconsistent power and operation")
|
||||
bad = saved.duplicate(true)
|
||||
bad[&"unexpected"] = true
|
||||
_reject(bad, saved, "unknown snapshot field")
|
||||
_tick(50)
|
||||
saved = _elevators.create_snapshot()
|
||||
bad = saved.duplicate(true)
|
||||
bad[&"request_sequence"] = 0
|
||||
_reject(bad, saved, "retired request sequence reuse")
|
||||
_expect(_elevators.restore_snapshot(saved), "completed trip history permits valid restore")
|
||||
|
||||
|
||||
func _reject(bad: Dictionary, before: Dictionary, label: String) -> void:
|
||||
_expect(not _elevators.restore_snapshot(bad) and _elevators.create_snapshot() == before, "%s is rejected atomically" % label)
|
||||
|
||||
|
||||
func _expect(condition: bool, label: String) -> void:
|
||||
_checks += 1
|
||||
if condition: print("PASS: %s" % label)
|
||||
else:
|
||||
_failures += 1
|
||||
push_error("FAIL: %s" % label)
|
||||
@@ -0,0 +1 @@
|
||||
uid://pe3usnvol833
|
||||
@@ -0,0 +1,147 @@
|
||||
extends SceneTree
|
||||
|
||||
var _checks := 0
|
||||
var _failures := 0
|
||||
var _runtime: Variant
|
||||
var _clock: Variant
|
||||
var _state: Variant
|
||||
var _serial := 0
|
||||
var _main: Control
|
||||
var _saw_trip := false
|
||||
var _saw_boarded := false
|
||||
var _saw_arrival := false
|
||||
var _saved := false
|
||||
var _preview_snapshot: Dictionary = {}
|
||||
|
||||
func _initialize() -> void:
|
||||
call_deferred("_run")
|
||||
|
||||
func _run() -> void:
|
||||
_runtime = root.get_node("SimulationRuntime")
|
||||
_clock = root.get_node("GameClock")
|
||||
_state = root.get_node("GameState")
|
||||
_clock.reset(1, 6, 0)
|
||||
_state.reset()
|
||||
_runtime.initialize(_clock, _state, 424242)
|
||||
_runtime.configure_save_policy({&"enabled": false, &"daily_checkpoint_enabled": false})
|
||||
_clock.set_paused(true)
|
||||
root.size = Vector2i(1440, 810)
|
||||
_main = load("res://scenes/main/main.tscn").instantiate() as Control
|
||||
_main.set("manual_save_path", "user://tests/transport_%d/manual" % OS.get_process_id())
|
||||
root.add_child(_main)
|
||||
await process_frame
|
||||
_runtime.configure_save_policy({&"enabled": false, &"daily_checkpoint_enabled": false})
|
||||
_expect(_state.get_resource(&"gold") == 5000 and _state.get_resource(&"materials") == 50, "ordinary starting budget")
|
||||
if not _build(&"guest_elevator", Vector3i(0, 0, 0)) or not _build(&"reception", Vector3i(2, 0, 0)):
|
||||
_finish(); return
|
||||
_clock.advance_minutes(300)
|
||||
_expect(_runtime.elevators.get_report()[&"shafts"].size() == 1, "completed construction registers physical cabin")
|
||||
if _runtime.elevators.get_report()[&"shafts"].is_empty(): _finish(); return
|
||||
if not _build(&"guest_elevator", Vector3i(0, 2, 0)) or not _build(&"standard_room", Vector3i(2, 2, 0)):
|
||||
_finish(); return
|
||||
_clock.advance_minutes(300)
|
||||
if not _build(&"standard_room", Vector3i(2, 3, 0)):
|
||||
_finish(); return
|
||||
_clock.advance_minutes(60)
|
||||
var report: Dictionary = _runtime.elevators.get_report()
|
||||
_expect(report[&"shafts"].size() == 1 and report[&"shafts"][0][&"stops"].size() == 4, "stacked modules share one cabin across four floors")
|
||||
_expect(_state.get_resource(&"materials") == 2, "buildings fit within 50 materials")
|
||||
var reception: FacilityInstance
|
||||
for facility: FacilityInstance in _runtime.facility_service.get_instances():
|
||||
if facility.definition_id == &"reception": reception = facility
|
||||
_expect(_cmd(&"hire_staff_candidate", {&"candidate_id": &"staff_akane"}).is_success(), "hire receptionist with earned starting funds")
|
||||
_expect(_cmd(&"set_staff_work_assignment", {&"staff_id": &"staff_akane", &"facility_id": reception.instance_id, &"template_id": &"DAY"}).is_success(), "assign real commute")
|
||||
_expect(reception.active_staff_ids.is_empty(), "assignment alone does not open reception")
|
||||
for index: int in range(250):
|
||||
_clock.advance_minutes(1)
|
||||
var current: Dictionary = _runtime.elevators.get_report()
|
||||
if not current[&"trips"].is_empty(): _saw_trip = true
|
||||
for trip: Dictionary in current[&"trips"]:
|
||||
if trip[&"state"] != PathService.ELEVATOR_REQUEST_BOARDED: continue
|
||||
_saw_boarded = true
|
||||
if not _saved:
|
||||
_saved = _verify_mid_ride()
|
||||
_preview_snapshot = _runtime.create_snapshot()
|
||||
for guest: GuestRuntime in _runtime.guest_service.get_guests():
|
||||
if guest.current_cell.y >= 2: _saw_arrival = true
|
||||
if _saw_boarded and not _main.get_meta(&"captured", false) and OS.get_environment("THE_TREE_TRANSPORT_CAPTURE") == "1":
|
||||
_main.set_meta(&"captured", true)
|
||||
await _capture()
|
||||
if index % 30 == 0: await process_frame
|
||||
if not _saw_trip:
|
||||
print("TRANSPORT_DIAGNOSTIC ", _runtime.elevators.get_report(), " STAFF ", _runtime.staff_commute.get_report())
|
||||
for f: FacilityInstance in _runtime.facility_service.get_instances(): print("FAC ", f.definition_id, " ", f.state, " ", f.active_staff_ids)
|
||||
for g: GuestRuntime in _runtime.guest_service.get_guests(): print("GUEST ", g.guest_id, " ", g.state, " ", g.current_cell, " ", _runtime._project_guest_candidates(g.guest_id))
|
||||
_expect(_saw_trip, "natural guest requests an elevator")
|
||||
_expect(_saw_boarded, "natural guest boards the shared cabin")
|
||||
_expect(_saw_arrival, "natural guest physically reaches an upper floor")
|
||||
_expect(_saved, "mid-ride replay tested")
|
||||
_expect(_runtime.restore_snapshot(_runtime.create_snapshot()), "current completed runtime restores")
|
||||
if OS.get_environment("THE_TREE_TRANSPORT_PREVIEW") == "1" and _failures == 0:
|
||||
_expect(_runtime.restore_snapshot(_preview_snapshot), "preview returns to the verified first boarding")
|
||||
if _failures > 0: _finish(); return
|
||||
await _capture()
|
||||
DisplayServer.window_set_title("The Tree · 昇降機と通勤の試遊(保存なし)")
|
||||
_clock.set_paused(true)
|
||||
print("Runtime transport: %d checks, %d failures" % [_checks, _failures])
|
||||
print("TRANSPORT_PREVIEW_READY Space resumes; test saves disabled")
|
||||
return
|
||||
_finish()
|
||||
|
||||
func _verify_mid_ride() -> bool:
|
||||
var save_path := "user://tests/transport_%d/ride" % OS.get_process_id()
|
||||
_expect(bool(_runtime.save_to_path(save_path).get(&"ok", false)), "boarded save writes isolated disk slot")
|
||||
_expect(bool(_runtime.load_from_path(save_path).get(&"ok", false)), "boarded disk slot reloads")
|
||||
var snapshot: Dictionary = _runtime.create_snapshot()
|
||||
_expect(snapshot[&"systems"][&"runtime"][&"runtime_schema_version"] == 8, "runtime schema8 records elevator state")
|
||||
_expect(_runtime.restore_snapshot(snapshot), "mid-ride Runtime snapshot restores actor/cabin/Path ownership")
|
||||
var shaft: Dictionary = _runtime.elevators.get_report()[&"shafts"][0]
|
||||
var busy := _cmd(&"demolish_facility", {&"instance_id": shaft[&"module_ids"][1]})
|
||||
_expect(not busy.is_success() and busy.details.get(&"reason") == &"ELEVATOR_BUSY", "occupied shaft demolition rejected before spending or topology changes")
|
||||
_expect(_runtime.restore_snapshot(snapshot), "snapshot resets rejected command metadata")
|
||||
_clock.advance_minutes(45)
|
||||
var expected: Dictionary = _runtime.create_snapshot()
|
||||
_expect(_runtime.restore_snapshot(snapshot), "rewind boarded save for deterministic replay")
|
||||
_clock.advance_minutes(45)
|
||||
_expect(_runtime._strict_variants_equal(expected, _runtime.create_snapshot()), "mid-ride save replays all Runtime state identically")
|
||||
_expect(_runtime.restore_snapshot(snapshot), "restore original ride position after replay test")
|
||||
var stopped := _cmd(&"set_elevator_operation", {&"shaft_id": shaft[&"shaft_id"], &"operator_open": false})
|
||||
_expect(stopped.is_success(), "player stops elevator through public command")
|
||||
_expect(_runtime.elevators.get_report()[&"trips"].is_empty(), "stop releases waiting and disembarks passengers at safe floor")
|
||||
_expect(_runtime.restore_snapshot(_runtime.create_snapshot()), "stopped elevator save restores")
|
||||
_expect(_runtime.restore_snapshot(snapshot), "resume original operating snapshot")
|
||||
return true
|
||||
|
||||
func _capture() -> void:
|
||||
var preview: Control = _main.get("world_preview")
|
||||
_main.call("_on_facility_selected", _runtime.elevators.get_report()[&"shafts"][0][&"module_ids"][0])
|
||||
preview.call("set_zoom", 2.0)
|
||||
preview.set("pan_offset", preview.get("pan_offset") + preview.size * Vector2(0.5, 0.55) - preview.call("cell_to_canvas", Vector3i(2, 1, 0)))
|
||||
preview.queue_redraw()
|
||||
await process_frame
|
||||
await RenderingServer.frame_post_draw
|
||||
for trip: Dictionary in _runtime.elevators.get_report()[&"trips"]:
|
||||
if trip[&"state"] != PathService.ELEVATOR_REQUEST_BOARDED: continue
|
||||
var key := String(trip[&"actor_id"]).replace("guest:", "guest/").replace("staff:", "staff/")
|
||||
_expect((preview.get("_boarded_actor_ids") as Dictionary).has(key), "live rider identified in cabin rendering")
|
||||
_expect(not (preview.get("_actor_tracks") as Dictionary).has(key), "live rider is not duplicated on floor")
|
||||
var path := ProjectSettings.globalize_path("res://artifacts/transport/elevator-cabin.png")
|
||||
root.get_texture().get_image().save_png(path)
|
||||
|
||||
func _build(id: StringName, origin: Vector3i) -> bool:
|
||||
var result := _cmd(&"build_facility", {&"definition_id": id, &"origin": origin})
|
||||
_expect(result.is_success(), "build %s at %s %s" % [id, origin, result.message])
|
||||
return result.is_success()
|
||||
|
||||
func _cmd(kind: StringName, payload: Dictionary) -> CommandResult:
|
||||
_serial += 1
|
||||
return _runtime.submit_command(kind, payload, &"player", &"", 0, _clock.absolute_minute, StringName("transport.test.%d" % _serial))
|
||||
|
||||
func _expect(condition: bool, message: String) -> void:
|
||||
_checks += 1
|
||||
if not condition: _failures += 1
|
||||
print("%s %s" % ["PASS" if condition else "FAIL", message])
|
||||
|
||||
func _finish() -> void:
|
||||
print("Runtime transport: %d checks, %d failures" % [_checks, _failures])
|
||||
quit(1 if _failures else 0)
|
||||
@@ -0,0 +1 @@
|
||||
uid://cityh7pucrssv
|
||||
@@ -0,0 +1,175 @@
|
||||
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])
|
||||
@@ -0,0 +1 @@
|
||||
uid://bjv4yxwqibp1n
|
||||
@@ -0,0 +1,143 @@
|
||||
extends SceneTree
|
||||
|
||||
var _checks := 0
|
||||
var _failures := 0
|
||||
var _serial := 0
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
call_deferred("_run")
|
||||
|
||||
|
||||
func _run() -> void:
|
||||
var fixture := _fixture(81423)
|
||||
var runtime: Variant = fixture[&"runtime"]
|
||||
var clock: Variant = fixture[&"clock"]
|
||||
clock.advance_minutes(3)
|
||||
var current: Dictionary = runtime.create_snapshot()
|
||||
_expect(current[&"systems"][&"runtime"][&"runtime_schema_version"] == 8, "Runtime emits schema8 with elevator aggregate")
|
||||
var legacy := current.duplicate(true)
|
||||
var data: Dictionary = legacy[&"systems"][&"runtime"]
|
||||
data[&"runtime_schema_version"] = 7
|
||||
data.erase(&"elevators")
|
||||
data[&"path"][&"schema_version"] = 3
|
||||
data[&"path"].erase(&"vertical_walk_blockers")
|
||||
_expect(runtime.restore_snapshot(legacy), "genuine v7 without lifts migrates from Path3 into Runtime8")
|
||||
_expect(runtime.create_snapshot() == current, "empty-lift migration preserves every existing system exactly")
|
||||
var bad := current.duplicate(true)
|
||||
bad[&"systems"][&"runtime"][&"runtime_schema_version"] = 7
|
||||
_expect(_rejects(runtime, bad), "outer-only downgrade retaining elevator state is rejected atomically")
|
||||
bad = current.duplicate(true)
|
||||
bad[&"systems"][&"runtime"].erase(&"elevators")
|
||||
_expect(_rejects(runtime, bad), "Runtime8 cannot omit elevator aggregate")
|
||||
bad = current.duplicate(true)
|
||||
bad[&"systems"][&"runtime"][&"elevators"][&"current_minute"] -= 1
|
||||
_expect(_rejects(runtime, bad), "elevator clock must match Runtime domain minute")
|
||||
bad = current.duplicate(true)
|
||||
bad[&"systems"][&"runtime"][&"path"][&"schema_version"] = 3
|
||||
_expect(_rejects(runtime, bad), "Path3 relabel with retained vertical blocker metadata is rejected")
|
||||
_expect(_command(runtime, clock, &"build_facility", {&"definition_id": &"guest_elevator", &"origin": Vector3i(0, 0, 0)}).is_success(), "real construction starts elevator module")
|
||||
clock.advance_minutes(310)
|
||||
_expect(_command(runtime, clock, &"hire_staff_candidate", {&"candidate_id": &"staff_akane"}).is_success(), "hire actual actor at entrance for ownership fixture")
|
||||
var worker: StaffRuntime = runtime.staff_service.get_staff(&"staff_akane")
|
||||
var actor_id := &"staff:staff_akane"
|
||||
_expect(worker != null and worker.current_cell == Vector3i.ZERO, "hired actor is physically at elevator's ground landing")
|
||||
var access: Array[StringName] = [&"staff"]
|
||||
var queued: Dictionary = runtime.elevators.request_trip(actor_id, Vector3i.ZERO, Vector3i(0, 1, 0), clock.absolute_minute, access)
|
||||
_expect(queued.get(&"ok", false), "queue real actor through transport ownership API: %s" % queued)
|
||||
if not queued.get(&"ok", false):
|
||||
for key: StringName in [&"runtime", &"clock", &"state"]: fixture[key].free()
|
||||
quit(1)
|
||||
return
|
||||
clock.advance_minutes(1)
|
||||
var trip: Dictionary = runtime.elevators.get_actor_trip(actor_id)
|
||||
_expect(trip.get(&"state") == PathService.ELEVATOR_REQUEST_BOARDED, "save fixture owns a physical cabin seat")
|
||||
current = runtime.create_snapshot()
|
||||
_expect(runtime.restore_snapshot(current), "Runtime8 restores actual cabin actor and projected origin crosslinks")
|
||||
bad = current.duplicate(true)
|
||||
bad[&"systems"][&"runtime"][&"elevators"][&"trips"][0][&"actor_id"] = &"staff:missing_person"
|
||||
bad[&"systems"][&"runtime"][&"elevators"][&"shafts"][0][&"boarded"][0] = &"staff:missing_person"
|
||||
for unit: Dictionary in bad[&"systems"][&"runtime"][&"path"][&"active_elevator_units"]:
|
||||
if unit[&"unit_id"] == actor_id: unit[&"unit_id"] = &"staff:missing_person"
|
||||
_expect(_rejects(runtime, bad), "internally consistent seat and Path owner cannot refer to nonexistent Runtime actor")
|
||||
bad = current.duplicate(true)
|
||||
bad[&"systems"][&"runtime"][&"elevators"][&"shafts"][0][&"config"][&"module_ids"][0] = &"missing_module"
|
||||
_expect(_rejects(runtime, bad), "elevator cabin cannot substitute a module absent from real construction")
|
||||
bad = current.duplicate(true)
|
||||
bad[&"systems"][&"runtime"][&"elevators"][&"shafts"][0][&"boarded"] = []
|
||||
_expect(_rejects(runtime, bad), "active Path rider cannot lose its cabin seat")
|
||||
bad = current.duplicate(true)
|
||||
bad[&"systems"][&"runtime"][&"elevators"][&"trips"][0][&"from"] = Vector3i(0, 1, 0)
|
||||
bad[&"systems"][&"runtime"][&"elevators"][&"trips"][0][&"to"] = Vector3i.ZERO
|
||||
bad[&"systems"][&"runtime"][&"elevators"][&"shafts"][0][&"boarding_floor"] = 1
|
||||
bad[&"systems"][&"runtime"][&"elevators"][&"shafts"][0][&"target_floor"] = 0
|
||||
bad[&"systems"][&"runtime"][&"elevators"][&"shafts"][0][&"current_floor"] = 1
|
||||
bad[&"systems"][&"runtime"][&"elevators"][&"shafts"][0][&"phase_origin_floor"] = 1
|
||||
_expect(_rejects(runtime, bad), "valid reverse cabin route cannot move actor's saved logical origin")
|
||||
var shaft_id: StringName = runtime.elevators.get_report()[&"shafts"][0][&"shaft_id"]
|
||||
_expect(_command(runtime, clock, &"set_elevator_operation", {&"shaft_id": shaft_id, &"operator_open": false}).is_success(), "player closes running elevator through public command")
|
||||
current = runtime.create_snapshot()
|
||||
_expect(runtime.restore_snapshot(current) and not runtime.elevators.get_report()[&"shafts"][0][&"operator_open"], "closed operator policy survives Runtime restore")
|
||||
for key: StringName in [&"runtime", &"clock", &"state"]: fixture[key].free()
|
||||
_test_attendance_migration()
|
||||
print("Transport save: %d checks, %d failures" % [_checks, _failures])
|
||||
quit(0 if _failures == 0 else 1)
|
||||
|
||||
|
||||
func _test_attendance_migration() -> void:
|
||||
var fixture := _fixture(81424)
|
||||
var runtime: Variant = fixture[&"runtime"]
|
||||
var clock: Variant = fixture[&"clock"]
|
||||
_expect(_command(runtime, clock, &"build_facility", {&"definition_id": &"reception", &"origin": Vector3i(2, 0, 0)}).is_success(), "attendance fixture builds a real workplace")
|
||||
clock.advance_minutes(180)
|
||||
var facility: FacilityInstance = runtime.facility_service.get_instances()[0]
|
||||
_expect(_command(runtime, clock, &"hire_staff_candidate", {&"candidate_id": &"staff_akane"}).is_success(), "attendance fixture hires actual receptionist")
|
||||
_expect(_command(runtime, clock, &"set_staff_work_assignment", {&"staff_id": &"staff_akane", &"facility_id": facility.instance_id, &"template_id": &"DAY"}).is_success(), "attendance fixture assigns a real shift")
|
||||
var worker: StaffRuntime = runtime.staff_service.get_staff(&"staff_akane")
|
||||
_expect(not StaffCommuteService.is_at_facility(worker, facility), "employee has not yet physically walked to reception")
|
||||
# Reproduce the old logical-attendance rule without changing position, route,
|
||||
# income or inventory. Facility projection is rebuilt by its public service.
|
||||
worker.state = StaffRuntime.State.AVAILABLE
|
||||
runtime.facility_service.set_operational_status(facility.instance_id, {&"active_staff_ids": [&"staff_akane"]}, clock.absolute_minute)
|
||||
var logical: Dictionary = runtime.create_snapshot()
|
||||
_expect(_rejects(runtime, logical), "Runtime8 rejects an AVAILABLE employee counted at a distant workplace")
|
||||
var legacy := logical.duplicate(true)
|
||||
var data: Dictionary = legacy[&"systems"][&"runtime"]
|
||||
data[&"runtime_schema_version"] = 7
|
||||
data.erase(&"elevators")
|
||||
data[&"path"][&"schema_version"] = 3
|
||||
data[&"path"].erase(&"vertical_walk_blockers")
|
||||
_expect(runtime.restore_snapshot(legacy), "genuine v7 logical attendance migrates into physical attendance")
|
||||
facility = runtime.facility_service.get_instance(facility.instance_id)
|
||||
worker = runtime.staff_service.get_staff(&"staff_akane")
|
||||
_expect(facility.active_staff_ids.is_empty() and worker.state == StaffRuntime.State.COMMUTING and worker.current_cell == Vector3i.ZERO, "migration removes phantom workplace capacity and starts commute without teleporting")
|
||||
_expect(runtime.restore_snapshot(runtime.create_snapshot()), "migrated attendance is a valid current Runtime snapshot")
|
||||
for key: StringName in [&"runtime", &"clock", &"state"]: fixture[key].free()
|
||||
|
||||
|
||||
func _fixture(seed: int) -> Dictionary:
|
||||
var clock: Variant = load("res://autoload/game_clock.gd").new()
|
||||
var state: Variant = load("res://autoload/game_state.gd").new()
|
||||
var runtime: Variant = load("res://autoload/simulation_runtime.gd").new()
|
||||
clock.reset(1, 6, 0)
|
||||
state.reset()
|
||||
_expect(runtime.initialize(clock, state, seed)[&"valid"], "transport save fixture initializes")
|
||||
runtime.configure_save_policy({&"enabled": false, &"daily_checkpoint_enabled": false})
|
||||
clock.set_paused(true)
|
||||
return {&"clock": clock, &"state": state, &"runtime": runtime}
|
||||
|
||||
|
||||
func _command(runtime: Variant, clock: Variant, kind: StringName, payload: Dictionary) -> CommandResult:
|
||||
_serial += 1
|
||||
return runtime.submit_command(kind, payload, &"player", &"", 0, clock.absolute_minute, StringName("transport.save.%d" % _serial))
|
||||
|
||||
|
||||
func _rejects(runtime: Variant, candidate: Dictionary) -> bool:
|
||||
var before: Dictionary = runtime.create_snapshot()
|
||||
return not runtime.restore_snapshot(candidate) and runtime.create_snapshot() == before
|
||||
|
||||
|
||||
func _expect(condition: bool, label: String) -> void:
|
||||
_checks += 1
|
||||
if not condition: _failures += 1
|
||||
print("%s: %s" % ["PASS" if condition else "FAIL", label])
|
||||
@@ -0,0 +1 @@
|
||||
uid://daygk8x6blorg
|
||||
Reference in New Issue
Block a user