test: 八项无头回归测试(道具/弹道/Boss/海面/地面单位/浮游炮/炸弹/冒烟模拟)
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
extends SceneTree
|
||||
## 玩家子弹弹道回归测试(时间驱动):
|
||||
## L4 齐射后快照所有子弹位置,0.25s 后比对实际位移方向,
|
||||
## 必须命中 UP / ±22° / ±30° 五个弹道之一,且各组数量正确。
|
||||
## 在 spawner 首波敌机(0.8s)出现前完成测量,保证确定性。
|
||||
## 运行:godot --headless -s tests/bullet_traj_test.gd
|
||||
|
||||
const SNAP_DELAY := 0.1
|
||||
const FLY_TIME := 0.25
|
||||
const DOT_EPS := 0.995
|
||||
const GLOBAL_TIMEOUT := 15.0
|
||||
|
||||
var _time := 0.0
|
||||
var _snapped := false
|
||||
var _game: Game
|
||||
var _pbullets: Node2D
|
||||
var _fails: Array[String] = []
|
||||
var _snapshot: Array = [] # [bullet: Node2D, pos: Vector2]
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
_game = (load("res://scenes/game.tscn") as PackedScene).instantiate()
|
||||
root.add_child(_game)
|
||||
print("[traj_test] game instantiated")
|
||||
|
||||
|
||||
func _fail(msg: String) -> void:
|
||||
_fails.append(msg)
|
||||
print("[FAIL] " + msg)
|
||||
|
||||
|
||||
func _ensure_refs() -> bool:
|
||||
if _pbullets == null:
|
||||
_pbullets = _game.get_node_or_null("PlayerBullets") as Node2D
|
||||
return _pbullets != null
|
||||
|
||||
|
||||
func _process(delta: float) -> bool:
|
||||
_time += delta
|
||||
if _time > GLOBAL_TIMEOUT:
|
||||
_fail("global timeout")
|
||||
return _finish()
|
||||
if not _ensure_refs():
|
||||
return false
|
||||
|
||||
if not _snapped and _time >= SNAP_DELAY:
|
||||
_snapped = true
|
||||
# 先射一梭子 L4(fire 从首帧按住,此处已有一发齐射在飞)
|
||||
_game.player.weapon_level = 4
|
||||
Input.action_release("fire")
|
||||
_game.player._fire()
|
||||
_snapshot = []
|
||||
for bullet in _pbullets.get_children():
|
||||
_snapshot.append([bullet, (bullet as Node2D).position])
|
||||
if _snapshot.size() != 12:
|
||||
_fail("L4 volley=%d expected 12" % _snapshot.size())
|
||||
return false
|
||||
|
||||
if _snapped and _time >= SNAP_DELAY + FLY_TIME:
|
||||
var allowed := [
|
||||
Vector2.UP,
|
||||
Vector2.UP.rotated(deg_to_rad(-22.0)),
|
||||
Vector2.UP.rotated(deg_to_rad(22.0)),
|
||||
Vector2.UP.rotated(deg_to_rad(-30.0)),
|
||||
Vector2.UP.rotated(deg_to_rad(30.0)),
|
||||
]
|
||||
var counts := [0, 0, 0, 0, 0]
|
||||
var classified := 0
|
||||
for entry in _snapshot:
|
||||
var bullet: Node2D = entry[0]
|
||||
var start: Vector2 = entry[1]
|
||||
if not is_instance_valid(bullet):
|
||||
_fail("bullet freed before measurement window ended")
|
||||
continue
|
||||
var disp: Vector2 = bullet.position - start
|
||||
if disp.length() < 50.0:
|
||||
_fail("bullet barely moved: %.1f px" % disp.length())
|
||||
continue
|
||||
var dir := disp.normalized()
|
||||
for i in allowed.size():
|
||||
if dir.dot(allowed[i]) >= DOT_EPS:
|
||||
counts[i] += 1
|
||||
classified += 1
|
||||
break
|
||||
if classified != _snapshot.size():
|
||||
_fail("only %d/%d bullets on valid trajectories" % [classified, _snapshot.size()])
|
||||
elif counts[0] != 6 or counts[1] != 2 or counts[2] != 2 or counts[3] != 1 or counts[4] != 1:
|
||||
_fail("trajectory group counts wrong: straight=%d L22=%d R22=%d L30=%d R30=%d" % counts)
|
||||
else:
|
||||
print("[traj_test] trajectories ok: straight=%d ±22=%d+%d ±30=%d+%d" % [counts[0], counts[1], counts[2], counts[3], counts[4]])
|
||||
return _finish()
|
||||
return false
|
||||
|
||||
|
||||
func _finish() -> bool:
|
||||
if _fails.is_empty():
|
||||
print("[traj_test] PASS")
|
||||
else:
|
||||
print("[traj_test] FAIL: %d issue(s)" % _fails.size())
|
||||
quit(0 if _fails.is_empty() else 1)
|
||||
return true
|
||||
Reference in New Issue
Block a user