@hraness/slopcamera 3.2.7 → 3.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (188) hide show
  1. package/NOTICE.md +1 -7
  2. package/PRIVACY.md +16 -7
  3. package/README.md +65 -20
  4. package/apps/desktop/README.md +14 -130
  5. package/apps/desktop/application/context.ts +6 -9
  6. package/apps/desktop/application/default-registry.ts +26 -10
  7. package/apps/desktop/application/operation.ts +14 -5
  8. package/apps/desktop/application/operations/index.ts +4 -1
  9. package/apps/desktop/application/operations/spatial-behavior.ts +151 -0
  10. package/apps/desktop/application/operations/spatial-direction.ts +222 -0
  11. package/apps/desktop/application/operations/spatial-rendered-audit.ts +174 -0
  12. package/apps/desktop/application/operations/spatial-review.ts +194 -0
  13. package/apps/desktop/application/operations/spatial-scene.ts +25 -1
  14. package/apps/desktop/application/registry.ts +2 -2
  15. package/apps/desktop/application/spatial-asset-admission.ts +201 -0
  16. package/apps/desktop/application/spatial-assets.ts +160 -31
  17. package/apps/desktop/application/spatial-render.ts +217 -20
  18. package/apps/desktop/application/spatial-rendered-audit.ts +273 -0
  19. package/apps/desktop/application/spatial-review.ts +361 -0
  20. package/apps/desktop/application/spatial-spz.ts +48 -3
  21. package/apps/desktop/application/spatial-world-import.ts +26 -5
  22. package/apps/desktop/application/spatial-world-metadata.ts +105 -0
  23. package/apps/desktop/cli/args.ts +528 -97
  24. package/apps/desktop/cli/capability-manifest.ts +374 -0
  25. package/apps/desktop/cli/cinema-renderer.ts +608 -0
  26. package/apps/desktop/cli/command-host-resources.ts +31 -2
  27. package/apps/desktop/cli/commands.ts +1215 -70
  28. package/apps/desktop/cli/gateway-review-provider.ts +334 -0
  29. package/apps/desktop/cli/help.ts +183 -23
  30. package/apps/desktop/cli/html-scene.ts +139 -28
  31. package/apps/desktop/cli/io.ts +2 -1
  32. package/apps/desktop/cli/main.ts +26 -50
  33. package/apps/desktop/cli/menubar.ts +244 -0
  34. package/apps/desktop/cli/music-analysis-service.ts +61 -0
  35. package/apps/desktop/cli/portable-surface.ts +18 -5
  36. package/apps/desktop/cli/spatial-asset-service.ts +124 -0
  37. package/apps/desktop/cli/spatial-design-service.ts +237 -0
  38. package/apps/desktop/cli/spatial-generate-service.ts +370 -0
  39. package/apps/desktop/cli/spatial-scene-service.ts +268 -4
  40. package/apps/desktop/cli/workflow-code.ts +1 -1
  41. package/apps/desktop/code/application-node-planner.ts +0 -47
  42. package/apps/desktop/code/host-source-layout.ts +46 -0
  43. package/apps/desktop/code/public.ts +0 -1
  44. package/apps/desktop/code/runtime-identity.ts +50 -8
  45. package/apps/desktop/code/semantic-builder.ts +24 -43
  46. package/apps/desktop/code/source-bundle.ts +23 -6
  47. package/apps/desktop/code/source-typecheck.ts +131 -43
  48. package/apps/desktop/code/worker-client.ts +14 -2
  49. package/apps/desktop/contracts/cinema.ts +736 -0
  50. package/apps/desktop/contracts/index.ts +1 -0
  51. package/apps/desktop/contracts/spatial-asset.ts +16 -0
  52. package/apps/desktop/contracts/spatial-world.ts +26 -3
  53. package/apps/desktop/core/cinema-plan.ts +1320 -0
  54. package/apps/desktop/core/index.ts +1 -0
  55. package/apps/desktop/core/music-analysis.ts +124 -1
  56. package/apps/desktop/core/storage.ts +75 -0
  57. package/apps/desktop/dist/cli/main.js +737 -291
  58. package/apps/desktop/html-overlay/audio-reactivity.ts +127 -0
  59. package/apps/desktop/html-overlay/index.ts +9 -0
  60. package/apps/desktop/html-overlay/runtime.ts +2 -0
  61. package/apps/desktop/html-overlay/scene.ts +29 -0
  62. package/apps/desktop/html-overlay/spatial.ts +619 -57
  63. package/apps/desktop/workflows/cinematic-world.ts +127 -0
  64. package/apps/desktop/workflows/index.ts +6 -0
  65. package/dist/cli.js +32 -434
  66. package/dist/code/advanced.js +1 -269
  67. package/dist/code/index.js +16 -2239
  68. package/dist/generate.js +1 -23
  69. package/dist/host-resources.js +1 -25
  70. package/dist/index-0bbesn4m.js +35 -0
  71. package/dist/index-296qhpf2.js +3 -0
  72. package/dist/index-3cxf2c58.js +3 -0
  73. package/dist/index-77fjfg9f.js +3 -0
  74. package/dist/index-80a36bc0.js +3 -0
  75. package/dist/index-a3vvs3rf.js +3 -0
  76. package/dist/index-b3geqczn.js +5 -0
  77. package/dist/index-cgpkyjqa.js +3 -0
  78. package/dist/index-f0ptv8dm.js +7 -0
  79. package/dist/index-grk47tgs.js +3 -0
  80. package/dist/index-qry58nj2.js +10 -0
  81. package/dist/index.js +1 -240
  82. package/dist/operations.js +1 -28
  83. package/dist/skill-install-gx7afxsd.js +2 -0
  84. package/dist/vectorize/worker.js +1 -141
  85. package/dist/workflow.js +1 -15
  86. package/docs/README.md +5 -1
  87. package/docs/studio.md +1 -1
  88. package/package.json +11 -24
  89. package/skills/slopcamera/SKILL.md +14 -1
  90. package/skills/slopcamera/references/diagrams.md +5 -14
  91. package/skills/slopcamera/references/directed-scenes.md +22 -2
  92. package/skills/slopcamera/references/gateway-media.md +15 -1
  93. package/skills/slopcamera/references/image-galleries.md +166 -0
  94. package/skills/slopcamera/references/install.md +2 -2
  95. package/skills/slopcamera/references/music-video.md +23 -3
  96. package/skills/slopcamera/references/parametric-design.md +14 -0
  97. package/skills/slopcamera/references/reference-led-3d.md +1 -1
  98. package/skills/slopcamera/references/scene-building.md +32 -0
  99. package/skills/slopcamera/references/social-collage-banners.md +204 -0
  100. package/skills/slopcamera/references/support.md +25 -0
  101. package/skills/slopcamera/references/web-media-excerpts.md +120 -0
  102. package/skills/slopcamera/references/workflows-sdk.md +3 -3
  103. package/skills/slopcamera/scripts/compose-social-collage-banner.ts +728 -0
  104. package/src/capability-manifest.ts +361 -0
  105. package/src/cli.ts +250 -102
  106. package/src/code/canonical-json.ts +9 -0
  107. package/src/generate.ts +2 -2
  108. package/src/icon.ts +1111 -0
  109. package/src/image-gallery.ts +1049 -0
  110. package/src/index.ts +11 -23
  111. package/src/mcp/boundary.ts +11 -11
  112. package/src/mcp/index.ts +3 -0
  113. package/src/mcp/server.ts +2 -1
  114. package/src/mcp/tools.ts +1642 -130
  115. package/src/mcp/types.ts +19 -0
  116. package/src/operations.ts +412 -0
  117. package/src/portable-capability-manifest.ts +67 -0
  118. package/src/scene-gallery.ts +184 -0
  119. package/src/spatial-scene/asset-admission.ts +183 -0
  120. package/src/spatial-scene/audit-rendered.ts +692 -0
  121. package/src/spatial-scene/audit.ts +617 -0
  122. package/src/spatial-scene/behavior-audit.ts +252 -0
  123. package/src/spatial-scene/behavior-bake.ts +233 -0
  124. package/src/spatial-scene/behavior-fns.ts +585 -0
  125. package/src/spatial-scene/behavior-gallery.ts +109 -0
  126. package/src/spatial-scene/behavior-stdlib.ts +323 -0
  127. package/src/spatial-scene/behavior-trace.ts +325 -0
  128. package/src/spatial-scene/behavior.ts +618 -0
  129. package/src/spatial-scene/build.ts +632 -0
  130. package/src/spatial-scene/camera-rig.ts +125 -0
  131. package/src/spatial-scene/camera-track.ts +4 -2
  132. package/src/spatial-scene/character.ts +124 -0
  133. package/src/spatial-scene/contracts.ts +75 -7
  134. package/src/spatial-scene/design-templates.ts +351 -0
  135. package/src/spatial-scene/design.ts +327 -0
  136. package/src/spatial-scene/direction-compile.ts +584 -0
  137. package/src/spatial-scene/direction.ts +122 -0
  138. package/src/spatial-scene/effects.ts +149 -0
  139. package/src/spatial-scene/evaluate.ts +109 -29
  140. package/src/spatial-scene/gallery.ts +277 -0
  141. package/src/spatial-scene/generate.ts +239 -0
  142. package/src/spatial-scene/geometry-native.ts +165 -0
  143. package/src/spatial-scene/geometry.ts +1216 -0
  144. package/src/spatial-scene/gltf.ts +600 -77
  145. package/src/spatial-scene/identity.ts +55 -2
  146. package/src/spatial-scene/index.ts +36 -0
  147. package/src/spatial-scene/inspect.ts +35 -6
  148. package/src/spatial-scene/material-lighting.ts +609 -0
  149. package/src/spatial-scene/math.ts +21 -4
  150. package/src/spatial-scene/motion-evidence.ts +88 -0
  151. package/src/spatial-scene/parametric.ts +765 -0
  152. package/src/spatial-scene/particle-preparation.ts +183 -0
  153. package/src/spatial-scene/particle.ts +195 -0
  154. package/src/spatial-scene/patch.ts +109 -15
  155. package/src/spatial-scene/performance.ts +1613 -0
  156. package/src/spatial-scene/probe.ts +137 -0
  157. package/src/spatial-scene/recipe-pack.ts +86 -0
  158. package/src/spatial-scene/render-effects.ts +240 -0
  159. package/src/spatial-scene/review.ts +639 -0
  160. package/src/spatial-scene/simulation-bake.ts +206 -0
  161. package/src/spatial-scene/simulation.ts +187 -0
  162. package/src/spatial-scene/solve.ts +366 -0
  163. package/src/spatial-scene/temporal-audit.ts +354 -0
  164. package/src/support-completion.ts +16 -0
  165. package/src/support.ts +42 -0
  166. package/src/version.ts +1 -1
  167. package/apps/desktop/application/operations/recording/index.ts +0 -5
  168. package/apps/desktop/application/operations/recording/pause.ts +0 -34
  169. package/apps/desktop/application/operations/recording/resume.ts +0 -34
  170. package/apps/desktop/application/operations/recording/shared.ts +0 -199
  171. package/apps/desktop/application/operations/recording/start.ts +0 -169
  172. package/apps/desktop/application/operations/recording/stop.ts +0 -34
  173. package/apps/desktop/capture/protocol.ts +0 -689
  174. package/apps/desktop/cli/capture-bundle.ts +0 -1117
  175. package/apps/desktop/cli/recording-controller.ts +0 -1567
  176. package/apps/desktop/cli/recording-daemon.ts +0 -931
  177. package/dist/index-42zsesc1.js +0 -2257
  178. package/dist/index-7308egqr.js +0 -48
  179. package/dist/index-8txs6fkn.js +0 -712
  180. package/dist/index-mcy8z0br.js +0 -192
  181. package/dist/index-p63wavx0.js +0 -2029
  182. package/dist/index-r7gdhmsp.js +0 -424
  183. package/dist/index-rvghzt7b.js +0 -2013
  184. package/dist/index-sh6xbav6.js +0 -1219
  185. package/dist/index-z1w83f81.js +0 -4
  186. package/dist/index-z7239b4h.js +0 -2363
  187. package/dist/skill-install-dh0ntqba.js +0 -10
  188. package/src/desktop.ts +0 -253
