extends SceneTree ## A07 Mana warp through ordinary construction, natural guests and the reserve. ## "-- writer" (default) runs the mechanics and saves mid-operation; "-- reader" ## restores that save in a new process and must reproduce the writer exactly. const Warp = preload("res://scripts/transport/warp_service.gd") const ManaOutage = preload("res://tests/helpers/mana_outage.gd") const SAVE_PATH := "user://saves/p14_02_warp_new_pid" const EXPECTED_PATH := "user://saves/p14_02_warp_new_pid_expected.json" const CONTINUE_MINUTES := 120 var _checks := 0 var _failures := 0 var _serial := 0 var _clock: Variant var _state: Variant var _runtime: Variant func _initialize() -> void: call_deferred("_run") func _run() -> void: var mode := "reader" if "reader" in OS.get_cmdline_user_args() else "writer" print("Warp runtime %s (pid %d)" % [mode, OS.get_process_id()]) _clock = load("res://autoload/game_clock.gd").new() _state = load("res://autoload/game_state.gd").new() _runtime = load("res://autoload/simulation_runtime.gd").new() _clock.reset(1, 6, 0) _state.reset() _runtime.initialize(_clock, _state, 8808) _runtime.configure_save_policy({&"enabled": false, &"daily_checkpoint_enabled": false}) _clock.set_paused(true) _expect(bool(_runtime.enable_production_operations().get(&"ok", false)), "natural operations start at Day 1 06:00") if mode == "writer": _write() else: _read() _runtime.free(); _clock.free(); _state.free() print("%s: %d checks" % ["PASS" if _failures == 0 else "FAIL", _checks]) quit(_failures) func _cmd(kind: StringName, payload: Dictionary) -> CommandResult: _serial += 1 return _runtime.submit_command(kind, payload, &"player", &"", 0, _clock.absolute_minute, StringName("warp.test.%d" % _serial)) 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 _warp_connectors() -> Array[StringName]: return _runtime.path_service.get_connector_ids_with_prefix(Warp.PREFIX) func _warp_events() -> Array[Dictionary]: var rows: Array[Dictionary] = [] for event: SimulationDomainEvent in _runtime.domain_events.recent_events(): if event.event_type == &"WARP_TRAVELED": rows.append({&"minute": event.absolute_minute, &"mana": int(event.payload[&"mana"])}) return rows func _write() -> void: _expect(_cmd(&"change_resources", {&"deltas": {&"gold": 12000, &"materials": 100}}).is_success(), "fund four warps for the fixture") var gold_before: int = _state.get_resource(&"gold") if not _build(&"mana_warp", Vector3i(0, 0, 0)) or not _build(&"reception", Vector3i(2, 0, 0)): return _expect(gold_before - int(_state.get_resource(&"gold")) >= 3000, "warp construction charges its approved 3000G") _clock.advance_minutes(359) _expect(_warp_connectors().is_empty(), "a single warp has no destination") _clock.advance_minutes(1) if not _build(&"mana_warp", Vector3i(0, 2, 0)) or not _build(&"standard_room", Vector3i(2, 2, 0)): return _clock.advance_minutes(360) _expect(_runtime.path_service.find_path(Vector3i(0, 0, 0), Vector3i(0, 2, 0)).is_empty(), "new warp waits for its first power evaluation") _clock.advance_minutes(10 - int(_clock.absolute_minute) % 10) var ids := _warp_connectors() _expect(ids.size() == 1, "two completed warps are linked by one connector") if ids.size() != 1: return var connector: Dictionary = _runtime.path_service.get_connector(ids[0]) _expect(connector[&"from"] == Vector3i(0, 0, 0) and connector[&"to"] == Vector3i(0, 2, 0) and connector[&"operational_state"] == PathService.CONNECTOR_OPERATIONAL, "connector joins both warp origins and is operational") _expect(_runtime.path_service.find_path(Vector3i(0, 0, 0), Vector3i(0, 2, 0)) == [Vector3i(0, 0, 0), Vector3i(0, 2, 0)], "path crosses floors in one instant step") _expect(_runtime.mana_network_service.get_total_consumer_demand() >= 2, "each warp registers 1 Mana upkeep") _expect(_runtime.path_service.find_path(Vector3i(0, 0, 0), Vector3i(0, 2, 0), [WorldService.LAYER_FRONT, WorldService.LAYER_BACK], [&"staff"]).is_empty(), "staff routes never use the guest warp (A08)") _expect(_build(&"service_warp", Vector3i(0, 0, 1)), "build lower BACK service warp") _clock.advance_minutes(360) _expect(_build(&"service_warp", Vector3i(0, 2, 1)), "build upper BACK service warp on the completed one") _clock.advance_minutes(360) _clock.advance_minutes(10 - int(_clock.absolute_minute) % 10) _expect(_warp_connectors().size() == 2, "service warps link only to each other, never to guest warps") var staff_route: Array = _runtime.path_service.find_path(Vector3i(0, 0, 1), Vector3i(0, 2, 1), [WorldService.LAYER_FRONT, WorldService.LAYER_BACK], [&"staff"]) _expect(staff_route == [Vector3i(0, 0, 1), Vector3i(0, 2, 1)], "staff cross floors through the BACK service warp") _expect(_runtime.path_service.find_path(Vector3i(0, 0, 1), Vector3i(0, 2, 1)).is_empty(), "guests cannot use the service warp") _expect(_runtime.call(&"_warp_step_connector", Vector3i(0, 0, 1), Vector3i(0, 2, 1), [&"staff"] as Array[StringName]) != &"", "a staff step through the service warp is fare-bearing") # Outage and an unaffordable fare both remove the warp from routing. var before_outage: Dictionary = _runtime.create_snapshot() ManaOutage.hold(_runtime, _state) _clock.advance_minutes(1) _expect(_runtime.path_service.get_connector(ids[0])[&"operational_state"] == PathService.CONNECTOR_OUTAGE, "reserve outage stops the warp") _expect(_runtime.path_service.find_path(Vector3i(0, 0, 0), Vector3i(0, 2, 0)).is_empty(), "stopped warp offers no route") _expect(_runtime.path_service.find_path_report(Vector3i(0, 0, 0), Vector3i(0, 2, 0))[&"reason"] == PathService.ROUTE_CONNECTOR_UNAVAILABLE, "guest sees the stopped warp as an unavailable connector") ManaOutage.release(_runtime, _state, Warp.MANA_PER_TRIP - 1) _clock.advance_minutes(1) _expect(_runtime.path_service.get_connector(ids[0])[&"operational_state"] == PathService.CONNECTOR_OUTAGE, "reserve below one fare keeps the warp closed") _expect(_runtime.restore_snapshot(before_outage), "rewind outage drill") _clock.advance_minutes(1) var mana_blocked: int = _state.get_resource(&"mana") _runtime.path_service.set_cell_temporary_block(Vector3i(0, 2, 0), true) _expect(_runtime.path_service.find_path(Vector3i(0, 0, 0), Vector3i(0, 2, 0)).is_empty() and _runtime.call(&"_warp_step_connector", Vector3i(0, 0, 0), Vector3i(0, 2, 0), [] as Array[StringName]) == &"", "blocked arrival offers no warp step and no fare") _expect(_state.get_resource(&"mana") == mana_blocked, "blocked arrival charges nothing") _expect(_runtime.restore_snapshot(before_outage), "rewind arrival drill") # Natural guests use the warp; each crossing charges exactly 3 Mana once. _expect(_cmd(&"hire_staff_candidate", {&"candidate_id": &"staff_akane"}).is_success(), "hire receptionist") var reception: FacilityInstance = null for facility: FacilityInstance in _runtime.facility_service.get_instances(): if facility.definition_id == &"reception": reception = facility _expect(_cmd(&"set_staff_work_assignment", {&"staff_id": &"staff_akane", &"facility_id": reception.instance_id, &"template_id": &"DAY"}).is_success(), "assign receptionist") var saved := false var crossings := 0 for minute: int in range(1200): var mana_before: int = _state.get_resource(&"mana") var events_before := _warp_events().size() _clock.advance_minutes(1) var new_events := _warp_events().size() - events_before if new_events > 0: crossings += new_events var boundary: bool = int(_clock.absolute_minute) % 10 == 0 if not boundary: _expect(mana_before - int(_state.get_resource(&"mana")) == Warp.MANA_PER_TRIP * new_events, "warp crossing charges 3 Mana per rider") if not saved: saved = true var snapshot: Dictionary = _runtime.create_snapshot() _expect(SaveService.save_snapshot(SAVE_PATH, snapshot)[&"ok"], "save right after a paid crossing") _clock.advance_minutes(CONTINUE_MINUTES) var file := FileAccess.open(EXPECTED_PATH, FileAccess.WRITE) file.store_string(JSON.stringify({ "writer_pid": OS.get_process_id(), "saved_events": events_before + new_events, "mana": _state.get_resource(&"mana"), "digest": SimulationDigest.compute(_runtime.create_snapshot()), "events": JSON.stringify(_warp_events()), })) file.close() break if crossings == 0: for guest: GuestRuntime in _runtime.guest_service.get_guests(): print("GUEST ", guest.guest_id, " ", guest.state, " ", guest.current_cell) for facility: FacilityInstance in _runtime.facility_service.get_instances(): print("FAC ", facility.definition_id, " ", facility.state, " ", facility.active_staff_ids) _expect(crossings > 0, "a natural guest crosses by warp") _expect(saved, "mid-operation save written") _expect(_runtime.restore_snapshot(_runtime.create_snapshot()), "warp state round trips") # Demolishing one end removes the link. var upper: StringName = &"" for facility: Dictionary in _runtime.construction_service.get_facilities(): if facility[&"definition_id"] == &"mana_warp" and facility[&"origin"] == Vector3i(0, 2, 0): upper = facility[&"instance_id"] var occupied := false for guest: GuestRuntime in _runtime.guest_service.get_guests(): if guest.current_cell.x <= 1 and guest.current_cell.y >= 2: occupied = true if not occupied: _expect(_cmd(&"demolish_facility", {&"instance_id": upper}).is_success(), "demolish upper warp") _expect(_warp_connectors().is_empty(), "demolition removes the warp link") _expect(_runtime.restore_snapshot(_runtime.create_snapshot()), "post-demolition state restores") func _read() -> void: var expected: Variant = JSON.parse_string(FileAccess.get_file_as_string(EXPECTED_PATH)) _expect(expected is Dictionary, "reader finds writer record") if not expected is Dictionary: return _expect(int(expected["writer_pid"]) != OS.get_process_id(), "reader runs in a different process") var loaded := SaveService.load_snapshot(SAVE_PATH) _expect(loaded[&"ok"] and _runtime.restore_snapshot(loaded[&"snapshot"]), "reader restores mid-operation save") _expect(_warp_events().size() == int(expected["saved_events"]), "restored crossing history is not replayed") _clock.advance_minutes(CONTINUE_MINUTES) _expect(_state.get_resource(&"mana") == int(expected["mana"]), "continued Mana matches: no double fare") _expect(JSON.stringify(_warp_events()) == String(expected["events"]), "continued crossings match the writer") _expect(SimulationDigest.compute(_runtime.create_snapshot()) == String(expected["digest"]), "whole continued state digest matches") SaveService.delete_save(SAVE_PATH) DirAccess.remove_absolute(ProjectSettings.globalize_path(EXPECTED_PATH)) func _expect(condition: bool, label: String) -> void: _checks += 1 if condition: print(" ✓ %s" % label) else: _failures += 1 push_error(" ✗ %s" % label)