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
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
// Shared by the browser and sidecar. No Node, React, renderer or provider imports.
|
|
2
2
|
import { STUDIO_ELEMENTS, isSettableElement } from "./studio-elements.js";
|
|
3
|
+
import { STUDIO_ERROR_CODES } from "./studio-error-codes.js";
|
|
4
|
+
import { EFFECT_VALUE_SCHEMA } from "./effects/effect-record.js";
|
|
5
|
+
import { wrongType, unknownField, missingField, arrayRefusal, duplicateItem, stringRefusal, enumRefusal, numberRefusal, unionRefusal } from "./studio-schema-errors.js";
|
|
3
6
|
// Mirrors PREVIS_MODES (project.js) and POSTURES (scenes.js). Neither is imported:
|
|
4
7
|
// both pull in three.js, which the agent sidecar must not load. The protocol test pins the lists to their owners.
|
|
5
8
|
const PREVIS_MODES = ["storyboard", "animation"], POSTURES = ["stand", "sit", "lie"];
|
|
@@ -10,7 +13,7 @@ export const STUDIO_PROTOCOL_VERSION = "studio-agent-v1";
|
|
|
10
13
|
export const STUDIO_CONTEXT_MAX_BYTES = 64 * 1024;
|
|
11
14
|
export const STUDIO_CONTEXT_LIMITS = Object.freeze({ entities: 24, entityIndex: 400, shots: 8, assets: 48, recentReceipts: 3, jobs: 8 });
|
|
12
15
|
export const STUDIO_TOOL_FAMILIES = Object.freeze(["inspect_studio", "run_action", "verify_result"]);
|
|
13
|
-
export const STUDIO_TOOL_ALIASES = Object.freeze(["operate_studio", "arrange_objects", "arrange_characters", "patch_elements", "frame_shot", "
|
|
16
|
+
export const STUDIO_TOOL_ALIASES = Object.freeze(["operate_studio", "arrange_objects", "arrange_characters", "patch_elements", "frame_shot", "undo_edit"]);
|
|
14
17
|
export const STUDIO_TOOL_LABELS = Object.freeze({
|
|
15
18
|
inspect_studio: "Read the scene",
|
|
16
19
|
operate_studio: "Selection and view",
|
|
@@ -18,7 +21,6 @@ export const STUDIO_TOOL_LABELS = Object.freeze({
|
|
|
18
21
|
arrange_characters: "Arrange characters",
|
|
19
22
|
patch_elements: "Edit properties",
|
|
20
23
|
frame_shot: "Frame the shot",
|
|
21
|
-
generate_motion: "Generate motion",
|
|
22
24
|
verify_result: "Verify the result",
|
|
23
25
|
undo_edit: "Undo an edit",
|
|
24
26
|
run_action: "Run an editor action",
|
|
@@ -28,12 +30,7 @@ export const STUDIO_TOOLS = Object.freeze(Object.keys(STUDIO_TOOL_LABELS));
|
|
|
28
30
|
* object→objects, shot→shot, stage→stage. */
|
|
29
31
|
export const STUDIO_PATCH_KINDS = Object.freeze(["character", "object", "shot", "stage"]);
|
|
30
32
|
export const STUDIO_PATCH_DOMAINS = Object.freeze({ character: "cast", object: "objects", shot: "shot", stage: "stage" });
|
|
31
|
-
export
|
|
32
|
-
"INVALID_ARGUMENT", "INVALID_CONTEXT", "INVALID_IDENTITY", "INVALID_TURN_ID", "INVALID_SESSION_ID", "INVALID_RECEIPT", "INVALID_RANGE", "INVALID_REQUEST",
|
|
33
|
-
"UNKNOWN_TOOL", "UNKNOWN_VARIANT", "DUPLICATE_NAME", "CONTEXT_LIMIT", "CONTEXT_TOO_LARGE", "AMBIGUOUS_TARGET", "AMBIGUOUS_BASIS", "TARGET_NOT_READY", "TARGET_BUSY",
|
|
34
|
-
"STALE_TARGET", "STALE_SCENE", "STALE_ENVIRONMENT", "STALE_CURSOR", "CAPABILITY_MISSING", "LIVE_HUB_UNAVAILABLE", "AUTH_REQUIRED", "GENERATION_LIMIT", "RATE_LIMITED", "BACKEND_UNAVAILABLE",
|
|
35
|
-
"VERIFICATION_FAILED", "REPAIR_REGRESSED", "CANCELLED", "UNCERTAIN_APPLY", "UNDO_CONFLICT", "CONFIRMATION_REQUIRED", "TIMEOUT", "UNDO_EXPIRED",
|
|
36
|
-
]);
|
|
33
|
+
export { STUDIO_ERROR_CODES };
|
|
37
34
|
export const STUDIO_VARIANTS = freezeStudioData({
|
|
38
35
|
selectionKinds: ["scene", "object", "character", "rig", "camera"], modes: ["scene", "pose", "camera", "motion"], shotModes: ["keys", "follow", "rail"],
|
|
39
36
|
framingSizes: ["extreme close-up", "close-up", "medium close-up", "medium shot", "medium-wide shot", "wide shot", "extreme wide shot"],
|
|
@@ -60,8 +57,7 @@ const record = value => value !== null && typeof value === "object" && !Array.is
|
|
|
60
57
|
const text = (maxLength = 120) => ({ type: "string", minLength: 1, maxLength });
|
|
61
58
|
const number = (minimum, maximum) => ({ type: "number", ...(minimum !== undefined ? { minimum } : {}), ...(maximum !== undefined ? { maximum } : {}) });
|
|
62
59
|
const integer = (minimum = 0, maximum = Number.MAX_SAFE_INTEGER) => ({ ...number(minimum, maximum), type: "integer" });
|
|
63
|
-
const bool = { type: "boolean" };
|
|
64
|
-
const literal = value => ({ const: value });
|
|
60
|
+
const bool = { type: "boolean" }, literal = value => ({ const: value });
|
|
65
61
|
const choices = values => ({ type: "string", enum: values });
|
|
66
62
|
const nullable = schema => ({ oneOf: [schema, { type: "null" }] });
|
|
67
63
|
const array = (items, maxItems, minItems = 0, uniqueItems = false) => ({ type: "array", items, minItems, maxItems, ...(uniqueItems ? { uniqueItems } : {}) });
|
|
@@ -114,9 +110,6 @@ const characterOp = union(
|
|
|
114
110
|
object({ op: literal("update"), characterId: id }, { position, facing, scale: positive, name, hidden: bool }),
|
|
115
111
|
object({ op: literal("remove"), characterId: id }),
|
|
116
112
|
);
|
|
117
|
-
export const BEAT_TEXT_MAX = 500;
|
|
118
|
-
const generateSource = object({ kind: literal("generate"), beats: array(object({ text: { ...text(BEAT_TEXT_MAX), description: 'An English description of the body movement starting with "A person". The editor refuses prompts over 500 characters.' } }, { seconds: number(0.5, 60) }), 8, 1) }, { durationSeconds: number(2, 60), seed: integer(-2147483648, 2147483647) });
|
|
119
|
-
const source = union(generateSource, object({ kind: literal("reuse"), artifactId: id }));
|
|
120
113
|
|
|
121
114
|
/* ----------------------------------------------- element patches ----
|
|
122
115
|
* The accepted fields of `patch_elements` are DERIVED from the element
|
|
@@ -126,18 +119,20 @@ const source = union(generateSource, object({ kind: literal("reuse"), artifactId
|
|
|
126
119
|
* (schedules, routes, pictures) declare their shape here, because the table
|
|
127
120
|
* records what an element IS, not how JSON carries it. */
|
|
128
121
|
const dataImage = { ...text(2 * 1024 * 1024), pattern: "^data:image/[A-Za-z0-9.+-]+[;,]" };
|
|
129
|
-
|
|
122
|
+
|
|
130
123
|
const cameraKey = object({ frame: integer(), framing: object({ pos: vec3, yaw: number(), pitch: number(), fovDeg: number(1, 179) }) }, { id });
|
|
131
124
|
// A mark is an arc fraction, or { t, bank, pitch } when the object should lean
|
|
132
125
|
// there (degrees, +-90). The route normaliser (src/object-path.js) owns the
|
|
133
126
|
// rules; this only carries the shape through untouched.
|
|
134
127
|
const routeMark = union(number(0, 1), object({ t: number(0, 1) }, { bank: number(-90, 90), pitch: number(-90, 90) }));
|
|
135
128
|
const objectRoute = nullable(object({ points: array(vec3, 64, 2) }, { speed: number(0, 50), faceTravel: bool, loop: bool, extend: bool, marks: array(routeMark, 6) }));
|
|
129
|
+
// A practical light and a glow (src/object-look.js owns the defaults and the repair).
|
|
130
|
+
const objectLight = object({}, { type: choices(["point", "spot"]), color: { ...text(32), pattern: "^#[0-9a-fA-F]{6}$" }, intensity: number(0, 1000), range: number(0, 100), angle: number(1, 89), softness: number(0, 1), shadow: bool, flicker: number(0, 1), aim: nullable(vec3) });
|
|
131
|
+
const objectGlow = nullable(object({}, { color: { ...text(32), pattern: "^#[0-9a-fA-F]{6}$" }, intensity: number(0, 20), halo: number(0, 4) }));
|
|
136
132
|
const PATCH_VALUE_SCHEMAS = {
|
|
137
|
-
"character.pose": nullable(id), "character.identityImage": nullable(dataImage), "
|
|
133
|
+
"character.pose": nullable(id), "character.identityImage": nullable(dataImage), "object.light": objectLight, "object.glow": objectGlow, "object.effect": EFFECT_VALUE_SCHEMA,
|
|
138
134
|
"object.parent": nullable(id), "object.path": objectRoute, "stage.environmentImage": nullable(dataImage),
|
|
139
|
-
"shot.cameraKeys": array(cameraKey, 64), "shot.targetModel": nullable(id)
|
|
140
|
-
};
|
|
135
|
+
"shot.cameraKeys": array(cameraKey, 64), "shot.targetModel": nullable(id) };
|
|
141
136
|
/** One declared element as a JSON value schema, or null when the declaration
|
|
142
137
|
* carries no carriable shape (a structured element without a declared value
|
|
143
138
|
* schema above). Null elements are omitted from the patch schema, and
|
|
@@ -204,8 +199,7 @@ const toolSchemas = {
|
|
|
204
199
|
arrange_characters: object({ ops: array(characterOp, 8, 1) }),
|
|
205
200
|
patch_elements: object({ ops: array(patchOp, 32, 1) }),
|
|
206
201
|
frame_shot: object({ subjectIds: ids(1), framing }, { shotId: id, caption: { type: "string", maxLength: 500 }, keyAtFrame: integer() }),
|
|
207
|
-
|
|
208
|
-
verify_result: object({ checks: array(choices(["placement", "framing", "motion"]), 3, 1, true) }, { receiptId: id, targets: ids(), range: union(literal("whole_clip"), range), visual: { ...choices(["none", "frame", "contact_sheet", "plan"]), default: "none" } }),
|
|
202
|
+
verify_result: object({ checks: { ...array(choices(["placement", "framing", "motion", "lighting"]), 4, 1, true), description: "lighting measures the shot frame rendered at the playhead: checks.lighting { meanLuma 0..1, clippedPct (a channel at full scale), crushedPct (pure black), subjects: [{ id, meanLuma, contrast (5th to 95th percentile luma spread, 0..1) }] inside each target character's projected box, look: { preset, envLight, ao } the stage look's environment light and occlusion strength }. motion also answers exportFraming (one per character with a take): what the camera the export renders shows of the take over sampledFrames: camera (shot where a shot covers the frame, studio = the Studio camera as it stands when none does; shotIds), character { screenFraction [min, max] of the frame height, inFrame [lowest share of its joints' screen box inside the frame, frame], behindCamera, cut { head, hand_l, hand_r, foot_l, foot_r } }, and every object the take names { id, name, inFrame, out, cut }; out, cut and behindCamera are spans [first, last] of sampled frames; { character: { id }, reason } when it cannot be measured" } }, { receiptId: id, targets: ids(), range: union(literal("whole_clip"), range), visual: { ...choices(["none", "frame", "contact_sheet", "plan", "trace"]), default: "none" }, frames: { ...union(choices(["even", "beats"]), array(integer(), 8, 1, true)), default: "even", description: "contact_sheet frames: even spacing, beats (grab/release, beat starts, the worst QA frames, then even fill, answered in visualRefs[0].picks), or up to 8 timeline frames of your choice" }, views: { ...array(choices(["front", "side", "three-quarter", "top"]), 2, 1, true), description: "contact_sheet from cameras of its own around the character instead of the shot camera (which stays as it is): a row per view, each held still over the frames and framed on focus, so small actions fill the tile; side and three-quarter look from the focus's side; a sheet always holds two views at least 60 degrees apart: if the asked ones look along nearly one line, side, front or top is added and named in visualRefs[0].addedView" }, focus: { ...union(text(120), array(text(120), 3, 1, true)), description: "with views: what to frame, as refs (object:<id>, joint:<Bone>, prop:<prop>.<point>, landmark:mouth); default the whole character" }, trail: { ...union(text(120), array(text(120), 3, 1, true)), description: "with views: refs whose path over the range is drawn as one dot per frame (spacing shows speed; later frames faint, the tile's own frame ringed), colours in visualRefs[0].trails" }, ghosts: { ...bool, description: "with views: each tile also shows the first target's poses around its frame as translucent ghosts (onion skin), earlier ones blue, later ones orange, carried props included, so spacing and arcs read in one tile; how many and how far apart is view.setGhosts (while it is off: 3 each side, every 4 frames), answered in visualRefs[0].ghosts" } }),
|
|
209
203
|
undo_edit: object({ receiptId: id }),
|
|
210
204
|
run_action: object({ action: id }, { args: openObject, confirmationToken: id }),
|
|
211
205
|
};
|
|
@@ -219,7 +213,7 @@ const entity = object({ id, kind: choices(["object", "character", "rig"]), token
|
|
|
219
213
|
name, detailsOmitted: bool, position: vec3, yawDeg: number(), rotationDeg: vec3, scale: union(positive, positiveVec3), bounds: nullable(bounds),
|
|
220
214
|
libraryKind: id, renderer: id, color: nullable(text(32)), tint: nullable(text(32)), modelId: nullable(text(120)), assetId: id, parentId: nullable(id), attachment: nullable(object({ characterId: id, bone: nullable(id) })), pathPointCount: integer(0, 64), pathMarks: array(union(number(0, 1), object({ t: number(0, 1) }, { bank: number(-90, 90), pitch: number(-90, 90) })), 6),
|
|
221
215
|
local: object({ position: vec3, rotationDeg: vec3 }),
|
|
222
|
-
motion: object({ takeId: nullable(id), frames: integer(), ikKeyCount: integer()
|
|
216
|
+
motion: object({ takeId: nullable(id), frames: integer(), ikKeyCount: integer() }, { poseId: nullable(id), keyIds: ids(8, 0) }),
|
|
223
217
|
capabilities: object({ rigReady: bool, ik: bool, measuredFeet: bool }),
|
|
224
218
|
// `kind` above already names the entity class ("character"); a character's own
|
|
225
219
|
// kind is `characterKind`: a rigged model, or a rig-free capsule figure.
|
|
@@ -298,7 +292,9 @@ const authoredReceipt = { ...receiptBase, authored: literal(true), undo };
|
|
|
298
292
|
// A run_action receipt names the registered action and what it did.
|
|
299
293
|
const actionFields = { action: id, summary: text(240) };
|
|
300
294
|
const receiptVariants = {
|
|
301
|
-
|
|
295
|
+
// A mutation inside an agent turn answers "completed" (the turn owns its undo entry); a patch
|
|
296
|
+
// among them still reports each op, so ops ride here too.
|
|
297
|
+
completed: object({ ...receiptBase, status: literal("completed"), kind: choices(["job", "document", "transaction", "mutation"]) }, { ...actionFields, ...batchDetails, output: openObject, jobId: id, txId: id, nextHost: identity, ops: opResults }),
|
|
302
298
|
started: object({ ...receiptBase, status: literal("started"), kind: literal("job"), jobId: id, authored: literal(false), undo: literal(null) }, { ...actionFields }),
|
|
303
299
|
applied: object({ ...authoredReceipt, status: literal("applied") }, { mutated: literal(true), ops: opResults, ...batchDetails, ...actionFields }),
|
|
304
300
|
partial: object({ ...authoredReceipt, status: literal("partial"), ops: opResults }, { mutated: literal(true), ...batchDetails }),
|
|
@@ -321,65 +317,66 @@ const failureSchema = object({ ok: literal(false), commandId: id, host: identity
|
|
|
321
317
|
// These are JSON Schema data, not validators with hidden browser dependencies.
|
|
322
318
|
// x-studio-range is the sole relational schema annotation: end > start.
|
|
323
319
|
export const StudioSchemas = freezeStudioData({ catalogue: STUDIO_CATALOGUE, variants: STUDIO_VARIANTS, Vec3: vec3, FrameRange: range, PositionSpec: position, FacingSpec: facing,
|
|
324
|
-
ObjectOp: objectOp, CharacterOp: characterOp, Framing: framing,
|
|
320
|
+
ObjectOp: objectOp, CharacterOp: characterOp, Framing: framing, Identity: identity, Host: host, TargetGuard: guardSchema,
|
|
325
321
|
Entity: entity, ShotSummary: shotSummary, StudioContextV1: contextSchema, StudioTurn: turnSchema, StudioStop: stopSchema, Receipt: receiptSchema, ReceiptVariants: receiptVariants, Failure: failureSchema, GeometryFacts: geometryFacts });
|
|
326
322
|
|
|
327
|
-
/** Validate the closed JSON Schema subset used above; return a detached value
|
|
328
|
-
*
|
|
323
|
+
/** Validate the closed JSON Schema subset used above; return a detached value with declared
|
|
324
|
+
* defaults. Errors name the path and what it allows (studio-schema-errors.js). */
|
|
329
325
|
export function validateStudioSchema(schema, value, code = "INVALID_ARGUMENT", path = "$") {
|
|
330
|
-
if (schema.oneOf) {
|
|
331
|
-
const matches = [];
|
|
332
|
-
const
|
|
333
|
-
for (const branch of schema.oneOf) {
|
|
326
|
+
if (schema.oneOf || schema.anyOf) {
|
|
327
|
+
const matches = [], failures = [];
|
|
328
|
+
for (const branch of schema.oneOf ?? schema.anyOf) {
|
|
334
329
|
try { matches.push(validateStudioSchema(branch, value, code, path)); }
|
|
335
330
|
catch (error) { if (!(error instanceof StudioProtocolError)) throw error; failures.push({ branch, error }); }
|
|
336
331
|
}
|
|
337
332
|
// No variant matched: the generic "one of N" message hides which path and
|
|
338
333
|
// bound actually offended. Surface the failure from the variant the input
|
|
339
334
|
// discriminates to (matching target.kind), or else the deepest failing
|
|
340
|
-
// path, instead of the union's own shallow message.
|
|
335
|
+
// path, instead of the union's own shallow message. A union of plain values
|
|
336
|
+
// (true, false or "ifClean") that failed at its own path names every form.
|
|
341
337
|
if (matches.length === 0 && failures.length) {
|
|
342
|
-
const kind = record(value) && record(value.target) ? value.target.kind : undefined;
|
|
343
|
-
const
|
|
338
|
+
const kind = record(value) && record(value.target) ? value.target.kind : undefined, discriminated = kind !== undefined && failures.find(({ branch }) => branch.properties?.target?.properties?.kind?.const === kind);
|
|
339
|
+
const forms = !discriminated && unionRefusal(path, failures, value);
|
|
340
|
+
if (forms) fail(code, forms, path);
|
|
344
341
|
const chosen = discriminated || failures.reduce((best, next) => next.error.details.path.length > best.error.details.path.length ? next : best);
|
|
345
342
|
throw chosen.error;
|
|
346
343
|
}
|
|
347
|
-
if (matches.length !== 1) fail(code,
|
|
344
|
+
if (matches.length !== 1 && schema.oneOf) fail(code, `${path}: matches ${matches.length} variants; send the fields of exactly one.`, path);
|
|
348
345
|
return matches[0];
|
|
349
346
|
}
|
|
350
|
-
if (Object.hasOwn(schema, "const")) { if (value !== schema.const) fail(code,
|
|
351
|
-
if (schema.type === "null") { if (value !== null) fail(code,
|
|
347
|
+
if (Object.hasOwn(schema, "const")) { if (value !== schema.const) fail(code, wrongType(path, schema, value), path); return value; }
|
|
348
|
+
if (schema.type === "null") { if (value !== null) fail(code, wrongType(path, schema, value), path); return null; }
|
|
352
349
|
if (schema.type === "object") {
|
|
353
|
-
if (!record(value)) fail(code,
|
|
350
|
+
if (!record(value)) fail(code, wrongType(path, schema, value), path);
|
|
354
351
|
const open = schema.additionalProperties === true;
|
|
355
|
-
for (const key of Object.keys(value)) if (!Object.hasOwn(schema.properties, key) && !open) fail(code,
|
|
352
|
+
for (const key of Object.keys(value)) if (!Object.hasOwn(schema.properties, key) && !open) fail(code, unknownField(path, schema, key), path);
|
|
356
353
|
const result = {};
|
|
357
354
|
if (open) for (const key of Object.keys(value)) if (!Object.hasOwn(schema.properties, key)) result[key] = structuredClone(value[key]);
|
|
358
355
|
for (const [key, child] of Object.entries(schema.properties)) {
|
|
359
356
|
if (Object.hasOwn(value, key)) result[key] = validateStudioSchema(child, value[key], code, `${path}.${key}`);
|
|
360
|
-
else if (schema.required.includes(key)) fail(code,
|
|
357
|
+
else if (schema.required.includes(key)) fail(code, missingField(path, key), `${path}.${key}`);
|
|
361
358
|
else if (Object.hasOwn(child, "default")) result[key] = structuredClone(child.default);
|
|
362
359
|
}
|
|
363
360
|
if (schema["x-studio-range"] && result.endFrameExclusive <= result.startFrame) fail(code, "Frame range must be nonempty and half-open.", path);
|
|
364
361
|
return result;
|
|
365
362
|
}
|
|
366
363
|
if (schema.type === "array") {
|
|
367
|
-
if (!Array.isArray(value) || value.length < schema.minItems || value.length > schema.maxItems) fail(code,
|
|
364
|
+
if (!Array.isArray(value) || value.length < schema.minItems || value.length > schema.maxItems) fail(code, arrayRefusal(path, schema, value), path);
|
|
368
365
|
const result = value.map((item, i) => validateStudioSchema(schema.items, item, code, `${path}[${i}]`));
|
|
369
|
-
if (schema.uniqueItems && new Set(result.map(item => JSON.stringify(item))).size !== result.length) fail(code,
|
|
366
|
+
if (schema.uniqueItems && new Set(result.map(item => JSON.stringify(item))).size !== result.length) fail(code, duplicateItem(path, result), path);
|
|
370
367
|
return result;
|
|
371
368
|
}
|
|
372
369
|
if (schema.type === "string") {
|
|
373
|
-
if (typeof value !== "string" || !value.trim() || (schema.maxLength !== undefined && [...value].length > schema.maxLength) || (schema.pattern && !new RegExp(schema.pattern).test(value))) fail(code,
|
|
374
|
-
if (schema.enum && !schema.enum.includes(value)) fail(code,
|
|
370
|
+
if (typeof value !== "string" || !value.trim() || (schema.maxLength !== undefined && [...value].length > schema.maxLength) || (schema.pattern && !new RegExp(schema.pattern).test(value))) fail(code, stringRefusal(path, schema, value), path);
|
|
371
|
+
if (schema.enum && !schema.enum.includes(value)) fail(code, enumRefusal(path, schema, value), path);
|
|
375
372
|
return value;
|
|
376
373
|
}
|
|
377
|
-
if (schema.type === "boolean") { if (typeof value !== "boolean") fail(code,
|
|
374
|
+
if (schema.type === "boolean") { if (typeof value !== "boolean") fail(code, wrongType(path, schema, value), path); return value; }
|
|
378
375
|
if (schema.type === "number" || schema.type === "integer") {
|
|
379
|
-
if (typeof value !== "number" || !Number.isFinite(value) || (schema.type === "integer" && !Number.isSafeInteger(value)) || (schema.exclusiveMinimum !== undefined && value <= schema.exclusiveMinimum)) fail(code,
|
|
376
|
+
if (typeof value !== "number" || !Number.isFinite(value) || (schema.type === "integer" && !Number.isSafeInteger(value)) || (schema.exclusiveMinimum !== undefined && value <= schema.exclusiveMinimum)) fail(code, numberRefusal(path, schema, value), path);
|
|
380
377
|
if ((schema.minimum !== undefined && value < schema.minimum) || (schema.maximum !== undefined && value > schema.maximum)) {
|
|
381
378
|
const bounds = [schema.minimum, schema.maximum].filter(bound => bound !== undefined);
|
|
382
|
-
fail(code, `${path}: Expected a number within [${bounds.join(", ")}].`, path);
|
|
379
|
+
fail(code, `${path}: Expected a number within [${bounds.join(", ")}], got ${value}.`, path);
|
|
383
380
|
}
|
|
384
381
|
return value;
|
|
385
382
|
}
|
|
@@ -455,13 +452,6 @@ export function validateStudioContextFreshness(submitted, current) {
|
|
|
455
452
|
}
|
|
456
453
|
const pickIdentity = value => Object.fromEntries(Object.keys(identityFields).map(key => [key, value[key]]));
|
|
457
454
|
|
|
458
|
-
function validateGenerationTiming(value) {
|
|
459
|
-
const timed = value.beats.filter(beat => beat.seconds !== undefined).length;
|
|
460
|
-
if ((value.durationSeconds !== undefined && timed !== 0) || (value.durationSeconds === undefined && timed !== value.beats.length)) fail("INVALID_ARGUMENT", "Use total duration or a duration for every beat, not mixed timing.");
|
|
461
|
-
const total = value.durationSeconds ?? value.beats.reduce((sum, beat) => sum + beat.seconds, 0);
|
|
462
|
-
if (total < 2 || total > 60 || total / value.beats.length < 0.5) fail("INVALID_ARGUMENT", "Generation must be 2-60 seconds with at least 0.5 seconds per beat.");
|
|
463
|
-
return total;
|
|
464
|
-
}
|
|
465
455
|
/** Unknown paths are answered with the paths that DO exist for that kind, so a
|
|
466
456
|
* caller never has to guess the vocabulary from a rejection. */
|
|
467
457
|
function validatePatchPaths(args) {
|
|
@@ -501,10 +491,6 @@ export function validateStudioCommand(command) {
|
|
|
501
491
|
// final batch is still checked for overlap.
|
|
502
492
|
if (args.collisionPolicy === "avoid" && !args.ops.some(op => ["create", "update"].includes(op.op) && op.position?.relativeTo)) fail("INVALID_ARGUMENT", "collisionPolicy avoid needs at least one op with position.relativeTo (it nudges outward from that reference); other ops are placed as given.");
|
|
503
493
|
}
|
|
504
|
-
if (command.name === "generate_motion" && args.source.kind === "generate") {
|
|
505
|
-
args.source.beats = args.source.beats.map(beat => ({ ...beat, text: beat.text.trim().replace(/\s+/g, " ") }));
|
|
506
|
-
validateGenerationTiming(args.source);
|
|
507
|
-
}
|
|
508
494
|
if (command.name === "frame_shot" && args.framing.exact && JSON.stringify(args.framing.exact.position) === JSON.stringify(args.framing.exact.lookAt)) fail("INVALID_ARGUMENT", "Camera position and aim cannot coincide.");
|
|
509
495
|
if (command.name === "patch_elements") {
|
|
510
496
|
for (const op of args.ops) if (!Object.keys(op.set).length) fail("INVALID_ARGUMENT", "Patch has no fields.");
|
|
@@ -518,22 +504,6 @@ export function validateStudioCommand(command) {
|
|
|
518
504
|
}
|
|
519
505
|
return { name: command.name, args };
|
|
520
506
|
}
|
|
521
|
-
export function compileStudioBeats(value) {
|
|
522
|
-
const source = validateStudioSchema(generateSource, value);
|
|
523
|
-
const totalSeconds = validateGenerationTiming(source);
|
|
524
|
-
const frameCount = Math.round(totalSeconds * 24);
|
|
525
|
-
const blocks = []; let cumulative = 0, startFrame = 0;
|
|
526
|
-
for (const [sourceBeat, beat] of source.beats.entries()) {
|
|
527
|
-
cumulative += beat.seconds ?? totalSeconds / source.beats.length;
|
|
528
|
-
const end = sourceBeat === source.beats.length - 1 ? frameCount : Math.round(cumulative * 24);
|
|
529
|
-
const pieces = Math.ceil((end - startFrame) / 120), start = startFrame;
|
|
530
|
-
for (let i = 0; i < pieces; i++) {
|
|
531
|
-
const endFrameExclusive = start + Math.round((end - start) * (i + 1) / pieces);
|
|
532
|
-
blocks.push({ sourceBeat, text: beat.text.trim().replace(/\s+/g, " "), startFrame, endFrameExclusive }); startFrame = endFrameExclusive;
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
return freezeStudioData({ fps: 24, frameCount, durationSeconds: frameCount / 24, blocks });
|
|
536
|
-
}
|
|
537
507
|
export function validateReceipt(value) {
|
|
538
508
|
if (value?.ok === false) {
|
|
539
509
|
const r = validateStudioSchema(failureSchema, value, "INVALID_RECEIPT");
|
|
@@ -3,16 +3,18 @@
|
|
|
3
3
|
// history, the action registry); this module owns no React or renderer state.
|
|
4
4
|
import { createCommandBus } from "./command-bus.js";
|
|
5
5
|
import { readElementDocument, elementReadback, elementPatchArgs, elementTarget, elementPatchReceipt } from "./commands/elements.js";
|
|
6
|
-
import { physicsKeyStamp } from "./
|
|
6
|
+
import { physicsKeyStamp } from "./motion/physics-review.js";
|
|
7
7
|
import { shotAtFrame } from "./cuts.js";
|
|
8
8
|
import { isProxyFigure } from "./scenes.js";
|
|
9
|
-
import {
|
|
9
|
+
import { supportHeightForObject } from "./scene-objects.js";
|
|
10
|
+
import { studioAssetList } from "./studio-asset-list.js";
|
|
10
11
|
import { buildStudioContext, physicsFingerprintInput, studioEntityCursor, validateStudioCursor } from "./studio-agent-context.js";
|
|
11
|
-
import { createStudioCommandJournal,
|
|
12
|
-
import { verifyInstalledTake } from "./studio-agent-motion.js";
|
|
13
|
-
import { shotGeometryFacts } from "./studio-geometry-facts.js";
|
|
12
|
+
import { createStudioCommandJournal, framingChecks, localTransformReadback, placementChecks, sampledFramingResult, studioObjectCatalogue } from "./studio-agent-commands.js";
|
|
13
|
+
import { verifyInstalledTake } from "./studio-agent-motion.js"; import { objectRowBounds } from "./studio-entity-bounds.js"; import { exportFramingInto } from "./studio-export-framing.js";
|
|
14
|
+
import { shotGeometryFacts } from "./studio-geometry-facts.js"; import { KIT_CATALOGUE } from "./kits/index.js";
|
|
14
15
|
import { STUDIO_TOOL_FAMILIES, StudioProtocolError, validateReceipt, validateStudioCommand, validateStudioIdentity } from "./studio-agent-protocol.js";
|
|
15
|
-
import {
|
|
16
|
+
import { studioVisual, verifyLighting } from "./studio-motion-visual.js";
|
|
17
|
+
import { withEffectState } from "./effects/state.js"; import { hdrEnvironmentCatalogue } from "./stage-hdr-library.js";
|
|
16
18
|
|
|
17
19
|
// App-owned adapter: the shared command modules remain the only
|
|
18
20
|
// planners and validators. Ports below publish through the native editor stores.
|
|
@@ -34,8 +36,7 @@ export function createStudioAppBinding(ports) {
|
|
|
34
36
|
const motionContentKey = value => {
|
|
35
37
|
if (!value || typeof value !== "object") return null;
|
|
36
38
|
if (typeof value.studioTakeId === "string" && value.studioTakeId) return value.studioTakeId;
|
|
37
|
-
|
|
38
|
-
return `${value.frames ?? 0}:${value.fps ?? 0}:${value.rotMats?.length ?? 0}:${value.rootPos?.length ?? 0}:${value.posedJoints?.length ?? 0}`;
|
|
39
|
+
return `${value.frames ?? 0}:${value.fps ?? 0}`;
|
|
39
40
|
};
|
|
40
41
|
const calibrationContentKey = value => value && typeof value === "object" ? JSON.stringify(value) : null;
|
|
41
42
|
const tokens = new Map(), receipts = new Map(), images = new Map();
|
|
@@ -121,30 +122,18 @@ export function createStudioAppBinding(ports) {
|
|
|
121
122
|
return { id: c.id, kind: "character", token: tokens.get(c.id).token, name: c.subject || c.id,
|
|
122
123
|
position: { x: c.x, y: c.y ?? 0, z: c.z }, yawDeg: c.rot ?? 0, scale: c.scale ?? 1, tint: c.tint ?? null, modelId: c.model ?? null,
|
|
123
124
|
motion: { takeId: t?.motion?.studioTakeId ?? null, frames: t?.motion?.frames ?? 0,
|
|
124
|
-
ikKeyCount: t?.ikState?.keys.size ?? 0
|
|
125
|
+
ikKeyCount: t?.ikState?.keys.size ?? 0 },
|
|
125
126
|
capabilities: { rigReady: Boolean(t?.rig), ik: Boolean(t?.rig?.userData?.poseBind), measuredFeet: false },
|
|
126
127
|
characterKind: isProxyFigure(c) ? "proxy" : "rig", ...(isProxyFigure(c) ? { posture: c.posture ?? "stand" } : {}) };
|
|
127
128
|
}), ...s.objects.map(o => { const local = localTransformReadback(o, s.objects); return { id: o.id, kind: "object", token: tokens.get(o.id).token, name: o.name || o.id,
|
|
128
129
|
position: { x: o.x, y: o.y ?? 0, z: o.z }, yawDeg: o.rot ?? 0,
|
|
129
130
|
rotationDeg: { x: o.rotX ?? 0, y: o.rot ?? 0, z: o.rotZ ?? 0 }, scale: { x: o.scaleX, y: o.scaleY, z: o.scaleZ },
|
|
130
|
-
renderer: o.renderer, color: o.color ?? null, ...(o.assetId ? { assetId: o.assetId } : {}),
|
|
131
|
+
renderer: o.renderer, color: o.color ?? null, bounds: objectRowBounds(o, s, ports), ...(o.assetId ? { assetId: o.assetId } : {}),
|
|
131
132
|
parentId: o.parent ?? null, attachment: o.attach ?? null, pathPointCount: o.path?.points.length ?? 0,
|
|
132
133
|
// the dots on the route, with the lean (degrees) a mark carries
|
|
133
134
|
...(o.path?.marks?.length ? { pathMarks: o.path.marks.map(mark => typeof mark === "number" ? mark : { t: mark.t, ...(mark.bank !== undefined ? { bank: mark.bank } : {}), ...(mark.pitch !== undefined ? { pitch: mark.pitch } : {}) }) } : {}), ...(local ? { local } : {}) }; })];
|
|
134
135
|
}
|
|
135
136
|
const frameRange = row => ({ startFrame: row.startFrame, endFrameExclusive: row.endFrame + 1 });
|
|
136
|
-
function assetList(s) {
|
|
137
|
-
const catalogue = studioObjectCatalogue().objects.map(({ kind }) => {
|
|
138
|
-
const entry = OBJECT_LIBRARY.find(row => row.kind === kind);
|
|
139
|
-
return { kind, name: entry?.label ?? kind, type: entry?.group === "Primitives" ? "primitive" : "set-piece" };
|
|
140
|
-
});
|
|
141
|
-
const imported = new Map();
|
|
142
|
-
for (const o of s.objects) {
|
|
143
|
-
const assetId = o.renderer === CUTOUT_KIND ? o.sourceAssetId || o.assetId : o.renderer === MESH_KIND ? o.assetId : null;
|
|
144
|
-
if (assetId && !imported.has(assetId)) imported.set(assetId, { id: assetId, name: o.name || assetId, type: o.renderer === CUTOUT_KIND ? "image" : "mesh" });
|
|
145
|
-
}
|
|
146
|
-
return [...catalogue, ...imported.values()];
|
|
147
|
-
}
|
|
148
137
|
function context() {
|
|
149
138
|
const s = refresh(), entities = entityProjection(s), registry = ports.actions?.();
|
|
150
139
|
const shot = s.shots.find(row => row.id === s.selectedShotId) ?? shotAtFrame(s.shots, s.view.frame);
|
|
@@ -158,7 +147,7 @@ export function createStudioAppBinding(ports) {
|
|
|
158
147
|
// buildStudioContext selects the detailed rows and writes the real page.
|
|
159
148
|
entities, entityPage: { returned: 0, total: 0, truncated: false, nextCursor: null },
|
|
160
149
|
shots: s.shots.map(row => ({ id: row.id, name: row.name, range: range(row), keyCount: row.cameraKeys.length })), shotsTruncated: false,
|
|
161
|
-
assets:
|
|
150
|
+
assets: studioAssetList(s.objects), recentReceipts: [...receipts.values()].filter(r => r.ok).reverse().slice(0, 3).map(r => ({ id: r.receiptId, summary: r.status, canUndoDirect: ports.canUndo(r) })),
|
|
162
151
|
jobs: [], capabilities: { profile: "studio-slice-1", tools: STUDIO_TOOL_FAMILIES,
|
|
163
152
|
rigReady: Boolean(s.targets.get(s.activeCharacterId)?.rig), cameraReady: Boolean(s.camera), bridgeReady: s.bridgeReady,
|
|
164
153
|
...(s.previsMode ? { storyboard: s.previsMode === "storyboard" } : {}) },
|
|
@@ -196,13 +185,13 @@ export function createStudioAppBinding(ports) {
|
|
|
196
185
|
if (id === s.host.sceneId) return { selection: s.selection, activeCharacterId: s.activeCharacterId, shotId: s.selectedShotId, view: s.view,
|
|
197
186
|
...(patched.length ? { patched } : {}) };
|
|
198
187
|
const object = s.objects.find(row => row.id === id), entity = object ?? s.characters.find(row => row.id === id);
|
|
199
|
-
// Beyond the transform an object's patched paths already carry: the world box it occupies
|
|
200
|
-
// group, who hangs off it. `compact` (a receipt near its size limit) drops back to the paths alone.
|
|
201
|
-
// A character's box means cloning and posing its rig, too dear for every receipt
|
|
188
|
+
// Beyond the transform an object's patched paths already carry: the world box it occupies (an empty: its
|
|
189
|
+
// parts', as inspect's rows) and, for a group, who hangs off it. `compact` (a receipt near its size limit) drops back to the paths alone.
|
|
190
|
+
// A character's box means cloning and posing its rig, too dear for every receipt or inspect row: verify has it.
|
|
202
191
|
const extras = () => {
|
|
203
192
|
if (!object || compact) return {};
|
|
204
193
|
const childIds = s.objects.filter(row => row.parent === id).map(row => row.id);
|
|
205
|
-
return { bounds:
|
|
194
|
+
return { bounds: objectRowBounds(object, s, ports),
|
|
206
195
|
...(childIds.length ? { childIds: childIds.slice(0, 12), ...(childIds.length > 12 ? { childCount: childIds.length } : {}) } : {}) };
|
|
207
196
|
};
|
|
208
197
|
if (patched.length) return { patched, ...extras() };
|
|
@@ -286,11 +275,12 @@ export function createStudioAppBinding(ports) {
|
|
|
286
275
|
// A framing check also carries the cut's geometry, read from the scene: side of the action line
|
|
287
276
|
// and sight lines per shot, the receipt's or targets' shots first (8 at most).
|
|
288
277
|
const result = { receiptId: receipt?.receiptId ?? null, revision: s.revision, evidenceRevision, stale: evidenceRevision !== s.revision, checks: measured ? { coverage: "current-scene-targets" } : receipt.checks,
|
|
289
|
-
verification: receipt?.verification ?? null, semanticStatus: "unavailable", visualRefs: [], unsupportedChecks: [], unsupportedReasons: {}, ...(args.checks.includes("framing") ? { geometry: shotGeometryFacts(scene, { first: receipt ? receipt.affectedIds : args.targets, ports: { bounds: ports.bounds } }) } : {}) };
|
|
290
|
-
|
|
291
|
-
|
|
278
|
+
verification: receipt?.verification ?? null, semanticStatus: "unavailable", visualRefs: [], unsupportedChecks: [], unsupportedReasons: {}, ...(args.checks.includes("framing") ? { geometry: shotGeometryFacts(scene, { first: receipt ? receipt.affectedIds : args.targets, ports: { bounds: ports.bounds } }) } : {}) }, reasons = result.unsupportedReasons, verified = [], skipped = [], pending = [];
|
|
279
|
+
// Lighting is measured from the rendered shot frame (studio-lighting-check.js), never from a receipt.
|
|
280
|
+
if (args.checks.includes("lighting")) verifyLighting(result, args.targets ?? receipt.affectedIds, scene, ports);
|
|
281
|
+
for (const check of args.checks.filter(check => !["motion", "lighting"].includes(check) && measured)) {
|
|
292
282
|
if (!measured.length) { reasons[check] = "No target is an object or character in the current scene."; continue; }
|
|
293
|
-
try { result.checks[check] = check === "placement" ? placementChecks(measured, scene, { bounds: ports.bounds }) : framingChecks(measured, scene, { bounds: ports.bounds }); }
|
|
283
|
+
try { result.checks[check] = check === "placement" ? placementChecks(measured, scene, { bounds: ports.bounds, takes: s.targets }) : framingChecks(measured, scene, { bounds: ports.bounds }); }
|
|
294
284
|
catch (error) { if (!(error instanceof StudioProtocolError)) throw error; reasons[check] = error.message; }
|
|
295
285
|
}
|
|
296
286
|
if (args.checks.includes("framing") && args.range !== undefined && !args.rangeWasDefaulted) {
|
|
@@ -309,17 +299,7 @@ export function createStudioAppBinding(ports) {
|
|
|
309
299
|
.then(verification => { verified[index] = verification; }, error => { if (!(error instanceof StudioProtocolError)) throw error; skipped.push(`${name}: ${error.message}`); }));
|
|
310
300
|
});
|
|
311
301
|
}
|
|
312
|
-
|
|
313
|
-
// with names and the shot camera's wedge.
|
|
314
|
-
if (args.visual !== "none") {
|
|
315
|
-
if (args.visual === "contact_sheet") {
|
|
316
|
-
// One image of frames across the range, each rendered through the export
|
|
317
|
-
// path, which puts the playhead pose, shot camera and props back.
|
|
318
|
-
const frames = sampleContactSheetFrames(args.range, s.frameCount), sheet = buildContactSheet(frames, ports.renderFrameBuffer), imageId = crypto.randomUUID();
|
|
319
|
-
images.set(imageId, { dataUrl: ports.encodePng(sheet.data, sheet), width: sheet.width, height: sheet.height, frames, layout: CONTACT_SHEET_LAYOUT, revision: s.revision, receiptId: result.receiptId });
|
|
320
|
-
result.visualRefs.push({ imageId, frames, layout: CONTACT_SHEET_LAYOUT });
|
|
321
|
-
} else { const capture = args.visual === "plan" ? (ports.capturePlan ?? (() => fail("CAPABILITY_MISSING", "This editor cannot render the top view.")))() : ports.capture(); const imageId = crypto.randomUUID(); images.set(imageId, { ...capture, revision: s.revision, receiptId: result.receiptId }); result.visualRefs.push({ imageId, ...(args.visual === "plan" ? { kind: "plan" } : {}) }); }
|
|
322
|
-
}
|
|
302
|
+
if (args.checks.includes("motion")) exportFramingInto(result, { args, s, receipt, scene, ports }); if (args.visual !== "none") studioVisual(args, { s, receipt, result, images, ports });
|
|
323
303
|
const finish = () => {
|
|
324
304
|
const computed = verified.filter(Boolean);
|
|
325
305
|
if (computed.length) result.verification = computed.length === 1 ? computed[0] : computed;
|
|
@@ -347,7 +327,7 @@ export function createStudioAppBinding(ports) {
|
|
|
347
327
|
// the model pays for): the revision is what the agent's next command is
|
|
348
328
|
// admitted at, so a scope without it leaves that admission stale.
|
|
349
329
|
const head = { revision: c.revision, host: (({ workspaceId, documentEpoch, sceneId, sceneEpoch }) => ({ workspaceId, documentEpoch, sceneId, sceneEpoch }))(c.host) };
|
|
350
|
-
if (command.args.scope === "catalogue") return { ...head, ...studioObjectCatalogue() };
|
|
330
|
+
if (command.args.scope === "catalogue") return { ...head, ...studioObjectCatalogue(), kits: KIT_CATALOGUE, environments: hdrEnvironmentCatalogue() };
|
|
351
331
|
if (command.args.scope === "document") return { ...head, scope: "document",
|
|
352
332
|
...readElementDocument(refresh().document, command.args, c.host.sceneId) };
|
|
353
333
|
// Discovery for run_action: every registered action with its label, kind,
|
|
@@ -370,7 +350,8 @@ export function createStudioAppBinding(ports) {
|
|
|
370
350
|
// Stable id order, so an offset cursor survives unrelated edits.
|
|
371
351
|
const filtered = entityProjection(s).filter(wanted).sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
|
|
372
352
|
const offset = args.cursor ? validateStudioCursor(args.cursor, c) : 0, limit = command.args.limit;
|
|
373
|
-
|
|
353
|
+
// An effect row also carries its particles at the playhead (src/effects/state.js).
|
|
354
|
+
return { ...head, entities: filtered.slice(offset, offset + limit).map(row => withEffectState(row, s.view.frame, s.objects)), total: filtered.length,
|
|
374
355
|
nextCursor: offset + limit < filtered.length ? studioEntityCursor(c, offset + limit) : null };
|
|
375
356
|
},
|
|
376
357
|
operate_studio: request => execute({ ...request, name: "operate_studio" }),
|
|
@@ -378,7 +359,6 @@ export function createStudioAppBinding(ports) {
|
|
|
378
359
|
arrange_characters: request => execute({ ...request, name: "arrange_characters" }),
|
|
379
360
|
patch_elements: request => execute({ ...request, name: "patch_elements" }),
|
|
380
361
|
frame_shot: request => execute({ ...request, name: "frame_shot" }),
|
|
381
|
-
generate_motion: request => execute({ ...request, name: "generate_motion" }),
|
|
382
362
|
verify_result: request => execute({ ...request, name: "verify_result" }),
|
|
383
363
|
undo_edit: request => runAction(request, { action: "edit.undo", args: request.args }),
|
|
384
364
|
run_action: request => execute({ ...request, name: "run_action" }),
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// The asset summary the agent's turn context lists: every catalogue kind the
|
|
2
|
+
// Studio can create, then each imported picture or model once.
|
|
3
|
+
import { CUTOUT_KIND, MESH_KIND, OBJECT_MENU_ENTRIES } from "./scene-objects.js";
|
|
4
|
+
import { studioObjectCatalogue } from "./studio-agent-commands.js";
|
|
5
|
+
|
|
6
|
+
export function studioAssetList(objects) {
|
|
7
|
+
const catalogue = studioObjectCatalogue().objects.map(({ kind }) => {
|
|
8
|
+
const entry = OBJECT_MENU_ENTRIES.find(row => row.kind === kind);
|
|
9
|
+
return { kind, name: entry?.label ?? kind, type: entry?.group === "Primitives" ? "primitive" : "set-piece" };
|
|
10
|
+
});
|
|
11
|
+
const imported = new Map();
|
|
12
|
+
for (const o of objects) {
|
|
13
|
+
const assetId = o.renderer === CUTOUT_KIND ? o.sourceAssetId || o.assetId : o.renderer === MESH_KIND ? o.assetId : null;
|
|
14
|
+
if (assetId && !imported.has(assetId)) imported.set(assetId, { id: assetId, name: o.name || assetId, type: o.renderer === CUTOUT_KIND ? "image" : "mesh" });
|
|
15
|
+
}
|
|
16
|
+
return [...catalogue, ...imported.values()];
|
|
17
|
+
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
const CONTACT_SHEET_COLUMNS = 3;
|
|
6
6
|
const CONTACT_SHEET_ROWS = 2;
|
|
7
7
|
export const CONTACT_SHEET_LAYOUT = "3x2 row-major";
|
|
8
|
-
const CONTACT_SHEET_MAX_SIDE = 1600;
|
|
8
|
+
const CONTACT_SHEET_MAX_SIDE = 1600, VIEW_SHEET_MAX_HEIGHT = 900;
|
|
9
9
|
|
|
10
10
|
/** Up to 6 frames spread evenly over a half-open range, first and last
|
|
11
11
|
* included; "whole_clip" (or no range) is timeline frames 0..frameCount-1. */
|
|
@@ -53,21 +53,53 @@ export function tileRgba(tiles, tileWidth, tileHeight, { columns = CONTACT_SHEET
|
|
|
53
53
|
return { data, width, height };
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
/** The grid of a sheet: 3x2 row-major for up to 6 frames, 4x2 for 7-8. Views of its own
|
|
57
|
+
* (viewCount >= 1) take a column per frame and a row per view when there are several views
|
|
58
|
+
* or at most 3 frames. viewCount 0 is the shot camera. */
|
|
59
|
+
export function sheetGrid(frameCount, viewCount = 0) {
|
|
60
|
+
if (viewCount > 1 || (viewCount === 1 && frameCount <= 3)) return { columns: frameCount, rows: viewCount, layout: `${frameCount}x${viewCount} row-major, a row per view` };
|
|
61
|
+
return frameCount <= CONTACT_SHEET_COLUMNS * CONTACT_SHEET_ROWS ? { columns: CONTACT_SHEET_COLUMNS, rows: CONTACT_SHEET_ROWS, layout: CONTACT_SHEET_LAYOUT } : { columns: 4, rows: 2, layout: "4x2 row-major" };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Square tile side for a grid of own-camera views: the sheet stays within
|
|
65
|
+
* maxSide wide and VIEW_SHEET_MAX_HEIGHT high (1600x900 at most). */
|
|
66
|
+
export const viewTileSide = ({ columns, rows }, maxSide = CONTACT_SHEET_MAX_SIDE) => Math.max(1, Math.floor(Math.min(maxSide / columns, VIEW_SHEET_MAX_HEIGHT / rows)));
|
|
67
|
+
|
|
68
|
+
/** Paint dots on a bottom-up RGBA buffer: { x, y (from the bottom row), r, color [r,g,b],
|
|
69
|
+
* alpha 0..1, ring } with ring drawing a 1 px white circle around the dot at r + 2. */
|
|
70
|
+
export function paintDots(data, width, height, dots) {
|
|
71
|
+
const put = (x, y, [r, g, b], a) => {
|
|
72
|
+
if (x < 0 || y < 0 || x >= width || y >= height) return;
|
|
73
|
+
const i = (y * width + x) * 4;
|
|
74
|
+
data[i] = Math.round(data[i] + (r - data[i]) * a); data[i + 1] = Math.round(data[i + 1] + (g - data[i + 1]) * a); data[i + 2] = Math.round(data[i + 2] + (b - data[i + 2]) * a);
|
|
75
|
+
};
|
|
76
|
+
for (const { x, y, r, color, alpha = 1, ring = false } of dots) {
|
|
77
|
+
const cx = Math.round(x), cy = Math.round(y), reach = Math.ceil(r) + (ring ? 3 : 0);
|
|
78
|
+
for (let dy = -reach; dy <= reach; dy++) for (let dx = -reach; dx <= reach; dx++) {
|
|
79
|
+
const d = Math.hypot(dx, dy);
|
|
80
|
+
if (d <= r) put(cx + dx, cy + dy, color, alpha);
|
|
81
|
+
else if (ring && Math.abs(d - r - 2) <= 0.5) put(cx + dx, cy + dy, [255, 255, 255], 1);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return data;
|
|
85
|
+
}
|
|
86
|
+
|
|
56
87
|
/** Render each frame (render(frame) -> { data, width, height }, rows bottom-up),
|
|
57
88
|
* shrink it so the whole sheet's longest side is at most maxSide, and tile
|
|
58
|
-
* the frames
|
|
59
|
-
|
|
89
|
+
* the frames row-major (3x2 unless columns and rows say) into one bottom-up buffer.
|
|
90
|
+
* A frame may be any cell the render understands (a frame and a view). */
|
|
91
|
+
export function buildContactSheet(frames, render, { maxSide = CONTACT_SHEET_MAX_SIDE, columns = CONTACT_SHEET_COLUMNS, rows = CONTACT_SHEET_ROWS } = {}) {
|
|
60
92
|
const tiles = [];
|
|
61
93
|
let tileWidth, tileHeight;
|
|
62
94
|
for (const frame of frames) {
|
|
63
95
|
const shot = render(frame);
|
|
64
96
|
if (tileWidth === undefined) {
|
|
65
|
-
const scale = Math.min(1, maxSide / Math.max(shot.width *
|
|
97
|
+
const scale = Math.min(1, maxSide / Math.max(shot.width * columns, shot.height * rows));
|
|
66
98
|
tileWidth = Math.max(1, Math.floor(shot.width * scale));
|
|
67
99
|
tileHeight = Math.max(1, Math.floor(shot.height * scale));
|
|
68
100
|
}
|
|
69
101
|
// The renderer reuses one read-back buffer: shrink this frame before the next render.
|
|
70
102
|
tiles.push(boxDownscaleRgba(shot.data, shot.width, shot.height, tileWidth, tileHeight));
|
|
71
103
|
}
|
|
72
|
-
return tileRgba(tiles, tileWidth, tileHeight, { bottomUp: true });
|
|
104
|
+
return tileRgba(tiles, tileWidth, tileHeight, { columns, rows, bottomUp: true });
|
|
73
105
|
}
|
package/src/studio-elements.js
CHANGED
|
@@ -31,15 +31,13 @@ const entries = [
|
|
|
31
31
|
{ path: "character.identityImage", type: "image", persisted: true, undoDomain: "cast", normalizer: "createCharacterEntry", note: "data:image only" },
|
|
32
32
|
{ path: "character.pose", type: "id", persisted: true, undoDomain: "cast", normalizer: "createCharacterEntry" },
|
|
33
33
|
{ path: "character.waypoints", type: "array", persisted: true, undoDomain: "cast", normalizer: "createCharacterEntry", actions: ["character.addWaypoint", "character.moveWaypoint", "character.removeWaypoint", "character.clearWaypoints"], note: "root path pins, addressed by frame" },
|
|
34
|
-
|
|
35
|
-
{ path: "character.motionRef.url", type: "string", persisted: true, undoDomain: "cast", normalizer: "createCharacterEntry" },
|
|
36
|
-
{ path: "character.motionRef.motionId", type: "id", persisted: true, undoDomain: "cast", normalizer: "createCharacterEntry" },
|
|
34
|
+
|
|
37
35
|
{ path: "character.sessionMotion", type: "array", persisted: false, undoDomain: "cast", normalizer: "createCharacterEntry", note: "dropped by createCharacterEntry" },
|
|
38
36
|
{ path: "character.ikKeys", type: "array", persisted: false, undoDomain: "cast", normalizer: "createCharacterEntry", actions: ["character.setIkKey", "character.removeIkKey", "character.clearIkKeys"], note: "dropped by createCharacterEntry; keys travel as JSON quaternions/positions" },
|
|
39
|
-
{ path: "object.renderer", type: "enum", persisted: true, undoDomain: "objects", normalizer: "normalizeSceneObject", enum: ["cube", "sphere", "capsule", "cylinder", "cone", "plane", "chair", "car", "small-plane", "empty"] },
|
|
37
|
+
{ path: "object.renderer", type: "enum", persisted: true, undoDomain: "objects", normalizer: "normalizeSceneObject", enum: ["cube", "sphere", "capsule", "cylinder", "cone", "plane", "chair", "car", "small-plane", "empty", "light", "effect"], note: "light is made from catalogue kind point-light or spot-light; effect (smoke, fire, rain, trail...) from kind effect" },
|
|
40
38
|
{ path: "object.position", type: "vec3", persisted: true, undoDomain: "objects", normalizer: "normalizeSceneObject", min: { x: -240, y: 0, z: -240 }, max: { x: 240, y: 240, z: 240 }, note: "y is the object's base: y=0 rests on the floor; height/supportY rise from it" },
|
|
41
|
-
{ path: "object.rotation", type: "vec3", persisted: true, undoDomain: "objects", normalizer: "normalizeSceneObject", min: { x: -180, y: -180, z: -180 }, max: { x: 180, y: 180, z: 180 }, note: "rot/rotX/rotZ degrees, wrapped at the upper bound" },
|
|
42
|
-
{ path: "object.scale", type: "vec3", persisted: true, undoDomain: "objects", normalizer: "normalizeSceneObject", min: { x: 0.
|
|
39
|
+
{ path: "object.rotation", type: "vec3", persisted: true, undoDomain: "objects", normalizer: "normalizeSceneObject", min: { x: -180, y: -180, z: -180 }, max: { x: 180, y: 180, z: 180 }, angle: true, note: "rot/rotX/rotZ degrees, wrapped at the upper bound" },
|
|
40
|
+
{ path: "object.scale", type: "vec3", persisted: true, undoDomain: "objects", normalizer: "normalizeSceneObject", min: { x: 0.01, y: 0.01, z: 0.01 }, max: { x: 100, y: 100, z: 100 } },
|
|
43
41
|
{ path: "object.name", type: "string", persisted: true, undoDomain: "objects", normalizer: "normalizeSceneObject" },
|
|
44
42
|
{ path: "object.color", type: "color", persisted: true, undoDomain: "objects", normalizer: "normalizeSceneObject" },
|
|
45
43
|
{ path: "object.opacity", type: "number", persisted: true, undoDomain: "objects", normalizer: "normalizeSceneObject", min: 0.05, max: 1, note: "1 is solid; lower is see-through, e.g. 0.3 for clear glass" },
|
|
@@ -48,6 +46,9 @@ const entries = [
|
|
|
48
46
|
{ path: "object.path", type: "array", persisted: true, undoDomain: "objects", normalizer: "normalizeSceneObject" },
|
|
49
47
|
{ path: "object.remove", type: "boolean", persisted: true, undoDomain: "objects", normalizer: null, note: "lifecycle operation, not a document field" },
|
|
50
48
|
{ path: "object.cutout", type: "image", persisted: true, undoDomain: "objects", normalizer: "normalizeSceneObject", actions: ["asset.import"], note: "assetId-backed cutout record; imported pictures and models are placed by asset.import" },
|
|
49
|
+
{ path: "object.light", type: "object", persisted: true, undoDomain: "objects", normalizer: "normalizeSceneObject", note: "renderer light only. { type point|spot, color, intensity 0..1000 candela (lighter 0.45, candle 0.3, desk lamp 5, street lamp 40), range m (0 none), angle 1..89 deg spot half-angle, softness 0..1, shadow (first 2 lights only; inside a solid record it is blocked: receipt LIGHT_BLOCKED), flicker 0..1 (lighter 0.6), aim {x,y,z} world point or null = local -Y }, e.g. { intensity: 40, aim: { x: 0, y: 0, z: 1 } }; a take level track scales it" },
|
|
50
|
+
{ path: "object.glow", type: "object", persisted: true, undoDomain: "objects", normalizer: "normalizeSceneObject", note: "any record: self-lit colour plus additive halo, same under any stage light; casts no shadow, so a light inside a glowing bulb shines out. { color, intensity 0..20, halo 0..4 x the record's radius }, e.g. ember { color: \"#ff5a1f\", intensity: 6, halo: 2 }; null removes; a take level track scales it" },
|
|
51
|
+
{ path: "object.effect", type: "object", persisted: true, undoDomain: "objects", normalizer: "normalizeSceneObject", note: "renderer effect only (object.add kind \"effect\"). { preset: smoke|exhale|steam|fire|sparks|dust|rain|snow|trail, rate particles/s, life s, size m start diameter, grow end/start size, speed m/s along the record's local +Y (rotX 90 aims +Z), spread 0..1 cone, gravity m/s2 (+ falls, - rises), wind {x,y,z} m/s, color, opacity 0..1, additive (emissive: fire, sparks), seed int, area {x,y,z} m birth box (rain/snow/dust fall through it from its top), width m trail ribbon across local Y }. A new preset loads its defaults and fields in the same set override them, e.g. { preset: \"fire\", rate: 40 }; the record stores the merged numbers. Particles are born where the record is at each frame (route, parent, take carry), only while it and its parents are shown, at rate x the take level. To emit from the body (a breath at the mouth), a motion spec entry objects.<id> = { follow: \"landmark:mouth\", aim: \"face\" } moves it there every frame. Measure with inspect_studio { scope: \"entities\", ids }: effect { frame (the playhead), alive, emitter [x,y,z], bounds { min, max } m }" },
|
|
51
52
|
{ path: "stage.keyLight.x", type: "number", persisted: true, undoDomain: "stage", normalizer: "createSceneStage", min: -30, max: 30 },
|
|
52
53
|
{ path: "stage.keyLight.y", type: "number", persisted: true, undoDomain: "stage", normalizer: "createSceneStage", min: 0.5, max: 30 },
|
|
53
54
|
{ path: "stage.keyLight.z", type: "number", persisted: true, undoDomain: "stage", normalizer: "createSceneStage", min: -30, max: 30 },
|
|
@@ -60,6 +61,25 @@ const entries = [
|
|
|
60
61
|
{ path: "stage.camera", documentPath: "shotAspect", type: "enum", persisted: true, undoDomain: "stage", normalizer: "createSceneStage", enum: ["16:9", "2.39:1", "9:16", "1:1", "4:3", "12:7", "fal 480P"], note: "output aspect ratio" },
|
|
61
62
|
{ path: "stage.cameraPresetId", type: "id", nullable: true, persisted: true, undoDomain: "stage", normalizer: "createSceneStage" },
|
|
62
63
|
{ path: "stage.sensorId", type: "enum", persisted: true, undoDomain: "stage", normalizer: "createSceneStage", enum: ["super16", "super35", "fullFrame", "65mm"] },
|
|
64
|
+
// The stage look (src/stage-lighting.js). stage.applyLighting writes all of it from a recipe; each field stays tweakable.
|
|
65
|
+
{ path: "stage.keyLight.color", type: "color", nullable: true, persisted: true, undoDomain: "stage", normalizer: "createSceneStage", note: "key light colour #rrggbb, overrides warmth, e.g. \"#9db6e6\" cool moonlight or \"#ffad5c\" low sun; null = white tinted by warmth" },
|
|
66
|
+
{ path: "stage.lighting.preset", type: "enum", persisted: true, readOnly: true, undoDomain: "stage", normalizer: "createSceneStage", enum: ["studio", "day", "overcast", "golden", "dusk", "night", "interior", "stage"], note: "label of the last recipe stage.applyLighting applied; studio is the neutral grey default" },
|
|
67
|
+
{ path: "stage.lighting.sky", type: "color", nullable: true, persisted: true, undoDomain: "stage", normalizer: "createSceneStage", note: "background and fog colour at the horizon, e.g. \"#0b1426\" night; null = the UI theme's studio grey" },
|
|
68
|
+
{ path: "stage.lighting.skyTop", type: "color", nullable: true, persisted: true, undoDomain: "stage", normalizer: "createSceneStage", note: "sky colour overhead: shot camera and exports grade from sky at the horizon to this, e.g. \"#020408\"; null = flat sky" },
|
|
69
|
+
{ path: "stage.lighting.ambient", type: "number", persisted: true, undoDomain: "stage", normalizer: "createSceneStage", min: 0, max: 3, note: "multiplier on the hemisphere and ambient light; 1 = studio, 0.6 = night, 0 = black stage" },
|
|
70
|
+
{ path: "stage.lighting.hemiSky", type: "color", persisted: true, undoDomain: "stage", normalizer: "createSceneStage", note: "hemisphere light from above, e.g. \"#34507f\" cool night sky; studio \"#ffffff\"" },
|
|
71
|
+
{ path: "stage.lighting.hemiGround", type: "color", persisted: true, undoDomain: "stage", normalizer: "createSceneStage", note: "hemisphere bounce from below, e.g. \"#6e5136\" warm ground; studio \"#5a5d64\"" },
|
|
72
|
+
{ path: "stage.lighting.fill", type: "number", persisted: true, undoDomain: "stage", normalizer: "createSceneStage", min: 0, max: 3, note: "multiplier on the fixed fill and rim lights; 1 = studio, 0 = key only" },
|
|
73
|
+
{ path: "stage.lighting.fog", type: "number", persisted: true, undoDomain: "stage", normalizer: "createSceneStage", min: 0, max: 1, note: "haze 0..1 fading the set into the sky colour: 0 = far horizon only, 0.45 = from ~18 m to ~38 m, 1 = from 2 m to 14 m" },
|
|
74
|
+
{ path: "stage.lighting.exposure", type: "number", persisted: true, undoDomain: "stage", normalizer: "createSceneStage", min: 0.2, max: 3, note: "camera exposure multiplier on lit surfaces; 1 = studio, e.g. 1.4 to lift a dark frame" },
|
|
75
|
+
{ path: "stage.lighting.shadowSoftness", type: "number", persisted: true, undoDomain: "stage", normalizer: "createSceneStage", min: 0, max: 1, note: "key shadow penumbra; 0 = crisp (studio), 0.75 = soft moonlight, 1 = overcast" },
|
|
76
|
+
{ path: "stage.lighting.envLight", type: "number", persisted: true, undoDomain: "stage", normalizer: "createSceneStage", min: 0, max: 2, note: "sky environment light on every surface: a map from sky, skyTop, hemiGround and a glow toward the key (sun or moon) tints and fills clay from all sides; it takes over the flat hemisphere as it rises. 0 = hemisphere only (studio), 1 = the map alone at the same brightness (every other recipe), e.g. 1.4 for a brighter fill" },
|
|
77
|
+
{ path: "stage.lighting.ao", type: "number", persisted: true, undoDomain: "stage", normalizer: "createSceneStage", min: 0, max: 1, note: "ambient occlusion strength: contact darkening within ~0.5 m where a body meets the floor or a prop and in creases (armpits, the back of a squat's knees), in the viewport and every export. 0 = off (studio), 0.8 = most recipes, 1 = overcast" },
|
|
78
|
+
// An HDR environment map (src/stage-hdr.js): the light and, with hdrBackground, the backdrop.
|
|
79
|
+
{ path: "stage.lighting.hdr", type: "id", nullable: true, persisted: true, undoDomain: "stage", normalizer: "createSceneStage", note: "HDR environment map that replaces the sky environment light: a built-in (\"builtin:night-street\", \"builtin:overcast-field\", \"builtin:studio-softbox\") or a file the user loaded (\"hdr-<32 hex>\"); inspect_studio { scope: \"catalogue\" } environments lists them with each map's brightest direction. It lights the set only while envLight > 0 (its light = map x hdrIntensity x envLight; studio has envLight 0, so set envLight 1). stage.applyLighting clears it unless keep has \"lighting.hdr\". null = the look's own sky" },
|
|
80
|
+
{ path: "stage.lighting.hdrRotation", type: "number", persisted: true, undoDomain: "stage", normalizer: "createSceneStage", min: -180, max: 180, angle: true, note: "degrees the HDR map turns about the vertical axis, light and backdrop together: +90 carries a light at +x (screen right from the default camera) to -z (behind the subject), so a brightest [x, y, z] lands at [x cos r + z sin r, y, z cos r - x sin r]; wrapped at the upper bound" },
|
|
81
|
+
{ path: "stage.lighting.hdrIntensity", type: "number", persisted: true, undoDomain: "stage", normalizer: "createSceneStage", min: 0, max: 4, note: "multiplier on the HDR map's radiance in its light and its backdrop; 1 = the file as authored, e.g. 0.5 to calm a bright daylight map" },
|
|
82
|
+
{ path: "stage.lighting.hdrBackground", type: "boolean", persisted: true, undoDomain: "stage", normalizer: "createSceneStage", note: "true = the HDR map is the backdrop behind the set (the floor fades into its horizon), in the viewport, shot and every export; false = keep the look's sky as the backdrop and use the map only as light" },
|
|
63
83
|
{ path: "shot.crud", type: "array", persisted: true, undoDomain: "shot", normalizer: null, actions: ["shot.create", "shot.split", "shot.duplicate", "shot.remove", "shot.setRange", "shot.reorder"], note: "create/split/duplicate/reorder/remove/range through the shared action registry" },
|
|
64
84
|
{ path: "shot.cameraKeys", type: "array", persisted: true, undoDomain: "shot", normalizer: null, frameMin: 0 },
|
|
65
85
|
{ path: "shot.cameraRail", type: "array", persisted: true, undoDomain: "shot", normalizer: "repairCamera", actions: ["shot.setCameraRail", "shot.clearCameraRail"], note: "rail points; crane/dolly timing follow the shot's camera block" },
|
|
@@ -74,7 +94,7 @@ const entries = [
|
|
|
74
94
|
{ path: "selection", type: "id", persisted: false, undoDomain: null, normalizer: null },
|
|
75
95
|
{ path: "timeline", type: "number", persisted: false, undoDomain: null, normalizer: null },
|
|
76
96
|
{ path: "view.mode", type: "enum", persisted: false, undoDomain: null, normalizer: null, enum: ["scene", "pose", "camera", "motion"] },
|
|
77
|
-
{ path: "view.partColoursGuideModeInset", type: "array", persisted: false, undoDomain: null, normalizer: null, actions: ["view.setPartColours", "view.setGuideMode", "view.setInset"], note: "partColours/guideMode/inset; viewer settings, not document fields" },
|
|
97
|
+
{ path: "view.partColoursGuideModeInset", type: "array", persisted: false, undoDomain: null, normalizer: null, actions: ["view.setPartColours", "view.setGuideMode", "view.setInset", "view.setGhosts"], note: "partColours/guideMode/inset/ghosts; viewer settings, not document fields" },
|
|
78
98
|
{ path: "read.sceneDescription", type: "string", persisted: false, undoDomain: null, normalizer: null },
|
|
79
99
|
{ path: "read.captureFrame", type: "image", persisted: false, undoDomain: null, normalizer: null },
|
|
80
100
|
{ path: "undo", type: "boolean", persisted: false, undoDomain: null, normalizer: null },
|