Skip to content

Commit b791fe4

Browse files
committed
Fix loop-wrap snaps, pin overshoot, and unreachable reach targets
Proactively audited all 65 movements by driving the real render pipeline (Playwright + live bone-position diagnostics) across every phase and past each loop's 100% mark, instead of waiting for individual bug reports. Found and fixed four distinct classes of animation bugs: - Loop-wrap snap: buildTimeline() always appends a wrap keyframe back to the start pose's joints, sized to the first phase's duration. Any movement whose last phase left a joint non-neutral produced a visible snap once per repeat cycle. Added an explicit reset phase to bent-over-row, step-up, and bicycle-crunch so every joint returns to its neutral/symmetric value before the loop wraps. - Pin overshoot: applyPins() recomputes the whole-root translation every frame from the pinned effector's current position, so engaging a pin from a pose far from the anchor (e.g. arms at rest, bar overhead) caused a wild one-frame root spike. Restructured pull-up, dead-hang, and hanging-knee-raise so an FK-only lead-in phase reaches the bar before the pin engages, and an FK-only outro releases it, instead of engaging/releasing pins across a large pose gap. - Unreachable reach-IK targets: solveCCD is a 2-joint chain hard-capped by physical arm/leg length (~0.54m for arms). touch-toes, cross-body-reach, and standing-quad-stretch asked for reaches beyond that envelope, leaving a visible gap between hand and target. Iteratively adjusted hinge/knee/rotation depth to bring each target within reach (touch-toes, standing-quad-stretch), and redesigned cross-body-reach to target the opposite shoulder instead of the knee, a standard reachable cross-body stretch. - Floor clipping under a fixed-height pin: the frame-loop safety net only clamped the root above the floor when a phase had neither ground-lock nor a pin active, so triceps-dips (hands pinned to a low chair seat, legs held at a constant FK bend) let the feet sink 0.2m through the floor as the elbows folded and the root translated down to match the seat. Generalized the clamp to always run after ground-lock/pins, since it only ever moves the root up and never fights a legitimate pose — verified this doesn't affect the bar-hang movements, where feet are legitimately suspended above the floor. seated-forward-fold's reach was blocked by a real engine gap: the hip-hinge coupling in timeline.ts snapshot() assumes a zero hip-angle baseline (true for standing) and breaks when combined with a pose that pre-bends the hips, like seated (hip_left/right start at -90°). Rather than change that coupling under time pressure, reworked the movement to use FK-only torso flex instead of pelvis hinge + reach. This is a known engine limitation, not a full fix. Verified with a full sweep sampling all 65 movements at 9 points each (including past 100% to exercise the loop-wrap transition) for both floor-clipping and pelvis-height sanity — zero issues remain. All 139 existing tests still pass.
1 parent 8257df4 commit b791fe4

11 files changed

Lines changed: 96 additions & 36 deletions