@@ -2,16 +2,33 @@ import { constants } from "node:fs";
2
2
  import { open, realpath } from "node:fs/promises";
3
3
  import { basename, dirname, relative, resolve } from "node:path";
4
4
  import { canonicalJson } from "../../../src/code/canonical-json";
5
+ import { normalizeSpatialAuditAssetBounds, spatialEntityLocalBounds } from "../../../src/spatial-scene/audit";
5
6
  import { createSpatialSceneStarter } from "../../../src/spatial-scene/authoring";
6
- import { SPATIAL_SCENE_LIMITS } from "../../../src/spatial-scene/contracts";
7
- import { parseSpatialScene, spatialSceneSha256 } from "../../../src/spatial-scene/index";
8
7
  import { sampleSpatialCameraTrack } from "../../../src/spatial-scene/camera-track";
8
+ import { SPATIAL_SCENE_LIMITS, SpatialCameraIdSchema } from "../../../src/spatial-scene/contracts";
9
+ import { checkSpatialBehavior } from "../../../src/spatial-scene/behavior";
10
+ import { auditSpatialBehaviorTrace } from "../../../src/spatial-scene/behavior-audit";
11
+ import { bakeSpatialBehavior } from "../../../src/spatial-scene/behavior-bake";
12
+ import { spatialBehaviorFnSignatures } from "../../../src/spatial-scene/behavior-fns";
13
+ import { planSpatialBehaviorGallery, spatialBehaviorGalleryPlanSha256 } from "../../../src/spatial-scene/behavior-gallery";
14
+ import { parseSpatialBehaviorChannelMap, spatialBehaviorBakeSha256 } from "../../../src/spatial-scene/behavior-trace";
15
+ import { checkSpatialDirection, compileSpatialDirection, spatialDirectionCompilationSha256 } from "../../../src/spatial-scene/direction-compile";
16
+ import { createSpatialEvaluationContext, evaluateSpatialSceneInContext } from "../../../src/spatial-scene/evaluate";
17
+ import { planSpatialDirectionGallery, spatialGalleryPlanSha256 } from "../../../src/spatial-scene/gallery";
18
+ import { checkSpatialRenderEffects, planSpatialRenderEffects } from "../../../src/spatial-scene/render-effects";
19
+ import { SPATIAL_REVIEW_LIMITS } from "../../../src/spatial-scene/review";
20
+ import { bakeSpatialSimulation, spatialSimulationBakeSha256 } from "../../../src/spatial-scene/simulation-bake";
21
+ import { auditSpatialTemporalEvidence, SpatialTemporalContactsFileSchema, spatialTemporalAuditReportSha256 } from "../../../src/spatial-scene/temporal-audit";
22
+ import { parseSpatialScene, parseSpatialValue, spatialSceneSha256 } from "../../../src/spatial-scene/index";
23
+ import { diffSpatialScenes } from "../../../src/spatial-scene/patch";
24
+ import { SpatialSolveError, SpatialSolveGoalsFileSchema, solveSpatialRelations, type SpatialSolveResult } from "../../../src/spatial-scene/solve";
9
25
  import type { ApplicationContext } from "../application/context";
