cozyclay 2.1.1 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +60 -0
- package/README.md +12 -30
- package/bin/agent/agent-routes.mjs +40 -113
- package/bin/agent/agent-runner.mjs +9 -10
- package/bin/agent/cliproxy-provider.mjs +135 -0
- package/bin/agent/credential-store.mjs +30 -7
- package/bin/agent/pi-tools.mjs +4 -1
- package/bin/agent/provider-keys.mjs +32 -0
- package/bin/agent/provider-login.mjs +110 -0
- package/bin/agent/provider-routes.mjs +99 -0
- package/bin/agent/providers.mjs +19 -97
- package/bin/agent/script-tool.mjs +17 -4
- package/bin/agent/studio-motion-reference.mjs +23 -0
- package/bin/agent/studio-prompt.mjs +1 -1
- package/bin/agent/studio-rebase-text.mjs +32 -0
- package/bin/agent/studio-rebase.mjs +26 -39
- package/bin/agent/studio-tools.mjs +9 -28
- package/bin/agent/tool-arguments.mjs +49 -0
- package/bin/agent/turn-frames.mjs +66 -0
- package/bin/codex-auth.mjs +9 -2
- package/bin/cozyclay.mjs +10 -116
- package/bin/live/cli.mjs +3 -3
- package/bin/mcp-runtime.mjs +2 -12
- package/bin/package-signature.mjs +0 -4
- package/dist/app/index.html +7 -6
- package/dist/assets/EXRLoader-CvVIvGVE.js +1 -0
- package/dist/assets/analytics-C9sUnndd.js +1 -0
- package/dist/assets/app-CCh--1Hc.js +5087 -0
- package/dist/assets/app-f-fkV5uP.css +1 -0
- package/dist/assets/compile-worker-CkxiD6lH.js +5 -0
- package/dist/assets/fflate.module-KbGj5-H6.js +5 -0
- package/dist/assets/{first-shot-handoff-msLI71g1.js → first-shot-handoff-CC4xe4HM.js} +1 -1
- package/dist/assets/{landing-ChUvB_zL.js → landing-DjHD8u7k.js} +1 -1
- package/dist/assets/scene-asset-sync-Hx_FzdOH.js +20 -0
- package/dist/assets/scene-asset-sync-_S8NQl5B.css +1 -0
- package/dist/assets/workflow-C4yKH1p4.js +179 -0
- package/dist/cozyclay-package.json +1 -1
- package/dist/index.html +3 -3
- package/dist/sw.js +3 -6
- package/dist/workflow/index.html +5 -4
- package/mcp/LIVE-PROTOCOL.md +7 -47
- package/mcp/README.md +16 -26
- package/mcp/live-hub.mjs +6 -10
- package/mcp/package.json +1 -3
- package/mcp/tool-handlers.mjs +4 -168
- package/package.json +3 -22
- package/src/App.jsx +150 -2624
- package/src/agent-progress.js +79 -0
- package/src/agent-turn-mark.js +30 -0
- package/src/analytics.js +1 -19
- package/src/app-context.js +5 -0
- package/src/app-stage.jsx +2 -34
- package/src/asset-pane.jsx +6 -5
- package/src/camera-block.js +21 -0
- package/src/camera-direction.js +120 -0
- package/src/character-kind.js +3 -9
- package/src/command-bus.js +8 -6
- package/src/commands/cast.js +5 -34
- package/src/commands/elements/character.js +1 -1
- package/src/commands/elements/motion.js +1 -6
- package/src/commands/elements/object.js +6 -2
- package/src/commands/elements/shot.js +4 -1
- package/src/commands/elements.js +38 -14
- package/src/commands/index.js +3 -1
- package/src/commands/kits.js +37 -0
- package/src/commands/motion-timing.js +57 -0
- package/src/commands/motion.js +9 -152
- package/src/commands/motion3-compile.js +65 -0
- package/src/commands/motion3.js +207 -0
- package/src/commands/objects.js +6 -4
- package/src/commands/shot.js +49 -11
- package/src/commands/stage.js +14 -1
- package/src/commands/view.js +4 -1
- package/src/document-mirrors.js +42 -0
- package/src/document-store.js +4 -0
- package/src/domains/cast.js +19 -194
- package/src/domains/motion/motion-range-pin-rules.js +1 -1
- package/src/domains/motion/take-source.js +51 -0
- package/src/domains/motion.js +60 -1838
- package/src/domains/scenes.js +12 -55
- package/src/domains/shots.js +2 -2
- package/src/domains/stage.js +4 -2
- package/src/draft-ghost.js +46 -0
- package/src/draft-ghost.jsx +129 -0
- package/src/dualview.jsx +4 -3
- package/src/effects/EffectsLayer.jsx +112 -0
- package/src/effects/effect-meshes.js +232 -0
- package/src/effects/effect-record.js +103 -0
- package/src/effects/emitter.js +59 -0
- package/src/effects/simulate.js +261 -0
- package/src/effects/state.js +59 -0
- package/src/execution-telemetry.js +2 -3
- package/src/first-success-guide.jsx +5 -5
- package/src/ghost-set.js +257 -0
- package/src/held-object-edit.jsx +113 -0
- package/src/held-object-pose.js +175 -0
- package/src/hierarchy-model.js +1 -1
- package/src/hierarchy-panel.jsx +1 -1
- package/src/imported-mesh.jsx +103 -0
- package/src/kits/held.js +84 -0
- package/src/kits/index.js +70 -0
- package/src/kits/smoking.js +57 -0
- package/src/light-blockers.js +93 -0
- package/src/motion/beat-board-model.js +84 -0
- package/src/motion/beat-board.css +124 -0
- package/src/motion/beat-board.jsx +59 -0
- package/src/motion/center-of-mass.js +66 -0
- package/src/motion/fix-collisions.js +850 -0
- package/src/motion/physics-review.js +118 -0
- package/src/{ardy → motion}/physics-support.js +1 -76
- package/src/motion/rig-snapshot.js +22 -0
- package/src/motion/take-segments.jsx +140 -0
- package/src/{ardy → motion}/timeline-coordinates.js +0 -21
- package/src/{ardy → motion}/timeline.css +1 -2
- package/src/{ardy → motion}/timeline.jsx +42 -413
- package/src/motion/waypoints.js +76 -0
- package/src/motion3/beats.js +83 -0
- package/src/motion3/body.js +158 -0
- package/src/motion3/bones.js +47 -0
- package/src/motion3/carried.js +47 -0
- package/src/motion3/compile-job.js +75 -0
- package/src/motion3/compile-worker.js +10 -0
- package/src/motion3/compiler/anchors.js +105 -0
- package/src/motion3/compiler/arm-orient.js +128 -0
- package/src/motion3/compiler/channels.js +125 -0
- package/src/motion3/compiler/compile.js +205 -0
- package/src/motion3/compiler/gait-rhythm.js +97 -0
- package/src/motion3/compiler/grip.js +171 -0
- package/src/motion3/compiler/head-steady.js +48 -0
- package/src/motion3/compiler/hold.js +134 -0
- package/src/motion3/compiler/idle.js +113 -0
- package/src/motion3/compiler/limbs.js +170 -0
- package/src/motion3/compiler/math.js +72 -0
- package/src/motion3/compiler/pose.js +162 -0
- package/src/motion3/compiler/quat.js +49 -0
- package/src/motion3/compiler/skeleton.js +74 -0
- package/src/motion3/compiler/validate-common.js +32 -0
- package/src/motion3/compiler/validate-goals.js +118 -0
- package/src/motion3/compiler/validate-objects.js +143 -0
- package/src/motion3/compiler/validate-parts.js +116 -0
- package/src/motion3/compiler/validate.js +122 -0
- package/src/motion3/compiler/vocab.js +73 -0
- package/src/motion3/compiler/walk-pelvis.js +275 -0
- package/src/motion3/compiler/walk.js +207 -0
- package/src/motion3/compiler/wrist-clamp.js +32 -0
- package/src/motion3/delta.js +47 -0
- package/src/motion3/describe.js +174 -0
- package/src/motion3/docs.js +27 -0
- package/src/motion3/edit-carry.js +21 -0
- package/src/motion3/fix/ease.js +16 -0
- package/src/motion3/fix/ground.js +37 -0
- package/src/motion3/fix/index.js +52 -0
- package/src/motion3/fix/push.js +71 -0
- package/src/motion3/fix/reach.js +46 -0
- package/src/motion3/fix/retime.js +79 -0
- package/src/motion3/fix/self.js +211 -0
- package/src/motion3/fix/slide.js +70 -0
- package/src/motion3/fix/turn.js +51 -0
- package/src/motion3/follow.js +39 -0
- package/src/motion3/free-motion.js +173 -0
- package/src/motion3/grip-choice.js +130 -0
- package/src/motion3/hold-plan.js +157 -0
- package/src/motion3/json-patch.js +77 -0
- package/src/motion3/key-aim.js +105 -0
- package/src/motion3/key-edit.js +82 -0
- package/src/motion3/lenient.js +127 -0
- package/src/motion3/object-keys.js +236 -0
- package/src/motion3/object-pose.js +182 -0
- package/src/motion3/object-tracks.js +12 -0
- package/src/motion3/pipeline.js +102 -0
- package/src/motion3/playback.js +51 -0
- package/src/motion3/probes.js +91 -0
- package/src/motion3/qa/body-checks.js +34 -0
- package/src/motion3/qa/check-names.js +14 -0
- package/src/motion3/qa/checks.js +121 -0
- package/src/motion3/qa/contact-checks.js +51 -0
- package/src/motion3/qa/explain.js +80 -0
- package/src/motion3/qa/fmt.js +5 -0
- package/src/motion3/qa/foot-checks.js +72 -0
- package/src/motion3/qa/geometry.js +77 -0
- package/src/motion3/qa/goals.js +191 -0
- package/src/motion3/qa/ground.js +60 -0
- package/src/motion3/qa/hints-contact.js +179 -0
- package/src/motion3/qa/hints-goal.js +160 -0
- package/src/motion3/qa/hints-knee.js +24 -0
- package/src/motion3/qa/hints-motion.js +209 -0
- package/src/motion3/qa/hints-object.js +91 -0
- package/src/motion3/qa/index.js +42 -0
- package/src/motion3/qa/key-checks.js +82 -0
- package/src/motion3/qa/near.js +96 -0
- package/src/motion3/qa/object-checks.js +135 -0
- package/src/motion3/qa/patch.js +161 -0
- package/src/motion3/qa/severity.js +38 -0
- package/src/motion3/qa/spec-checks.js +65 -0
- package/src/motion3/qa/style.js +218 -0
- package/src/motion3/qa/wrist-checks.js +69 -0
- package/src/motion3/qa/wrist-obs.js +60 -0
- package/src/motion3/refs.js +120 -0
- package/src/motion3/repair-tune.js +121 -0
- package/src/motion3/repair.js +83 -0
- package/src/motion3/report.js +67 -0
- package/src/motion3/rigs/index.js +14 -0
- package/src/motion3/rigs/x-bot-tpose-body.json +1 -0
- package/src/motion3/rigs/x-bot-tpose.json +1 -0
- package/src/motion3/rigs/y-bot-tpose-body.json +1 -0
- package/src/motion3/rigs/y-bot-tpose.json +1 -0
- package/src/motion3/scene.js +60 -0
- package/src/motion3/schema/brief.js +89 -0
- package/src/motion3/schema/fields.js +44 -0
- package/src/motion3/schema/spec-examples.js +46 -0
- package/src/motion3/schema/topics.js +111 -0
- package/src/motion3/schema/types.js +147 -0
- package/src/motion3/search.js +117 -0
- package/src/motion3/second-hand.js +112 -0
- package/src/motion3/skills/basic.js +82 -0
- package/src/motion3/skills/book.js +76 -0
- package/src/motion3/skills/car.js +145 -0
- package/src/motion3/skills/frames-auto.js +29 -0
- package/src/motion3/skills/index.js +83 -0
- package/src/motion3/skills/info.js +65 -0
- package/src/motion3/skills/smoke-kit.js +109 -0
- package/src/motion3/skills/smoke.js +160 -0
- package/src/motion3/spec-store.js +59 -0
- package/src/motion3/take-edit.js +155 -0
- package/src/motion3/take.js +63 -0
- package/src/motion3/timeline.js +127 -0
- package/src/motion3/views.js +121 -0
- package/src/motion3/walk-plan.js +119 -0
- package/src/object-catalog.jsx +7 -1
- package/src/object-colors.js +98 -0
- package/src/object-gizmo-parts.js +115 -0
- package/src/object-gizmo.jsx +37 -122
- package/src/object-glow.js +139 -0
- package/src/object-hidden.js +33 -0
- package/src/object-look.js +196 -0
- package/src/object-size.js +10 -0
- package/src/object-travel.js +71 -6
- package/src/panels/CameraPanel.jsx +7 -7
- package/src/panels/HdrEnvironmentControls.jsx +59 -0
- package/src/panels/LightPanel.jsx +25 -4
- package/src/panels/ObjectLookPanel.jsx +88 -0
- package/src/panels/PosePanel.jsx +2 -17
- package/src/panels/RigControlPanel.jsx +5 -53
- package/src/panels/SubjectBox.jsx +4 -1
- package/src/panels/details.css +14 -0
- package/src/posestudio.jsx +4 -27
- package/src/project-browser.jsx +3 -4
- package/src/project-resources.js +18 -31
- package/src/project.js +13 -104
- package/src/props.jsx +25 -280
- package/src/rail-dot-drag.js +23 -0
- package/src/render-passes.js +10 -0
- package/src/resource-status.jsx +1 -0
- package/src/result-modal.jsx +4 -19
- package/src/room.jsx +20 -12
- package/src/scene-assets.js +8 -6
- package/src/scene-light-place.js +72 -0
- package/src/scene-lights.jsx +148 -0
- package/src/scene-objects.js +31 -139
- package/src/scenes.js +15 -105
- package/src/semantic-edit.js +2 -5
- package/src/set-pieces.jsx +183 -0
- package/src/settings-menu.jsx +3 -11
- package/src/shell/BottomDock.jsx +9 -51
- package/src/shell/DetailsSlot.jsx +16 -200
- package/src/shell/GhostsMenuGroup.jsx +74 -0
- package/src/shell/LibrarySlot.jsx +1 -3
- package/src/shell/MenuBar.jsx +3 -3
- package/src/shell/PreferencesDialog.jsx +15 -264
- package/src/shell/PreferencesSlot.jsx +2 -8
- package/src/shell/TopBar.jsx +5 -84
- package/src/shell/ViewportToolbar.jsx +10 -70
- package/src/shell/agent-glass.css +19 -6
- package/src/shell/details-selection.js +42 -0
- package/src/shell/ghosts-menu.css +22 -0
- package/src/shell/glass-regions.css +29 -3
- package/src/shell/preferences.css +6 -1
- package/src/shot-authoring.js +4 -7
- package/src/soft-shadows.js +37 -0
- package/src/stage-attach-frames.js +12 -12
- package/src/stage-canvas-rigs.jsx +3 -3
- package/src/stage-capture.jsx +42 -8
- package/src/stage-characters.jsx +22 -216
- package/src/stage-editor-gizmos.jsx +6 -6
- package/src/stage-environment.js +93 -0
- package/src/stage-hdr-builtins.js +107 -0
- package/src/stage-hdr-image.js +116 -0
- package/src/stage-hdr-library.js +70 -0
- package/src/stage-hdr-map.js +94 -0
- package/src/stage-hdr.js +130 -0
- package/src/stage-lighting.js +149 -0
- package/src/stage-occlusion.js +210 -0
- package/src/stage-sky.jsx +159 -0
- package/src/stage-startup-defaults.js +5 -9
- package/src/studio-actions.js +12 -14
- package/src/studio-agent-commands.js +19 -16
- package/src/studio-agent-motion.js +9 -9
- package/src/studio-agent-protocol.js +41 -71
- package/src/studio-app-binding.js +25 -45
- package/src/studio-asset-list.js +17 -0
- package/src/studio-contact-sheet.js +37 -5
- package/src/studio-elements.js +27 -7
- package/src/studio-entity-bounds.js +18 -0
- package/src/studio-error-codes.js +8 -0
- package/src/studio-export-framing.js +152 -0
- package/src/studio-lighting-check.js +103 -0
- package/src/studio-motion-visual.js +150 -0
- package/src/studio-schema-errors.js +80 -0
- package/src/studio-support-gap.js +74 -0
- package/src/styles.css +1 -19
- package/src/timeline-defaults.js +5 -0
- package/src/timeline-extent.js +8 -20
- package/src/tutorial-walk.js +17 -0
- package/src/ui-scrub.js +1 -1
- package/src/use-shot-camera-tracks.js +12 -0
- package/src/view-ghosts.js +97 -0
- package/src/view-ghosts.jsx +49 -0
- package/src/workflow/AgentPanel.jsx +47 -166
- package/src/workflow/AgentProgressCard.jsx +107 -0
- package/src/workflow/AgentReplyText.jsx +37 -0
- package/src/workflow/AgentTurnSteps.jsx +84 -0
- package/src/workflow/LoginPanel.jsx +140 -0
- package/src/workflow/WorkflowBuilder.jsx +7 -23
- package/src/workflow/agent-client.js +54 -51
- package/src/workflow/agent-panel.css +71 -1
- package/src/workflow/agent-progress-card.css +167 -0
- package/src/workflow/agent-progress-view.js +47 -0
- package/src/workflow/agent-turn-steps.css +113 -0
- package/src/workflow/agent-turn.js +191 -0
- package/src/workflow/login-client.js +34 -0
- package/src/workflow/motion-input.js +18 -31
- package/src/workflow/node-schema.js +1 -1
- package/src/workflow/reply-markdown.js +69 -0
- package/src/workflow/scene-asset-sync.js +8 -8
- package/tools/agent-session-stats.mjs +61 -0
- package/tools/dev-full.mjs +5 -56
- package/tools/eslint-baseline.json +12 -13
- package/tools/knip-baseline.json +0 -179
- package/tools/loc-baseline.json +31 -81
- package/tools/motion3/extract-body.mjs +103 -0
- package/tools/motion3/extract-rig.mjs +85 -0
- package/tools/process-supervisor.mjs +0 -88
- package/tools/run-tests.mjs +8 -124
- package/bin/agent/motion-runtime.mjs +0 -76
- package/dist/ardy/cskel27-rest.json +0 -557
- package/dist/assets/analytics-C9pMpW7s.js +0 -1
- package/dist/assets/app-B_2XB9rw.css +0 -1
- package/dist/assets/app-DAuvKPP8.js +0 -4920
- package/dist/assets/config-Bx7pYsD9.js +0 -1
- package/dist/assets/config-DeoFTYzb.css +0 -1
- package/dist/assets/demo-CvKuBgy5.js +0 -1
- package/dist/assets/scene-asset-sync-DKkpjKBV.js +0 -19
- package/dist/assets/scene-asset-sync-DjJ_cJ2R.css +0 -1
- package/dist/assets/ticket-BPk2Ni95.js +0 -1
- package/dist/assets/workflow-jJRBR94Z.js +0 -179
- package/dist/demo/walk-then-stop.npz +0 -0
- package/mcp/ardy-prompts.mjs +0 -218
- package/src/ardy/auto-fix-panel.css +0 -108
- package/src/ardy/auto-physics.js +0 -1194
- package/src/ardy/client.js +0 -153
- package/src/ardy/convert.js +0 -313
- package/src/ardy/cskel27-neutral.js +0 -39
- package/src/ardy/cskel27.js +0 -77
- package/src/ardy/export.js +0 -165
- package/src/ardy/fix-collisions.js +0 -2257
- package/src/ardy/foot-lock.js +0 -230
- package/src/ardy/motion-calibration.js +0 -118
- package/src/ardy/motion-edit.js +0 -213
- package/src/ardy/motion-url.js +0 -55
- package/src/ardy/mp4-duration.js +0 -102
- package/src/ardy/npz.js +0 -595
- package/src/ardy/physics-panel.css +0 -21
- package/src/ardy/physics-panel.jsx +0 -89
- package/src/ardy/physics-refine.js +0 -111
- package/src/ardy/physics-review.js +0 -567
- package/src/ardy/physics-temporal.js +0 -54
- package/src/ardy/platform-fit-panel.css +0 -64
- package/src/ardy/platform-fit-panel.jsx +0 -41
- package/src/ardy/platform-fit.js +0 -326
- package/src/ardy/playback.js +0 -711
- package/src/ardy/pose-pin.js +0 -97
- package/src/ardy/prompt-clips.js +0 -30
- package/src/ardy/retime.js +0 -211
- package/src/ardy/root-drop.js +0 -369
- package/src/ardy/timeline-resize.js +0 -13
- package/src/ardy/to-cskel27.js +0 -225
- package/src/ardy/trim.js +0 -33
- package/src/ardy/waypoints.js +0 -369
- package/src/ardy-bridge-contract.js +0 -281
- package/src/domains/motion/motion-video-capture.js +0 -664
- package/src/i2v-motion-client.js +0 -77
- package/src/i2v-motion-studio.jsx +0 -180
- package/src/line-edit.js +0 -1348
- package/src/motion/generation.js +0 -105
- package/src/motion-readiness-ui.jsx +0 -67
- package/src/motion-readiness.js +0 -40
- package/src/motion-resources.js +0 -177
- package/src/motion-store.js +0 -65
- package/src/motion-trail.js +0 -601
- package/src/multimodel-ingest.js +0 -394
- package/src/panels/PromptBlocksPanel.jsx +0 -444
- package/src/panels/VideoCapturePanel.jsx +0 -181
- package/src/pose-extract/browser.js +0 -42
- package/src/pose-extract/detector.js +0 -100
- package/src/pose-extract/fit.js +0 -227
- package/src/pose-extract/image-frame.js +0 -51
- package/src/pose-extract/index.js +0 -58
- package/src/pose-extract/keyframes.js +0 -74
- package/src/pose-extract/landmarks.js +0 -162
- package/src/pose-extract/mirror.js +0 -153
- package/src/pose-extract/take.js +0 -131
- package/src/pose-extract/video-frames.js +0 -91
- package/src/take-recipe.js +0 -165
- package/src/trail-key-conflicts.js +0 -39
- package/src/trail-pick.js +0 -59
- package/src/use-i2v-capture-modal.js +0 -15
- package/tools/ardy/BRIDGE.md +0 -37
- package/tools/ardy/GVHMR-NPZ.md +0 -68
- package/tools/ardy/README.md +0 -124
- package/tools/ardy/__pycache__/cclay_gvhmr_worker.cpython-313.pyc +0 -0
- package/tools/ardy/artifacts.mjs +0 -34
- package/tools/ardy/bridge.mjs +0 -1732
- package/tools/ardy/bvh-cskel27.mjs +0 -1265
- package/tools/ardy/cclay_constrained_generate.py +0 -561
- package/tools/ardy/cclay_gvhmr_worker.py +0 -264
- package/tools/ardy/cclay_motion_edit.py +0 -510
- package/tools/ardy/cclay_sequence_generate.py +0 -705
- package/tools/ardy/cclay_worker.py +0 -304
- package/tools/ardy/dump-npz.py +0 -205
- package/tools/ardy/extract-rest.mjs +0 -299
- package/tools/ardy/extract.mjs +0 -507
- package/tools/ardy/footage.mjs +0 -467
- package/tools/ardy/gvhmr-floor.mjs +0 -102
- package/tools/ardy/gvhmr-to-cskel27.mjs +0 -21
- package/tools/ardy/gvhmr_fastpath.py +0 -168
- package/tools/ardy/gvhmr_trajectory.py +0 -240
- package/tools/ardy/merge-text-encoder.py +0 -160
- package/tools/ardy/mocap-metrics.mjs +0 -200
- package/tools/ardy/mocap-quality-gate.mjs +0 -110
- package/tools/ardy/motion-stabilize.mjs +0 -348
- package/tools/ardy/npz.mjs +0 -414
- package/tools/ardy/pose-to-npz.mjs +0 -106
- package/tools/ardy/prompt-limits.mjs +0 -5
- package/tools/ardy/run-edit-on-box.sh +0 -73
- package/tools/ardy/run-local.mjs +0 -233
- package/tools/ardy/run-on-box.sh +0 -593
- package/tools/ardy/run-sequence-on-box.sh +0 -177
- package/tools/ardy/runners/gvhmr-worker.mjs +0 -206
- package/tools/ardy/runners/index.mjs +0 -18
- package/tools/ardy/runners/local.mjs +0 -455
- package/tools/ardy/runners/proc.mjs +0 -125
- package/tools/ardy/runners/remote.mjs +0 -252
- package/tools/ardy/setup-local.mjs +0 -189
- package/tools/ardy/setup-text-encoder-on-box.sh +0 -93
- package/tools/ardy/setup-text-encoder.py +0 -306
- package/tools/ardy/smpl-cskel27.mjs +0 -178
- package/tools/ardy/visual-qa.mjs +0 -196
- package/tools/ardy/vq-car.mjs +0 -52
- package/tools/bench/EXP3.md +0 -61
- package/tools/bench/cclay_bench_extract_incam.py +0 -125
- package/tools/bench/cclay_bench_extract_obs.py +0 -204
- package/tools/bench/cclay_bench_runner.py +0 -42
- package/tools/bench/cube-contact.mjs +0 -162
- package/tools/bench/exp3.mjs +0 -152
- package/tools/bench/extract-bench-lib.mjs +0 -216
- package/tools/bench/extract-bench.mjs +0 -302
- package/tools/bench/fal-generate.mjs +0 -88
- package/tools/bench/fit/README.md +0 -189
- package/tools/bench/fit/camera.mjs +0 -59
- package/tools/bench/fit/contact.mjs +0 -419
- package/tools/bench/fit/footlock.mjs +0 -234
- package/tools/bench/fit/motion.mjs +0 -85
- package/tools/bench/fit/pin.mjs +0 -27
- package/tools/bench/fit/remote.mjs +0 -105
- package/tools/bench/fit-bench.mjs +0 -126
- package/tools/bench/fit-sanity.mjs +0 -45
- package/tools/bench/metrics.mjs +0 -313
- package/tools/bench/obs/depth.mjs +0 -258
- package/tools/bench/obs/extrinsics.mjs +0 -175
- package/tools/bench/obs/fit_mannequin_betas.py +0 -340
- package/tools/bench/obs/ground.mjs +0 -294
- package/tools/bench/obs/heading.mjs +0 -151
- package/tools/bench/obs/ladder.mjs +0 -555
- package/tools/bench/obs/mannequin-betas.json +0 -124
- package/tools/bench/obs/remote.mjs +0 -83
- package/tools/bench/obs/rest_joints.py +0 -52
- package/tools/bench/obs/ybot-targets.mjs +0 -49
- package/tools/bench/obs-bench.mjs +0 -520
- package/tools/bench/score.mjs +0 -343
- package/tools/bench/summarize.mjs +0 -86
- package/tools/demo-worker/README.md +0 -137
- package/tools/demo-worker/api-client.mjs +0 -233
- package/tools/demo-worker/generate.mjs +0 -301
- package/tools/demo-worker/index.mjs +0 -466
- package/tools/demo-worker/sign-core.mjs +0 -112
- package/tools/demo-worker/sign.mjs +0 -161
- package/tools/gt-render/browser.mjs +0 -182
- package/tools/gt-render/camera-math.mjs +0 -276
- package/tools/gt-render/page.mjs +0 -267
- package/tools/gt-render/render.mjs +0 -514
- package/tools/gt-render/scene-box.mjs +0 -31
- package/tools/gt-render/take-transform.mjs +0 -87
- package/tools/kimodo/compare-seam.mjs +0 -108
- package/tools/kimodo/constraints.mjs +0 -244
- package/tools/kimodo/edit.mjs +0 -259
- package/tools/kimodo/effector-constraints.mjs +0 -200
- package/tools/kimodo/generate.mjs +0 -646
- package/tools/kimodo/local-output.mjs +0 -265
- package/tools/kimodo/measure-pose.mjs +0 -65
- package/tools/kimodo/measure-preserve.mjs +0 -899
- package/tools/kimodo/measure-waypoints.mjs +0 -55
- package/tools/kimodo/mlx-generate.py +0 -111
- package/tools/kimodo/pose-constraints.mjs +0 -248
- package/tools/kimodo/preserve-mask.mjs +0 -543
- package/tools/kimodo/read-npz.mjs +0 -189
- package/tools/kimodo/run-edit-on-box.mjs +0 -155
- package/tools/kimodo/run-sequence-on-box.mjs +0 -178
- package/tools/kimodo/runner.mjs +0 -284
- package/tools/kimodo/seam-batch.mjs +0 -77
- package/tools/kimodo/setup-local.mjs +0 -30
- package/tools/kimodo/setup-on-box.sh +0 -339
- package/tools/kimodo/soma77-to-cskel27.mjs +0 -305
- package/tools/morphgs/assets/gen_truth.py +0 -39
- package/tools/morphgs/assets/mesh_ori_rig.txt +0 -27
- package/tools/morphgs/assets/playback-check.mjs +0 -30
- package/tools/morphgs/demo-gate.sh +0 -26
- package/tools/morphgs/fbx2morphgs.mjs +0 -68
- package/tools/morphgs/morphgs-to-cskel27.mjs +0 -105
- package/tools/morphgs/patches/preprocess_src-none-mode.patch +0 -76
- package/tools/morphgs/setup-on-cluster.sh +0 -126
- package/tools/projflow/driver.py +0 -1069
- package/tools/projflow/generate.mjs +0 -805
- package/tools/projflow/hml22-to-cskel27.mjs +0 -735
- package/tools/projflow/line-edit-job.mjs +0 -547
- package/tools/projflow/replay.mjs +0 -452
- package/tools/projflow/runner.mjs +0 -269
- package/tools/projflow/service.mjs +0 -742
- package/tools/track/DESIGN.md +0 -513
- package/tools/track/backfill-provenance.mjs +0 -116
- package/tools/track/budget.mjs +0 -8
- package/tools/track/check-rig.mjs +0 -209
- package/tools/track/diagnostics.schema.json +0 -60
- package/tools/track/export-rig.mjs +0 -256
- package/tools/track/fallback.mjs +0 -7
- package/tools/track/fk-parity-fixture.mjs +0 -161
- package/tools/track/gate.mjs +0 -349
- package/tools/track/masks.mjs +0 -318
- package/tools/track/metrics.mjs +0 -201
- package/tools/track/publish-obs.mjs +0 -108
- package/tools/track/py/check_env.py +0 -61
- package/tools/track/py/eval_lr.py +0 -229
- package/tools/track/py/lr_viterbi.py +0 -294
- package/tools/track/py/masks.py +0 -405
- package/tools/track/py/objective.py +0 -382
- package/tools/track/py/rig.py +0 -508
- package/tools/track/py/scene.py +0 -326
- package/tools/track/py/test_joint_indices.py +0 -123
- package/tools/track/py/test_lr_viterbi.py +0 -195
- package/tools/track/py/test_masks.py +0 -229
- package/tools/track/py/test_rig.py +0 -288
- package/tools/track/py/test_scene.py +0 -354
- package/tools/track/py/test_track.py +0 -328
- package/tools/track/py/track.py +0 -411
- package/tools/track/remote.mjs +0 -140
- package/tools/track/rig-dump.mjs +0 -227
- package/tools/track/run-box-tests.mjs +0 -14
- package/tools/track/run-box.mjs +0 -168
- package/tools/track/setup-box.sh +0 -59
- package/tools/track/study-2d.mjs +0 -728
- /package/src/{ardy → motion}/collision-blockers.js +0 -0
- /package/src/{ardy → motion}/ground.js +0 -0
- /package/src/{ardy → motion}/ik-drag.js +0 -0
- /package/src/{ardy → motion}/ik-key-json.js +0 -0
- /package/src/{ardy → motion}/ik.js +0 -0
- /package/src/{ardy → motion}/physics-surface.js +0 -0
- /package/src/{ardy → motion}/playback-clock.js +0 -0
- /package/src/{ardy → motion}/range-pin.js +0 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
// Style quantities: how fast a joint moves, how hard it speeds up, how far the centre of mass
|
|
2
|
+
// leaves the feet and how deep a knee or elbow bends. They depend on what the motion is for (a
|
|
3
|
+
// bat swing moves the wrists at 9 m/s), so QA measures them (output.observations) and does not
|
|
4
|
+
// judge them. A spec that names a style limit in checks.limits asks for that quantity to be a
|
|
5
|
+
// check again: speedCore/speedLimb -> SPEED, accCore/accLimb -> ACC, knee -> KNEE (bend),
|
|
6
|
+
// balance -> BALANCE. A pop is not style: one frame whose acceleration stands out from the
|
|
7
|
+
// frames around it is an artifact (a step key, a pole flip, a snapped anchor), so POP stays a
|
|
8
|
+
// QA error, and so is a FLOAT: the body off the ground without falling (floatChecks). A pop is a
|
|
9
|
+
// spike, not a large value: a hard but continuous acceleration (a swing's launch) is style.
|
|
10
|
+
// See docs/motion-api-v3.1.md C13.
|
|
11
|
+
import { add, sub, scale, len, dot, angle } from "../compiler/math.js";
|
|
12
|
+
import { CAPSULES, segPoints, sdBox } from "./geometry.js";
|
|
13
|
+
import { groundSole } from "./ground.js";
|
|
14
|
+
|
|
15
|
+
const SPEED_JOINTS = ["Hips", "Head", "LeftHand", "RightHand", "LeftFoot", "RightFoot", "LeftLeg", "RightLeg", "LeftForeArm", "RightForeArm"];
|
|
16
|
+
const CORE = new Set(["Hips", "Head"]);
|
|
17
|
+
const BENDS = [["knee_l", "LeftUpLeg", "LeftLeg", "LeftFoot"], ["knee_r", "RightUpLeg", "RightLeg", "RightFoot"],
|
|
18
|
+
["elbow_l", "LeftArm", "LeftForeArm", "LeftHand"], ["elbow_r", "RightArm", "RightForeArm", "RightHand"]];
|
|
19
|
+
/** A pop's acceleration is more than this many times the level around it (popLevel). */
|
|
20
|
+
const POP_RATIO = 2;
|
|
21
|
+
/** FLOAT: the share of gravity an airborne run may miss (0.75: the hips fall at a quarter of g). */
|
|
22
|
+
const FLOAT_LIMIT = 0.75;
|
|
23
|
+
/** Peak speed (m/s) under which a joint counts as still and peaks leaves it out. */
|
|
24
|
+
const STILL = 0.05;
|
|
25
|
+
const r2 = x => Math.round(x * 100) / 100;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Per SPEED_JOINT: speed v[f] (m/frame, from f-1), acceleration a[f] (m/frame^2, from f-2) and
|
|
29
|
+
* its vector A[f]; 0 where undefined.
|
|
30
|
+
*/
|
|
31
|
+
function kinematics(sk, out) {
|
|
32
|
+
const F = out.frames, n = F.length;
|
|
33
|
+
return SPEED_JOINTS.map(j => {
|
|
34
|
+
const X = F.map(fr => fr.s.P[sk.index[j]]), v = new Float64Array(n), a = new Float64Array(n), A = X.map(() => [0, 0, 0]);
|
|
35
|
+
for (let f = 1; f < n; f++) v[f] = len(sub(X[f], X[f - 1]));
|
|
36
|
+
for (let f = 2; f < n; f++) { A[f] = sub(sub(X[f], X[f - 1]), sub(X[f - 1], X[f - 2])); a[f] = len(A[f]); }
|
|
37
|
+
return { j, core: CORE.has(j), v, a, A };
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The acceleration level around frame f that a pop is measured against (m/frame^2), the larger of
|
|
43
|
+
* far the largest acceleration 2 and 3 frames away. A one-frame event shows in up to three
|
|
44
|
+
* neighbouring frames (a step key in f and f+1, a one-frame glitch in f-1..f+1), so the
|
|
45
|
+
* frames next to f are not read here;
|
|
46
|
+
* burst what the frames next to f carry in f's own direction, less what the other one turns
|
|
47
|
+
* back. At 24 fps a hard move gathers its acceleration into 2 or 3 frames that all push
|
|
48
|
+
* the same way (a swing's launch, the hips turning round): a burst, not a pop, however
|
|
49
|
+
* hard. A glitch pushes back on both sides (+1, -2, +1), a step on one (+1, -1), and a
|
|
50
|
+
* jitter turns back on one side as the other follows (+, -, -, +): those carry none.
|
|
51
|
+
* k = { a, A } (kinematics).
|
|
52
|
+
*/
|
|
53
|
+
function popLevel({ a, A }, f) {
|
|
54
|
+
let far = 0;
|
|
55
|
+
for (const q of [f - 3, f - 2, f + 2, f + 3]) if (q >= 2 && q < a.length) far = Math.max(far, a[q]);
|
|
56
|
+
const along = a[f] > 0 ? [f - 1, f + 1].filter(q => q >= 2 && q < a.length).map(q => dot(A[q], A[f]) / a[f]) : [];
|
|
57
|
+
return along.length ? Math.max(far, Math.max(...along) + Math.min(0, ...along)) : far;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** True when k.a[f] (m/frame^2) is a pop: above floor and POP_RATIO times popLevel(k, f); k = { a, A } (kinematics). */
|
|
61
|
+
export const isPop = (k, f, floor) => k.a[f] > floor && k.a[f] > POP_RATIO * popLevel(k, f);
|
|
62
|
+
|
|
63
|
+
/** Hard POP issues (limits L: accCore, accLimb are the floor); push(kind, msg, f, value, limit). */
|
|
64
|
+
export function popChecks(sk, out, L, push) {
|
|
65
|
+
for (const k of kinematics(sk, out)) {
|
|
66
|
+
const floor = k.core ? L.accCore : L.accLimb;
|
|
67
|
+
for (let f = 2; f < k.a.length; f++) if (isPop(k, f, floor)) push("POP", `${k.j} accel ${(k.a[f] * 100).toFixed(2)} cm/f^2 in one frame`, f, k.a[f], floor);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** True when a declared support (checks.supports) carries the body at frame f. */
|
|
72
|
+
function supported(out, f, supports) {
|
|
73
|
+
const { P } = out.frames[f].s, I = out.ctx.sk.index;
|
|
74
|
+
return supports.some(sp => {
|
|
75
|
+
const box = out.objectsAt(f)[sp.object], cap = CAPSULES.find(c => c[0] === (sp.part ?? "hips"));
|
|
76
|
+
return box && cap && Math.min(...segPoints(P[I[cap[1]]], P[I[cap[2]]]).map(p => sdBox(p, box))) - cap[3] < (sp.tol ?? 0.04);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Per frame: null where a declared support carries the body, "air" with no foot on the ground,
|
|
82
|
+
* else the balance margin in metres: how far the centre of mass (hips, spine, head) sits inside
|
|
83
|
+
* the box of the feet on the ground, the floor or an object's top (ground.js; < 0: outside it).
|
|
84
|
+
* While the hips travel every foot counts.
|
|
85
|
+
*/
|
|
86
|
+
function balanceMargins(out, supports = []) {
|
|
87
|
+
const sk = out.ctx.sk, I = sk.index, F = out.frames;
|
|
88
|
+
return F.map(({ s }, f) => {
|
|
89
|
+
if (supported(out, f, supports)) return null;
|
|
90
|
+
const { P } = s;
|
|
91
|
+
const moving = f > 0 && f < F.length - 1 && Math.hypot(F[f + 1].s.P[I.Hips][0] - F[f - 1].s.P[I.Hips][0], F[f + 1].s.P[I.Hips][2] - F[f - 1].s.P[I.Hips][2]) / 2 > 0.006;
|
|
92
|
+
const feet = [];
|
|
93
|
+
for (const S of ["Left", "Right"]) {
|
|
94
|
+
const sp = groundSole(out, f, S);
|
|
95
|
+
if (moving || Math.min(sp.heel[1], sp.ball[1]) < 0.02) feet.push(sp.heel, sp.ball, sp.tip);
|
|
96
|
+
}
|
|
97
|
+
if (!feet.length) return "air";
|
|
98
|
+
const com = scale(["Hips", "Spine1", "Spine2", "Head"].reduce((acc, j) => add(acc, P[I[j]]), [0, 0, 0]), 0.25);
|
|
99
|
+
const xs = feet.map(p => p[0]), zs = feet.map(p => p[2]);
|
|
100
|
+
return Math.min(com[0] - Math.min(...xs), Math.max(...xs) - com[0], com[2] - Math.min(...zs), Math.max(...zs) - com[2]);
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Runs [a, b] (indexes of out.frames) with both soles 2 cm or more above the ground (ground.js) and no declared support. */
|
|
105
|
+
function airRuns(out, supports = []) {
|
|
106
|
+
const n = out.frames.length, runs = [];
|
|
107
|
+
const air = out.frames.map((_, f) => !supported(out, f, supports)
|
|
108
|
+
&& ["Left", "Right"].every(S => { const sp = groundSole(out, f, S); return Math.min(sp.heel[1], sp.ball[1]) >= 0.02; }));
|
|
109
|
+
for (let a = 0; a < n; a++) {
|
|
110
|
+
if (!air[a]) continue;
|
|
111
|
+
let b = a;
|
|
112
|
+
while (b + 1 < n && air[b + 1]) b++;
|
|
113
|
+
runs.push([a, b]);
|
|
114
|
+
a = b;
|
|
115
|
+
}
|
|
116
|
+
return runs;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Hard FLOAT issues: 4 or more frames off the ground (airRuns) in which the hips do not fall.
|
|
121
|
+
* A jump or a drop is ballistic: over the run the hips' vertical speed drops by gravity (the
|
|
122
|
+
* speed into the run minus the speed out of it, per frame, is g). A body held up by nothing
|
|
123
|
+
* keeps it; under a quarter of g is a float. value = the share of g missing (1 = no fall).
|
|
124
|
+
*/
|
|
125
|
+
export function floatChecks(out, checks = {}) {
|
|
126
|
+
const F = out.frames, n = F.length, I = out.ctx.sk.index, g = 9.81 / (out.fps ?? 24) ** 2, f0 = out.f0 ?? 0, issues = [];
|
|
127
|
+
const y = f => F[Math.max(0, Math.min(n - 1, f))].s.P[I.Hips][1];
|
|
128
|
+
for (const [a, b] of airRuns(out, checks.supports)) {
|
|
129
|
+
const frames = b - a + 1;
|
|
130
|
+
if (frames < 4) continue;
|
|
131
|
+
const vin = a > 0 ? y(a) - y(a - 1) : y(a + 1) - y(a), vout = b < n - 1 ? y(b + 1) - y(b) : y(b) - y(b - 1);
|
|
132
|
+
const missing = 1 - (vin - vout) / frames / g;
|
|
133
|
+
if (missing <= FLOAT_LIMIT) continue;
|
|
134
|
+
for (let f = a; f <= b; f++) issues.push({ kind: "FLOAT", msg: `no foot on the ground f${a + f0}-f${b + f0}, hips fall at ${Math.round(Math.max(0, 1 - missing) * 100)}% of g`, frame: f, value: +missing.toFixed(4), limit: FLOAT_LIMIT });
|
|
135
|
+
}
|
|
136
|
+
return issues;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const bendAt = (sk, s, [, a, b, c]) => angle(sub(s.P[sk.index[b]], s.P[sk.index[a]]), sub(s.P[sk.index[c]], s.P[sk.index[b]]));
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* The style checks the spec opted into by naming their limit in checks.limits (own keys only);
|
|
143
|
+
* L is QA_LIMITS merged with them. Issues as generalChecks makes them (f indexes out.frames).
|
|
144
|
+
*/
|
|
145
|
+
export function styleChecks(sk, out, checks, L) {
|
|
146
|
+
const own = checks?.limits ?? {}, on = k => Object.hasOwn(own, k), issues = [];
|
|
147
|
+
const push = (kind, msg, f, value, limit) => issues.push({ kind, msg, frame: f, value: +value.toFixed(4), limit });
|
|
148
|
+
for (const k of kinematics(sk, out)) {
|
|
149
|
+
const { j, core, v, a } = k, vk = core ? "speedCore" : "speedLimb", ak = core ? "accCore" : "accLimb";
|
|
150
|
+
for (let f = 1; f < v.length; f++) {
|
|
151
|
+
if (on(vk) && v[f] > L[vk]) push("SPEED", `${j} ${(v[f] * 100).toFixed(1)} cm/frame`, f, v[f], L[vk]);
|
|
152
|
+
// a pop is already a POP group
|
|
153
|
+
if (on(ak) && f > 1 && a[f] > L[ak] && !isPop(k, f, L[ak])) push("ACC", `${j} accel ${(a[f] * 100).toFixed(2)} cm/f^2`, f, a[f], L[ak]);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (on("knee")) out.frames.forEach(({ s }, f) => {
|
|
157
|
+
for (const bend of BENDS.slice(0, 2)) {
|
|
158
|
+
const deg = bendAt(sk, s, bend);
|
|
159
|
+
if (deg > L.knee) push("KNEE", `${bend[0] === "knee_l" ? "Left" : "Right"} knee bent ${deg.toFixed(0)}deg`, f, deg, L.knee);
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
if (on("balance")) balanceMargins(out, checks.supports).forEach((m, f) => {
|
|
163
|
+
if (m === "air") push("BALANCE", "no foot on the ground", f, 0, 0);
|
|
164
|
+
else if (m !== null && -m > L.balance) {
|
|
165
|
+
const com = scale(["Hips", "Spine1", "Spine2", "Head"].reduce((acc, j) => add(acc, out.frames[f].s.P[sk.index[j]]), [0, 0, 0]), 0.25);
|
|
166
|
+
push("BALANCE", `centre of mass ${com[0].toFixed(2)},${com[2].toFixed(2)} outside the feet`, f, -m, L.balance);
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
return issues;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* output.observations: measurements, not verdicts. Frames are absolute (out.f0 added).
|
|
174
|
+
* { units, peaks: { <joint>: { speed: [m/s, f], accel: [m/s^2, f] } } (joints that move: STILL),
|
|
175
|
+
* balance: { min: [m, f], outside?: [f0, f1], air?: [[f0, f1], ...] (at most 4 runs with both
|
|
176
|
+
* feet off the ground: the floor or an object's top) }, bend: { knee_l: [deg, f], ... } }
|
|
177
|
+
*/
|
|
178
|
+
export function observeMotion(sk, out, checks = {}) {
|
|
179
|
+
const f0 = out.f0 ?? 0, fps = out.fps ?? 24, peak = (arr, k) => {
|
|
180
|
+
let at = 0;
|
|
181
|
+
for (let f = 1; f < arr.length; f++) if (arr[f] > arr[at]) at = f;
|
|
182
|
+
return [Math.round(arr[at] * k * 100) / 100, at + f0];
|
|
183
|
+
};
|
|
184
|
+
const peaks = Object.fromEntries(kinematics(sk, out).map(({ j, v, a }) => [j, { speed: peak(v, fps), accel: peak(a, fps * fps) }]).filter(([, p]) => p.speed[0] >= STILL));
|
|
185
|
+
const margins = balanceMargins(out, checks.supports), span = test => {
|
|
186
|
+
const fs = margins.map((m, f) => (test(m) ? f + f0 : -1)).filter(f => f >= 0);
|
|
187
|
+
return fs.length ? [fs[0], fs[fs.length - 1]] : null;
|
|
188
|
+
}, air = airRuns(out, checks.supports).slice(0, 4).map(([a, b]) => [a + f0, b + f0]);
|
|
189
|
+
let min = null;
|
|
190
|
+
margins.forEach((m, f) => { if (typeof m === "number" && (!min || m < min[0])) min = [r2(m), f + f0]; });
|
|
191
|
+
const outside = span(m => typeof m === "number" && m < 0);
|
|
192
|
+
const bend = Object.fromEntries(BENDS.map(b => {
|
|
193
|
+
let best = [0, f0];
|
|
194
|
+
out.frames.forEach(({ s }, f) => { const d = bendAt(sk, s, b); if (d > best[0]) best = [d, f + f0]; });
|
|
195
|
+
return [b[0], [Math.round(best[0]), best[1]]];
|
|
196
|
+
}));
|
|
197
|
+
return { units: "[value, frame]; speed m/s, accel m/s^2, balance m inside the feet (< 0 outside), bend deg (0 straight)",
|
|
198
|
+
peaks, balance: { min, ...(outside ? { outside } : {}), ...(air.length ? { air } : {}) }, bend };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Per frame, how many times too fast the motion is by what QA reports (fix/retime.js): a pop
|
|
203
|
+
* against its surroundings (square root: acceleration falls with the square of the stretch) and
|
|
204
|
+
* the speed or acceleration limits the spec opted into. L = QA_LIMITS merged with own.
|
|
205
|
+
*/
|
|
206
|
+
export function speedExcess(sk, out, own, L) {
|
|
207
|
+
const on = k => Object.hasOwn(own ?? {}, k), e = new Float64Array(out.frames.length);
|
|
208
|
+
for (const k of kinematics(sk, out)) {
|
|
209
|
+
const { core, v, a } = k, vk = core ? "speedCore" : "speedLimb", ak = core ? "accCore" : "accLimb";
|
|
210
|
+
for (let f = 1; f < v.length; f++) {
|
|
211
|
+
if (on(vk)) e[f] = Math.max(e[f], v[f] / L[vk]);
|
|
212
|
+
if (f < 2) continue;
|
|
213
|
+
if (isPop(k, f, L[ak])) e[f] = Math.max(e[f], Math.sqrt(a[f] / Math.max(L[ak], POP_RATIO * popLevel(k, f))));
|
|
214
|
+
if (on(ak)) e[f] = Math.max(e[f], Math.sqrt(a[f] / L[ak]));
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return e;
|
|
218
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// QA WRIST (lane wristqa): a holding wrist at its anatomical limit is broken whatever the motion is for
|
|
2
|
+
// (C13 keeps style measured, not judged; a wrist the arm had to clamp is neither). On the frames a hand
|
|
3
|
+
// holds (wrist-obs.js holding: a fit, a keyed carry, a grip or a carry), the wrist's turn on its forearm
|
|
4
|
+
// is read where the arm asked for it: a keyed carry's need before hold.js clamped it (diag.hold_<k>),
|
|
5
|
+
// else the turn the solved pose shows at the joint, against the whole limits: the wtwist channel turns the
|
|
6
|
+
// hand alone (limbs.js wrist, the forearm bone takes no share), and a grip stays inside the comfortable twist.
|
|
7
|
+
// An axis more than AT deg past its limit (arm-orient.js WRIST_LIMITS: the clamp engaged, or a turn no wrist makes) is an issue:
|
|
8
|
+
// WRIST "hand_r flex 72deg (limit 70)", value the degrees asked, limit the limit
|
|
9
|
+
// Past the comfortable range only is not judged: output.observations.wrist counts it.
|
|
10
|
+
// The hint moves the body, not the wrist. Which move frees it depends on the elbow's path the hold plans
|
|
11
|
+
// over the whole carry (hold-plan.js), so no single frame predicts it (a 10 cm hip drop computed to free
|
|
12
|
+
// the wrist at f90 of bench S6 kept it at its limit once compiled; a 20 deg lean freed 3 of its 4
|
|
13
|
+
// frames): repair compiles the leans instead (repair-tune.js WRIST) and keeps the one that lowers the summed
|
|
14
|
+
// severity with every goal held.
|
|
15
|
+
import { WRIST_LIMITS, wristNeed } from "../compiler/arm-orient.js";
|
|
16
|
+
import { palmAxes } from "../compiler/limbs.js";
|
|
17
|
+
import { apply, len, mul, scale, sub, T } from "../compiler/math.js";
|
|
18
|
+
import { holding } from "./wrist-obs.js";
|
|
19
|
+
|
|
20
|
+
const AT = 0.5, HELD = 0.5, AXES = ["flex", "dev", "twist"], SIDES = { l: "Left", r: "Right" };
|
|
21
|
+
const WORDS = { flex: v => (v >= 0 ? "flex" : "extension"), dev: v => (v >= 0 ? "radial deviation" : "ulnar deviation"), twist: () => "twist" };
|
|
22
|
+
|
|
23
|
+
/** The worst axis of a wrist need against its limits: { axis, value, edge, past } (past > AT is an issue). */
|
|
24
|
+
function worstAxis(need) {
|
|
25
|
+
let best = null;
|
|
26
|
+
for (const n of AXES) {
|
|
27
|
+
const [lo, hi] = WRIST_LIMITS[n], v = need[n], edge = v >= 0 ? hi : lo, past = v >= 0 ? v - hi : lo - v;
|
|
28
|
+
if (!best || past > best.past) best = { axis: n, value: v, edge, past };
|
|
29
|
+
}
|
|
30
|
+
return best;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** The wrist need of hand k on frame i: the hold's (before its clamp) when a keyed carry leads it, else the joint's on the solved pose. */
|
|
34
|
+
function needAt(sk, out, k, i) {
|
|
35
|
+
const { s, diag } = out.frames[i], h = diag[`hold_${k}`];
|
|
36
|
+
if ((h?.w ?? 0) > HELD) return h;
|
|
37
|
+
const S = SIDES[k], I = sk.index, hb = I[`${S}Hand`], fa = I[`${S}ForeArm`], rw = sk.restWorldRot;
|
|
38
|
+
const axT = scale(sub(s.P[hb], s.P[fa]), 1 / Math.max(1e-9, len(sub(s.P[hb], s.P[fa])))), neutral = mul(s.R[fa], mul(T(rw[fa]), rw[hb]));
|
|
39
|
+
return wristNeed(neutral, s.R[hb], axT, apply(mul(neutral, T(s.R[hb])), palmAxes(sk, s, S).l));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** WRIST issues of every holding hand ({ kind, msg, frame, value, limit }, frame an out.frames index). */
|
|
43
|
+
export function wristIssues(sk, out) {
|
|
44
|
+
const issues = [], f0 = out.f0 ?? 0;
|
|
45
|
+
for (const k of ["l", "r"]) out.frames.forEach((_, i) => {
|
|
46
|
+
if (!holding(out, k, i, i + f0)) return;
|
|
47
|
+
const w = worstAxis(needAt(sk, out, k, i));
|
|
48
|
+
if (w.past <= AT) return;
|
|
49
|
+
const lim = Math.abs(w.edge);
|
|
50
|
+
issues.push({ kind: "WRIST", msg: `hand_${k} ${WORDS[w.axis](w.value)} ${Math.round(Math.abs(w.value))}deg (limit ${Math.round(lim)})`, frame: i, value: +Math.abs(w.value).toFixed(2), limit: lim });
|
|
51
|
+
});
|
|
52
|
+
return issues;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** WRIST group -> { cause, hint }: what bends the wrist (fix.self's elbow turn, or where the body stands), and the body moves repair tries (repair-tune.js WRIST). */
|
|
56
|
+
function wristHint(X, g) {
|
|
57
|
+
const m = /^hand_([lr]) /.exec(g.subject), k = m?.[1];
|
|
58
|
+
if (!k) return null;
|
|
59
|
+
const f = g.worstFrame, where = `at f${f}${g.count > 1 ? ` (f${g.first}-f${g.last}, ${g.count} frames)` : ""}`;
|
|
60
|
+
// fix.self turned the elbow about the shoulder-wrist line and the hand kept its turn: the wrist took the turn
|
|
61
|
+
const turn = Math.round(X.out.ctx?.swivel?.[k]?.get(f) ?? 0);
|
|
62
|
+
if (Math.abs(turn) >= 1) return { cause: `fix.self elbow turn ${turn}deg at f${f}`, hint: `${g.subject} ${where}: fix.self turned the elbow ${turn} deg to keep the arm out of the body and the hand kept its turn, so the wrist took it; key elbow_${k} (the elbow pole) or the hand's target so the arm clears the body without the turn` };
|
|
63
|
+
const held = (X.out.frames[X.rel(f)]?.diag[`hold_${k}`]?.w ?? 0) > HELD && Object.keys(X.spec.objects ?? {}).find(id => X.spec.props?.[X.spec.objects[id]?.prop]?.hand === k);
|
|
64
|
+
const lean = X.fr(f).c.spine_pitch ?? 0;
|
|
65
|
+
return { cause: held ? `objects.${held}.keys near f${f}` : X.cause(`armw_${k}`, f),
|
|
66
|
+
hint: `${g.subject} ${where}: the arm cannot turn the hand that far from where the body stands; lean the upper body over the hand (spine_pitch ${Math.round(lean)} at f${f}: try ${Math.round(lean + 20)}) or bring the body to it (root), ${held ? `or key ${held} so the hand need not bend that far (its aim or pos)` : "or move what the hand reaches"}; repair: { rounds } tries leans of 10-30 deg here and keeps one that helps` };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export const wristHints = { WRIST: wristHint };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// Measured, not judged (output.observations.wrist, lane wrist): how far each wrist bends while its hand
|
|
2
|
+
// holds something. A hand holds on the frames a fit's weight is on (reachGrip, any fit), a keyed carry
|
|
3
|
+
// leads it (diag.hold_<k>), a grip turns it (diag.grip_<k>) or it carries a body. On those frames the
|
|
4
|
+
// hand's turn relative to its forearm is read the way compiler/hold.js turns it (arm-orient.js
|
|
5
|
+
// wristNeed): flex (+ toward the palm), deviation (+ toward the thumb, radial; - ulnar) and the twist the
|
|
6
|
+
// wrist joint shows (the forearm bone takes FOREARM_SHARE of a turn, so the joint's comfortable twist
|
|
7
|
+
// is that share of the forearm's). Frames past the comfortable ranges (arm-orient.js WRIST_COMFORT) and
|
|
8
|
+
// at the anatomical limits (WRIST_LIMITS) are counted.
|
|
9
|
+
// Why: a hand drawn by keys bent 70 deg (the limit) with 33 deg of ulnar deviation for frames while QA
|
|
10
|
+
// stayed clean (bench S6), and no answer said so. The shoulder too (lane elbow): a cup held at the mouth with
|
|
11
|
+
// the upper arm 130-160 deg up, the elbow 20 cm over the shoulder, in most bench cup takes.
|
|
12
|
+
import { wristNeed, WRIST_LIMITS, WRIST_COMFORT, FOREARM_SHARE } from "../compiler/arm-orient.js";
|
|
13
|
+
import { palmAxes } from "../compiler/limbs.js";
|
|
14
|
+
import { apply, len, mul, scale, sub, T } from "../compiler/math.js";
|
|
15
|
+
import { handOf } from "../object-pose.js";
|
|
16
|
+
|
|
17
|
+
const HOLD = 0.5, AT = 1, LEVEL = 90;
|
|
18
|
+
const JOINT = { twist: 1 - FOREARM_SHARE, flex: 1, dev: 1 };
|
|
19
|
+
const range = (k, table) => table[k].map(v => v * JOINT[k]);
|
|
20
|
+
|
|
21
|
+
export const WRIST_UNITS = "wrist: deg on the frames a hand holds (a fit, a carry or a grip on); flex + toward the palm, dev + radial / - ulnar, twist at the wrist joint; [min, f, max, f], frames past the comfortable range and at the limit; grip: the grip a keyed carry of a thin body keeps (grip-choice.js), cost [it, today's] (comfort-priced wrist, reach and body over the keys); elevation [max deg, f] of the upper arm (90 level), raised: frames the elbow is over the shoulder";
|
|
22
|
+
|
|
23
|
+
/** True when the hand of side k holds something on frame i of out (absolute frame f); qa/wrist-checks.js reads it too. */
|
|
24
|
+
export function holding(out, k, i, f) {
|
|
25
|
+
const { diag } = out.frames[i], baked = out.baked ?? {};
|
|
26
|
+
if ((diag[`hold_${k}`]?.w ?? 0) > HOLD || (diag[`grip_${k}`]?.w ?? 0) > HOLD) return true;
|
|
27
|
+
for (const fit of out.ctx.spec.fits ?? []) if (fit.side === k && (baked[`w_${fit.anchor}_${k}`]?.[f] ?? 0) > HOLD) return true;
|
|
28
|
+
return Object.keys(out.ctx.spec.objects ?? {}).some(id => out.ctx.spec.objects[id]?.prop && handOf(out, id, f) === k);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** observeWrist(sk, out) -> { hand_l?, hand_r? } or null when no hand holds anything. Frames are absolute. */
|
|
32
|
+
export function observeWrist(sk, out) {
|
|
33
|
+
const I = sk.index, rw = sk.restWorldRot, f0 = out.f0 ?? 0, res = {};
|
|
34
|
+
for (const [S, k] of [["Left", "l"], ["Right", "r"]]) {
|
|
35
|
+
const h = I[`${S}Hand`], fa = I[`${S}ForeArm`], a = I[`${S}Arm`], row = { frames: 0, past: 0, limit: 0, raised: 0 };
|
|
36
|
+
out.frames.forEach(({ s }, i) => {
|
|
37
|
+
const f = i + f0;
|
|
38
|
+
if (!holding(out, k, i, f)) return;
|
|
39
|
+
const axT = scale(sub(s.P[h], s.P[fa]), 1 / Math.max(1e-9, len(sub(s.P[h], s.P[fa])))), neutral = mul(s.R[fa], mul(T(rw[fa]), rw[h]));
|
|
40
|
+
const need = wristNeed(neutral, s.R[h], axT, apply(mul(neutral, T(s.R[h])), palmAxes(sk, s, S).l));
|
|
41
|
+
row.frames++;
|
|
42
|
+
for (const n of ["flex", "dev", "twist"]) {
|
|
43
|
+
const v = Math.round(need[n]), cur = row[n];
|
|
44
|
+
row[n] = cur ? [Math.min(cur[0], v), v < cur[0] ? f : cur[1], Math.max(cur[2], v), v > cur[2] ? f : cur[3]] : [v, f, v, f];
|
|
45
|
+
}
|
|
46
|
+
// past the comfortable range by more than a degree; at a limit within a degree of it
|
|
47
|
+
const outside = (table, by) => ["flex", "dev", "twist"].some(n => { const [lo, hi] = range(n, table); return need[n] < lo + by || need[n] > hi - by; });
|
|
48
|
+
if (outside(WRIST_COMFORT, -AT)) row.past++;
|
|
49
|
+
if (outside(WRIST_LIMITS, AT)) row.limit++;
|
|
50
|
+
// the shoulder (lane elbow): the upper arm's elevation from hanging down, and the frames the elbow is over the shoulder's level
|
|
51
|
+
const up = sub(s.P[fa], s.P[a]), el = Math.round((Math.acos(Math.max(-1, Math.min(1, -up[1] / Math.max(1e-9, len(up))))) * 180) / Math.PI);
|
|
52
|
+
if (!row.elevation || el > row.elevation[0]) row.elevation = [el, f];
|
|
53
|
+
if (el > LEVEL) row.raised++;
|
|
54
|
+
});
|
|
55
|
+
if (row.frames) res[`hand_${k}`] = row;
|
|
56
|
+
}
|
|
57
|
+
// the grip a keyed carry chose for where it goes (grip-choice.js): what the hand of that carry holds
|
|
58
|
+
for (const g of out.ctx.hold?.grips?.() ?? []) if (res[`hand_${g.hand}`]) res[`hand_${g.hand}`].grip = { object: g.id, chose: g.name, cost: [g.cost, g.today] };
|
|
59
|
+
return Object.keys(res).length ? res : null;
|
|
60
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// C2. Point references, the one grammar goals, probes and the timeline use (spec.checks keep
|
|
2
|
+
// their older joint/prop/anchor subset, validate-common.js refOk):
|
|
3
|
+
// joint:<Bone> | bone:<Bone> the bone's joint (the two are the same; names go through bones.js)
|
|
4
|
+
// prop:<prop>.<point> a point of a held prop (spec.props; "origin" always exists)
|
|
5
|
+
// anchor:<name> a spec anchor, resolved as the compiler resolves it
|
|
6
|
+
// landmark:mouth the measured face landmark
|
|
7
|
+
// object:<id>[.center|.top|.base] a body or a group (object-pose.js objectBoxesAt): the centre,
|
|
8
|
+
// top-face centre or bottom-face centre of the AABB of its
|
|
9
|
+
// part boxes' corners (default center)
|
|
10
|
+
// Frames are absolute; a frame outside the compiled window (out.f0 ..) answers null.
|
|
11
|
+
import { REF_KINDS } from "./schema/fields.js";
|
|
12
|
+
import { resolveBone } from "./bones.js";
|
|
13
|
+
import { objectBoxesAt, partsOf } from "./object-pose.js";
|
|
14
|
+
import { objectOk } from "./compiler/validate-common.js";
|
|
15
|
+
import { resolveAnchor } from "./compiler/anchors.js";
|
|
16
|
+
import { bonePoint } from "./compiler/skeleton.js";
|
|
17
|
+
import { didYouMean } from "./compiler/vocab.js";
|
|
18
|
+
import { add, apply, sub, len } from "./compiler/math.js";
|
|
19
|
+
import { sdBox } from "./qa/geometry.js";
|
|
20
|
+
|
|
21
|
+
const SYNTAX = { joint: "joint:<Bone>", bone: "bone:<Bone>", prop: "prop:<prop>.<point>", anchor: "anchor:<name>", landmark: "landmark:mouth", object: "object:<id>[.center|.top|.base]" };
|
|
22
|
+
|
|
23
|
+
function parse(ref) {
|
|
24
|
+
const m = /^([a-z]+):(.+)$/s.exec(String(ref));
|
|
25
|
+
if (!m) return null;
|
|
26
|
+
const [, kind, rest] = m;
|
|
27
|
+
if (kind === "prop") { const [name, point] = rest.split("."); return { kind, name, point: point ?? "" }; }
|
|
28
|
+
if (kind === "object") { const o = /^(.+)\.(center|top|base)$/s.exec(rest); return { kind, name: o ? o[1] : rest, point: o ? o[2] : null }; }
|
|
29
|
+
return { kind, name: rest, point: null };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function boxesPoint(boxes, which) {
|
|
33
|
+
const lo = [Infinity, Infinity, Infinity], hi = [-Infinity, -Infinity, -Infinity];
|
|
34
|
+
for (const b of boxes) for (const x of [-1, 1]) for (const y of [0, 2]) for (const z of [-1, 1]) {
|
|
35
|
+
const p = add(b.pos, apply(b.R, [x * b.half[0], y * b.half[1], z * b.half[2]]));
|
|
36
|
+
for (let i = 0; i < 3; i++) { lo[i] = Math.min(lo[i], p[i]); hi[i] = Math.max(hi[i], p[i]); }
|
|
37
|
+
}
|
|
38
|
+
const c = [(lo[0] + hi[0]) / 2, (lo[1] + hi[1]) / 2, (lo[2] + hi[2]) / 2];
|
|
39
|
+
return which === "top" ? [c[0], hi[1], c[2]] : which === "base" ? [c[0], lo[1], c[2]] : c;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** World point [x, y, z] of `ref` at absolute frame f of compile result out, or null. */
|
|
43
|
+
export function refPoint(out, f, ref) {
|
|
44
|
+
const fr = out.frames?.[f - (out.f0 ?? 0)], r = parse(ref);
|
|
45
|
+
if (!fr || !r) return null;
|
|
46
|
+
const { s, diag, c } = fr, sk = out.ctx.sk;
|
|
47
|
+
switch (r.kind) {
|
|
48
|
+
case "joint": case "bone": { const { bone } = resolveBone(sk, r.name); return bone ? s.P[sk.index[bone]].slice() : null; }
|
|
49
|
+
case "prop": { const p = diag.props?.[r.name]?.[r.point]; return Array.isArray(p) ? p.slice() : null; }
|
|
50
|
+
case "anchor": return out.ctx.spec.anchors?.[r.name] ? resolveAnchor(out.ctx, r.name, s, { ...diag, frame: f }, c) : null;
|
|
51
|
+
case "landmark": return sk.landmarks?.[r.name] ? bonePoint(s, sk.index.Head, sk.landmarks[r.name]) : null;
|
|
52
|
+
case "object": { const boxes = objectBoxesAt(out, r.name, f); return boxes ? boxesPoint(boxes, r.point) : null; }
|
|
53
|
+
default: return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Metres between refs a and b at absolute frame f, or null when either is unknown there. A bare
|
|
59
|
+
* object:<id> (no .center/.top/.base) means "to the object's surface": the distance from the
|
|
60
|
+
* other point to the nearest part box (sdBox, 0 inside); two bare objects measure a's centre to
|
|
61
|
+
* b's surface. Any other pair is the distance between the two points.
|
|
62
|
+
*/
|
|
63
|
+
export function refDistance(out, f, a, b) {
|
|
64
|
+
const bare = ref => { const r = parse(ref); return r?.kind === "object" && r.point === null ? r : null; };
|
|
65
|
+
const surf = bare(b) ? [a, bare(b)] : bare(a) ? [b, bare(a)] : null;
|
|
66
|
+
if (surf) {
|
|
67
|
+
const p = refPoint(out, f, surf[0]), boxes = objectBoxesAt(out, surf[1].name, f);
|
|
68
|
+
return p && boxes ? Math.max(0, Math.min(...boxes.map(box => sdBox(p, box)))) : null;
|
|
69
|
+
}
|
|
70
|
+
const p = refPoint(out, f, a), q = refPoint(out, f, b);
|
|
71
|
+
return p && q ? len(sub(p, q)) : null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* The prefixed refs a bare name could mean in this spec, rig and scene (`; did you mean
|
|
76
|
+
* "object:empty-2"?`), or "". A field that takes more than one ref kind stays strict.
|
|
77
|
+
*/
|
|
78
|
+
function bareHint(spec, { sk, scene }, ref, kinds) {
|
|
79
|
+
if (typeof ref !== "string" || !ref) return "";
|
|
80
|
+
const id = ref.replace(/\.(center|top|base)$/, ""), [prop, point] = ref.split(".");
|
|
81
|
+
const bone = sk && (kinds.includes("joint") || kinds.includes("bone")) ? resolveBone(sk, ref).bone : null;
|
|
82
|
+
const known = [
|
|
83
|
+
kinds.includes("object") && (scene?.objects?.[id] || partsOf(scene, id).length) && `object:${ref}`,
|
|
84
|
+
bone && `${kinds.includes("joint") ? "joint" : "bone"}:${bone}`,
|
|
85
|
+
kinds.includes("anchor") && spec.anchors?.[ref] && `anchor:${ref}`,
|
|
86
|
+
kinds.includes("prop") && spec.props?.[prop] && (point === "origin" || spec.props[prop].points?.[point]) && `prop:${ref}`,
|
|
87
|
+
kinds.includes("landmark") && ref === "mouth" && "landmark:mouth",
|
|
88
|
+
].filter(Boolean);
|
|
89
|
+
return known.length ? `; did you mean ${known.map(k => `"${k}"`).join(" or ")}?` : "";
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Validates ref against the spec (props, anchors), the rig (bones) and the scene (objects and
|
|
94
|
+
* groups). Reports one exact message through err, prefixed `${at} "${ref}"`, and answers whether
|
|
95
|
+
* the ref is good. kinds limits the grammar (default all of REF_KINDS). A bare name (no kind:)
|
|
96
|
+
* is refused with the forms it could mean (bareHint).
|
|
97
|
+
*/
|
|
98
|
+
export function checkRef(spec, err, { sk, scene }, at, ref, kinds = REF_KINDS) {
|
|
99
|
+
const r = parse(ref), tag = `${at} "${ref}"`;
|
|
100
|
+
if (!r || !kinds.includes(r.kind)) { err(`${tag} must be ${kinds.map(k => SYNTAX[k]).join(", ")}${r ? "" : bareHint(spec, { sk, scene }, ref, kinds)}`); return false; }
|
|
101
|
+
if (r.kind === "joint" || r.kind === "bone") {
|
|
102
|
+
const { bone, hint } = sk ? resolveBone(sk, r.name) : { bone: r.name };
|
|
103
|
+
if (!bone) err(`${tag}: unknown bone${hint}`);
|
|
104
|
+
return Boolean(bone);
|
|
105
|
+
}
|
|
106
|
+
if (r.kind === "prop") {
|
|
107
|
+
const ok = Boolean(spec.props?.[r.name] && (r.point === "origin" || spec.props[r.name].points?.[r.point]));
|
|
108
|
+
if (!ok) err(`${tag}: spec.props has no point ${r.name}.${r.point}`);
|
|
109
|
+
return ok;
|
|
110
|
+
}
|
|
111
|
+
if (r.kind === "anchor") {
|
|
112
|
+
if (!spec.anchors?.[r.name]) err(`${tag}: no anchor "${r.name}" in spec.anchors${didYouMean(r.name, Object.keys(spec.anchors ?? {}))}`);
|
|
113
|
+
return Boolean(spec.anchors?.[r.name]);
|
|
114
|
+
}
|
|
115
|
+
if (r.kind === "landmark") {
|
|
116
|
+
if (r.name !== "mouth") err(`${tag}: the only landmark is "mouth"`);
|
|
117
|
+
return r.name === "mouth";
|
|
118
|
+
}
|
|
119
|
+
return objectOk(err, scene, tag, r.name, { groups: true });
|
|
120
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// C6. Repair's second move: a QA group just past its limit that carries no patch. Its cause names
|
|
2
|
+
// what made it, and a skill arg or a key time maps to the geometry too indirectly for a hint to
|
|
3
|
+
// predict (in the cup take, firstStep -1 cm cleared a thigh hit but a 5.04 cm/f^2 pop appeared;
|
|
4
|
+
// step did nothing). A compile is cheap, so repair tries a few small changes instead and keeps
|
|
5
|
+
// the best (repair.js), like an automatic { range } that stays near the agent's value:
|
|
6
|
+
// cause "... use[i] <skill> ..." each arg in SKILL_INFO[skill].tune, 1 then 2 steps
|
|
7
|
+
// either way, from the arg as sent or its default
|
|
8
|
+
// SPEED/ACC/POP "between keys[a] fA and keys[b] fB" (the agent's keys)
|
|
9
|
+
// key a 1 or 2 frames earlier, key b later
|
|
10
|
+
// GOAL up to 10 cm short (with or without a patch): where the body stands at the worst frame,
|
|
11
|
+
// 1 then 2 steps either way: the agent's root keys
|
|
12
|
+
// there (x, z), else the last standIdle/walkTo before
|
|
13
|
+
// it (SKILL_INFO[skill].place); then the lean, the
|
|
14
|
+
// spine_pitch of the agent's keys around it (5 deg)
|
|
15
|
+
// WRIST (qa/wrist-checks.js) at the worst frame: a lean there (a spine_pitch key 10, 20 or 30 deg
|
|
16
|
+
// forward of the lean before it, or 10 back, eased back to it 12
|
|
17
|
+
// frames either side when no lean key is nearer), then the lean
|
|
18
|
+
// keys around it 5 or 10 deg either way; which move frees
|
|
19
|
+
// a held wrist depends on the elbow path the hold plans, so it is
|
|
20
|
+
// compiled (on bench S6 the body's floor steps only made it worse)
|
|
21
|
+
// ; none when fix.self's elbow turn bent it (cause "fix.self ..."): a lean
|
|
22
|
+
// does not undo the turn (car exit: 21 compiles, 20 s, 57 -> 51 deg)
|
|
23
|
+
// Every candidate is a JSON Patch on the agent's spec; the order is fixed, so the search is
|
|
24
|
+
// deterministic. Least-change candidates come first, so a tie keeps the smaller edit.
|
|
25
|
+
import { excessOf } from "./qa/severity.js";
|
|
26
|
+
import { SKILL_INFO } from "./skills/info.js";
|
|
27
|
+
|
|
28
|
+
const NEAR = 0.5, STEPS = [-1, 1, -2, 2], PLACE_MAX = 0.1, LEAN = 5;
|
|
29
|
+
const isNum = x => typeof x === "number" && Number.isFinite(x);
|
|
30
|
+
|
|
31
|
+
/** A group no more than half its limit (or 5 mm, for small limits) past it. */
|
|
32
|
+
const nearLimit = g => excessOf(g) <= NEAR * Math.max(Math.abs(g.limit), 0.01);
|
|
33
|
+
|
|
34
|
+
/** use[i] <skill>: one patch per tune arg and step, smallest steps first. */
|
|
35
|
+
function skillCandidates(cause, spec) {
|
|
36
|
+
const m = /\buse\[(\d+)\] (\w+)/.exec(cause), i = Number(m?.[1]), u = m && spec.use?.[i];
|
|
37
|
+
const info = m && u?.skill === m[2] ? SKILL_INFO[u.skill] : null;
|
|
38
|
+
if (!info?.tune) return [];
|
|
39
|
+
const out = [];
|
|
40
|
+
for (const n of [1, 2]) for (const [arg, step] of Object.entries(info.tune)) {
|
|
41
|
+
const schema = info.args[arg] ?? {}, now = u.args?.[arg] ?? schema.default;
|
|
42
|
+
if (!isNum(now)) continue;
|
|
43
|
+
for (const sg of STEPS.filter(s => Math.abs(s) === n)) {
|
|
44
|
+
const raw = now + sg * step, value = schema.type === "integer" ? Math.round(raw) : Math.round(raw * 1e4) / 1e4;
|
|
45
|
+
if (!(value > 0) || value === now) continue;
|
|
46
|
+
out.push(u.args ? [{ op: u.args[arg] === undefined ? "add" : "replace", path: `/use/${i}/args/${arg}`, value }]
|
|
47
|
+
: [{ op: "add", path: `/use/${i}/args`, value: { [arg]: value } }]);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** SPEED/ACC/POP between two of the agent's keys: more frames between them, 1 then 2. */
|
|
54
|
+
function keyCandidates(g, cause, spec) {
|
|
55
|
+
if (!["SPEED", "ACC", "POP"].includes(g.kind)) return [];
|
|
56
|
+
const m = /between keys\[(\d+)\] f(\d+) and keys\[(\d+)\] f(\d+)/.exec(cause);
|
|
57
|
+
if (!m) return [];
|
|
58
|
+
const [a, fa, b, fb] = m.slice(1).map(Number), keys = spec.keys ?? [];
|
|
59
|
+
if (keys[a]?.f !== fa || keys[b]?.f !== fb) return [];
|
|
60
|
+
const last = Number.isInteger(spec.frames) ? spec.frames - 1 : Infinity, out = [];
|
|
61
|
+
for (const n of [1, 2]) {
|
|
62
|
+
if (fa - n >= 0) out.push([{ op: "replace", path: `/keys/${a}/f`, value: fa - n }]);
|
|
63
|
+
if (fb + n <= last) out.push([{ op: "replace", path: `/keys/${b}/f`, value: fb + n }]);
|
|
64
|
+
}
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const round4 = v => Math.round(v * 1e4) / 1e4;
|
|
69
|
+
|
|
70
|
+
/** One step (n = 1 or 2, either way) of what places the body at frame f, then of the lean. */
|
|
71
|
+
function placeSteps(spec, f, n) {
|
|
72
|
+
const keys = spec.keys ?? [], out = [];
|
|
73
|
+
const skill = (spec.use ?? []).map((u, i) => ({ u, i })).filter(({ u }) => (u?.skill === "standIdle" || u?.skill === "walkTo") && u.f0 <= f).sort((a, b) => a.u.f0 - b.u.f0).at(-1);
|
|
74
|
+
const roots = keys.map((k, j) => ({ k, j })).filter(({ k }) => Array.isArray(k?.set?.root) && k.set.root.length === 3 && isNum(k.f) && k.f > (skill?.u.f0 ?? -1));
|
|
75
|
+
if (roots.some(({ k }) => k.f <= f)) {
|
|
76
|
+
for (const axis of [0, 2]) for (const sg of [-n, n]) out.push(roots.map(({ k, j }) => ({ op: "replace", path: `/keys/${j}/set/root`, value: k.set.root.map((v, a) => (a === axis ? round4(v + sg * 0.03) : v)) })));
|
|
77
|
+
} else if (skill) {
|
|
78
|
+
const { u, i } = skill, place = SKILL_INFO[u.skill].place ?? {}, steps = u.args?.steps ?? 8;
|
|
79
|
+
for (const [arg, step] of Object.entries(place)) {
|
|
80
|
+
if (arg === "step" && steps <= 2) continue;
|
|
81
|
+
const now = u.args?.[arg] ?? SKILL_INFO[u.skill].args[arg]?.default;
|
|
82
|
+
const set = value => [u.args ? { op: u.args[arg] === undefined ? "add" : "replace", path: `/use/${i}/args/${arg}`, value } : { op: "add", path: `/use/${i}/args`, value: { [arg]: value } }];
|
|
83
|
+
if (Array.isArray(now)) for (const axis of [0, now.length - 1]) for (const sg of [-n, n]) out.push(set(now.map((v, a) => (a === axis ? round4(v + sg * step) : v))));
|
|
84
|
+
else if (isNum(now)) for (const sg of [-n, n]) if (now + sg * step > 0.05) out.push(set(round4(now + sg * step)));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
const pitched = keys.map((k, j) => ({ f: k?.f, j })).filter(({ j }) => isNum(keys[j]?.set?.spine_pitch) && isNum(keys[j].f)).sort((a, b) => a.f - b.f);
|
|
88
|
+
const lean = [pitched.filter(p => p.f <= f).at(-1), pitched.find(p => p.f > f)].filter(Boolean).map(p => p.j);
|
|
89
|
+
for (const sg of lean.length ? [n, -n] : []) out.push(lean.map(j => ({ op: "replace", path: `/keys/${j}/set/spine_pitch`, value: keys[j].set.spine_pitch + sg * LEAN })));
|
|
90
|
+
return out;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const LEANS = [10, 20, 30, -10], EASE = 12;
|
|
94
|
+
|
|
95
|
+
/** WRIST: a spine_pitch key at f that leans the upper body by d from the lean before it, eased back over EASE frames where no lean key is nearer. */
|
|
96
|
+
function leanSteps(spec, f) {
|
|
97
|
+
const keys = spec.keys ?? [], at = Math.round(f), last = Number.isInteger(spec.frames) ? spec.frames - 1 : Infinity;
|
|
98
|
+
const pitched = keys.filter(k => isNum(k?.f) && isNum(k?.set?.spine_pitch)).sort((a, b) => a.f - b.f);
|
|
99
|
+
const prev = pitched.filter(k => k.f < at).at(-1), next = pitched.find(k => k.f > at), base = prev?.set.spine_pitch ?? 0;
|
|
100
|
+
const same = keys.findIndex(k => k?.f === at && k.set && typeof k.set === "object");
|
|
101
|
+
return LEANS.map(d => {
|
|
102
|
+
const value = base + d, ops = [same >= 0 ? { op: keys[same].set.spine_pitch === undefined ? "add" : "replace", path: `/keys/${same}/set/spine_pitch`, value } : { op: "add", path: "/keys/-", value: { f: at, set: { spine_pitch: value } } }];
|
|
103
|
+
if ((!prev || at - prev.f > EASE) && at - EASE >= 0) ops.push({ op: "add", path: "/keys/-", value: { f: at - EASE, set: { spine_pitch: base } } });
|
|
104
|
+
if ((!next || next.f - at > EASE) && at + EASE <= last) ops.push({ op: "add", path: "/keys/-", value: { f: at + EASE, set: { spine_pitch: next?.set.spine_pitch ?? base } } });
|
|
105
|
+
return ops;
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* tuneCandidates(g, spec) -> patch[] (each a JSON Patch on spec; [] when the group is not near its
|
|
111
|
+
* limit, already carries a patch, or its cause names nothing tunable; a GOAL group up to 10 cm
|
|
112
|
+
* short also gets the body placement steps)
|
|
113
|
+
*/
|
|
114
|
+
export function tuneCandidates(g, spec) {
|
|
115
|
+
if (typeof g.cause !== "string") return [];
|
|
116
|
+
if (g.kind === "WRIST" && g.cause.startsWith("fix.self")) return [];
|
|
117
|
+
if (g.kind === "WRIST" && isNum(g.worstFrame)) return [...leanSteps(spec, g.worstFrame), ...[1, 2].flatMap(n => placeSteps(spec, g.worstFrame, n)).filter(p => p.every(op => op.path.endsWith("/spine_pitch")))];
|
|
118
|
+
const near = !g.patch && nearLimit(g) ? [...skillCandidates(g.cause, spec), ...keyCandidates(g, g.cause, spec)] : [];
|
|
119
|
+
if (g.kind !== "GOAL" || !isNum(g.worstFrame) || excessOf(g) > PLACE_MAX) return near;
|
|
120
|
+
return [...near, ...placeSteps(spec, g.worstFrame, 1), ...placeSteps(spec, g.worstFrame, 2)];
|
|
121
|
+
}
|