Files
rthythm_archor_game/tests/test_x1_visual_baseline.gd
T
2026-07-06 22:59:47 -07:00

128 lines
6.1 KiB
GDScript

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)