extends "res://tests/fixtures/p08-03/placement.gd" ## Real Runtime commands; inherited formal codec/save-operation helpers unchanged. const Stream := preload("res://tests/fixtures/p23-03/diagnostic_stream.gd") const Comparison := preload("res://tests/fixtures/p23-03/layout_comparison.gd") var stream: RefCounted var layout := "A" var facility_ids: Dictionary = {} func setup() -> void: clock.reset(1, 6, 0) var state: Variant = tree.root.get_node("GameState") state.reset() expect(runtime.initialize(clock, state, 806042).get(&"valid", false), "initialize observation") clock.set_paused(true) tree.root.size = Vector2i(1440, 810) layout = OS.get_environment("P23_LAYOUT") expect(layout in ["A", "B"], "declared layout A or B") main = load("res://scenes/main/main.tscn").instantiate() main.manual_save_path = "user://saves/manual_ui" if role == "reader": import_package(directory.path_join("formal"), String(main.manual_save_path)) tree.root.add_child(main) runtime.configure_save_policy({&"enabled": false, &"daily_checkpoint_enabled": false}) stream = Stream.new() expect(stream.open(directory.path_join(role + "-stream")), "fresh observation stream") stream.watch(main.world_preview) await frames() func command(id: StringName, payload: Dictionary) -> bool: var result: CommandResult = runtime.submit_command(id, payload) expect(result.is_success(), String(id) + ": " + str(result.code)) expect(stream.record("command", {&"command": id, &"payload": payload, &"result": result.to_dictionary(), &"minute": clock.absolute_minute}), "record public command") return result.is_success() func advance(minutes: int) -> void: for unused: int in range(minutes): clock.advance_minutes(1) await frames() await observe_progress() if clock.absolute_minute % 30 == 0: expect(stream.capture("minute-%d" % clock.absolute_minute, runtime, main.world_preview), "sparse minute observation") func observe_progress() -> void: pass func prepare() -> void: # Both layouts receive the same definition-derived diagnostic construction budget. # Cafeteria/kitchen move together to x=6, leaving x=4..5 for the declared B EV. var builds: Array = [[&"reception", Vector3i(-8, 0, 0)], [&"cafeteria", Vector3i(6, 0, 0)], [&"warehouse", Vector3i(-8, 0, 1)], [&"kitchen", Vector3i(6, 0, 1)], [&"staff_door", Vector3i(0, 0, 1)], [&"standard_room", Vector3i(-8, 2, 0)], [&"guest_stairs", Vector3i(-4, 1, 0)], [&"guest_stairs", Vector3i(-4, 2, 0)], [&"guest_corridor", Vector3i(-5, 2, 0)], [&"guest_corridor", Vector3i(-6, 2, 0)], [&"guest_elevator", Vector3i.ZERO], [&"guest_corridor", Vector3i(2, 1, 0)], [&"guest_corridor", Vector3i(3, 1, 0)], [&"staff_waiting_room", Vector3i(-4, 0, 1)]] var improvement: Dictionary = Comparison.new().declaration()[&"improvement"] var budget_builds := builds.duplicate(true) budget_builds.append([improvement[&"definition_id"], improvement[&"origin"]]) var needed := {&"gold": 0, &"materials": 0} var costs: Array = [] for row: Array in budget_builds: var definition: FacilityDefinition = runtime.definition_registry.get_facility(row[0]) costs.append({&"definition": row[0], &"cost": definition.construction_cost.duplicate(true)}) for key: StringName in needed: needed[key] += int(definition.construction_cost.get(key, 0)) var state: Variant = tree.root.get_node("GameState") var deltas := {} for key: StringName in needed: if needed[key] > state.get_resource(key): deltas[key] = needed[key] - state.get_resource(key) budget = {"setup": "diagnostic", "costs": costs, "needed": needed, "initial": state.resources.duplicate(true), "deltas": deltas} if not deltas.is_empty(): command(&"change_resources", {&"deltas": deltas}) expect(stream.record("budget", budget), "common diagnostic budget") if layout == "B": builds.append([improvement[&"definition_id"], improvement[&"origin"]]) # Preserve the supply scenario's staged support/construction sequence. for index: int in range(builds.size()): if index == 5: await advance(180) if index == 7: await advance(60) if index == 8: await advance(10) command(&"build_facility", {&"definition_id": builds[index][0], &"origin": builds[index][1]}) var transport_minutes := 0 for id: StringName in [&"guest_elevator", &"guest_corridor"]: transport_minutes = maxi(transport_minutes, runtime.definition_registry.get_facility(id).build_minutes) await advance(maxi(10, transport_minutes)) for facility: FacilityInstance in runtime.facility_service.get_instances(): facility_ids[facility.definition_id] = facility.instance_id command(&"set_facility_conditions", {&"facility_id": facility_ids[&"cafeteria"], &"conditions": {&"related_facility_id": facility_ids[&"kitchen"]}}) for assignment: Array in [[&"staff_akane", &"reception"], [&"staff_gina", &"warehouse"], [&"staff_chika", &"kitchen"], [&"staff_iris", &"cafeteria"], [&"staff_juno", &"cafeteria"]]: command(&"hire_staff_candidate", {&"candidate_id": assignment[0]}) command(&"set_staff_work_assignment", {&"staff_id": assignment[0], &"facility_id": facility_ids[assignment[1]], &"template_id": &"DAY"}) command(&"purchase_supplies", {&"warehouse_id": facility_ids[&"warehouse"], &"item_id": SupplyCatalog.RAW_FOOD, &"quantity": 40}) command(&"set_kitchen_production", {&"facility_id": facility_ids[&"kitchen"], &"enabled": true, &"target_stock": 8}) # The OS observation uses the already declared transport cohort, plus # ordinary public arrivals whose service choice remains Runtime-owned. # These commands are recorded and identical across A/B; no actor is moved, # despawned, or inserted into a room/queue by the fixture. var demand: Dictionary = Comparison.new().declaration() expect(stream.record("observation-demand", {"cohort": demand[&"arrivals"], "schedule": "all observation actors at the recorded current minute; offsets apply only to native layout comparison", "minute": clock.absolute_minute}), "declared observation demand") for arrival: Dictionary in demand[&"arrivals"]: command(&"spawn_guest", {&"guest_id": StringName("os-" + String(arrival[&"id"])), &"guest_type_id": &"traveler", &"gold": 100, &"options": {&"current_cell": Comparison.START, &"state": GuestRuntime.State.MOVING, &"state_entered_at": clock.absolute_minute - 1, &"planned_exit_at": clock.absolute_minute + 2000, &"path_handle": {&"target_cell": Comparison.GOAL}}}) command(&"spawn_guest", {&"guest_id": StringName("service-" + String(arrival[&"id"])), &"guest_type_id": &"traveler", &"gold": 100}) await advance(120) func layer_observations(prefix: String) -> void: await button(main.front_button) expect(stream.capture(prefix + "-FRONT", runtime, main.world_preview), "FRONT state/report") await button(main.back_button) expect(stream.capture(prefix + "-BACK", runtime, main.world_preview), "BACK state/report") func writer() -> void: await prepare() await layer_observations("writer") capture("presave") await button(main.find_child("ManualSaveButton", true, false)) expect(main.build_status_label.text == "手動セーブを保存しました。", "normal save") capture("checkpoint") export_package(String(main.manual_save_path), "formal") var formal := inspect_formal() var presave := FileAccess.get_file_as_bytes(directory.path_join("writer-presave.var")) expect(presave == var_to_bytes(formal), "whole presave formal model") expect(not operation_results.is_empty(), "save result recorded") if not operation_results.is_empty(): verify_operation(formal, operation_results.back(), &"SAVE_CHECKPOINT_SUCCEEDED") var members := {} for member: String in MEMBERS: members[member] = FileAccess.get_sha256(directory.path_join("formal/" + member)) json_file("save.json", {"pid": OS.get_process_id(), "members": members}) func reader(distinct: bool) -> void: var saved: Dictionary = JSON.parse_string(FileAccess.get_file_as_string(directory.path_join("save.json"))) expect(distinct and saved.pid != OS.get_process_id(), "distinct actual reader PID") await load_menu(main.find_child("SaveLoadMenu", true, false)) await frames() expect(main.build_status_label.text == "手動セーブを読み込みました。", "normal resume") capture("checkpoint") var formal := inspect_formal() expect(FileAccess.get_file_as_bytes(directory.path_join("writer-formal-model.var")) == var_to_bytes(formal), "whole formal resume model") for member: String in MEMBERS: expect(FileAccess.get_sha256(directory.path_join("formal/" + member)) == saved.members[member], "formal member " + member) expect(not operation_results.is_empty(), "load result recorded") if not operation_results.is_empty(): verify_operation(formal, operation_results.back(), &"SAVE_RECOVERY_LOADED") await layer_observations("reader") func finish_observations() -> void: stream.close()