extends SceneTree # 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 click(menu) await key(KEY_HOME,menu.get_popup()) await key(KEY_ENTER,menu.get_popup()) 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) main.queue_free(); await process_frame print("Rank viewport input: %d checks, %d failures" % [checks,failures]) quit(failures)