extends SceneTree ## Regression for authored directional locomotion. ## ## The old test expected one forward run to be procedurally yawed or reversed. ## The runtime now blends separate forward/back/left/right source cycles. This ## gate verifies that physical direction enters that authored BlendSpace, stride ## speed stays calibrated, and the feet actually move in every cardinal case. ## The real controller ground speed, not a convenient library-preview speed. ## This is the failure mode the test exists to catch: the old lateral cycle was ## technically "matched" at 5 m/s but exploded to 6.1x playback in gameplay. const SPEED := 11.0 const SETTLE_FRAMES := 18 const SAMPLE_FRAMES := 24 const CASES := [ ["forward", 0.0, 1.0, "Sprint"], ["backward", 0.0, -1.0, "RunBackward"], ["left", -1.0, 0.0, "RunLeft"], ["right", 1.0, 0.0, "RunRight"], ["forward-right", 1.0, 1.0, ""], ["back-left", -1.0, -1.0, ""], ] var _failures := 0 func _initialize() -> void: call_deferred("_run") func _run() -> void: await process_frame var args := OS.get_cmdline_user_args() var path: String = args[0] if not args.is_empty() \ else "res://assets/characters/skins/taila.glb" var scene := Node3D.new() root.add_child(scene) current_scene = scene var model := SkinnedPlayerModel.new() model.model_path = path scene.add_child(model) for _frame in 60: await process_frame if not model.loaded or model.skeleton == null: _expect(false, "the authored character loads") _done() return var foot := model._role_bone("foot.L", ["foot.L", "LeftFoot", "Left ankle"]) _expect(foot >= 0, "a foot bone resolves") for test_case in CASES: var direction := Vector2(float(test_case[1]), float(test_case[2])).normalized() for _frame in SETTLE_FRAMES: model.set_locomotion(direction.x, direction.y, 0.0) model.update_state("ground", SPEED, false) await process_frame var blend := model.locomotion_blend_debug() _expect(blend.length() > 0.01 and blend.normalized().dot(direction) > 0.999, "%s drives the authored BlendSpace in the physical direction" % String(test_case[0])) _expect(absf(model.locomotion_effective_speed_debug() - SPEED) < 0.02, "%s authored stride matches %.1f m/s" % [String(test_case[0]), SPEED]) var expected_clip := String(test_case[3]) if not expected_clip.is_empty(): _expect(model.current_clip_debug() == expected_clip, "%s selects %s" % [String(test_case[0]), expected_clip]) if foot >= 0: var start := model.skeleton.get_bone_pose_rotation(foot) var greatest := 0.0 for _frame in SAMPLE_FRAMES: model.set_locomotion(direction.x, direction.y, 0.0) model.update_state("ground", SPEED, false) await process_frame greatest = maxf(greatest, start.angle_to( model.skeleton.get_bone_pose_rotation(foot))) _expect(greatest > 0.03, "%s uses a moving authored foot cycle (%.3f rad)" % [String(test_case[0]), greatest]) model.queue_free() _done() func _expect(ok: bool, description: String) -> void: if ok: print(" OK: ", description) else: printerr(" FAIL: ", description) _failures += 1 func _done() -> void: print("\n=== AUTHORED DIRECTION SUMMARY ===") print("Failures: %d" % _failures) quit(1 if _failures > 0 else 0)