10
26
  import { createApplicationOperationRegistry } from "../application/default-registry";
11
27
  import { planSpatialRender } from "../application/spatial-render";
12
28
  import { createNodeBundleFileSystem } from "../core/storage";
13
29
  import type { SpatialCliExecutionProfile, SpatialSceneCommand } from "./args";
14
- import { CliError } from "./errors";
30
+ import { CliError, type CliErrorCode } from "./errors";
31
+ import { executeSpatialGenerateCommand } from "./spatial-generate-service";
15
32
 
16
33
  /** Capture an explicit local regular file once, without following a leaf symlink. */
17
34
  export async function readSpatialJson(path: string, maximumBytes: number = SPATIAL_SCENE_LIMITS.sourceBytes): Promise<unknown> {
@@ -54,15 +71,166 @@ function assertCameraTrackActive(signal: AbortSignal | undefined): void {
54
71
  if (signal?.aborted === true) throw new CliError("cancelled", "Camera track export was cancelled.");
55
72
  }
56
73
 
74
+ /** Solver codes collapse onto the CLI code set; the typed code stays in the bracketed message prefix. */
75
+ const SOLVE_ERROR_CODES: Readonly<Record<string, CliErrorCode>> = {
76
+ "invalid-data": "invalid-data",
77
+ "unknown-entity": "not-found",
78
+ "bounds-unknown": "invalid-data",
79
+ "relation-cycle": "conflict",
80
+ "duplicate-goal": "conflict",
81
+ "relation-failed": "invalid-data",
82
+ };
83
+
57
84
  export async function executeSpatialSceneCommand(application: ApplicationContext, command: SpatialSceneCommand, signal?: AbortSignal): Promise<unknown> {
85
+ if (command.action === "generate") return executeSpatialGenerateCommand(application, command);
86
+ // Fail closed before touching the filesystem, renderer, or provider: review
87
+ // uploads the selected bounded rendered beauty frames to a vision model and
88
+ // requires --allow-cloud-upload on this exact invocation.
89
+ if (command.action === "review" && !command.allowCloudUpload) {
90
+ throw new CliError("authorization-required", "scene review uploads bounded rendered beauty frames to a vision model and requires --allow-cloud-upload on this invocation.", { command: "scene review" });
91
+ }
92
+ if (command.action === "behavior-audit") {
93
+ const bake = await readSpatialJson(resolve(application.paths.repositoryRoot, command.bake));
94
+ const report = auditSpatialBehaviorTrace(bake);
95
+ if (command.output === undefined) return report;
96
+ const output = resolve(application.paths.repositoryRoot, command.output);
97
+ await publishSpatialSource(output, report);
98
+ return { path: output, findings: report.findings.length, channels: report.channelCount, emitted: report.emittedCount };
99
+ }
58
100
  if (command.action === "camera-track") assertCameraTrackActive(signal);
59
- const sourcePath = resolve(application.paths.repositoryRoot, command.path);
101
+ const sourcePath = resolve(application.paths.repositoryRoot, "path" in command ? command.path : command.scene);
60
102
  if (command.action === "init") {
61
103
  const scene = createSpatialSceneStarter();
62
104
  await publishSpatialSource(sourcePath, scene);
63
105
  return { path: sourcePath, sceneSha256: spatialSceneSha256(scene) };
64
106
  }
107
+ if (command.action === "review") {
108
+ const acknowledgedAt = application.clock.now().toISOString();
109
+ const authorizedApplication: ApplicationContext = {
110
+ ...application,
111
+ spatialReviewAuthorization: {
112
+ authorize: async request => (typeof request.sceneSha256 === "string" && /^[a-f0-9]{64}$/u.test(request.sceneSha256)
113
+ && SpatialCameraIdSchema.safeParse(request.cameraId).success
114
+ && Array.isArray(request.timesUs)
115
+ && request.timesUs.length >= 1
116
+ && request.timesUs.length <= SPATIAL_REVIEW_LIMITS.frames
117
+ && request.maximumFrames >= 1
118
+ && request.maximumFrames <= SPATIAL_REVIEW_LIMITS.frames
119
+ && request.maximumFrameBytes <= SPATIAL_REVIEW_LIMITS.pngBytes
120
+ && request.maximumUploadBytes <= SPATIAL_REVIEW_LIMITS.uploadBytes
121
+ ? { acknowledgedAt }
122
+ : undefined),
123
+ },
124
+ };
125
+ const result = await createApplicationOperationRegistry().execute({ application: authorizedApplication, abortSignal: signal ?? new AbortController().signal }, {
126
+ kind: "scene.review", version: 1,
127
+ input: {
128
+ source: { path: relative(application.paths.repositoryRoot, sourcePath) },
129
+ request: { cameraId: command.camera, ...(command.timesUs === undefined ? {} : { timesUs: [...command.timesUs] }) },
130
+ },
131
+ });
132
+ return result.output;
133
+ }
134
+ if (command.action === "render-audit") {
135
+ const result = await createApplicationOperationRegistry().execute({ application, abortSignal: signal ?? new AbortController().signal }, {
136
+ kind: "scene.render-audit", version: 1,
137
+ input: {
138
+ source: { path: relative(application.paths.repositoryRoot, sourcePath) },
139
+ request: { cameraId: command.camera, ...(command.timesUs === undefined ? {} : { timesUs: [...command.timesUs] }) },
140
+ },
141
+ });
142
+ return result.output;
143
+ }
65
144
  const scene = parseSpatialScene(await readSpatialJson(sourcePath));
145
+ if (command.action === "diff") {
146
+ const other = parseSpatialScene(await readSpatialJson(resolve(application.paths.repositoryRoot, command.other)));
147
+ return { sceneSha256: spatialSceneSha256(scene), otherSha256: spatialSceneSha256(other), diff: diffSpatialScenes(scene, other) };
148
+ }
149
+ if (command.action === "solve") {
150
+ const goalsFile = parseSpatialValue(SpatialSolveGoalsFileSchema, await readSpatialJson(resolve(application.paths.repositoryRoot, command.goals)), "solve goals");
151
+ const assetBounds = command.assetBounds === undefined
152
+ ? {}
153
+ : normalizeSpatialAuditAssetBounds(await readSpatialJson(resolve(application.paths.repositoryRoot, command.assetBounds)));
154
+ const entities = new Map(scene.entities.map(entity => [entity.entityId, entity]));
155
+ const cameras = new Map(scene.cameras.map(camera => [camera.cameraId, camera]));
156
+ const transformOverrides = new Map(
157
+ scene.overrides.filter(override => override.property === "transform").map(override => [override.entityId, override.value]),
158
+ );
159
+ const animatedTransforms = new Map<string, string[]>();
160
+ for (const channel of scene.animations) {
161
+ if (channel.property === "position" || channel.property === "rotation" || channel.property === "scale") {
162
+ animatedTransforms.set(channel.targetId, [...(animatedTransforms.get(channel.targetId) ?? []), channel.channelId]);
163
+ }
164
+ }
165
+ const referenced = new Set<string>();
166
+ for (const goal of goalsFile.goals) {
167
+ referenced.add(goal.entityKey);
168
+ for (const relation of goal.relations) if ("target" in relation) referenced.add(relation.target);
169
+ }
170
+ for (const goal of goalsFile.goals) {
171
+ const entity = entities.get(goal.entityKey);
172
+ if (entity === undefined) throw new CliError("not-found", `Solve goal ${goal.entityKey} is not an entity in the scene.`);
173
+ if (entity.origin.kind === "generated") {
174
+ throw new CliError("conflict", `Goal entity ${entity.entityId} is generated; edit it through declared overrides or retained output replacement.`);
175
+ }
176
+ if (entity.parentId !== null) {
177
+ throw new CliError("conflict", `Goal entity ${entity.entityId} is parented under ${entity.parentId}; solver bases are world transforms — pass an explicit base entry or reparent first.`);
178
+ }
179
+ if (entity.placement.kind !== "world") {
180
+ throw new CliError("conflict", `Goal entity ${entity.entityId} uses view placement; solver transforms are world-space.`);
181
+ }
182
+ if (transformOverrides.has(entity.entityId)) {
183
+ throw new CliError("conflict", `Goal entity ${entity.entityId} has a transform override; a set-transform patch would stay shadowed.`);
184
+ }
185
+ const writers = animatedTransforms.get(entity.entityId);
186
+ if (writers !== undefined) {
187
+ throw new CliError("conflict", `Goal entity ${entity.entityId} is animated (${writers.join(", ")}); a solved transform would not take effect.`);
188
+ }
189
+ }
190
+ const bases: Record<string, unknown> = {};
191
+ for (const key of [...referenced].sort()) {
192
+ const entity = entities.get(key);
193
+ if (entity !== undefined) {
194
+ // Local bounds are transform-agnostic, so they seed whenever derivable;
195
+ // the authored transform only seeds a base when it is the world transform.
196
+ const explicit = goalsFile.bases?.[key]?.transform !== undefined;
197
+ if (!explicit && (entity.parentId !== null || entity.placement.kind !== "world")) {
198
+ throw new CliError("conflict", `Referenced entity ${key} is ${entity.parentId === null ? "view-placed" : `parented under ${entity.parentId}`}; solver bases are world transforms — pass an explicit base entry.`);
199
+ }
200
+ const enclosure = spatialEntityLocalBounds(entity, assetBounds);
201
+ bases[key] = {
202
+ ...(explicit ? {} : { transform: transformOverrides.get(key) ?? entity.transform }),
203
+ ...(enclosure.status === "bounded" ? { bounds: enclosure.bounds } : {}),
204
+ };
205
+ continue;
206
+ }
207
+ const camera = cameras.get(key);
208
+ if (camera !== undefined) {
209
+ bases[key] = { transform: { position: camera.pose.position, rotation: camera.pose.rotation, scale: [1, 1, 1] } };
210
+ }
211
+ }
212
+ for (const [key, basePatch] of Object.entries(goalsFile.bases ?? {})) {
213
+ const existing = bases[key] as { transform?: unknown; bounds?: unknown } | undefined;
214
+ bases[key] = {
215
+ ...(existing ?? {}),
216
+ ...(basePatch.transform === undefined ? {} : { transform: basePatch.transform }),
217
+ ...(basePatch.bounds === undefined ? {} : { bounds: basePatch.bounds }),
218
+ };
219
+ }
220
+ let result: SpatialSolveResult;
221
+ try {
222
+ result = solveSpatialRelations({ goals: goalsFile.goals, bases });
223
+ } catch (error) {
224
+ if (error instanceof SpatialSolveError) {
225
+ throw new CliError(SOLVE_ERROR_CODES[error.code] ?? "invalid-data", `[${error.code}] ${error.message}`);
226
+ }
227
+ throw error;
228
+ }
229
+ if (command.output === undefined) return result.patch;
230
+ const output = resolve(application.paths.repositoryRoot, command.output);
231
+ await publishSpatialSource(output, result.patch);
232
+ return { path: output, sceneSha256: spatialSceneSha256(scene), solvedEntities: Object.keys(result.transforms) };
233
+ }
66
234
  if (command.action === "camera-track") {
67
235
  const fence = async () => {
68
236
  assertCameraTrackActive(signal);
@@ -78,6 +246,91 @@ export async function executeSpatialSceneCommand(application: ApplicationContext
78
246
  await fence();
79
247
  return { path: output, sceneSha256: track.sceneSha256, cameraId: track.cameraId, clock: track.clock, executed: false };
80
248
  }
249
+ if (command.action === "direction-check") {
250
+ const direction = await readSpatialJson(resolve(application.paths.repositoryRoot, command.direction));
251
+ return checkSpatialDirection({ direction, scene });
252
+ }
253
+ if (command.action === "direction-plan") {
254
+ const direction = await readSpatialJson(resolve(application.paths.repositoryRoot, command.direction));
255
+ const compilation = compileSpatialDirection({ direction, scene, ...(command.camera === undefined ? {} : { cameraId: command.camera }) });
256
+ if (command.output === undefined) return compilation;
257
+ const output = resolve(application.paths.repositoryRoot, command.output);
258
+ await publishSpatialSource(output, compilation);
259
+ return { path: output, compilationSha256: spatialDirectionCompilationSha256(compilation), proposals: compilation.proposals, advisories: compilation.advisories };
260
+ }
261
+ if (command.action === "direction-gallery") {
262
+ const direction = await readSpatialJson(resolve(application.paths.repositoryRoot, command.direction));
263
+ const plan = planSpatialDirectionGallery({ axis: command.axis, direction, scene, ...(command.camera === undefined ? {} : { cameraId: command.camera }) });
264
+ if (command.output === undefined) return plan;
265
+ const output = resolve(application.paths.repositoryRoot, command.output);
266
+ await publishSpatialSource(output, plan);
267
+ return { path: output, galleryPlanSha256: spatialGalleryPlanSha256(plan), candidates: plan.candidates.map(candidate => ({ candidateId: candidate.candidateId, documentSha256: candidate.documentSha256 })) };
268
+ }
269
+ if (command.action === "effects-check") {
270
+ const effects = await readSpatialJson(resolve(application.paths.repositoryRoot, command.effects));
271
+ return checkSpatialRenderEffects({ effects, scene });
272
+ }
273
+ if (command.action === "effects-plan") {
274
+ const draft = await readSpatialJson(resolve(application.paths.repositoryRoot, command.draft));
275
+ if (typeof draft !== "object" || draft === null || Array.isArray(draft)) throw new CliError("invalid-data", "An effects draft must be a JSON object with renderPlan, particleSystems, and simulationBakes fields.");
276
+ const binding = planSpatialRenderEffects({ ...draft, scene });
277
+ if (command.output === undefined) return binding;
278
+ const output = resolve(application.paths.repositoryRoot, command.output);
279
+ await publishSpatialSource(output, binding);
280
+ return { path: output, documentSha256: binding.documentSha256 };
281
+ }
282
+ if (command.action === "effects-bake") {
283
+ const plan = await readSpatialJson(resolve(application.paths.repositoryRoot, command.simulation));
284
+ const result = bakeSpatialSimulation(plan);
285
+ const output = resolve(application.paths.repositoryRoot, command.output);
286
+ await publishSpatialSource(output, result.document);
287
+ return { path: output, documentSha256: spatialSimulationBakeSha256(result.document), receipt: result.receipt };
288
+ }
289
+ if (command.action === "behavior-check") {
290
+ const behavior = await readSpatialJson(resolve(application.paths.repositoryRoot, command.behavior));
291
+ return checkSpatialBehavior({ behavior, scene }, spatialBehaviorFnSignatures());
292
+ }
293
+ if (command.action === "behavior-bake" || command.action === "behavior-gallery") {
294
+ const behavior = await readSpatialJson(resolve(application.paths.repositoryRoot, command.behavior));
295
+ const channelMap = command.channelMap === undefined
296
+ ? undefined
297
+ : parseSpatialBehaviorChannelMap(await readSpatialJson(resolve(application.paths.repositoryRoot, command.channelMap)));
298
+ if (command.action === "behavior-bake") {
299
+ const { bake } = await bakeSpatialBehavior({ behavior, scene, ...(channelMap === undefined ? {} : { channelMap }) });
300
+ const output = resolve(application.paths.repositoryRoot, command.output);
301
+ await publishSpatialSource(output, bake);
302
+ return {
303
+ path: output,
304
+ bakeSha256: spatialBehaviorBakeSha256(bake),
305
+ emitted: bake.emitted.length,
306
+ directives: bake.directives.length,
307
+ unresolvedIntents: bake.unresolvedIntents.length,
308
+ receipt: bake.receipt,
309
+ };
310
+ }
311
+ const plan = await planSpatialBehaviorGallery({ behavior, scene, ...(channelMap === undefined ? {} : { channelMap }) });
312
+ if (command.output === undefined) return plan;
313
+ const output = resolve(application.paths.repositoryRoot, command.output);
314
+ await publishSpatialSource(output, plan);
315
+ return { path: output, galleryPlanSha256: spatialBehaviorGalleryPlanSha256(plan), candidates: plan.candidates.map(candidate => ({ candidateId: candidate.candidateId, documentSha256: candidate.documentSha256, parameter: candidate.parameter })) };
316
+ }
317
+ if (command.action === "temporal-audit") {
318
+ const times = command.timesUs ?? (() => {
319
+ const count = Math.min(17, Math.max(2, Math.ceil(scene.durationUs / 1_000_000) + 1));
320
+ return Array.from({ length: count }, (_, index) => Math.floor((scene.durationUs * index) / (count - 1)));
321
+ })();
322
+ if (times.length < 2) throw new CliError("invalid-data", "scene temporal-audit requires at least two sample times.");
323
+ const contacts = command.contacts === undefined
324
+ ? []
325
+ : parseSpatialValue(SpatialTemporalContactsFileSchema, await readSpatialJson(resolve(application.paths.repositoryRoot, command.contacts)), "temporal contacts").contacts;
326
+ const context = createSpatialEvaluationContext(scene);
327
+ const snapshots = [...times].sort((left, right) => left - right).map(timeUs => evaluateSpatialSceneInContext(context, { cameraId: command.camera, timeUs }));
328
+ const report = auditSpatialTemporalEvidence({
329
+ snapshots,
330
+ options: { contacts, ...(command.cutBeforeUs === undefined ? {} : { cutBeforeUs: [...command.cutBeforeUs] }) },
331
+ });
332
+ return { ...report, reportSha256: spatialTemporalAuditReportSha256(report) };
333
+ }
81
334
  if (command.action === "plan" || command.action === "render") {
82
335
  const request = bindSpatialCliExecutionProfile(await readSpatialJson(resolve(application.paths.repositoryRoot, command.request)), command.executionProfile);
83
336
  if (command.action === "plan") return planSpatialRender(scene, request);
@@ -90,6 +343,17 @@ export async function executeSpatialSceneCommand(application: ApplicationContext
90
343
  return result.output;
91
344
  }
92
345
  const registry = createApplicationOperationRegistry();
346
+ if (command.action === "audit") {
347
+ const result = await registry.execute({ application, abortSignal: new AbortController().signal }, {
348
+ kind: "scene.audit", version: 1,
349
+ input: {
350
+ scene, cameraId: command.camera,
351
+ ...(command.timesUs === undefined ? {} : { timesUs: [...command.timesUs] }),
352
+ ...(command.assetBounds === undefined ? {} : { assetBounds: normalizeSpatialAuditAssetBounds(await readSpatialJson(resolve(application.paths.repositoryRoot, command.assetBounds))) }),
353
+ },
354
+ });
355
+ return result.output;
356
+ }
93
357
  const input = command.action === "patch"
94
358
  ? { scene, patch: await readSpatialJson(resolve(application.paths.repositoryRoot, command.patch)) }
95
359
  : command.action === "evaluate" ? { scene, cameraId: command.camera, timeUs: command.timeUs } : { scene };
@@ -308,7 +308,7 @@ export function workflowCatalogDescription(workflow: BuiltInWorkflow) {
308
308
  return {
309
309
  ...workflowCatalogEntry(workflow),
310
310
  inputJsonSchema: {
311
- ...z.toJSONSchema(workflow.inputSchema),
311
+ ...z.toJSONSchema(workflow.inputSchema, { io: "input" }),
312
312
  $id: workflow.inputSchemaId,
313
313
  },
314
314
  } as const;
@@ -51,7 +51,6 @@ import {
51
51
  bindProjectRenderInputV2,
52
52
  bindProjectRenderInputV3,
53
53
  bindProjectRenderToolchain,
54
- parseRecordingOperationOutput,
55
54
  projectEditCommitReceipt,
56
55
  projectEditTransactionId,
57
56
  reconcileVariantSelectionPromotion,
@@ -105,7 +104,6 @@ import type {
105
104
  import { workflowBoundary, workflowValidation, type WorkflowFailure } from "./workflow-effects";
106
105
 
107
106
  const ANALYSIS_ID_DOMAIN = "studio.workflow.analysis-id/v1";
108
- type RecordingOperationKind = Extract<OperationKind, `recording.${string}`>;
109
107
  type GatewayOperationKind = Extract<OperationKind, `gateway.${string}`>;
110
108
 
111
109
  export const APPLICATION_VERIFIED_RECEIPT_RECONCILER_KINDS =
@@ -114,15 +112,6 @@ export const APPLICATION_VERIFIED_RECEIPT_RECONCILER_KINDS =
114
112
  "render.project",
115
113
  ] as const satisfies readonly OperationKind[]);
116
114
 
117
- function isRecordingOperationKind(
118
- kind: OperationKind,
119
- ): kind is RecordingOperationKind {
120
- return kind === "recording.start"
121
- || kind === "recording.pause"
122
- || kind === "recording.resume"
123
- || kind === "recording.stop";
124
- }
125
-
126
115
  function isGatewayOperationKind(
127
116
  kind: OperationKind,
128
117
  ): kind is GatewayOperationKind {
@@ -934,36 +923,6 @@ function renderNodeReconciliation(result: ProjectRenderReconciliation | Awaited<
934
923
  };
935
924
  }
936
925
 
937
- async function reconcileRecordingNode(
938
- application: ApplicationContext,
939
- operationKind: RecordingOperationKind,
940
- ): Promise<NodeReconciliation> {
941
- const controller = application.recordingController;
942
- if (controller === undefined) {
943
- return {
944
- kind: "ambiguous",
945
- message: "Recording controller is unavailable for live-state reconciliation.",
946
- };
947
- }
948
- let output;
949
- try {
950
- output = parseRecordingOperationOutput(await controller.status());
951
- } catch (error) {
952
- return {
953
- kind: "ambiguous",
954
- message: error instanceof Error
955
- ? `Recording state could not be reconciled: ${error.message}`
956
- : "Recording state could not be reconciled.",
957
- };
958
- }
959
- return {
960
- kind: "ambiguous",
961
- message: `Current controller state ${output.state} for ${
962
- output.recordingId ?? "no active recording"
963
- } is not an exact receipt proving ${operationKind} completed; issue a new explicit action.`,
964
- };
965
- }
966
-
967
926
  async function reconcileGatewayNode(
968
927
  application: ApplicationContext,
969
928
  request: NodeReconciliationRequest,
@@ -1223,12 +1182,6 @@ async function reconcileApplicationNode(
1223
1182
  request.operation.kind,
1224
1183
  );
1225
1184
  }
1226
- if (isRecordingOperationKind(request.operation.kind)) {
1227
- return await reconcileRecordingNode(
1228
- application,
1229
- request.operation.kind,
1230
- );
1231
- }
1232
1185
  if (request.operation.kind === "project.commit-edits") {
1233
1186
  return await reconcileProjectCommit(application, request);
1234
1187
  }
@@ -0,0 +1,46 @@
1
+ import { basename, dirname, join, resolve } from "node:path";
2
+
3
+ export interface HostSourceRoots {
4
+ /**
5
+ * True when the running module is inside the committed bundle at
6
+ * apps/desktop/dist/cli. A bundled host still spawns the checked TypeScript
7
+ * sources below apps/desktop and src/.
8
+ */
9
+ readonly bundled: boolean;
10
+ /** The installed apps/desktop source directory. */
11
+ readonly desktopRoot: string;
12
+ /** The checkout or package root containing src/, packages/, and package.json. */
13
+ readonly repositoryRoot: string;
14
+ }
15
+
16
+ /**
17
+ * Walks to the apps/desktop anchor instead of assuming a fixed module depth.
18
+ * From a checkout, caller modules sit below apps/desktop/<area>/; inside the
19
+ * committed bundle they all share apps/desktop/dist/cli.
20
+ */
21
+ export function hostSourceRoots(moduleDirectory: string): HostSourceRoots {
22
+ const requested = resolve(moduleDirectory);
23
+ let candidate = requested;
24
+ while (true) {
25
+ if (basename(candidate) === "desktop" && basename(dirname(candidate)) === "apps") {
26
+ return {
27
+ bundled: requested === join(candidate, "dist", "cli"),
28
+ desktopRoot: candidate,
29
+ repositoryRoot: dirname(dirname(candidate)),
30
+ };
31
+ }
32
+ const parent = dirname(candidate);
33
+ if (parent === candidate) {
34
+ // Embedded compiled-binary modules and foreign callers keep the
35
+ // historical assumption that the module sits one level below
36
+ // apps/desktop.
37
+ const desktopRoot = resolve(requested, "..");
38
+ return {
39
+ bundled: false,
40
+ desktopRoot,
41
+ repositoryRoot: resolve(desktopRoot, "../.."),
42
+ };
43
+ }
44
+ candidate = parent;
45
+ }
46
+ }
@@ -100,7 +100,6 @@ export {
100
100
  type ProducedVariantMatrixHandle,
101
101
  type ProducedVariantSelectionHandle,
102
102
  type ProjectSnapshotHandle,
103
- type RecordingStartOptions,
104
103
  type SelectCreativeVariantOptions,
105
104
  type VariantMatrixHandle,
106
105
  type VariantSelectionHandle,
@@ -9,7 +9,6 @@ import {
9
9
  extname,
10
10
  join,
11
11
  relative,
12
- resolve,
13
12
  sep,
14
13
  } from "node:path";
15
14
 
@@ -31,6 +30,7 @@ import {
31
30
  WorkflowRuntimeIdentitySchema,
32
31
  type WorkflowRuntimeIdentity,
33
32
  } from "./contracts";
33
+ import { hostSourceRoots } from "./host-source-layout";
34
34
  import type { WorkflowSourceBundle } from "./source-bundle";
35
35
 
36
36
  export interface CreateWorkflowRuntimeIdentityOptions {
@@ -38,7 +38,7 @@ export interface CreateWorkflowRuntimeIdentityOptions {
38
38
  readonly bundle: WorkflowSourceBundle;
39
39
  }
40
40
 
41
- const APPLICATION_BUILD_DOMAIN = "studio.application-build/v1";
41
+ const APPLICATION_BUILD_DOMAIN = "studio.application-build/v2";
42
42
  const HOST_APPLICATION_BUILD_DOMAIN = "studio.host-application-build/v1";
43
43
  const APPLICATION_SOURCE_EXTENSIONS = new Set([
44
44
  ".json",
@@ -52,17 +52,23 @@ const APPLICATION_SOURCE_EXTENSIONS = new Set([
52
52
  const DESKTOP_SOURCE_DIRECTORIES = [
53
53
  "analysis",
54
54
  "application",
55
- "capture",
56
55
  "cli",
57
56
  "code",
58
57
  "contracts",
59
58
  "core",
60
- "runtime",
61
59
  "studio",
62
60
  "workflows",
63
61
  ] as const;
64
62
  const MAXIMUM_APPLICATION_SOURCE_FILES = 4_096;
65
63
  const MAXIMUM_APPLICATION_SOURCE_FILE_BYTES = 64 * 1024 * 1024;
64
+ // The reviewed packed boundary never ships the Zig runtime build tree or the
65
+ // root lockfile, while the committed CLI bundle ships beside the checked
66
+ // sources in both layouts. Each of these inputs stays hash-bound wherever it
67
+ // is present and is skipped only when it is absent.
68
+ const OPTIONAL_APPLICATION_SOURCE_FILES = new Set<string>([
69
+ "apps/desktop/dist/cli/main.js",
70
+ "bun.lock",
71
+ ]);
66
72
  let cachedApplicationBuild: Promise<string> | undefined;
67
73
 
68
74
  function isWithin(root: string, candidate: string): boolean {
@@ -137,6 +143,22 @@ async function collectSourceFiles(
137
143
  }
138
144
  }
139
145
 
146
+ async function presentDesktopDirectory(
147
+ desktopRoot: string,
148
+ directory: string,
149
+ ): Promise<string | undefined> {
150
+ const path = join(desktopRoot, directory);
151
+ try {
152
+ if ((await lstat(path)).isDirectory()) return path;
153
+ return undefined;
154
+ } catch (error) {
155
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
156
+ return undefined;
157
+ }
158
+ throw error;
159
+ }
160
+ }
161
+
140
162
  async function computeApplicationBuildIdentity(options: {
141
163
  readonly desktopRoot: string;
142
164
  readonly repositoryRoot: string;
@@ -151,9 +173,16 @@ async function computeApplicationBuildIdentity(options: {
151
173
  }
152
174
  const sources: Array<{ readonly logicalPath: string; readonly path: string }> = [];
153
175
  for (const directory of DESKTOP_SOURCE_DIRECTORIES) {
176
+ const path = await presentDesktopDirectory(desktopRoot, directory);
177
+ if (path === undefined) {
178
+ throw new ApplicationError(
179
+ "not-found",
180
+ `Application build source directory is missing: apps/desktop/${directory}`,
181
+ );
182
+ }
154
183
  await collectSourceFiles(
155
184
  desktopRoot,
156
- join(desktopRoot, directory),
185
+ path,
157
186
  `apps/desktop/${directory}`,
158
187
  sources,
159
188
  );
@@ -176,10 +205,20 @@ async function computeApplicationBuildIdentity(options: {
176
205
  );
177
206
  }
178
207
  for (const [logicalPath, path] of [
208
+ ["apps/desktop/dist/cli/main.js", join(desktopRoot, "dist", "cli", "main.js")],
179
209
  ["bun.lock", join(repositoryRoot, "bun.lock")],
180
210
  ["package.json", join(repositoryRoot, "package.json")],
181
211
  ] as const) {
182
- const physical = await realpath(path);
212
+ let physical: string;
213
+ try {
214
+ physical = await realpath(path);
215
+ } catch (error) {
216
+ if (
217
+ OPTIONAL_APPLICATION_SOURCE_FILES.has(logicalPath)
218
+ && error instanceof Error && "code" in error && error.code === "ENOENT"
219
+ ) continue;
220
+ throw error;
221
+ }
183
222
  if (!isWithin(repositoryRoot, physical)) {
184
223
  throw new ApplicationError("unsafe-path", `Application build source escapes: ${logicalPath}`);
185
224
  }
@@ -227,8 +266,11 @@ export async function createApplicationBuildIdentity(options: {
227
266
  readonly desktopRoot?: string;
228
267
  readonly repositoryRoot?: string;
229
268
  } = {}): Promise<string> {
230
- const defaultDesktopRoot = resolve(import.meta.dir, "..");
231
- const defaultRepositoryRoot = resolve(defaultDesktopRoot, "../..");
269
+ // Inside the committed bundle, import.meta.dir is apps/desktop/dist/cli;
270
+ // the checked host sources stay at their installed paths below apps/desktop.
271
+ const defaults = hostSourceRoots(import.meta.dir);
272
+ const defaultDesktopRoot = defaults.desktopRoot;
273
+ const defaultRepositoryRoot = defaults.repositoryRoot;
232
274
  if (options.desktopRoot !== undefined || options.repositoryRoot !== undefined) {
233
275
  return await computeApplicationBuildIdentity({
234
276
  desktopRoot: options.desktopRoot ?? defaultDesktopRoot,