5f6258e3d4
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
102 lines
3.0 KiB
GDScript
102 lines
3.0 KiB
GDScript
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
|