From 97ce5e02db4fb330b4e3fd81336ced9d552a0891 Mon Sep 17 00:00:00 2001 From: Fernando Date: Wed, 22 Jul 2026 02:36:00 -0400 Subject: [PATCH 1/3] feat(t2m): locomotion facing/orientation cull gates (#838) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Walk/run/sit generated "sideways" or face-planted because matchAmong could roll a stylized/quadruped source clip (Fox Warriors, Gynoid, SpongeBob, Spider-Man dive runs) whose locomotion is baked off-axis. The library is rotation-only (root locked at retarget), so a hip baked facing sideways renders as walking sideways; the existing quality gate measured uprightness/ energy but NOT facing direction, so these scored 0.80-0.92 and passed. Add three hip-vector gates, scoped to FORWARD_FACING actions (walk/run/ march) where upright-and-forward is unambiguous — gestures/dance/crouch legitimately lean, and the spine-based topple gate already guards those: - yaw > 25° → faces sideways (clean human ≤10°, stylized rigs 38-153°) - pitch > 30° → diving/lunging (clean ≤17°, superhero dive runs 68-79°) - inverted-frame fraction > 15% → mis-oriented rig renders head-down All reads are on the hip world forward/up vectors: mirror-invariant and rig-agnostic. A signed knee-flex "hyperextension" gate was prototyped and REJECTED — the quat sign isn't mirror-invariant (false-positives on clean left legs) and it caught zero locomotion clips the yaw gate didn't. Library 115 → 109 clips (walk 10, run 8 — all upright/forward now); gestures unaffected. Render-verified: 8/8 walk+run rolls clean, front- facing, proper stride (was ~1/3 sideways before). Co-Authored-By: Claude Opus 4.8 (1M context) --- scripts/build-motion-library-v5.py | 93 ++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/scripts/build-motion-library-v5.py b/scripts/build-motion-library-v5.py index 6b82d849..4a881d00 100644 --- a/scripts/build-motion-library-v5.py +++ b/scripts/build-motion-library-v5.py @@ -185,6 +185,55 @@ def vnorm(v): HIP, ABDOMEN, CHEST, NECK = 0, 1, 2, 3 RHIP, LHIP = 15, 19 +# Forward-locomotion actions where the body must FACE forward. Excludes +# strafes (deliberately angled), turns, and the HORIZONTAL_OK ground actions. +FORWARD_FACING = {"walk", "run", "march", "jog", "sprint"} + + +def _quat_fwd(q): + """World +Z basis rotated by quat q=(x,y,z,w) → world forward vector.""" + x, y, z, w = q + return (2 * (x * z + w * y), 2 * (y * z - w * x), 1 - 2 * (x * x + y * y)) + + +def _mean_hip_yaw_deg(quats): + """Mean absolute yaw (deg off +Z) of the hip's world forward across the + clip. ~0 = faces straight ahead; large = the stride is baked sideways + (the stylized/quadruped-rig locomotion failure).""" + ys = [] + for f in quats: + fx, fy, fz = _quat_fwd(f[HIP]) + ys.append(math.degrees(math.atan2(fx, fz))) + return abs(sum(ys) / len(ys)) if ys else 0.0 + + +def _inverted_frame_fraction(quats): + """Fraction of frames where the hip's world UP-vector Y drops below +0.3 + (tilted past ~70° toward horizontal/upside-down). A clean upright clip + holds ~+1 every frame (fraction ~0); a systematically mis-oriented rig + (persistent axis offset — GIGI Fox, some dance rigs) reads 1.0. A rare + transient loop-seam spike reads a few percent. Mirror-invariant.""" + if not quats: + return 0.0 + bad = 0 + for f in quats: + x, y, z, w = f[HIP] + if 1.0 - 2.0 * (x * x + z * z) < 0.3: # Y of R·(0,1,0) + bad += 1 + return bad / len(quats) + + +def _mean_hip_pitch_deg(quats): + """Mean pitch (deg, +up/−down) of the hip's world forward across the clip. + Same reliable, mirror-invariant hip-forward-vector read as yaw, on the Y + axis. A clean upright walk/run sits within ±17°; a diving/lunging clip + (e.g. a superhero run pitched face-down) reads −70..−80°.""" + ps = [] + for f in quats: + _, fy, _ = _quat_fwd(f[HIP]) + ps.append(math.degrees(math.asin(max(-1.0, min(1.0, fy))))) + return abs(sum(ps) / len(ps)) if ps else 0.0 + def clip_quality(action, quats, rest_world, rest_dir, resolved_roles, mean_energy, min_energy): @@ -343,6 +392,50 @@ def group_energy(roles): f"{[round(u, 2) for u in downdots]}) — zombie/" f"T-pose/raised style, not a generic {action}") + # Directionality gate (#838 follow-up): a forward-locomotion clip whose hip + # faces sideways renders as "walking sideways" once the root is locked at + # retarget. Measured across the corpus, clean human walks/runs sit at + # |mean hip yaw| < 10°, while stylized/quadruped rigs (Fox Warriors, Gynoid, + # SpongeBob) land 38–153°. Cull at 25° — a wide, safe margin in the gap. + # Locomotion facing/orientation gates. Scoped to FORWARD_FACING (walk/run/ + # march) — a locomotion clip must travel upright and forward, so an off-axis + # or inverted hip is unambiguously broken there. Gestures / dance / crouch + # legitimately lean or bend, so these gates would false-positive on them + # (the spine-based topple gate above already protects non-locomotion + # actions from genuine head-below-hips). All three reads are on the hip's + # world-forward/up vectors: mirror-invariant and rig-agnostic. + if action in FORWARD_FACING and len(quats) > 1: + # Sideways: hip faces off +Z. Clean human walks/runs < 10°; stylized/ + # quadruped rigs (Fox, Gynoid, SpongeBob) land 38–153°. Cull at 25°. + yaw = _mean_hip_yaw_deg(quats) + if yaw > 25.0: + return 0.0, (f"faces sideways (mean hip yaw {yaw:.0f}° off " + f"forward) — off-axis {action}, renders sideways") + # Diving/lunging: clip runs face-down. Clean ≤17°; superhero dive + # runs read 68–79°. Cull at 30°. + pitch = _mean_hip_pitch_deg(quats) + if pitch > 30.0: + return 0.0, (f"pitches over (mean hip pitch {pitch:.0f}° off " + f"level) — diving/lunging, not an upright {action}") + # Inverted: the hip tilts past horizontal for a sustained fraction of + # the clip (a systematically mis-oriented rig renders head-down while + # "walking"). Clean clips ~0%; mis-oriented rigs read ~100%. + inv = _inverted_frame_fraction(quats) + if inv > 0.15: + return 0.0, (f"hip tilted/inverted for {inv:.0%} of the clip — " + f"mis-oriented rig, renders wrong for {action}") + + # NOTE: a knee-hyperextension gate was prototyped here (user-reported + # backward-bending knees on the Fox/stylized rigs) but REMOVED. The signed + # knee-flex read from quaternions is not mirror-invariant — the left leg's + # local axis is mirrored on many rigs, so a straight-standing knee reads + # +170° on the right but −30° on the left, producing FALSE positives that + # culled clean Quaternius idle/jump clips. Crucially, every knee-broken + # LOCOMOTION clip (the Fox rigs) is ALREADY caught by the yaw gate above — + # the knee gate added zero unique locomotion coverage. A reliable knee + # check needs joint POSITIONS (bind-relative hinge sign), not bare quats; + # that belongs in the retargeting-math pass (foot-lock / IK), not here. + # Energy band: below = a pose, way above = spasm/mis-mapped. lo, hi, cap = min_energy * 2.0, 0.10, 0.20 if mean_energy <= lo: From 2a20a889af15f62061c2615c291636514fa78981 Mon Sep 17 00:00:00 2001 From: Fernando Date: Wed, 22 Jul 2026 20:34:48 -0400 Subject: [PATCH 2/3] fix(t2m): circular mean for hip-yaw cull gate (review) (#838) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review (chatgpt-codex P2 + coderabbit): _mean_hip_yaw_deg used an arithmetic mean of signed angles, which cancels across the ±180° wrap — a backward/ off-axis walk jittering +179°/−179° averages to ~0° and wrongly PASSES the 25° directionality cull, still rendering backwards/sideways. Switch to a circular mean (atan2 of summed sin/cos): [179,−179] now reads 180° (culled) while genuine forward [1,−1] stays 0° (kept). Pitch is unaffected (asin, bounded ±90°, no wrap). Library output unchanged on the current corpus (no wraparound cases were being missed) — this hardens the gate for future clips. Co-Authored-By: Claude Opus 4.8 (1M context) --- scripts/build-motion-library-v5.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/scripts/build-motion-library-v5.py b/scripts/build-motion-library-v5.py index 4a881d00..880973f8 100644 --- a/scripts/build-motion-library-v5.py +++ b/scripts/build-motion-library-v5.py @@ -197,14 +197,22 @@ def _quat_fwd(q): def _mean_hip_yaw_deg(quats): - """Mean absolute yaw (deg off +Z) of the hip's world forward across the + """|circular mean| yaw (deg off +Z) of the hip's world forward across the clip. ~0 = faces straight ahead; large = the stride is baked sideways - (the stylized/quadruped-rig locomotion failure).""" - ys = [] + (the stylized/quadruped-rig locomotion failure). Uses a CIRCULAR mean + (atan2 of summed sin/cos) — a plain arithmetic mean of signed angles + cancels across the ±180° wrap (a backward walk jittering +179°/−179° + would average to ~0° and wrongly PASS the cull); the circular mean keeps + it near ±180°.""" + if not quats: + return 0.0 + sin_sum = cos_sum = 0.0 for f in quats: fx, fy, fz = _quat_fwd(f[HIP]) - ys.append(math.degrees(math.atan2(fx, fz))) - return abs(sum(ys) / len(ys)) if ys else 0.0 + yaw = math.atan2(fx, fz) + sin_sum += math.sin(yaw) + cos_sum += math.cos(yaw) + return abs(math.degrees(math.atan2(sin_sum, cos_sum))) def _inverted_frame_fraction(quats): From 0d6436b023537a87be3a2a319989bcb0bc7ba534 Mon Sep 17 00:00:00 2001 From: Fernando Date: Wed, 22 Jul 2026 21:05:38 -0400 Subject: [PATCH 3/3] fix(t2m): per-frame abs deviation for yaw+pitch gates (review) (#838) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up review (coderabbit, Major): even the circular-mean yaw still hides OPPOSING bad frames — a clip jittering +90°/−90° yaw has a zero resultant, and pitch +70°/−70° averages to 0° (and stays under the inversion cutoff), so an off-axis clip passes. Fix both gates to take the |deviation| PER FRAME and then average, instead of |aggregate of signed values|: yaw = mean over frames of |atan2(fwd.x, fwd.z)| pitch = mean over frames of |asin(fwd.y)| Verified: +90/−90 yaw → 90° (culled), +70/−70 pitch → 70° (culled), genuine forward +2/−2 → 2° (kept). Current-corpus library unchanged (109 clips) — a robustness hardening. Co-Authored-By: Claude Opus 4.8 (1M context) --- scripts/build-motion-library-v5.py | 40 ++++++++++++++++-------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/scripts/build-motion-library-v5.py b/scripts/build-motion-library-v5.py index 880973f8..f4e51d69 100644 --- a/scripts/build-motion-library-v5.py +++ b/scripts/build-motion-library-v5.py @@ -197,22 +197,21 @@ def _quat_fwd(q): def _mean_hip_yaw_deg(quats): - """|circular mean| yaw (deg off +Z) of the hip's world forward across the - clip. ~0 = faces straight ahead; large = the stride is baked sideways - (the stylized/quadruped-rig locomotion failure). Uses a CIRCULAR mean - (atan2 of summed sin/cos) — a plain arithmetic mean of signed angles - cancels across the ±180° wrap (a backward walk jittering +179°/−179° - would average to ~0° and wrongly PASS the cull); the circular mean keeps - it near ±180°.""" + """Mean PER-FRAME absolute yaw deviation (deg) of the hip's world forward + from +Z. ~0 = every frame faces straight ahead; large = the stride is + baked sideways (the stylized/quadruped-rig locomotion failure). + + We take |yaw| PER FRAME and then average — NOT |average of signed yaw|. + Aggregating first hides opposing bad frames: a clip jittering +90°/−90° + (or the ±180° wrap +179°/−179°) has a zero mean/resultant yet faces + sideways every single frame. Per-frame-abs-then-mean flags both.""" if not quats: return 0.0 - sin_sum = cos_sum = 0.0 + tot = 0.0 for f in quats: fx, fy, fz = _quat_fwd(f[HIP]) - yaw = math.atan2(fx, fz) - sin_sum += math.sin(yaw) - cos_sum += math.cos(yaw) - return abs(math.degrees(math.atan2(sin_sum, cos_sum))) + tot += abs(math.degrees(math.atan2(fx, fz))) + return tot / len(quats) def _inverted_frame_fraction(quats): @@ -232,15 +231,18 @@ def _inverted_frame_fraction(quats): def _mean_hip_pitch_deg(quats): - """Mean pitch (deg, +up/−down) of the hip's world forward across the clip. - Same reliable, mirror-invariant hip-forward-vector read as yaw, on the Y - axis. A clean upright walk/run sits within ±17°; a diving/lunging clip - (e.g. a superhero run pitched face-down) reads −70..−80°.""" - ps = [] + """Mean PER-FRAME absolute pitch deviation (deg) of the hip's world forward + from level. Clean upright walk/run stays within ~17°; a diving/lunging clip + reads 70–80°. As with yaw, take |pitch| PER FRAME then average — averaging + signed pitch first lets +70°/−70° frames cancel to 0° (and slip under the + inversion cutoff too), so an off-axis clip would wrongly pass.""" + if not quats: + return 0.0 + tot = 0.0 for f in quats: _, fy, _ = _quat_fwd(f[HIP]) - ps.append(math.degrees(math.asin(max(-1.0, min(1.0, fy))))) - return abs(sum(ps) / len(ps)) if ps else 0.0 + tot += abs(math.degrees(math.asin(max(-1.0, min(1.0, fy))))) + return tot / len(quats) def clip_quality(action, quats, rest_world, rest_dir, resolved_roles,