cozyclay 2.1.1 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +60 -0
- package/README.md +12 -30
- package/bin/agent/agent-routes.mjs +40 -113
- package/bin/agent/agent-runner.mjs +9 -10
- package/bin/agent/cliproxy-provider.mjs +135 -0
- package/bin/agent/credential-store.mjs +30 -7
- package/bin/agent/pi-tools.mjs +4 -1
- package/bin/agent/provider-keys.mjs +32 -0
- package/bin/agent/provider-login.mjs +110 -0
- package/bin/agent/provider-routes.mjs +99 -0
- package/bin/agent/providers.mjs +19 -97
- package/bin/agent/script-tool.mjs +17 -4
- package/bin/agent/studio-motion-reference.mjs +23 -0
- package/bin/agent/studio-prompt.mjs +1 -1
- package/bin/agent/studio-rebase-text.mjs +32 -0
- package/bin/agent/studio-rebase.mjs +26 -39
- package/bin/agent/studio-tools.mjs +9 -28
- package/bin/agent/tool-arguments.mjs +49 -0
- package/bin/agent/turn-frames.mjs +66 -0
- package/bin/codex-auth.mjs +9 -2
- package/bin/cozyclay.mjs +10 -116
- package/bin/live/cli.mjs +3 -3
- package/bin/mcp-runtime.mjs +2 -12
- package/bin/package-signature.mjs +0 -4
- package/dist/app/index.html +7 -6
- package/dist/assets/EXRLoader-CvVIvGVE.js +1 -0
- package/dist/assets/analytics-C9sUnndd.js +1 -0
- package/dist/assets/app-CCh--1Hc.js +5087 -0
- package/dist/assets/app-f-fkV5uP.css +1 -0
- package/dist/assets/compile-worker-CkxiD6lH.js +5 -0
- package/dist/assets/fflate.module-KbGj5-H6.js +5 -0
- package/dist/assets/{first-shot-handoff-msLI71g1.js → first-shot-handoff-CC4xe4HM.js} +1 -1
- package/dist/assets/{landing-ChUvB_zL.js → landing-DjHD8u7k.js} +1 -1
- package/dist/assets/scene-asset-sync-Hx_FzdOH.js +20 -0
- package/dist/assets/scene-asset-sync-_S8NQl5B.css +1 -0
- package/dist/assets/workflow-C4yKH1p4.js +179 -0
- package/dist/cozyclay-package.json +1 -1
- package/dist/index.html +3 -3
- package/dist/sw.js +3 -6
- package/dist/workflow/index.html +5 -4
- package/mcp/LIVE-PROTOCOL.md +7 -47
- package/mcp/README.md +16 -26
- package/mcp/live-hub.mjs +6 -10
- package/mcp/package.json +1 -3
- package/mcp/tool-handlers.mjs +4 -168
- package/package.json +3 -22
- package/src/App.jsx +150 -2624
- package/src/agent-progress.js +79 -0
- package/src/agent-turn-mark.js +30 -0
- package/src/analytics.js +1 -19
- package/src/app-context.js +5 -0
- package/src/app-stage.jsx +2 -34
- package/src/asset-pane.jsx +6 -5
- package/src/camera-block.js +21 -0
- package/src/camera-direction.js +120 -0
- package/src/character-kind.js +3 -9
- package/src/command-bus.js +8 -6
- package/src/commands/cast.js +5 -34
- package/src/commands/elements/character.js +1 -1
- package/src/commands/elements/motion.js +1 -6
- package/src/commands/elements/object.js +6 -2
- package/src/commands/elements/shot.js +4 -1
- package/src/commands/elements.js +38 -14
- package/src/commands/index.js +3 -1
- package/src/commands/kits.js +37 -0
- package/src/commands/motion-timing.js +57 -0
- package/src/commands/motion.js +9 -152
- package/src/commands/motion3-compile.js +65 -0
- package/src/commands/motion3.js +207 -0
- package/src/commands/objects.js +6 -4
- package/src/commands/shot.js +49 -11
- package/src/commands/stage.js +14 -1
- package/src/commands/view.js +4 -1
- package/src/document-mirrors.js +42 -0
- package/src/document-store.js +4 -0
- package/src/domains/cast.js +19 -194
- package/src/domains/motion/motion-range-pin-rules.js +1 -1
- package/src/domains/motion/take-source.js +51 -0
- package/src/domains/motion.js +60 -1838
- package/src/domains/scenes.js +12 -55
- package/src/domains/shots.js +2 -2
- package/src/domains/stage.js +4 -2
- package/src/draft-ghost.js +46 -0
- package/src/draft-ghost.jsx +129 -0
- package/src/dualview.jsx +4 -3
- package/src/effects/EffectsLayer.jsx +112 -0
- package/src/effects/effect-meshes.js +232 -0
- package/src/effects/effect-record.js +103 -0
- package/src/effects/emitter.js +59 -0
- package/src/effects/simulate.js +261 -0
- package/src/effects/state.js +59 -0
- package/src/execution-telemetry.js +2 -3
- package/src/first-success-guide.jsx +5 -5
- package/src/ghost-set.js +257 -0
- package/src/held-object-edit.jsx +113 -0
- package/src/held-object-pose.js +175 -0
- package/src/hierarchy-model.js +1 -1
- package/src/hierarchy-panel.jsx +1 -1
- package/src/imported-mesh.jsx +103 -0
- package/src/kits/held.js +84 -0
- package/src/kits/index.js +70 -0
- package/src/kits/smoking.js +57 -0
- package/src/light-blockers.js +93 -0
- package/src/motion/beat-board-model.js +84 -0
- package/src/motion/beat-board.css +124 -0
- package/src/motion/beat-board.jsx +59 -0
- package/src/motion/center-of-mass.js +66 -0
- package/src/motion/fix-collisions.js +850 -0
- package/src/motion/physics-review.js +118 -0
- package/src/{ardy → motion}/physics-support.js +1 -76
- package/src/motion/rig-snapshot.js +22 -0
- package/src/motion/take-segments.jsx +140 -0
- package/src/{ardy → motion}/timeline-coordinates.js +0 -21
- package/src/{ardy → motion}/timeline.css +1 -2
- package/src/{ardy → motion}/timeline.jsx +42 -413
- package/src/motion/waypoints.js +76 -0
- package/src/motion3/beats.js +83 -0
- package/src/motion3/body.js +158 -0
- package/src/motion3/bones.js +47 -0
- package/src/motion3/carried.js +47 -0
- package/src/motion3/compile-job.js +75 -0
- package/src/motion3/compile-worker.js +10 -0
- package/src/motion3/compiler/anchors.js +105 -0
- package/src/motion3/compiler/arm-orient.js +128 -0
- package/src/motion3/compiler/channels.js +125 -0
- package/src/motion3/compiler/compile.js +205 -0
- package/src/motion3/compiler/gait-rhythm.js +97 -0
- package/src/motion3/compiler/grip.js +171 -0
- package/src/motion3/compiler/head-steady.js +48 -0
- package/src/motion3/compiler/hold.js +134 -0
- package/src/motion3/compiler/idle.js +113 -0
- package/src/motion3/compiler/limbs.js +170 -0
- package/src/motion3/compiler/math.js +72 -0
- package/src/motion3/compiler/pose.js +162 -0
- package/src/motion3/compiler/quat.js +49 -0
- package/src/motion3/compiler/skeleton.js +74 -0
- package/src/motion3/compiler/validate-common.js +32 -0
- package/src/motion3/compiler/validate-goals.js +118 -0
- package/src/motion3/compiler/validate-objects.js +143 -0
- package/src/motion3/compiler/validate-parts.js +116 -0
- package/src/motion3/compiler/validate.js +122 -0
- package/src/motion3/compiler/vocab.js +73 -0
- package/src/motion3/compiler/walk-pelvis.js +275 -0
- package/src/motion3/compiler/walk.js +207 -0
- package/src/motion3/compiler/wrist-clamp.js +32 -0
- package/src/motion3/delta.js +47 -0
- package/src/motion3/describe.js +174 -0
- package/src/motion3/docs.js +27 -0
- package/src/motion3/edit-carry.js +21 -0
- package/src/motion3/fix/ease.js +16 -0
- package/src/motion3/fix/ground.js +37 -0
- package/src/motion3/fix/index.js +52 -0
- package/src/motion3/fix/push.js +71 -0
- package/src/motion3/fix/reach.js +46 -0
- package/src/motion3/fix/retime.js +79 -0
- package/src/motion3/fix/self.js +211 -0
- package/src/motion3/fix/slide.js +70 -0
- package/src/motion3/fix/turn.js +51 -0
- package/src/motion3/follow.js +39 -0
- package/src/motion3/free-motion.js +173 -0
- package/src/motion3/grip-choice.js +130 -0
- package/src/motion3/hold-plan.js +157 -0
- package/src/motion3/json-patch.js +77 -0
- package/src/motion3/key-aim.js +105 -0
- package/src/motion3/key-edit.js +82 -0
- package/src/motion3/lenient.js +127 -0
- package/src/motion3/object-keys.js +236 -0
- package/src/motion3/object-pose.js +182 -0
- package/src/motion3/object-tracks.js +12 -0
- package/src/motion3/pipeline.js +102 -0
- package/src/motion3/playback.js +51 -0
- package/src/motion3/probes.js +91 -0
- package/src/motion3/qa/body-checks.js +34 -0
- package/src/motion3/qa/check-names.js +14 -0
- package/src/motion3/qa/checks.js +121 -0
- package/src/motion3/qa/contact-checks.js +51 -0
- package/src/motion3/qa/explain.js +80 -0
- package/src/motion3/qa/fmt.js +5 -0
- package/src/motion3/qa/foot-checks.js +72 -0
- package/src/motion3/qa/geometry.js +77 -0
- package/src/motion3/qa/goals.js +191 -0
- package/src/motion3/qa/ground.js +60 -0
- package/src/motion3/qa/hints-contact.js +179 -0
- package/src/motion3/qa/hints-goal.js +160 -0
- package/src/motion3/qa/hints-knee.js +24 -0
- package/src/motion3/qa/hints-motion.js +209 -0
- package/src/motion3/qa/hints-object.js +91 -0
- package/src/motion3/qa/index.js +42 -0
- package/src/motion3/qa/key-checks.js +82 -0
- package/src/motion3/qa/near.js +96 -0
- package/src/motion3/qa/object-checks.js +135 -0
- package/src/motion3/qa/patch.js +161 -0
- package/src/motion3/qa/severity.js +38 -0
- package/src/motion3/qa/spec-checks.js +65 -0
- package/src/motion3/qa/style.js +218 -0
- package/src/motion3/qa/wrist-checks.js +69 -0
- package/src/motion3/qa/wrist-obs.js +60 -0
- package/src/motion3/refs.js +120 -0
- package/src/motion3/repair-tune.js +121 -0
- package/src/motion3/repair.js +83 -0
- package/src/motion3/report.js +67 -0
- package/src/motion3/rigs/index.js +14 -0
- package/src/motion3/rigs/x-bot-tpose-body.json +1 -0
- package/src/motion3/rigs/x-bot-tpose.json +1 -0
- package/src/motion3/rigs/y-bot-tpose-body.json +1 -0
- package/src/motion3/rigs/y-bot-tpose.json +1 -0
- package/src/motion3/scene.js +60 -0
- package/src/motion3/schema/brief.js +89 -0
- package/src/motion3/schema/fields.js +44 -0
- package/src/motion3/schema/spec-examples.js +46 -0
- package/src/motion3/schema/topics.js +111 -0
- package/src/motion3/schema/types.js +147 -0
- package/src/motion3/search.js +117 -0
- package/src/motion3/second-hand.js +112 -0
- package/src/motion3/skills/basic.js +82 -0
- package/src/motion3/skills/book.js +76 -0
- package/src/motion3/skills/car.js +145 -0
- package/src/motion3/skills/frames-auto.js +29 -0
- package/src/motion3/skills/index.js +83 -0
- package/src/motion3/skills/info.js +65 -0
- package/src/motion3/skills/smoke-kit.js +109 -0
- package/src/motion3/skills/smoke.js +160 -0
- package/src/motion3/spec-store.js +59 -0
- package/src/motion3/take-edit.js +155 -0
- package/src/motion3/take.js +63 -0
- package/src/motion3/timeline.js +127 -0
- package/src/motion3/views.js +121 -0
- package/src/motion3/walk-plan.js +119 -0
- package/src/object-catalog.jsx +7 -1
- package/src/object-colors.js +98 -0
- package/src/object-gizmo-parts.js +115 -0
- package/src/object-gizmo.jsx +37 -122
- package/src/object-glow.js +139 -0
- package/src/object-hidden.js +33 -0
- package/src/object-look.js +196 -0
- package/src/object-size.js +10 -0
- package/src/object-travel.js +71 -6
- package/src/panels/CameraPanel.jsx +7 -7
- package/src/panels/HdrEnvironmentControls.jsx +59 -0
- package/src/panels/LightPanel.jsx +25 -4
- package/src/panels/ObjectLookPanel.jsx +88 -0
- package/src/panels/PosePanel.jsx +2 -17
- package/src/panels/RigControlPanel.jsx +5 -53
- package/src/panels/SubjectBox.jsx +4 -1
- package/src/panels/details.css +14 -0
- package/src/posestudio.jsx +4 -27
- package/src/project-browser.jsx +3 -4
- package/src/project-resources.js +18 -31
- package/src/project.js +13 -104
- package/src/props.jsx +25 -280
- package/src/rail-dot-drag.js +23 -0
- package/src/render-passes.js +10 -0
- package/src/resource-status.jsx +1 -0
- package/src/result-modal.jsx +4 -19
- package/src/room.jsx +20 -12
- package/src/scene-assets.js +8 -6
- package/src/scene-light-place.js +72 -0
- package/src/scene-lights.jsx +148 -0
- package/src/scene-objects.js +31 -139
- package/src/scenes.js +15 -105
- package/src/semantic-edit.js +2 -5
- package/src/set-pieces.jsx +183 -0
- package/src/settings-menu.jsx +3 -11
- package/src/shell/BottomDock.jsx +9 -51
- package/src/shell/DetailsSlot.jsx +16 -200
- package/src/shell/GhostsMenuGroup.jsx +74 -0
- package/src/shell/LibrarySlot.jsx +1 -3
- package/src/shell/MenuBar.jsx +3 -3
- package/src/shell/PreferencesDialog.jsx +15 -264
- package/src/shell/PreferencesSlot.jsx +2 -8
- package/src/shell/TopBar.jsx +5 -84
- package/src/shell/ViewportToolbar.jsx +10 -70
- package/src/shell/agent-glass.css +19 -6
- package/src/shell/details-selection.js +42 -0
- package/src/shell/ghosts-menu.css +22 -0
- package/src/shell/glass-regions.css +29 -3
- package/src/shell/preferences.css +6 -1
- package/src/shot-authoring.js +4 -7
- package/src/soft-shadows.js +37 -0
- package/src/stage-attach-frames.js +12 -12
- package/src/stage-canvas-rigs.jsx +3 -3
- package/src/stage-capture.jsx +42 -8
- package/src/stage-characters.jsx +22 -216
- package/src/stage-editor-gizmos.jsx +6 -6
- package/src/stage-environment.js +93 -0
- package/src/stage-hdr-builtins.js +107 -0
- package/src/stage-hdr-image.js +116 -0
- package/src/stage-hdr-library.js +70 -0
- package/src/stage-hdr-map.js +94 -0
- package/src/stage-hdr.js +130 -0
- package/src/stage-lighting.js +149 -0
- package/src/stage-occlusion.js +210 -0
- package/src/stage-sky.jsx +159 -0
- package/src/stage-startup-defaults.js +5 -9
- package/src/studio-actions.js +12 -14
- package/src/studio-agent-commands.js +19 -16
- package/src/studio-agent-motion.js +9 -9
- package/src/studio-agent-protocol.js +41 -71
- package/src/studio-app-binding.js +25 -45
- package/src/studio-asset-list.js +17 -0
- package/src/studio-contact-sheet.js +37 -5
- package/src/studio-elements.js +27 -7
- package/src/studio-entity-bounds.js +18 -0
- package/src/studio-error-codes.js +8 -0
- package/src/studio-export-framing.js +152 -0
- package/src/studio-lighting-check.js +103 -0
- package/src/studio-motion-visual.js +150 -0
- package/src/studio-schema-errors.js +80 -0
- package/src/studio-support-gap.js +74 -0
- package/src/styles.css +1 -19
- package/src/timeline-defaults.js +5 -0
- package/src/timeline-extent.js +8 -20
- package/src/tutorial-walk.js +17 -0
- package/src/ui-scrub.js +1 -1
- package/src/use-shot-camera-tracks.js +12 -0
- package/src/view-ghosts.js +97 -0
- package/src/view-ghosts.jsx +49 -0
- package/src/workflow/AgentPanel.jsx +47 -166
- package/src/workflow/AgentProgressCard.jsx +107 -0
- package/src/workflow/AgentReplyText.jsx +37 -0
- package/src/workflow/AgentTurnSteps.jsx +84 -0
- package/src/workflow/LoginPanel.jsx +140 -0
- package/src/workflow/WorkflowBuilder.jsx +7 -23
- package/src/workflow/agent-client.js +54 -51
- package/src/workflow/agent-panel.css +71 -1
- package/src/workflow/agent-progress-card.css +167 -0
- package/src/workflow/agent-progress-view.js +47 -0
- package/src/workflow/agent-turn-steps.css +113 -0
- package/src/workflow/agent-turn.js +191 -0
- package/src/workflow/login-client.js +34 -0
- package/src/workflow/motion-input.js +18 -31
- package/src/workflow/node-schema.js +1 -1
- package/src/workflow/reply-markdown.js +69 -0
- package/src/workflow/scene-asset-sync.js +8 -8
- package/tools/agent-session-stats.mjs +61 -0
- package/tools/dev-full.mjs +5 -56
- package/tools/eslint-baseline.json +12 -13
- package/tools/knip-baseline.json +0 -179
- package/tools/loc-baseline.json +31 -81
- package/tools/motion3/extract-body.mjs +103 -0
- package/tools/motion3/extract-rig.mjs +85 -0
- package/tools/process-supervisor.mjs +0 -88
- package/tools/run-tests.mjs +8 -124
- package/bin/agent/motion-runtime.mjs +0 -76
- package/dist/ardy/cskel27-rest.json +0 -557
- package/dist/assets/analytics-C9pMpW7s.js +0 -1
- package/dist/assets/app-B_2XB9rw.css +0 -1
- package/dist/assets/app-DAuvKPP8.js +0 -4920
- package/dist/assets/config-Bx7pYsD9.js +0 -1
- package/dist/assets/config-DeoFTYzb.css +0 -1
- package/dist/assets/demo-CvKuBgy5.js +0 -1
- package/dist/assets/scene-asset-sync-DKkpjKBV.js +0 -19
- package/dist/assets/scene-asset-sync-DjJ_cJ2R.css +0 -1
- package/dist/assets/ticket-BPk2Ni95.js +0 -1
- package/dist/assets/workflow-jJRBR94Z.js +0 -179
- package/dist/demo/walk-then-stop.npz +0 -0
- package/mcp/ardy-prompts.mjs +0 -218
- package/src/ardy/auto-fix-panel.css +0 -108
- package/src/ardy/auto-physics.js +0 -1194
- package/src/ardy/client.js +0 -153
- package/src/ardy/convert.js +0 -313
- package/src/ardy/cskel27-neutral.js +0 -39
- package/src/ardy/cskel27.js +0 -77
- package/src/ardy/export.js +0 -165
- package/src/ardy/fix-collisions.js +0 -2257
- package/src/ardy/foot-lock.js +0 -230
- package/src/ardy/motion-calibration.js +0 -118
- package/src/ardy/motion-edit.js +0 -213
- package/src/ardy/motion-url.js +0 -55
- package/src/ardy/mp4-duration.js +0 -102
- package/src/ardy/npz.js +0 -595
- package/src/ardy/physics-panel.css +0 -21
- package/src/ardy/physics-panel.jsx +0 -89
- package/src/ardy/physics-refine.js +0 -111
- package/src/ardy/physics-review.js +0 -567
- package/src/ardy/physics-temporal.js +0 -54
- package/src/ardy/platform-fit-panel.css +0 -64
- package/src/ardy/platform-fit-panel.jsx +0 -41
- package/src/ardy/platform-fit.js +0 -326
- package/src/ardy/playback.js +0 -711
- package/src/ardy/pose-pin.js +0 -97
- package/src/ardy/prompt-clips.js +0 -30
- package/src/ardy/retime.js +0 -211
- package/src/ardy/root-drop.js +0 -369
- package/src/ardy/timeline-resize.js +0 -13
- package/src/ardy/to-cskel27.js +0 -225
- package/src/ardy/trim.js +0 -33
- package/src/ardy/waypoints.js +0 -369
- package/src/ardy-bridge-contract.js +0 -281
- package/src/domains/motion/motion-video-capture.js +0 -664
- package/src/i2v-motion-client.js +0 -77
- package/src/i2v-motion-studio.jsx +0 -180
- package/src/line-edit.js +0 -1348
- package/src/motion/generation.js +0 -105
- package/src/motion-readiness-ui.jsx +0 -67
- package/src/motion-readiness.js +0 -40
- package/src/motion-resources.js +0 -177
- package/src/motion-store.js +0 -65
- package/src/motion-trail.js +0 -601
- package/src/multimodel-ingest.js +0 -394
- package/src/panels/PromptBlocksPanel.jsx +0 -444
- package/src/panels/VideoCapturePanel.jsx +0 -181
- package/src/pose-extract/browser.js +0 -42
- package/src/pose-extract/detector.js +0 -100
- package/src/pose-extract/fit.js +0 -227
- package/src/pose-extract/image-frame.js +0 -51
- package/src/pose-extract/index.js +0 -58
- package/src/pose-extract/keyframes.js +0 -74
- package/src/pose-extract/landmarks.js +0 -162
- package/src/pose-extract/mirror.js +0 -153
- package/src/pose-extract/take.js +0 -131
- package/src/pose-extract/video-frames.js +0 -91
- package/src/take-recipe.js +0 -165
- package/src/trail-key-conflicts.js +0 -39
- package/src/trail-pick.js +0 -59
- package/src/use-i2v-capture-modal.js +0 -15
- package/tools/ardy/BRIDGE.md +0 -37
- package/tools/ardy/GVHMR-NPZ.md +0 -68
- package/tools/ardy/README.md +0 -124
- package/tools/ardy/__pycache__/cclay_gvhmr_worker.cpython-313.pyc +0 -0
- package/tools/ardy/artifacts.mjs +0 -34
- package/tools/ardy/bridge.mjs +0 -1732
- package/tools/ardy/bvh-cskel27.mjs +0 -1265
- package/tools/ardy/cclay_constrained_generate.py +0 -561
- package/tools/ardy/cclay_gvhmr_worker.py +0 -264
- package/tools/ardy/cclay_motion_edit.py +0 -510
- package/tools/ardy/cclay_sequence_generate.py +0 -705
- package/tools/ardy/cclay_worker.py +0 -304
- package/tools/ardy/dump-npz.py +0 -205
- package/tools/ardy/extract-rest.mjs +0 -299
- package/tools/ardy/extract.mjs +0 -507
- package/tools/ardy/footage.mjs +0 -467
- package/tools/ardy/gvhmr-floor.mjs +0 -102
- package/tools/ardy/gvhmr-to-cskel27.mjs +0 -21
- package/tools/ardy/gvhmr_fastpath.py +0 -168
- package/tools/ardy/gvhmr_trajectory.py +0 -240
- package/tools/ardy/merge-text-encoder.py +0 -160
- package/tools/ardy/mocap-metrics.mjs +0 -200
- package/tools/ardy/mocap-quality-gate.mjs +0 -110
- package/tools/ardy/motion-stabilize.mjs +0 -348
- package/tools/ardy/npz.mjs +0 -414
- package/tools/ardy/pose-to-npz.mjs +0 -106
- package/tools/ardy/prompt-limits.mjs +0 -5
- package/tools/ardy/run-edit-on-box.sh +0 -73
- package/tools/ardy/run-local.mjs +0 -233
- package/tools/ardy/run-on-box.sh +0 -593
- package/tools/ardy/run-sequence-on-box.sh +0 -177
- package/tools/ardy/runners/gvhmr-worker.mjs +0 -206
- package/tools/ardy/runners/index.mjs +0 -18
- package/tools/ardy/runners/local.mjs +0 -455
- package/tools/ardy/runners/proc.mjs +0 -125
- package/tools/ardy/runners/remote.mjs +0 -252
- package/tools/ardy/setup-local.mjs +0 -189
- package/tools/ardy/setup-text-encoder-on-box.sh +0 -93
- package/tools/ardy/setup-text-encoder.py +0 -306
- package/tools/ardy/smpl-cskel27.mjs +0 -178
- package/tools/ardy/visual-qa.mjs +0 -196
- package/tools/ardy/vq-car.mjs +0 -52
- package/tools/bench/EXP3.md +0 -61
- package/tools/bench/cclay_bench_extract_incam.py +0 -125
- package/tools/bench/cclay_bench_extract_obs.py +0 -204
- package/tools/bench/cclay_bench_runner.py +0 -42
- package/tools/bench/cube-contact.mjs +0 -162
- package/tools/bench/exp3.mjs +0 -152
- package/tools/bench/extract-bench-lib.mjs +0 -216
- package/tools/bench/extract-bench.mjs +0 -302
- package/tools/bench/fal-generate.mjs +0 -88
- package/tools/bench/fit/README.md +0 -189
- package/tools/bench/fit/camera.mjs +0 -59
- package/tools/bench/fit/contact.mjs +0 -419
- package/tools/bench/fit/footlock.mjs +0 -234
- package/tools/bench/fit/motion.mjs +0 -85
- package/tools/bench/fit/pin.mjs +0 -27
- package/tools/bench/fit/remote.mjs +0 -105
- package/tools/bench/fit-bench.mjs +0 -126
- package/tools/bench/fit-sanity.mjs +0 -45
- package/tools/bench/metrics.mjs +0 -313
- package/tools/bench/obs/depth.mjs +0 -258
- package/tools/bench/obs/extrinsics.mjs +0 -175
- package/tools/bench/obs/fit_mannequin_betas.py +0 -340
- package/tools/bench/obs/ground.mjs +0 -294
- package/tools/bench/obs/heading.mjs +0 -151
- package/tools/bench/obs/ladder.mjs +0 -555
- package/tools/bench/obs/mannequin-betas.json +0 -124
- package/tools/bench/obs/remote.mjs +0 -83
- package/tools/bench/obs/rest_joints.py +0 -52
- package/tools/bench/obs/ybot-targets.mjs +0 -49
- package/tools/bench/obs-bench.mjs +0 -520
- package/tools/bench/score.mjs +0 -343
- package/tools/bench/summarize.mjs +0 -86
- package/tools/demo-worker/README.md +0 -137
- package/tools/demo-worker/api-client.mjs +0 -233
- package/tools/demo-worker/generate.mjs +0 -301
- package/tools/demo-worker/index.mjs +0 -466
- package/tools/demo-worker/sign-core.mjs +0 -112
- package/tools/demo-worker/sign.mjs +0 -161
- package/tools/gt-render/browser.mjs +0 -182
- package/tools/gt-render/camera-math.mjs +0 -276
- package/tools/gt-render/page.mjs +0 -267
- package/tools/gt-render/render.mjs +0 -514
- package/tools/gt-render/scene-box.mjs +0 -31
- package/tools/gt-render/take-transform.mjs +0 -87
- package/tools/kimodo/compare-seam.mjs +0 -108
- package/tools/kimodo/constraints.mjs +0 -244
- package/tools/kimodo/edit.mjs +0 -259
- package/tools/kimodo/effector-constraints.mjs +0 -200
- package/tools/kimodo/generate.mjs +0 -646
- package/tools/kimodo/local-output.mjs +0 -265
- package/tools/kimodo/measure-pose.mjs +0 -65
- package/tools/kimodo/measure-preserve.mjs +0 -899
- package/tools/kimodo/measure-waypoints.mjs +0 -55
- package/tools/kimodo/mlx-generate.py +0 -111
- package/tools/kimodo/pose-constraints.mjs +0 -248
- package/tools/kimodo/preserve-mask.mjs +0 -543
- package/tools/kimodo/read-npz.mjs +0 -189
- package/tools/kimodo/run-edit-on-box.mjs +0 -155
- package/tools/kimodo/run-sequence-on-box.mjs +0 -178
- package/tools/kimodo/runner.mjs +0 -284
- package/tools/kimodo/seam-batch.mjs +0 -77
- package/tools/kimodo/setup-local.mjs +0 -30
- package/tools/kimodo/setup-on-box.sh +0 -339
- package/tools/kimodo/soma77-to-cskel27.mjs +0 -305
- package/tools/morphgs/assets/gen_truth.py +0 -39
- package/tools/morphgs/assets/mesh_ori_rig.txt +0 -27
- package/tools/morphgs/assets/playback-check.mjs +0 -30
- package/tools/morphgs/demo-gate.sh +0 -26
- package/tools/morphgs/fbx2morphgs.mjs +0 -68
- package/tools/morphgs/morphgs-to-cskel27.mjs +0 -105
- package/tools/morphgs/patches/preprocess_src-none-mode.patch +0 -76
- package/tools/morphgs/setup-on-cluster.sh +0 -126
- package/tools/projflow/driver.py +0 -1069
- package/tools/projflow/generate.mjs +0 -805
- package/tools/projflow/hml22-to-cskel27.mjs +0 -735
- package/tools/projflow/line-edit-job.mjs +0 -547
- package/tools/projflow/replay.mjs +0 -452
- package/tools/projflow/runner.mjs +0 -269
- package/tools/projflow/service.mjs +0 -742
- package/tools/track/DESIGN.md +0 -513
- package/tools/track/backfill-provenance.mjs +0 -116
- package/tools/track/budget.mjs +0 -8
- package/tools/track/check-rig.mjs +0 -209
- package/tools/track/diagnostics.schema.json +0 -60
- package/tools/track/export-rig.mjs +0 -256
- package/tools/track/fallback.mjs +0 -7
- package/tools/track/fk-parity-fixture.mjs +0 -161
- package/tools/track/gate.mjs +0 -349
- package/tools/track/masks.mjs +0 -318
- package/tools/track/metrics.mjs +0 -201
- package/tools/track/publish-obs.mjs +0 -108
- package/tools/track/py/check_env.py +0 -61
- package/tools/track/py/eval_lr.py +0 -229
- package/tools/track/py/lr_viterbi.py +0 -294
- package/tools/track/py/masks.py +0 -405
- package/tools/track/py/objective.py +0 -382
- package/tools/track/py/rig.py +0 -508
- package/tools/track/py/scene.py +0 -326
- package/tools/track/py/test_joint_indices.py +0 -123
- package/tools/track/py/test_lr_viterbi.py +0 -195
- package/tools/track/py/test_masks.py +0 -229
- package/tools/track/py/test_rig.py +0 -288
- package/tools/track/py/test_scene.py +0 -354
- package/tools/track/py/test_track.py +0 -328
- package/tools/track/py/track.py +0 -411
- package/tools/track/remote.mjs +0 -140
- package/tools/track/rig-dump.mjs +0 -227
- package/tools/track/run-box-tests.mjs +0 -14
- package/tools/track/run-box.mjs +0 -168
- package/tools/track/setup-box.sh +0 -59
- package/tools/track/study-2d.mjs +0 -728
- /package/src/{ardy → motion}/collision-blockers.js +0 -0
- /package/src/{ardy → motion}/ground.js +0 -0
- /package/src/{ardy → motion}/ik-drag.js +0 -0
- /package/src/{ardy → motion}/ik-key-json.js +0 -0
- /package/src/{ardy → motion}/ik.js +0 -0
- /package/src/{ardy → motion}/physics-surface.js +0 -0
- /package/src/{ardy → motion}/playback-clock.js +0 -0
- /package/src/{ardy → motion}/range-pin.js +0 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
// C9. motion.describe { topic, name? }: the spec's schema, a valid example and notes. The field
|
|
2
|
+
// lists come from schema/fields.js, their types from schema/types.js, channels from
|
|
3
|
+
// compiler/vocab.js, FIX_KEYS from fix/index.js, the skills from skills/index.js and
|
|
4
|
+
// skills/info.js; examples and notes from schema/topics.js. Every answer fits DESCRIBE_BUDGET.
|
|
5
|
+
// topic "essentials" { topic, types: { <topic or shared shape>: type line }, skills: [{ name, args (type line), needs, frames, adds }], notes }
|
|
6
|
+
// (the lines of MEANT parts carry each field's meaning; SHALLOW topics name their parts only)
|
|
7
|
+
// other topic { topic, schema (JSON Schema 2020-12 fragment), example, notes }
|
|
8
|
+
// topic "skills" { topic, skills: [{ name, args, needs, frames, adds }] }
|
|
9
|
+
// topic "skills", name { topic, name, schema (the skill's args), example (a use entry), notes }
|
|
10
|
+
// topic "examples" { topic, examples: [{ name, scene, spec }] (whole specs, schema/spec-examples.js), notes }
|
|
11
|
+
// motion.describe { topics: [{ topic, name? }] } (describeMany): { topics: [those answers, in order], more?: [the asks that
|
|
12
|
+
// did not fit] }, so a request's topics cost one call; the Studio prompt already carries essentials and goals.
|
|
13
|
+
import { SPEC_FIELDS, KEY_FIELDS, USE_FIELDS, ANCHOR_FIELDS, PROP_FIELDS, FIT_FIELDS, OBJECT_FIELDS, WALK_FIELDS, CHECK_FIELDS, CHECK_ENTRY_FIELDS,
|
|
14
|
+
GOAL_FIELDS, PROBE_FIELDS, REF_KINDS, DESCRIBE_TOPICS } from "./schema/fields.js";
|
|
15
|
+
import { DEFS, SPEC_TYPES, KEY_TYPES, USE_TYPES, WALK_TYPES, ANCHOR_TYPES, ANCHOR_REQUIRED, PROP_TYPES, FIT_TYPES, OBJECT_TYPES, FREE_TYPES, CHECK_ENTRY_TYPES, CHECK_REQUIRED, CHECKS_TYPES,
|
|
16
|
+
GOAL_TYPES, PROBE_TYPES, FIX_TYPES, REF_DOCS } from "./schema/types.js";
|
|
17
|
+
import { EXAMPLES, NOTES } from "./schema/topics.js";
|
|
18
|
+
import { SPEC_EXAMPLES, EXAMPLE_NOTES } from "./schema/spec-examples.js";
|
|
19
|
+
import { briefType } from "./schema/brief.js";
|
|
20
|
+
import { VEC_CHANNELS, NUM_CHANNELS, MAP_CHANNELS, SKILL_CHANNELS, FINGER_PRESETS, CHANNEL_DOCS, channelFamily, rangeOf, didYouMean } from "./compiler/vocab.js";
|
|
21
|
+
import { FIX_KEYS } from "./fix/index.js";
|
|
22
|
+
import { SKILL_NAMES } from "./skills/index.js";
|
|
23
|
+
import { SKILL_INFO } from "./skills/info.js";
|
|
24
|
+
|
|
25
|
+
/** Bytes of JSON a describe answer may take: the 8 KiB receipt minus 768 B for a describe receipt's
|
|
26
|
+
* head (569 B in a live Studio receipt: ids, host, revision, summary; no readback, it changes nothing). */
|
|
27
|
+
export const DESCRIBE_BUDGET = 8192 - 768;
|
|
28
|
+
|
|
29
|
+
const bad = msg => { throw new Error(`MOTION_SPEC_INVALID: ${msg}`); };
|
|
30
|
+
const typeOf = (types, f, table) => types[f] ?? bad(`describe: ${table}.${f} has no type in schema/types.js`);
|
|
31
|
+
|
|
32
|
+
/** { type: object } with exactly `fields`, each typed from `types`. */
|
|
33
|
+
const objectOf = (fields, types, table, required = []) => ({ type: "object", properties: Object.fromEntries(fields.map(f => [f, typeOf(types, f, table)])),
|
|
34
|
+
...(required.length ? { required } : {}), additionalProperties: false });
|
|
35
|
+
const listOf = (items, extra) => ({ type: "array", items, ...extra });
|
|
36
|
+
const oneOfRequired = names => ({ oneOf: names.map(n => ({ required: [n] })) });
|
|
37
|
+
|
|
38
|
+
const LOCAL_DEFS = {
|
|
39
|
+
key: () => objectOf(KEY_FIELDS, KEY_TYPES, "key", ["f", "set"]),
|
|
40
|
+
use: () => { const o = objectOf(USE_FIELDS, USE_TYPES, "use", ["skill", "f0"]); o.properties.skill = { enum: SKILL_NAMES }; return o; },
|
|
41
|
+
walk: () => objectOf(WALK_FIELDS, WALK_TYPES, "generators[i]", ["type", "f0"]),
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/** The schema with $schema and the $defs it (transitively) refers to. */
|
|
45
|
+
function rooted(schema) {
|
|
46
|
+
const defs = {}, all = { ...DEFS, ...LOCAL_DEFS };
|
|
47
|
+
for (let more = true; more;) {
|
|
48
|
+
more = false;
|
|
49
|
+
const json = JSON.stringify([schema, defs]);
|
|
50
|
+
for (const [name, d] of Object.entries(all)) if (!defs[name] && json.includes(`"#/$defs/${name}"`)) { defs[name] = typeof d === "function" ? d() : d; more = true; }
|
|
51
|
+
}
|
|
52
|
+
return { $schema: "https://json-schema.org/draft/2020-12/schema", ...schema, ...(Object.keys(defs).length ? { $defs: defs } : {}) };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function channelSchema(ch) {
|
|
56
|
+
const twin = ch.replace(/_r$/, "_l");
|
|
57
|
+
if (twin !== ch && (VEC_CHANNELS.has(twin) || NUM_CHANNELS.has(twin) || MAP_CHANNELS.has(twin))) return { $ref: `#/properties/${twin}` };
|
|
58
|
+
const r = rangeOf(ch), d = CHANNEL_DOCS[channelFamily(ch)] ?? bad(`describe: channel ${ch} has no entry in CHANNEL_DOCS`);
|
|
59
|
+
if (VEC_CHANNELS.has(ch)) return { $ref: "#/$defs/vec3", description: d };
|
|
60
|
+
if (MAP_CHANNELS.has(ch)) return { type: "object", propertyNames: { enum: FINGER_PRESETS }, additionalProperties: { type: "number", minimum: 0, maximum: 1 } };
|
|
61
|
+
return r ? { type: "number", minimum: r[1], maximum: r[2], description: r[3] } : { type: "number", description: d };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const SCHEMAS = {
|
|
65
|
+
spec: () => objectOf(SPEC_FIELDS, SPEC_TYPES, "spec", ["schema", "frames"]),
|
|
66
|
+
anchors: () => ({ type: "object", description: "name -> anchor", additionalProperties: { oneOf: Object.entries(ANCHOR_FIELDS).map(([kind, fields]) =>
|
|
67
|
+
objectOf(["kind", ...fields], { ...ANCHOR_TYPES, kind: { const: kind } }, `anchors (${kind})`, ["kind", ...(ANCHOR_REQUIRED[kind] ?? [])])) } }),
|
|
68
|
+
props: () => ({ type: "object", properties: {
|
|
69
|
+
props: { type: "object", description: "name -> prop", additionalProperties: objectOf(PROP_FIELDS, PROP_TYPES, "props", ["hand", "origin"]) },
|
|
70
|
+
fits: listOf(objectOf(FIT_FIELDS, FIT_TYPES, "fits", ["side", "anchor", "effector"])) } }),
|
|
71
|
+
objects: () => ({ type: "object", description: "object or group id -> hinge, carry, switch, follow or free", additionalProperties: { oneOf: [
|
|
72
|
+
{ title: "hinge", ...objectOf(OBJECT_FIELDS.hinge, OBJECT_TYPES, "objects (hinge)", ["pivot", "channel"]) },
|
|
73
|
+
{ title: "carry", ...objectOf(OBJECT_FIELDS.carry, OBJECT_TYPES, "objects (carry)", ["prop"]) },
|
|
74
|
+
{ title: "switch", ...objectOf(OBJECT_FIELDS.switch, OBJECT_TYPES, "objects (switch)") },
|
|
75
|
+
{ title: "follow", ...objectOf(OBJECT_FIELDS.follow, OBJECT_TYPES, "objects (follow)", ["follow"]) },
|
|
76
|
+
{ title: "free", ...objectOf(OBJECT_FIELDS.free, { ...OBJECT_TYPES, ...FREE_TYPES }, "objects (free)") }] } }),
|
|
77
|
+
checks: () => ({ type: "object", properties: Object.fromEntries(CHECK_FIELDS.map(f => [f, CHECK_ENTRY_FIELDS[f]
|
|
78
|
+
? listOf(objectOf(CHECK_ENTRY_FIELDS[f], CHECK_ENTRY_TYPES, `checks.${f}`, CHECK_REQUIRED[f]))
|
|
79
|
+
: typeOf(CHECKS_TYPES, f, "checks")])), additionalProperties: false }),
|
|
80
|
+
goals: () => listOf({ ...objectOf(GOAL_FIELDS, GOAL_TYPES, "goals", ["name", "at"]), ...oneOfRequired(["near", "above", "moved", "still", "speed"]) }),
|
|
81
|
+
probes: () => listOf({ ...objectOf(PROBE_FIELDS, PROBE_TYPES, "probes", ["name"]), ...oneOfRequired(["distance", "position"]) }, { maxItems: 16 }),
|
|
82
|
+
channels: () => ({ type: "object", description: "key.set: channel -> value",
|
|
83
|
+
properties: Object.fromEntries([...VEC_CHANNELS, ...NUM_CHANNELS, ...MAP_CHANNELS].filter(ch => !SKILL_CHANNELS.has(ch)).map(ch => [ch, channelSchema(ch)])),
|
|
84
|
+
patternProperties: { "^w_.+_[lr]$": { type: "number", minimum: 0, maximum: 1, description: rangeOf("w_a_l")[3] }, "_vis$": { type: "number", description: "shown above 0.5" } },
|
|
85
|
+
additionalProperties: { type: "number", description: "spec.channels, an object's or a check's when" } }),
|
|
86
|
+
refs: () => ({ type: "string", oneOf: REF_KINDS.map(k => ({ pattern: `^${k}:`, description: typeOf(REF_DOCS, k, "refs") })) }),
|
|
87
|
+
fix: () => ({ oneOf: [{ const: false }, objectOf(FIX_KEYS, FIX_TYPES, "fix")] }),
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
function skillArgs(name) {
|
|
91
|
+
const { args, required = [] } = SKILL_INFO[name] ?? bad(`describe: skill ${name} has no entry in skills/info.js`);
|
|
92
|
+
return Object.entries(args).map(([a, s]) => `${a}${required.includes(a) ? "*" : ""}${s.default !== undefined ? `=${JSON.stringify(s.default)}` : ""}`).join(", ");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function describeSkill(name) {
|
|
96
|
+
const info = SKILL_INFO[name] ?? bad(`describe: skill ${name} has no entry in skills/info.js`);
|
|
97
|
+
return { topic: "skills", name, schema: rooted({ type: "object", properties: info.args, ...(info.required ? { required: info.required } : {}), additionalProperties: false }),
|
|
98
|
+
example: { skill: name, ...info.example }, notes: [`needs: ${info.needs}`, `frames from f0: ${info.frames}`, `adds: ${info.adds.join("; ")}`] };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** The parts round-2 sessions read again right after essentials (topics objects and channels, skill
|
|
102
|
+
* reachGrip): in essentials their fields also say what they mean, so a first spec needs no second read. */
|
|
103
|
+
const MEANT = { topics: ["objects", "channels"], skills: ["reachGrip"] };
|
|
104
|
+
/** Topics no round-2 session needed for a first spec: essentials names their parts, the full topic has the fields. */
|
|
105
|
+
const SHALLOW = ["checks"];
|
|
106
|
+
|
|
107
|
+
/** A shared shape whose fields are mostly a skill's args (walk, walkTo): its own fields and `...<skill> args`. */
|
|
108
|
+
function shapeLine(d) {
|
|
109
|
+
for (const [skill, { args }] of Object.entries(SKILL_INFO)) {
|
|
110
|
+
const own = Object.entries(d.properties ?? {}).filter(([k, v]) => JSON.stringify(args[k]) !== JSON.stringify(v)), shared = Object.keys(d.properties ?? {}).length - own.length;
|
|
111
|
+
if (shared <= own.length) continue;
|
|
112
|
+
const not = Object.keys(args).filter(a => !d.properties[a]);
|
|
113
|
+
return briefType({ ...d, properties: Object.fromEntries(own) }).replace(/\}$/, `, ...${skill} args${not.length ? ` but ${not.join(", ")}` : ""}}`);
|
|
114
|
+
}
|
|
115
|
+
return briefType(d) + (d.description ? ` (${d.description})` : "");
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** The spec schema with each field a topic spells ("topic anchors") named by that topic's type line: `anchors?: anchors`, `fits?: props.fits`. */
|
|
119
|
+
function pointed(spec) {
|
|
120
|
+
const at = (f, t) => (t === f && !SCHEMAS[t]().properties?.[f] ? t : `${t}.${f}`);
|
|
121
|
+
return { ...spec, properties: Object.fromEntries(Object.entries(spec.properties).map(([f, s]) => {
|
|
122
|
+
const t = s.description?.match(/\btopic (\w+)\)?$/)?.[1];
|
|
123
|
+
return [f, t && SCHEMAS[t] && t !== "skills" ? { $ref: `#/types/${at(f, t)}` } : s];
|
|
124
|
+
})) };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Every topic's schema and every skill's args as one type line each, and the shared shapes they name. */
|
|
128
|
+
function essentials() {
|
|
129
|
+
const types = {}, shared = {};
|
|
130
|
+
for (const topic of DESCRIBE_TOPICS.filter(t => SCHEMAS[t])) {
|
|
131
|
+
const { $defs = {}, ...schema } = rooted(topic === "spec" ? pointed(SCHEMAS.spec()) : SCHEMAS[topic]());
|
|
132
|
+
types[topic] = briefType(schema, { means: MEANT.topics.includes(topic), depth: SHALLOW.includes(topic) ? 1 : undefined });
|
|
133
|
+
Object.assign(shared, $defs);
|
|
134
|
+
}
|
|
135
|
+
// use.skill without the enum: the skills list below names them
|
|
136
|
+
if (shared.use) shared.use = objectOf(USE_FIELDS, USE_TYPES, "use", ["skill", "f0"]);
|
|
137
|
+
const skills = SKILL_NAMES.map(n => {
|
|
138
|
+
const { args, required = [], needs, frames, adds } = SKILL_INFO[n] ?? bad(`describe: skill ${n} has no entry in skills/info.js`);
|
|
139
|
+
return { name: n, args: briefType({ type: "object", properties: args, required }, { means: MEANT.skills.includes(n) }), needs, frames, adds };
|
|
140
|
+
});
|
|
141
|
+
// the shared shapes a line names as a type (": vec3", "| frameSpan", "[ref, ref]"); a shallow topic names fewer than its schema has
|
|
142
|
+
const named = name => new RegExp(`(?:[:|] |[[(])${name}\\b`).test(JSON.stringify([types, skills.map(k => k.args)]));
|
|
143
|
+
for (let more = true; more;) {
|
|
144
|
+
more = false;
|
|
145
|
+
for (const [name, d] of Object.entries(shared)) if (!types[name] && named(name)) { types[name] = shapeLine(d); more = true; }
|
|
146
|
+
}
|
|
147
|
+
return { topic: "essentials", types, skills, notes: NOTES.essentials };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** describe(topic, name?) -> the answer for one topic (see the table above). */
|
|
151
|
+
export function describe(topic, name) {
|
|
152
|
+
if (!DESCRIBE_TOPICS.includes(topic)) bad(`topic "${topic}" is not a describe topic${didYouMean(String(topic), DESCRIBE_TOPICS)} (topics: ${DESCRIBE_TOPICS.join(", ")})`);
|
|
153
|
+
if (topic === "essentials") return essentials();
|
|
154
|
+
if (topic === "examples") return { topic, examples: SPEC_EXAMPLES, notes: EXAMPLE_NOTES };
|
|
155
|
+
if (topic !== "skills") return { topic, schema: rooted(SCHEMAS[topic]()), example: EXAMPLES[topic], notes: NOTES[topic] };
|
|
156
|
+
if (name === undefined) return { topic, skills: SKILL_NAMES.map(n => ({ name: n, args: skillArgs(n), needs: SKILL_INFO[n].needs, frames: SKILL_INFO[n].frames, adds: SKILL_INFO[n].adds })) };
|
|
157
|
+
if (!SKILL_NAMES.includes(name)) bad(`skill "${name}" is not a skill${didYouMean(String(name), SKILL_NAMES)} (skills: ${SKILL_NAMES.join(", ")})`);
|
|
158
|
+
return describeSkill(name);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const bytes = value => new TextEncoder().encode(JSON.stringify(value)).length;
|
|
162
|
+
|
|
163
|
+
/** describeMany([{ topic, name? }]) -> { topics, more? }: the answers in order while they fit DESCRIBE_BUDGET (the first
|
|
164
|
+
* always does), each ask once; the rest are named in `more` for another call. */
|
|
165
|
+
export function describeMany(asks) {
|
|
166
|
+
const seen = new Set(), wanted = asks.filter(({ topic, name }) => !seen.has(`${topic} ${name ?? ""}`) && seen.add(`${topic} ${name ?? ""}`));
|
|
167
|
+
const topics = [];
|
|
168
|
+
for (const [i, { topic, name }] of wanted.entries()) {
|
|
169
|
+
const answer = describe(topic, name), rest = wanted.slice(i + 1);
|
|
170
|
+
if (topics.length && bytes({ topics: [...topics, answer], more: rest }) > DESCRIBE_BUDGET) return { topics, more: wanted.slice(i) };
|
|
171
|
+
topics.push(answer);
|
|
172
|
+
}
|
|
173
|
+
return { topics };
|
|
174
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// The agent-facing words of the motion actions (src/commands/motion3.js): each action's description.
|
|
2
|
+
// Lane P owns the wording; the code only reads these constants. The procedure lives in the Studio
|
|
3
|
+
// system prompt and the field tables in motion.describe, so these strings say only what each action takes and answers.
|
|
4
|
+
export const MOTION_ACTION_HELP = {
|
|
5
|
+
compile: "Bake a spec on the character's rig and run QA. install: true installs (default), false or \"never\" changes nothing, \"ifClean\" only if the final qa.ok; output.installed tells. "
|
|
6
|
+
+ "Send spec, or base (an answered specId) + optional patch (JSON Patch); base alone or patch: [] re-measures (new probes). "
|
|
7
|
+
+ "spec = { schema: \"cozyclay.motion-spec.v1\", fps: 24, frames (number or \"auto\"), ... } (parts: motion.describe); any number may be { range: [lo, hi] } (searched). "
|
|
8
|
+
+ "probes (at most 16, request level; spec.probes: measured, not stored): { name, distance: [ref, ref] | position: ref, frames, every? }. "
|
|
9
|
+
+ "Refs need a prefix (joint: prop: anchor: landmark: object:; goals[].moved: a bare id too) and take loose bone names (any case); spec.checks need exact ones. "
|
|
10
|
+
+ "repair: { rounds } (1-8, default 4) applies group patches while severity drops. "
|
|
11
|
+
+ "qa judges only what is broken; speed, acceleration, balance, knee bend are observations unless checks.limits names them. "
|
|
12
|
+
+ "Answers frames, installed, specId, qa { ok, total, goals, groups (GOAL first, then severity; cause, hint, patch?) }, observations, delta (with base), repair, probes, timeline, fixed, search, skills. output.more: motion.report pages the rest.",
|
|
13
|
+
qa: "Measure the character's installed take again against the scene as it is now (objects may have moved). Optional checks replace the spec's checks and its skills' checks. "
|
|
14
|
+
+ "Answers specId and qa as motion.compile does (goals, ranked groups with cause, hint and patch); output.more pages with motion.report. Changes nothing.",
|
|
15
|
+
sample: "World positions (metres) of named bones of the installed take at up to 64 frames. Bone names may be loose (any case, a mixamorig: prefix, aliases such as hips or hand_l); an unknown one answers a did-you-mean. "
|
|
16
|
+
+ "Before a take is installed, use motion.compile probes instead. Changes nothing.",
|
|
17
|
+
describe: "The motion spec reference. topic essentials: all a first spec needs, { types (every field of every topic, one line each; objects, channels and reachGrip "
|
|
18
|
+
+ "say what fields mean), skills (typed args, needs, frames, adds), notes }. topic spec, skills, anchors, props, objects, checks, goals, probes, channels, refs or fix: "
|
|
19
|
+
+ "{ topic, schema, example, notes }; skills lists the skills, with name one skill's args and a use example. topic examples answers whole specs that compile. "
|
|
20
|
+
+ "topics: [topic | { topic, name? }] answers several in one call: { topics, more (the asks that did not fit) }. Changes nothing.",
|
|
21
|
+
keyObject: "Key an object the installed take moves (objectId) at frame, then compile and install it whatever QA says (a user edit, one undo). "
|
|
22
|
+
+ "A carried object takes pos [x, y, z] and rot [x, y, z, w], or aim [x, y, z] (its +Y) and roll?: its base's world pose (a group: its root), grab..release only (objects.<id>.keys); the hand follows. "
|
|
23
|
+
+ "A hinge takes angle (degrees) or rot (its turn about the axis). remove: true deletes the key at frame. Refused where the take neither holds nor hinges it (drag it then). "
|
|
24
|
+
+ "Answers keyed and the motion.compile output; REACH \"<id>: n cm off its keys\": what the hand missed.",
|
|
25
|
+
report: "Page through the parts of a motion.compile or motion.qa answer that did not fit (output.more): part is groups (the default), timeline or probes, from the item index to start at; "
|
|
26
|
+
+ "answers { specId, part, from, items, next } (next = the index to ask for next, null at the end). Changes nothing.",
|
|
27
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// A take's timing edit (take-edit.js: cuts, segment speeds, removed segments, a trim) carried onto the
|
|
2
|
+
// next take compiled for the same character, so a recompile does not throw away timing the user (or
|
|
3
|
+
// the agent) set on the take it replaces and nobody has to cut and slow it again by hand.
|
|
4
|
+
// The segments keep their source frames (a recompile mostly keeps its beats where they were): the
|
|
5
|
+
// same frame count keeps the edit as it is; on another count a segment that ran to the old last frame
|
|
6
|
+
// runs to the new one, segments past the new end drop out and one across it is clipped.
|
|
7
|
+
import { isIdentityEdit, normalizeTakeEdit } from "./take-edit.js";
|
|
8
|
+
|
|
9
|
+
const RESET = "motion.resetTiming plays the take as compiled";
|
|
10
|
+
|
|
11
|
+
/** carryEdit(edit, oldFrames, newFrames) -> null (nothing to carry) | { edit (null: dropped), note }. */
|
|
12
|
+
export function carryEdit(edit, oldFrames, newFrames) {
|
|
13
|
+
const previous = normalizeTakeEdit(edit, oldFrames);
|
|
14
|
+
if (!previous || isIdentityEdit(previous, oldFrames)) return null;
|
|
15
|
+
if (oldFrames === newFrames) return { edit: previous, note: `kept the timing edit of the take it replaced (${previous.length} segments); ${RESET}` };
|
|
16
|
+
const last = newFrames - 1, final = previous.length - 1;
|
|
17
|
+
const next = previous.map((segment, i) => ({ ...segment, sourceEnd: i === final && segment.sourceEnd === oldFrames - 1 ? last : Math.min(segment.sourceEnd, last) }))
|
|
18
|
+
.filter(segment => segment.sourceStart <= segment.sourceEnd);
|
|
19
|
+
if (!next.length || isIdentityEdit(next, newFrames)) return { edit: null, note: `dropped the timing edit of the take it replaced: nothing of it is left on the new ${newFrames} frames (it had ${oldFrames})` };
|
|
20
|
+
return { edit: next, note: `kept the timing edit of the take it replaced (${next.length} segments) on the new ${newFrames} frames (it had ${oldFrames}); ${RESET}` };
|
|
21
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Small helpers the auto-fixes share.
|
|
2
|
+
|
|
3
|
+
/** Eased dilation of a per-frame amount: every frame gets at least amount * ease over +-w frames. */
|
|
4
|
+
export function easeAround(values, w) {
|
|
5
|
+
const n = values.length, out = new Float64Array(n);
|
|
6
|
+
for (let i = 0; i < n; i++) {
|
|
7
|
+
if (!(values[i] > 0)) continue;
|
|
8
|
+
for (let j = Math.max(0, i - w - 1); j <= Math.min(n - 1, i + w + 1); j++) {
|
|
9
|
+
const t = 1 - Math.abs(i - j) / (w + 1), e = t * t * (3 - 2 * t);
|
|
10
|
+
out[j] = Math.max(out[j], values[i] * e);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return out;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const round4 = x => Math.round(x * 1e4) / 1e4;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Soles on the floor: where a foot's lowest sole point (qa/geometry.js solePoints: heel, ball, tip,
|
|
2
|
+
// on the skin's sole, where verify_result measures the floor) is under the floor, its ankle target
|
|
3
|
+
// goes up until that point is on the floor. A squat on the balls of the feet (fpitch +) stands on
|
|
4
|
+
// the balls instead of sinking them. The sole's shape under the ankle depends only on fyaw and
|
|
5
|
+
// fpitch, so the lift is measured at the target, not where an out-of-reach leg left the ankle.
|
|
6
|
+
// The lift eases in and out over EASE frames, so a foot keyed into the floor lands, not stops dead.
|
|
7
|
+
import { solePoints } from "../qa/geometry.js";
|
|
8
|
+
import { SIDES } from "../compiler/pose.js";
|
|
9
|
+
import { easeAround, round4 } from "./ease.js";
|
|
10
|
+
|
|
11
|
+
/** Depth (m) a sole point may keep under the floor; lifts past MAX_LIFT stop there for QA (GROUND). */
|
|
12
|
+
const GROUND_EPS = 0.001, MAX_LIFT = 0.15, EASE = 2;
|
|
13
|
+
|
|
14
|
+
export function liftSoles(out, engine) {
|
|
15
|
+
const { sk } = out.ctx, n = out.frames.length, touched = new Set();
|
|
16
|
+
let worst = 0;
|
|
17
|
+
for (const [k, S] of Object.entries(SIDES)) {
|
|
18
|
+
const ch = out.baked[`foot_${k}`], ankle = sk.index[`${S}Foot`];
|
|
19
|
+
if (!ch) continue;
|
|
20
|
+
const need = out.frames.map(({ s }, i) => {
|
|
21
|
+
const sp = solePoints(sk, s, S), low = Math.min(sp.heel[1], sp.ball[1], sp.tip[1]) - s.P[ankle][1] + ch[out.f0 + i][1];
|
|
22
|
+
return Math.min(MAX_LIFT, -low - GROUND_EPS);
|
|
23
|
+
});
|
|
24
|
+
if (!need.some(d => d > 0)) continue;
|
|
25
|
+
const lift = easeAround(need, EASE);
|
|
26
|
+
for (let i = 0; i < n; i++) {
|
|
27
|
+
if (!(lift[i] > 0)) continue;
|
|
28
|
+
const f = out.f0 + i;
|
|
29
|
+
ch[f] = [ch[f][0], ch[f][1] + lift[i], ch[f][2]];
|
|
30
|
+
worst = Math.max(worst, need[i]);
|
|
31
|
+
touched.add(f);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (!touched.size) return null;
|
|
35
|
+
engine.solve([...touched]);
|
|
36
|
+
return { frames: touched.size, max: round4(worst), how: "soles lifted onto the floor" };
|
|
37
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// Deterministic auto-fixes the compiler applies after the first solve (spec.fix). Each fix edits
|
|
2
|
+
// the baked channels or the per-frame IK offsets and re-solves only the frames it touched, so QA
|
|
3
|
+
// still measures the final frames; every fix reports what it changed (counts, worst amount).
|
|
4
|
+
//
|
|
5
|
+
// spec.fix = { reach: true, slide: true, penetration: 0.02, self: true, retime: false } (false = all off)
|
|
6
|
+
// reach wrist targets past the arm's reach go onto it through a soft knee (no snap);
|
|
7
|
+
// ankle targets past the leg's reach lower the hips just enough, eased in and out
|
|
8
|
+
// slide a sole under the floor lifts its ankle onto it (ground.js); a planted heel or ball
|
|
9
|
+
// that drifts less than 1 cm stays put, and a foot turning on its planted ball (heel
|
|
10
|
+
// up) or heel pivots about it whatever the drift; a flat planted foot (heel and ball
|
|
11
|
+
// down) keeps its heading until one of them lifts (turn.js)
|
|
12
|
+
// penetration hands pushed out of scene boxes along the box normal when the depth past the
|
|
13
|
+
// allowance is at most this many metres (true = 0.02), eased in and out
|
|
14
|
+
// self arms out of the rest of the body on its fitted skin: the elbow turns about the
|
|
15
|
+
// shoulder-wrist line, the hand is pushed out along the skin (self.js)
|
|
16
|
+
// retime speed spikes get more frames, borrowed from the neighbouring keys
|
|
17
|
+
import { lowerHips, armReach } from "./reach.js";
|
|
18
|
+
import { lockFeet } from "./slide.js";
|
|
19
|
+
import { holdTurns } from "./turn.js";
|
|
20
|
+
import { liftSoles } from "./ground.js";
|
|
21
|
+
import { pushOut } from "./push.js";
|
|
22
|
+
import { retime } from "./retime.js";
|
|
23
|
+
import { selfClear } from "./self.js";
|
|
24
|
+
|
|
25
|
+
const FIX_KEYS = ["reach", "slide", "penetration", "self", "retime"];
|
|
26
|
+
export { FIX_KEYS };
|
|
27
|
+
|
|
28
|
+
export function fixOptions(fix) {
|
|
29
|
+
if (fix === false) return { reach: false, slide: false, penetration: 0, self: false, retime: false };
|
|
30
|
+
const f = fix ?? {};
|
|
31
|
+
const pen = f.penetration === true ? 0.02 : Math.min(0.05, Math.max(0, Number(f.penetration) || 0));
|
|
32
|
+
return { reach: f.reach !== false, slide: f.slide !== false, penetration: pen, self: f.self !== false, retime: f.retime === true };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** The slide report with the flat feet whose turn waited (turn.js): turns { frames, max deg }. */
|
|
36
|
+
const withTurns = (turns, slide) => (turns ? { ...(slide ?? { frames: 0, how: "planted heel/ball held in place" }), turns, how: `${slide?.how ?? "planted heel/ball held in place"}; a flat planted foot keeps its heading until it lifts` } : slide);
|
|
37
|
+
const mergeReach = (a, b) => (a && b ? { frames: Math.max(a.frames, b.frames), eased: Math.max(a.eased, b.eased), max: Math.max(a.max, b.max), how: a.how } : a ?? b);
|
|
38
|
+
|
|
39
|
+
/** Runs the enabled fixes in order (retime first: it moves keys) and returns their reports. */
|
|
40
|
+
export function applyFixes(out, fix, engine, { whole = true } = {}) {
|
|
41
|
+
const fixed = {}, put = (name, report) => { if (report) fixed[name] = report; };
|
|
42
|
+
if (fix.retime && whole) put("retime", retime(out, engine));
|
|
43
|
+
if (fix.slide) put("ground", liftSoles(out, engine));
|
|
44
|
+
if (fix.reach) put("reachLegs", lowerHips(out, engine));
|
|
45
|
+
if (fix.slide) put("slide", withTurns(holdTurns(out, engine), lockFeet(out, engine)));
|
|
46
|
+
// a pivot can hold a foot past the leg's reach: the hips go down for those frames too
|
|
47
|
+
if (fix.reach && fixed.slide?.pivots) put("reachLegs", mergeReach(fixed.reachLegs, lowerHips(out, engine)));
|
|
48
|
+
if (fix.penetration > 0) put("penetration", pushOut(out, engine, fix.penetration));
|
|
49
|
+
if (fix.self) put("self", selfClear(out, engine));
|
|
50
|
+
if (fix.reach) put("reachArms", armReach(out));
|
|
51
|
+
return fixed;
|
|
52
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// Shallow penetration push-out (spec.fix.penetration = max depth in metres past the allowance):
|
|
2
|
+
// a hand or forearm capsule inside a scene box moves its wrist IK target out along the box
|
|
3
|
+
// normal, eased over neighbouring frames. Deeper hits are staging problems and stay for QA.
|
|
4
|
+
// Frames where that arm is FK (armw 0) cannot be pushed and are counted as skipped.
|
|
5
|
+
import { add, scale, len } from "../compiler/math.js";
|
|
6
|
+
import { QA_LIMITS, allowance } from "../qa/checks.js";
|
|
7
|
+
import { CAPSULES, segPoints, sdBox, boxNormal } from "../qa/geometry.js";
|
|
8
|
+
import { round4 } from "./ease.js";
|
|
9
|
+
|
|
10
|
+
const EASE = 4, MARGIN = 0.002, PASSES = 3;
|
|
11
|
+
const ARM_PARTS = { l: CAPSULES.filter(c => c[0] === "hand_l" || c[0] === "farm_l"), r: CAPSULES.filter(c => c[0] === "hand_r" || c[0] === "farm_r") };
|
|
12
|
+
|
|
13
|
+
/** Per-frame push vectors for side k: the needed exit (normal * excess) summed over boxes. */
|
|
14
|
+
function needs(out, k, maxDepth, allow, stats) {
|
|
15
|
+
const { sk } = out.ctx, I = sk.index, n = out.frames.length, v = Array.from({ length: n }, () => null);
|
|
16
|
+
for (let i = 0; i < n; i++) {
|
|
17
|
+
const { s, c } = out.frames[i], objs = out.objectsAt(i);
|
|
18
|
+
let sum = null;
|
|
19
|
+
for (const [id, box] of Object.entries(objs)) {
|
|
20
|
+
if (!box.half || box.ignore) continue;
|
|
21
|
+
let best = null;
|
|
22
|
+
for (const [part, a, b, r] of ARM_PARTS[k]) {
|
|
23
|
+
const lim = allowance(allow, part, id) ?? QA_LIMITS.allow;
|
|
24
|
+
const pts = segPoints(s.P[I[a]], s.P[I[b]]), ds = pts.map(p => sdBox(p, box)), m = Math.min(...ds);
|
|
25
|
+
const excess = r - m - lim;
|
|
26
|
+
if (excess <= 0) continue;
|
|
27
|
+
if (excess > maxDepth) { stats.deep++; continue; }
|
|
28
|
+
if (!best || excess > best[0]) best = [excess, pts[ds.indexOf(m)]];
|
|
29
|
+
}
|
|
30
|
+
if (best) sum = add(sum ?? [0, 0, 0], scale(boxNormal(best[1], box), best[0] + MARGIN));
|
|
31
|
+
}
|
|
32
|
+
if (!sum) continue;
|
|
33
|
+
if (!(c[`armw_${k}`] > 1e-4)) { stats.skipped++; continue; }
|
|
34
|
+
v[i] = sum;
|
|
35
|
+
}
|
|
36
|
+
return v;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function pushOut(out, engine, maxDepth) {
|
|
40
|
+
const allow = out.ctx.spec.checks?.allow, n = out.frames.length, stats = { deep: 0, skipped: 0 };
|
|
41
|
+
const hit = new Set();
|
|
42
|
+
let max = 0;
|
|
43
|
+
for (let pass = 0; pass < PASSES; pass++) {
|
|
44
|
+
stats.deep = 0; stats.skipped = 0;
|
|
45
|
+
const touched = new Set();
|
|
46
|
+
for (const k of ["l", "r"]) {
|
|
47
|
+
const v = needs(out, k, maxDepth, allow, stats), push = out.ctx.push[k];
|
|
48
|
+
// eased dilation of the vectors: neighbours get the strongest nearby push, scaled down
|
|
49
|
+
const eased = Array.from({ length: n }, () => null);
|
|
50
|
+
for (let i = 0; i < n; i++) {
|
|
51
|
+
if (!v[i]) continue;
|
|
52
|
+
hit.add(`${k}${i}`);
|
|
53
|
+
for (let j = Math.max(0, i - EASE - 1); j <= Math.min(n - 1, i + EASE + 1); j++) {
|
|
54
|
+
const t = 1 - Math.abs(i - j) / (EASE + 1), w = scale(v[i], t * t * (3 - 2 * t));
|
|
55
|
+
if (!eased[j] || len(w) > len(eased[j])) eased[j] = w;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
for (let i = 0; i < n; i++) {
|
|
59
|
+
if (!eased[i]) continue;
|
|
60
|
+
const f = out.f0 + i, next = add(push.get(f) ?? [0, 0, 0], eased[i]);
|
|
61
|
+
push.set(f, next);
|
|
62
|
+
max = Math.max(max, len(next));
|
|
63
|
+
touched.add(f);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (!touched.size) break;
|
|
67
|
+
engine.solve([...touched]);
|
|
68
|
+
}
|
|
69
|
+
if (!hit.size && !stats.skipped) return null;
|
|
70
|
+
return { frames: hit.size, max: round4(max), skipped: stats.skipped, tooDeep: stats.deep, how: "hand pushed out along the box normal" };
|
|
71
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// Reach fixes: the legs lower the hips (eased over neighbouring frames) until every ankle target
|
|
2
|
+
// is reachable; the arms are clamped inline by pose.js (hooks.softReach), counted here. Both only
|
|
3
|
+
// take misses up to 5 cm: anything farther is a staging error that QA must keep reporting.
|
|
4
|
+
import { sub, len, dot } from "../compiler/math.js";
|
|
5
|
+
import { SIDES, REACH_FIX_MAX } from "../compiler/pose.js";
|
|
6
|
+
import { easeAround, round4 } from "./ease.js";
|
|
7
|
+
|
|
8
|
+
const EASE = 6;
|
|
9
|
+
|
|
10
|
+
export function lowerHips(out, engine) {
|
|
11
|
+
const { sk } = out.ctx, I = sk.index, P = sk.restWorldPos, root = out.baked.root;
|
|
12
|
+
if (!root) return null;
|
|
13
|
+
const n = out.frames.length, need = new Float64Array(n);
|
|
14
|
+
for (let i = 0; i < n; i++) {
|
|
15
|
+
const { s, diag, c } = out.frames[i];
|
|
16
|
+
for (const [k, side] of Object.entries(SIDES)) {
|
|
17
|
+
if (!(diag[`leg_miss_${k}`] > 5e-4) || !Array.isArray(c[`foot_${k}`])) continue;
|
|
18
|
+
const R = 0.999 * (len(sub(P[I[`${side}Leg`]], P[I[`${side}UpLeg`]])) + len(sub(P[I[`${side}Foot`]], P[I[`${side}Leg`]])));
|
|
19
|
+
const v = sub(c[`foot_${k}`], s.P[I[`${side}UpLeg`]]), disc = v[1] * v[1] - dot(v, v) + R * R;
|
|
20
|
+
// hips go down by d so that |v + d*up| = R (the smaller root); sideways misses only get closer
|
|
21
|
+
const d = disc >= 0 ? -v[1] - Math.sqrt(disc) : -v[1];
|
|
22
|
+
if (d > 0 && d <= REACH_FIX_MAX) need[i] = Math.max(need[i], d + 5e-4);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const frames = need.filter(d => d > 0).length;
|
|
26
|
+
if (!frames) return null;
|
|
27
|
+
const drop = easeAround(need, EASE), touched = [];
|
|
28
|
+
for (let i = 0; i < n; i++) {
|
|
29
|
+
if (!(drop[i] > 0)) continue;
|
|
30
|
+
const f = out.f0 + i, r = root[f];
|
|
31
|
+
root[f] = [r[0], r[1] - drop[i], r[2]];
|
|
32
|
+
touched.push(f);
|
|
33
|
+
}
|
|
34
|
+
engine.solve(touched);
|
|
35
|
+
return { frames, eased: touched.length, max: round4(Math.max(...need)), how: "hips lowered" };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Frames whose wrist target was past reach and got the soft clamp. */
|
|
39
|
+
export function armReach(out) {
|
|
40
|
+
let frames = 0, max = 0;
|
|
41
|
+
for (const fr of out.frames) for (const k of ["l", "r"]) {
|
|
42
|
+
const over = fr.diag[`reach_over_${k}`] ?? 0;
|
|
43
|
+
if (over > 1e-3) { frames++; max = Math.max(max, over); }
|
|
44
|
+
}
|
|
45
|
+
return frames ? { frames, max: round4(max), how: "wrist target clamped to reach" } : null;
|
|
46
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// Retime (spec.fix.retime): where QA reports the motion too fast, the key intervals around it
|
|
2
|
+
// get longer, borrowing the frames from the neighbouring key intervals of the same window
|
|
3
|
+
// (slower or held stretches get shorter). Too fast means a one-frame pop, or a speed or
|
|
4
|
+
// acceleration limit the spec named in checks.limits (qa/style.js speedExcess); a fast but smooth
|
|
5
|
+
// motion is style and keeps its speed. Interpolation between keys scales with their spacing, so
|
|
6
|
+
// each interval is stretched by its own peak excess. The frame count and every key outside the
|
|
7
|
+
// windows stay where they are; the time map is kept on out.timeMap so frame-range checks follow.
|
|
8
|
+
import { QA_LIMITS } from "../qa/checks.js";
|
|
9
|
+
import { speedExcess } from "../qa/style.js";
|
|
10
|
+
|
|
11
|
+
const HEADROOM = 1.1, MIN_RATE = 0.35, PASSES = 3;
|
|
12
|
+
|
|
13
|
+
/** e[i] = how many times too fast frame i is (speed ratio, or sqrt of the acceleration ratio). */
|
|
14
|
+
const excess = out => {
|
|
15
|
+
const own = out.ctx.spec?.checks?.limits;
|
|
16
|
+
return speedExcess(out.ctx.sk, out, own, { ...QA_LIMITS, ...own });
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/** Windows [a, b] (key frames) around each run of too-fast frames, merged where they overlap. */
|
|
20
|
+
function windows(e, keyFrames, n) {
|
|
21
|
+
const out = [];
|
|
22
|
+
for (let i = 1; i < n; i++) {
|
|
23
|
+
if (e[i] <= 1) continue;
|
|
24
|
+
let j = i;
|
|
25
|
+
while (j + 1 < n && e[j + 1] > 1) j++;
|
|
26
|
+
const w = Math.max(6, 2 * (j - i + 1));
|
|
27
|
+
const a = Math.max(0, ...keyFrames.filter(f => f <= i - 1 - w)), b = Math.min(n - 1, ...keyFrames.filter(f => f >= j + w));
|
|
28
|
+
if (out.length && a <= out[out.length - 1][1]) out[out.length - 1][1] = Math.max(out[out.length - 1][1], b);
|
|
29
|
+
else out.push([a, b]);
|
|
30
|
+
i = j;
|
|
31
|
+
}
|
|
32
|
+
return out;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** New times for the breakpoints of window [a, b]: each interval scaled by max(peak excess, alpha). */
|
|
36
|
+
function spread(e, cuts) {
|
|
37
|
+
const total = cuts[cuts.length - 1] - cuts[0];
|
|
38
|
+
const segs = cuts.slice(1).map((k, i) => { let m = 0; for (let f = Math.floor(cuts[i]) + 1; f <= Math.ceil(k); f++) m = Math.max(m, e[f] ?? 0); return { len: k - cuts[i], need: HEADROOM * m }; });
|
|
39
|
+
const sum = alpha => segs.reduce((acc, s) => acc + s.len * Math.max(s.need, alpha), 0);
|
|
40
|
+
let lo = MIN_RATE, hi = 1;
|
|
41
|
+
for (let it = 0; it < 40; it++) { const mid = (lo + hi) / 2; if (sum(mid) > total) hi = mid; else lo = mid; }
|
|
42
|
+
const dur = segs.map(s => s.len * Math.max(s.need, lo)), scale = total / dur.reduce((acc, d) => acc + d, 0);
|
|
43
|
+
const T = [cuts[0]];
|
|
44
|
+
dur.forEach((d, i) => T.push(T[i] + d * scale));
|
|
45
|
+
T[T.length - 1] = cuts[cuts.length - 1];
|
|
46
|
+
return { T, stretch: Math.max(...dur.map((d, i) => (d * scale) / segs[i].len)) };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function retime(out, engine) {
|
|
50
|
+
const n = out.frames.length, maps = [];
|
|
51
|
+
let segments = 0, stretch = 1;
|
|
52
|
+
for (let pass = 0; pass < PASSES; pass++) {
|
|
53
|
+
const e = excess(out), keyFrames = [...new Set(out.keys.map(k => k.f))].sort((p, q) => p - q);
|
|
54
|
+
const wins = windows(e, keyFrames, n);
|
|
55
|
+
if (!wins.length) break;
|
|
56
|
+
const pieces = wins.map(([a, b]) => {
|
|
57
|
+
const cuts = [...new Set([a, ...keyFrames.filter(f => f > a && f < b), b])].sort((p, q) => p - q), r = spread(e, cuts);
|
|
58
|
+
stretch = Math.max(stretch, r.stretch);
|
|
59
|
+
return { cuts, T: r.T };
|
|
60
|
+
});
|
|
61
|
+
segments += wins.length;
|
|
62
|
+
const map = f => {
|
|
63
|
+
for (const { cuts, T } of pieces) {
|
|
64
|
+
if (f < cuts[0] || f > cuts[cuts.length - 1]) continue;
|
|
65
|
+
let i = 0;
|
|
66
|
+
while (i < cuts.length - 2 && f > cuts[i + 1]) i++;
|
|
67
|
+
return T[i] + (T[i + 1] - T[i]) * (f - cuts[i]) / (cuts[i + 1] - cuts[i]);
|
|
68
|
+
}
|
|
69
|
+
return f;
|
|
70
|
+
};
|
|
71
|
+
maps.push(map);
|
|
72
|
+
engine.rebake(out.keys.map(k => ({ ...k, f: map(k.f) })));
|
|
73
|
+
engine.solve(engine.all());
|
|
74
|
+
}
|
|
75
|
+
if (!maps.length) return null;
|
|
76
|
+
out.timeMap = f => maps.reduce((x, m) => m(x), f);
|
|
77
|
+
const left = excess(out).filter(x => x > 1).length;
|
|
78
|
+
return { windows: segments, maxStretch: Math.round(stretch * 100) / 100, stillFast: left, how: "key intervals around speed spikes stretched" };
|
|
79
|
+
}
|