Skip to content

feat(t2m): locomotion facing/orientation cull gates (#838)#919

Merged
fernandotonon merged 3 commits into
masterfrom
feat/t2m-locomotion-cull-gate
Jul 23, 2026
Merged

feat(t2m): locomotion facing/orientation cull gates (#838)#919
fernandotonon merged 3 commits into
masterfrom
feat/t2m-locomotion-cull-gate

Conversation

@fernandotonon

@fernandotonon fernandotonon commented Jul 22, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes the walk/run/sit "moves sideways / face-plants" problem reported after the 3.23.0 corpus expansion.

Root cause (measured): the motion library is rotation-only (root locked at retarget), so a source clip whose hip is baked facing sideways renders as walking sideways. matchAmong picks randomly among an action's clips, so ~1/3 of walk/run rolls landed a stylized/quadruped rig (Fox Warriors, Gynoid, SpongeBob, Spider-Man dive runs) with off-axis locomotion. The existing quality gate measured uprightness/energy but not facing direction — so these scored 0.80–0.92 and passed.

Fix

Three hip-vector gates in clip_quality, scoped to FORWARD_FACING actions (walk/run/march) where "upright and forward" is unambiguous (gestures/dance/crouch legitimately lean; the spine-based topple gate already guards those):

Gate Threshold Clean vs broken (measured)
yaw (faces sideways) > 25° human ≤10° · Fox/Gynoid/SpongeBob 38–153°
pitch (diving/lunging) > 30° clean ≤17° · superhero dive runs 68–79°
inverted-frame fraction > 15% clean ~0% · mis-oriented rigs ~100%

All reads are on the hip world forward/up vectors — mirror-invariant and rig-agnostic.

A signed knee-hyperextension gate was prototyped (the user also reported backward-bending knees) and rejected: the quaternion flex sign isn't mirror-invariant (false-positives on clean left legs — dropped good Quaternius idle/jump), and it caught zero locomotion clips the yaw gate didn't already catch. A reliable knee check needs joint positions and belongs in a future retargeting-math (foot-lock/IK) pass.

Result

  • Library 115 → 109 clips. Locomotion cleaned: walk 10, run 8, all upright + forward. Gestures unaffected (shake 5, wave 6, working, pickup, idle 11, jump 12).
  • Render-verified: 8/8 walk+run rolls clean, front-facing, proper alternating stride (was ~1/3 sideways/inverted before).
  • Published to HF (QtMeshEditor-models/motion/, 109 clips live).

Context

Backed by a research pass on how text-to-motion evolved under a permissive-license constraint (AMASS/HumanML3D are non-commercial → no usable pretrained weights; data + retargeting math dominate). This gate is the highest-leverage deterministic win; retargeting-math improvements (root-align, foot-lock) and more clean CC0/CC-BY data (100STYLE is CC-BY + locomotion-dense) are the tracked next steps.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Improved motion clip quality filtering for forward-facing locomotion using hip-orientation measurements (yaw and pitch) derived from motion quaternions.
    • Clips are now rejected when average hip yaw/pitch deviate beyond configured thresholds or when hip inversion/tilt occurs too frequently during the clip.
    • Updated quality rejection messaging to clarify the specific reason a clip fails the forward-facing gate.

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 10bfeb12-ea59-4476-a745-540dc0af592c

📥 Commits

Reviewing files that changed from the base of the PR and between 2a20a88 and 0d6436b.

📒 Files selected for processing (1)
  • scripts/build-motion-library-v5.py

📝 Walkthrough

Walkthrough

Adds quaternion-based hip orientation metrics and applies yaw, pitch, and inversion thresholds to forward-facing locomotion clips in clip_quality.

Changes

Hip Directionality Filtering

Layer / File(s) Summary
Hip orientation metrics
scripts/build-motion-library-v5.py
Adds the FORWARD_FACING action set and helpers for hip forward direction, yaw, pitch, and inverted-frame fraction.
Locomotion directionality gate
scripts/build-motion-library-v5.py
Rejects forward-facing clips exceeding yaw, pitch, or inversion thresholds and documents removal of the knee-hyperextension gate.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding locomotion facing/orientation quality gates.
Description check ✅ Passed The description provides a solid summary, technical rationale, thresholds, results, and context, though it doesn't follow the template headings exactly.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/t2m-locomotion-cull-gate

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7fbee53385

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread scripts/build-motion-library-v5.py Outdated
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use circular yaw before applying the cull

For backward or off-axis locomotion whose hip yaw jitters across the ±180° boundary, this arithmetic mean cancels the angles before the abs() is applied (e.g. +179° and -179° average to 0°), so a clip that should exceed the 25° directionality threshold is kept and can still render as backwards/sideways walking. Compute the circular mean or average angular distance from +Z instead of averaging signed degrees directly.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/build-motion-library-v5.py`:
- Around line 199-207: Update _mean_hip_yaw_deg to compute the hip yaw using a
circular mean of the per-frame atan2 angles, avoiding ±180° branch-cut
cancellation. Remove the unused fy binding from the _quat_fwd result while
preserving the existing degree conversion and empty-input behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 20179a52-6042-4e49-8933-4fcf546416c8

📥 Commits

Reviewing files that changed from the base of the PR and between 70df2b9 and 7fbee53.

📒 Files selected for processing (1)
  • scripts/build-motion-library-v5.py

Comment thread scripts/build-motion-library-v5.py Outdated
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) <noreply@anthropic.com>
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) <noreply@anthropic.com>
@fernandotonon

Copy link
Copy Markdown
Owner Author

Fixed in 2a20a88 — thanks, good catch. _mean_hip_yaw_deg now uses a circular mean (atan2 of summed sin/cos) instead of an arithmetic mean of signed degrees, so the ±180° wraparound no longer cancels: [179°, −179°] → 180° (correctly culled) while genuine-forward [1°, −1°] → 0° (kept). Pitch is unaffected (asin, bounded ±90°, no wrap). Verified against the exact wraparound cases raised here; the current corpus output is unchanged (no clips were being missed), so this is a robustness hardening for future off-axis clips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/build-motion-library-v5.py`:
- Around line 199-215: Update _mean_hip_yaw_deg to compute
abs(math.degrees(math.atan2(fx, fz))) for each frame and return the arithmetic
mean of those per-frame absolute yaw errors, removing the circular sin/cos
aggregation. In scripts/build-motion-library-v5.py lines 234-243, apply the same
per-frame absolute-value averaging to pitch so opposing signed pitch frames do
not cancel; preserve the existing empty-input behavior and thresholds.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e0bbb093-9a3f-433f-8306-0ba6ac362fa7

📥 Commits

Reviewing files that changed from the base of the PR and between 7fbee53 and 2a20a88.

📒 Files selected for processing (1)
  • scripts/build-motion-library-v5.py

Comment thread scripts/build-motion-library-v5.py Outdated
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) <noreply@anthropic.com>
@fernandotonon

Copy link
Copy Markdown
Owner Author

Addressed in the follow-up commit — you're right that even the circular mean hides opposing frames. Both gates now take the per-frame |deviation| then average, not |aggregate|: yaw = mean(|atan2(fwd.x,fwd.z)|), pitch = mean(|asin(fwd.y)|). Verified: +90°/−90° yaw → 90° (culled), +70°/−70° pitch → 70° (culled), genuine forward +2°/−2° → 2° (kept). Current-corpus output unchanged (109 clips).

@sonarqubecloud

Copy link
Copy Markdown

@fernandotonon
fernandotonon merged commit ebd8531 into master Jul 23, 2026
21 checks passed
@fernandotonon
fernandotonon deleted the feat/t2m-locomotion-cull-gate branch July 23, 2026 06:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant