extends SceneTree var failures: Array[String] = [] func _init() -> void: _run.call_deferred() func _run() -> void: await _check_stage_matches_x1_visual_baseline() await _check_hud_matches_x1_visual_baseline() _finish() func _check_stage_matches_x1_visual_baseline() -> void: var scene: PackedScene = load("res://scenes/stage/stage.tscn") _expect(scene != null, "Stage scene should load") if scene == null: return var stage := scene.instantiate() var initial_player_position := (stage.get_node("ActorsContainer/Player") as Node2D).position var initial_melee_position := (stage.get_node("ActorsContainer/JinZhanMinion") as Node2D).position var initial_boss_position := (stage.get_node("ActorsContainer/Boss") as Node2D).position root.add_child(stage) await process_frame await process_frame var camera := stage.get_node_or_null("Camera2D") as Camera2D var player := stage.get_node_or_null("ActorsContainer/Player") as Node2D var melee := stage.get_node_or_null("ActorsContainer/JinZhanMinion") as Node2D var boss := stage.get_node_or_null("ActorsContainer/Boss") as Node2D _expect_bool(bool(stage.get("use_authored_camera_view")), false, "Stage should use the x1 follow-camera baseline") _expect_vector(camera.global_position, Vector2(1180, 395), "Camera should follow the left-spawn player (new2)") _expect_vector(camera.zoom, Vector2(1.25, 1.25), "Camera zoom should match x1") _expect(stage.get_node_or_null("Ground/ArtLayer/PastBackground") is Sprite2D, "Stage should use the x1 ground1 background") _expect(stage.get_node_or_null("Ground/ArtLayer/FutureBackground") is Sprite2D, "Stage should keep the x1 future background") _expect_vector(initial_player_position, Vector2(1180, 560), "Player should spawn at the map's left side (new2)") _expect_vector(initial_melee_position, Vector2(2200, 560), "Initial minion anchor should use the left-shifted safe position") _expect_vector(initial_boss_position, Vector2(2520, 560), "Boss anchor should match x1") _expect_float(player.global_position.y, melee.global_position.y, "Player and minion anchors should share one horizontal line") _expect_float(player.global_position.y, boss.global_position.y, "Player and Boss anchors should share one horizontal line") _expect_float(_visible_sprite_bottom_global_y(player.get_node("Visual/CharacterSprite") as Sprite2D), 520.0, "Player visible feet should match Rthythm_archor_game") _expect_float(_visible_sprite_bottom_global_y(melee.get_node("Visual/CharacterSprite") as Sprite2D), 520.0, "Initial minion visible feet should match Rthythm_archor_game") _expect_float(_visible_sprite_bottom_global_y(boss.get_node("Visual/CharacterSprite") as Sprite2D), 520.0, "Boss visible feet should match Rthythm_archor_game") _expect_vector(player.get_node("Visual").scale, Vector2(1, 1), "Player visual scale should match x1") _expect_vector(melee.get_node("Visual").scale, Vector2(2, 2), "Minion visual scale should match x1") _expect_float(float(melee.get("visual_ground_offset")), -38.0, "Minion visual offset should match Rthythm_archor_game anchor baseline") var boss_scale: Vector2 = boss.get_node("Visual").scale _expect_vector(Vector2(absf(boss_scale.x), boss_scale.y), Vector2(2, 2), "Boss visual scale should match x1") _expect_float(float(boss.get("visual_ground_offset")), -40.0, "Boss visual offset should match Rthythm_archor_game so the visible body lines up") _expect_float(float(boss.get("projectile_spawn_height")), 74.0, "Boss projectile lane should match Rthythm_archor_game after the visual offset is restored") stage.free() func _check_hud_matches_x1_visual_baseline() -> void: var main_scene: PackedScene = load("res://scenes/main/main.tscn") _expect(main_scene != null, "Main scene should load") if main_scene == null: return var main := main_scene.instantiate() root.add_child(main) await process_frame _expect(main.get_node_or_null("UILayer") is CanvasLayer, "HUD should stay in the x1 CanvasLayer") var ui := main.get_node_or_null("UILayer/UI") as Control _expect(ui != null, "Main UI should be under UILayer/UI") if ui != null: _expect_vector((ui.get_node("StatusBars") as Control).scale, Vector2(1.125, 1.125), "Player HUD runtime scale should match x1") _expect_vector((ui.get_node("RhythmTrack") as Control).scale, Vector2(1, 1), "Rhythm HUD scale should match x1") _expect_vector((ui.get_node("BossStatus") as Control).scale, Vector2(1.062, 1.062), "Boss HUD runtime scale should match x1") _expect_vector((ui.get_node("ComboWindow") as Control).scale, Vector2(1.152, 1.152), "Combo HUD runtime scale should match x1") main.free() func _expect(condition: bool, label: String) -> void: if not condition: failures.append(label) func _expect_bool(actual: bool, expected: bool, label: String) -> void: if actual != expected: failures.append("%s: expected %s, got %s" % [label, expected, actual]) func _expect_float(actual: float, expected: float, label: String) -> void: if absf(actual - expected) > 0.03: failures.append("%s: expected %.3f, got %.3f" % [label, expected, actual]) func _expect_vector(actual: Vector2, expected: Vector2, label: String) -> void: if actual.distance_to(expected) > 0.03: failures.append("%s: expected %s, got %s" % [label, expected, actual]) func _visible_sprite_bottom_global_y(sprite: Sprite2D) -> float: if sprite == null or sprite.texture == null: return INF var image := sprite.texture.get_image() if image == null or image.is_empty(): return INF var hframes := maxi(1, sprite.hframes) var vframes := maxi(1, sprite.vframes) var frame_width := image.get_width() / hframes var frame_height := image.get_height() / vframes var frame_index := clampi(sprite.frame, 0, hframes * vframes - 1) var column := frame_index % hframes var row := int(frame_index / hframes) var bottom := -1 for y: int in range(frame_height): for x: int in range(frame_width): var pixel := image.get_pixel(column * frame_width + x, row * frame_height + y) if pixel.a > 0.01: bottom = y if bottom < 0: return INF return sprite.to_global(sprite.offset + Vector2(0.0, bottom)).y func _finish() -> void: if failures.is_empty(): print("PASS x1 visual baseline") quit(0) else: for failure: String in failures: push_error(failure) quit(1)