big
This commit is contained in:
+82
-48
@@ -1,41 +1,36 @@
|
||||
extends SceneTree
|
||||
|
||||
## Dev tool: how abruptly does the model change posture when you start running?
|
||||
## Regression for the original "slide, then walk, then run" locomotion bug.
|
||||
##
|
||||
## godot --headless --path . -s res://debug/transition_check.gd -- [skin_glb]
|
||||
##
|
||||
## Accelerates from a standstill the way the movement code does, then stops, and
|
||||
## logs the clip in play plus the body's forward lean each frame. Reports the
|
||||
## worst single-frame change in lean and how long the lean took to arrive.
|
||||
##
|
||||
## Written for "idle to running snaps the character leaning forward". The lean
|
||||
## is procedural (ShooterPoseModifier), so it does NOT come from the clip
|
||||
## crossfade and is not visible in an animation-blend graph — it was driven by a
|
||||
## normalised input direction that steps 0 -> 1 the instant a key goes down,
|
||||
## planting a full run posture in ~0.1 s while the Idle->Run crossfade still had
|
||||
## 0.4 s to run.
|
||||
## 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 ACCEL := 18.0 # m/s^2, roughly the controller's ground acceleration
|
||||
const TOP := 9.0 # m/s
|
||||
const RESPONSE := 60.0
|
||||
const TOP := 11.0
|
||||
const DT := 1.0 / 60.0
|
||||
const RUN_FRAMES := 24
|
||||
|
||||
var _t := 0.0
|
||||
var _frames := 0
|
||||
var _model: SkinnedPlayerModel = null
|
||||
var _speed := 0.0
|
||||
var _prev_lean := 0.0
|
||||
var _worst_step := 0.0
|
||||
var _clip_changes: Array = []
|
||||
var _last_clip := ""
|
||||
var _t_10 := -1.0
|
||||
var _t_90 := -1.0
|
||||
var _peak := 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/taila.glb"
|
||||
else "res://assets/characters/skins/miku.glb"
|
||||
var scene := Node3D.new()
|
||||
root.add_child(scene)
|
||||
current_scene = scene
|
||||
@@ -46,36 +41,75 @@ func _initialize() -> void:
|
||||
|
||||
func _process(_delta: float) -> bool:
|
||||
_frames += 1
|
||||
if _frames < 8:
|
||||
return false
|
||||
if not _model.loaded:
|
||||
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"])
|
||||
|
||||
# Accelerate for 1.5 s, then coast at top speed to 2.5 s.
|
||||
_t += DT
|
||||
_speed = minf(TOP, _speed + ACCEL * DT) if _t < 2.5 else 0.0
|
||||
_model.update_state("ground", _speed, false)
|
||||
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 lean: float = _model.get_lean_debug()
|
||||
if _last_clip != _model.current_clip_debug():
|
||||
_last_clip = _model.current_clip_debug()
|
||||
_clip_changes.append("%.2fs %s @ %.1f m/s" % [_t, _last_clip, _speed])
|
||||
if _t < 2.5:
|
||||
_peak = maxf(_peak, lean)
|
||||
if _t_10 < 0.0 and lean > 0.1:
|
||||
_t_10 = _t
|
||||
if _t_90 < 0.0 and lean > 0.9:
|
||||
_t_90 = _t
|
||||
_worst_step = maxf(_worst_step, absf(lean - _prev_lean))
|
||||
_prev_lean = lean
|
||||
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 _t > 3.5:
|
||||
print("\n=== idle -> run transition ===")
|
||||
print(" clip changes: " + ", ".join(_clip_changes))
|
||||
print(" lean reached 10%% at %.2fs, 90%% at %.2fs (peak %.2f)" % [_t_10, _t_90, _peak])
|
||||
print(" worst single-frame lean change: %.4f (%.2f per second at 60fps)"
|
||||
% [_worst_step, _worst_step * 60.0])
|
||||
print(" a snap looks like ~0.10s to 90%%; a blended move is ~0.6s or more\n")
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user