extends SceneTree const FixtureCleanup := preload("res://tests/helpers/ui_audio_fixture_cleanup.gd") var checks:=0 var failures:=0 var runtime: Variant var clock: Variant func expect(ok: bool,label: String)->void: checks+=1 if not ok:failures+=1;push_error(label) func _initialize()->void:call_deferred("run") func active_job(id: StringName)->StaffTask: for task: StaffTask in runtime.staff_service.get_tasks(): if task.task_type==&"facility_clean" and task.target_entity_id==id and not task.is_terminal():return task return null func cleaner_id() -> StringName: return &"staff_eri" func work_ticks() -> int: return 8 func hire_cleaner(main: Control) -> void: main.staff_candidate_buttons[cleaner_id()].pressed.emit() func run()->void: runtime=root.get_node("SimulationRuntime");clock=root.get_node("GameClock") var state: Variant=root.get_node("GameState") clock.reset(1,6,0);state.reset() expect(runtime.initialize(clock,state,42631)[&"valid"],"initialize normal cleaning catalog") clock.set_paused(true) var main: Control=load("res://scenes/main/main.tscn").instantiate();root.add_child(main) runtime.configure_save_policy({&"enabled":false,&"daily_checkpoint_enabled":false}) await process_frame;await process_frame expect(main.MVP_FACILITY_IDS.has(&"cleaning_room"),"normal palette contains cleaning room") for build: Array in [[&"cleaning_room",Vector3i(-6,0,1)],[&"reception",Vector3i(1,0,0)]]: main.call("_select_build_definition",build[0]);main.call("_on_map_cell_clicked",build[1]);clock.advance_minutes(runtime.definition_registry.get_facility(build[0]).build_minutes) var room: FacilityInstance var reception: FacilityInstance for facility: FacilityInstance in runtime.facility_service.get_instances(): if facility.definition_id==&"cleaning_room":room=facility if facility.definition_id==&"reception":reception=facility expect(room!=null and reception!=null,"normal UI builds both completed facilities") if room==null or reception==null: var early_room_cleanup: Dictionary = await FixtureCleanup.free_owner_and_wait(self, main) if not bool(early_room_cleanup.get(&"passed", false)): push_error("Cleaning ordinary owner cleanup failed: %s" % str(early_room_cleanup)) quit(97) return quit(failures);return var preview: WorldPreview=main.world_preview var room_id:=room.instance_id var id:=reception.instance_id expect(room.customer_capacity==0 and room.state==FacilityInstance.State.NO_STAFF,"cleaning room excludes customers and waits for cleaner") var panel: StaffManagementPanel=main.find_child("StaffManagementPanel",true,false) for assignment: Array in [[cleaner_id(),room_id],[&"staff_akane",id]]: if assignment[0]==cleaner_id(): hire_cleaner(main) else: main.staff_candidate_buttons[assignment[0]].pressed.emit() panel.refresh();panel.select_staff(assignment[0]) for i in panel.facility_option.item_count: if panel.facility_option.get_item_metadata(i)==assignment[1]:panel.facility_option.select(i);panel.facility_option.item_selected.emit(i) panel.apply_button.pressed.emit() clock.advance_minutes(30) expect(room.operation_state==FacilityInstance.OperationState.OPERATING and reception.operation_state==FacilityInstance.OperationState.OPERATING,"actual cleaner and receptionist commute to assigned workplaces") expect(runtime.restore_snapshot(runtime.create_snapshot()),"staffed cleaning room save/load") expect(preview._staff_work_animation(runtime.staff_service.get_staff(cleaner_id()))==&"idle","cleaner at staffed base does not mime cleaning") var count:=0 for batch in 4: var size:=8 if batch<3 else 1 for index in size: var guest:=StringName("cleaning_payer_%d"%count) expect(runtime.submit_command(&"register_guest_wallet",{&"guest_id":guest,&"gold":100}).is_success(),"fund controlled visit") expect(runtime.submit_command(&"create_service_request",{&"request_id":StringName("cleaning_visit_%d"%count),&"facility_id":id,&"guest_ids":[guest],&"service_type":&"check_in"}).is_success(),"request real reception service") count+=1 for minute in 25: clock.advance_one_minute() if int(runtime.facility_cleanliness.get_facility(id)["current"])==100-count:break expect(runtime.facility_cleanliness.get_facility(id)["current"]==75,"25 completed defined visits reach cleaning threshold") var task:=active_job(id) expect(task!=null,"threshold schedules an actual staff task") if task==null: var early_task_cleanup: Dictionary = await FixtureCleanup.free_owner_and_wait(self, main) if not bool(early_task_cleanup.get(&"passed", false)): push_error("Cleaning ordinary owner cleanup failed: %s" % str(early_task_cleanup)) quit(97) return quit(failures);return var task_id:=task.task_id var walking_seen:=false for minute in 80: clock.advance_one_minute();task=runtime.staff_service.get_task(task_id) if task.state==StaffTask.State.MOVING: walking_seen=true expect(preview._staff_work_animation(runtime.staff_service.get_staff(cleaner_id()))!=&"clean","moving cleaner does not mop before arrival") expect(runtime.facility_cleanliness.get_facility(id)["current"]==75,"walking does not clean") if task.state==StaffTask.State.WORKING:break expect(walking_seen and task.state==StaffTask.State.WORKING,"assigned cleaner physically reaches work site") var worker: StaffRuntime=runtime.staff_service.get_staff(cleaner_id()) expect(worker.current_cell==task.target_cell and task.progress_minutes==0,"arrival starts work with zero elapsed minutes") expect(runtime.facility_service.get_instance(room_id).operation_state==FacilityInstance.OperationState.OPERATING,"deployed cleaner continues staffing cleaning room") if work_ticks() < 8: expect(runtime.cleaning_bridge.status(id).contains("3倍速・残り3分"),"cleaning UI describes real remaining time at triple rate") var working: Dictionary=runtime.create_snapshot() expect(runtime.restore_snapshot(working),"cleaner away from room and working task restore together") expect(preview._staff_work_animation(runtime.staff_service.get_staff(cleaner_id()))==&"clean","restored on-site active task selects mop animation") expect(not runtime.submit_command(&"progress_staff_task",{&"task_id":task_id,&"minutes":8}).is_success(),"manual progress cannot bypass actual work time") # Save this command receipt too, so both replay paths have identical command history. working=runtime.create_snapshot() clock.advance_minutes(work_ticks()-1) expect(runtime.facility_cleanliness.get_facility(id)["current"]==75 and runtime.staff_service.get_task(task_id).progress_minutes==(work_ticks()-1)*FacilityCleaningBridge.work_units_per_minute(runtime.staff_service.get_staff(cleaner_id())),"work before final tick is insufficient") clock.advance_one_minute() expect(runtime.facility_cleanliness.get_facility(id)["current"]==100 and runtime.staff_service.get_task(task_id).state==StaffTask.State.COMPLETED,"final tick completes one25-point recovery") expect(preview._staff_work_animation(runtime.staff_service.get_staff(cleaner_id()))!=&"clean","completed job stops mop animation") var finished: Dictionary=runtime.create_snapshot() expect(runtime.restore_snapshot(working),"rewind actual cleaning work") clock.advance_minutes(work_ticks()) expect(runtime.create_snapshot()==finished,"cleaning replay matches all Runtime state") # Stop an in-progress job through normal business controls. No partial recovery. expect(runtime.restore_snapshot(working),"rewind for closed cleaning room") clock.advance_minutes(work_ticks()-1) expect(runtime.submit_command(&"set_facility_policy",{&"facility_id":room_id,&"patch":{&"business_open":false}}).is_success(),"close cleaning room through business policy") clock.advance_minutes(10) expect(runtime.facility_cleanliness.get_facility(id)["current"]<=75 and runtime.staff_service.get_task(task_id).state==StaffTask.State.CANCELLED,"closed source cancels partial work without recovery") expect(preview._staff_work_animation(runtime.staff_service.get_staff(cleaner_id()))!=&"clean","closed base cancels visible cleaning") expect(runtime.restore_snapshot(runtime.create_snapshot()),"cancelled work plus waiting replacement job restore") expect(runtime.restore_snapshot(working),"rewind for power interruption") clock.advance_minutes(work_ticks()-1) state.set_resource(&"mana",0);clock.advance_minutes(10) expect(runtime.facility_cleanliness.get_facility(id)["current"]<=75 and runtime.staff_service.get_task(task_id).state==StaffTask.State.CANCELLED,"power loss stops cleaning without recovery") expect(runtime.restore_snapshot(runtime.create_snapshot()),"unpowered cleaning checkpoint restores") expect(runtime.restore_snapshot(finished),"completed cleaning receipt restores") clock.advance_minutes(5) var cleaning_receipts:=0 for receipt: Dictionary in runtime.facility_cleanliness.create_snapshot()["receipts"]: if receipt["kind"]=="clean" and StringName(receipt["source_id"])==task_id:cleaning_receipts+=1 expect(cleaning_receipts==1 and active_job(id)==null,"completion is not repeated; later normal guest visits may soil again") main.navigation_buttons[&"business"].pressed.emit() var business: BusinessManagementPanel=main.find_child("BusinessManagementPanel",true,false);business.refresh() for i in business.facility_option.item_count: if business.facility_option.get_item_metadata(i)==id:business.facility_option.select(i);business.facility_option.item_selected.emit(i) expect(business.facility_status_label.text.contains("清潔度 %d / 100" % runtime.facility_cleanliness.get_facility(id)["current"]),"normal business UI shows actual cleanliness") business.select_facility(room_id) expect(business.business_open_button.text=="清掃業務を有効にする" and business.facility_status_label.text.contains("顧客利用なし") and not business.effect_label.text.contains("受付"),"cleaning room controls describe staff work rather than customer reception") if OS.get_environment("THE_TREE_CLEANING_CAPTURE")=="1": root.size=Vector2i(1280,720) await process_frame;await process_frame;await RenderingServer.frame_post_draw expect(root.get_texture().get_image().save_png(ProjectSettings.globalize_path("res://artifacts/cleanliness-runtime-2026-09-29/cleaning-gameplay.png"))==OK,"capture actual cleaning UI") var cleanup: Dictionary = await FixtureCleanup.free_owner_and_wait(self, main) if not bool(cleanup.get(&"passed", false)): push_error("Cleaning ordinary owner cleanup failed: %s" % str(cleanup)) quit(97) return print("Cleaning gameplay: %d checks, %d failures"%[checks,failures]);quit(failures)