extends SceneTree const Anchor = preload("res://fx/motion_effect_anchor.gd") const PlayerView = preload("res://ui/player_view.gd") var failures := 0 func check(ok: bool, label: String) -> void: if not ok: failures += 1 printerr("FAIL: " + label) func _init() -> void: call_deferred("run") func run() -> void: var view := PlayerView.new() check(view.build(AssetRoot.path(), 0), "real warrior builds") root.add_child(view) view.position = Vector3(7, 2, -4) view.rotation.y = 0.7 view.anim.set_process(false) view.anim.set_time(0.2) var effect := Node3D.new() root.add_child(effect) var anchor := Anchor.new() effect.add_child(anchor) var event := {"attaching": true, "following": true, "independent": false, "bone": "equip_right_hand", "pos": Vector3(10, 20, 30)} check(anchor.configure(view, event), "real equip_right_hand resolves") check(anchor.is_processing(), "following bone updates") var pose: Dictionary = view.anim.get_effect_bone_pose(event.bone) if not pose.is_empty(): var expected: Vector3 = view.model.global_transform * (pose.transform.origin + event.pos) check(effect.global_position.distance_to(expected) < 0.00001, "bone + actor-local cm offset, then actor rotation") var before := effect.global_transform view.anim.set_time(1.0) anchor.update_anchor() check(not effect.global_transform.is_equal_approx(before), "anchor uses sampled animation pose") event.following = false check(anchor.configure(view, event) and not anchor.is_processing(), "snapshot bone does not follow") before = effect.global_transform view.position.x += 3.0 await process_frame check(effect.global_transform.is_equal_approx(before), "snapshot remains in world after actor moves") event.independent = true event.bone = "missing" check(anchor.configure(view, event), "independent ignores bone") check(effect.global_position.distance_to(view.model.global_transform * event.pos) < 0.00001, "independent offset converts source Z to world up") event.independent = false check(not anchor.configure(view, event), "missing bone rejected") event.attaching = false check(anchor.configure(view, event) and anchor.is_processing(), "root attachment follows regardless of FollowingEnable") # Metadata from the real file must survive the file-reader parser and native API. view.anim.set_anim_path(AssetRoot.path().path_join("PC/ymir work/pc/warrior/skill/gigongcham.msa")) var events: Array = view.anim.get_events() check(not events.is_empty(), "real skill events loaded") if not events.is_empty(): check(events[0].attaching and events[0].bone == "Bip01 Spine1", "file parser retains bone metadata") check(String(events[0].effect).contains("/"), "full effect path retained") effect.free() view.free() await process_frame print("motion_effect_anchor_test: failures=%d" % failures) quit(1 if failures else 0)