extends SceneTree const FixtureCleanup := preload("res://tests/helpers/ui_audio_fixture_cleanup.gd") ## Normal UI commands, default funds, and natural arrivals only. No demo fixture. ## Optional THE_TREE_OPENING_CAPTURE=1 renders the new-game and close screens. var _checks := 0 var _failures := 0 var _runtime: Variant var _clock: Variant var _main: Control var _guide: InnOpeningGuide var _ledger: OperatingLedgerPanel func _initialize() -> void: call_deferred("_run") func _run() -> void: _clock = root.get_node("GameClock") _runtime = root.get_node("SimulationRuntime") var state: Variant = 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 root.add_child(_main) _runtime.configure_save_policy({&"enabled": false, &"daily_checkpoint_enabled": false}) await process_frame await process_frame _guide = _main.find_child("InnOpeningGuide", true, false) as InnOpeningGuide _ledger = _main.find_child("OperatingLedgerPanel", true, false) as OperatingLedgerPanel _expect(_guide != null and _ledger != null, "normal main scene mounts opening guide and real ledger") if _guide == null or _ledger == null: _finish() return _expect(_runtime.economy_service.get_balance(&"PLAYER_CASH") == 5000, "ordinary initial funds are unchanged") _expect(_guide.get_progress().get(&"stage") == &"reception", "new game starts with reception guidance") await _capture("opening") if not _build(&"reception", Vector3i(-8, 0, 0)): _finish() return _expect(_guide.get_progress().get(&"action") == &"resume", "guide explains construction time while paused") await _advance(120) _expect(_guide.get_progress().get(&"stage") == &"room", "completed reception advances room guidance") _build(&"standard_room", Vector3i(-8, 2, 0)) _build(&"standard_room", Vector3i(-6, 2, 0)) _build(&"guest_stairs", Vector3i(-4, 1, 0)) await _advance(60) var access_guide := _guide.get_progress() _expect(access_guide.get(&"stage") == &"access" and access_guide.get(&"action") == &"build_stairs" and "上下階は客用階段" in access_guide.get(&"detail", ""), "guide explains explicit stairs before the upper rooms become accessible") await _capture("stairs-guidance") _main.call("_on_opening_action", access_guide[&"action"]) _expect(_main.get("world_preview").build_definition.id == &"guest_stairs", "opening-guide action selects the real guest stairs palette entry") _build(&"guest_stairs", Vector3i(-4, 2, 0)) await _advance(10) for x: int in range(-8, -3): _build(&"guest_stairs" if x == -4 else &"guest_corridor", Vector3i(x, 3, 0)) await _advance(10) var reception_id: StringName = &"" for facility: FacilityInstance in _runtime.facility_service.get_instances(): if facility.definition_id == &"reception": reception_id = facility.instance_id _expect(not reception_id.is_empty(), "normal construction created the reception") var before_manager: Dictionary=_runtime.create_snapshot() _main.call("_hire_staff_candidate", &"heroine_anri") var manager_panel := _main.find_child("StaffManagementPanel", true, false) as StaffManagementPanel manager_panel.refresh();manager_panel.select_staff(&"heroine_anri") for index: int in manager_panel.facility_option.item_count: if StringName(manager_panel.facility_option.get_item_metadata(index))==reception_id: manager_panel.facility_option.select(index) manager_panel.facility_option.item_selected.emit(index) manager_panel.apply_button.pressed.emit() _expect(_runtime.staff_service.get_work_assignment(&"heroine_anri").get(&"facility_id")==reception_id, "normal manager placement established for opening guidance boundary") _guide.refresh() _expect(_guide.get_progress().get(&"stage")==&"staff", "manager-only reception keeps actual opening guide on staff recruitment") _expect(_runtime.get_floor_price_multiplier(0)==12000, "manager passive remains enabled while receptionist guidance is unmet") _expect(_runtime.restore_snapshot(before_manager), "restore ordinary opening scenario after manager-only boundary") _main.call("_hire_staff_candidate", &"staff_akane") var staff_members: Array = _runtime.staff_service.get_staff_members() _expect(staff_members.size() == 1, "normal hire button employs one receptionist") var staff_panel := _main.find_child("StaffManagementPanel", true, false) as StaffManagementPanel _expect(staff_panel != null, "staff assignment controls are part of the dashboard") if staff_panel == null or staff_members.is_empty(): _finish() return staff_panel.select_staff(staff_members[0].staff_id) var assigned := false for index: int in staff_panel.facility_option.item_count: if StringName(staff_panel.facility_option.get_item_metadata(index)) == reception_id: staff_panel.facility_option.select(index) staff_panel.facility_option.item_selected.emit(index) staff_panel.apply_button.pressed.emit() assigned = true break _expect(assigned, "reception can be selected through the assignment dropdown") await _advance(2) _expect(_runtime.staff_service.get_staff(staff_members[0].staff_id).assigned_facility_id == reception_id, "UI applies the receptionist assignment to the simulation") _expect(_guide.get_progress().get(&"stage") == &"sale", "guide recognizes accessible rooms and staffing") _main.call("_clear_active_tool") _main.call("_refresh_dashboard") await _capture("staffed") # The night is simulated naturally. No service request or guest is inserted. await _advance(1439 - _clock.absolute_minute) _expect(int(_ledger.get_report()[&"business_day"]) == 1, "23:59 remains part of the first calendar day") _expect(int(_ledger.get_report()[&"construction_cash_out"]) > 0, "building cash outflow remains visible before midnight") await _advance(1) _expect(int(_ledger.get_report()[&"business_day"]) == 2, "midnight rolls the cash-flow period") _expect(int(_ledger.get_report()[&"construction_cash_out"]) == 0, "prior-day construction is excluded from new-day cash flow") var summaries: Array = _runtime.operations_coordinator.get_day_summaries() _expect(summaries.size() == 1, "midnight creates one authoritative operating-day close") var full_operations: Dictionary = _runtime.get_operations_report() var compact_operations: Dictionary = _runtime.get_operations_dashboard_report() for key: StringName in [&"enabled", &"arrears_total", &"tick_blocked", &"runtime_failure_latched", &"personal_action_points", &"deferred_runtime_minute"]: _expect(compact_operations[key] == full_operations[key], "compact HUD matches live %s" % key) var compact_coordinator: Dictionary = compact_operations[&"coordinator"] var full_coordinator: Dictionary = full_operations[&"coordinator"] for key: StringName in [&"terminal_state", &"consecutive_insolvency_count", &"legacy_history_last_day"]: _expect(compact_coordinator[key] == full_coordinator[key], "compact HUD matches coordinator %s" % key) _expect(compact_coordinator[&"day_summaries"].back()[&"net_profit"] == summaries.back()[&"net_profit"], "compact HUD preserves latest operating profit") compact_coordinator[&"day_summaries"].back()[&"net_profit"] = -999999 _expect(_runtime.operations_coordinator.get_day_summaries() == summaries, "editing HUD projection cannot mutate authoritative day close") _expect(_ledger.is_visible_in_tree(), "daily settlement automatically presents the ledger report") var lodging_sales := 0 for entry: LedgerEntry in _runtime.economy_service.get_entries(): var facility: FacilityInstance = _runtime.facility_service.get_instance(entry.source_entity_id) if entry.category == &"SERVICE_SALE" and facility != null and facility.definition_id == &"standard_room": lodging_sales += entry.amount _expect(lodging_sales > 0, "naturally arriving guests complete paid lodging") if not summaries.is_empty(): var first: Dictionary = summaries[0] _expect(int(first[&"category_totals"][&"wages"]) > 0, "the close includes real employee wages") _expect(bool(first[&"balanced"]), "daily close reconciles the ledger") _expect(_ledger.get_report()[&"selected_close"] == first, "ledger panel shows the authoritative close without reclassification") _expect(int(_ledger.get_report()[&"business_day"]) == 2, "cash-flow period rolls at midnight") _expect(_guide.get_progress().get(&"stage") == &"complete", "first lodging and following close complete opening guidance") var digest: String = _runtime.latest_digest _guide.refresh() _ledger.refresh() _expect(_runtime.latest_digest == digest, "guide and ledger refresh never mutate simulation") _main.call("_activate_management_mode", &"ledger") await _capture("first-close") await _advance(1800 - _clock.absolute_minute) _expect(_runtime.operations_coordinator.get_day_summaries().size() == 1, "06:00 morning cannot duplicate the midnight close") # A dedicated temp slot avoids every normal player save path. var save_path := "user://tests/first_operating_day_%d/restore-check" % OS.get_process_id() var saved: Dictionary = _runtime.save_to_path(save_path, {&"display_name": "First operating day regression"}) _expect(bool(saved.get(&"ok", false)), "first operating day saves to a dedicated test slot: %s" % saved) var close_before: Dictionary = _ledger.get_report()[&"selected_close"] var restored: Dictionary = _runtime.load_from_path(save_path) _expect(bool(restored.get(&"ok", false)), "first operating day can be restored: %s" % restored.get(&"code", &"UNKNOWN")) # Coordinator snapshots intentionally normalize StringName keys and values # to String. Compare the JSON-shaped public report, preserving every value. _expect(JSON.stringify(_ledger.get_report()[&"selected_close"]) == JSON.stringify(close_before), "restored close preserves every financial value") _expect(_guide.get_progress().get(&"stage") == &"complete", "restored state preserves guide progress: %s" % _guide.get_progress()) # Continue the loaded second morning, then replay the same complete state. # This checks active work after load, not only a static financial report. var loaded_checkpoint: Dictionary = _runtime.create_snapshot() await _advance(120) var continued_digest := SimulationDigest.compute(_runtime.create_snapshot()) _expect(_clock.absolute_minute == 1920, "disk-loaded inn continues through the second morning") _expect(_runtime.restore_snapshot(loaded_checkpoint), "second-morning checkpoint restores before replay") await _advance(120) _expect(SimulationDigest.compute(_runtime.create_snapshot()) == continued_digest, "second-morning guests, staff, ledger and events replay exactly after load") _expect(_guide.get_progress().get(&"stage") == &"complete", "continued second morning preserves completed guidance") await _capture("second-morning") var evidence := {"lodging_sales": lodging_sales, "report": _ledger.get_report(), "staff_count": _runtime.staff_service.get_staff_members().size()} print("FIRST_OPERATING_DAY_REPORT ", JSON.stringify(evidence)) var evidence_dir := OS.get_environment("THE_TREE_FIRST_OPERATING_EVIDENCE_DIR") var evidence_path := ProjectSettings.globalize_path("res://artifacts/first-operating-day/operating-report.json" if evidence_dir.is_empty() else evidence_dir.path_join("operating-report.json")) DirAccess.make_dir_recursive_absolute(evidence_path.get_base_dir()) var evidence_file := FileAccess.open(evidence_path, FileAccess.WRITE) if evidence_file != null: evidence_file.store_string(JSON.stringify(evidence, "\t") + "\n") SaveService.delete_save(save_path) _test_new_report_selection() _finish() func _test_new_report_selection() -> void: # Projection-only fixture: test navigation between two closed reports without # fabricating transactions in the real inn or changing its saved history. var original: OperationsCoordinator = _runtime.operations_coordinator var reports := OperationsCoordinator.new() reports.advance_to(2880) _runtime.operations_coordinator = reports _ledger._selected_day = 1 _ledger.refresh() _expect(int(_ledger.get_report()[&"selected_close"][&"day"]) == 1, "player can inspect an earlier close") _main.set("_last_presented_close_minute", 1440) var projection := {&"systems": {&"runtime": {&"operations": {&"coordinator": reports.create_snapshot()}}}} _main.call("_on_snapshot_published", projection, "") _expect(int(_ledger.get_report()[&"selected_close"][&"day"]) == 2, "new close selects the latest report even after an earlier day was selected") _ledger._selected_day = 1 _main.call("_on_snapshot_published", projection, "") _expect(int(_ledger.get_report()[&"selected_close"][&"day"]) == 1, "ordinary refresh preserves the player's historical selection") _runtime.operations_coordinator = original func _build(definition_id: StringName, origin: Vector3i) -> bool: var before: int = _runtime.construction_service.get_jobs().size() _main.call("_select_build_definition", definition_id) _main.call("_on_map_cell_clicked", origin) var built: bool = _runtime.construction_service.get_jobs().size() == before + 1 _expect(built, "UI starts %s at %s" % [definition_id, origin]) return built 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, "normal operations must advance: %s" % _runtime.get_operations_report()) return if index % 120 == 119: print("OPENING_PROGRESS ", _clock.formatted_time()) await process_frame func _capture(suffix: String) -> void: if OS.get_environment("THE_TREE_OPENING_CAPTURE") != "1": return _main.call("_refresh_dashboard") for frame: int in 5: await process_frame await RenderingServer.frame_post_draw var image := root.get_texture().get_image() var path := ProjectSettings.globalize_path("res://artifacts/first-operating-day/%s.png" % suffix) DirAccess.make_dir_recursive_absolute(path.get_base_dir()) _expect(image != null and image.save_png(path) == OK, "captures %s UI" % suffix) func _expect(condition: bool, message: String) -> void: _checks += 1 if not condition: _failures += 1 push_error("FAIL: %s" % message) func _finish() -> void: var _audio_cleanup_final: Dictionary = await FixtureCleanup.free_owner_and_wait(self, _main) if not bool(_audio_cleanup_final.get(&"passed", false)): push_error("Ordinary fixture audio cleanup failed: %s" % str(_audio_cleanup_final)) quit(96) return print("First operating day: %d checks, %d failures" % [_checks, _failures]) quit(1 if _failures > 0 else 0)