feat(t2m): curate library + Animation picker (#838)#925
Conversation
📝 WalkthroughWalkthroughThe PR adds a searchable QML animation-library picker and extends motion generation with explicit clip selection and optional vertical descent. Motion extraction, library parsing, CLI/MCP flows, retargeting, and tests now carry per-frame root-Y descent data. ChangesAnimation library and vertical descent
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant PropertiesPanel
participant AnimationPickerDialog
participant AnimationControlController
participant MotionLibrary
participant AnimationMerger
PropertiesPanel->>AnimationPickerDialog: open()
AnimationPickerDialog->>AnimationControlController: listMotionClips()
AnimationControlController->>MotionLibrary: load clips
MotionLibrary-->>AnimationPickerDialog: clip metadata
AnimationPickerDialog->>AnimationControlController: generateMotion(variantIndex, verticalDescent)
AnimationControlController->>MotionLibrary: resolve selected clip and rootY
AnimationControlController->>AnimationMerger: applyMotionClip(clipRootY, verticalDescent)
AnimationMerger-->>AnimationControlController: applied animation result
AnimationControlController-->>AnimationPickerDialog: success or error
AnimationPickerDialog-->>PropertiesPanel: applied(animation, entity)
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
b9dd171 to
ec3b495
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b9dd171363
ℹ️ 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".
| lastGeneratedAnim = anim | ||
| setArmSpaceTarget(anim, entity || "") | ||
| } | ||
| refreshAnimData() |
There was a problem hiding this comment.
Move picker callback into animation component scope
When using the new Browse animation library flow and clicking Apply, this applied handler runs in the top-level PropertiesPanel/Loader scope, but lastGeneratedAnim, setArmSpaceTarget, and refreshAnimData are declared inside the animationComponent instance below, not on root. A successful apply therefore hits a QML ReferenceError in this callback instead of wiring the generated clip into the panel/arm-space state; keep the connection in the animation component instance or expose a root-level method that can reach it.
Useful? React with 👍 / 👎.
|
Fixed — good catch. The picker's |
ec3b495 to
9b2d20d
Compare
…x first-frame flip (#838) User review of every template variant (rendered via the --variant curation harness) identified clips to remove and a frame glitch to repair: - License: drop ALL Mixamo-derived clips (MIXAMO_MARKERS). A Sketchfab uploader's CC-BY covers the upload, not Adobe's underlying animation — not redistributable in a standalone library. (Removes climb, which was Mixamo-only.) - Review drop-list (REVIEW_DROP, matched by asset+anim so it survives rebuilds): GIGI & KAI Fox rigs (tip/hunch/invert on every reviewed action), Shar Pei dog (wrong body plan), Square Head "Loose" shake, Samurai dance. - fix_first_frame_flip: Mini Chibi Kid (and similar) export frame 0 with a rotated hip while the rest is upright — a loop-seam artifact. Detect (hip up-Y flipped past horizontal on frame 0 but upright on 1&2) and replace frame 0 with frame 1. Verified: walk/run/jump now open upright; the genuinely-horizontal death clip is correctly left untouched. Library 109 → 96 clips / 20 actions, zero residual Mixamo/Fox/dog sources. Core actions still well-stocked (walk/run/jump 12, punch/death 11, idle 8). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the random quality-weighted pick with an explicit, Mixamo-style
"browse and select" flow — the reliable path — while keeping the free-text
prompt for the experimental AI model only.
- AnimationPickerDialog.qml: a searchable list of every library clip with a
human-readable label ("Walk (Zombie)" vs "Walk (Monkey D. Luffy)") and a
per-row Apply that retargets that EXACT clip onto the selected rig.
- AnimationControlController::listMotionClips() → the list model
{index, action, name, source, quality, frames}; name derives the most
descriptive source segment (Quaternius pack name, else the asset).
- generateMotion() gains variantIndex: ≥0 forces the template path + that
clip (no model, no matchAmong). Wired into the picker's Apply.
- PropertiesPanel Animations section: "Browse animation library…" is now the
primary button; the text field is relabelled as the experimental AI path.
- CLI: `qtmesh anim <file> --generate <action> --variant N` (the curation
harness that rendered the review sheets) — cmdAnimGenerate variantIndex.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
User + measurement (mean upper-arm world up-Y) found four Quaternius packs whose RIGHT upper-arm bone mis-maps onto the canonical skeleton — the arm stays raised/out (mean up-Y positive ~+0.3..+0.7) the whole clip on every action, while a correct hanging arm reads ~−0.7..−0.9. Add them to REVIEW_DROP: "Animated Men Characters" + "Animated Women Characters" (Feb 2019), "Alien Animated" (Apr 2019), "Knight Character Animated" (Jul 2018). NB: matched by FULL pack name — the good "Man/Woman Animated" (Oct/Dec 2017) packs are DIFFERENT releases and are preserved (verified: 6 + 8 clips kept). Side effect: swim (Alien-only) and roll (Knight-only) drop out — their only sources were these packs; re-scrape clean versions later. Library 90 → 74 clips / 18 actions. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…prompt (#838) Two bugs in the picker's first cut: - AnimationPickerDialog imported only PropertiesPanel, so AnimationControlController (registered under the AnimationControl module) resolved to undefined and listMotionClips() returned nothing ("0 animations"). Add `import AnimationControl 1.0`. - generateMotion's empty-prompt guard fired before the variant-index check, so the picker's Apply (which passes "" prompt + an index) failed with "Enter a motion prompt". Skip the guard when variantIndex >= 0. Both verified live: picker lists 74 named clips and Apply retargets the selected clip. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…#838) Review (chatgpt-codex P2): the animation picker's `applied` handler ran in the top-level Loader scope, but lastGeneratedAnim / setArmSpaceTarget / refreshAnimData are declared inside the animationComponent instance — so a successful Apply threw a QML ReferenceError instead of wiring the clip into the panel/arm-space state. Fix: the Loader now just re-emits a root-level `animationPicked(animation, entity)` signal; a Connections{ target: root } INSIDE the animation component handles it, where those members resolve. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Non-locomotion template clips (working/crawl/death/…) rendered "floating" — the retarget locked the hip at the standing pose and discarded the source's hip translation, so a crouch played in place at full height. Extract a per-frame normalized hip-Y track (`rootY`, in source leg-lengths, max hip→foot as the reference length) during canonical extraction, carry it through --dump-canonical → the v5 library builder (windowed, re-based to frame 0, descent clamped to one leg length), and apply it to the root bone's keyframe Y, scaled by the TARGET rig's leg length. The descent is applied in BOTH retarget branches: the bind-referenced path (the one template clips with restDir actually take — root keyframe is a parent-space delta, so the drop is Ct⁻¹·(0,−y,0) along canonical-up mapped to the rig frame) and the legacy standing-pose transport (model / no-restDir clips). Target leg length comes from the bind-pose derived hip→foot distance. Descent-only: only the negative (lowering) component is applied, so it can pull a floating crouch down but never lift a grounded pose — source rigs vary in whether/which-sign they bake hip translation (many author the squat purely in knee/hip joint rotations we already retarget). Scoped to MotionLibrary::isVerticalDescentAction() (pickup/working/sit/crawl/death/ pray); locomotion keeps a flat root. Wired through GUI/CLI/MCP callers + their retime paths. Render-verified on Rumba (Mixamo): `working` hip Y drops 0.96→0.12 (0.85 leg-length crouch); `walk` stays flat (0.06 pelvic sway only). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Curation: add "Rigged and Animated Humanoid" (OpenGameArt) to the
REVIEW_DROP list — it retargets badly on BOTH Mixamo and UniRig skeletons
(user review). Library drops from 63 → 51 clips (18 actions).
- Vertical descent is now user-controllable, since it helps most crouch/
pickup/sit/crawl/death clips but a few author the squat purely in the
joints (no hip translation) and over-sink with it on. Exposed as:
* GUI "Lower body (crouch/pickup)" checkbox in the Generate-from-text
section (default ON), passed as generateMotion's verticalDescent arg
* CLI `--no-descent` on `qtmesh anim --generate`
* MCP `vertical_descent` boolean on generate_motion (default true)
The flag ANDs with isVerticalDescentAction(), so it only ever gates the
non-locomotion actions; locomotion stays flat regardless.
Verified: working with descent ON drops hip Y 0.85; --no-descent keeps it
flat (0.06). "Rigged and Animated Humanoid" no longer appears in the library.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Raw hip-Y translation was unreliable per rig — some Quaternius clips read
the hip RISING during pickup/sit (rig-dependent axis), so descent-only
zeroed them ("pickup not going down"), while working over-sank at the −1.0
clamp ("too far down").
Replace it with a sign-safe CROUCH-DEPTH measure: the hip's height ABOVE the
foot along canonical +Y (always ≥ 0). Standing = the max across the clip;
each frame's rootY is its drop below that (≤ 0) in leg-lengths. A genuine
crouch always lowers the hip toward the planted foot regardless of rig axes,
so pickup/sit now correctly descend. Builder re-anchors the window to its
shallowest frame and applies a 0.6 gain (full-kneel hip-to-foot compression
is nearly a whole leg — 0.6 lands a believable depth).
Verified on Rumba: pickup hip-Y-range 0.06→0.38 (descends), death 0.39,
and the descent toggle (checkbox / --no-descent / vertical_descent) flips
pickup between 0.38 and 0.06 as expected.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…838) Descent-supposed clips that never STAND (crawl, ground-sit) read ~0 crouch depth because the reference "standing" height was the clip's own max hip height — an always-low clip has no tall frame to measure the drop from, so it floated upright in the center. Anchor the crouch depth to the rig's BIND-pose (T-pose) hip-above-foot height instead — an absolute upright reference. Now a crawl that opens and stays low reads its true full depth (0.07 → 0.50 hip drop on Rumba); sit and pickup likewise descend. Fall back to the clip max only if the clip ever stands taller than the bind pose (non-upright bind). Builder no longer re-anchors rootY to the window (that re-zeroed always-low clips); keeps the 0.6 display gain. Curation: drop FNaf_DLC_moon_sun (jumpscare rig) and Low_Poly_Zombie_Game_Animation (weak) per user review. Library now 46 clips. Verified on Rumba: crawl/working/death hip-Y-range ~0.50, pickup 0.38, sit 0.31 — all descend; render confirms crawl stays low instead of floating. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The "Lower body (crouch/pickup)" toggle lived only in the Generate-from-text section, but applying a clip from the Browse-library PICKER hard-coded verticalDescent=true — so toggling the panel checkbox had no effect on picker-applied clips (the likely "checkbox is being ignored" report). Give the picker its own "Lower body (crouch/pickup)" checkbox (default ON) and pass it as generateMotion's verticalDescent arg, so descent is controllable wherever the user applies from. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Samurai-dance clip snapped the model in half — its rig stacks the Spine2 joint BELOW its parent in bind pose, so that bone's canonical bind direction points DOWN the hip→head axis. The aim-based retarget faithfully reproduced a chest folded UNDER the hip. Detect a spine-chain bone (abdomen/chest/neck/neck1, roles 1..4) whose bind direction has a clearly-downward component along the hip→head axis (dot < −0.2) and ZERO its restDir. The retarget's existing `squaredLength() <= 1e-8` guard then skips that bone, so the target holds its upright bind pose while the rest of the clip plays — the spine stays intact. Verified on Rumba: the Samurai dance now reads as a coherent crouching dance with an upright torso instead of a chest-below-hip fold. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ec7c225 to
c0807c5
Compare
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/MCPServer.cpp (1)
4183-4185: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winExpose exact clip selection through MCP.
The MCP path still requires
promptand always usesmatchPrompt(), so callers cannot select a curated variant deterministically.
src/MCPServer.cpp#L4183-L4185: acceptvariant_index, allow it in place ofprompt, force the template path, and bounds-check it before reading the clip.src/MCPServer.cpp#L8859-L8869: publishvariant_indexingenerate_motionand makepromptoptional when an index is supplied.🤖 Prompt for 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. In `@src/MCPServer.cpp` around lines 4183 - 4185, Update src/MCPServer.cpp lines 4183-4185 in the generate_motion handling to accept variant_index as an alternative to prompt, force the template-based selection path when it is provided, and bounds-check the index before accessing the clip; retain prompt validation when neither input is supplied. Update src/MCPServer.cpp lines 8859-8869 to expose variant_index in the generate_motion MCP schema and make prompt optional when variant_index is present.
🧹 Nitpick comments (2)
qml/AnimationPickerDialog.qml (1)
210-227: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winBusy indicator never actually renders —
generateMotionruns synchronously between the set/reset ofbusyIndex.
dialog.busyIndex = idxanddialog.busyIndex = -1bracket a single synchronous call with no yield to the event loop in between, so the "…" busy state on the Apply button (bound tobusyIndex) never gets a chance to paint before the (potentially slow) retarget completes — the UI just appears to freeze with no feedback.🛠️ Proposed fix — defer the call so the busy state can paint first
function applyClip(idx, name) { if (dialog.busyIndex >= 0) return dialog.busyIndex = idx pickStatus.isError = false pickStatus.text = "Applying " + name + "…" - // variantIndex forces this exact clip (template path, no random pick). - // Pass the picker's own descent checkbox (`#838`) as the last arg. - var r = AnimationControlController.generateMotion("", 0.0, false, 0.0, true, idx, - pickDescentChk.checked) - dialog.busyIndex = -1 - if (r && r.ok) { - pickStatus.text = "Applied: " + name - dialog.applied(r.animation || "", r.entity || "") - } else { - pickStatus.isError = true - pickStatus.text = (r && r.error) ? r.error : "Failed to apply." - } + Qt.callLater(function() { + // variantIndex forces this exact clip (template path, no random pick). + // Pass the picker's own descent checkbox (`#838`) as the last arg. + var r = AnimationControlController.generateMotion("", 0.0, false, 0.0, true, idx, + pickDescentChk.checked) + dialog.busyIndex = -1 + if (r && r.ok) { + pickStatus.text = "Applied: " + name + dialog.applied(r.animation || "", r.entity || "") + } else { + pickStatus.isError = true + pickStatus.text = (r && r.error) ? r.error : "Failed to apply." + } + }) }🤖 Prompt for 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. In `@qml/AnimationPickerDialog.qml` around lines 210 - 227, Update applyClip so the generateMotion call executes asynchronously after busyIndex is set, yielding to the event loop so the busy state can render before processing begins. Keep the existing success/error handling and reset busyIndex after generateMotion completes, using the dialog’s established deferred-callback mechanism if available.scripts/build-motion-library-v5.py (1)
694-711: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winStale/contradictory comment on
rootYwindowing.The block comment at Lines 694-697 says the sliced
rootYis carry the per-frame hip Y offset, sliced to the SAME active window as the quats and re-based so frame 0 of the window reads ~0 (the retarget deltas the descent against its start frame), but the code that follows explicitly does the opposite: rootY is a crouch DEPTH vs the rig's BIND-pose standing height (absolute, ≤ 0 leg-lengths), so an always-low crawl/sit keeps its real depth — do NOT re-anchor to the window. Only the window-slice + 0.6 gain are applied — there is no re-basing to zero. A future maintainer reading only the top comment could "fix" this into re-anchoring the descent depth, silently breaking always-low clips (crawl/sit).Update the top-level comment to match the actual (correct) behavior described below it.
📝 Proposed comment fix
- # `#838` vertical descent: carry the per-frame hip Y - # offset, sliced to the SAME active window as the quats - # and re-based so frame 0 of the window reads ~0 (the - # retarget deltas the descent against its start frame). + # `#838` vertical descent: carry the per-frame hip Y + # offset, sliced to the SAME active window as the + # quats. NOT re-based to the window start — rootY is + # an absolute depth vs bind-pose standing height (see + # below), so re-anchoring would zero out clips that + # open already crouched. ry = c.get("rootY")🤖 Prompt for 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. In `@scripts/build-motion-library-v5.py` around lines 694 - 711, Update the top-level comment above the rootY handling in the active window processing to remove the incorrect claim that rootY is re-based to zero. Describe that rootY is sliced to the same window as the quaternions, preserves absolute crouch depth relative to the bind-pose standing height, and receives the existing 0.6 display gain.
🤖 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 `@src/AnimationControlController.cpp`:
- Around line 1840-1846: Update AnimationControlController::listMotionClips() to
emit a ui.action breadcrumb immediately before calling
MotionLibrary::ensureLibraryBlocking(). Record the picker-library operation,
then preserve the existing blocking library acquisition and early-return
behavior.
In `@src/AnimationMerger_test.cpp`:
- Around line 1346-1415: Update the skeleton setup in
VerticalDescentLowersRootDescentOnly so the right and left leg chains are
vertically aligned beneath Hips, removing their horizontal ±0.15 offsets while
preserving the existing arm offsets. Keep the foot positions and test
expectations unchanged so the bind-pose Euclidean leg length is exactly 1.0.
In `@src/AnimationMerger.cpp`:
- Around line 2667-2690: Update the vertical-descent measurement around
derivedYForCanon and the related target-position lookup to use target bind-pose
positions from readTargetBindFrame() and tb.bindPos. Ensure the helper resets
and updates the skeleton to bind pose before reading positions, then restores
the previously applied pose. Compute targetLegLen from bind-pose hip and foot
positions so crouched runtime poses cannot affect scaling.
In `@src/CLIPipeline.cpp`:
- Around line 2405-2410: Update the --variant parsing in the argument-processing
block to validate conversion with QString::toInt’s ok flag and reject negative
indices. For malformed or negative values, emit the existing usage/error
response and terminate with exit code 2; only assign generateVariant and
continue when the supplied value is a valid non-negative integer.
In `@src/MotionLibrary.cpp`:
- Around line 345-356: Add the canonical "crouch" action label to the kDescent
set in MotionLibrary::isVerticalDescentAction, preserving the existing
normalization and classification behavior for all other actions.
In `@src/MotionLibrary.h`:
- Around line 47-52: Update the rootY documentation in the optional
vertical-descent field comment to state that values preserve bind-pose-relative
hip depth rather than being re-based to the selected window’s first frame. Keep
the existing normalization, target-leg-length scaling, root-bone Y application,
and empty-value behavior descriptions unchanged.
---
Outside diff comments:
In `@src/MCPServer.cpp`:
- Around line 4183-4185: Update src/MCPServer.cpp lines 4183-4185 in the
generate_motion handling to accept variant_index as an alternative to prompt,
force the template-based selection path when it is provided, and bounds-check
the index before accessing the clip; retain prompt validation when neither input
is supplied. Update src/MCPServer.cpp lines 8859-8869 to expose variant_index in
the generate_motion MCP schema and make prompt optional when variant_index is
present.
---
Nitpick comments:
In `@qml/AnimationPickerDialog.qml`:
- Around line 210-227: Update applyClip so the generateMotion call executes
asynchronously after busyIndex is set, yielding to the event loop so the busy
state can render before processing begins. Keep the existing success/error
handling and reset busyIndex after generateMotion completes, using the dialog’s
established deferred-callback mechanism if available.
In `@scripts/build-motion-library-v5.py`:
- Around line 694-711: Update the top-level comment above the rootY handling in
the active window processing to remove the incorrect claim that rootY is
re-based to zero. Describe that rootY is sliced to the same window as the
quaternions, preserves absolute crouch depth relative to the bind-pose standing
height, and receives the existing 0.6 display gain.
🪄 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 Plus
Run ID: 8c18f21f-d1b9-40fa-aab1-3ff27e7bdd65
📒 Files selected for processing (15)
qml/AnimationPickerDialog.qmlqml/PropertiesPanel.qmlscripts/build-motion-library-v5.pysrc/AnimationControlController.cppsrc/AnimationControlController.hsrc/AnimationMerger.cppsrc/AnimationMerger.hsrc/AnimationMerger_test.cppsrc/CLIPipeline.cppsrc/CLIPipeline.hsrc/MCPServer.cppsrc/MotionLibrary.cppsrc/MotionLibrary.hsrc/MotionLibrary_test.cppsrc/qml_resources.qrc
- MotionLibrary::isVerticalDescentAction: include "crouch" (was missing, so crouch-labelled clips never descended). - MotionLibrary.h: correct the rootY doc — it preserves BIND-pose-relative depth (not re-based to the window's first frame), so always-low clips stay down. - applyMotionClip legacy path: compute targetLegLen from the BIND pose (readTargetBindFrame + tb.bindPos) instead of live derived positions, so a crouched runtime pose can't skew the descent scale (matches the bind-referenced path). - AnimationMerger_test: make the descent-test legs purely vertical so the bind hip→foot distance is exactly 1.0 (a lateral offset made it ~1.011 and skewed the expected hip delta). - CLIPipeline: reject non-numeric / negative --variant with exit code 2 instead of silently selecting clip 0. - AnimationControlController::listMotionClips: emit a ui.action breadcrumb before the blocking library load (picker instrumentation). - MCP generate_motion: accept variant_index for deterministic clip selection (parity with CLI --variant + the GUI picker); prompt is optional when it's given; bounds-checked. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
VerticalDescentLowersRootDescentOnly failed on Linux CI with "skeleton resolved only 9/22 canonical joints — not a humanoid rig": the minimal 9-bone test skeleton didn't clear applyMotionClip's ~half-of-22 role- resolution gate, so the retarget rejected it before any descent ran. Build a full Mixamo-named humanoid (spine chain + collar/shoulder/elbow/hand + upleg/knee/foot per side, 19 bones) so >11 canonical roles resolve. Legs stay purely vertical → bind hip→foot distance is exactly 1.0, keeping the rootY=-0.5 → -0.5 hip-delta assertion exact. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/MCPServer.cpp (1)
4184-4193: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUse the required MCP breadcrumb category.
This MCP invocation is logged as
ai.assist.text_to_motion; update it toai.tool_calland include the selected variant/action without logging the prompt. As per coding guidelines, MCP invocations must useai.tool_callbreadcrumbs.🤖 Prompt for 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. In `@src/MCPServer.cpp` around lines 4184 - 4193, Update the breadcrumb emitted by the MCP invocation around the variant/prompt validation in the relevant text-to-motion handler: use the required category ai.tool_call instead of ai.assist.text_to_motion, and record the selected variant/action while excluding the prompt from logged data.Source: Coding guidelines
🤖 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.
Nitpick comments:
In `@src/MCPServer.cpp`:
- Around line 4184-4193: Update the breadcrumb emitted by the MCP invocation
around the variant/prompt validation in the relevant text-to-motion handler: use
the required category ai.tool_call instead of ai.assist.text_to_motion, and
record the selected variant/action while excluding the prompt from logged data.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 1ccf577c-4b57-47fe-815d-c08356835053
📒 Files selected for processing (7)
src/AnimationControlController.cppsrc/AnimationMerger.cppsrc/AnimationMerger_test.cppsrc/CLIPipeline.cppsrc/MCPServer.cppsrc/MotionLibrary.cppsrc/MotionLibrary.h
🚧 Files skipped from review as they are similar to previous changes (6)
- src/MotionLibrary.cpp
- src/MotionLibrary.h
- src/AnimationMerger_test.cpp
- src/CLIPipeline.cpp
- src/AnimationControlController.cpp
- src/AnimationMerger.cpp
|



Summary
Stacked on #919 (locomotion cull gate). Two things, both driven by hands-on review of every library variant:
1. Library curation (
build-motion-library-v5.py)Dropped clips by license, bad retarget, and user review:
MIXAMO_MARKERS) — a Sketchfab CC-BY upload doesn't clear Adobe's ToS on the underlying animation; not redistributable.REVIEW_DROP, matched by asset+anim so it survives rebuilds):fix_first_frame_flip— Mini Chibi Kid (and similar) export frame 0 with a rotated hip while the rest is upright (loop-seam artifact); detect + replace frame 0 with frame 1. Verified walk/run/jump open upright; the genuinely-horizontal death clip is left untouched.Library 109 → 74 clips / 18 actions, zero Mixamo/Fox/dog. (swim/roll dropped with the arm-broken packs — re-scrape clean ones later.)
2. Animation picker
Replace the quality-weighted random pick with explicit browse-and-select:
AnimationPickerDialog.qml— searchable list of every clip with a human-readable label ("Walk (Man)", "Run (Zombie)", "Punch (Alien)") and a per-row Apply that retargets that exact clip onto the selected rig.AnimationControlController::listMotionClips()→ the list model;generateMotion(variantIndex)forces a specific clip (no model, no matchAmong).qtmesh anim <file> --generate <action> --variant N(the curation harness that produced the review sheets).Verified live: picker lists 74 named clips and Apply retargets correctly.
🤖 Generated with Claude Code
Summary by CodeRabbit