extends SceneTree const SANDBOX_STAGE: PackedScene = preload("res://scenes/stage/sandbox_stage.tscn") const DIALOG_STYLER: Script = preload("res://scripts/shared/stage_ui/stage_dialog_styler.gd") var _checks: int = 0 var _failures: int = 0 var _stage: SandboxStage var _chooser: StageGroundFinishChooser func _initialize() -> void: call_deferred("_run") func _run() -> void: root.size = Vector2i(1280, 720) _stage = SANDBOX_STAGE.instantiate() as SandboxStage root.add_child(_stage) await _wait_frames(3) _open_through_palette_signal() await _wait_frames(3) _test_initial_gallery() await _save_capture("phase19_visual_ground_finish_chooser_1280x720.png") await _test_narrow_viewport_fit() await _save_capture("phase19_visual_ground_finish_chooser_470x417.png") _test_close_keeps_placement_unchanged() root.size = Vector2i(1280, 720) await _wait_frames(2) _open_through_palette_signal() await process_frame DIALOG_STYLER.dismiss_on_escape(_chooser) _test_escape_keeps_placement_unchanged() _open_through_palette_signal() await _wait_frames(2) _test_finish_selection_is_a_draft() await _save_capture("phase19_visual_ground_finish_chooser_dirt_selected.png") _test_draw_commits_selected_finish() if DisplayServer.get_name() != "headless": Input.warp_mouse(Vector2i(900, 420)) await _wait_frames(3) _test_ground_ghost_is_armed() await _save_capture("phase19_visual_ground_finish_placement.png") await _test_ground_click_commits_selected_finish() _stage.queue_free() await _wait_frames(3) if _failures == 0: print("RESULT: ALL PASSED (%d checks)" % _checks) quit(0) else: print("RESULT: %d FAILURE(S) out of %d checks" % [_failures, _checks]) quit(1) func _open_through_palette_signal() -> void: var buttons: Dictionary = _stage.get("_palette_buttons") as Dictionary var ground_button: AssetCard = buttons.get("ground") as AssetCard _expect(ground_button != null, "Ground palette card exists") if ground_button == null: return ground_button.set_pressed_no_signal(true) ground_button.activated.emit(ground_button.payload()) _chooser = _stage.get("_ground_finish_chooser") as StageGroundFinishChooser func _test_initial_gallery() -> void: _expect(_chooser != null, "Ground finish chooser is built") if _chooser == null: return _expect(DIALOG_STYLER.is_open(_chooser), "Ground card opens the modal chooser through the palette signal") _expect(_chooser.title == "Choose Ground", "Chooser title matches the approved gallery") _expect(_chooser.dialog_text == "Stickmen walk here", "Chooser explains the Ground role") _expect(_chooser.cancel_button_text == "Close", "Footer dismissal action is Close") _expect(_chooser.get_ok_button().text == "Draw Grass Ground", "Primary action names the selected finish") var buttons: Dictionary = _chooser.choice_buttons() _expect(buttons.size() == 7, "Gallery contains seven exclusive Ground finish choices") var grass_button: Button = buttons.get(&"finish_ground_grass") as Button _expect(grass_button != null and grass_button.button_pressed, "Current Grass finish is visibly selected") _expect(_chooser.selected_preview_finish() == &"finish_ground_grass", "Large sample previews the selected finish") var grid: GridContainer = _chooser.get("_choice_grid") as GridContainer var wide_row: HBoxContainer = _chooser.get("_layout_row") as HBoxContainer var preview_panel: Control = _chooser.get("_preview_panel") as Control _expect(bool(_chooser.get("_is_wide_layout")) and grid != null and grid.columns == 3, "1280×720 opens the side-by-side three-column gallery") _expect(grid != null and grid.get_parent() == wide_row and preview_panel != null and preview_panel.visible, "Wide gallery keeps choices beside the selected preview") var card: Control = _chooser.get_meta(DIALOG_STYLER.CARD_META) as Control var viewport_size: Vector2 = root.get_visible_rect().size var card_rect: Rect2 = card.get_global_rect() if card != null else Rect2() _expect(card != null and card_rect.size.x < viewport_size.x * 0.9, "Roomy chooser card leaves visible horizontal Stage margins") _expect(card != null and card_rect.size.y < viewport_size.y * 0.82, "Roomy chooser card stays comfortably below the Stage height") _expect(card != null and card_rect.position.x >= 20.0 and card_rect.end.x <= viewport_size.x - 20.0, "Roomy chooser remains inside the viewport width") _expect(card != null and card_rect.position.y >= 20.0 and card_rect.end.y <= viewport_size.y - 20.0, "Roomy chooser remains inside the viewport height") func _test_narrow_viewport_fit() -> void: if _chooser == null: return root.size = Vector2i(470, 417) await _wait_frames(2) _chooser.call("_apply_responsive_layout") DIALOG_STYLER.refit(_chooser) await _wait_frames(2) var card: Control = _chooser.get_meta(DIALOG_STYLER.CARD_META) as Control var viewport_size: Vector2 = root.get_visible_rect().size var card_rect: Rect2 = card.get_global_rect() if card != null else Rect2() var grid: GridContainer = _chooser.get("_choice_grid") as GridContainer var preview_panel: Control = _chooser.get("_preview_panel") as Control _expect(card != null and card_rect.position.x >= 12.0 and card_rect.end.x <= viewport_size.x - 12.0, "Narrow chooser clamps within horizontal safe margins") _expect(card != null and card_rect.position.y >= 12.0 and card_rect.end.y <= viewport_size.y - 12.0, "Narrow chooser clamps within vertical safe margins") _expect(card != null and card_rect.size.x < viewport_size.x and card_rect.size.y < viewport_size.y, "Narrow chooser remains a card instead of a full-scene sheet") _expect(bool(_chooser.get("_is_compact_layout")) and grid != null and grid.columns == 3, "470×417 uses the compact three-column gallery") _expect(preview_panel != null and not preview_panel.visible, "Compact gallery hides the redundant large preview") var body_scroll: ScrollContainer = _chooser.get_meta(DIALOG_STYLER.BODY_SCROLL_META) as ScrollContainer var body_rect: Rect2 = body_scroll.get_global_rect() if body_scroll != null else Rect2() var all_choices_visible: bool = grid != null and grid.get_child_count() == 7 and body_scroll != null if all_choices_visible: for child: Node in grid.get_children(): var choice: Control = child as Control var choice_rect: Rect2 = choice.get_global_rect() if not choice.visible or choice_rect.position.x < body_rect.position.x - 1.0 or choice_rect.end.x > body_rect.end.x + 1.0 or choice_rect.position.y < body_rect.position.y - 1.0 or choice_rect.end.y > body_rect.end.y + 1.0: all_choices_visible = false break _expect(all_choices_visible, "Compact gallery exposes all seven finish cards without scrolling") var actions: HBoxContainer = _chooser.get_meta(DIALOG_STYLER.ACTIONS_META) as HBoxContainer var close_button: Button = DIALOG_STYLER.visual_button(_chooser.get_cancel_button()) var draw_button: Button = DIALOG_STYLER.visual_button(_chooser.get_ok_button()) _expect(actions != null and close_button != null and draw_button != null, "Narrow chooser keeps both footer actions in its fixed footer") _expect(close_button != null and close_button.get_global_rect().intersects(card_rect), "Narrow chooser keeps Close reachable inside the card") _expect(draw_button != null and draw_button.get_global_rect().intersects(card_rect), "Narrow chooser keeps Draw Ground reachable inside the card") func _test_close_keeps_placement_unchanged() -> void: if _chooser == null: return var close_button: Button = DIALOG_STYLER.visual_button(_chooser.get_cancel_button()) if close_button != null: close_button.pressed.emit() var placement_id: String = String(_stage.get("_placement_id")) var new_finish: StringName = StringName(_stage.get("_new_ground_finish")) var ground_button: Button = (_stage.get("_palette_buttons") as Dictionary).get("ground") as Button _expect(not DIALOG_STYLER.is_open(_chooser), "Close dismisses the chooser") _expect(placement_id.is_empty(), "Close does not arm Ground placement") _expect(new_finish == &"finish_ground_grass", "Close discards the unconfirmed finish choice") _expect(ground_button != null and not ground_button.button_pressed, "Close restores the Ground card to its inactive state") func _test_escape_keeps_placement_unchanged() -> void: if _chooser == null: return var placement_id: String = String(_stage.get("_placement_id")) var ground_button: Button = (_stage.get("_palette_buttons") as Dictionary).get("ground") as Button _expect(not DIALOG_STYLER.is_open(_chooser), "Escape dismisses the chooser") _expect(placement_id.is_empty(), "Escape does not arm Ground placement") _expect(ground_button != null and not ground_button.button_pressed, "Escape restores the Ground card to its inactive state") func _test_finish_selection_is_a_draft() -> void: if _chooser == null: return var dirt_button: Button = _chooser.choice_buttons().get(&"finish_ground_dirt") as Button if dirt_button != null: dirt_button.set_pressed_no_signal(true) dirt_button.pressed.emit() _expect(_chooser.selected_finish() == &"finish_ground_dirt", "Choosing Dirt updates the draft finish") _expect(_chooser.selected_preview_finish() == &"finish_ground_dirt", "Choosing Dirt updates the large preview") _expect(_chooser.get_ok_button().text == "Draw Dirt Ground", "Choosing Dirt updates the primary action") _expect(StringName(_stage.get("_new_ground_finish")) == &"finish_ground_grass", "Draft selection does not mutate the active Ground finish") _expect(String(_stage.get("_placement_id")).is_empty(), "Draft selection does not start placement") func _test_draw_commits_selected_finish() -> void: if _chooser == null: return var draw_button: Button = DIALOG_STYLER.visual_button(_chooser.get_ok_button()) if draw_button != null: draw_button.pressed.emit() var painter: StageTerrainPainter = _stage.get("_terrain_painter") as StageTerrainPainter _expect(not DIALOG_STYLER.is_open(_chooser), "Draw Ground closes the chooser") _expect(StringName(_stage.get("_new_ground_finish")) == &"finish_ground_dirt", "Draw Ground commits the selected finish") _expect(String(_stage.get("_placement_id")) == "ground", "Draw Ground enters the existing Ground placement tool") _expect(painter != null and StringName(painter.get("_ground_finish")) == &"finish_ground_dirt", "Ground painter receives the selected finish") func _test_ground_ghost_is_armed() -> void: var ghost: Node2D = _stage.get("_ghost") as Node2D _expect(ghost != null and is_instance_valid(ghost), "Draw Ground shows the cursor-following Ground ghost") func _test_ground_click_commits_selected_finish() -> void: var stage_position := Vector2(900.0, 420.0) var press := InputEventMouseButton.new() press.position = stage_position press.global_position = stage_position press.button_index = MOUSE_BUTTON_LEFT press.pressed = true _stage._unhandled_input(press) var release := InputEventMouseButton.new() release.position = stage_position release.global_position = stage_position release.button_index = MOUSE_BUTTON_LEFT release.pressed = false _stage._unhandled_input(release) await _wait_frames(3) var placed_dirt_ground: bool = false var world: Node2D = _stage.get_node("World") as Node2D if world != null: for child: Node in world.get_children(): if child is TerrainBlock and (child as TerrainBlock).spawn_id == "ground" and (child as TerrainBlock).finish_id == &"finish_ground_dirt": placed_dirt_ground = true break _expect(placed_dirt_ground, "Production Ground input places the selected Dirt finish") func _wait_frames(count: int) -> void: for _frame_index: int in count: await process_frame func _save_capture(file_name: String) -> void: if DisplayServer.get_name() == "headless": return await RenderingServer.frame_post_draw var capture: Image = root.get_texture().get_image() var path: String = ProjectSettings.globalize_path("res://tests/" + file_name) var error: Error = capture.save_png(path) _expect(error == OK, "render capture saved for visual inspection: %s" % path) print("CAPTURE: %s size=%s result=%s" % [path, str(capture.get_size()), str(error)]) func _expect(condition: bool, description: String) -> void: _checks += 1 if condition: print("PASS: %s" % description) else: _failures += 1 push_error("FAIL: %s" % description)