P14-03: Price whole-shaft elevator waits in A* route choice
Riders queued for any floor pair in a shaft now delay every pair: the elevator service publishes the rest of the shaft queue as each connector's dynamic cost, refreshed on trip changes and each cabin minute, and restore rejects saves whose wait disagrees with their trips. A two-shaft fixture with real guests shows rush diversion, shared cabins within capacity, recovery, route loss and warp-exit congestion. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
50ceca0b7d
commit
0754f412a8
@@ -170,6 +170,7 @@ func request_trip(actor_id: StringName, from: Vector3i, to: Vector3i, minute: in
|
||||
&"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)}
|
||||
|
||||
@@ -264,6 +265,7 @@ func _cancel_single(actor_id: StringName) -> bool:
|
||||
last_error = _path.last_error
|
||||
return false
|
||||
_trips.erase(actor_id)
|
||||
_refresh_shaft_wait(trip[&"shaft_id"])
|
||||
last_error = ""
|
||||
return true
|
||||
|
||||
@@ -400,6 +402,8 @@ func restore_snapshot(snapshot: Dictionary) -> bool:
|
||||
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):
|
||||
@@ -455,6 +459,15 @@ func restore_snapshot(snapshot: Dictionary) -> bool:
|
||||
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"]):
|
||||
@@ -605,6 +618,22 @@ 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:
|
||||
|
||||
Reference in New Issue
Block a user