Files
stickman/tests/phase19_sky_camera_visual_capture.gd
T
ryan 0b1dda2e2f Add Phase 19 Starter Scene Composition UI/UX Specification and Tests
- Introduced a new UI/UX specification document for the Phase 19 Starter Scene Composition, detailing user paths, composition rules for Town Street and Living Room, camera framing, and observable acceptance outcomes.
- Implemented a visual capture test for different sky states in the sandbox stage, ensuring proper rendering and visual consistency.
- Created a comprehensive test suite for the Phase 19 starter scene composition, validating scene loading, geometry, navigation, and interaction behaviors for both Town Street and Living Room.
- Added checks for visual alignment, object placements, and scene transitions to ensure adherence to the new UI/UX specifications.
2026-10-21 17:46:58 -04:00

132 lines
6.0 KiB
GDScript

extends SceneTree
const SANDBOX_STAGE: PackedScene = preload("res://scenes/stage/sandbox_stage.tscn")
const TOWN_STREET_PATH: String = "res://content/stages/starters/town_street.scene"
const SKY_IDS: Array[StringName] = [&"sky_clear_day", &"sky_cloudy_day", &"sky_sunset", &"sky_starry_night"]
const CAPTURE_DIR: String = "/private/tmp"
const TEMP_SCENE_PATH: String = "res://tests/phase19_sky_camera_tmp.scene"
func _initialize() -> void:
call_deferred("_capture_sky_states")
func _capture_sky_states() -> void:
root.size = Vector2i(1280, 720)
var stage: SandboxStage = SANDBOX_STAGE.instantiate() as SandboxStage
root.add_child(stage)
await _wait_for_render_frames(4)
var town_loaded: bool = bool(stage.call("_load_scene_from_path", TOWN_STREET_PATH, false))
print("VISUAL town scene loaded=", town_loaded)
stage.call("_on_grid_toggled", false)
var camera: Camera2D = stage.get_node("Camera2D") as Camera2D
var sky_view: StageSkyView = stage.get("_sky_view") as StageSkyView
var spawner: StageSpawner = stage.get("_spawner") as StageSpawner
var stickman: StickmanRig = spawner.spawn_stickman_asset("res://content/stickmen/break.stk", Vector2(0.0, -400.0))
if stickman != null:
stage.call("_save_object_state", stickman)
var scenery_controller: StageSceneryController = stage.get("_scenery_controller") as StageSceneryController
scenery_controller.begin_background_ground(&"finish_background_grass", Vector2(-900.0, 0.0), Color.WHITE)
var ground_press: InputEventMouseButton = InputEventMouseButton.new()
ground_press.button_index = MOUSE_BUTTON_LEFT
ground_press.pressed = true
scenery_controller.handle_mouse_button(ground_press, Vector2(-900.0, 0.0), false)
var ground_release: InputEventMouseButton = InputEventMouseButton.new()
ground_release.button_index = MOUSE_BUTTON_LEFT
ground_release.pressed = false
scenery_controller.handle_mouse_button(ground_release, Vector2(900.0, 900.0), false)
scenery_controller.exit_tool()
print("VISUAL action context stickman=", stickman != null, " background_ground_count=", (stage.get_node("World") as Node2D).get_children().filter(func(child: Node) -> bool: return child.get_script() == preload("res://scripts/stage/scenery/stage_background_ground.gd")).size())
await _wait_for_render_frames(4)
for sky_id: StringName in SKY_IDS:
camera.global_position = Vector2(0.0, -400.0)
camera.zoom = Vector2.ONE * 0.5
stage.set("_sky_id", sky_id)
sky_view.set_sky(sky_id)
await _wait_for_render_frames(2)
await _save_capture("sky_%s_reference.png" % String(sky_id))
stage.set("_sky_id", &"sky_sunset")
sky_view.set_sky(&"sky_sunset")
camera.global_position = Vector2(420.0, -210.0)
camera.zoom = Vector2.ONE * 1.5
await _wait_for_render_frames(3)
await _save_capture("sky_sunset_close_pan.png")
camera.global_position = Vector2(-620.0, -740.0)
camera.zoom = Vector2.ONE * 0.12
await _wait_for_render_frames(3)
await _save_capture("sky_sunset_wide_pan.png")
camera.global_position = Vector2(0.0, -400.0)
camera.zoom = Vector2.ONE * 0.5
await _wait_for_render_frames(3)
root.size = Vector2i(900, 700)
await _wait_for_render_frames(4)
await _save_capture("sky_sunset_resize_900x700.png")
stage.set("_sky_id", &"sky_starry_night")
sky_view.set_sky(&"sky_starry_night")
await _wait_for_render_frames(3)
await _save_capture("sky_starry_night_resize_900x700.png")
root.size = Vector2i(1440, 720)
await _wait_for_render_frames(4)
stage.set("_sky_id", &"sky_sunset")
sky_view.set_sky(&"sky_sunset")
await _wait_for_render_frames(3)
await _save_capture("sky_sunset_resize_1440x720.png")
stage.set("_sky_id", &"sky_starry_night")
sky_view.set_sky(&"sky_starry_night")
await _wait_for_render_frames(3)
await _save_capture("sky_starry_night_resize_1440x720.png")
root.size = Vector2i(1280, 720)
stage.set("_sky_id", &"sky_sunset")
sky_view.set_sky(&"sky_sunset")
stage.set("_cli_startup", true)
stage.call("set_mode", 1)
await _wait_for_render_frames(3)
await _save_capture("sky_sunset_direct.png")
stage.call("set_mode", 2)
await _wait_for_render_frames(3)
await _save_capture("sky_sunset_play.png")
stage.call("set_mode", 0)
await _wait_for_render_frames(3)
stage.set("_cli_startup", false)
await _save_capture("sky_sunset_play_stopped_edit.png")
camera.global_position = Vector2(420.0, -210.0)
camera.zoom = Vector2.ONE * 1.5
await _wait_for_render_frames(3)
var saved_shift: Vector2 = sky_view.get("_motif_shift")
var saved_scale: float = float(sky_view.get("_motif_scale"))
var saved_camera_position: Vector2 = camera.global_position
var saved_camera_zoom: Vector2 = camera.zoom
var saved: bool = stage.call("_save_scene_to_path", TEMP_SCENE_PATH, false)
var reopened: bool = bool(stage.call("_load_scene_from_path", TEMP_SCENE_PATH, false)) if saved else false
await _wait_for_render_frames(4)
var reopened_shift: Vector2 = sky_view.get("_motif_shift")
var reopened_scale: float = float(sky_view.get("_motif_scale"))
print("VISUAL sky save/reopen saved=", saved, " reopened=", reopened, " camera=", saved_camera_position, "/", saved_camera_zoom, " reopened_camera=", camera.global_position, "/", camera.zoom, " shift=", saved_shift, "/", reopened_shift, " scale=", saved_scale, "/", reopened_scale, " framing_matches=", saved_shift.is_equal_approx(reopened_shift) and is_equal_approx(saved_scale, reopened_scale))
await _save_capture("sky_sunset_save_reopen.png")
if FileAccess.file_exists(TEMP_SCENE_PATH):
DirAccess.remove_absolute(ProjectSettings.globalize_path(TEMP_SCENE_PATH))
stage.call("_load_scene_from_path", "res://content/stages/starters/blank.scene", false)
await _wait_for_render_frames(3)
stage.queue_free()
await process_frame
quit(0)
func _wait_for_render_frames(count: int) -> void:
for _frame: int in count:
await process_frame
await RenderingServer.frame_post_draw
func _save_capture(file_name: String) -> void:
await RenderingServer.frame_post_draw
var image: Image = root.get_texture().get_image()
var path: String = CAPTURE_DIR.path_join("phase19_" + file_name)
var error: Error = image.save_png(path)
print("VISUAL capture=", path, " size=", image.get_size(), " error=", error)