# fly_target_lifecycle_test —— CFlyTarget 销毁/目标中心回归。 extends SceneTree const FlyObject = preload("res://fly_object.gd") const FlyTargetAnchor = preload("res://fx/target_effect_anchor.gd") var _failures := 0 func _ck(condition: bool, message: String) -> void: if condition: return _failures += 1 printerr("FAIL: ", message) func _init() -> void: await _run() quit(1 if _failures else 0) func _run() -> void: await process_frame var mount := Node3D.new() get_root().add_child(mount) var manager := FlyObject.new() manager.manual_step = true mount.add_child(manager) manager.setup(mount) var target := Node3D.new() target.position = Vector3(8.0, 0.0, 0.0) var body := MeshInstance3D.new() body.mesh = BoxMesh.new() body.position = Vector3(0.0, 2.0, 0.0) target.add_child(body) mount.add_child(target) await process_frame var data := FlyObject.FlyData.new() data.init_vel = 10.0 data.flat_range = 30.0 data.bomb_range = 0.1 var inst = manager.spawn(Vector3.ZERO, target, false, data) var expected_center := FlyTargetAnchor.fly_target_position(target) _ck(inst.target_position().distance_to(expected_center) < 0.0001, "object target uses OnGetFlyTargetPosition-equivalent body centre") if manager.has_method("notify_target_despawn"): manager.notify_target_despawn(target) else: _ck(false, "FlyManager exposes target-despawn notification") _ck(not inst._is_object and inst._target_obj == null, "destroyed Actor converts object target to position target immediately") _ck(inst.target_position().distance_to(expected_center) < 0.0001, "converted target retains last target position") target.free() manager.step(0.01) _ck(inst.alive, "projectile remains valid after target node is destroyed") manager.clear_for_map_change() manager.free() mount.free() await process_frame if _failures == 0: print("PASS: fly_target_lifecycle_test")