extends SceneTree const FixtureCleanup := preload("res://tests/helpers/ui_audio_fixture_cleanup.gd") # Viewport input routing, including the embedded modal; not OS input automation. var checks := 0 var failures := 0 func _initialize() -> void: call_deferred("run") func expect(ok: bool, message: String) -> void: checks += 1 if not ok: failures += 1 push_error(message) func click(control: Control) -> void: var viewport := control.get_viewport() var point := control.get_global_rect().get_center() if viewport is Window and viewport != root and viewport.is_embedded(): point += Vector2(viewport.position) viewport = root var motion := InputEventMouseMotion.new() motion.position = point motion.global_position = point viewport.push_input(motion, true) for pressed: bool in [true, false]: var event := InputEventMouseButton.new() event.position = point event.global_position = point event.button_index = MOUSE_BUTTON_LEFT event.pressed = pressed event.button_mask = MOUSE_BUTTON_MASK_LEFT if pressed else 0 viewport.push_input(event, true) await process_frame func key(code: Key, window: Window) -> void: for pressed: bool in [true, false]: var event := InputEventKey.new() event.keycode = code event.physical_keycode = code event.window_id = window.get_window_id() event.pressed = pressed Input.parse_input_event(event) await process_frame func run() -> void: var runtime: Variant = root.get_node("SimulationRuntime") var clock: Variant = root.get_node("GameClock") var state: Variant = root.get_node("GameState") clock.reset(1,6,0); state.reset(); runtime.initialize(clock,state,42511); clock.set_paused(true) root.size = Vector2i(1280,720) var main: Control = load("res://scenes/main/main.tscn").instantiate() var path := "user://tests/rank_pointer_%d" % Time.get_ticks_usec() main.set("manual_save_path",path) root.add_child(main) runtime.configure_save_policy({&"enabled":false,&"daily_checkpoint_enabled":false}) await process_frame; await process_frame var button: Button = main.get("rank_button") var dialog: ConfirmationDialog = main.get("rank_dialog") print("RANK_RECT ",button.get_global_rect()," root ",root.size) await click(button) print("HOVER ",root.gui_get_hovered_control()) expect(dialog.visible,"pointer opens rank conditions") await click(dialog.get_ok_button()) expect(dialog.visible and runtime.progression_service.current_rank == 1,"disabled promotion cannot activate by pointer") await key(KEY_ESCAPE,dialog) expect(not dialog.visible,"Escape closes rank modal") # Controlled fixture isolates interaction from natural campaign progression. expect(runtime.submit_command(&"update_progression_metrics",{&"metrics":{&"reputation":25}}).is_success(),"prepare reputation boundary") expect(runtime.submit_command(&"change_resources",{&"deltas":{&"gold":5000}}).is_success(),"prepare money boundary") await click(button) await click(dialog.get_cancel_button()) expect(not dialog.visible and runtime.progression_service.current_rank == 1,"cancel does not promote eligible game") await click(button) await click(dialog.get_ok_button()) expect(not dialog.visible and runtime.progression_service.current_rank == 2,"pointer confirmation promotes and closes modal") expect(button.text == "世界樹ランク 2","promotion updates visible HUD") for node: Node in main.find_children("*","Button",true,false): if node.text == "保存": await click(node) break expect(SaveService.load_snapshot(path)[&"ok"],"pointer saves promoted game") clock.advance_one_minute() var menu: MenuButton = main.find_child("SaveLoadMenu",true,false) await choose_rank_manual_load(menu, main, runtime, clock, path) expect(clock.absolute_minute == 360 and runtime.progression_service.current_rank == 2,"load menu keyboard restores saved promoted game") await click(button) expect(dialog.visible and "ランク3への条件" in dialog.dialog_text,"restored rank opens next requirements") await key(KEY_ESCAPE,dialog) if OS.get_environment("THE_TREE_RANK_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/rank-pointer.png")) == OK,"capture promoted HUD") SaveService.delete_save(path) 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 await process_frame print("Rank viewport input: %d checks, %d failures" % [checks,failures]) quit(failures) # Standard headless GUI fixture: no claim of native OS Window focus or human input. # IDs come from the actual Main menu; do not call/emit a load handler. func choose_rank_manual_load(menu: MenuButton, main: Control, runtime: Variant, clock: Variant, path: String) -> void: var records: Array[Dictionary] = [] var activated: Array[int] = [] var completions: Array[Dictionary] = [] var before: Dictionary = runtime.create_snapshot() # Public inspect reads only the named formal; the original load_snapshot assertion stays unchanged. var inspected: Dictionary = SaveService.inspect_snapshot(path) expect(bool(inspected.get(&"ok", false)), "rank menu evidence reads the actual saved formal snapshot") expect(var_to_bytes(runtime.create_snapshot()) == var_to_bytes(before), "public formal inspection preserves every typed Runtime field") var saved_snapshot: Dictionary = inspected.get(&"snapshot", {}) if menu == null: expect(false, "actual rank manual Load menu exists") store_rank_menu_probe(main, runtime, records, activated, completions, inspected, saved_snapshot, before, runtime.create_snapshot()) return var load_popup: PopupMenu = menu.get_popup() var on_id := func(id: int) -> void: activated.append(id) record_rank_menu_probe(records, menu, load_popup, clock, "passive_id_pressed", {"id": id}) var on_completion := func(result: Dictionary) -> void: completions.append(result.duplicate(true)) record_rank_menu_probe(records, menu, load_popup, clock, "passive_save_operation_completed", {"result": result.duplicate(true)}) load_popup.id_pressed.connect(on_id) var completed_signal := Signal(runtime, &"save_operation_completed") completed_signal.connect(on_completion) var on_focus := func(id: int) -> void: record_rank_menu_probe(records, menu, load_popup, clock, "passive_id_focused", {"id": id}) load_popup.id_focused.connect(on_focus) # Read-only settling: never request or override Root/Menu/Popup focus. var stable_focus := 0 var focus_frames := 0 for frame in 120: focus_frames = frame + 1 stable_focus = stable_focus + 1 if root.has_focus() else 0 if stable_focus >= 2: break await process_frame record_rank_menu_probe(records, menu, load_popup, clock, "before_pointer", {"focus_frames": focus_frames, "stable_focus": stable_focus, "saved_formal_inspection": inspected.duplicate(true)}) expect(stable_focus >= 2, "native Root focus is already stable before rank Menu press; no focus override") if stable_focus >= 2: await click_rank_menu(menu, records, load_popup, clock) record_rank_menu_probe(records, menu, load_popup, clock, "after_full_pointer_release") # Preserve HOME; PopupMenu does not guarantee it selects its first item. await rank_menu_key(KEY_HOME, load_popup, records, menu, clock) record_rank_menu_probe(records, menu, load_popup, clock, "after_original_HOME") var inventory: Array[Dictionary] = [] var id0_count := 0 for index in load_popup.get_item_count(): var item: Dictionary = {"index": index, "id": load_popup.get_item_id(index), "text": load_popup.get_item_text(index), "disabled": load_popup.is_item_disabled(index), "separator": load_popup.is_item_separator(index), "submenu": load_popup.get_item_submenu(index)} inventory.append(item) if item["id"] == 0 and not item["disabled"] and not item["separator"] and item["submenu"] == "": id0_count += 1 var remaining: int = load_popup.get_item_count() var focused: int = load_popup.get_focused_item() while load_popup.visible and remaining > 0 and (focused < 0 or focused >= load_popup.get_item_count() or load_popup.get_item_id(focused) != 0): await rank_menu_key(KEY_DOWN, load_popup, records, menu, clock) remaining -= 1 focused = load_popup.get_focused_item() record_rank_menu_probe(records, menu, load_popup, clock, "after_navigation_DOWN", {"remaining": remaining}) var id0_focused: bool = load_popup.visible and id0_count == 1 and focused >= 0 and focused < load_popup.get_item_count() and load_popup.get_item_id(focused) == 0 and not load_popup.is_item_disabled(focused) and not load_popup.is_item_separator(focused) and load_popup.get_item_submenu(focused) == "" record_rank_menu_probe(records, menu, load_popup, clock, "before_original_ENTER", {"inventory": inventory, "id0_focused": id0_focused, "remaining": remaining, "activated": activated.duplicate()}) expect(id0_focused, "actual rank Popup focus reaches unique enabled manual-load ID0 before ENTER") if id0_focused: await rank_menu_key(KEY_ENTER, load_popup, records, menu, clock) record_rank_menu_probe(records, menu, load_popup, clock, "after_original_ENTER", {"activated": activated.duplicate(), "completions": completions.duplicate(true)}) expect(activated.size() == 1 and activated[0] == 0, "actual rank manual-load ID0 emits exactly once after full ENTER") expect(completions.size() == 1 and bool(completions[0].get(&"ok", false)) and StringName(completions[0].get(&"operation", &"")) == &"recovery" and String(completions[0].get(&"save_path", "")) == path, "actual rank manual-load recovery completes once for the saved path") if bool(inspected.get(&"ok", false)): var after: Dictionary = runtime.create_snapshot() for section: StringName in [&"facilities", &"economy", &"guests", &"staff", &"construction", &"progression"]: expect(var_to_bytes(after[&"systems"][&"runtime"][section]) == var_to_bytes(saved_snapshot[&"systems"][&"runtime"][section]), "rank actual load restores whole typed %s section" % section) load_popup.id_focused.disconnect(on_focus) load_popup.id_pressed.disconnect(on_id) completed_signal.disconnect(on_completion) store_rank_menu_probe(main, runtime, records, activated, completions, inspected, saved_snapshot, before, runtime.create_snapshot()) func click_rank_menu(menu: MenuButton, records: Array[Dictionary], popup: PopupMenu, clock: Variant) -> void: var logical_point: Vector2 = menu.get_global_rect().get_center() var physical_point: Vector2 = root.get_final_transform() * logical_point var logical_ok: bool = menu.is_visible_in_tree() and menu.get_global_rect().has_point(logical_point) and root.get_visible_rect().has_point(logical_point) var physical_ok: bool = Rect2(Vector2.ZERO, Vector2(root.size)).has_point(physical_point) record_rank_menu_probe(records, menu, popup, clock, "actual_menu_geometry", {"logical_point": logical_point, "physical_point": physical_point, "control_rect": menu.get_global_rect(), "logical_root_rect": root.get_visible_rect(), "physical_root_rect": Rect2(Vector2.ZERO, Vector2(root.size)), "root_final_transform": root.get_final_transform(), "logical_ok": logical_ok, "physical_ok": physical_ok}) expect(logical_ok and physical_ok, "actual rank menu pointer lies inside logical target and physical Root") if not logical_ok or not physical_ok: return var motion := InputEventMouseMotion.new() motion.position = physical_point; motion.global_position = physical_point; motion.window_id = root.get_window_id() Input.parse_input_event(motion); Input.flush_buffered_events() record_rank_menu_probe(records, menu, popup, clock, "after_motion_parse_flush") for pressed: bool in [true, false]: var event := InputEventMouseButton.new() event.position = physical_point; event.global_position = physical_point; event.window_id = root.get_window_id() event.button_index = MOUSE_BUTTON_LEFT; event.pressed = pressed event.button_mask = MOUSE_BUTTON_MASK_LEFT if pressed else 0 record_rank_menu_probe(records, menu, popup, clock, "before_button_parse_flush", {"pressed": pressed, "event_position": event.position, "event_window_id": event.window_id}) Input.parse_input_event(event); Input.flush_buffered_events() record_rank_menu_probe(records, menu, popup, clock, "after_button_parse_flush", {"pressed": pressed}) await process_frame record_rank_menu_probe(records, menu, popup, clock, "after_button_frame", {"pressed": pressed}) func record_rank_menu_probe(records: Array[Dictionary], menu: MenuButton, popup: PopupMenu, clock: Variant, label: String, extra: Dictionary = {}) -> void: var focused: int = popup.get_focused_item() var report: Dictionary = {"label": label, "pid": OS.get_process_id(), "ticks_usec": Time.get_ticks_usec(), "absolute_minute": clock.absolute_minute, "backend": DisplayServer.get_name(), "root_window_id": root.get_window_id(), "root_instance_id": root.get_instance_id(), "root_has_focus": root.has_focus(), "menu_instance_id": menu.get_instance_id(), "menu_path": String(menu.get_path()), "menu_disabled": menu.disabled, "menu_visible_in_tree": menu.is_visible_in_tree(), "popup_instance_id": popup.get_instance_id(), "popup_window_id": popup.get_window_id(), "popup_visible": popup.visible, "popup_item_count": popup.get_item_count(), "focused_index": focused, "focused_id": popup.get_item_id(focused) if focused >= 0 and focused < popup.get_item_count() else -1, "Input_mouse_button_mask": Input.get_mouse_button_mask(), "Input_left_pressed": Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT)} report.merge(extra) records.append(report) func store_rank_menu_probe(main: Control, runtime: Variant, records: Array[Dictionary], activated: Array[int], completions: Array[Dictionary], inspected: Dictionary, saved_snapshot: Dictionary, before: Dictionary, after: Dictionary) -> void: var prefix: String = ProjectSettings.globalize_path(String(main.get("manual_save_path")) + "_menu_probe") var requested: String = OS.get_environment("THE_TREE_RANK_MENU_PROOF_FILE") var manifest_path: String = prefix.path_join("menu-proof.json") if not requested.is_empty(): manifest_path = requested prefix = requested.get_base_dir() var error := DirAccess.make_dir_recursive_absolute(prefix) expect(error == OK, "fresh rank menu observation directory can be created") if error != OK: return var nonce: String = OS.get_environment("THE_TREE_RANK_MENU_PROOF_NONCE") if nonce.is_empty(): nonce = String(main.get("manual_save_path")).get_file() var raw_files: Dictionary = {"saved-snapshot.varbytes": saved_snapshot, "before-menu-runtime.varbytes": before, "after-menu-runtime.varbytes": after, "menu-observations.varbytes": {"pid": OS.get_process_id(), "nonce": nonce, "manual_save_path": String(main.get("manual_save_path")), "observations": records, "activated_ids": activated, "save_operation_completed": completions, "saved_formal_inspection": inspected, "latest_save_operation_result": runtime.get("latest_save_operation_result").duplicate(true), "Runtime_fields_added": 0, "normalization": false, "headless_viewport_scope": DisplayServer.get_name() == "headless", "human_OS_input": false}} var descriptors: Dictionary = {} for name: String in raw_files: var path: String = prefix.path_join(name) expect(not FileAccess.file_exists(path), "rank menu observation file is fresh: %s" % name) if FileAccess.file_exists(path): continue var file := FileAccess.open(path, FileAccess.WRITE) expect(file != null, "rank menu observation file can be opened: %s" % name) if file != null: var raw: PackedByteArray = var_to_bytes(raw_files[name]) file.store_buffer(raw); file.close() var digest := HashingContext.new() digest.start(HashingContext.HASH_SHA256); digest.update(raw) descriptors[name] = {"path": path, "bytes": raw.size(), "sha256": digest.finish().hex_encode()} var source_bytes: PackedByteArray = FileAccess.get_file_as_bytes("res://tests/ui/test_rank_pointer.gd") var source_digest := HashingContext.new() source_digest.start(HashingContext.HASH_SHA256); source_digest.update(source_bytes) var manifest: Dictionary = {"schema_version": 1, "pid": OS.get_process_id(), "nonce": nonce, "report_owner_source_sha256": source_digest.finish().hex_encode(), "raw_files": descriptors, "raw_file_count": descriptors.size(), "normalization": false, "Runtime_fields_added": 0, "headless_viewport_scope": DisplayServer.get_name() == "headless", "human_OS_input": false} expect(not FileAccess.file_exists(manifest_path), "rank menu observation manifest is fresh") if FileAccess.file_exists(manifest_path): return var report_file := FileAccess.open(manifest_path, FileAccess.WRITE) expect(report_file != null, "rank menu observation manifest can be opened") if report_file != null: var text: String = JSON.stringify(manifest) report_file.store_string(text); report_file.close() print("RANK_MENU_PROOF " + text) func rank_menu_key(code: Key, popup: PopupMenu, records: Array[Dictionary], menu: MenuButton, clock: Variant) -> void: for pressed: bool in [true, false]: var event := InputEventKey.new() event.keycode = code event.physical_keycode = code event.window_id = popup.get_window_id() event.pressed = pressed record_rank_menu_probe(records, menu, popup, clock, "before_key_parse_flush", {"pressed": pressed, "keycode": int(code), "event_window_id": event.window_id}) Input.parse_input_event(event); Input.flush_buffered_events() record_rank_menu_probe(records, menu, popup, clock, "after_key_parse_flush", {"pressed": pressed, "keycode": int(code)}) await process_frame record_rank_menu_probe(records, menu, popup, clock, "after_key_frame", {"pressed": pressed, "keycode": int(code)})