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

175 lines
6.7 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
extends SceneTree
## 第三轮修复契约(2026-07-05):
## 1. 标题/难度背景改用无 Logo 的干净关卡原画 + 独立悬浮 Logo,
## 背景以 2 秒为单位渐变轮换,渐变期间 UI 不再重叠。
## 2. 判定字样(JudgementArt/Label)显示 1 秒后自动消失,新判定顶替重置计时。
## 3. Boss 被击退:击退残速不再被 handle_movement 清零,滑行距离 ×3
## (距离 ∝ 初速²,由 CombatManager 按 √3 换算初速)。
const CLEAN_PAST_ART := "res://assets/art/ground/past/past.png"
const CLEAN_FUTURE_ART := "res://assets/art/ground/future/future.png"
var failures: Array[String] = []
func _init() -> void:
_run.call_deferred()
func _run() -> void:
root.size = Vector2i(1152, 648)
var settings := root.get_node_or_null("GameSettings")
if settings != null:
settings.call("set_difficulty", &"easy")
await _check_title_composition()
await _check_judgement_auto_hide()
await _check_boss_knockback()
_finish()
func _check_title_composition() -> void:
var flow: Node = load("res://autoload/game_flow_manager.gd").new()
flow.name = "FlowUnderTestRound3"
flow.set("manage_scene", false)
root.add_child(flow)
await process_frame
var consts: Dictionary = (flow.get_script() as Script).get_script_constant_map()
_expect_float(float(consts.get("TITLE_BG_HOLD_SECONDS", 0.0)), 2.0, "标题背景以 2 秒为单位轮流切换")
_expect_float(float(consts.get("TITLE_BG_FADE_SECONDS", 0.0)), 0.8, "轮换带渐入过渡")
var title_screen := (flow.get("_screens") as Dictionary).get(&"Title", null) as Control
_expect(title_screen != null, "标题屏存在")
if title_screen != null:
var past := title_screen.get_node_or_null("SceneBackgroundPast") as TextureRect
var future := title_screen.get_node_or_null("SceneBackgroundFuture") as TextureRect
_expect(past != null and past.texture != null and past.texture.resource_path == CLEAN_PAST_ART,
"标题底层背景使用干净的过去原画(无 Logo 烙印)")
_expect(future != null and future.texture != null and future.texture.resource_path == CLEAN_FUTURE_ART,
"标题叠层背景使用干净的未来原画(无 Logo 烙印)")
var logo := title_screen.get_node_or_null("TitleLogo") as TextureRect
_expect(logo != null, "标题持有独立悬浮 Logo 节点")
if logo != null:
_expect(logo.position.x > 340.0, "Logo 悬浮在背景中央区域(面板之外),渐变时保持固定")
_expect(logo.size.is_equal_approx(Vector2(616.0, 300.0)), "Logo 按原生尺寸显示")
_expect(logo.mouse_filter == Control.MOUSE_FILTER_IGNORE, "悬浮 Logo 不得拦截鼠标点击")
var difficulty_screen := (flow.get("_screens") as Dictionary).get(&"DifficultySelect", null) as Control
_expect(difficulty_screen != null, "难度屏存在")
if difficulty_screen != null:
var bg := difficulty_screen.get_node_or_null("SceneBackground") as TextureRect
_expect(bg != null and bg.texture != null and bg.texture.resource_path == CLEAN_FUTURE_ART,
"难度屏背景使用干净的未来原画")
_expect(difficulty_screen.get_node_or_null("TitleLogo") is TextureRect,
"难度屏同样叠加悬浮 Logo")
flow.free()
func _check_judgement_auto_hide() -> void:
var scene: PackedScene = load("res://scenes/ui/main_ui.tscn")
if scene == null:
failures.append("main_ui.tscn 应可加载")
return
var ui := scene.instantiate()
root.add_child(ui)
await process_frame
var track := ui.get_node_or_null("RhythmTrack") as Control
var art := track.get_node_or_null("JudgementArt") as TextureRect if track != null else null
if art == null:
failures.append("RhythmTrack 应持有 JudgementArt")
ui.free()
return
var bus := _event_bus()
bus.emit_signal("judgement_made", &"perfect", 0.0, 12)
await process_frame
_expect(art.visible, "判定字样在判定后立即可见")
await create_timer(0.5).timeout
_expect(art.visible, "判定字样满 1 秒前保持可见")
await create_timer(0.85).timeout
_expect(not art.visible, "判定字样满 1 秒后自动消失")
# 顶替语义:1 秒内出现新判定则重置计时,从新判定起再算 1 秒。
bus.emit_signal("judgement_made", &"good", 20.0, 13)
await process_frame
_expect(art.visible, "新判定重新点亮字样")
await create_timer(0.6).timeout
bus.emit_signal("judgement_made", &"perfect", 0.0, 14)
await process_frame
await create_timer(0.6).timeout
_expect(art.visible, "被顶替后计时重置:距新判定不足 1 秒仍可见")
await create_timer(0.7).timeout
_expect(not art.visible, "顶替后的判定同样满 1 秒消失")
ui.free()
func _check_boss_knockback() -> void:
var combat := root.get_node_or_null("CombatManager")
var boss_scene: PackedScene = load("res://scenes/enemies/boss.tscn")
if boss_scene == null or combat == null:
failures.append("boss.tscn 与 CombatManager 应可用")
return
var boss := boss_scene.instantiate() as Node2D
root.add_child(boss)
await process_frame
boss.global_position = Vector2(500.0, 300.0)
# 2026-07-05 策划两轮调参:首轮 ×3 试玩仍嫌短,加码到 ×9(初速 ×3)。
_expect_float(float(boss.call("knockback_distance_taken_mult")), 9.0, "Boss 被击退距离倍数为 9")
_expect_float(float(combat.call("_knockback_velocity_scale_for_receiver", boss)), 3.0,
"CombatManager 将距离 ×9 换算为初速 ×3")
var plain := Node2D.new()
root.add_child(plain)
_expect_float(float(combat.call("_knockback_velocity_scale_for_receiver", plain)), 1.0,
"无钩子的受击者保持原始击退")
plain.free()
# 结构性修复:击退残速必须在 Boss 的 handle_movement 里活下来并产生滑行。
var motor := boss.get_node("MovementMotor")
var start_x := boss.global_position.x
motor.call("apply_knockback", Vector2(120.0, 0.0))
await physics_frame
await physics_frame
await physics_frame
_expect((boss as CharacterBody2D).velocity.x > 0.0, "击退残速不再被 Boss 每帧清零")
for _index: int in range(60):
await physics_frame
var displacement := boss.global_position.x - start_x
_expect(displacement > 5.0, "Boss 击退产生实际水平滑行(got %.2f px" % displacement)
boss.free()
func _event_bus() -> Node:
var bus := root.get_node_or_null("EventBus")
if bus == null:
bus = load("res://autoload/event_bus.gd").new()
bus.name = "EventBus"
root.add_child(bus)
return bus
func _expect(condition: bool, label: String) -> void:
if not condition:
failures.append(label)
func _expect_float(actual: float, expected: float, label: String) -> void:
if not is_equal_approx(actual, expected):
failures.append("%s: expected %.4f, got %.4f" % [label, expected, actual])
func _finish() -> void:
paused = false
if failures.is_empty():
print("PASS round3 fixes")
quit(0)
else:
for failure: String in failures:
push_error(failure)
quit(1)