cozyclay 2.1.0 → 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 +65 -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 +22 -6
- 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-BlT33CGf.js → first-shot-handoff-CC4xe4HM.js} +1 -1
- package/dist/assets/{landing-BSesrCpL.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/llms.txt +14 -0
- 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 +174 -2639
- 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 +48 -419
- 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/PoseImagePanel.jsx +1 -1
- 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 +11 -53
- package/src/shell/CompositionGuideButton.jsx +20 -0
- 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/ShotPreviewHeader.jsx +17 -0
- package/src/shell/TopBar.jsx +5 -83
- package/src/shell/ViewportToolbar.jsx +23 -93
- 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/shell/viewport.css +12 -0
- 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 +46 -16
- package/src/studio-agent-motion.js +9 -9
- package/src/studio-agent-protocol.js +46 -73
- package/src/studio-app-binding.js +29 -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/headless-studio.mjs +85 -0
- package/tools/knip-baseline.json +0 -179
- package/tools/loc-baseline.json +33 -83
- 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 -124
- package/bin/agent/motion-runtime.mjs +0 -76
- package/dist/ardy/cskel27-rest.json +0 -557
- package/dist/assets/analytics-B41FmPYI.js +0 -1
- package/dist/assets/app-Do-Og5Pu.css +0 -1
- package/dist/assets/app-DwP-tl2x.js +0 -4920
- package/dist/assets/config-Bx7pYsD9.js +0 -1
- package/dist/assets/config-DeoFTYzb.css +0 -1
- package/dist/assets/demo-Dt0qZfS7.js +0 -1
- package/dist/assets/scene-asset-sync-BCWDJB1d.js +0 -19
- package/dist/assets/scene-asset-sync-DjJ_cJ2R.css +0 -1
- package/dist/assets/ticket-jqMhqL88.js +0 -1
- package/dist/assets/workflow-Br-K7Srs.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 -159
- 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,108 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* compare-seam.mjs — measure what a multi-segment take does AT THE SEAM,
|
|
3
|
-
* comparably across backends that run at different frame rates.
|
|
4
|
-
*
|
|
5
|
-
* ARDY-Core runs at 20 fps and Kimodo-SOMA at 30, so a per-FRAME root
|
|
6
|
-
* displacement means different things in each file and cannot be compared
|
|
7
|
-
* directly — every figure here is per SECOND.
|
|
8
|
-
*
|
|
9
|
-
* Two different failures are reported because they are not the same thing:
|
|
10
|
-
* - a STALL: root speed collapsing toward zero at the boundary, which is the
|
|
11
|
-
* "the motion stops between the two prompts" symptom.
|
|
12
|
-
* - a JUMP: a one-frame teleport, which is a discontinuity rather than a stall.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import { readNpz } from "./read-npz.mjs";
|
|
16
|
-
|
|
17
|
-
/** Root speed per frame, in m/s. */
|
|
18
|
-
export function rootSpeeds(rootPos, frames, fps) {
|
|
19
|
-
const speeds = [];
|
|
20
|
-
for (let frame = 1; frame < frames; frame += 1) {
|
|
21
|
-
const a = frame * 3;
|
|
22
|
-
const b = (frame - 1) * 3;
|
|
23
|
-
speeds.push(
|
|
24
|
-
Math.hypot(rootPos[a] - rootPos[b], rootPos[a + 1] - rootPos[b + 1], rootPos[a + 2] - rootPos[b + 2]) * fps
|
|
25
|
-
);
|
|
26
|
-
}
|
|
27
|
-
return speeds;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* @param {Float32Array} rootPos flat [frames*3]
|
|
32
|
-
* @param {number} boundaryFrame first frame of the second segment
|
|
33
|
-
* @param {number} windowS how far either side of the seam to look
|
|
34
|
-
*/
|
|
35
|
-
export function seamReport(rootPos, frames, fps, boundaryFrame, windowS = 0.25) {
|
|
36
|
-
const speeds = rootSpeeds(rootPos, frames, fps);
|
|
37
|
-
const half = Math.max(1, Math.round(windowS * fps));
|
|
38
|
-
// speeds[i] is the step from frame i to i+1.
|
|
39
|
-
const seamIndex = boundaryFrame - 1;
|
|
40
|
-
const from = Math.max(0, seamIndex - half);
|
|
41
|
-
const to = Math.min(speeds.length - 1, seamIndex + half);
|
|
42
|
-
|
|
43
|
-
const window = speeds.slice(from, to + 1);
|
|
44
|
-
const outsideBefore = speeds.slice(Math.max(0, from - half), from);
|
|
45
|
-
const outsideAfter = speeds.slice(to + 1, Math.min(speeds.length, to + 1 + half));
|
|
46
|
-
const surrounding = [...outsideBefore, ...outsideAfter];
|
|
47
|
-
|
|
48
|
-
const mean = (xs) => (xs.length ? xs.reduce((a, b) => a + b, 0) / xs.length : 0);
|
|
49
|
-
const cruise = mean(surrounding);
|
|
50
|
-
const seamMin = window.length ? Math.min(...window) : 0;
|
|
51
|
-
const seamMax = window.length ? Math.max(...window) : 0;
|
|
52
|
-
|
|
53
|
-
return {
|
|
54
|
-
fps,
|
|
55
|
-
frames,
|
|
56
|
-
boundaryFrame,
|
|
57
|
-
seam_speed_at_boundary_mps: speeds[seamIndex] ?? null,
|
|
58
|
-
seam_min_speed_mps: seamMin,
|
|
59
|
-
seam_max_speed_mps: seamMax,
|
|
60
|
-
cruise_speed_mps: cruise,
|
|
61
|
-
// 1.0 = the seam keeps the surrounding pace; 0.0 = a dead stop.
|
|
62
|
-
stall_ratio: cruise > 0 ? seamMin / cruise : null,
|
|
63
|
-
overall_max_speed_mps: Math.max(...speeds),
|
|
64
|
-
overall_mean_speed_mps: mean(speeds),
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function loadRoot(path) {
|
|
69
|
-
const members = readNpz(path);
|
|
70
|
-
const root = members.root_positions ?? members.smooth_root_pos;
|
|
71
|
-
if (!root) throw new Error(`${path}: no root_positions member`);
|
|
72
|
-
const frames = root.shape[0];
|
|
73
|
-
return { rootPos: root.data, frames };
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Only act as a CLI when executed directly; seam-batch.mjs imports seamReport.
|
|
77
|
-
const invokedDirectly = process.argv[1] && process.argv[1].endsWith("compare-seam.mjs");
|
|
78
|
-
const [, , ...args] = process.argv;
|
|
79
|
-
if (!invokedDirectly) {
|
|
80
|
-
// imported as a library
|
|
81
|
-
} else if (args.length % 3 !== 0 || args.length === 0) {
|
|
82
|
-
console.error('usage: compare-seam.mjs <label> <npz> <fps> [<label> <npz> <fps> ...]');
|
|
83
|
-
process.exit(2);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if (invokedDirectly) {
|
|
87
|
-
const rows = [];
|
|
88
|
-
for (let index = 0; index < args.length; index += 3) {
|
|
89
|
-
const label = args[index];
|
|
90
|
-
const path = args[index + 1];
|
|
91
|
-
const fps = Number(args[index + 2]);
|
|
92
|
-
const { rootPos, frames } = loadRoot(path);
|
|
93
|
-
// Both takes here are two equal-length segments, so the seam is the middle.
|
|
94
|
-
const report = seamReport(rootPos, frames, fps, Math.round(frames / 2));
|
|
95
|
-
rows.push({ label, ...report });
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
for (const row of rows) {
|
|
99
|
-
console.log(`\n=== ${row.label} ===`);
|
|
100
|
-
console.log(` ${row.frames} frames @ ${row.fps} fps (seam at frame ${row.boundaryFrame})`);
|
|
101
|
-
console.log(` cruise speed around seam : ${row.cruise_speed_mps.toFixed(3)} m/s`);
|
|
102
|
-
console.log(` slowest speed at seam : ${row.seam_min_speed_mps.toFixed(3)} m/s`);
|
|
103
|
-
console.log(` STALL RATIO : ${row.stall_ratio.toFixed(3)} (1.0 = no stall, 0.0 = dead stop)`);
|
|
104
|
-
console.log(` fastest speed at seam : ${row.seam_max_speed_mps.toFixed(3)} m/s`);
|
|
105
|
-
console.log(` overall max / mean : ${row.overall_max_speed_mps.toFixed(3)} / ${row.overall_mean_speed_mps.toFixed(3)} m/s`);
|
|
106
|
-
}
|
|
107
|
-
console.log("");
|
|
108
|
-
}
|
|
@@ -1,244 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* constraints.mjs — CozyClay's authored root path → Kimodo's `--constraints` JSON.
|
|
3
|
-
*
|
|
4
|
-
* Two coordinate translations happen here, and both are silent-bug shaped, so
|
|
5
|
-
* each is pinned by test/verify-kimodo-waypoints.mjs:
|
|
6
|
-
*
|
|
7
|
-
* 1. FRAME SPACE. CozyClay authors waypoints against the app's clip, which runs
|
|
8
|
-
* at ARDY's 20 fps (src/App.jsx ARDY_FPS). Kimodo generates at 30 and the
|
|
9
|
-
* take is retimed back down afterwards, so a constraint frame must be scaled
|
|
10
|
-
* onto the GENERATION clock or the path lands at 2/3 of the intended time.
|
|
11
|
-
*
|
|
12
|
-
* 2. ORIGIN. Kimodo canonicalises training motion so the smoothed root starts at
|
|
13
|
-
* XZ (0,0) on frame 0, and the docs require constraints to be authored
|
|
14
|
-
* against that canonical origin. CozyClay waypoints are absolute world
|
|
15
|
-
* metres, so the whole path is translated by its own first waypoint. Feeding
|
|
16
|
-
* absolute coordinates straight in would ask the character to teleport to its
|
|
17
|
-
* world position on frame 0.
|
|
18
|
-
*
|
|
19
|
-
* 3. SEGMENT SPACE. Kimodo generates each prompt segment separately. For every
|
|
20
|
-
* segment after the first it shifts that segment's constraints to the origin
|
|
21
|
-
* (translate_2d(observed_motion, -last_smooth_root_2d)), generates, then adds
|
|
22
|
-
* last_smooth_root_2d back — where last_smooth_root_2d is the segment's
|
|
23
|
-
* ACTUAL GENERATED start. `crop_move` only re-indexes frames; it never moves
|
|
24
|
-
* smooth_root_2d. So a constraint in segment N must be a DISPLACEMENT FROM
|
|
25
|
-
* THAT SEGMENT'S START, and an absolute whole-clip target lands there as a
|
|
26
|
-
* doubled offset that yanks the root at the boundary.
|
|
27
|
-
*
|
|
28
|
-
* This was isolated by measurement on a real 2-segment take, not inferred:
|
|
29
|
-
* no waypoints -> boundary jump 0.105 m
|
|
30
|
-
* waypoints inside segment 1 -> boundary jump 0.084 m
|
|
31
|
-
* absolute waypoints in seg 2 -> boundary jump 2.353 m
|
|
32
|
-
* A first attempt that re-anchored each segment onto its own first waypoint
|
|
33
|
-
* was measured WORSE still (4.48 m, waypoint error 3.55 m) because it
|
|
34
|
-
* discarded the displacement the author actually asked for.
|
|
35
|
-
*
|
|
36
|
-
* `global_root_heading` is a [cos, sin] direction PAIR per constrained frame,
|
|
37
|
-
* NOT a radian — passing radians through would be accepted as a number pair and
|
|
38
|
-
* silently steer the character wrong, so heading is converted, never forwarded.
|
|
39
|
-
*/
|
|
40
|
-
|
|
41
|
-
export const ROOT2D_TYPE = "root2d";
|
|
42
|
-
|
|
43
|
-
function requireFinite(value, label) {
|
|
44
|
-
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
45
|
-
throw new Error(`buildRoot2dConstraints: ${label} must be a finite number, got ${JSON.stringify(value)}`);
|
|
46
|
-
}
|
|
47
|
-
return value;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* @param {Array<{frame:number,x:number,z:number,heading:number|null}>} waypoints
|
|
52
|
-
* @param {{appFps:number, genFps:number, genFrames:number, segmentBoundaries?:number[],
|
|
53
|
-
* segmentStarts?:number[], transitionFrames?:number}} options
|
|
54
|
-
* `segmentBoundaries` are APP-space frames where a new prompt segment begins
|
|
55
|
-
* (excluding 0). Omit or pass [] for a single-segment take.
|
|
56
|
-
* `segmentStarts` are the same boundaries already in GENERATION space, used
|
|
57
|
-
* to keep constraints clear of each segment's transition window.
|
|
58
|
-
* @returns {Array<object>} Kimodo constraint entries (empty when nothing is constrained)
|
|
59
|
-
*/
|
|
60
|
-
export function buildRoot2dConstraints(
|
|
61
|
-
waypoints,
|
|
62
|
-
{ appFps, genFps, genFrames, segmentBoundaries = [], segmentStarts = [], transitionFrames = 5, densifyStride = 0 } = {}
|
|
63
|
-
) {
|
|
64
|
-
if (!Number.isInteger(densifyStride) || densifyStride < 0) {
|
|
65
|
-
throw new Error(`buildRoot2dConstraints: densifyStride must be a non-negative integer, got ${JSON.stringify(densifyStride)}`);
|
|
66
|
-
}
|
|
67
|
-
if (!waypoints || waypoints.length === 0) return [];
|
|
68
|
-
if (!Array.isArray(waypoints)) {
|
|
69
|
-
throw new Error("buildRoot2dConstraints: waypoints must be an array");
|
|
70
|
-
}
|
|
71
|
-
requireFinite(appFps, "appFps");
|
|
72
|
-
requireFinite(genFps, "genFps");
|
|
73
|
-
requireFinite(genFrames, "genFrames");
|
|
74
|
-
if (genFrames < 1) throw new Error(`buildRoot2dConstraints: genFrames must be >= 1, got ${genFrames}`);
|
|
75
|
-
|
|
76
|
-
const scale = genFps / appFps;
|
|
77
|
-
const lastFrame = Math.round(genFrames) - 1;
|
|
78
|
-
|
|
79
|
-
// Validate and normalise in author order first, so a bad path is refused
|
|
80
|
-
// before any of it is translated.
|
|
81
|
-
let previousFrame = -1;
|
|
82
|
-
const normalised = waypoints.map((waypoint, index) => {
|
|
83
|
-
if (!waypoint || typeof waypoint !== "object") {
|
|
84
|
-
throw new Error(`buildRoot2dConstraints: waypoints[${index}] must be an object`);
|
|
85
|
-
}
|
|
86
|
-
const frame = waypoint.frame;
|
|
87
|
-
if (!Number.isInteger(frame) || frame < 0) {
|
|
88
|
-
throw new Error(
|
|
89
|
-
`buildRoot2dConstraints: waypoints[${index}].frame must be a non-negative integer, got ${JSON.stringify(frame)}`
|
|
90
|
-
);
|
|
91
|
-
}
|
|
92
|
-
if (frame <= previousFrame) {
|
|
93
|
-
throw new Error(
|
|
94
|
-
`buildRoot2dConstraints: waypoint frames must be strictly ascending; waypoints[${index}].frame ${frame} follows ${previousFrame}`
|
|
95
|
-
);
|
|
96
|
-
}
|
|
97
|
-
previousFrame = frame;
|
|
98
|
-
const x = requireFinite(waypoint.x, `waypoints[${index}].x`);
|
|
99
|
-
const z = requireFinite(waypoint.z, `waypoints[${index}].z`);
|
|
100
|
-
const heading = waypoint.heading;
|
|
101
|
-
if (heading !== null && heading !== undefined) {
|
|
102
|
-
requireFinite(heading, `waypoints[${index}].heading`);
|
|
103
|
-
}
|
|
104
|
-
// A frame past the generated clip is CLAMPED rather than dropped: the
|
|
105
|
-
// author asked for the path to end there, and Kimodo rejects an index
|
|
106
|
-
// outside the clip, so the honest reading is "as late as possible".
|
|
107
|
-
const genFrame = Math.min(lastFrame, Math.max(0, Math.round(frame * scale)));
|
|
108
|
-
return { genFrame, x, z, heading: heading ?? null };
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
// Two authored frames can round onto one generation frame (a downscale, or
|
|
112
|
-
// waypoints one app-frame apart). Duplicate frame_indices would constrain a
|
|
113
|
-
// single frame to two different targets, so the LAST author wins — matching
|
|
114
|
-
// "the most recent instruction for that moment".
|
|
115
|
-
const byFrame = new Map();
|
|
116
|
-
for (const entry of normalised) byFrame.set(entry.genFrame, entry);
|
|
117
|
-
const points = [...byFrame.values()].sort((a, b) => a.genFrame - b.genFrame);
|
|
118
|
-
|
|
119
|
-
// The AUTHORED polyline, frozen before any nudge or densification mutates
|
|
120
|
-
// `points`: every later interpolation samples what the author drew, never a
|
|
121
|
-
// constraint that has already been moved for Kimodo's benefit.
|
|
122
|
-
const authored = points.map((point) => ({ ...point }));
|
|
123
|
-
|
|
124
|
-
// Where does the authored path sit when each segment BEGINS? That position is
|
|
125
|
-
// the origin Kimodo will re-zero this segment to, so every constraint inside
|
|
126
|
-
// the segment is emitted as a displacement from it. The path is sampled
|
|
127
|
-
// (linearly, matching a straight walk between pins) rather than snapped to a
|
|
128
|
-
// waypoint, because a segment need not start on one.
|
|
129
|
-
const genBoundaries = [...new Set(segmentBoundaries.map((frame) => Math.round(frame * scale)))].sort(
|
|
130
|
-
(a, b) => a - b
|
|
131
|
-
);
|
|
132
|
-
const sampleAt = (genFrame) => {
|
|
133
|
-
if (genFrame <= authored[0].genFrame) return authored[0];
|
|
134
|
-
const last = authored[authored.length - 1];
|
|
135
|
-
if (genFrame >= last.genFrame) return last;
|
|
136
|
-
for (let index = 1; index < authored.length; index += 1) {
|
|
137
|
-
const a = authored[index - 1];
|
|
138
|
-
const b = authored[index];
|
|
139
|
-
if (genFrame <= b.genFrame) {
|
|
140
|
-
const span = b.genFrame - a.genFrame;
|
|
141
|
-
const t = span === 0 ? 0 : (genFrame - a.genFrame) / span;
|
|
142
|
-
return { x: a.x + (b.x - a.x) * t, z: a.z + (b.z - a.z) * t };
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
return last;
|
|
146
|
-
};
|
|
147
|
-
const segmentStartOf = (genFrame) => {
|
|
148
|
-
let start = 0;
|
|
149
|
-
for (const boundary of genBoundaries) if (genFrame >= boundary) start = boundary;
|
|
150
|
-
return start;
|
|
151
|
-
};
|
|
152
|
-
const originCache = new Map();
|
|
153
|
-
const originFor = (genFrame) => {
|
|
154
|
-
const start = segmentStartOf(genFrame);
|
|
155
|
-
if (!originCache.has(start)) originCache.set(start, start === 0 ? points[0] : sampleAt(start));
|
|
156
|
-
return originCache.get(start);
|
|
157
|
-
};
|
|
158
|
-
|
|
159
|
-
// Keep constraints out of each segment's TRANSITION WINDOW. For every segment
|
|
160
|
-
// after the first, Kimodo builds its own FullBody + EndEffector constraints
|
|
161
|
-
// over that segment's first `num_transition_frames` from the previous
|
|
162
|
-
// segment's tail. A root2d constraint in the same window disagrees with them
|
|
163
|
-
// and the root teleports at the seam: measured 2.353 m with the constraint on
|
|
164
|
-
// the boundary against 0.400 m ten frames later, same path and seed.
|
|
165
|
-
//
|
|
166
|
-
// Only the TIMING moves; the authored position is preserved exactly, because
|
|
167
|
-
// dropping the constraint would silently discard what the author asked for.
|
|
168
|
-
if (segmentStarts.length > 0 && points.length > 0) {
|
|
169
|
-
const windows = [...new Set(segmentStarts.map((frame) => Math.round(frame)))].sort((a, b) => a - b);
|
|
170
|
-
for (const point of points) {
|
|
171
|
-
for (const start of windows) {
|
|
172
|
-
if (point.genFrame >= start && point.genFrame < start + transitionFrames) {
|
|
173
|
-
point.genFrame = Math.min(lastFrame, start + transitionFrames);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
// A nudge can land on a frame another waypoint already owns, and duplicate
|
|
178
|
-
// indices would constrain one frame twice; the later author wins, matching
|
|
179
|
-
// the dedupe above.
|
|
180
|
-
const deduped = new Map();
|
|
181
|
-
for (const point of points) deduped.set(point.genFrame, point);
|
|
182
|
-
points.length = 0;
|
|
183
|
-
points.push(...[...deduped.values()].sort((a, b) => a.genFrame - b.genFrame));
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// DENSIFICATION — the seam fix. Kimodo generates each prompt segment
|
|
187
|
-
// separately and crops constraints with a half-open [start, end): a waypoint
|
|
188
|
-
// ON a boundary belongs entirely to the segment that starts there, so the
|
|
189
|
-
// segment BEFORE it never learns the path exists, ends wherever it likes, and
|
|
190
|
-
// the root teleports at the seam (measured 2.353 m) to satisfy the constraint
|
|
191
|
-
// one frame later. Laying interpolated samples of the authored polyline every
|
|
192
|
-
// `densifyStride` generation frames tells every segment where the path goes,
|
|
193
|
-
// so each one arrives at its boundary already in place. Samples stay strictly
|
|
194
|
-
// inside [first, last] authored frames (constraining past the path's end
|
|
195
|
-
// would invent motion) and out of every transition window (Kimodo owns those
|
|
196
|
-
// frames with its own constraints; see the nudge above).
|
|
197
|
-
if (densifyStride > 0 && points.length >= 2) {
|
|
198
|
-
const windows = [...new Set(segmentStarts.map((frame) => Math.round(frame)))];
|
|
199
|
-
const inWindow = (frame) => windows.some((start) => frame >= start && frame < start + transitionFrames);
|
|
200
|
-
const taken = new Set(points.map((point) => point.genFrame));
|
|
201
|
-
const first = authored[0].genFrame;
|
|
202
|
-
const last = authored[authored.length - 1].genFrame;
|
|
203
|
-
for (let frame = first + densifyStride; frame < last; frame += densifyStride) {
|
|
204
|
-
if (taken.has(frame) || inWindow(frame)) continue;
|
|
205
|
-
const { x, z } = sampleAt(frame);
|
|
206
|
-
points.push({ genFrame: frame, x, z, heading: null });
|
|
207
|
-
taken.add(frame);
|
|
208
|
-
}
|
|
209
|
-
points.sort((a, b) => a.genFrame - b.genFrame);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
const anyHeading = points.some((point) => point.heading !== null);
|
|
213
|
-
|
|
214
|
-
// A lone waypoint at the origin carries no displacement and no heading, so
|
|
215
|
-
// it constrains nothing; emitting it would hand Kimodo a degenerate entry.
|
|
216
|
-
if (points.length === 1 && !anyHeading) return [];
|
|
217
|
-
|
|
218
|
-
const entry = {
|
|
219
|
-
type: ROOT2D_TYPE,
|
|
220
|
-
frame_indices: points.map((point) => point.genFrame),
|
|
221
|
-
smooth_root_2d: points.map((point) => {
|
|
222
|
-
const origin = originFor(point.genFrame);
|
|
223
|
-
return [point.x - origin.x, point.z - origin.z];
|
|
224
|
-
}),
|
|
225
|
-
};
|
|
226
|
-
|
|
227
|
-
if (anyHeading) {
|
|
228
|
-
// Every constrained frame needs a pair so the array stays index-aligned
|
|
229
|
-
// with frame_indices. An unauthored heading borrows the next authored
|
|
230
|
-
// one (falling back to the previous), which keeps the character facing
|
|
231
|
-
// along the path it is about to follow rather than snapping at the
|
|
232
|
-
// first authored pin.
|
|
233
|
-
const resolved = points.map((point) => point.heading);
|
|
234
|
-
for (let index = resolved.length - 1; index >= 0; index -= 1) {
|
|
235
|
-
if (resolved[index] === null) resolved[index] = resolved[index + 1] ?? null;
|
|
236
|
-
}
|
|
237
|
-
for (let index = 0; index < resolved.length; index += 1) {
|
|
238
|
-
if (resolved[index] === null) resolved[index] = resolved[index - 1] ?? 0;
|
|
239
|
-
}
|
|
240
|
-
entry.global_root_heading = resolved.map((radians) => [Math.cos(radians), Math.sin(radians)]);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
return [entry];
|
|
244
|
-
}
|
package/tools/kimodo/edit.mjs
DELETED
|
@@ -1,259 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* edit.mjs — regenerate one span of an existing take with Kimodo.
|
|
3
|
-
*
|
|
4
|
-
* ARDY does this by feeding the surrounding motion in as autoregressive
|
|
5
|
-
* history. Kimodo has no history input, so the same intent is expressed with the
|
|
6
|
-
* constraint protocol it does have: the span is regenerated as part of a whole
|
|
7
|
-
* clip that is PINNED to the original take on both sides of the edit, and only
|
|
8
|
-
* the edited span is kept.
|
|
9
|
-
*
|
|
10
|
-
* frames < start anchored to the source -> the take rejoins what came before
|
|
11
|
-
* frames in [start,end) free, except the author's own IK keyframes
|
|
12
|
-
* frames >= end anchored to the source -> the take rejoins what comes after
|
|
13
|
-
*
|
|
14
|
-
* Anchors are ordinary `fullbody` constraints built by pose-constraints.mjs, the
|
|
15
|
-
* same ones pose pinning uses and which measured 0.00 deg tracking, so the joins
|
|
16
|
-
* are as tight as a pinned pose rather than a blend.
|
|
17
|
-
*
|
|
18
|
-
* The result is spliced back with replaceMotionSegment: everything outside the
|
|
19
|
-
* edited range is copied from the source byte for byte, because the author
|
|
20
|
-
* changed one span and expects the rest of their take untouched.
|
|
21
|
-
*
|
|
22
|
-
* ROUND 2 (contract C5) — THE AUTHOR'S KEYS MAY BE EFFECTOR-SCOPED
|
|
23
|
-
* ---------------------------------------------------------------
|
|
24
|
-
* A `fullbody` key freezes all 77 joints at that frame. For an ANCHOR that is the
|
|
25
|
-
* point: the frame is context we want held exactly. For the author's own key it is
|
|
26
|
-
* the bug C5 exists to remove — dragging one hand should move the arm, not weld
|
|
27
|
-
* the legs, the spine and the head to a pose the user never authored.
|
|
28
|
-
*
|
|
29
|
-
* So planEditConstraints splits the two kinds of frame:
|
|
30
|
-
*
|
|
31
|
-
* anchors -> ALWAYS fullbody, in every mode
|
|
32
|
-
* author keyframes -> end-effector constraints IFF every edited track in the
|
|
33
|
-
* manifest maps to a limb chain, else fullbody
|
|
34
|
-
*
|
|
35
|
-
* "EVERY track" is the condition, not "any": the manifest's tracks describe ONE
|
|
36
|
-
* authored pose per frame, and an edit that also moved the spine or the head has
|
|
37
|
-
* no effector to carry those bones — emitting hand constraints for it would
|
|
38
|
-
* silently drop half the user's edit. Mixed or non-effector track lists therefore
|
|
39
|
-
* keep round 1's behaviour wholesale, and the chosen mode is REPORTED
|
|
40
|
-
* (`constraintMode`) rather than inferred, so the wrapper can log which pinning a
|
|
41
|
-
* take actually got.
|
|
42
|
-
*
|
|
43
|
-
* A CAVEAT WORTH REPEATING AT THE SWITCH (effector-constraints.mjs states it in
|
|
44
|
-
* full): an EE constraint still pins root XZ, root height and heading at its
|
|
45
|
-
* frames — Kimodo's EndEffectorConstraintSet appends them unconditionally and has
|
|
46
|
-
* no flag to disable it. On an edit that is harmless by construction: every pose
|
|
47
|
-
* here is either read back out of the source take or authored on top of it, so
|
|
48
|
-
* the pinned root is the take's own root at that frame. It is NOT a free root.
|
|
49
|
-
*/
|
|
50
|
-
|
|
51
|
-
import { readFileSync } from "node:fs";
|
|
52
|
-
import { dirname, isAbsolute, resolve } from "node:path";
|
|
53
|
-
import { buildEffectorConstraints, effectorTypeForTrack } from "./effector-constraints.mjs";
|
|
54
|
-
import { buildFullBodyConstraints } from "./pose-constraints.mjs";
|
|
55
|
-
import { readNpz } from "./read-npz.mjs";
|
|
56
|
-
|
|
57
|
-
/** How many source frames to pin on each side when the caller asks for context. */
|
|
58
|
-
const ANCHOR_SAMPLES = 3;
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Read the bridge's edit-manifest.json into a resolved plan.
|
|
62
|
-
* Paths inside it are relative to the manifest, which is what the bridge writes.
|
|
63
|
-
*/
|
|
64
|
-
export function parseEditManifest(manifestPath) {
|
|
65
|
-
const raw = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
66
|
-
const base = dirname(resolve(manifestPath));
|
|
67
|
-
|
|
68
|
-
if (!Number.isInteger(raw.start_frame) || raw.start_frame < 0) {
|
|
69
|
-
throw new Error(`parseEditManifest: start_frame must be a non-negative integer, got ${JSON.stringify(raw.start_frame)}`);
|
|
70
|
-
}
|
|
71
|
-
if (!Number.isInteger(raw.end_frame)) {
|
|
72
|
-
throw new Error(`parseEditManifest: end_frame must be an integer, got ${JSON.stringify(raw.end_frame)}`);
|
|
73
|
-
}
|
|
74
|
-
if (raw.end_frame <= raw.start_frame) {
|
|
75
|
-
throw new Error(`parseEditManifest: range ${raw.start_frame}..${raw.end_frame} is empty or inverted`);
|
|
76
|
-
}
|
|
77
|
-
if (!Array.isArray(raw.edits) || raw.edits.length === 0) {
|
|
78
|
-
throw new Error("parseEditManifest: edits must be a non-empty array");
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
let previous = -1;
|
|
82
|
-
const edits = raw.edits.map((entry, index) => {
|
|
83
|
-
if (!entry || typeof entry !== "object") {
|
|
84
|
-
throw new Error(`parseEditManifest: edits[${index}] must be an object`);
|
|
85
|
-
}
|
|
86
|
-
if (!Number.isInteger(entry.frame)) {
|
|
87
|
-
throw new Error(`parseEditManifest: edits[${index}].frame must be an integer`);
|
|
88
|
-
}
|
|
89
|
-
if (entry.frame < raw.start_frame || entry.frame >= raw.end_frame) {
|
|
90
|
-
throw new Error(
|
|
91
|
-
`parseEditManifest: edits[${index}].frame ${entry.frame} is outside the edited range ${raw.start_frame}..${raw.end_frame}`
|
|
92
|
-
);
|
|
93
|
-
}
|
|
94
|
-
if (entry.frame <= previous) {
|
|
95
|
-
throw new Error(
|
|
96
|
-
`parseEditManifest: edit frames must be strictly ascending; edits[${index}].frame ${entry.frame} follows ${previous}`
|
|
97
|
-
);
|
|
98
|
-
}
|
|
99
|
-
previous = entry.frame;
|
|
100
|
-
if (typeof entry.pose_path !== "string" || !entry.pose_path) {
|
|
101
|
-
throw new Error(`parseEditManifest: edits[${index}].pose_path is required`);
|
|
102
|
-
}
|
|
103
|
-
return {
|
|
104
|
-
frame: entry.frame,
|
|
105
|
-
tracks: Array.isArray(entry.tracks) ? entry.tracks : [],
|
|
106
|
-
posePath: isAbsolute(entry.pose_path) ? entry.pose_path : resolve(base, entry.pose_path),
|
|
107
|
-
};
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
return { startFrame: raw.start_frame, endFrame: raw.end_frame, edits };
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/** One frame of a cclay motion npz as the {local_rot_mats, posed_joints} a pose wants. */
|
|
114
|
-
function frameAsPose(members, frame) {
|
|
115
|
-
const rot = members.local_rot_mats;
|
|
116
|
-
const pos = members.posed_joints;
|
|
117
|
-
const joints = rot.shape.at(-3);
|
|
118
|
-
if (joints !== 27) throw new Error(`planEditConstraints: source motion has ${joints} joints, expected 27`);
|
|
119
|
-
const local_rot_mats = [];
|
|
120
|
-
const posed_joints = [];
|
|
121
|
-
for (let j = 0; j < 27; j += 1) {
|
|
122
|
-
const b = (frame * 27 + j) * 9;
|
|
123
|
-
local_rot_mats.push([
|
|
124
|
-
[rot.data[b], rot.data[b + 1], rot.data[b + 2]],
|
|
125
|
-
[rot.data[b + 3], rot.data[b + 4], rot.data[b + 5]],
|
|
126
|
-
[rot.data[b + 6], rot.data[b + 7], rot.data[b + 8]],
|
|
127
|
-
]);
|
|
128
|
-
const p = (frame * 27 + j) * 3;
|
|
129
|
-
posed_joints.push([pos.data[p], pos.data[p + 1], pos.data[p + 2]]);
|
|
130
|
-
}
|
|
131
|
-
return { local_rot_mats, posed_joints };
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/** A cclay pose npz (single keyframe) as the same shape. */
|
|
135
|
-
function poseFromNpz(path) {
|
|
136
|
-
const members = readNpz(path);
|
|
137
|
-
for (const key of ["local_rot_mats", "posed_joints"]) {
|
|
138
|
-
if (!members[key]) throw new Error(`planEditConstraints: pose npz ${path} is missing ${key}`);
|
|
139
|
-
}
|
|
140
|
-
return frameAsPose(members, 0);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Build the constraint set for an edit: the author's keyframes inside the range,
|
|
145
|
-
* plus source anchors on both sides so the regenerated span rejoins the take.
|
|
146
|
-
*
|
|
147
|
-
* `poses` are the same pins in APP frame space, which is what generateOnBox
|
|
148
|
-
* takes; `constraints` is the already-scaled Kimodo form, used by the tests and
|
|
149
|
-
* for reporting how many frames ended up pinned.
|
|
150
|
-
*
|
|
151
|
-
* @returns {{constraints:Array<object>, poses:Array<object>, startFrame:number, endFrame:number, sourceFrames:number, fps:number}}
|
|
152
|
-
*/
|
|
153
|
-
export function planEditConstraints({ sourcePath, manifestPath, contextBefore = 0, contextAfter = 0, genFps = 30, appFps = 24 }) {
|
|
154
|
-
const manifest = parseEditManifest(manifestPath);
|
|
155
|
-
const source = readNpz(sourcePath);
|
|
156
|
-
if (!source.local_rot_mats || !source.posed_joints) {
|
|
157
|
-
throw new Error(`planEditConstraints: ${sourcePath} is not a cclay motion npz`);
|
|
158
|
-
}
|
|
159
|
-
const sourceFrames = source.local_rot_mats.shape[0];
|
|
160
|
-
const fps = source.fps ? Math.round(source.fps.data[0]) : appFps;
|
|
161
|
-
if (manifest.endFrame > sourceFrames) {
|
|
162
|
-
throw new Error(
|
|
163
|
-
`planEditConstraints: edited range ends at ${manifest.endFrame} but the source has ${sourceFrames} frames`
|
|
164
|
-
);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// Anchor frames: the last few source frames before the range and the first
|
|
168
|
-
// few at/after it. Sampling several rather than one gives the model a
|
|
169
|
-
// direction to rejoin on, not just a point.
|
|
170
|
-
const anchors = [];
|
|
171
|
-
if (contextBefore > 0) {
|
|
172
|
-
const from = Math.max(0, manifest.startFrame - contextBefore);
|
|
173
|
-
const step = Math.max(1, Math.floor((manifest.startFrame - from) / ANCHOR_SAMPLES) || 1);
|
|
174
|
-
for (let f = from; f < manifest.startFrame; f += step) anchors.push(f);
|
|
175
|
-
}
|
|
176
|
-
if (contextAfter > 0) {
|
|
177
|
-
const to = Math.min(sourceFrames - 1, manifest.endFrame + contextAfter - 1);
|
|
178
|
-
const step = Math.max(1, Math.floor((to - manifest.endFrame) / ANCHOR_SAMPLES) || 1);
|
|
179
|
-
for (let f = manifest.endFrame; f <= to; f += step) anchors.push(f);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// Author keyframes win over an anchor on the same frame: the whole point of
|
|
183
|
-
// the edit is that those frames changed. `kind` rides along because round 2
|
|
184
|
-
// constrains the two differently — and this map is what GUARANTEES a frame can
|
|
185
|
-
// never carry both an anchor and an author key, i.e. never both a fullbody
|
|
186
|
-
// anchor and an EE edit.
|
|
187
|
-
const byFrame = new Map();
|
|
188
|
-
for (const frame of anchors) {
|
|
189
|
-
if (frame < 0 || frame >= sourceFrames) continue;
|
|
190
|
-
byFrame.set(frame, { frame, kind: "anchor", pose: frameAsPose(source, frame) });
|
|
191
|
-
}
|
|
192
|
-
for (const edit of manifest.edits) {
|
|
193
|
-
byFrame.set(edit.frame, { frame: edit.frame, kind: "edit", pose: poseFromNpz(edit.posePath) });
|
|
194
|
-
}
|
|
195
|
-
const poses = [...byFrame.values()].sort((a, b) => a.frame - b.frame);
|
|
196
|
-
|
|
197
|
-
// C5's switch, decided from the MANIFEST rather than from the surviving poses:
|
|
198
|
-
// an author key that lost its slot to app->gen frame rounding still describes
|
|
199
|
-
// what the user edited. EVERY edited track must map to a limb chain (see the
|
|
200
|
-
// header); an edit that names no tracks at all cannot claim one, so it is
|
|
201
|
-
// fullbody.
|
|
202
|
-
const editedTracks = [];
|
|
203
|
-
let allEffector = manifest.edits.length > 0;
|
|
204
|
-
for (const edit of manifest.edits) {
|
|
205
|
-
if (edit.tracks.length === 0) allEffector = false;
|
|
206
|
-
for (const track of edit.tracks) {
|
|
207
|
-
if (effectorTypeForTrack(track) === null) allEffector = false;
|
|
208
|
-
if (!editedTracks.includes(track)) editedTracks.push(track);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
const constraintMode = allEffector ? "effector" : "fullbody";
|
|
212
|
-
|
|
213
|
-
// The clip Kimodo generates runs at genFps; the manifest speaks in app frames.
|
|
214
|
-
// ONE scaling pass over the merged, sorted list, so the gen-frame dedup stays
|
|
215
|
-
// SHARED: two source frames that round onto the same generation frame keep the
|
|
216
|
-
// earlier one whatever kind it is, and an anchor and an author key can never
|
|
217
|
-
// both survive onto one frame in two different constraint entries.
|
|
218
|
-
const scale = genFps / appFps;
|
|
219
|
-
const genFrames = Math.max(1, Math.round(sourceFrames * scale));
|
|
220
|
-
const scaled = [];
|
|
221
|
-
const seen = new Set();
|
|
222
|
-
for (const entry of poses) {
|
|
223
|
-
const genFrame = Math.min(genFrames - 1, Math.max(0, Math.round(entry.frame * scale)));
|
|
224
|
-
if (seen.has(genFrame)) continue;
|
|
225
|
-
seen.add(genFrame);
|
|
226
|
-
scaled.push({ frame: genFrame, kind: entry.kind, pose: entry.pose });
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// fullbody mode is round 1 unchanged: ONE entry over every frame, anchors and
|
|
230
|
-
// author keys alike. Effector mode splits them — the anchors stay fullbody
|
|
231
|
-
// (they are context to hold exactly), the author's keys become one entry per
|
|
232
|
-
// distinct effector chain, over the edited frames only.
|
|
233
|
-
let constraints;
|
|
234
|
-
if (constraintMode === "effector") {
|
|
235
|
-
const anchorPoses = scaled.filter((entry) => entry.kind === "anchor");
|
|
236
|
-
const editPoses = scaled.filter((entry) => entry.kind === "edit");
|
|
237
|
-
constraints = [
|
|
238
|
-
...buildFullBodyConstraints(anchorPoses, { genFrames }),
|
|
239
|
-
...buildEffectorConstraints(editPoses, { genFrames, tracks: editedTracks }),
|
|
240
|
-
];
|
|
241
|
-
} else {
|
|
242
|
-
constraints = buildFullBodyConstraints(scaled, { genFrames });
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
return {
|
|
246
|
-
constraints,
|
|
247
|
-
// Which pinning the AUTHOR's keyframes got ("effector" | "fullbody"), and
|
|
248
|
-
// the tracks that decided it. Reported rather than re-derived downstream:
|
|
249
|
-
// run-edit-on-box logs it and the tests assert it.
|
|
250
|
-
constraintMode,
|
|
251
|
-
editedTracks,
|
|
252
|
-
poses,
|
|
253
|
-
startFrame: manifest.startFrame,
|
|
254
|
-
endFrame: manifest.endFrame,
|
|
255
|
-
sourceFrames,
|
|
256
|
-
fps,
|
|
257
|
-
genFrames,
|
|
258
|
-
};
|
|
259
|
-
}
|