extends SceneTree ## A real inn from the default 5,000 G / 50 materials. No inserted guests, ## inventory, employees, completed construction, or free resource commands. var _checks := 0 var _failures := 0 var _runtime: Variant var _clock: Variant var _state: Variant var _ids: Dictionary = {} var _saw_loaded := false var _saw_movement := false var _saw_batch := false var _save_checked := false var _last_porter_cell := Vector3i.ZERO var _save_path := "user://tests/normal_supply_%d/cargo" % OS.get_process_id() var _main: Control func _initialize() -> void: call_deferred("_run") func _run() -> void: _runtime = root.get_node("SimulationRuntime") _clock = root.get_node("GameClock") _state = root.get_node("GameState") _clock.reset(1, 6, 0) _state.reset() _runtime.initialize(_clock, _state, 424242) _clock.set_paused(true) root.size = Vector2i(1440, 810) _main = load("res://scenes/main/main.tscn").instantiate() as Control _main.set("manual_save_path", "user://tests/supply_normal_%d/manual" % OS.get_process_id()) root.add_child(_main) await process_frame _runtime.configure_save_policy({&"enabled": false, &"daily_checkpoint_enabled": false}) _expect(_state.get_resource(&"gold") == 5000 and _state.get_resource(&"materials") == 50, "ordinary starting resources") for row: Array in [[&"reception", Vector3i(-8, 0, 0)], [&"cafeteria", Vector3i(2, 0, 0)], [&"warehouse", Vector3i(-8, 0, 1)], [&"kitchen", Vector3i(2, 0, 1)], [&"staff_door", Vector3i(0, 0, 1)]]: if not _command(&"build_facility", {&"definition_id": row[0], &"origin": row[1]}): _finish() return await _advance(180) _command(&"build_facility", {&"definition_id": &"standard_room", &"origin": Vector3i(-8, 2, 0)}) _command(&"build_facility", {&"definition_id": &"guest_corridor", &"origin": Vector3i(-4, 1, 0)}) await _advance(60) _command(&"build_facility", {&"definition_id": &"guest_corridor", &"origin": Vector3i(-4, 2, 0)}) await _advance(10) for x: int in [-5, -6]: _command(&"build_facility", {&"definition_id": &"guest_corridor", &"origin": Vector3i(x, 2, 0)}) await _advance(10) for facility: FacilityInstance in _runtime.facility_service.get_instances(): _ids[facility.definition_id] = facility.instance_id for assignment: Array in [[&"staff_akane", &"reception"], [&"staff_gina", &"warehouse"], [&"staff_chika", &"kitchen"], [&"staff_iris", &"cafeteria"], [&"staff_juno", &"cafeteria"]]: if not _command(&"hire_staff_candidate", {&"candidate_id": assignment[0]}) or not _command(&"set_staff_work_assignment", {&"staff_id": assignment[0], &"facility_id": _ids[assignment[1]], &"template_id": &"DAY"}): _finish() return _expect(_state.get_resource(&"materials") == 4, "all buildings and the staff door fit within the starting material budget") var warehouse: FacilityInstance = _runtime.facility_service.get_instance(_ids[&"warehouse"]) var cash_before: int = _state.get_resource(&"gold") _command(&"purchase_supplies", {&"warehouse_id": warehouse.instance_id, &"item_id": SupplyCatalog.RAW_FOOD, &"quantity": 40}) _expect(_state.get_resource(&"gold") == cash_before - 120 and _runtime.inventory_service.total_quantity(warehouse.local_inventory_id, SupplyCatalog.RAW_FOOD) == 40, "40 raw food costs exactly 120 G and arrives only in the warehouse") _command(&"set_kitchen_production", {&"facility_id": _ids[&"kitchen"], &"enabled": true, &"target_stock": 8}) await _advance(120) _expect(_saw_movement and _saw_loaded, "an employed porter walks the real route and carries purchased food") _expect(_saw_batch, "the kitchen starts autonomous production without a guest request") _expect(_save_checked, "a mid-delivery disk save restores ownership and replays exactly") _expect(_completed_deliveries(SupplyCatalog.RAW_FOOD) > 0 and _completed_deliveries(SupplyCatalog.BASIC_MEAL) > 0, "both warehouse-to-kitchen and kitchen-to-cafeteria deliveries complete") _expect(_sales(&"cafeteria") > 0, "natural guests pay for meals made from purchased food") await _capture() if OS.get_environment("THE_TREE_SUPPLY_PREVIEW") == "1": _start_preview() return if OS.get_environment("THE_TREE_SUPPLY_CAPTURE_ONLY") == "1": _finish() return for day: int in range(1, 4): await _advance(360 + 1440 * day - _clock.absolute_minute) var summaries: Array = _runtime.operations_coordinator.get_day_summaries() _expect(summaries.size() == day and bool(summaries[-1].get(&"balanced", false)), "business day %d closes with a reconciled ledger" % day) _expect(_state.get_resource(&"gold") >= 0, "day %d retains cash after real payroll and purchases" % day) if day < 3: _command(&"purchase_supplies", {&"warehouse_id": _ids[&"warehouse"], &"item_id": SupplyCatalog.RAW_FOOD, &"quantity": 40}) _expect(_sales(&"standard_room") > 0, "the same inn earns lodging revenue") _expect(_runtime.restore_snapshot(_runtime.create_snapshot()), "three-day supply history remains restorable") var evidence := {"checks": _checks, "failures": _failures, "meal_sales": _sales(&"cafeteria"), "lodging_sales": _sales(&"standard_room"), "raw_deliveries": _completed_deliveries(SupplyCatalog.RAW_FOOD), "meal_deliveries": _completed_deliveries(SupplyCatalog.BASIC_MEAL), "cash": _state.get_resource(&"gold"), "closes": _runtime.operations_coordinator.get_day_summaries(), "supply": _runtime.get_supply_management_report()} var evidence_path := ProjectSettings.globalize_path("res://artifacts/supply-chain/normal-play-report.json") DirAccess.make_dir_recursive_absolute(evidence_path.get_base_dir()) var file := FileAccess.open(evidence_path, FileAccess.WRITE) if file != null: file.store_string(JSON.stringify(evidence, "\t")) print("NORMAL_SUPPLY_REPORT ", JSON.stringify({"cash": evidence["cash"], "meal_sales": evidence["meal_sales"], "raw_deliveries": evidence["raw_deliveries"], "meal_deliveries": evidence["meal_deliveries"]})) _finish() func _advance(minutes: int) -> void: for index: int in maxi(minutes, 0): var before: int = _clock.absolute_minute _clock.advance_minutes(1) if _clock.absolute_minute != before + 1: _expect(false, "clock must progress: %s" % _runtime.get_operations_report()) return var porter: StaffRuntime = _runtime.staff_service.get_staff(&"staff_gina") if porter != null: _saw_movement = _saw_movement or porter.current_cell != _last_porter_cell _last_porter_cell = porter.current_cell if not porter.carried_inventory_id.is_empty(): _saw_loaded = true if not _save_checked: _check_loaded_save() if _ids.has(&"kitchen"): _saw_batch = _saw_batch or not _runtime.kitchen_production.get_report(_ids[&"kitchen"]).get(&"active_batch", {}).is_empty() if index % 120 == 119: print("SUPPLY_PROGRESS ", _clock.formatted_time(), " meals=", _sales(&"cafeteria")) await process_frame func _check_loaded_save() -> void: _save_checked = true var saved: Dictionary = _runtime.save_to_path(_save_path) _expect(bool(saved.get(&"ok", false)), "mid-delivery save writes to an isolated slot") var before: Dictionary = _runtime.create_snapshot() var loaded: Dictionary = _runtime.load_from_path(_save_path) _expect(bool(loaded.get(&"ok", false)), "mid-delivery disk restore: %s" % loaded) var normalized: Dictionary = _runtime.create_snapshot() _clock.advance_minutes(5) var expected: Dictionary = _runtime.create_snapshot() _expect(_runtime.restore_snapshot(normalized), "mid-delivery checkpoint rewinds") _clock.advance_minutes(5) _expect(_runtime.create_snapshot() == expected, "mid-delivery replay preserves exact inventory, tasks, positions, and ledger") _expect(_runtime.restore_snapshot(before), "resume the observed minute after replay verification") func _completed_deliveries(item: StringName) -> int: var count := 0 for request: LogisticsRequest in _runtime.logistics_requests.values(): if String(request.request_id).begins_with("supply.") and request.state == LogisticsRequest.State.COMPLETED and request.item_requirements.has(item): count += 1 return count func _sales(definition_id: StringName) -> int: var total := 0 for entry: LedgerEntry in _runtime.economy_service.get_entries(): if entry.category == &"SERVICE_SALE" and entry.source_entity_id == _ids.get(definition_id, &"missing"): total += entry.amount return total func _command(type: StringName, payload: Dictionary) -> bool: var result: CommandResult = _runtime.submit_command(type, payload) _expect(result.is_success(), "%s %s: %s" % [type, payload, result.message]) return result.is_success() func _capture() -> void: if OS.get_environment("THE_TREE_SUPPLY_CAPTURE") != "1": return await process_frame _main.call("_activate_management_mode", &"product") var panel := _main.find_child("SupplyManagementPanel", true, false) as SupplyManagementPanel for tab: StringName in [&"purchase", &"kitchen", &"delivery"]: panel.select_tab(tab) await process_frame await RenderingServer.frame_post_draw var path := ProjectSettings.globalize_path("res://artifacts/supply-chain/%s.png" % tab) DirAccess.make_dir_recursive_absolute(path.get_base_dir()) root.get_texture().get_image().save_png(path) func _expect(condition: bool, message: String) -> void: _checks += 1 if not condition: _failures += 1 push_error("FAIL: " + message) func _finish() -> void: SaveService.delete_save(_save_path) print("Normal supply chain: %d checks, %d failures" % [_checks, _failures]) quit(1 if _failures > 0 else 0) func _start_preview() -> void: if _failures > 0: _finish() return SaveService.delete_save(_save_path) root.title = "The Tree · 仕入れと厨房の試遊(保存なし)" for node: Node in _main.find_children("*", "Button", true, false): var button := node as Button for connection: Dictionary in button.pressed.get_connections(): var callback: Callable = connection[&"callable"] if callback.get_method() in [&"_save_manual", &"_load_manual"]: button.disabled = true button.tooltip_text = "この試遊は保存しません。" _main.call("_activate_management_mode", &"product") var panel := _main.find_child("SupplyManagementPanel", true, false) as SupplyManagementPanel panel.select_tab(&"kitchen") _main.call("_set_status", "通常予算から育てた宿の試遊です。Spaceで再開し、商品タブで仕入れ・調理・配送を確認できます(保存なし)。", true, true) _clock.set_paused(true) print("SUPPLY_PREVIEW_READY checks=%d failures=0" % _checks)