103 lines
3.2 KiB
GDScript
103 lines
3.2 KiB
GDScript
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)
|