Connect physical elevators and staff commutes to construction, saves, and cutaway UI

This commit is contained in:
Takeshi
2026-09-09 12:58:40 +09:00
parent 4d72de8973
commit 0f12c748e9
46 changed files with 3267 additions and 165 deletions
+258
View File
@@ -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
+147
View File
@@ -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
+143
View File
@@ -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