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
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// Measured, not judged (output.observations.near): how close each hand and foot comes to the scene
|
|
2
|
+
// objects. A hand is its palm and its five fingertips, a foot its sole (geometry.js solePoints);
|
|
3
|
+
// every scene body is a box at its pose that frame (object-pose.js: carried, hinged or still).
|
|
4
|
+
// Per frame the nearest body within FAR m names its top group (the object) and the body (the
|
|
5
|
+
// part); consecutive frames near the same object make a run, and the RUNS closest runs are kept,
|
|
6
|
+
// in frame order. gap is the signed distance to the box (sdBox), < 0 inside. No run: nothing
|
|
7
|
+
// within FAR m. A hand is not measured against what it carries (it is on it), and a follow
|
|
8
|
+
// record (it rides the body) is none of these objects.
|
|
9
|
+
// Why: a hand meant to strum floated 20 cm in front of a guitar with QA clean (session df388a9b),
|
|
10
|
+
// and a toe went 4 cm into a stair's edge during a swing (session e7edb718); no other answer said so.
|
|
11
|
+
import { sdBox, solePoints } from "./geometry.js";
|
|
12
|
+
import { objectPoseAt, handOf } from "../object-pose.js";
|
|
13
|
+
import { isFollow } from "../carried.js";
|
|
14
|
+
import { add, apply, dist, len, scale } from "../compiler/math.js";
|
|
15
|
+
|
|
16
|
+
const FAR = 0.3, RUNS = 2, TIPS = ["Thumb4", "Index4", "Middle4", "Ring4", "Pinky4"];
|
|
17
|
+
const r3 = v => Math.round(v * 1000) / 1000;
|
|
18
|
+
|
|
19
|
+
export const NEAR_UNITS = "near: hand (palm, fingertips), foot (sole) to the nearest object within 0.3 m (top group; part: the body at the closest frame), 2 closest runs; gap [min, f, max, f] m, < 0 inside"
|
|
20
|
+
|
|
21
|
+
/** The effector points of a solved pose s: { hand_l, hand_r, foot_l, foot_r } -> [point, ...]. */
|
|
22
|
+
function effectorPoints(sk, s) {
|
|
23
|
+
const I = sk.index, out = {};
|
|
24
|
+
for (const [S, k] of [["Left", "l"], ["Right", "r"]]) {
|
|
25
|
+
const palm = scale(add(s.P[I[`${S}Hand`]], s.P[I[`${S}HandMiddle1`]]), 0.5);
|
|
26
|
+
out[`hand_${k}`] = [palm, ...TIPS.map(t => s.P[I[`${S}Hand${t}`]]).filter(Boolean)];
|
|
27
|
+
out[`foot_${k}`] = Object.values(solePoints(sk, s, S));
|
|
28
|
+
}
|
|
29
|
+
return out;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** True while a spec.objects entry over body `id` (its own or a group's) hides it at absolute frame f. */
|
|
33
|
+
const hiddenAt = (out, id, f) => [id, ...(out.ctx.scene.objects[id].ancestors ?? [])]
|
|
34
|
+
.some(k => { const o = out.ctx.spec.objects?.[k]; return o?.visible && !((out.baked?.[o.visible]?.[f] ?? 0) > 0.5); });
|
|
35
|
+
|
|
36
|
+
/** The scene bodies at absolute frame f as boxes with their top group, the hand carrying them and a bounding sphere; follow records left out. */
|
|
37
|
+
function boxesAt(out, f) {
|
|
38
|
+
const { scene, spec } = out.ctx, boxes = [];
|
|
39
|
+
for (const [id, o] of Object.entries(scene?.objects ?? {})) {
|
|
40
|
+
const up = o.ancestors ?? [];
|
|
41
|
+
if ([id, ...up].some(k => isFollow(spec.objects?.[k])) || hiddenAt(out, id, f)) continue;
|
|
42
|
+
const pose = objectPoseAt(out, id, f);
|
|
43
|
+
if (!pose) continue;
|
|
44
|
+
const box = { pos: pose.pos, R: pose.R, half: o.half };
|
|
45
|
+
boxes.push({ id, root: up[up.length - 1] ?? id, hand: handOf(out, id, f), box, c: add(box.pos, apply(box.R, [0, o.half[1], 0])), r: len(o.half) });
|
|
46
|
+
}
|
|
47
|
+
return boxes;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Nearest box to a point set: [root, part, gap] within FAR, else null; the boxes hand `side` carries are left out. */
|
|
51
|
+
function nearest(points, boxes, side) {
|
|
52
|
+
const c = scale(points.reduce((a, p) => add(a, p), [0, 0, 0]), 1 / points.length), spread = Math.max(...points.map(p => dist(p, c)));
|
|
53
|
+
let best = null;
|
|
54
|
+
for (const b of boxes) {
|
|
55
|
+
if (side && b.hand === side) continue;
|
|
56
|
+
if (dist(c, b.c) - b.r - spread > (best ? best[2] : FAR)) continue;
|
|
57
|
+
const gap = Math.min(...points.map(p => sdBox(p, b.box)));
|
|
58
|
+
if (gap <= FAR && (!best || gap < best[2])) best = [b.root, b.id, gap];
|
|
59
|
+
}
|
|
60
|
+
return best;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Runs of frames with the same nearest object -> the RUNS closest, in frame order. */
|
|
64
|
+
function runsOf(rows, f0) {
|
|
65
|
+
const runs = [];
|
|
66
|
+
rows.forEach((row, i) => {
|
|
67
|
+
const last = runs[runs.length - 1];
|
|
68
|
+
if (!row) return;
|
|
69
|
+
const [root, part, gap] = row, f = i + f0;
|
|
70
|
+
if (last && last.object === root && last.frames[1] === f - 1) {
|
|
71
|
+
last.frames[1] = f;
|
|
72
|
+
if (gap < last.gap[0]) { last.gap[0] = gap; last.gap[1] = f; last.part = part; }
|
|
73
|
+
if (gap > last.gap[2]) { last.gap[2] = gap; last.gap[3] = f; }
|
|
74
|
+
} else runs.push({ object: root, part, frames: [f, f], gap: [gap, f, gap, f] });
|
|
75
|
+
});
|
|
76
|
+
return runs.sort((a, b) => a.gap[0] - b.gap[0]).slice(0, RUNS).sort((a, b) => a.frames[0] - b.frames[0])
|
|
77
|
+
.map(({ object, part, frames, gap }) => ({ object, ...(part !== object ? { part } : {}), frames, gap: [r3(gap[0]), gap[1], r3(gap[2]), gap[3]] }));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* observeNear(sk, out) -> { hand_l?, hand_r?, foot_l?, foot_r? } (null when nothing is within FAR m
|
|
82
|
+
* of any of them, or the scene has no bodies). Frames are absolute (out.f0 added).
|
|
83
|
+
*/
|
|
84
|
+
export function observeNear(sk, out) {
|
|
85
|
+
if (!Object.keys(out.ctx.scene?.objects ?? {}).length) return null;
|
|
86
|
+
const f0 = out.f0 ?? 0, rows = { hand_l: [], hand_r: [], foot_l: [], foot_r: [] };
|
|
87
|
+
out.frames.forEach(({ s }, i) => {
|
|
88
|
+
const f = i + f0, boxes = boxesAt(out, f), pts = effectorPoints(sk, s);
|
|
89
|
+
for (const k of Object.keys(rows)) {
|
|
90
|
+
// a hand is on what it carries: measure it against everything else
|
|
91
|
+
rows[k].push(nearest(pts[k], boxes, k.startsWith("hand_") ? k.slice(-1) : null));
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
const near = Object.fromEntries(Object.entries(rows).map(([k, r]) => [k, runsOf(r, f0)]).filter(([, r]) => r.length));
|
|
95
|
+
return Object.keys(near).length ? near : null;
|
|
96
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
// QA of scene objects the spec carries (C3 and 7.1), as plain issues { kind, msg, frame, value,
|
|
2
|
+
// limit } (frame indexes out.frames):
|
|
3
|
+
// GRAB "<id>: hand 12.0 cm from it at grab", frame = grab, limit = checks.limits.grab
|
|
4
|
+
// (default 0.03 m); the distance runs from the prop origin to the part boxes' surface
|
|
5
|
+
// at their scene pose (where the hand finds them). An object its visible channel shows
|
|
6
|
+
// only from grab on appears in the hand (a cigarette out of a pocket), and one grabbed
|
|
7
|
+
// at frame 0 is in hand from the take's first frame (C3, as with no grab): nothing to
|
|
8
|
+
// pick up (session d5c110a7: a bat kit left on the floor, grab 0, "63.6 cm").
|
|
9
|
+
// COLLIDE "<part> x <id>": a body capsule in a carried object (every part box of a group) at
|
|
10
|
+
// any frame it is shown, the holding hand and forearm excused (the fingers ride on the
|
|
11
|
+
// hand), and a second hand on the frames it grips the object (../second-hand.js): at
|
|
12
|
+
// weight 0.5 or more, or with its palm inside the object (a grip the arm turns only
|
|
13
|
+
// partly still holds the handle: session 6262a69c, f37-40, the agent allowed it);
|
|
14
|
+
// "<id> x <other>": a carried object in another scene box from grab on, the
|
|
15
|
+
// support it stood on at grab excused for +-3 frames around grab and release. The
|
|
16
|
+
// depths and checks.allow work as for the body (checks.js); <id> is the spec.objects
|
|
17
|
+
// key, so a group reads as one object.
|
|
18
|
+
// REACH "<id>: 6.2 cm off its keys": a keyed object the hand could not hold where its keys ask (key-checks.js)
|
|
19
|
+
import { QA_LIMITS, allowance } from "./checks.js";
|
|
20
|
+
import { keyIssues } from "./key-checks.js";
|
|
21
|
+
import { CAPSULES, segPoints, sdBox } from "./geometry.js";
|
|
22
|
+
import { isCarried, propOrigin } from "../carried.js";
|
|
23
|
+
import { objectBoxesAt, partsOf, spanOf, restingAt } from "../object-pose.js";
|
|
24
|
+
import { add, sub, apply, dot, cross, len, dist, scale, norm } from "../compiler/math.js";
|
|
25
|
+
|
|
26
|
+
const NEAR_SUPPORT = 3;
|
|
27
|
+
|
|
28
|
+
const centre = b => add(b.pos, apply(b.R, [0, b.half[1], 0]));
|
|
29
|
+
const col = (R, i) => [R[0][i], R[1][i], R[2][i]];
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Overlap of two boxes (separating axes): { depth, axis } with depth > 0 when they overlap and
|
|
33
|
+
* axis the unit direction that moves `a` out of `b` by depth; depth <= 0 when apart. With
|
|
34
|
+
* `side` (a direction from b toward where a came from) the exit is taken on that side.
|
|
35
|
+
*/
|
|
36
|
+
export function boxOverlap(a, b, side = null) {
|
|
37
|
+
const ca = centre(a), cb = centre(b), d = sub(ca, cb), A = [0, 1, 2].map(i => col(a.R, i)), B = [0, 1, 2].map(i => col(b.R, i));
|
|
38
|
+
const axes = [...A, ...B, ...A.flatMap(u => B.map(v => cross(u, v)))];
|
|
39
|
+
let best = { depth: Infinity, axis: [0, 1, 0] };
|
|
40
|
+
for (const raw of axes) {
|
|
41
|
+
if (len(raw) < 1e-6) continue;
|
|
42
|
+
const L = norm(raw), r = (box, U) => U.reduce((s, u, i) => s + Math.abs(dot(u, L)) * box.half[i], 0), reach = r(a, A) + r(b, B);
|
|
43
|
+
if (reach - Math.abs(dot(d, L)) <= 0) return { depth: reach - Math.abs(dot(d, L)), axis: L };
|
|
44
|
+
const out = dot(side ?? d, L) < 0 ? scale(L, -1) : L, depth = reach - dot(d, out);
|
|
45
|
+
if (depth < best.depth) best = { depth, axis: out };
|
|
46
|
+
}
|
|
47
|
+
return best;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** True when the palm of hand k (between its index and middle finger, where a grip holds) is inside one of `boxes`: the hand holds the object there. */
|
|
51
|
+
const palmIn = (sk, s, k, boxes) => {
|
|
52
|
+
const S = k === "l" ? "Left" : "Right", p = scale(add(s.P[sk.index[`${S}HandIndex2`]], s.P[sk.index[`${S}HandMiddle2`]]), 0.5);
|
|
53
|
+
return boxes.some(b => sdBox(p, b) <= 0);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/** True while entry o's record is shown (its `visible` channel above 0.5, or no channel) at absolute f. */
|
|
57
|
+
const shownAt = (out, o, f) => !o.visible || (out.baked?.[o.visible]?.[f] ?? 0) > 0.5;
|
|
58
|
+
|
|
59
|
+
/** The part boxes of `id` where the hand finds it (any frame f, the hint asks later ones too): its scene pose, or where a fall before its grab left it (C17). */
|
|
60
|
+
const restingBoxes = (out, id, f) => partsOf(out.ctx.scene, id).map(part => ({ ...out.ctx.scene.objects[part], ...restingAt(out, part, f) }));
|
|
61
|
+
|
|
62
|
+
/** Prop origin to the nearest part box surface of `id` where it rests before grab, at absolute frame f (0 inside). */
|
|
63
|
+
export function grabGap(out, id, f) {
|
|
64
|
+
const { sk, spec } = out.ctx, fr = out.frames[f - (out.f0 ?? 0)];
|
|
65
|
+
if (!fr) return null;
|
|
66
|
+
const p = propOrigin(sk, spec, fr.s, spec.objects[id]);
|
|
67
|
+
return Math.max(0, Math.min(...restingBoxes(out, id, f).map(box => sdBox(p, box))));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** The scene box right below `id` at grab (absolute frame), the support it may touch near grab and release. */
|
|
71
|
+
function supportOf(out, id, grab) {
|
|
72
|
+
const parts = restingBoxes(out, id, grab);
|
|
73
|
+
if (!parts.length) return null;
|
|
74
|
+
const low = Math.min(...parts.map(b => b.pos[1] - Math.abs(b.R[1][0]) * b.half[0] - Math.abs(b.R[1][2]) * b.half[2]));
|
|
75
|
+
const c = scale(parts.map(centre).reduce((s, p) => add(s, p), [0, 0, 0]), 1 / parts.length), probe = [c[0], low - 0.01, c[2]];
|
|
76
|
+
let best = null;
|
|
77
|
+
for (const [oid, box] of Object.entries(out.ctx.objectsAt(grab))) {
|
|
78
|
+
if (box.carried || !box.half) continue;
|
|
79
|
+
const d = sdBox(probe, box);
|
|
80
|
+
if (d <= 0.02 && (!best || d < best[1])) best = [oid, d];
|
|
81
|
+
}
|
|
82
|
+
return best?.[0] ?? null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* objectChecks(sk, out, spec) -> issues
|
|
87
|
+
* @param {object} sk the rig
|
|
88
|
+
* @param {object} out compileMotion result (out.ctx.spec, out.ctx.scene; object-pose.js)
|
|
89
|
+
* @param {object} spec the expanded spec QA measures (checks, objects)
|
|
90
|
+
* @returns {{ kind: string, msg: string, frame: number, value: number, limit: number }[]}
|
|
91
|
+
*/
|
|
92
|
+
export function objectChecks(sk, out, spec) {
|
|
93
|
+
const carried = Object.entries(out.ctx.spec.objects ?? {}).filter(([, o]) => isCarried(o));
|
|
94
|
+
if (!carried.length) return [];
|
|
95
|
+
const checks = spec.checks ?? {}, L = { ...QA_LIMITS, ...checks.limits }, allow = checks.allow, issues = [], f0 = out.f0 ?? 0, I = sk.index;
|
|
96
|
+
const push = (kind, msg, f, value, limit) => issues.push({ kind, msg, frame: f, value: +value.toFixed(4), limit });
|
|
97
|
+
// checks.allow names the carried id or one of its parts, on either side
|
|
98
|
+
const lim = (parts, objs) => parts.reduce((v, p) => objs.reduce((w, x) => w ?? allowance(allow, p, x), v), null) ?? L.allow;
|
|
99
|
+
for (const [id, o] of carried) {
|
|
100
|
+
const [grab, release] = spanOf(out, o), k = spec.props?.[o.prop]?.hand, excused = new Set([`hand_${k}`, `farm_${k}`]);
|
|
101
|
+
if (o.grab !== undefined && grab > 0 && out.frames[grab - f0] && shownAt(out, o, grab - 1)) {
|
|
102
|
+
const gap = grabGap(out, id, grab);
|
|
103
|
+
if (gap > L.grab) push("GRAB", `${id}: hand ${(gap * 100).toFixed(1)} cm from it at grab`, grab - f0, gap, L.grab);
|
|
104
|
+
}
|
|
105
|
+
const support = o.grab !== undefined ? supportOf(out, id, grab) : null;
|
|
106
|
+
const nearSupport = f => Math.abs(f - grab) <= NEAR_SUPPORT || (o.release !== undefined && Math.abs(f - release) <= NEAR_SUPPORT);
|
|
107
|
+
for (let i = 0; i < out.frames.length; i++) {
|
|
108
|
+
const f = i + f0, boxes = objectBoxesAt(out, id, f);
|
|
109
|
+
if (!boxes || !shownAt(out, o, f)) continue;
|
|
110
|
+
const ids = boxes.map(b => b.id), spheres = boxes.map(b => [b, centre(b), len(b.half)]), { s } = out.frames[i];
|
|
111
|
+
const grips = Object.entries(out.frames[i].diag).filter(([k, v]) => /^hold_[lr]$/.test(k) && v?.second === id && (v.w >= 0.5 || palmIn(sk, s, k.slice(-1), boxes))).map(([k]) => `hand_${k.slice(-1)}`);
|
|
112
|
+
for (const [part, a, b, r] of CAPSULES) {
|
|
113
|
+
if (excused.has(part) || grips.includes(part)) continue;
|
|
114
|
+
const A = s.P[I[a]], B = s.P[I[b]], mid = scale(add(A, B), 0.5), reach = r + dist(A, B) / 2, l = lim([part], [id, ...ids]);
|
|
115
|
+
let pts = null, pen = -Infinity;
|
|
116
|
+
for (const [box, c, rb] of spheres) {
|
|
117
|
+
if (reach - (dist(mid, c) - rb) <= l) continue;
|
|
118
|
+
pts ??= segPoints(A, B);
|
|
119
|
+
pen = Math.max(pen, r - Math.min(...pts.map(p => sdBox(p, box))));
|
|
120
|
+
}
|
|
121
|
+
if (pen > l) push("COLLIDE", `${part} x ${id}`, i, pen, l);
|
|
122
|
+
}
|
|
123
|
+
if (f < grab) continue;
|
|
124
|
+
for (const [oid, ob] of Object.entries(out.objectsAt(i))) {
|
|
125
|
+
if (!ob.half || ob.ignore || (oid === support && nearSupport(f))) continue;
|
|
126
|
+
const l = lim([id, ...ids], [oid]), oc = centre(ob), orad = len(ob.half);
|
|
127
|
+
let depth = -Infinity;
|
|
128
|
+
for (const [box, c, rb] of spheres) if (dist(c, oc) < rb + orad + l) depth = Math.max(depth, boxOverlap(box, ob).depth);
|
|
129
|
+
if (depth > l) push("COLLIDE", `${id} x ${oid}`, i, depth, l);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
keyIssues(out, L, push);
|
|
134
|
+
return issues;
|
|
135
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// C6. Hint fixes -> JSON Patch (RFC 6902, json-patch.js) on the agent's own spec.
|
|
2
|
+
//
|
|
3
|
+
// A hint function may return fix (non-enumerable on the group, never in the output):
|
|
4
|
+
// { channel: "hand_l", f: 106, delta: [dx, dy, dz] | n } move a keyed channel by delta at f
|
|
5
|
+
// { channel: "elbow_r", f: 120, value: [x, y, z] | n } set a channel at f
|
|
6
|
+
// { path: "/fix/retime", value: true } set a spec path
|
|
7
|
+
// { path: "/anchors/cupHandle/p", delta: [dx, dy, dz] } move a spec value by delta
|
|
8
|
+
// { channel: "root", f, delta, hold: true } move the body that stands at f
|
|
9
|
+
// Each becomes g.patch = [{ op, path, value }] against opts.userSpec (the spec as the agent sent
|
|
10
|
+
// it, use[] not expanded): a user key that sets the channel within f +-2 -> replace
|
|
11
|
+
// /keys/<i>/set/<ch>; a standIdle stance moved sideways -> /use/<i>/args/at; otherwise add
|
|
12
|
+
// /keys/- with the frame's value moved. A path fix replaces the value, or adds it when only the
|
|
13
|
+
// last step is missing (or the top-level /fix object). A hold fix moves what placed the body
|
|
14
|
+
// at f by the delta's x and z: a standIdle args.at, a walkTo's length (firstStep with 2 steps,
|
|
15
|
+
// else step; its to when steps follow from it), or the run of the agent's root keys around f.
|
|
16
|
+
// g.patch is set only when the patch applies to userSpec and changes it.
|
|
17
|
+
import { applyPatch } from "../json-patch.js";
|
|
18
|
+
|
|
19
|
+
const NEAR = 2;
|
|
20
|
+
const round4 = v => (Array.isArray(v) ? v.map(round4) : typeof v === "number" ? Math.round(v * 1e4) / 1e4 : v);
|
|
21
|
+
const tokens = path => path.split("/").slice(1).map(t => t.replaceAll("~1", "/").replaceAll("~0", "~"));
|
|
22
|
+
const pointer = parts => parts.map(t => `/${String(t).replaceAll("~", "~0").replaceAll("/", "~1")}`).join("");
|
|
23
|
+
const isNum = x => typeof x === "number" && Number.isFinite(x);
|
|
24
|
+
|
|
25
|
+
/** The value at a JSON pointer, or undefined. */
|
|
26
|
+
function valueAt(doc, parts) {
|
|
27
|
+
let node = doc;
|
|
28
|
+
for (const t of parts) {
|
|
29
|
+
if (node === null || typeof node !== "object" || !Object.hasOwn(node, t)) return undefined;
|
|
30
|
+
node = node[t];
|
|
31
|
+
}
|
|
32
|
+
return node;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** v moved by d (same shape: a number, or a vector of numbers), else undefined. */
|
|
36
|
+
function moved(v, d) {
|
|
37
|
+
if (isNum(v) && isNum(d)) return v + d;
|
|
38
|
+
if (Array.isArray(v) && Array.isArray(d) && v.length === d.length && v.every(isNum) && d.every(isNum)) return v.map((x, i) => x + d[i]);
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** The user key nearest f (within NEAR frames) that sets channel ch, as its index; -1 if none. */
|
|
43
|
+
function userKey(userSpec, ch, f) {
|
|
44
|
+
let best = -1;
|
|
45
|
+
(userSpec.keys ?? []).forEach((k, i) => {
|
|
46
|
+
if (!k?.set || !Object.hasOwn(k.set, ch) || !isNum(k.f) || Math.abs(k.f - f) > NEAR) return;
|
|
47
|
+
if (best < 0 || Math.abs(k.f - f) < Math.abs(userSpec.keys[best].f - f)) best = i;
|
|
48
|
+
});
|
|
49
|
+
return best;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** A standIdle stance moved by the root's sideways delta: /use/<i>/args/at. */
|
|
53
|
+
function standPatch(X, userSpec, fix, near) {
|
|
54
|
+
const m = /^use\[(\d+)\] standIdle$/.exec(near?.src ?? ""), d = fix.delta;
|
|
55
|
+
if (!m || fix.channel !== "root" || !Array.isArray(d) || Math.hypot(d[0], d[2]) < Math.abs(d[1])) return null;
|
|
56
|
+
const i = Number(m[1]), u = userSpec.use?.[i], root = X.fr(near.f).c.root;
|
|
57
|
+
if (u?.skill !== "standIdle") return null;
|
|
58
|
+
const at = Array.isArray(u.args?.at) ? u.args.at : root && [root[0], root[2]];
|
|
59
|
+
if (!at) return null;
|
|
60
|
+
const value = round4(at.map((v, j) => v + (j === 0 ? d[0] : j === at.length - 1 ? d[2] : 0)));
|
|
61
|
+
if (!u.args) return [{ op: "add", path: `/use/${i}/args`, value: { at: value } }];
|
|
62
|
+
return [{ op: Object.hasOwn(u.args, "at") ? "replace" : "add", path: `/use/${i}/args/at`, value }];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** A walkTo made longer or shorter by the delta along the body's heading at f (null when it is mostly sideways). */
|
|
66
|
+
function walkPatch(X, userSpec, i, fix) {
|
|
67
|
+
const u = userSpec.use?.[i], a = u?.args ?? {}, d = fix.delta, h = (X.fr(fix.f).c.yaw ?? 0) * Math.PI / 180;
|
|
68
|
+
const along = d[0] * Math.sin(h) + d[2] * Math.cos(h);
|
|
69
|
+
if (u?.skill !== "walkTo" || Math.abs(d[0] * Math.cos(h) - d[2] * Math.sin(h)) > Math.abs(along)) return null;
|
|
70
|
+
const set = (arg, value) => [u.args ? { op: Object.hasOwn(a, arg) ? "replace" : "add", path: `/use/${i}/args/${arg}`, value: round4(value) } : { op: "add", path: `/use/${i}/args`, value: { [arg]: round4(value) } }];
|
|
71
|
+
if (a.steps === undefined && Array.isArray(a.to)) return set("to", a.to.map((v, j) => v + (j === 0 ? d[0] : j === a.to.length - 1 ? d[2] : 0)));
|
|
72
|
+
const steps = a.steps ?? 8, arg = steps <= 2 ? "firstStep" : "step", now = a[arg] ?? (arg === "step" ? 0.5 : 0.4), value = now + along / Math.max(1, steps - 2);
|
|
73
|
+
return steps >= 2 && value > 0.05 ? set(arg, value) : null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** The agent's root keys around f (no skill root key between them) moved by the delta's x and z. */
|
|
77
|
+
function rootRunPatch(X, userSpec, fix) {
|
|
78
|
+
const list = X.keys("root"), mine = k => /^keys\[\d+\]$/.test(k.src);
|
|
79
|
+
const at = list.findLastIndex(k => k.f <= fix.f);
|
|
80
|
+
if (at < 0 || !mine(list[at])) return null;
|
|
81
|
+
let a = at, b = at;
|
|
82
|
+
while (a > 0 && mine(list[a - 1])) a -= 1;
|
|
83
|
+
while (b < list.length - 1 && mine(list[b + 1])) b += 1;
|
|
84
|
+
return list.slice(a, b + 1).map(k => {
|
|
85
|
+
const j = Number(/\d+/.exec(k.src)[0]), r = userSpec.keys?.[j]?.set?.root;
|
|
86
|
+
return Array.isArray(r) && r.length === 3 ? { op: "replace", path: `/keys/${j}/set/root`, value: round4([r[0] + fix.delta[0], r[1], r[2] + fix.delta[2]]) } : null;
|
|
87
|
+
}).filter(Boolean);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** fix.hold: what placed the body at f (the root key at or before it) moves. */
|
|
91
|
+
function bodyPatch(X, userSpec, fix) {
|
|
92
|
+
if (!Array.isArray(fix.delta) || !X?.fr) return null;
|
|
93
|
+
const prev = X.bracket("root", fix.f).prev, m = /^use\[(\d+)\] (standIdle|walkTo)\b/.exec(prev?.src ?? "");
|
|
94
|
+
if (m?.[2] === "standIdle") return standPatch(X, userSpec, fix, prev);
|
|
95
|
+
if (m?.[2] === "walkTo") return walkPatch(X, userSpec, Number(m[1]), fix);
|
|
96
|
+
const run = prev && rootRunPatch(X, userSpec, fix);
|
|
97
|
+
return run?.length ? run : null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function channelPatch(X, userSpec, fix) {
|
|
101
|
+
const { channel: ch, f } = fix;
|
|
102
|
+
if (typeof ch !== "string" || !isNum(f)) return null;
|
|
103
|
+
if (fix.hold) return bodyPatch(X, userSpec, fix);
|
|
104
|
+
const to = v => (fix.value !== undefined ? fix.value : moved(v, fix.delta));
|
|
105
|
+
const i = userKey(userSpec, ch, f);
|
|
106
|
+
if (i >= 0) {
|
|
107
|
+
const value = to(userSpec.keys[i].set[ch]);
|
|
108
|
+
return value === undefined ? null : [{ op: "replace", path: pointer(["keys", i, "set", ch]), value: round4(value) }];
|
|
109
|
+
}
|
|
110
|
+
if (!X?.fr) return null;
|
|
111
|
+
const { prev, next } = X.bracket(ch, f);
|
|
112
|
+
const near = prev && f - prev.f <= NEAR ? prev : next && next.f - f <= NEAR ? next : null;
|
|
113
|
+
const stand = fix.delta !== undefined && standPatch(X, userSpec, fix, near);
|
|
114
|
+
if (stand) return stand;
|
|
115
|
+
const value = to(X.fr(f).c[ch]);
|
|
116
|
+
return value === undefined ? null : [{ op: "add", path: "/keys/-", value: { f: Math.round(f), set: { [ch]: round4(value) } } }];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function pathPatch(userSpec, fix) {
|
|
120
|
+
if (typeof fix.path !== "string" || !fix.path.startsWith("/")) return null;
|
|
121
|
+
const parts = tokens(fix.path), now = valueAt(userSpec, parts);
|
|
122
|
+
const value = fix.delta === undefined ? fix.value : now === undefined && fix.path.endsWith("/offset") ? fix.delta : moved(now, fix.delta);
|
|
123
|
+
if (value === undefined) return null;
|
|
124
|
+
if (now !== undefined) return [{ op: "replace", path: fix.path, value: round4(value) }];
|
|
125
|
+
const parent = valueAt(userSpec, parts.slice(0, -1));
|
|
126
|
+
if (parent !== undefined) return [{ op: "add", path: fix.path, value: round4(value) }];
|
|
127
|
+
// a missing top-level object (spec.fix): add it with the one member
|
|
128
|
+
if (parts.length === 2 && parts[0] === "fix") return [{ op: "add", path: pointer([parts[0]]), value: { [parts[1]]: round4(value) } }];
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** The patch for one fix, or null when it does not apply mechanically or changes nothing. */
|
|
133
|
+
function patchOf(fix, X, userSpec) {
|
|
134
|
+
const patch = fix.channel !== undefined ? channelPatch(X, userSpec, fix) : fix.path !== undefined ? pathPatch(userSpec, fix) : null;
|
|
135
|
+
if (!patch) return null;
|
|
136
|
+
try {
|
|
137
|
+
return JSON.stringify(applyPatch(userSpec, patch)) === JSON.stringify(userSpec) ? null : patch;
|
|
138
|
+
} catch {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* attachPatches(groups, { X, userSpec }) -> groups (the same array)
|
|
145
|
+
* @param {object[]} groups ranked QA groups, each maybe with a non-enumerable g.fix
|
|
146
|
+
* @param {{ X: object, userSpec?: object }} ctx X = the explain context (qa/explain.js: X.out,
|
|
147
|
+
* X.spec the expanded spec, X.keys(ch), X.bracket(ch, f)); userSpec undefined = no patches
|
|
148
|
+
*/
|
|
149
|
+
export function attachPatches(groups, { X, userSpec } = {}) {
|
|
150
|
+
if (!userSpec || typeof userSpec !== "object") return groups;
|
|
151
|
+
for (const g of groups) {
|
|
152
|
+
if (!g.fix) continue;
|
|
153
|
+
try {
|
|
154
|
+
const patch = patchOf(g.fix, X, userSpec);
|
|
155
|
+
if (patch) g.patch = patch;
|
|
156
|
+
} catch {
|
|
157
|
+
// a fix the context cannot place (no frame, no key table) gets no patch
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return groups;
|
|
161
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// C6. How bad a QA issue group is, one formula for the agent (qa.groups order and severity),
|
|
2
|
+
// the open-parameter search (search.js cost) and repair (repair.js): the issue count weighted by
|
|
3
|
+
// how far the worst issue is past its limit, at most 4x. See docs/motion-api-v3.1.md.
|
|
4
|
+
|
|
5
|
+
/** Kinds whose value must stay ABOVE the limit (a cosine, a drop, a clearance). */
|
|
6
|
+
export const LOW_IS_BAD = new Set(["ORIENT", "BELOW", "CLEARANCE"]);
|
|
7
|
+
|
|
8
|
+
/** How far the worst issue of group g is past its limit (>= 0, in the kind's unit). */
|
|
9
|
+
export const excessOf = g => Math.max(0, LOW_IS_BAD.has(g.kind) ? g.limit - g.worst : g.worst - g.limit);
|
|
10
|
+
|
|
11
|
+
/** Unrounded severity; search.js sums these as its cost. */
|
|
12
|
+
export const weightOf = g => g.count * (1 + Math.min(3, excessOf(g) / Math.max(Math.abs(g.limit), 0.01)));
|
|
13
|
+
|
|
14
|
+
/** Severity of group g, rounded to 2 decimals. */
|
|
15
|
+
export const severityOf = g => Math.round(weightOf(g) * 100) / 100;
|
|
16
|
+
|
|
17
|
+
/** A measurement in a subject: a free-standing number ("12.0 cm", "120deg", "-0.30") or a frame ("f150"). */
|
|
18
|
+
const MEASURE = /(?<![\w\-.[])[-+]?\d+(\.\d+)?|\bf\d+\b/g;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The grouping key of an issue (kind + subject): every measurement in the subject becomes "#",
|
|
22
|
+
* so one problem at many frames and depths is one group. Digits that are part of a name stay:
|
|
23
|
+
* an id ("cube-6", "empty-2"), a bone ("LeftHandIndex3"), a bracketed index ("contacts[1]", a
|
|
24
|
+
* default check name, qa/check-names.js) and the whole "<name>: " prefix of a named check or goal.
|
|
25
|
+
*/
|
|
26
|
+
export const groupKey = g => {
|
|
27
|
+
const cut = g.subject.indexOf(": "), head = cut >= 0 ? g.subject.slice(0, cut + 2) : "";
|
|
28
|
+
return `${g.kind} ${head}${g.subject.slice(head.length).replace(MEASURE, m => (m[0] === "f" ? "f#" : "#"))}`;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Adds excess (4 decimals) and severity to every group and returns them ordered: GOAL groups
|
|
33
|
+
* first, then severity, then count, both descending (ties keep their order).
|
|
34
|
+
*/
|
|
35
|
+
export function rankGroups(groups) {
|
|
36
|
+
for (const g of groups) { g.excess = Math.round(excessOf(g) * 1e4) / 1e4; g.severity = severityOf(g); }
|
|
37
|
+
return groups.sort((a, b) => (b.kind === "GOAL") - (a.kind === "GOAL") || b.severity - a.severity || b.count - a.count);
|
|
38
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Task checks the agent declares in spec.checks (balance is style: style.js).
|
|
2
|
+
// contacts: [{ name, point, target, tol, when }] point on target (e.g. filter on lips)
|
|
3
|
+
// orient: [{ name, from, to, along, minCos, when }] direction from->to against "face" | "up" | [x,y,z]
|
|
4
|
+
// below: [{ name, joint, ref, min, when }] joint at least `min` m below ref (no chicken wing)
|
|
5
|
+
// clearance: [{ name, part, object, min, frames }] body part keeps `min` m from an object
|
|
6
|
+
// appear: [{ name, channel, joint, anchor, tol }] a visibility switch happens with joint at anchor
|
|
7
|
+
// supports: [{ object, part, tol }] frames where `part` rests on `object` skip balance (style.js)
|
|
8
|
+
// point/from/to: "joint:Name" | "prop:name.point" | "anchor:name"; when: weight channel (> 0.99) or [f0, f1].
|
|
9
|
+
// f below indexes out.frames (frame out.f0 + f when the compile was windowed).
|
|
10
|
+
import { sub, dot, norm, len } from "../compiler/math.js";
|
|
11
|
+
import { carried } from "../compiler/limbs.js";
|
|
12
|
+
import { resolveAnchor } from "../compiler/anchors.js";
|
|
13
|
+
import { CAPSULES, segPoints, sdBox } from "./geometry.js";
|
|
14
|
+
|
|
15
|
+
export function pointOf(out, f, ref) {
|
|
16
|
+
const { s, diag, c } = out.frames[f], [kind, rest] = ref.split(/:(.*)/s);
|
|
17
|
+
if (kind === "joint") return s.P[out.ctx.sk.index[rest]];
|
|
18
|
+
if (kind === "prop") { const [p, pt] = rest.split("."); return diag.props?.[p]?.[pt] ?? null; }
|
|
19
|
+
if (kind === "anchor") return resolveAnchor(out.ctx, rest, s, diag, c);
|
|
20
|
+
throw new Error(`MOTION_SPEC_INVALID: check point "${ref}"`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const active = (out, f, when) => (Array.isArray(when) ? f + (out.f0 ?? 0) >= when[0] && f + (out.f0 ?? 0) <= when[1] : when ? (out.frames[f].c[when] ?? 0) > 0.99 : true);
|
|
24
|
+
|
|
25
|
+
function alongDir(out, f, along) {
|
|
26
|
+
if (along === "up") return [0, 1, 0];
|
|
27
|
+
if (along === "face") return carried(out.ctx.sk, out.frames[f].s, out.ctx.sk.index.Head, [0, 0, 1]);
|
|
28
|
+
return norm(along);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function specChecks(out, checks = {}) {
|
|
32
|
+
const issues = [], n = out.frames.length, sk = out.ctx.sk, I = sk.index;
|
|
33
|
+
const push = (kind, msg, f, value, limit) => issues.push({ kind, msg, frame: f, value: +value.toFixed(4), limit });
|
|
34
|
+
for (let f = 0; f < n; f++) {
|
|
35
|
+
for (const k of checks.contacts ?? []) if (active(out, f, k.when)) {
|
|
36
|
+
const a = pointOf(out, f, k.point), b = pointOf(out, f, k.target);
|
|
37
|
+
if (!a || !b) continue;
|
|
38
|
+
const e = len(sub(a, b));
|
|
39
|
+
if (e > (k.tol ?? 0.01)) push("CONTACT", `${k.name}: ${(e * 100).toFixed(1)} cm apart`, f, e, k.tol ?? 0.01);
|
|
40
|
+
}
|
|
41
|
+
for (const k of checks.orient ?? []) if (active(out, f, k.when)) {
|
|
42
|
+
const a = pointOf(out, f, k.from), b = pointOf(out, f, k.to);
|
|
43
|
+
if (!a || !b) continue;
|
|
44
|
+
const cosv = dot(norm(sub(b, a)), alongDir(out, f, k.along));
|
|
45
|
+
if (cosv < (k.minCos ?? 0.64)) push("ORIENT", `${k.name}: cos ${cosv.toFixed(2)}`, f, cosv, k.minCos ?? 0.64);
|
|
46
|
+
}
|
|
47
|
+
for (const k of checks.below ?? []) if (active(out, f, k.when)) {
|
|
48
|
+
const { P } = out.frames[f].s, drop = P[I[k.ref]][1] - P[I[k.joint]][1];
|
|
49
|
+
if (drop < (k.min ?? 0.08)) push("BELOW", `${k.name}: ${k.joint} only ${(drop * 100).toFixed(1)} cm below ${k.ref}`, f, drop, k.min ?? 0.08);
|
|
50
|
+
}
|
|
51
|
+
for (const k of checks.clearance ?? []) if (active(out, f, k.frames)) {
|
|
52
|
+
const box = out.objectsAt(f)[k.object], cap = CAPSULES.find(c => c[0] === k.part);
|
|
53
|
+
if (!box || !cap) continue;
|
|
54
|
+
const { P } = out.frames[f].s, d = Math.min(...segPoints(P[I[cap[1]]], P[I[cap[2]]]).map(p => sdBox(p, box))) - cap[3];
|
|
55
|
+
if (d < k.min) push("CLEARANCE", `${k.name}: ${(d * 100).toFixed(1)} cm`, f, d, k.min);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
for (const k of checks.appear ?? []) for (let f = 1; f < n; f++) {
|
|
59
|
+
const was = (out.frames[f - 1].c[k.channel] ?? 0) > 0.5, is = (out.frames[f].c[k.channel] ?? 0) > 0.5;
|
|
60
|
+
if (was === is) continue;
|
|
61
|
+
const e = len(sub(pointOf(out, f, `joint:${k.joint}`), pointOf(out, f, `anchor:${k.anchor}`)));
|
|
62
|
+
if (e > (k.tol ?? 0.035)) push("APPEAR", `${k.name}: switches with the hand ${(e * 100).toFixed(1)} cm from ${k.anchor}`, f, e, k.tol ?? 0.035);
|
|
63
|
+
}
|
|
64
|
+
return issues;
|
|
65
|
+
}
|