extends SceneTree const FixtureCleanup := preload("res://tests/helpers/ui_audio_fixture_cleanup.gd") ## Real Runtime commands through the staff panel controls, including queued ## assignment, draft preservation, a rejected role, and pending Save restoration. var _checks := 0 var _failures := 0 var _main: Control var _panel: StaffManagementPanel var _runtime: Variant var _clock: Variant var _receptions: Array[StringName] = [] var _kitchen: StringName = &"" var _save_path := "" func _test_staff_layer_skin() -> void: _main.call("_set_layer", 0) var front_color := _panel.staff_detail_label.get_theme_color("font_color") _panel.select_staff(&"staff_akane") var before: Dictionary = _runtime.staff_service.get_work_assignment(&"staff_akane") _main.call("_set_layer", 1) _panel.refresh() _expect(_panel.staff_detail_label.get_theme_color("font_color") == Color("d8eeea") and _panel.summary_label.get_theme_color("font_color") == Color("d8eeea"), "staff text stays readable in BACK after refresh") _panel.call("_set_result", "配属できません", false) _expect(_panel.result_label.get_theme_color("font_color") == Color("ffb498"), "BACK rejection retains a readable warning colour") _main.call("_set_layer", 0) _expect(_panel.staff_detail_label.get_theme_color("font_color") == front_color and _panel.result_label.get_theme_color("font_color") == Color("a7402e"), "FRONT restores staff text and rejection colours") _main.call("_set_layer", 1) _panel.call("_set_result", "配属を確認しました", true) _expect(_panel.result_label.get_theme_color("font_color") == Color("b5e7ba"), "BACK success retains distinct readable colour") _expect(_runtime.staff_service.get_work_assignment(&"staff_akane") == before, "skin changes do not submit an assignment") if OS.get_environment("THE_TREE_STAFF_SKIN_CAPTURE") == "1": await process_frame;await RenderingServer.frame_post_draw _expect(root.get_texture().get_image().save_png(ProjectSettings.globalize_path("res://artifacts/facility-dimensions-2026-09-29/staff-skin-back.png")) == OK, "capture staff BACK skin") _main.call("_set_layer", 0) _panel.call("_set_result", "", true) 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, 824242) _clock.set_paused(false) root.size = Vector2i(1280, 720) _main = load("res://scenes/main/main.tscn").instantiate() as Control root.add_child(_main) _runtime.configure_save_policy({&"enabled": false, &"daily_checkpoint_enabled": false}) _expect(_clock.paused, "a truly new opening pauses before the first construction") await process_frame await process_frame _panel = _main.find_child("StaffManagementPanel", true, false) as StaffManagementPanel _expect(_panel != null, "normal UI includes a separate staff management panel") if _panel == null: _finish() return for request: Array in [ [&"reception", Vector3i(-8, 0, 0)], [&"reception", Vector3i(2, 0, 0)], [&"kitchen", Vector3i(2, 0, 1)], ]: _main.call("_select_build_definition", request[0]) _main.call("_on_map_cell_clicked", request[1]) _clock.advance_minutes(180) for facility: FacilityInstance in _runtime.facility_service.get_instances(): if facility.definition_id == &"reception": _receptions.append(facility.instance_id) elif facility.definition_id == &"kitchen": _kitchen = facility.instance_id _expect(_receptions.size() == 2 and not _kitchen.is_empty(), "three real facilities are ready for assignment tests") if _receptions.size() != 2 or _kitchen.is_empty(): _finish() return _main.call("_unhandled_key_input", _key(KEY_H)) _expect((_main.get("staff_candidate_panel") as Control).visible, "H opens the unchanged hiring candidates") for candidate: StringName in [&"staff_akane", &"staff_bardo"]: var hire := (_main.get("staff_candidate_buttons") as Dictionary).get(candidate) as Button hire.pressed.emit() (_main.get("staff_management_toggle_button") as Button).pressed.emit() _expect(_panel.visible and not (_main.get("staff_candidate_panel") as Control).visible, "hired-staff tab replaces the candidates without hiding the management panel") _expect(_panel.roster_buttons.size() == 2 and "未配属 2人" in _panel.summary_label.text, "roster shows both real employees and unassigned count") await _test_staff_layer_skin() await _test_drag_assignment() await _test_drag_rejection_boundaries() await _test_drag_state_change() await _test_drag_across_load() await _test_drag_target_demolition() await _test_drag_defense_lock() await _test_drag_defense_lock(false) _panel.select_staff(&"staff_akane") _choose_facility(_receptions[0]) _panel.apply_button.pressed.emit() _expect(_assignment(&"staff_akane").get(&"facility_id") == _receptions[0], "paused Apply assigns the receptionist to the chosen completed facility") _expect(_runtime.staff_service.get_staff(&"staff_akane").state == StaffRuntime.State.COMMUTING and _runtime.facility_service.get_instance(_receptions[0]).active_staff_ids.is_empty(), "paused Apply visibly starts a commute and does not invent facility attendance") _expect("出勤中" in _panel.staff_detail_label.text, "staff panel distinguishes physical commuting from active duty") _expect("変更しました" in _panel.result_label.text and "毎日" in _panel.staff_detail_label.text, "assignment success and recurring schedule are visible in Japanese") _test_rejected_assignment() _test_queued_assignment_preserves_other_draft() _test_save_pending_assignment() _test_guide_respects_current_assignments() await _test_navigation_and_layout() await _test_restored_opening_does_not_pause() _finish() func _test_rejected_assignment() -> void: var before: Dictionary = _assignment(&"staff_akane").duplicate(true) _choose_facility(_kitchen) _panel.apply_button.pressed.emit() _expect(_assignment(&"staff_akane") == before, "wrong-role assignment never mutates the existing reception schedule") _expect("役割" in _panel.result_label.text and "役割" in (_main.get("build_status_label") as Label).text, "rejected assignment shows its actionable Japanese reason in both panel and status") _expect(_chosen_facility() == _kitchen, "a rejected assignment preserves the attempted facility for correction") func _test_queued_assignment_preserves_other_draft() -> void: _choose_facility(_receptions[1]) _clock.set_paused(false) _panel.apply_button.pressed.emit() var queued: Array = _runtime.command_queue.create_snapshot().get("pending", []) _expect(queued.size() == 1 and _panel.apply_button.disabled, "running-clock Apply queues one assignment and disables repeated submission") _panel.submit_assignment() _expect((_runtime.command_queue.create_snapshot().get("pending", []) as Array).size() == 1, "repeated submit cannot create a second assignment for the same employee") _panel.select_staff(&"staff_bardo") _choose_facility(_receptions[0]) _panel.shift_option.select(1) _panel.shift_option.item_selected.emit(1) _clock.advance_minutes(1) _clock.set_paused(true) _expect(_assignment(&"staff_akane").get(&"facility_id") == _receptions[1], "queued reassignment commits on the command tick") _expect(_panel.selected_staff_id == &"staff_bardo" and _chosen_facility() == _receptions[0] and _panel.shift_option.selected == 1, "another employee's pending completion preserves the active facility and night-shift draft") _panel.apply_button.pressed.emit() _expect(_assignment(&"staff_bardo").get(&"template_id") == &"NIGHT", "night selector creates an actual recurring night schedule") _expect("18:00" in _panel.result_label.text and "反映" in _panel.staff_detail_label.text, "future night shift clearly reports when it begins") _panel.select_staff(&"staff_akane") _choose_facility(&"") _panel.apply_button.pressed.emit() _expect(StringName(_assignment(&"staff_akane").get(&"facility_id", &"missing")).is_empty(), "unassigned option removes the facility duty through Runtime") func _test_save_pending_assignment() -> void: _choose_facility(_receptions[0]) _clock.set_paused(false) _panel.apply_button.pressed.emit() _save_path = "user://tests/staff-management-%d" % OS.get_process_id() var saved: Dictionary = _runtime.save_to_path(_save_path) _expect(bool(saved.get(&"ok", false)), "pending UI assignment saves to an isolated test slot") _clock.advance_minutes(1) var restored: Dictionary = _runtime.load_from_path(_save_path) _expect(bool(restored.get(&"ok", false)), "pending UI assignment can be restored") _panel.refresh() _expect(_panel.apply_button.disabled and not (_panel.get("_pending_commands") as Dictionary).is_empty(), "restored command metadata restores disabled pending controls") var sequence_before := int(_panel.get("_sequence")) _clock.advance_minutes(1) _clock.set_paused(true) _panel.refresh() _expect(_assignment(&"staff_akane").get(&"facility_id") == _receptions[0] and (_panel.get("_pending_commands") as Dictionary).is_empty(), "restored assignment completes once and releases its UI pending state") _choose_facility(_receptions[1]) _panel.apply_button.pressed.emit() _expect(int(_panel.get("_sequence")) > sequence_before and _assignment(&"staff_akane").get(&"facility_id") == _receptions[1], "post-load Apply uses a fresh command key and changes the real assignment") func _test_guide_respects_current_assignments() -> void: # Remove both saved plans so an unrelated receptionist cannot satisfy the # guide while this employee's assignment is being changed. for staff_id: StringName in [&"staff_akane", &"staff_bardo"]: _panel.select_staff(staff_id) _choose_facility(&"") _panel.apply_button.pressed.emit() _build_guide_part(&"standard_room", Vector3i(-8, 2, 0)) _build_guide_part(&"standard_room", Vector3i(-6, 2, 0)) _build_guide_part(&"guest_stairs", Vector3i(-4, 1, 0)) _clock.advance_minutes(60) var incomplete_guide := _main.find_child("InnOpeningGuide", true, false) as InnOpeningGuide _expect(incomplete_guide.get_progress().get(&"stage") == &"access", "incomplete stairs cannot advance guidance to staffing") _build_guide_part(&"guest_stairs", Vector3i(-4, 2, 0)) _clock.advance_minutes(10) for x: int in range(-8, -3): _build_guide_part(&"guest_stairs" if x == -4 else &"guest_corridor", Vector3i(x, 3, 0)) _clock.advance_minutes(10) var accessible_room := false for facility: FacilityInstance in _runtime.facility_service.get_instances(): accessible_room = accessible_room or (facility.definition_id == &"standard_room" and facility.access_available) var guide := _main.find_child("InnOpeningGuide", true, false) as InnOpeningGuide _expect(accessible_room and guide.get_progress().get(&"stage") == &"staff", "real accessible room still requires a reception assignment before first sale") _main.call("_activate_management_mode", &"staff") _panel.select_staff(&"staff_akane") _choose_facility(_receptions[0]) _panel.shift_option.select(0) _panel.shift_option.item_selected.emit(0) _panel.apply_button.pressed.emit() _expect(guide.get_progress().get(&"stage") == &"sale", "day assignment advances the accessible inn to waiting for its first sale") _choose_facility(&"") _panel.apply_button.pressed.emit() var historical_reception_shift := false for shift: ShiftInstance in _runtime.staff_service.get_shifts(): if shift.staff_id == &"staff_akane" and shift.assigned_facility_id == _receptions[0]: historical_reception_shift = historical_reception_shift or shift.cancelled or shift.end_at <= _clock.absolute_minute _expect(historical_reception_shift and guide.get_progress().get(&"stage") == &"staff", "unassigning reception returns guidance to staffing despite past or cancelled reception shifts") _choose_facility(_receptions[0]) _panel.shift_option.select(1) _panel.shift_option.item_selected.emit(1) _panel.apply_button.pressed.emit() var night_plan: Dictionary = _assignment(&"staff_akane") var reception: FacilityInstance = _runtime.facility_service.get_instance(_receptions[0]) _expect(night_plan.get(&"template_id") == &"NIGHT" and int(night_plan.get(&"effective_from", -1)) > _clock.absolute_minute and reception.active_staff_ids.is_empty(), "night assignment is genuinely future duty with nobody staffing reception during daytime") _expect(guide.get_progress().get(&"stage") == &"sale", "saved future night duty satisfies staffing guidance before its shift starts") func _build_guide_part(definition_id: StringName, origin: Vector3i) -> void: var before: int = _runtime.construction_service.get_jobs().size() _main.call("_select_build_definition", definition_id) _main.call("_on_map_cell_clicked", origin) _expect(_runtime.construction_service.get_jobs().size() == before + 1, "guide fixture builds %s through UI at %s" % [definition_id, origin]) func _test_navigation_and_layout() -> void: _main.call("_clear_active_tool") _expect(not _panel.visible, "Escape behavior closes the hired staff pane") _main.call("_activate_management_mode", &"ledger") var ledger := _main.find_child("OperatingLedgerPanel", true, false) as Control _expect(ledger.visible and not (_main.get("operations_controls") as Control).visible, "ledger navigation displays the actual ledger while hiding assignment controls") _main.call("_unhandled_key_input", _key(KEY_H)) _expect(not ledger.visible and (_main.get("staff_candidate_panel") as Control).visible, "H returns from the ledger to hiring candidates") _main.call("_activate_management_mode", &"staff") await process_frame await process_frame var guide := _main.find_child("InnOpeningGuide", true, false) as Control var side := _main.find_child("SidePanel", true, false) as Control _expect(not guide.get_global_rect().intersects(side.get_global_rect()), "1280x720 keeps opening guidance and staff inspector apart") _expect(_panel.get_global_rect().position.x >= side.get_global_rect().position.x and _panel.get_global_rect().end.x <= side.get_global_rect().end.x, "staff roster and assignment controls fit the compact sidebar width") if OS.get_environment("THE_TREE_STAFF_UI_CAPTURE") == "1": await RenderingServer.frame_post_draw root.get_texture().get_image().save_png("/tmp/the-tree-staff-management.png") func _test_restored_opening_does_not_pause() -> void: var _audio_cleanup_transition: Dictionary = await _cleanup_fixture_owner(_main, "restored-transition") if not bool(_audio_cleanup_transition.get(&"passed", false)): push_error("Ordinary fixture audio cleanup failed: %s" % str(_audio_cleanup_transition)) quit(96) return await process_frame _clock.reset(1, 6, 0) var state: Variant = root.get_node("GameState") state.reset() _runtime.initialize(_clock, state, 824242) _clock.set_paused(false) var snapshot: Dictionary = _runtime.create_snapshot() _expect(_runtime.restore_snapshot(snapshot), "restored opening fixture accepts its empty initial save") _main = load("res://scenes/main/main.tscn").instantiate() as Control root.add_child(_main) _runtime.configure_save_policy({&"enabled": false, &"daily_checkpoint_enabled": false}) _expect(not _clock.paused and int(_clock.absolute_minute) == 360, "mounting a restored empty opening preserves its saved running state and time") _clock.set_paused(true) func _assignment(staff_id: StringName) -> Dictionary: return _runtime.staff_service.get_work_assignment(staff_id) func _choose_facility(facility_id: StringName) -> void: for index: int in _panel.facility_option.item_count: if StringName(_panel.facility_option.get_item_metadata(index)) == facility_id: _panel.facility_option.select(index) _panel.facility_option.item_selected.emit(index) return _expect(false, "requested facility is available in the UI: %s" % facility_id) func _chosen_facility() -> StringName: return StringName(_panel.facility_option.get_item_metadata(_panel.facility_option.selected)) func _key(code: Key) -> InputEventKey: var event := InputEventKey.new() event.keycode = code event.pressed = true return event func _expect(condition: bool, message: String) -> void: _checks += 1 if not condition: _failures += 1 push_error(message) func _finish() -> void: if not _save_path.is_empty(): SaveService.delete_save(_save_path) if is_instance_valid(_main): var _audio_cleanup_final: Dictionary = await _cleanup_fixture_owner(_main, "finish") 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("Staff management UI: %d checks, %d failures" % [_checks, _failures]) quit(1 if _failures > 0 else 0) func _test_drag_assignment() -> void: var before: Dictionary = _runtime.create_snapshot() var preview: WorldPreview = _main.get("world_preview") _main.call("_set_layer", 0) _main.call("_clear_build_selection") await process_frame var target: Vector3i for room: Dictionary in _runtime.construction_service.get_facilities(): if room[&"instance_id"] == _receptions[0]: target = room[&"origin"] await _drag_staff_to(preview.global_position + preview.cell_to_canvas(target), target) _expect(_assignment(&"staff_akane").get(&"facility_id") == _receptions[0], "viewport drag assigns selected employee to completed reception") _expect("変更しました" in _panel.result_label.text, "drag reports authoritative assignment success") _expect(_runtime.restore_snapshot(before), "restore drag fixture before invalid target") _panel.refresh() var unchanged: Dictionary = _runtime.create_snapshot() await _drag_staff_to(preview.global_position + preview.cell_to_canvas(Vector3i(-4,4,0)), Vector3i(-4,4,0)) _expect(_runtime.create_snapshot() == unchanged, "empty-map drop preserves all simulation state") _expect("完成した配属先施設" in _panel.result_label.text, "invalid drop explains target requirement") _expect(not preview._can_drop_data(Vector2.ZERO,{&"kind":&"unrelated"}), "map refuses unrelated drag payload") _panel.refresh() var schedule_before := _assignment(&"staff_akane").duplicate(true) _main.call("_set_layer", 1) await process_frame await _drag_staff_to(preview.global_position + preview.cell_to_canvas(Vector3i(2,0,1)), Vector3i(2,0,1)) _expect(_assignment(&"staff_akane") == schedule_before, "wrong-role drag preserves staff schedule") _expect("役割" in _panel.result_label.text, "wrong-role drag displays authoritative refusal") _main.call("_set_layer", 0) _panel.select_staff(&"staff_akane") _panel.shift_option.select(1) _panel.shift_option.item_selected.emit(1) await process_frame _clock.set_paused(false) await _drag_staff_to(preview.global_position + preview.cell_to_canvas(target), target) _clock.set_paused(true) _expect(_panel.apply_button.disabled and (_runtime.command_queue.create_snapshot()["pending"] as Array).size() == 1, "night drag queues exactly one assignment") var drag_path := "user://tests/staff-drag-%d" % Time.get_ticks_usec() _expect(_runtime.save_to_path(drag_path).get(&"ok", false), "save pending night drag") _expect(_runtime.load_from_path(drag_path).get(&"ok", false), "restore pending night drag") _panel.refresh() _expect(_panel.apply_button.disabled, "loaded drag remains pending") _clock.advance_one_minute() _expect(_assignment(&"staff_akane").get(&"template_id") == &"NIGHT" and _assignment(&"staff_akane").get(&"facility_id") == _receptions[0], "restored drag applies selected night shift to exact facility") _expect((_panel.get("_pending_commands") as Dictionary).is_empty(), "restored drag clears pending feedback after execution") if OS.get_environment("THE_TREE_STAFF_DRAG_CAPTURE") == "1": await process_frame; await RenderingServer.frame_post_draw _expect(root.get_texture().get_image().save_png(ProjectSettings.globalize_path("res://artifacts/facility-dimensions-2026-09-29/staff-drag-night.png")) == OK, "capture restored night drag assignment") SaveService.delete_save(drag_path) _expect(_runtime.restore_snapshot(before), "restore drag boundary fixture for existing regression") _panel.refresh() func _drag_staff_to(target: Vector2, cell: Variant = null, before_release := Callable()) -> void: # Let restored roster layout finish before reading the card to press. await process_frame var source: Button = _panel.roster_buttons[&"staff_akane"] var start := source.get_global_rect().get_center() var motion := InputEventMouseMotion.new() motion.position=start; motion.global_position=start motion.window_id = root.get_window_id() _send_drag_input(motion) var down := InputEventMouseButton.new() down.position=start; down.global_position=start; down.button_index=MOUSE_BUTTON_LEFT down.pressed=true; down.button_mask=MOUSE_BUTTON_MASK_LEFT down.window_id = root.get_window_id() _send_drag_input(down) await process_frame for point: Vector2 in [start+Vector2(18,0),target]: var move := InputEventMouseMotion.new() if point == target and cell is Vector3i: var map: WorldPreview = _main.get("world_preview") point = map.global_position + map.cell_to_canvas(cell) target = point move.position=point; move.global_position=point; move.relative=point-start move.button_mask=MOUSE_BUTTON_MASK_LEFT move.window_id = root.get_window_id() _send_drag_input(move) await process_frame if before_release.is_valid(): _expect(root.gui_is_dragging(), "state changes only after the viewport has started dragging") before_release.call() var up := InputEventMouseButton.new() up.position=target; up.global_position=target; up.button_index=MOUSE_BUTTON_LEFT; up.pressed=false up.window_id = root.get_window_id() _send_drag_input(up) await process_frame func _send_drag_input(event: InputEventMouse) -> void: if DisplayServer.get_name() == "headless": event.position *= Vector2(root.size) / root.get_visible_rect().size event.global_position = event.position Input.parse_input_event(event) else: if event is InputEventMouseButton and not event.pressed: Input.warp_mouse(event.position * Vector2(root.size) / root.get_visible_rect().size) root.push_input(event, true) func _test_drag_rejection_boundaries() -> void: # Controlled boundary fixtures; the action under test uses viewport input. var before: Dictionary = _runtime.create_snapshot() var preview: WorldPreview = _main.get("world_preview") var target: Vector3i = _runtime.facility_service.get_instance(_receptions[0]).origin _main.call("_set_layer", 0) _panel.select_staff(&"staff_akane") _panel.shift_option.select(0) _panel.shift_option.item_selected.emit(0) var role: StringName = _runtime.staff_service.get_staff(&"staff_akane").role_id var maximum: int = _runtime.definition_registry.get_facility(&"reception").staff_max for i in maximum: var id := StringName("drag_capacity_%d" % i) _expect(_runtime.staff_service.create_staff(id, role).is_success() and _runtime.staff_service.set_work_assignment(id, _receptions[0], &"DAY", _clock.absolute_minute).is_success(), "fill actual reception shift slots for capacity boundary") var staff_before: Dictionary = _runtime.staff_service.create_snapshot() await _drag_staff_to(preview.global_position + preview.cell_to_canvas(target), target) _expect(_runtime.staff_service.create_snapshot() == staff_before and "人員上限" in _panel.result_label.text, "full-facility drag preserves all staff and explains capacity limit") await _capture_drag_rejection("capacity") _expect(_runtime.restore_snapshot(before), "restore after capacity drag fixture") _panel.refresh() _panel.select_staff(&"staff_akane") _runtime.staff_service.get_staff(&"staff_akane").current_cell = Vector3i(999,999,0) staff_before = _runtime.staff_service.create_snapshot() await _drag_staff_to(preview.global_position + preview.cell_to_canvas(target), target) _expect(_runtime.staff_service.create_snapshot() == staff_before and "通路と従業員ドア" in _panel.result_label.text, "unreachable drag preserves staff and explains route failure") await _capture_drag_rejection("route") _expect(_runtime.restore_snapshot(before), "restore after inaccessible route fixture") _panel.refresh() _panel.select_staff(&"staff_akane") # Inject only the lock boundary, not a complete narrative event. _runtime.event_service._actor_locks[&"staff_akane"] = &"drag_lock_fixture" _panel.refresh() staff_before = _runtime.staff_service.create_snapshot() var queue_before: Dictionary = _runtime.command_queue.create_snapshot() await _drag_staff_to(preview.global_position + preview.cell_to_canvas(target), target) _expect(_runtime.staff_service.create_snapshot() == staff_before and _runtime.command_queue.create_snapshot() == queue_before, "locked employee cannot start an assignment drag or submit a command") _expect("変更できません" in _panel.result_label.text, "locked drag explains why assignment is unavailable") await _capture_drag_rejection("locked") _runtime.event_service._actor_locks.erase(&"staff_akane") _expect(_runtime.restore_snapshot(before), "restore after locked employee fixture") _panel.refresh() func _capture_drag_rejection(kind: String) -> void: if OS.get_environment("THE_TREE_DRAG_REJECTION_CAPTURE") != "1": return await process_frame await RenderingServer.frame_post_draw var path := "res://artifacts/facility-dimensions-2026-09-29/staff-drag-rejection-%s.png" % kind _expect(root.get_texture().get_image().save_png(ProjectSettings.globalize_path(path)) == OK, "capture drag rejection %s" % kind) func _test_drag_state_change() -> void: var before: Dictionary = _runtime.create_snapshot() var preview: WorldPreview = _main.get("world_preview") var target: Vector3i = _runtime.facility_service.get_instance(_receptions[0]).origin var feedback: Array[CommandResult] = [] var listener := func(result: CommandResult) -> void: feedback.append(result) _runtime.command_completed.connect(listener) for reason: StringName in [&"EVENT_LOCKED", &"STAFF_BUSY"]: _panel.select_staff(&"staff_akane") var assignment := _assignment(&"staff_akane").duplicate(true) feedback.clear() await _drag_staff_to(preview.global_position + preview.cell_to_canvas(target), target, func() -> void: if reason == &"EVENT_LOCKED": _runtime.event_service._actor_locks[&"staff_akane"] = &"drag_race_fixture" else: _runtime.staff_service.get_staff(&"staff_akane").current_task_id = &"drag_race_task" ) _expect(not feedback.is_empty() and not feedback.back().is_success() and feedback.back().details.get(&"reason") == reason, "drop revalidates latest %s state through real command" % reason) _expect(_assignment(&"staff_akane") == assignment and _panel.apply_button.disabled, "rejected race preserves assignment and reflects unavailable state") var explanation := "イベント参加中" if reason == &"EVENT_LOCKED" else "作業中" _expect(explanation in _panel.staff_detail_label.text and explanation in _panel.apply_button.tooltip_text and explanation in _panel.result_label.text and (_panel.get("_pending_commands") as Dictionary).is_empty(), "race rejection shows precise reason and clears pending UI") _runtime.event_service._actor_locks.erase(&"staff_akane") _runtime.staff_service.get_staff(&"staff_akane").current_task_id = &"" _expect(_runtime.restore_snapshot(before), "restore state after drag race") _panel.refresh() _runtime.command_completed.disconnect(listener) func _test_drag_across_load() -> void: var before: Dictionary = _runtime.create_snapshot() var path := "user://tests/staff-drag-load-%d" % Time.get_ticks_usec() _expect(_runtime.save_to_path(path).get(&"ok", false), "save before live drag reload") var preview: WorldPreview = _main.get("world_preview") var target: Vector3i = _runtime.facility_service.get_instance(_receptions[0]).origin var loaded_staff: Array[Dictionary] = [] var loaded_queue: Array[Dictionary] = [] _panel.select_staff(&"staff_akane") await _drag_staff_to(preview.global_position + preview.cell_to_canvas(target), target, func() -> void: _expect(_runtime.load_from_path(path).get(&"ok", false), "load real save while pointer is dragging") loaded_staff.append(_runtime.staff_service.create_snapshot()) loaded_queue.append(_runtime.command_queue.create_snapshot()) ) _expect(_runtime.staff_service.create_snapshot() == loaded_staff[0], "pre-load drag cannot alter restored staff or assignment") _expect(_runtime.command_queue.create_snapshot() == loaded_queue[0], "pre-load drag cannot enqueue a command in restored world") _expect("読み込み" in _panel.result_label.text, "stale drag explains that load requires a new drag") if OS.get_environment("THE_TREE_DRAG_LOAD_CAPTURE") == "1": await process_frame await RenderingServer.frame_post_draw _expect(root.get_texture().get_image().save_png(ProjectSettings.globalize_path("res://artifacts/staff-drag-load-2026-09-29/rejected.png")) == OK, "capture load cancellation feedback") _panel.select_staff(&"staff_akane") await _drag_staff_to(preview.global_position + preview.cell_to_canvas(target), target) _expect(_assignment(&"staff_akane").get(&"facility_id") == _receptions[0], "fresh drag after load still assigns normally") SaveService.delete_save(path) _expect(_runtime.restore_snapshot(before), "restore fixture after live drag reload") _panel.refresh() _panel.select_staff(&"staff_akane") await _drag_staff_to(preview.global_position + preview.cell_to_canvas(target), target, func() -> void: _expect(not _runtime.restore_snapshot({}), "invalid snapshot is refused during drag") ) _expect(_assignment(&"staff_akane").get(&"facility_id") == _receptions[0], "failed restore preserves current world and live drag") _expect(_runtime.restore_snapshot(before), "restore fixture after refused load") _panel.refresh() func _test_drag_target_demolition() -> void: var before: Dictionary = _runtime.create_snapshot() var preview: WorldPreview = _main.get("world_preview") var target: Vector3i = _runtime.facility_service.get_instance(_receptions[0]).origin var staff_before: Dictionary = _runtime.staff_service.create_snapshot() _panel.select_staff(&"staff_akane") await _drag_staff_to(preview.global_position + preview.cell_to_canvas(target), target, func() -> void: var removed: CommandResult = _runtime.submit_command(&"demolish_facility", {&"instance_id":_receptions[0]}, &"player", &"", 30, -1, &"ui.drag.target.demolition") _expect(removed.is_success(), "normal demolition command succeeds while dragging toward unstaffed facility") ) _expect(not _runtime.facility_service.has_instance(_receptions[0]), "drop cannot resurrect demolished facility") _expect(_runtime.staff_service.create_snapshot() == staff_before, "demolition during drag preserves staff state and schedules") _expect("完成した配属先施設" in _panel.result_label.text or "見つかりません" in _panel.result_label.text, "removed drop target has actionable rejection feedback") _expect((_panel.get("_pending_commands") as Dictionary).is_empty(), "removed target leaves no stuck assignment UI") _expect(_runtime.restore_snapshot(before), "restore after live demolition boundary") _panel.refresh() func _test_drag_defense_lock(register_objective := true) -> void: var before: Dictionary = _runtime.create_snapshot() var preview: WorldPreview = _main.get("world_preview") var target: Vector3i = _runtime.facility_service.get_instance(_receptions[0]).origin var assignment := _assignment(&"staff_akane").duplicate(true) _panel.select_staff(&"staff_akane") await _drag_staff_to(preview.global_position + preview.cell_to_canvas(target), target, func() -> void: var created: CommandResult = _runtime.submit_command(&"create_defense_raid", { &"raid_id": &"ui.drag.defense", &"definition": { &"source_id": &"runtime_test", &"entry_points": [&"gate.north"], &"objective_ids": [_receptions[0]], &"round_minutes": 5, &"max_duration_minutes": 180, &"retreat_policy": &"HOLD", &"defender_retreat_threshold": 0, &"waves": [{&"wave_id": &"wave.one", &"threat_strength": 15, &"retreat_threshold": 0, &"max_rounds": 1, &"entry_points": [&"gate.north"]}], }}, &"defense_controller") _expect(created.is_success(), "create real defense raid during assignment drag") if register_objective: var asset: CommandResult = _runtime.submit_command(&"register_defense_asset", { &"raid_id": &"ui.drag.defense", &"asset_id": _receptions[0], &"kind": &"objective", &"max_integrity": 10000, &"defense": 40, &"options": {}}, &"defense_controller") _expect(asset.is_success(), "register real facility as defense objective") var registered: CommandResult = _runtime.submit_command(&"register_defense_combatant", { &"raid_id": &"ui.drag.defense", &"actor_id": &"staff_akane", &"side": &"DEFENDER", &"combat": 90, &"max_health": 100, &"options": {}}, &"defense_controller") _expect(registered.is_success(), "register dragged employee as defender") ) _expect(_assignment(&"staff_akane") == assignment and "防衛参加中" in _panel.result_label.text, "defense registration during drag rejects assignment with precise feedback") _expect(_panel.apply_button.disabled and "防衛参加中" in _panel.staff_detail_label.text and "防衛参加中" in _panel.apply_button.tooltip_text, "locked editor explains defense restriction before retry") var path := "user://tests/staff-defense-lock-%d" % Time.get_ticks_usec() _expect(_runtime.save_to_path(path).get(&"ok", false), "save registered defense lock") _expect(_runtime.load_from_path(path).get(&"ok", false), "restore registered defense lock") _panel.refresh() _panel.select_staff(&"staff_akane") var queue_before: Dictionary = _runtime.command_queue.create_snapshot() await _drag_staff_to(preview.global_position + preview.cell_to_canvas(target), target) _expect(_runtime.command_queue.create_snapshot() == queue_before and "防衛参加中" in _panel.result_label.text, "restored defender cannot begin new drag and sees the reason") var cancelled: CommandResult = _runtime.submit_command(&"cancel_defense_raid", {&"raid_id": &"ui.drag.defense"}, &"defense_controller") _expect(cancelled.is_success(), "cancel raid through normal command: %s %s" % [cancelled.message, cancelled.details]) _panel.refresh() _expect(not _panel.apply_button.disabled and not "防衛参加中" in _panel.staff_detail_label.text, "terminal raid releases editor lock and stale explanation") await _drag_staff_to(preview.global_position + preview.cell_to_canvas(target), target) _expect(_assignment(&"staff_akane").get(&"facility_id") == _receptions[0], "new drag after defense cancellation assigns normally") SaveService.delete_save(path) _expect(_runtime.restore_snapshot(before), "restore fixture after defense boundary") _panel.refresh() ## QA-only control proof routing for sequential ordinary Main owners. ## The old02cd/6be99 helpers and original Clock/Runtime values stay exact. func _cleanup_fixture_owner(owner: Node, phase: String) -> Dictionary: var original_control: String = OS.get_environment("THE_TREE_AUDIO_FIXTURE_CONTROL_PROOF") if original_control.is_empty() or not original_control.is_absolute_path(): return {&"passed": false, &"reason": "fresh absolute owned control proof required"} var phase_control: String = original_control.get_basename() + "-" + phase + ".json" if FileAccess.file_exists(phase_control): return {&"passed": false, &"reason": "ordinary owner control proof overwrite forbidden"} OS.set_environment("THE_TREE_AUDIO_FIXTURE_CONTROL_PROOF", phase_control) var report: Dictionary = await FixtureCleanup.free_owner_and_wait(self, owner) OS.set_environment("THE_TREE_AUDIO_FIXTURE_CONTROL_PROOF", original_control) return report