extends SceneTree ## MOVE-06 component comparison: fixed arrival demand, actual public construction, and the ## production Path/Elevator services. These are measurement actors, not the ## economic Guest AI. Route choice is committed once at common-lobby arrival. ## Both scenarios use Path's production explicit floor-connector policy. const SEED := 806042 const POPULATION := 48 const WINDOW_MINUTES := 480 const START := Vector3i(2, 0, 0) const GOAL := Vector3i(2, 1, 0) const CREDIT_SCALE := 1000 const REPORT_PATH := "res://artifacts/transport/path-comparison.json" const DEMAND_PATH := "res://artifacts/transport/comparison-demand.json" var _checks := 0 var _failures := 0 func _initialize() -> void: call_deferred("_run") func _run() -> void: var global_clock := root.get_node_or_null("GameClock") if global_clock != null: global_clock.set_paused(true) var demand := _demand() _expect(demand.size() == POPULATION and SimulationDigest.compute(demand) == SimulationDigest.compute(_demand()), "seed fixes all 48 actor IDs, arrival minutes, origins and destinations") var one := _prepare(1) var two := _prepare(2) if one.is_empty() or two.is_empty(): _finish() return var before := _observe(one, demand, WINDOW_MINUTES) var after := _observe(two, demand, WINDOW_MINUTES) _expect(before[&"demand_hash"] == after[&"demand_hash"] and before[&"demand_count"] == after[&"demand_count"], "both configurations replay the identical demand manifest") _expect(before[&"window_minutes"] == after[&"window_minutes"], "both configurations use the same fixed observation window") _expect(before[&"arrived_count"] == POPULATION and after[&"arrived_count"] == POPULATION, "no passengers are removed to make the improved layout faster") _expect(before[&"completed_count"] + before[&"unfinished_count"] == POPULATION and after[&"completed_count"] + after[&"unfinished_count"] == POPULATION, "completed and censored unfinished populations account for every arrival") _expect(before[&"failed_count"] == 0 and after[&"failed_count"] == 0, "every measurement actor has a legal route and conserved Path ownership") _expect(before[&"walking_steps_min"] == 4 and before[&"walking_steps_max"] == 4 and after[&"walking_steps_min"] == 4 and after[&"walking_steps_max"] == 4, "left and right boarding positions have the same four horizontal walking steps") _expect(after[&"shaft_usage"].size() == 2, "public shortest-path costs distribute demand to both installed cabins") _expect(before[&"completed_count"] == POPULATION and after[&"completed_count"] == POPULATION, "both layouts complete the entire cohort inside the fixed window") _expect(_is_improved(before, after), "second identical cabin improves wait and journey P95 only after both layouts complete equal demand") var replay := _observe(one, demand, WINDOW_MINUTES) _expect(replay == before, "same topology snapshot and seed reproduce all measured outcomes exactly") var censored := _observe(two, demand, 30) _expect(censored[&"unfinished_count"] > 0 and not _is_improved(before, censored), "short-window incomplete observations cannot masquerade as an improvement") var report := { &"scope": "Fixed-demand Path/Elevator actor harness; not natural economic Guest AI or full S14 congestion acceptance.", &"route_policy": "Public find_path at common-lobby arrival; commit that route for this journey; real shared-cabin boarding and minute ticks.", &"topology_policy": "Production explicit vertical connectors; no benchmark floor-separation blockers.", &"seed": SEED, &"demand_count": POPULATION, &"observation_minutes": WINDOW_MINUTES, &"demand_hash": SimulationDigest.compute(demand), &"before": before, &"after": after, &"equipment_cost_difference": { &"gold": two[&"costs"][&"gold"] - one[&"costs"][&"gold"], &"materials": two[&"costs"][&"materials"] - one[&"costs"][&"materials"], &"maintenance_mana_demand": two[&"costs"][&"maintenance_mana_demand"] - one[&"costs"][&"maintenance_mana_demand"]}, &"improvement_accepted": _is_improved(before, after), &"checks": _checks, &"failures": _failures} DirAccess.make_dir_recursive_absolute(ProjectSettings.globalize_path("res://artifacts/transport")) var output := FileAccess.open(REPORT_PATH, FileAccess.WRITE) if output != null: output.store_string(JSON.stringify(report, "\t") + "\n") var manifest := FileAccess.open(DEMAND_PATH, FileAccess.WRITE) if manifest != null: manifest.store_string(JSON.stringify(demand, "\t") + "\n") print("TRANSPORT_COMPARISON ", JSON.stringify({&"before": _summary(before), &"after": _summary(after), &"improved": report[&"improvement_accepted"]})) _finish() func _demand() -> Array[Dictionary]: var rng := RandomNumberGenerator.new() rng.seed = SEED var result: Array[Dictionary] = [] for index: int in range(POPULATION): result.append({&"actor_id": StringName("comparison/%03d" % index), &"arrived_at": (index / 4) * 15 + rng.randi_range(0, 2), &"from": [START.x, START.y, START.z], &"to": [GOAL.x, GOAL.y, GOAL.z]}) result.sort_custom(func(a: Dictionary, b: Dictionary) -> bool: if a[&"arrived_at"] != b[&"arrived_at"]: return a[&"arrived_at"] < b[&"arrived_at"] return str(a[&"actor_id"]) < str(b[&"actor_id"])) return result func _prepare(cabins: int) -> Dictionary: var runtime: Variant = load("res://autoload/simulation_runtime.gd").new() var clock: Variant = load("res://autoload/game_clock.gd").new() var state: Variant = load("res://autoload/game_state.gd").new() clock.reset(1, 6, 0) state.reset() _expect(runtime.initialize(clock, state, SEED)[&"valid"], "%d-cabin fixture initializes with normal 5000G/50 materials" % cabins) runtime.configure_save_policy({&"enabled": false, &"daily_checkpoint_enabled": false}) clock.set_paused(true) var builds: Array = [ [&"guest_elevator", Vector3i.ZERO], [&"guest_corridor", Vector3i(2, 1, 0)], [&"guest_corridor", Vector3i(3, 1, 0)]] if cabins == 2: builds.append([&"guest_elevator", Vector3i(4, 0, 0)]) var success := true for index: int in range(builds.size()): var result: CommandResult = runtime.submit_command(&"build_facility", { &"definition_id": builds[index][0], &"origin": builds[index][1]}, &"player", &"", 0, clock.absolute_minute, StringName("comparison.build.%d" % index)) _expect(result.is_success(), "%d-cabin case pays for %s at %s" % [cabins, builds[index][0], builds[index][1]]) success = success and result.is_success() clock.advance_minutes(310) var report: Dictionary = runtime.elevators.get_report() _expect(report[&"shafts"].size() == cabins, "%d-cabin construction physically completes" % cabins) success = success and report[&"shafts"].size() == cabins var maintenance := 0 for shaft: Dictionary in report[&"shafts"]: _expect(shaft[&"operational"] and shaft[&"capacity"] == 4 and shaft[&"travel_minutes_per_floor"] == 10, "comparison cabins have identical power, capacity and speed") for module: StringName in shaft[&"module_ids"]: var facility: Dictionary = runtime.construction_service.get_facility(module) var definition: FacilityDefinition = runtime.definition_registry.get_facility(facility[&"definition_id"]) maintenance += definition.maintenance_mana var result := {&"shaft_count": cabins, &"minute": clock.absolute_minute, &"cells": runtime.route_subslot_path_adapter.get_projected_walkable_cells(), &"path_snapshot": runtime.path_service.create_snapshot(), &"elevator_snapshot": runtime.elevators.create_snapshot(), &"costs": {&"gold": 5000 - state.get_resource(&"gold"), &"materials": 50 - state.get_resource(&"materials"), &"maintenance_mana_demand": maintenance}} runtime.free() clock.free() state.free() return result if success else {} func _observe(prepared: Dictionary, demand: Array[Dictionary], window: int) -> Dictionary: var path := PathService.new() path.rebuild_graph(prepared[&"cells"]) path.restore_snapshot(prepared[&"path_snapshot"]) _expect(path.get_vertical_walk_requires_connector(), "prepared Runtime uses production strict floor routing without benchmark overrides") var elevators := ElevatorService.new() elevators.configure(path) elevators.restore_snapshot(prepared[&"elevator_snapshot"]) var actors: Dictionary = {} var shaft_usage: Dictionary = {} var completed: Array[Dictionary] = [] var failed := 0 var start_minute: int = prepared[&"minute"] for offset: int in range(window + 1): var minute := start_minute + offset if offset > 0: for arrival: Dictionary in elevators.advance_one_minute(minute): var actor: Dictionary = actors[arrival[&"actor_id"]] actor[&"cell"] = arrival[&"cell"] actor[&"route_index"] += 1 actor[&"alighted_at"] = offset actor[&"next_move_at"] = offset + 1 path.project_actor_position(arrival[&"actor_id"], arrival[&"cell"], minute) for row: Dictionary in demand: if row[&"arrived_at"] != offset: continue var actor_id: StringName = row[&"actor_id"] path.actor_enter_cell(actor_id, START, minute) var route := path.find_path(START, GOAL, [0]) var actor := {&"actor_id": actor_id, &"arrived_at": offset, &"cell": START, &"route": route, &"route_index": 0, &"requested_at": -1, &"boarded_at": -1, &"alighted_at": -1, &"completed_at": -1, &"walking_steps": 0, &"credit": 0, &"next_move_at": offset + 1, &"failed": route.is_empty()} actors[actor_id] = actor if route.is_empty(): failed += 1 var actor_ids: Array = actors.keys() actor_ids.sort() for actor_id: StringName in actor_ids: var actor: Dictionary = actors[actor_id] if actor[&"failed"] or actor[&"completed_at"] >= 0: continue var trip := elevators.get_actor_trip(actor_id) if not trip.is_empty(): if trip[&"state"] == PathService.ELEVATOR_REQUEST_BOARDED and actor[&"boarded_at"] < 0: actor[&"boarded_at"] = offset continue if actor[&"cell"] == GOAL: actor[&"completed_at"] = offset completed.append(actor.duplicate(true)) path.despawn_actor(actor_id) continue if actor[&"next_move_at"] > offset: continue var route: Array = actor[&"route"] var index: int = actor[&"route_index"] var next: Vector3i = route[index + 1] var connector := path.get_step_connector(actor[&"cell"], next) if elevators.owns_connector(connector): var request := elevators.request_trip(actor_id, actor[&"cell"], next, minute) if not request[&"ok"]: actor[&"failed"] = true failed += 1 else: actor[&"requested_at"] = offset var shaft: StringName = request[&"trip"][&"shaft_id"] shaft_usage[shaft] = int(shaft_usage.get(shaft, 0)) + 1 continue actor[&"credit"] += roundi(path.get_speed_multiplier(actor[&"cell"]) * CREDIT_SCALE) if actor[&"credit"] < CREDIT_SCALE: continue if not path.project_actor_position(actor_id, next, minute): actor[&"failed"] = true failed += 1 continue actor[&"cell"] = next actor[&"route_index"] += 1 actor[&"walking_steps"] += 1 actor[&"credit"] -= CREDIT_SCALE if next == GOAL: actor[&"completed_at"] = offset completed.append(actor.duplicate(true)) path.despawn_actor(actor_id) var waits: Array[int] = [] var journeys: Array[int] = [] var rides: Array[int] = [] var walking_steps: Array[int] = [] var unfinished: Array[Dictionary] = [] var waiting := 0 var aboard := 0 var walking := 0 for actor_id: StringName in actors: var actor: Dictionary = actors[actor_id] if actor[&"boarded_at"] >= 0: waits.append(actor[&"boarded_at"] - actor[&"requested_at"]) if actor[&"alighted_at"] >= 0: rides.append(actor[&"alighted_at"] - actor[&"boarded_at"]) if actor[&"completed_at"] >= 0: journeys.append(actor[&"completed_at"] - actor[&"arrived_at"]) walking_steps.append(actor[&"walking_steps"]) else: var state: StringName = elevators.get_actor_trip(actor_id).get(&"state", &"WALKING") if state == PathService.ELEVATOR_REQUEST_QUEUED: waiting += 1 elif state == PathService.ELEVATOR_REQUEST_BOARDED: aboard += 1 else: walking += 1 unfinished.append({&"actor_id": actor_id, &"state": state, &"age_minutes": window - actor[&"arrived_at"], &"observed_wait_minutes": window - actor[&"requested_at"] if state == PathService.ELEVATOR_REQUEST_QUEUED else 0}) walking_steps.sort() return {&"shaft_count": prepared[&"shaft_count"], &"costs": prepared[&"costs"], &"window_minutes": window, &"demand_count": demand.size(), &"demand_hash": SimulationDigest.compute(demand), &"arrived_count": actors.size(), &"not_arrived_count": demand.size() - actors.size(), &"completed_count": completed.size(), &"unfinished_count": actors.size() - completed.size(), &"waiting_count": waiting, &"aboard_count": aboard, &"walking_count": walking, &"failed_count": failed, &"queue_wait_minutes": _stats(waits), &"journey_minutes": _stats(journeys), &"ride_minutes": _stats(rides), &"walking_steps_min": walking_steps[0] if not walking_steps.is_empty() else -1, &"walking_steps_max": walking_steps[-1] if not walking_steps.is_empty() else -1, &"shaft_usage": shaft_usage, &"completed_actors": completed, &"unfinished_actors": unfinished} func _stats(values: Array[int]) -> Dictionary: if values.is_empty(): return {&"samples": 0, &"mean": null, &"p95": null, &"max": null} var sorted := values.duplicate() sorted.sort() var total := 0 for value: int in sorted: total += value return {&"samples": sorted.size(), &"mean": float(total) / sorted.size(), &"p95": sorted[maxi(0, ceili(sorted.size() * 0.95) - 1)], &"max": sorted[-1]} func _is_improved(before: Dictionary, after: Dictionary) -> bool: return before[&"demand_hash"] == after[&"demand_hash"] and before[&"window_minutes"] == after[&"window_minutes"] \ and before[&"completed_count"] == before[&"demand_count"] and after[&"completed_count"] == before[&"completed_count"] \ and before[&"failed_count"] == 0 and after[&"failed_count"] == 0 \ and after[&"queue_wait_minutes"][&"p95"] < before[&"queue_wait_minutes"][&"p95"] \ and after[&"journey_minutes"][&"p95"] < before[&"journey_minutes"][&"p95"] func _summary(report: Dictionary) -> Dictionary: return {&"cabins": report[&"shaft_count"], &"completed": report[&"completed_count"], &"unfinished": report[&"unfinished_count"], &"wait": report[&"queue_wait_minutes"], &"journey": report[&"journey_minutes"], &"shaft_usage": report[&"shaft_usage"]} func _expect(condition: bool, label: String) -> void: _checks += 1 if not condition: _failures += 1 print("%s: %s" % ["PASS" if condition else "FAIL", label]) func _finish() -> void: print("Path and cabin comparison: %d checks, %d failures" % [_checks, _failures]) quit(0 if _failures == 0 else 1)