Files
Papay-Shooter/debug/transition_check.gd
T
2026-08-09 01:31:44 -04:00

130 lines
4.4 KiB
GDScript

extends SceneTree
## Regression for natural authored locomotion transitions.
## Gameplay velocity remains responsive, but the visual BlendSpace must ease
## into acceleration, direction changes and idle instead of teleporting the
## skeleton between unrelated poses on one frame.
const RESPONSE := 60.0
const TOP := 11.0
const DT := 1.0 / 60.0
const RUN_FRAMES := 48
const STOP_FRAMES := 60
var _frames := 0
var _model: SkinnedPlayerModel
var _speed := 0.0
var _first_speed := 0.0
var _first_stride := 0.0
var _settled_run_stride := 0.0
var _first_stop_stride := 0.0
var _mid_stop_stride := 0.0
var _settled_stop_stride := 0.0
var _largest_stride_step := 0.0
var _previous_stride := 0.0
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 not args.is_empty() \
else "res://assets/characters/skins/kiyoko.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 _frames == 8:
_model.set_process(false)
if _model._spring_mod:
_model._spring_mod.lod = 3
_model._spring_mod = null
_foot_bone = _model._role_bone("foot.L",
["foot.L", "LeftFoot", "Left ankle"])
var sample := _frames - 8
if sample < RUN_FRAMES:
_speed = lerpf(_speed, TOP, 1.0 - exp(-RESPONSE * DT))
else:
_speed = 0.0
_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()
_largest_stride_step = maxf(_largest_stride_step, absf(stride - _previous_stride))
_previous_stride = stride
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
elif sample == RUN_FRAMES - 1:
_settled_run_stride = stride
elif sample == RUN_FRAMES:
_first_stop_stride = stride
elif sample == RUN_FRAMES + 12:
_mid_stop_stride = stride
elif sample == RUN_FRAMES + STOP_FRAMES:
_settled_stop_stride = stride
_finish()
return true
return false
func _finish() -> void:
_model.update_state("dash", 13.0, false)
var dash_crossfade := _model._state_trans.xfade_time
_model.update_state("ground", 0.0, false)
var ground_crossfade := _model._state_trans.xfade_time
print("\n=== NATURAL LOCOMOTION TRANSITIONS ===")
print(" first frame body %.3f, visual stride %.3f" % [_first_speed, _first_stride])
print(" settled run %.3f, stop %.3f -> %.3f -> %.3f" % [
_settled_run_stride, _first_stop_stride, _mid_stop_stride,
_settled_stop_stride])
print(" largest visual stride step %.3f m/s" % _largest_stride_step)
print(" traversal/ground crossfades %.3f / %.3f s" % [
dash_crossfade, ground_crossfade])
var failures: Array[String] = []
if _first_speed < 6.0:
failures.append("test did not reproduce high gameplay acceleration")
if _first_stride <= 0.15 or _first_stride >= _first_speed * 0.65:
failures.append("acceleration visual either froze or snapped on frame one")
if absf(_settled_run_stride - TOP) > 0.30:
failures.append("visual stride did not converge to physical run speed")
if _first_stop_stride < _settled_run_stride * 0.65:
failures.append("run pose snapped too close to idle on the stop frame")
if _mid_stop_stride >= _first_stop_stride or _mid_stop_stride < 0.5:
failures.append("run-to-idle recovery is not a progressive ease")
if _settled_stop_stride > 0.30:
failures.append("run-to-idle recovery did not settle")
if _largest_stride_step > 2.5:
failures.append("a rendered frame contains an excessive gait jump")
if dash_crossfade < 0.19 or ground_crossfade < 0.28:
failures.append("full-body state crossfades are still too abrupt")
if _foot_bone < 0 or _foot_rotation_delta < 0.03:
failures.append("authored foot animation did not move")
if failures.is_empty():
print("TRANSITION_CHECK PASS: responsive gameplay, eased animation\n")
quit(0)
else:
for failure in failures:
push_error("TRANSITION_CHECK FAIL: " + failure)
quit(1)