extends SceneTree ## Regression for the original "slide, then walk, then run" locomotion bug. ## ## godot --headless --path . -s res://debug/transition_check.gd -- [skin_glb] ## ## Ground acceleration is the same exponential response as state_ground.gd. ## The test proves that the authored BlendSpace receives that physical speed on ## the same frame, including the first high-acceleration frame and the stop ## frame. It also proves that the removed procedural lean/brake layer stays off. const RESPONSE := 60.0 const TOP := 11.0 const DT := 1.0 / 60.0 const RUN_FRAMES := 24 var _frames := 0 var _model: SkinnedPlayerModel = null var _speed := 0.0 var _first_speed := -1.0 var _first_stride := -1.0 var _worst_error := 0.0 var _stop_stride := -1.0 var _manual_tick := false var _foot_bone := -1 var _foot_rotation_start := Quaternion.IDENTITY var _foot_rotation_delta := 0.0 func _initialize() -> void: var args := OS.get_cmdline_user_args() var path: String = args[0] if args.size() > 0 \ else "res://assets/characters/skins/miku.glb" var scene := Node3D.new() root.add_child(scene) current_scene = scene _model = SkinnedPlayerModel.new() _model.model_path = path scene.add_child(_model) func _process(_delta: float) -> bool: _frames += 1 if _frames < 8 or not _model.loaded: return false if not _manual_tick: _model.set_process(false) _manual_tick = true _foot_bone = _model._role_bone("foot.L", ["foot.L", "LeftFoot", "Left ankle"]) var sample := _frames - 8 if sample < RUN_FRAMES: var response := 1.0 - exp(-RESPONSE * DT) _speed = lerpf(_speed, TOP, response) else: _speed = 0.0 # Same order as the live controller: direction first, then physical speed. _model.set_locomotion(0.0, 1.0 if _speed > 0.01 else 0.0, 0.0) _model.update_state("ground", _speed, false) _model._process(DT) var stride := _model.locomotion_effective_speed_debug() _worst_error = maxf(_worst_error, absf(stride - _speed)) if _foot_bone >= 0: var foot_rotation := _model.skeleton.get_bone_pose_rotation(_foot_bone) if sample == 1: _foot_rotation_start = foot_rotation elif sample > 1 and sample < RUN_FRAMES: _foot_rotation_delta = maxf(_foot_rotation_delta, _foot_rotation_start.angle_to(foot_rotation)) if sample == 0: _first_speed = _speed _first_stride = stride if sample == RUN_FRAMES: _stop_stride = stride if sample > RUN_FRAMES + 2: print("\n=== velocity-driven locomotion ===") print(" first frame: body %.3f m/s, authored stride %.3f m/s" \ % [_first_speed, _first_stride]) print(" worst stride/speed error: %.5f m/s" % _worst_error) print(" stop-frame stride: %.5f m/s" % _stop_stride) print(" animated foot rotation delta: %.5f rad" % _foot_rotation_delta) print(" procedural lean %.5f, brake %.5f" \ % [_model.get_lean_debug(), _model.get_brake_debug()]) var failures: Array[String] = [] if _first_speed < 6.0: failures.append("test did not reproduce high first-frame acceleration") if absf(_first_stride - _first_speed) > 0.02: failures.append("legs did not receive first-frame physical speed") if _worst_error > 0.02: failures.append("authored stride diverged from physical speed") if _stop_stride > 0.02: failures.append("legs did not reach Idle on the stop frame") if _foot_bone < 0: failures.append("could not resolve a foot bone") elif _foot_rotation_delta < 0.03: failures.append("runtime foot pose stayed static") if absf(_model.get_lean_debug()) > 0.02: failures.append("procedural locomotion lean is still active") if absf(_model.get_brake_debug()) > 0.02: failures.append("procedural stop pose is still active") if failures.is_empty(): print("TRANSITION_CHECK PASS: same-frame, authored locomotion\n") quit(0) else: for failure in failures: push_error("TRANSITION_CHECK FAIL: " + failure) quit(1) return true return false