packages/movit-render/src/index.ts

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -408,20 +408,20 @@ export function createViewer(
408408
mannequin.root.updateMatrixWorld(true);
409409
applyGroundLock(info.groundLock);
410410
applyPins(info.pins);
411-
// Safety net: a phase with neither ground-lock nor a pin has no
412-
// per-frame anchor at all, so the root stays frozen at the base pose's
413-
// grounded height while the FK animates freely on top of it — bending
414-
// joints (e.g. a prone "superman" lift, a supine crunch) can then swing
415-
// part of the mesh below the floor with nothing to catch it. Clamp the
416-
// root up so the lowest point never dips below y=0; a no-op whenever the
417-
// pose is legitimately elevated (bbox min already ≥ 0).
418-
if (info.groundLock.length === 0 && info.pins.length === 0) {
411+
// Safety net: nothing above ever intentionally pushes part of the body
412+
// below the floor, so clamp the root up whenever the lowest point dips
413+
// below y=0 — a no-op whenever the pose is legitimately grounded or
414+
// elevated (bbox min already ≥ 0). This also catches phases with
415+
// neither ground-lock nor a pin (the root stays frozen at the base
416+
// pose's grounded height while FK animates freely on top of it, e.g. a
417+
// prone "superman" lift), and pinned phases where a fixed-height anchor
418+
// (a low chair seat) combined with static leg FK can otherwise let the
419+
// feet sink through the floor as the arms fold (e.g. a chair dip).
420+
mannequin.root.updateMatrixWorld(true);
421+
const box = new THREE.Box3().setFromObject(mannequin.root);
422+
if (box.min.y < 0) {
423+
mannequin.root.position.y -= box.min.y;
419424
mannequin.root.updateMatrixWorld(true);
420-
const box = new THREE.Box3().setFromObject(mannequin.root);
421-
if (box.min.y < 0) {
422-
mannequin.root.position.y -= box.min.y;
423-
mannequin.root.updateMatrixWorld(true);
424-
}
425425
}
426426
applyReaches(info.reaches);
427427
if (info.phaseName !== lastPhaseName) {

spec/examples/bent-over-row.movit

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,13 @@ movit exercise "Bent-over row"
2323
ground-lock: feet
2424
cue "Lower the bar under control, keep the back flat"
2525

26+
step "Stand" 1s ease-out:
27+
pelvis: hinge 0
28+
knees: flex 0
29+
shoulders: extend 0
30+
elbows: flex 0
31+
neck: extend 0
32+
ground-lock: feet
33+
cue "Stand up tall between sets"
34+
2635
repeat 8

spec/examples/bicycle-crunch.movit

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,12 @@ movit exercise "Bicycle crunch"
2727
knee_left: flex 20
2828
cue "Switch — left elbow toward the right knee"
2929

30+
step "Center" 0.6s ease-out:
31+
spine: rotate-in 0
32+
hip_right: flex 90
33+
knee_right: flex 90
34+
hip_left: flex 90
35+
knee_left: flex 90
36+
cue "Return to center, knees stacked over the hips"
37+
3038
repeat 5

spec/examples/cross-body-reach.movit

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,24 @@ movit stretch "Cross-body reach"
33
pose start = standing
44

55
step "Reach across right" 2s ease-in-out:
6-
spine: rotate-in 20
7-
reach: hand_right knee_left
6+
shoulder_right: flex 80
7+
reach: hand_right shoulder_left
88
ground-lock: feet
9-
cue "Rotate gently and reach the right hand toward the left knee"
9+
cue "Pull the right arm across the chest toward the left shoulder"
1010

1111
step "Return" 1.4s ease-out:
12-
spine: rotate-in 0
12+
shoulder_right: flex 0
1313
ground-lock: feet
14-
cue "Unwind back to standing tall"
14+
cue "Release back to center"
1515

1616
step "Reach across left" 2s ease-in-out:
17-
spine: rotate-out 20
18-
reach: hand_left knee_right
17+
shoulder_left: flex 80
18+
reach: hand_left shoulder_right
1919
ground-lock: feet
20-
cue "Reach the left hand toward the right knee"
20+
cue "Pull the left arm across the chest toward the right shoulder"
2121

2222
step "Center" 1.4s ease-out:
23-
spine: rotate-out 0
23+
shoulder_left: flex 0
2424
ground-lock: feet
2525
cue "Return to center"
2626

spec/examples/dead-hang.movit

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,23 @@ movit exercise "Dead hang"
33
prop bar
44
pose start = standing
55

6+
step "Reach" 0.8s ease-out:
7+
shoulders: flex 175
8+
elbows: flex 5
9+
ground-lock: feet
10+
cue "Reach up and grip the bar"
11+
612
step "Hang" 3s ease-in-out:
713
shoulders: flex 175
814
elbows: flex 5
915
pin: hand_left bar
1016
pin: hand_right bar
11-
cue "Reach up, grip the bar, and relax into a long, straight-arm hang"
17+
cue "Relax into a long, straight-arm hang"
1218

1319
step "Down" 1.5s ease-out:
1420
shoulders: flex 0
1521
elbows: flex 0
22+
ground-lock: feet
1623
cue "Drop down off the bar and shake out the arms"
1724

1825
repeat 3

spec/examples/hanging-knee-raise.movit

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,13 @@ movit exercise "Hanging knee raise"
33
prop bar
44
pose start = standing
55

6-
step "Grip" 1.5s ease-in-out:
6+
step "Reach" 0.8s ease-out:
7+
shoulders: flex 175
8+
elbows: flex 5
9+
ground-lock: feet
10+
cue "Reach up and grip the bar"
11+
12+
step "Grip" 0.7s ease-in-out:
713
shoulders: flex 175
814
elbows: flex 5
915
pin: hand_left bar
@@ -13,15 +19,25 @@ movit exercise "Hanging knee raise"
1319
step "Raise knees" 1.2s ease-out:
1420
hips: flex 90
1521
knees: flex 90
22+
shoulders: flex 175
23+
elbows: flex 5
1624
pin: hand_left bar
1725
pin: hand_right bar
1826
cue "Draw both knees up toward the chest"
1927

2028
step "Lower" 1.4s ease-in:
2129
hips: flex 0
2230
knees: flex 0
31+
shoulders: flex 175
32+
elbows: flex 5
2333
pin: hand_left bar
2434
pin: hand_right bar
2535
cue "Lower the legs under control, staying in a steady hang"
2636

37+
step "Release" 0.8s ease-in:
38+
shoulders: flex 0
39+
elbows: flex 0
40+
ground-lock: feet
41+
cue "Drop off the bar and rest"
42+
2743
repeat 8

spec/examples/pull-up.movit

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,13 @@ movit exercise "Pull-up"
33
prop bar
44
pose start = standing
55

6-
step "Hang" 1.5s ease-in-out:
6+
step "Reach" 0.8s ease-out:
7+
shoulders: flex 175
8+
elbows: flex 5
9+
ground-lock: feet
10+
cue "Reach up and grip the bar"
11+
12+
step "Hang" 0.7s ease-in-out:
713
shoulders: flex 175
814
elbows: flex 5
915
pin: hand_left bar
@@ -24,4 +30,10 @@ movit exercise "Pull-up"
2430
pin: hand_right bar
2531
cue "Lower under control back to a full hang"
2632

33+
step "Release" 0.8s ease-in:
34+
shoulders: flex 0
35+
elbows: flex 0
36+
ground-lock: feet
37+
cue "Drop off the bar and rest"
38+
2739
repeat 6

spec/examples/seated-forward-fold.movit

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,17 @@ movit stretch "Seated forward fold"
33
pose start = seated
44

55
step "Fold" 3s ease-in-out:
6-
pelvis: hinge 70
7-
neck: flex 10
8-
reach: hand_left ankle_left
9-
reach: hand_right ankle_right
10-
cue "Hinge forward over the legs and reach toward the feet"
6+
spine: flex 60
7+
chest: flex 25
8+
neck: flex 15
9+
shoulders: flex 60
10+
cue "Hinge forward from the hips over the legs, reaching toward the feet"
1111

1212
step "Rise" 2.5s ease-in-out:
13-
pelvis: hinge 0
13+
spine: flex 0
14+
chest: flex 0
1415
neck: flex 0
16+
shoulders: flex 0
1517
cue "Roll back up to a tall seated spine"
1618

1719
repeat 3

spec/examples/standing-quad-stretch.movit

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ movit stretch "Standing quad stretch"
33
pose start = standing
44

55
step "Catch the foot" 3s ease-in-out:
6-
knee_right: flex 120
7-
hip_right: extend 10
6+
knee_right: flex 144
7+
hip_right: extend 20
88
reach: hand_right ankle_right
99
cue "Bend the right knee back and reach the hand for the ankle"
1010

spec/examples/step-up.movit

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,10 @@ movit exercise "Step-up"
2424
pin: foot_right box
2525
cue "Lower the trailing foot back to the floor"
2626

27+
step "Off the box" 0.8s ease-out:
28+
hip_right: flex 0
29+
knee_right: flex 0
30+
ground-lock: feet
31+
cue "Step the lead foot down to meet it, standing tall on the floor"
32+
2733
repeat 6

0 commit comments

Comments
 (0)