cozyclay 2.1.1 → 3.0.1
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 +88 -0
- package/README.md +12 -30
- package/bin/agent/agent-routes.mjs +43 -116
- package/bin/agent/agent-runner.mjs +14 -15
- 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/session-store.mjs +2 -2
- package/bin/agent/studio-context-budget.mjs +93 -0
- 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-BWCqdgWA.js +1 -0
- package/dist/assets/app-DfTCxaKk.js +5087 -0
- package/dist/assets/app-f-fkV5uP.css +1 -0
- package/dist/assets/compile-worker-BxKXh07s.js +5 -0
- package/dist/assets/fflate.module-KbGj5-H6.js +5 -0
- package/dist/assets/{first-shot-handoff-msLI71g1.js → first-shot-handoff-DNO8kv_N.js} +1 -1
- package/dist/assets/{landing-ChUvB_zL.js → landing-yfZaJkWc.js} +1 -1
- package/dist/assets/scene-asset-sync-U3kS2rvK.js +20 -0
- package/dist/assets/scene-asset-sync-_S8NQl5B.css +1 -0
- package/dist/assets/workflow-D78eiKTa.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 +151 -2625
- package/src/agent-progress.js +80 -0
- package/src/agent-turn-mark.js +30 -0
- package/src/analytics.js +1 -19
- package/src/app-context.js +12 -2
- 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 +10 -8
- 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 +214 -0
- package/src/commands/objects.js +6 -4
- package/src/commands/shot.js +55 -13
- 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 +53 -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 +47 -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 +60 -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 +79 -0
- package/src/motion3/compile-worker.js +10 -0
- package/src/motion3/compiler/anchors.js +113 -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 +197 -0
- package/src/motion3/compiler/math.js +72 -0
- package/src/motion3/compiler/pose.js +177 -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 +144 -0
- package/src/motion3/compiler/validate-parts.js +118 -0
- package/src/motion3/compiler/validate.js +122 -0
- package/src/motion3/compiler/vocab.js +74 -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 +177 -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 +204 -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/people.js +82 -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 +125 -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 +195 -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 +44 -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/people-checks.js +37 -0
- package/src/motion3/qa/severity.js +38 -0
- package/src/motion3/qa/spec-checks.js +65 -0
- package/src/motion3/qa/style.js +228 -0
- package/src/motion3/qa/wrist-checks.js +69 -0
- package/src/motion3/qa/wrist-obs.js +60 -0
- package/src/motion3/refs.js +129 -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 +113 -0
- package/src/motion3/schema/types.js +149 -0
- package/src/motion3/search.js +117 -0
- package/src/motion3/second-hand.js +112 -0
- package/src/motion3/settle.js +136 -0
- package/src/motion3/skills/basic.js +83 -0
- package/src/motion3/skills/book.js +76 -0
- package/src/motion3/skills/car.js +145 -0
- package/src/motion3/skills/flip.js +200 -0
- package/src/motion3/skills/frames-auto.js +29 -0
- package/src/motion3/skills/impact.js +109 -0
- package/src/motion3/skills/index.js +85 -0
- package/src/motion3/skills/info.js +80 -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/qa-label.js +25 -0
- 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 +16 -14
- package/src/studio-agent-commands.js +19 -16
- package/src/studio-agent-motion.js +9 -9
- package/src/studio-agent-protocol.js +47 -77
- package/src/studio-app-binding.js +27 -47
- 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 +15 -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 +109 -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 +9 -125
- 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
|
@@ -1,543 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* preserve-mask.mjs — user edit ranges → Kimodo's `--preserve_mask` JSON (C1 v2).
|
|
3
|
-
*
|
|
4
|
-
* Scheduled inpainting (Disney, "Interactive Generative Motion Editing via
|
|
5
|
-
* Scheduled Inpainting", 2026) blends a preserved base take back into the
|
|
6
|
-
* evolving sample inside the denoising loop:
|
|
7
|
-
*
|
|
8
|
-
* x_blended = alpha_t * noise(base, t) + (1 - alpha_t) * x_gen
|
|
9
|
-
* alpha_t = alpha_time(t) * alpha_mask(frame)
|
|
10
|
-
*
|
|
11
|
-
* This file builds ONLY alpha_mask: one float per GENERATION frame, 1 = keep the
|
|
12
|
-
* base take exactly, 0 = let the model generate freely. Everything about
|
|
13
|
-
* diffusion time (sigma_s / sigma_e) lives on the Python side; the mask is a
|
|
14
|
-
* pure function of "which frames did the user edit".
|
|
15
|
-
*
|
|
16
|
-
* Three things are silent-bug shaped here, so each is pinned by
|
|
17
|
-
* test/verify-kimodo-preserve.mjs:
|
|
18
|
-
*
|
|
19
|
-
* 1. FRAME SPACE. Edit ranges arrive in the app's clip space, which runs at
|
|
20
|
-
* ARDY's 20 fps (src/App.jsx ARDY_FPS). Kimodo generates at 30. An unscaled
|
|
21
|
-
* range would free the wrong 2/3 of the clip — the user edits seconds 2..4
|
|
22
|
-
* and the model regenerates seconds 1.33..2.67. The scaling rule is copied
|
|
23
|
-
* verbatim from buildRoot2dConstraints in ./constraints.mjs (frame * genFps /
|
|
24
|
-
* appFps, Math.round, clamped into the generated clip) so a waypoint and an
|
|
25
|
-
* edit range authored on the same app frame always land on the same
|
|
26
|
-
* generation frame. If that rule ever changes, it must change in both files.
|
|
27
|
-
*
|
|
28
|
-
* 2. THE FALLOFF MUST NOT BE A STEP. The paper's own ablation is explicit: a
|
|
29
|
-
* square (hard-edged) kernel breaks motion at the seam — the preserved side
|
|
30
|
-
* is frozen on the base pose and the free side is generated with no knowledge
|
|
31
|
-
* of it, so the character snaps. The edit's influence therefore decays as a
|
|
32
|
-
* Gaussian shoulder over `influenceRadius` generation frames:
|
|
33
|
-
*
|
|
34
|
-
* weight(f) = 1 - exp( -(d^2) / (2 * sigma^2) ), sigma = influenceRadius/2
|
|
35
|
-
*
|
|
36
|
-
* where d is the distance in generation frames from f to the NEAREST frame of
|
|
37
|
-
* the edit range (so d = 0 anywhere inside it, giving weight 0 = fully free).
|
|
38
|
-
* sigma = radius/2 is chosen so the named radius is 2 sigma: weight is ~0.86
|
|
39
|
-
* at exactly `influenceRadius` frames out and reaches float-exact 1.0 around
|
|
40
|
-
* 4.3 radii, i.e. "the radius is where the edit has mostly stopped mattering",
|
|
41
|
-
* not where it is cut off. The first step away from a boundary is only
|
|
42
|
-
* 1 - exp(-1/(2*sigma^2)) (0.0198 for the default radius 10), which is the
|
|
43
|
-
* smoothness the tests assert.
|
|
44
|
-
*
|
|
45
|
-
* 3. OVERLAPS TAKE THE MINIMUM. Two edits whose shoulders meet must not add up
|
|
46
|
-
* to MORE preservation in the gap between them than either edit asked for on
|
|
47
|
-
* its own. Combining by min means "any edit that wants this frame free wins",
|
|
48
|
-
* which is the only rule that keeps the mask monotone in the number of edits:
|
|
49
|
-
* adding an edit can never preserve a frame harder than before.
|
|
50
|
-
*
|
|
51
|
-
* ---------------------------------------------------------------------------
|
|
52
|
-
* ROUND 2 (C1v2) adds two orthogonal axes to that same per-frame mask. Both are
|
|
53
|
-
* strictly additive: a call that uses neither emits a byte-identical v1 file.
|
|
54
|
-
*
|
|
55
|
-
* 4. PER-JOINT-GROUP MASKS. "Recompose only the arm" needs a mask that is free
|
|
56
|
-
* for the arm and preserved for everything else on the SAME frames. The unit
|
|
57
|
-
* is a GROUP, never a single joint: the seven groups of C1v2 (root, torso,
|
|
58
|
-
* head, leftArm, rightArm, leftLeg, rightLeg) are the coarsest partition that
|
|
59
|
-
* still lets a limb move without dragging the body, and they are coarse on
|
|
60
|
-
* purpose — freeing a forearm while pinning its own hand asks the model for a
|
|
61
|
-
* pose that does not exist, and it answers with a broken elbow.
|
|
62
|
-
*
|
|
63
|
-
* Callers name IK TRACK ids (the things the user actually drags in the pose
|
|
64
|
-
* studio: `leftHand`, `leftElbow`, `hips`, ...), not group names, and
|
|
65
|
-
* TRACK_GROUPS below is the single source of truth for that mapping —
|
|
66
|
-
* src/ardy/ik.js owns the track ids, this file owns what they preserve.
|
|
67
|
-
* `hips` deliberately maps to BOTH torso and root: dragging the hips moves
|
|
68
|
-
* the pelvis bone AND translates the character, and the two live in different
|
|
69
|
-
* halves of the motion representation, so freeing only one of them would let
|
|
70
|
-
* the model fight the half that stayed pinned.
|
|
71
|
-
*
|
|
72
|
-
* A range WITHOUT tracks is a whole-body edit and lowers the TOP-LEVEL
|
|
73
|
-
* weights, exactly as in v1. A range WITH tracks lowers only its groups'
|
|
74
|
-
* arrays and leaves the top level at 1 there. Python reads a group's array
|
|
75
|
-
* when one exists and falls back to the top level otherwise, so every emitted
|
|
76
|
-
* group array must ALSO carry the whole-body edits — otherwise a mixed call
|
|
77
|
-
* (one tracked range + one whole-body range) would quietly EXEMPT the tracked
|
|
78
|
-
* groups from the whole-body edit, which is the opposite of what was asked.
|
|
79
|
-
* That is why a group's array is built from the union of its own ranges and
|
|
80
|
-
* every untracked range, not from its own ranges alone.
|
|
81
|
-
*
|
|
82
|
-
* 5. THE NOISE-SCHEDULED KERNEL WIDTH (paper, Appendix A). One mask width cannot
|
|
83
|
-
* serve the whole denoising trajectory. At high noise the sample only carries
|
|
84
|
-
* global structure, so the edit's influence has to reach FAR to move the
|
|
85
|
-
* trajectory at all; at low noise it carries detail, and a wide mask there
|
|
86
|
-
* would smear the preserved take. The paper's answer is two masks and a lerp:
|
|
87
|
-
*
|
|
88
|
-
* alpha_mask(f, t) = lerp(weights[f], wideWeights[f], w(t))
|
|
89
|
-
*
|
|
90
|
-
* with w(t) rising with noise (Python picks the exact schedule and documents
|
|
91
|
-
* it). This file emits both arrays; `wideWeights` is the same Gaussian built
|
|
92
|
-
* with `influenceRadius * wideRadiusScale`. Wider means the shoulder decays
|
|
93
|
-
* more slowly, so wideWeights[f] <= weights[f] everywhere — the wide mask is
|
|
94
|
-
* always the more permissive one, never the other way round.
|
|
95
|
-
*
|
|
96
|
-
* wideWeights is OPT-IN, not automatic: it is emitted when the call carries
|
|
97
|
-
* tracks (grouped masks are a round-2 feature and always want the schedule)
|
|
98
|
-
* or when the caller names `wideRadiusScale` explicitly. A plain v1-shaped
|
|
99
|
-
* call therefore still produces a v1 file, which is what keeps round-1
|
|
100
|
-
* callers and their fixtures valid.
|
|
101
|
-
*
|
|
102
|
-
* VERSION. `version` is 2 as soon as either axis is used (groups or
|
|
103
|
-
* wideWeights) and 1 otherwise. It is a property of the emitted SHAPE, not of
|
|
104
|
-
* the caller's intent: a reader that only understands v1 can trust that a
|
|
105
|
-
* version-1 file has nothing it would silently ignore.
|
|
106
|
-
*/
|
|
107
|
-
|
|
108
|
-
export const PRESERVE_MASK_VERSION = 1;
|
|
109
|
-
|
|
110
|
-
/** The version stamped on a mask that uses groups and/or wideWeights. */
|
|
111
|
-
export const PRESERVE_MASK_VERSION_V2 = 2;
|
|
112
|
-
|
|
113
|
-
/** Default shoulder width in GENERATION frames (~0.33 s at 30 fps). */
|
|
114
|
-
export const DEFAULT_INFLUENCE_RADIUS = 10;
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Default multiplier for the high-noise (wide) kernel. 3 comes from the paper's
|
|
118
|
-
* Appendix A ablation: narrower than ~2x and the wide pass barely differs from
|
|
119
|
-
* the narrow one (no schedule at all); much wider than ~4x and the high-noise
|
|
120
|
-
* pass frees most of the clip, which is just "generate from scratch" with extra
|
|
121
|
-
* steps. 3 is the middle of that band.
|
|
122
|
-
*/
|
|
123
|
-
export const DEFAULT_WIDE_RADIUS_SCALE = 3;
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* The seven mask groups of C1v2, in canonical order. Python maps these to
|
|
127
|
-
* somaskel30 joints and then to motion-rep feature indices; a group missing from
|
|
128
|
-
* a mask means "use the top-level weights for those features".
|
|
129
|
-
*/
|
|
130
|
-
export const PRESERVE_GROUPS = Object.freeze([
|
|
131
|
-
"root",
|
|
132
|
-
"torso",
|
|
133
|
-
"head",
|
|
134
|
-
"leftArm",
|
|
135
|
-
"rightArm",
|
|
136
|
-
"leftLeg",
|
|
137
|
-
"rightLeg",
|
|
138
|
-
]);
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* IK track id -> the mask groups that track's edits free. SINGLE SOURCE OF
|
|
142
|
-
* TRUTH for the JS side (contract: "Track -> group map"); the bridge, the
|
|
143
|
-
* effector-constraint builder and this mask builder must all read it from here.
|
|
144
|
-
*
|
|
145
|
-
* The ids are exactly the ones the pose studio drags, spread across three
|
|
146
|
-
* tables in src/ardy/ik.js: IK_TRACKS (hands/feet), MID_TRACKS (elbows/knees)
|
|
147
|
-
* and FK_TRACKS (hips/spine/chest/neck/head/shoulders). They are NOT cskel27
|
|
148
|
-
* joint names — `leftElbow` is the joint cskel27 calls LeftForeArm, and `chest`
|
|
149
|
-
* spans Spine1/Spine2.
|
|
150
|
-
*
|
|
151
|
-
* Two mappings are worth stating out loud because they look like typos:
|
|
152
|
-
* - the SHOULDERS belong to `torso`, not to their arm. A clavicle rotation
|
|
153
|
-
* swings the whole shoulder mass and reads as a torso motion; freeing it
|
|
154
|
-
* with the arm would let the chest twist inside a preserved spine.
|
|
155
|
-
* - `hips` frees `torso` AND `root` — the pelvis bone and the character's
|
|
156
|
-
* global position/heading are different features of the representation and
|
|
157
|
-
* a hips drag moves both.
|
|
158
|
-
*/
|
|
159
|
-
export const TRACK_GROUPS = Object.freeze({
|
|
160
|
-
// arms
|
|
161
|
-
leftHand: Object.freeze(["leftArm"]),
|
|
162
|
-
leftElbow: Object.freeze(["leftArm"]),
|
|
163
|
-
rightHand: Object.freeze(["rightArm"]),
|
|
164
|
-
rightElbow: Object.freeze(["rightArm"]),
|
|
165
|
-
// legs
|
|
166
|
-
leftFoot: Object.freeze(["leftLeg"]),
|
|
167
|
-
leftKnee: Object.freeze(["leftLeg"]),
|
|
168
|
-
rightFoot: Object.freeze(["rightLeg"]),
|
|
169
|
-
rightKnee: Object.freeze(["rightLeg"]),
|
|
170
|
-
// head
|
|
171
|
-
head: Object.freeze(["head"]),
|
|
172
|
-
neck: Object.freeze(["head"]),
|
|
173
|
-
// torso
|
|
174
|
-
spine: Object.freeze(["torso"]),
|
|
175
|
-
chest: Object.freeze(["torso"]),
|
|
176
|
-
leftShoulder: Object.freeze(["torso"]),
|
|
177
|
-
rightShoulder: Object.freeze(["torso"]),
|
|
178
|
-
// the pelvis is both a bone and the character's global transform
|
|
179
|
-
hips: Object.freeze(["torso", "root"]),
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
const KNOWN_TRACK_IDS = Object.freeze(Object.keys(TRACK_GROUPS));
|
|
183
|
-
|
|
184
|
-
function requireFinite(value, label, fn = "buildPreserveMask") {
|
|
185
|
-
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
186
|
-
throw new Error(`${fn}: ${label} must be a finite number, got ${JSON.stringify(value)}`);
|
|
187
|
-
}
|
|
188
|
-
return value;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
function requirePositive(value, label, fn = "buildPreserveMask") {
|
|
192
|
-
requireFinite(value, label, fn);
|
|
193
|
-
if (value <= 0) {
|
|
194
|
-
throw new Error(`${fn}: ${label} must be greater than 0, got ${JSON.stringify(value)}`);
|
|
195
|
-
}
|
|
196
|
-
return value;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
/**
|
|
200
|
-
* Resolve one range's `tracks` list to the canonical group names it frees.
|
|
201
|
-
* Returns null when the range has no tracks (a whole-body edit).
|
|
202
|
-
*/
|
|
203
|
-
function resolveGroups(tracks, index) {
|
|
204
|
-
if (tracks === undefined || tracks === null) return null;
|
|
205
|
-
if (!Array.isArray(tracks)) {
|
|
206
|
-
throw new Error(
|
|
207
|
-
`buildPreserveMask: editRanges[${index}].tracks must be an array of IK track ids, got ${JSON.stringify(tracks)}`
|
|
208
|
-
);
|
|
209
|
-
}
|
|
210
|
-
// An EMPTY list is refused rather than read as either "whole body" or
|
|
211
|
-
// "nothing": both readings are plausible, they differ by the entire clip, and
|
|
212
|
-
// the caller that produced it meant one of them. Omitting the key is the
|
|
213
|
-
// unambiguous way to say "whole body".
|
|
214
|
-
if (tracks.length === 0) {
|
|
215
|
-
throw new Error(
|
|
216
|
-
`buildPreserveMask: editRanges[${index}].tracks is empty — omit the key entirely for a whole-body edit`
|
|
217
|
-
);
|
|
218
|
-
}
|
|
219
|
-
const groups = new Set();
|
|
220
|
-
for (let slot = 0; slot < tracks.length; slot += 1) {
|
|
221
|
-
const track = tracks[slot];
|
|
222
|
-
const mapped = typeof track === "string" ? TRACK_GROUPS[track] : undefined;
|
|
223
|
-
if (!mapped) {
|
|
224
|
-
throw new Error(
|
|
225
|
-
`buildPreserveMask: editRanges[${index}].tracks[${slot}] ${JSON.stringify(track)} is not a known IK track id; ` +
|
|
226
|
-
`valid ids are ${KNOWN_TRACK_IDS.join(", ")}`
|
|
227
|
-
);
|
|
228
|
-
}
|
|
229
|
-
for (const group of mapped) groups.add(group);
|
|
230
|
-
}
|
|
231
|
-
// Canonical order so two callers naming the same tracks in a different order
|
|
232
|
-
// emit byte-identical JSON.
|
|
233
|
-
return PRESERVE_GROUPS.filter((group) => groups.has(group));
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* The v1 Gaussian, factored out so the narrow mask, the wide mask and every
|
|
238
|
-
* group array are provably the same function of a range list and a radius.
|
|
239
|
-
*
|
|
240
|
-
* `radius` is a plain number here rather than the validated integer option: the
|
|
241
|
-
* wide kernel is `influenceRadius * wideRadiusScale` and has no reason to land
|
|
242
|
-
* on an integer.
|
|
243
|
-
*/
|
|
244
|
-
function gaussianWeights(ranges, frameCount, radius) {
|
|
245
|
-
// 1.0 everywhere = preserve the whole base take. Edits only ever subtract.
|
|
246
|
-
const weights = new Array(frameCount).fill(1);
|
|
247
|
-
// sigma = radius/2; see the header for why the named radius is 2 sigma.
|
|
248
|
-
const sigma = radius / 2;
|
|
249
|
-
const twoSigmaSquared = 2 * sigma * sigma;
|
|
250
|
-
|
|
251
|
-
for (const { start, end } of ranges) {
|
|
252
|
-
for (let frame = 0; frame < frameCount; frame += 1) {
|
|
253
|
-
// Distance to the NEAREST frame of the range. `end` is exclusive, so the
|
|
254
|
-
// last free frame is end - 1 and the first frame past the range is one
|
|
255
|
-
// step away from it, not zero — getting this wrong would shift the whole
|
|
256
|
-
// trailing shoulder by a frame.
|
|
257
|
-
let distance = 0;
|
|
258
|
-
if (frame < start) distance = start - frame;
|
|
259
|
-
else if (frame > end - 1) distance = frame - (end - 1);
|
|
260
|
-
|
|
261
|
-
let weight;
|
|
262
|
-
if (distance === 0) {
|
|
263
|
-
weight = 0; // inside the edit: fully free generation
|
|
264
|
-
} else if (radius === 0) {
|
|
265
|
-
weight = 1; // opt-in hard edge: no shoulder at all
|
|
266
|
-
} else {
|
|
267
|
-
weight = 1 - Math.exp(-(distance * distance) / twoSigmaSquared);
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
// MIN, not sum or product: any edit that wants this frame free wins, so
|
|
271
|
-
// adding an edit can never preserve a frame harder than before.
|
|
272
|
-
if (weight < weights[frame]) weights[frame] = weight;
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// Clamp defensively. The Gaussian is analytically inside [0, 1), but the mask
|
|
277
|
-
// is fed straight into a blend coefficient and a value outside [0, 1] there
|
|
278
|
-
// would amplify rather than mix, so the invariant is enforced, not assumed.
|
|
279
|
-
for (let frame = 0; frame < frameCount; frame += 1) {
|
|
280
|
-
weights[frame] = Math.min(1, Math.max(0, weights[frame]));
|
|
281
|
-
}
|
|
282
|
-
return weights;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
/**
|
|
286
|
-
* Turn APP-space edit ranges into the dense per-frame preserve mask (C1 v1/v2).
|
|
287
|
-
*
|
|
288
|
-
* @param {Array<{startFrame:number,endFrame:number,tracks?:string[]}>} editRanges
|
|
289
|
-
* Half-open [startFrame, endFrame) ranges in APP frame space (20 fps clip).
|
|
290
|
-
* An EMPTY array is valid and means "nothing was edited": the result is all
|
|
291
|
-
* ones, i.e. pure reconstruction of the base take. That case is the project's
|
|
292
|
-
* G1 acceptance gate, so it must be expressible, not an error.
|
|
293
|
-
* `tracks` (optional, v2) names IK track ids from TRACK_GROUPS; with it the
|
|
294
|
-
* range frees only the mapped groups, without it the whole body.
|
|
295
|
-
* @param {{appFps:number, genFps:number, genFrames:number, influenceRadius?:number,
|
|
296
|
-
* wideRadiusScale?:number|null}} options
|
|
297
|
-
* `wideRadiusScale` (default 3) is the multiplier for the paper's high-noise
|
|
298
|
-
* wide kernel. Naming it — at any value — opts into `wideWeights`; passing
|
|
299
|
-
* null suppresses `wideWeights` even for a grouped mask, which is the escape
|
|
300
|
-
* hatch for a caller whose Python side predates the schedule.
|
|
301
|
-
* @returns {{version:1|2, genFps:number, genFrames:number, weights:number[],
|
|
302
|
-
* wideWeights?:number[], groups?:Object<string,{weights:number[],wideWeights?:number[]}>}}
|
|
303
|
-
*/
|
|
304
|
-
export function buildPreserveMask(editRanges, options = {}) {
|
|
305
|
-
const {
|
|
306
|
-
appFps,
|
|
307
|
-
genFps,
|
|
308
|
-
genFrames,
|
|
309
|
-
influenceRadius = DEFAULT_INFLUENCE_RADIUS,
|
|
310
|
-
wideRadiusScale = DEFAULT_WIDE_RADIUS_SCALE,
|
|
311
|
-
} = options ?? {};
|
|
312
|
-
// Presence, not value: the default width has to exist for grouped masks, but
|
|
313
|
-
// defaulting the EMISSION on would rewrite every round-1 caller's file.
|
|
314
|
-
const wideRequested =
|
|
315
|
-
options !== null && typeof options === "object" &&
|
|
316
|
-
Object.prototype.hasOwnProperty.call(options, "wideRadiusScale");
|
|
317
|
-
|
|
318
|
-
if (!Array.isArray(editRanges)) {
|
|
319
|
-
throw new Error(`buildPreserveMask: editRanges must be an array, got ${JSON.stringify(editRanges)}`);
|
|
320
|
-
}
|
|
321
|
-
requirePositive(appFps, "appFps");
|
|
322
|
-
requirePositive(genFps, "genFps");
|
|
323
|
-
requirePositive(genFrames, "genFrames");
|
|
324
|
-
if (genFrames < 1) throw new Error(`buildPreserveMask: genFrames must be >= 1, got ${genFrames}`);
|
|
325
|
-
// influenceRadius 0 is EXPLICITLY ALLOWED and means a hard-edged square
|
|
326
|
-
// kernel. The paper measured that as the broken case, so it is never the
|
|
327
|
-
// default — but a caller that has already smoothed its own seam (or is
|
|
328
|
-
// measuring the ablation, tools/kimodo/measure-preserve.mjs) needs the opt
|
|
329
|
-
// out, and silently substituting a ramp would falsify their measurement.
|
|
330
|
-
if (!Number.isInteger(influenceRadius) || influenceRadius < 0) {
|
|
331
|
-
throw new Error(
|
|
332
|
-
`buildPreserveMask: influenceRadius must be a non-negative integer, got ${JSON.stringify(influenceRadius)}`
|
|
333
|
-
);
|
|
334
|
-
}
|
|
335
|
-
// A scale below 1 would make the "wide" kernel NARROWER than the narrow one,
|
|
336
|
-
// inverting the paper's schedule (high noise would preserve harder than low
|
|
337
|
-
// noise). That is not a tuning choice, it is a sign error, so it is refused.
|
|
338
|
-
if (wideRadiusScale !== null && (!Number.isFinite(wideRadiusScale) || wideRadiusScale < 1)) {
|
|
339
|
-
throw new Error(
|
|
340
|
-
`buildPreserveMask: wideRadiusScale must be a finite number >= 1 (or null to suppress wideWeights), got ${JSON.stringify(wideRadiusScale)}`
|
|
341
|
-
);
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
const scale = genFps / appFps;
|
|
345
|
-
const frameCount = Math.round(genFrames);
|
|
346
|
-
const lastFrame = frameCount - 1;
|
|
347
|
-
|
|
348
|
-
// Validate and scale every range in author order first, so a bad edit list is
|
|
349
|
-
// refused before any of it shapes the mask.
|
|
350
|
-
const scaled = editRanges.map((range, index) => {
|
|
351
|
-
if (!range || typeof range !== "object") {
|
|
352
|
-
throw new Error(`buildPreserveMask: editRanges[${index}] must be an object`);
|
|
353
|
-
}
|
|
354
|
-
const { startFrame, endFrame, tracks } = range;
|
|
355
|
-
const groups = resolveGroups(tracks, index);
|
|
356
|
-
if (!Number.isInteger(startFrame) || startFrame < 0) {
|
|
357
|
-
throw new Error(
|
|
358
|
-
`buildPreserveMask: editRanges[${index}].startFrame must be a non-negative integer, got ${JSON.stringify(startFrame)}`
|
|
359
|
-
);
|
|
360
|
-
}
|
|
361
|
-
if (!Number.isInteger(endFrame) || endFrame < 0) {
|
|
362
|
-
throw new Error(
|
|
363
|
-
`buildPreserveMask: editRanges[${index}].endFrame must be a non-negative integer, got ${JSON.stringify(endFrame)}`
|
|
364
|
-
);
|
|
365
|
-
}
|
|
366
|
-
// Ranges are half-open, so an empty or inverted one edits nothing. It is
|
|
367
|
-
// almost always an off-by-one in the caller rather than intent, and
|
|
368
|
-
// accepting it would produce an all-ones mask that looks like a working
|
|
369
|
-
// reconstruction while silently ignoring the user's edit.
|
|
370
|
-
if (endFrame <= startFrame) {
|
|
371
|
-
throw new Error(
|
|
372
|
-
`buildPreserveMask: editRanges[${index}] must be a non-empty half-open range, got [${startFrame}, ${endFrame})`
|
|
373
|
-
);
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
const rawStart = Math.round(startFrame * scale);
|
|
377
|
-
const rawEnd = Math.round(endFrame * scale);
|
|
378
|
-
// A range that misses the generated clip entirely cannot be clamped into
|
|
379
|
-
// anything meaningful — clamping it would free a frame at one end that the
|
|
380
|
-
// user never touched. Refuse it by name, quoting the clip length, because
|
|
381
|
-
// the usual cause is a mask built against a different take's duration.
|
|
382
|
-
if (rawStart >= frameCount || rawEnd <= 0) {
|
|
383
|
-
throw new Error(
|
|
384
|
-
`buildPreserveMask: editRanges[${index}] [${startFrame}, ${endFrame}) scales to generation frames [${rawStart}, ${rawEnd}) which lies outside the ${frameCount}-frame clip`
|
|
385
|
-
);
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
// Partial overlap CLAMPS rather than throwing: an edit that runs off the
|
|
389
|
-
// end of the clip still asks for the frames it shares with it.
|
|
390
|
-
const start = Math.min(lastFrame, Math.max(0, rawStart));
|
|
391
|
-
// The exclusive end clamps to frameCount, not frameCount - 1: the last
|
|
392
|
-
// index this frees is end - 1, which is still inside [0, frameCount - 1].
|
|
393
|
-
// Clamping the END index itself would silently preserve the clip's final
|
|
394
|
-
// frame even when the user edited right up to it.
|
|
395
|
-
// A downscale (e.g. 60 fps app -> 30 fps generation) can also round both
|
|
396
|
-
// ends onto one frame; forcing at least one free frame keeps the edit
|
|
397
|
-
// visible instead of collapsing it to nothing.
|
|
398
|
-
const end = Math.min(frameCount, Math.max(start + 1, rawEnd));
|
|
399
|
-
return { start, end, groups };
|
|
400
|
-
});
|
|
401
|
-
|
|
402
|
-
// Whole-body ranges shape the top level; tracked ranges shape their groups.
|
|
403
|
-
const wholeBody = scaled.filter((range) => range.groups === null);
|
|
404
|
-
const rangesByGroup = new Map();
|
|
405
|
-
for (const range of scaled) {
|
|
406
|
-
if (range.groups === null) continue;
|
|
407
|
-
for (const group of range.groups) {
|
|
408
|
-
if (!rangesByGroup.has(group)) rangesByGroup.set(group, []);
|
|
409
|
-
rangesByGroup.get(group).push(range);
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
const weights = gaussianWeights(wholeBody, frameCount, influenceRadius);
|
|
414
|
-
|
|
415
|
-
// No edits at all is pure reconstruction (gate G1): a v1 all-ones mask, and
|
|
416
|
-
// deliberately NO wideWeights — there is no shoulder whose width could be
|
|
417
|
-
// scheduled, and an all-ones wide array would only invite a reader to lerp
|
|
418
|
-
// between two identical masks and call it a feature.
|
|
419
|
-
const hasEdits = scaled.length > 0;
|
|
420
|
-
const hasGroups = rangesByGroup.size > 0;
|
|
421
|
-
const emitWide = hasEdits && wideRadiusScale !== null && (hasGroups || wideRequested);
|
|
422
|
-
const wideRadius = influenceRadius * (wideRadiusScale ?? 1);
|
|
423
|
-
|
|
424
|
-
const mask = {
|
|
425
|
-
version: hasGroups || emitWide ? PRESERVE_MASK_VERSION_V2 : PRESERVE_MASK_VERSION,
|
|
426
|
-
genFps,
|
|
427
|
-
genFrames: frameCount,
|
|
428
|
-
weights,
|
|
429
|
-
};
|
|
430
|
-
if (emitWide) mask.wideWeights = gaussianWeights(wholeBody, frameCount, wideRadius);
|
|
431
|
-
|
|
432
|
-
if (hasGroups) {
|
|
433
|
-
const groups = {};
|
|
434
|
-
// Canonical order, so the JSON is stable regardless of edit order.
|
|
435
|
-
for (const name of PRESERVE_GROUPS) {
|
|
436
|
-
const own = rangesByGroup.get(name);
|
|
437
|
-
if (!own) continue;
|
|
438
|
-
// The union with `wholeBody` is the load-bearing part: Python reads this
|
|
439
|
-
// array INSTEAD of the top level for the group's features, so a group
|
|
440
|
-
// that dropped the whole-body ranges would be exempted from them.
|
|
441
|
-
const union = wholeBody.concat(own);
|
|
442
|
-
const entry = { weights: gaussianWeights(union, frameCount, influenceRadius) };
|
|
443
|
-
if (emitWide) entry.wideWeights = gaussianWeights(union, frameCount, wideRadius);
|
|
444
|
-
groups[name] = entry;
|
|
445
|
-
}
|
|
446
|
-
mask.groups = groups;
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
return mask;
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
/**
|
|
453
|
-
* The preserve + waypoints mask (paper 4.4, round-2 feature B).
|
|
454
|
-
*
|
|
455
|
-
* The user keeps a take's style but redraws its path. The root — global
|
|
456
|
-
* translation and heading — must therefore come entirely from the waypoints,
|
|
457
|
-
* while every other feature rides the preserved take. That is exactly a mask
|
|
458
|
-
* whose `root` group is 0 for the whole clip and whose top level is 1.
|
|
459
|
-
*
|
|
460
|
-
* It lives here rather than in the bridge because it is a MASK SHAPE, and the
|
|
461
|
-
* one thing worse than a one-line helper is two callers each inventing their own
|
|
462
|
-
* spelling of the same shape. No wideWeights: a constant mask has no shoulder,
|
|
463
|
-
* so there is no width to schedule at any noise level.
|
|
464
|
-
*
|
|
465
|
-
* @param {{genFps:number, genFrames:number}} options
|
|
466
|
-
* @returns {{version:2, genFps:number, genFrames:number, weights:number[],
|
|
467
|
-
* groups:{root:{weights:number[]}}}}
|
|
468
|
-
*/
|
|
469
|
-
export function rootFreeMask({ genFps, genFrames } = {}) {
|
|
470
|
-
requirePositive(genFps, "genFps", "rootFreeMask");
|
|
471
|
-
requirePositive(genFrames, "genFrames", "rootFreeMask");
|
|
472
|
-
const frameCount = Math.round(genFrames);
|
|
473
|
-
return {
|
|
474
|
-
version: PRESERVE_MASK_VERSION_V2,
|
|
475
|
-
genFps,
|
|
476
|
-
genFrames: frameCount,
|
|
477
|
-
weights: new Array(frameCount).fill(1),
|
|
478
|
-
groups: { root: { weights: new Array(frameCount).fill(0) } },
|
|
479
|
-
};
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
/** Free / ramp / preserved counts for one weights array, validating as it goes. */
|
|
483
|
-
function countWeights(weights, label) {
|
|
484
|
-
if (!Array.isArray(weights)) {
|
|
485
|
-
throw new Error(`preserveMaskStats: ${label} must be an array of finite weights`);
|
|
486
|
-
}
|
|
487
|
-
let freeFrames = 0;
|
|
488
|
-
let preservedFrames = 0;
|
|
489
|
-
let rampFrames = 0;
|
|
490
|
-
for (let index = 0; index < weights.length; index += 1) {
|
|
491
|
-
const weight = weights[index];
|
|
492
|
-
if (typeof weight !== "number" || !Number.isFinite(weight)) {
|
|
493
|
-
throw new Error(`preserveMaskStats: ${label}[${index}] must be a finite number, got ${JSON.stringify(weight)}`);
|
|
494
|
-
}
|
|
495
|
-
if (weight === 0) freeFrames += 1;
|
|
496
|
-
else if (weight === 1) preservedFrames += 1;
|
|
497
|
-
else rampFrames += 1;
|
|
498
|
-
}
|
|
499
|
-
return { freeFrames, preservedFrames, rampFrames };
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
/**
|
|
503
|
-
* Summarise a mask for the UI and for run reporting: how much of the take is
|
|
504
|
-
* being regenerated, how much is held, and how much is the blend in between.
|
|
505
|
-
* A run whose rampFrames dwarf its freeFrames is a warning sign — the shoulders
|
|
506
|
-
* are wider than the edit, so the "preserved" take will drift almost everywhere.
|
|
507
|
-
*
|
|
508
|
-
* Accepts either the mask object from buildPreserveMask or a bare weights array.
|
|
509
|
-
*
|
|
510
|
-
* When the mask carries groups the same three counts are reported per group
|
|
511
|
-
* under `groups`, because the top-level numbers of a purely grouped mask are
|
|
512
|
-
* "0 free, everything preserved" — technically true and completely misleading
|
|
513
|
-
* as a summary of a run that is about to regenerate an arm. The key is ABSENT
|
|
514
|
-
* for a groupless mask so a v1 summary stays deep-equal to what it always was.
|
|
515
|
-
*
|
|
516
|
-
* @param {{weights:number[], groups?:object}|number[]} mask
|
|
517
|
-
* @returns {{freeFrames:number, preservedFrames:number, rampFrames:number,
|
|
518
|
-
* groups?:Object<string,{freeFrames:number,preservedFrames:number,rampFrames:number}>}}
|
|
519
|
-
*/
|
|
520
|
-
export function preserveMaskStats(mask) {
|
|
521
|
-
const weights = Array.isArray(mask) ? mask : mask && mask.weights;
|
|
522
|
-
if (!Array.isArray(weights)) {
|
|
523
|
-
throw new Error("preserveMaskStats: mask must be a preserve mask object or a weights array");
|
|
524
|
-
}
|
|
525
|
-
const stats = countWeights(weights, "weights");
|
|
526
|
-
|
|
527
|
-
const maskGroups = Array.isArray(mask) ? null : mask.groups;
|
|
528
|
-
if (maskGroups !== undefined && maskGroups !== null) {
|
|
529
|
-
if (typeof maskGroups !== "object" || Array.isArray(maskGroups)) {
|
|
530
|
-
throw new Error(`preserveMaskStats: groups must be an object, got ${JSON.stringify(maskGroups)}`);
|
|
531
|
-
}
|
|
532
|
-
const names = Object.keys(maskGroups);
|
|
533
|
-
if (names.length > 0) {
|
|
534
|
-
const groups = {};
|
|
535
|
-
for (const name of names) {
|
|
536
|
-
const group = maskGroups[name];
|
|
537
|
-
groups[name] = countWeights(group && group.weights, `groups.${name}.weights`);
|
|
538
|
-
}
|
|
539
|
-
stats.groups = groups;
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
return stats;
|
|
543
|
-
}
|