class_name ElevatorService extends RefCounted ## One physical cabin per contiguous shaft. Path connectors expose reachability; ## boarding and arrival here remain authoritative for actual movement. ## Dispatch is oldest request first, batching one origin/destination at a time. const SNAPSHOT_VERSION := 1 const PREFIX := "transport." const REQUEST_PREFIX := "transport.trip." const DOOR_MINUTES := 2 const QUEUE_CAPACITY := 256 var last_error := "" var _path: PathService var _shafts: Dictionary = {} var _trips: Dictionary = {} var _minute := -1 var _sequence := 0 var current_minute: int: get: return _minute func configure(path: PathService) -> void: _path = path static func configurations_from_facilities(facilities: Array, registry: DefinitionRegistry) -> Array: var modules: Array[Dictionary] = [] for value: Variant in facilities: if not value is Dictionary: continue var facility: Dictionary = value var definition_id := StringName(str(facility.get(&"definition_id", ""))) if definition_id not in [&"guest_elevator", &"service_elevator"]: continue var definition := registry.get_facility(definition_id) var origin: Variant = facility.get(&"origin") if definition == null or not origin is Vector3i: continue var width := definition.footprint.x var height := definition.footprint.y var cells: Array = facility.get(&"occupied_cells", []) if not cells.is_empty(): width = 1 height = 1 for cell: Vector3i in cells: width = maxi(width, cell.x - origin.x + 1) height = maxi(height, cell.y - origin.y + 1) modules.append({&"origin": origin, &"width": width, &"height": height, &"module_id": StringName(str(facility.get(&"instance_id", "")))}) modules.sort_custom(func(a: Dictionary, b: Dictionary) -> bool: var ac: Vector3i = a[&"origin"] var bc: Vector3i = b[&"origin"] if ac.z != bc.z: return ac.z < bc.z if ac.x != bc.x: return ac.x < bc.x if a[&"width"] != b[&"width"]: return a[&"width"] < b[&"width"] return ac.y < bc.y) var result: Array = [] for module: Dictionary in modules: var origin: Vector3i = module[&"origin"] var width: int = module[&"width"] var config: Dictionary = {} if not result.is_empty(): var previous: Dictionary = result.back() var stops: Array = previous[&"stops"] var top: Vector3i = stops.back() if top.x == origin.x and top.z == origin.z and top.y + 1 == origin.y and previous[&"width"] == width: config = previous if config.is_empty(): config = {&"shaft_id": StringName("transport.shaft.%d.%d.%d.%d" % [origin.z, origin.x, origin.y, width]), &"module_ids": [], &"stops": [], &"width": width, &"capacity": 4 if origin.z == 0 else 8, &"travel_minutes_per_floor": 10 if origin.z == 0 else 5, &"access_id": &"" if origin.z == 0 else &"staff"} result.append(config) (config[&"module_ids"] as Array).append(module[&"module_id"]) for y: int in range(origin.y, origin.y + int(module[&"height"])): (config[&"stops"] as Array).append(Vector3i(origin.x, y, origin.z)) return result func synchronize(configurations: Array, minute: int) -> Dictionary: if _path == null or minute < 0 or (_minute >= 0 and minute < _minute): return _failure(&"INVALID_CLOCK", "Elevator synchronization requires a path and current time.") var normalized := _normalize_configs(configurations) if not normalized[&"ok"]: return normalized var configs: Dictionary = normalized[&"configs"] for shaft_id: StringName in _names(_shafts): var car: Dictionary = _shafts[shaft_id] if configs.has(shaft_id) and configs[shaft_id] == car[&"config"]: continue if _shaft_busy(shaft_id): return _failure(&"ELEVATOR_BUSY", "An occupied or moving shaft cannot be modified.") # All new IDs must belong to us and must not overwrite an unrelated connector. var old_ids: Dictionary = {} for value: Variant in _shafts.values(): for connector: Dictionary in _connector_configs(value[&"config"]): old_ids[connector[&"id"]] = true for value: Variant in configs.values(): for connector: Dictionary in _connector_configs(value): if _path.has_connector(connector[&"id"]) and not old_ids.has(connector[&"id"]): return _failure(&"CONNECTOR_CONFLICT", "Elevator connector is already owned.") var path_before := _path.create_snapshot() var next: Dictionary = {} for shaft_id: StringName in _names(_shafts): var car: Dictionary = _shafts[shaft_id] if configs.has(shaft_id) and configs[shaft_id] == car[&"config"]: next[shaft_id] = car continue for connector: Dictionary in _connector_configs(car[&"config"]): if not _path.remove_connector(connector[&"id"]): _path.restore_snapshot(path_before) return _failure(&"CONNECTOR_BUSY", _path.last_error) for shaft_id: StringName in _names(configs): if next.has(shaft_id): continue var config: Dictionary = configs[shaft_id] var car := _new_car(config) if _shafts.has(shaft_id): car[&"operational"] = _shafts[shaft_id][&"operational"] car[&"operator_open"] = _shafts[shaft_id][&"operator_open"] car[&"powered"] = _shafts[shaft_id][&"powered"] var previous_floor: int = _shafts[shaft_id][&"current_floor"] if _has_floor(config, previous_floor): car[&"current_floor"] = previous_floor for connector: Dictionary in _connector_configs(config): if not _path.configure_connector(connector[&"id"], connector[&"from"], connector[&"to"], PathService.ORTHOGONAL_MOVE_COST, true, config[&"access_id"], true, connector): _path.restore_snapshot(path_before) return _failure(&"CONNECTOR_CONFIGURATION_FAILED", _path.last_error) next[shaft_id] = car _shafts = next if _minute < 0: _minute = minute for shaft_id: StringName in _names(_shafts): _publish_cabin(_shafts[shaft_id]) last_error = "" return {&"ok": true, &"shaft_count": _shafts.size()} func request_trip(actor_id: StringName, from: Vector3i, to: Vector3i, minute: int, access: Array[StringName] = []) -> Dictionary: if actor_id.is_empty() or minute < _minute or minute > _minute + 1: return _failure(&"INVALID_REQUEST", "Elevator request has invalid actor or time.") if _trips.has(actor_id): var existing: Dictionary = _trips[actor_id] if existing[&"from"] == from and existing[&"to"] == to: return {&"ok": true, &"replay": true, &"trip": existing.duplicate(true)} return _failure(&"ACTOR_ALREADY_TRAVELLING", "Actor already owns an elevator trip.") var selected: Dictionary = {} for shaft_id: StringName in _names(_shafts): var car: Dictionary = _shafts[shaft_id] var config: Dictionary = car[&"config"] if from != to and (config[&"stops"] as Array).has(from) and (config[&"stops"] as Array).has(to): selected = car break if selected.is_empty(): return _failure(&"NO_ELEVATOR", "No shaft joins those stops.") var config: Dictionary = selected[&"config"] if not selected[&"operational"]: return _failure(&"ELEVATOR_STOPPED", "Elevator is stopped.") if not StringName(config[&"access_id"]).is_empty() and not access.has(config[&"access_id"]): return _failure(&"ACCESS_DENIED", "Elevator access is not permitted.") if not _valid_stop(from) or not _valid_stop(to): return _failure(&"STOP_UNAVAILABLE", "Elevator landing is unavailable.") var connector_id := _connector_id(config[&"shaft_id"], from.y, to.y) _sequence += 1 var request_id := StringName("transport.trip.%012d" % _sequence) var queued := _path.enqueue_elevator_request(connector_id, {&"request_id": request_id, &"unit_id": actor_id, &"floor_from": from.y, &"floor_to": to.y, &"enqueued_at": minute}) if not queued[&"ok"]: return _failure(queued.get(&"reason", &"QUEUE_REJECTED"), _path.last_error) var trip := {&"actor_id": actor_id, &"request_id": request_id, &"shaft_id": config[&"shaft_id"], &"connector_id": connector_id, &"from": from, &"to": to, &"enqueued_at": minute, &"state": PathService.ELEVATOR_REQUEST_QUEUED} _trips[actor_id] = trip _refresh_shaft_wait(config[&"shaft_id"]) last_error = "" return {&"ok": true, &"replay": false, &"trip": trip.duplicate(true)} ## Reserve two real seats atomically; no synthetic passenger or hidden capacity. func request_pair(first: StringName, second: StringName, from: Vector3i, to: Vector3i, minute: int, access: Array[StringName] = []) -> Dictionary: if first.is_empty() or second.is_empty() or first == second: return _failure(&"INVALID_PAIR", "Two distinct passengers are required.") if _trips.has(first) or _trips.has(second): if _trips.has(first) and _trips.has(second) and _trips[first].get(&"companion_id", &"") == second and _trips[second].get(&"companion_id", &"") == first and _trips[first][&"from"] == from and _trips[first][&"to"] == to: return {&"ok": true, &"replay": true, &"trip": _trips[first].duplicate(true)} return _failure(&"ACTOR_ALREADY_TRAVELLING", "Pair member already owns another trip.") var path_before := _path.create_snapshot() var sequence_before := _sequence var a := request_trip(first, from, to, minute, access) if not a[&"ok"]: _path.restore_snapshot(path_before) _sequence = sequence_before return a var b := request_trip(second, from, to, minute, access) if not b[&"ok"] or _shafts[_trips[first][&"shaft_id"]][&"config"][&"capacity"] < 2: _path.restore_snapshot(path_before) _trips.erase(first) _trips.erase(second) _sequence = sequence_before return _failure(&"PAIR_REJECTED", "Both passengers must fit in the same cabin queue.") _trips[first][&"companion_id"] = second _trips[second][&"companion_id"] = first return {&"ok": true, &"replay": false, &"trip": _trips[first].duplicate(true)} func advance_one_minute(minute: int) -> Array[Dictionary]: var arrivals: Array[Dictionary] = [] if minute == _minute: return arrivals if minute != _minute + 1: last_error = "Elevators must advance in consecutive minutes." return arrivals _minute = minute for shaft_id: StringName in _names(_shafts): var car: Dictionary = _shafts[shaft_id] if not car[&"operational"]: continue var unavailable := false for actor_id: StringName in car[&"boarded"]: if not _valid_stop(_trips[actor_id][&"to"]): unavailable = true if unavailable: arrivals.append_array(set_operational(shaft_id, false, minute)) continue if car[&"phase"] == &"IDLE": _dispatch(car) elif int(car[&"remaining_minutes"]) > 0: car[&"remaining_minutes"] -= 1 if int(car[&"remaining_minutes"]) == 0: _finish_phase(car, arrivals) _publish_cabin(car) last_error = "" return arrivals func get_actor_trip(actor_id: StringName) -> Dictionary: return (_trips.get(actor_id, {}) as Dictionary).duplicate(true) func cancel_actor(actor_id: StringName) -> bool: var partner: StringName = _trips.get(actor_id, {}).get(&"companion_id", &"") if not partner.is_empty(): if _trips[actor_id][&"state"] == PathService.ELEVATOR_REQUEST_BOARDED: last_error = "An onboard pair must alight together at a real landing." return false var path_before := _path.create_snapshot() var trips_before := _trips.duplicate(true) if not _cancel_single(actor_id) or not _cancel_single(partner): _path.restore_snapshot(path_before) _trips = trips_before return false return true return _cancel_single(actor_id) func _cancel_single(actor_id: StringName) -> bool: if not _trips.has(actor_id): return true var trip: Dictionary = _trips[actor_id] if trip[&"state"] == PathService.ELEVATOR_REQUEST_BOARDED: if not _path.complete_elevator_request(trip[&"connector_id"], trip[&"request_id"]): last_error = _path.last_error return false (_shafts[trip[&"shaft_id"]][&"boarded"] as Array).erase(actor_id) _trips.erase(actor_id) last_error = "" return true if not _path.cancel_elevator_request(trip[&"connector_id"], trip[&"request_id"]): last_error = _path.last_error return false _trips.erase(actor_id) _refresh_shaft_wait(trip[&"shaft_id"]) last_error = "" return true func set_operator_open(shaft_id: StringName, enabled: bool, minute: int) -> Array[Dictionary]: if not _shafts.has(shaft_id) or minute < _minute or minute > _minute + 1: last_error = "Unknown shaft or invalid operator change time." return [] _shafts[shaft_id][&"operator_open"] = enabled return _apply_operational(shaft_id, enabled and _shafts[shaft_id][&"powered"], minute) func set_powered(shaft_id: StringName, powered: bool, minute: int) -> Array[Dictionary]: if not _shafts.has(shaft_id) or minute < _minute or minute > _minute + 1: last_error = "Unknown shaft or invalid power change time." return [] _shafts[shaft_id][&"powered"] = powered return _apply_operational(shaft_id, powered and _shafts[shaft_id][&"operator_open"], minute) func set_operational(shaft_id: StringName, operational: bool, minute: int) -> Array[Dictionary]: return set_powered(shaft_id, operational, minute) func _apply_operational(shaft_id: StringName, operational: bool, minute: int) -> Array[Dictionary]: var arrivals: Array[Dictionary] = [] if not _shafts.has(shaft_id) or minute < _minute or minute > _minute + 1: last_error = "Unknown shaft or invalid operational change time." return arrivals var car: Dictionary = _shafts[shaft_id] if car[&"operational"] == operational: return arrivals if not operational: var landing: Variant = _nearest_valid_stop(car) if landing == null and not (car[&"boarded"] as Array).is_empty(): last_error = "Occupied elevator has no safe landing; cabin remains held." # No legal teleportation destination exists. The caller must repair a # landing; movement remains held and requests remain owned. car[&"operational"] = false for actor_id: StringName in _names(_trips): if not _trips.has(actor_id): continue var waiting: Dictionary = _trips[actor_id] if waiting[&"shaft_id"] == shaft_id and waiting[&"state"] == PathService.ELEVATOR_REQUEST_QUEUED and _cancel_single(actor_id): arrivals.append({&"actor_id": actor_id, &"request_id": waiting[&"request_id"], &"cell": waiting[&"from"], &"emergency": false, &"cancelled": true}) _publish_cabin(car) return arrivals for actor_id: StringName in _names(_trips): var trip: Dictionary = _trips[actor_id] if trip[&"shaft_id"] != shaft_id: continue var boarded: bool = trip[&"state"] == PathService.ELEVATOR_REQUEST_BOARDED if boarded: if not _path.complete_elevator_request(trip[&"connector_id"], trip[&"request_id"]): continue else: if not _path.cancel_elevator_request(trip[&"connector_id"], trip[&"request_id"]): continue arrivals.append({&"actor_id": actor_id, &"request_id": trip[&"request_id"], &"cell": landing if boarded else trip[&"from"], &"emergency": boarded, &"cancelled": not boarded}) _trips.erase(actor_id) car[&"boarded"] = [] if landing != null: car[&"current_floor"] = (landing as Vector3i).y _idle(car) if operational and car[&"phase"] != &"IDLE": car[&"phase_started_at"] = _minute - (int(car[&"phase_total_minutes"]) - int(car[&"remaining_minutes"])) car[&"operational"] = operational _publish_cabin(car) last_error = "" return arrivals func owns_connector(connector_id: StringName) -> bool: return not get_shaft_for_connector(connector_id).is_empty() func get_shaft_for_connector(connector_id: StringName) -> StringName: for shaft_id: StringName in _names(_shafts): for connector: Dictionary in _connector_configs(_shafts[shaft_id][&"config"]): if connector[&"id"] == connector_id: return shaft_id return &"" func get_report() -> Dictionary: var rows: Array[Dictionary] = [] for shaft_id: StringName in _names(_shafts): var car: Dictionary = _shafts[shaft_id] var row: Dictionary = car.duplicate(true) row.merge(car[&"config"], true) row[&"visual_floor"] = _visual_floor(car) row[&"waiting_count"] = 0 row[&"passenger_count"] = (car[&"boarded"] as Array).size() row[&"passenger_ids"] = car[&"boarded"].duplicate() for trip: Dictionary in _trips.values(): if trip[&"shaft_id"] == shaft_id and trip[&"state"] == PathService.ELEVATOR_REQUEST_QUEUED: row[&"waiting_count"] += 1 rows.append(row) return {&"current_minute": _minute, &"shafts": rows, &"trips": _ordered_trips()} func create_snapshot() -> Dictionary: var cars: Array[Dictionary] = [] for shaft_id: StringName in _names(_shafts): cars.append(_shafts[shaft_id].duplicate(true)) return {&"version": SNAPSHOT_VERSION, &"current_minute": _minute, &"request_sequence": _sequence, &"shafts": cars, &"trips": _ordered_trips()} func restore_snapshot(snapshot: Dictionary) -> bool: if _path == null or not _exact_keys(snapshot, [&"version", &"current_minute", &"request_sequence", &"shafts", &"trips"]): return _invalid("Elevator snapshot shape is invalid.") if snapshot[&"version"] != SNAPSHOT_VERSION or not snapshot[&"current_minute"] is int or snapshot[&"current_minute"] < 0 \ or not snapshot[&"request_sequence"] is int or snapshot[&"request_sequence"] < 0 \ or not snapshot[&"shafts"] is Array or not snapshot[&"trips"] is Array: return _invalid("Elevator snapshot header is invalid.") var next_shafts: Dictionary = {} var next_trips: Dictionary = {} var seen_modules: Dictionary = {} var expected_connectors: Dictionary = {} var boarded_owners: Dictionary = {} for value: Variant in snapshot[&"shafts"]: if not value is Dictionary: return _invalid("Elevator cabin must be a Dictionary.") var car: Dictionary = value if not _exact_keys(car, _new_car({&"stops": [Vector3i.ZERO]}).keys()) or not car[&"config"] is Dictionary: return _invalid("Elevator cabin fields are invalid.") var normalized := _normalize_configs([car[&"config"]]) if not normalized[&"ok"]: return false var config: Dictionary = car[&"config"] var shaft_id := StringName(config[&"shaft_id"]) if next_shafts.has(shaft_id): return _invalid("Duplicate elevator shaft.") for module_id: StringName in config[&"module_ids"]: if seen_modules.has(module_id): return _invalid("Module belongs to multiple shafts.") seen_modules[module_id] = true if not _validate_car(car, int(snapshot[&"current_minute"])): return false for actor: Variant in car[&"boarded"]: if not (actor is String or actor is StringName) or str(actor).is_empty() or boarded_owners.has(StringName(actor)): return _invalid("Elevator cabin actor is invalid or duplicated.") boarded_owners[StringName(actor)] = shaft_id for expected: Dictionary in _connector_configs(config): var connector_id: StringName = expected[&"id"] var live := _path.get_connector(connector_id) for key: StringName in expected: # The shaft-wide wait is derived from trips and checked below. if key == &"dynamic_cost": continue if not live.has(key) or live[key] != expected[key]: return _invalid("Path elevator configuration disagrees with cabin.") if live[&"cabin_state"] != _path_cabin_state(car) \ or live[&"operational_state"] != (PathService.CONNECTOR_OPERATIONAL if car[&"operational"] else PathService.CONNECTOR_STOPPED): return _invalid("Path elevator operation disagrees with cabin.") expected_connectors[connector_id] = true next_shafts[shaft_id] = car.duplicate(true) for value: Variant in snapshot[&"trips"]: if not value is Dictionary: return _invalid("Elevator trip must be a Dictionary.") var trip: Dictionary = value var keys: Array = [&"actor_id", &"request_id", &"shaft_id", &"connector_id", &"from", &"to", &"enqueued_at", &"state"] if trip.has(&"companion_id"): if not (trip[&"companion_id"] is String or trip[&"companion_id"] is StringName) or String(trip[&"companion_id"]).is_empty(): return _invalid("Invalid companion identity.") keys.append(&"companion_id") if not _exact_keys(trip, keys): return _invalid("Elevator trip fields are invalid.") for key: StringName in [&"actor_id", &"request_id", &"shaft_id", &"connector_id", &"state"]: if not (trip[key] is String or trip[key] is StringName) or str(trip[key]).is_empty(): return _invalid("Elevator trip names are invalid.") var actor_id := StringName(trip[&"actor_id"]) var shaft_id := StringName(trip[&"shaft_id"]) if next_trips.has(actor_id) or not next_shafts.has(shaft_id): return _invalid("Elevator trip actor or shaft is duplicated/missing.") var config: Dictionary = next_shafts[shaft_id][&"config"] if not trip[&"from"] is Vector3i or not trip[&"to"] is Vector3i \ or trip[&"from"] == trip[&"to"] or not (config[&"stops"] as Array).has(trip[&"from"]) or not (config[&"stops"] as Array).has(trip[&"to"]): return _invalid("Elevator trip stops are invalid.") if trip[&"connector_id"] != _connector_id(shaft_id, trip[&"from"].y, trip[&"to"].y) \ or not trip[&"enqueued_at"] is int or trip[&"enqueued_at"] < 0 or trip[&"enqueued_at"] > snapshot[&"current_minute"]: return _invalid("Elevator trip connector/time is invalid.") var active := _path.get_active_elevator_request(actor_id) if active.get(&"request_id") != trip[&"request_id"] or active.get(&"connector_id") != trip[&"connector_id"] or active.get(&"state") != trip[&"state"]: return _invalid("Elevator trip and Path active owner disagree.") if trip[&"state"] == PathService.ELEVATOR_REQUEST_BOARDED: if boarded_owners.get(actor_id) != shaft_id: return _invalid("Boarded trip has no cabin seat.") var car: Dictionary = next_shafts[shaft_id] if trip[&"to"].y != car[&"target_floor"] or trip[&"from"].y != car[&"boarding_floor"]: return _invalid("Boarded trip and cabin destination disagree.") elif trip[&"state"] == PathService.ELEVATOR_REQUEST_QUEUED: if boarded_owners.has(actor_id): return _invalid("Queued trip also owns a cabin seat.") var found := false for queued: Dictionary in _path.get_elevator_queue(trip[&"connector_id"]): if queued[&"request_id"] == trip[&"request_id"]: found = queued[&"unit_id"] == actor_id and queued[&"floor_from"] == trip[&"from"].y and queued[&"floor_to"] == trip[&"to"].y and queued[&"enqueued_at"] == trip[&"enqueued_at"] and queued[&"priority"] == PathService.ELEVATOR_PRIORITY_NORMAL if not found: return _invalid("Queued trip details disagree with Path queue.") else: return _invalid("Unknown elevator trip state.") next_trips[actor_id] = trip.duplicate(true) for actor_id: StringName in next_trips: var trip: Dictionary = next_trips[actor_id] var partner: StringName = trip.get(&"companion_id", &"") if partner.is_empty(): continue if partner == actor_id or not next_trips.has(partner): return _invalid("Missing companion trip.") var other: Dictionary = next_trips[partner] if other.get(&"companion_id", &"") != actor_id: return _invalid("Companion ownership is not reciprocal.") for key: StringName in [&"shaft_id", &"connector_id", &"from", &"to", &"state", &"enqueued_at"]: if trip[key] != other[key]: return _invalid("Companion trip was split.") for actor_id: StringName in boarded_owners: if not next_trips.has(actor_id): return _invalid("Cabin seat has no matching trip.") for shaft_id: StringName in next_shafts: var queued := 0 for trip: Dictionary in next_trips.values(): if trip[&"shaft_id"] == shaft_id and trip[&"state"] == PathService.ELEVATOR_REQUEST_QUEUED: queued += 1 for connector: Dictionary in _connector_configs(next_shafts[shaft_id][&"config"]): var expected_wait := maxi(queued - _path.elevator_queue_size(connector[&"id"]), 0) * PathService.ORTHOGONAL_MOVE_COST if int(_path.get_connector(connector[&"id"]).get(&"dynamic_cost", -1)) != expected_wait: return _invalid("Path shaft wait disagrees with queued trips.") var path_snapshot := _path.create_snapshot() for connector: Dictionary in path_snapshot[&"connectors"]: if str(connector[&"id"]).begins_with(PREFIX) and not expected_connectors.has(connector[&"id"]): return _invalid("Path contains an orphan transport connector.") for active: Dictionary in path_snapshot[&"active_elevator_units"]: if expected_connectors.has(active[&"connector_id"]) and not next_trips.has(active[&"unit_id"]): return _invalid("Path contains an orphan transport passenger.") for request_id: Variant in path_snapshot[&"known_elevator_request_ids"]: var name := str(request_id) if not name.begins_with(REQUEST_PREFIX): continue var suffix := name.trim_prefix(REQUEST_PREFIX) if not suffix.is_valid_int() or int(suffix) <= 0 or int(suffix) > snapshot[&"request_sequence"]: return _invalid("Elevator sequence would reuse an active or retired request ID.") var all_configs: Array = [] for car: Dictionary in next_shafts.values(): all_configs.append(car[&"config"]) if not _normalize_configs(all_configs)[&"ok"]: return false _shafts = next_shafts _trips = next_trips _minute = snapshot[&"current_minute"] _sequence = snapshot[&"request_sequence"] last_error = "" return true func _dispatch(car: Dictionary) -> void: var candidates: Array[Dictionary] = [] for trip: Dictionary in _trips.values(): if trip[&"shaft_id"] == car[&"config"][&"shaft_id"] and trip[&"state"] == PathService.ELEVATOR_REQUEST_QUEUED and trip[&"enqueued_at"] <= _minute: candidates.append(trip) candidates.sort_custom(func(a: Dictionary, b: Dictionary) -> bool: if a[&"enqueued_at"] != b[&"enqueued_at"]: return a[&"enqueued_at"] < b[&"enqueued_at"] return str(a[&"actor_id"]) < str(b[&"actor_id"])) if candidates.is_empty(): _idle(car) return var trip: Dictionary = candidates.front() car[&"boarding_floor"] = trip[&"from"].y car[&"target_floor"] = trip[&"to"].y if car[&"current_floor"] != car[&"boarding_floor"]: _start_phase(car, &"PICKUP", absi(int(car[&"current_floor"]) - int(car[&"boarding_floor"])) * int(car[&"config"][&"travel_minutes_per_floor"])) else: _board(car) func _board(car: Dictionary) -> void: var config: Dictionary = car[&"config"] var connector_id := _connector_id(config[&"shaft_id"], car[&"boarding_floor"], car[&"target_floor"]) var companions: Dictionary = {} for actor: StringName in _trips: if _trips[actor].has(&"companion_id"): companions[actor] = _trips[actor][&"companion_id"] var boarded := _path.dequeue_elevator_boarding(connector_id, car[&"current_floor"], _minute, config[&"capacity"], companions) car[&"boarded"] = [] for request: Dictionary in boarded: var actor_id: StringName = request[&"unit_id"] if not _trips.has(actor_id): continue _trips[actor_id][&"state"] = PathService.ELEVATOR_REQUEST_BOARDED (car[&"boarded"] as Array).append(actor_id) if boarded.is_empty(): _idle(car) return _start_phase(car, &"BOARDING", DOOR_MINUTES + boarded.size()) func _finish_phase(car: Dictionary, arrivals: Array[Dictionary]) -> void: match car[&"phase"]: &"PICKUP": car[&"current_floor"] = car[&"boarding_floor"] _board(car) &"BOARDING": _start_phase(car, &"TRAVEL", absi(int(car[&"target_floor"]) - int(car[&"current_floor"])) * int(car[&"config"][&"travel_minutes_per_floor"])) &"TRAVEL": car[&"current_floor"] = car[&"target_floor"] _start_phase(car, &"ALIGHTING", DOOR_MINUTES + (car[&"boarded"] as Array).size()) &"ALIGHTING": for actor_id: StringName in car[&"boarded"]: var trip: Dictionary = _trips[actor_id] if _path.complete_elevator_request(trip[&"connector_id"], trip[&"request_id"]): arrivals.append({&"actor_id": actor_id, &"request_id": trip[&"request_id"], &"cell": trip[&"to"], &"emergency": false, &"cancelled": false}) _trips.erase(actor_id) car[&"boarded"] = [] _idle(car) _dispatch(car) func _new_car(config: Dictionary) -> Dictionary: var floor: int = config[&"stops"][0].y return {&"config": config.duplicate(true), &"operational": true, &"operator_open": true, &"powered": true, &"current_floor": floor, &"target_floor": floor, &"boarding_floor": floor, &"phase": &"IDLE", &"remaining_minutes": 0, &"phase_total_minutes": 0, &"phase_started_at": -1, &"phase_origin_floor": floor, &"phase_passenger_count": 0, &"boarded": []} func _start_phase(car: Dictionary, phase: StringName, duration: int) -> void: car[&"phase"] = phase car[&"remaining_minutes"] = duration car[&"phase_total_minutes"] = duration car[&"phase_started_at"] = _minute car[&"phase_origin_floor"] = car[&"current_floor"] car[&"phase_passenger_count"] = car[&"boarded"].size() func _idle(car: Dictionary) -> void: car[&"phase"] = &"IDLE" car[&"target_floor"] = car[&"current_floor"] car[&"boarding_floor"] = car[&"current_floor"] car[&"phase_origin_floor"] = car[&"current_floor"] car[&"phase_started_at"] = -1 car[&"remaining_minutes"] = 0 car[&"phase_total_minutes"] = 0 car[&"phase_passenger_count"] = 0 func _visual_floor(car: Dictionary) -> float: if car[&"phase"] not in [&"PICKUP", &"TRAVEL"]: return float(car[&"current_floor"]) var destination: int = car[&"boarding_floor"] if car[&"phase"] == &"PICKUP" else car[&"target_floor"] return lerpf(float(car[&"phase_origin_floor"]), float(destination), 1.0 - float(car[&"remaining_minutes"]) / maxf(float(car[&"phase_total_minutes"]), 1.0)) func _nearest_valid_stop(car: Dictionary) -> Variant: var floor := _visual_floor(car) var nearest: Variant = null var distance := INF for cell: Vector3i in car[&"config"][&"stops"]: if not _valid_stop(cell): continue var candidate := absf(float(cell.y) - floor) if candidate < distance: nearest = cell distance = candidate return nearest func _valid_stop(cell: Vector3i) -> bool: var traffic := _path.get_cell_traffic(cell) return not traffic.is_empty() and not bool(traffic.get(&"temporary_block", false)) func _path_cabin_state(car: Dictionary) -> StringName: if not car[&"operational"]: return PathService.CABIN_OUTAGE if car[&"phase"] in [&"BOARDING", &"ALIGHTING"]: return PathService.CABIN_DOOR_OPEN if car[&"phase"] in [&"PICKUP", &"TRAVEL"]: var target: int = car[&"boarding_floor"] if car[&"phase"] == &"PICKUP" else car[&"target_floor"] return PathService.CABIN_MOVING_UP if target > int(car[&"current_floor"]) else PathService.CABIN_MOVING_DOWN return PathService.CABIN_IDLE func _publish_cabin(car: Dictionary) -> void: for connector: Dictionary in _connector_configs(car[&"config"]): _path.set_connector_operational_state(connector[&"id"], PathService.CONNECTOR_OPERATIONAL if car[&"operational"] else PathService.CONNECTOR_STOPPED) _path.set_elevator_cabin_state(connector[&"id"], _path_cabin_state(car)) _refresh_shaft_wait(car[&"config"][&"shaft_id"]) ## The cabin serves the whole shaft, so riders queued for any other floor pair ## delay this pair too. PathService already prices the connector's own queue; ## the rest of the shaft queue is published as the connector's dynamic cost. func _refresh_shaft_wait(shaft_id: StringName) -> void: if not _shafts.has(shaft_id): return var queued := 0 for trip: Dictionary in _trips.values(): if trip[&"shaft_id"] == shaft_id and trip[&"state"] == PathService.ELEVATOR_REQUEST_QUEUED: queued += 1 for connector: Dictionary in _connector_configs(_shafts[shaft_id][&"config"]): var own := _path.elevator_queue_size(connector[&"id"]) _path.set_connector_dynamic_cost(connector[&"id"], maxi(queued - own, 0) * PathService.ORTHOGONAL_MOVE_COST) func _shaft_busy(shaft_id: StringName) -> bool: if _shafts[shaft_id][&"phase"] != &"IDLE": return true for trip: Dictionary in _trips.values(): if trip[&"shaft_id"] == shaft_id: return true return false static func _connector_id(shaft_id: StringName, from: int, to: int) -> StringName: return StringName("%s.link.%d.%d" % [shaft_id, mini(from, to), maxi(from, to)]) func _connector_configs(config: Dictionary) -> Array[Dictionary]: var result: Array[Dictionary] = [] var stops: Array = config[&"stops"] for i: int in range(stops.size()): for j: int in range(i + 1, stops.size()): result.append({&"id": _connector_id(config[&"shaft_id"], stops[i].y, stops[j].y), &"from": stops[i], &"to": stops[j], &"movement_type": PathService.MOVEMENT_ELEVATOR, &"cost": PathService.ORTHOGONAL_MOVE_COST, &"bidirectional": true, &"access_id": config[&"access_id"], &"enabled": true, &"cabin_capacity": config[&"capacity"], &"queue_capacity": QUEUE_CAPACITY, &"travel_minutes_per_floor": config[&"travel_minutes_per_floor"], &"dynamic_cost": 0}) return result func _normalize_configs(configurations: Array) -> Dictionary: var configs: Dictionary = {} var modules: Dictionary = {} var landing_owners: Dictionary = {} for value: Variant in configurations: if not value is Dictionary: return _failure(&"INVALID_CONFIG", "Shaft configuration must be a Dictionary.") var config: Dictionary = value if not _exact_keys(config, [&"shaft_id", &"module_ids", &"stops", &"width", &"capacity", &"travel_minutes_per_floor", &"access_id"]): return _failure(&"INVALID_CONFIG", "Shaft configuration fields are invalid.") for key: StringName in [&"shaft_id", &"access_id"]: if not (config[key] is String or config[key] is StringName): return _failure(&"INVALID_CONFIG", "Shaft names are invalid.") var shaft_id := StringName(config[&"shaft_id"]) if not str(shaft_id).begins_with("transport.shaft.") or configs.has(shaft_id): return _failure(&"INVALID_CONFIG", "Shaft ID is invalid or duplicated.") if not config[&"stops"] is Array or config[&"stops"].size() < 2 or not config[&"module_ids"] is Array or config[&"module_ids"].is_empty(): return _failure(&"INVALID_CONFIG", "Shaft must have modules and at least two stops.") for key: StringName in [&"capacity", &"travel_minutes_per_floor", &"width"]: if not config[key] is int or config[key] <= 0: return _failure(&"INVALID_CONFIG", "Shaft dimensions/timing are invalid.") var first: Variant = config[&"stops"][0] if not first is Vector3i or first.z not in [0, 1]: return _failure(&"INVALID_CONFIG", "Shaft stop layer is invalid.") if config[&"capacity"] != (4 if first.z == 0 else 8) or config[&"travel_minutes_per_floor"] != (10 if first.z == 0 else 5): return _failure(&"INVALID_CONFIG", "Shaft capacity/speed differs from its layer.") for i: int in range(config[&"stops"].size()): var cell: Variant = config[&"stops"][i] if not cell is Vector3i or cell != Vector3i(first.x, first.y + i, first.z) or landing_owners.has(cell): return _failure(&"INVALID_CONFIG", "Shaft stops must form a unique contiguous column.") landing_owners[cell] = true for module: Variant in config[&"module_ids"]: if not (module is String or module is StringName) or str(module).is_empty() or modules.has(StringName(module)): return _failure(&"INVALID_CONFIG", "Shaft module is empty or duplicated.") modules[StringName(module)] = true configs[shaft_id] = config.duplicate(true) return {&"ok": true, &"configs": configs} func _validate_car(car: Dictionary, minute: int) -> bool: if not car[&"operational"] is bool or not car[&"operator_open"] is bool or not car[&"powered"] is bool or car[&"operational"] != (car[&"operator_open"] and car[&"powered"]) or not car[&"boarded"] is Array \ or car[&"boarded"].size() > car[&"config"][&"capacity"]: return _invalid("Elevator cabin operation/capacity is invalid.") for key: StringName in [&"current_floor", &"target_floor", &"boarding_floor", &"phase_origin_floor", &"remaining_minutes", &"phase_total_minutes", &"phase_started_at", &"phase_passenger_count"]: if not car[key] is int: return _invalid("Elevator cabin clocks and floors must be integers.") for key: StringName in [&"current_floor", &"target_floor", &"boarding_floor", &"phase_origin_floor"]: if not _has_floor(car[&"config"], car[key]): return _invalid("Elevator cabin floor is absent from its shaft.") if car[&"phase_passenger_count"] < car[&"boarded"].size() or car[&"phase_passenger_count"] > car[&"config"][&"capacity"]: return _invalid("Elevator phase passenger count is invalid.") var phase := StringName(str(car[&"phase"])) if phase not in [&"IDLE", &"PICKUP", &"BOARDING", &"TRAVEL", &"ALIGHTING"]: return _invalid("Unknown elevator phase.") if phase == &"IDLE": return _valid_or_error(car[&"boarded"].is_empty() and car[&"phase_passenger_count"] == 0 and car[&"remaining_minutes"] == 0 and car[&"phase_total_minutes"] == 0 and car[&"phase_started_at"] == -1 and car[&"target_floor"] == car[&"current_floor"] and car[&"boarding_floor"] == car[&"current_floor"] and car[&"phase_origin_floor"] == car[&"current_floor"], "Idle cabin contains active phase data.") if car[&"remaining_minutes"] <= 0 or car[&"remaining_minutes"] > car[&"phase_total_minutes"] or car[&"phase_started_at"] < 0 or car[&"phase_started_at"] > minute: return _invalid("Elevator phase countdown is invalid.") # Operational time is exact; an outage with no valid floor may hold a phase. if car[&"operational"] and car[&"phase_total_minutes"] - car[&"remaining_minutes"] != minute - car[&"phase_started_at"]: return _invalid("Elevator phase countdown disagrees with simulation time.") var expected := 0 if phase in [&"BOARDING", &"ALIGHTING"]: expected = DOOR_MINUTES + int(car[&"phase_passenger_count"]) else: var destination: int = car[&"boarding_floor"] if phase == &"PICKUP" else car[&"target_floor"] expected = absi(destination - int(car[&"phase_origin_floor"])) * int(car[&"config"][&"travel_minutes_per_floor"]) if car[&"phase_total_minutes"] != expected or expected <= 0: return _invalid("Elevator phase duration does not match distance/passengers.") if phase == &"PICKUP" and not car[&"boarded"].is_empty(): return _invalid("Empty pickup cabin contains passengers.") if car[&"phase_origin_floor"] != car[&"current_floor"]: return _invalid("Elevator phase origin disagrees with logical floor.") if phase == &"ALIGHTING" and car[&"current_floor"] != car[&"target_floor"]: return _invalid("Alighting cabin is at the wrong floor.") if phase in [&"BOARDING", &"TRAVEL"] and car[&"current_floor"] != car[&"boarding_floor"]: return _invalid("Boarding/travel origin disagrees with pickup floor.") return true func _has_floor(config: Dictionary, floor: int) -> bool: for cell: Vector3i in config[&"stops"]: if cell.y == floor: return true return false func _ordered_trips() -> Array[Dictionary]: var trips: Array[Dictionary] = [] for actor_id: StringName in _names(_trips): trips.append(_trips[actor_id].duplicate(true)) return trips func _names(values: Dictionary) -> Array[StringName]: var result: Array[StringName] = [] for value: Variant in values: result.append(StringName(value)) result.sort_custom(func(a: StringName, b: StringName) -> bool: return str(a) < str(b)) return result func _exact_keys(value: Dictionary, keys: Array) -> bool: if value.size() != keys.size(): return false for key: Variant in keys: if not value.has(key): return false return true func _failure(reason: StringName, message: String) -> Dictionary: last_error = message return {&"ok": false, &"reason": reason, &"error": message} func _invalid(message: String) -> bool: last_error = message return false func _valid_or_error(valid: bool, message: String) -> bool: if not valid: last_error = message return valid