@hraness/slopcamera 3.2.8 → 3.3.2

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 (153) hide show
  1. package/README.md +29 -9
  2. package/apps/desktop/application/context.ts +6 -0
  3. package/apps/desktop/application/default-registry.ts +26 -0
  4. package/apps/desktop/application/operation.ts +14 -1
  5. package/apps/desktop/application/operations/index.ts +4 -0
  6. package/apps/desktop/application/operations/spatial-behavior.ts +151 -0
  7. package/apps/desktop/application/operations/spatial-direction.ts +222 -0
  8. package/apps/desktop/application/operations/spatial-rendered-audit.ts +174 -0
  9. package/apps/desktop/application/operations/spatial-review.ts +194 -0
  10. package/apps/desktop/application/operations/spatial-scene.ts +25 -1
  11. package/apps/desktop/application/registry.ts +2 -2
  12. package/apps/desktop/application/spatial-asset-admission.ts +201 -0
  13. package/apps/desktop/application/spatial-assets.ts +160 -31
  14. package/apps/desktop/application/spatial-render.ts +217 -20
  15. package/apps/desktop/application/spatial-rendered-audit.ts +273 -0
  16. package/apps/desktop/application/spatial-review.ts +361 -0
  17. package/apps/desktop/application/spatial-spz.ts +48 -3
  18. package/apps/desktop/application/spatial-world-import.ts +26 -5
  19. package/apps/desktop/application/spatial-world-metadata.ts +105 -0
  20. package/apps/desktop/cli/args.ts +514 -11
  21. package/apps/desktop/cli/capability-manifest.ts +374 -0
  22. package/apps/desktop/cli/cinema-renderer.ts +608 -0
  23. package/apps/desktop/cli/command-host-resources.ts +29 -1
  24. package/apps/desktop/cli/commands.ts +1188 -4
  25. package/apps/desktop/cli/gateway-review-provider.ts +334 -0
  26. package/apps/desktop/cli/help.ts +172 -6
  27. package/apps/desktop/cli/main.ts +3 -3
  28. package/apps/desktop/cli/portable-surface.ts +6 -1
  29. package/apps/desktop/cli/spatial-asset-service.ts +124 -0
  30. package/apps/desktop/cli/spatial-design-service.ts +237 -0
  31. package/apps/desktop/cli/spatial-generate-service.ts +370 -0
  32. package/apps/desktop/cli/spatial-scene-service.ts +271 -4
  33. package/apps/desktop/cli/workflow-code.ts +1 -1
  34. package/apps/desktop/code/semantic-builder.ts +24 -0
  35. package/apps/desktop/contracts/cinema.ts +736 -0
  36. package/apps/desktop/contracts/index.ts +1 -0
  37. package/apps/desktop/contracts/spatial-asset.ts +16 -0
  38. package/apps/desktop/contracts/spatial-world.ts +26 -3
  39. package/apps/desktop/core/cinema-plan.ts +1320 -0
  40. package/apps/desktop/core/index.ts +1 -0
  41. package/apps/desktop/core/storage.ts +75 -0
  42. package/apps/desktop/dist/cli/main.js +725 -273
  43. package/apps/desktop/html-overlay/spatial.ts +619 -57
  44. package/apps/desktop/workflows/cinematic-world.ts +127 -0
  45. package/apps/desktop/workflows/index.ts +6 -0
  46. package/dist/cli.js +25 -429
  47. package/dist/code/advanced.js +1 -269
  48. package/dist/code/index.js +16 -2239
  49. package/dist/generate.js +1 -23
  50. package/dist/host-resources.js +1 -25
  51. package/dist/index-0bbesn4m.js +35 -0
  52. package/dist/index-296qhpf2.js +3 -0
  53. package/dist/index-3cxf2c58.js +3 -0
  54. package/dist/index-77fjfg9f.js +3 -0
  55. package/dist/index-80a36bc0.js +3 -0
  56. package/dist/index-a3vvs3rf.js +3 -0
  57. package/dist/index-b3geqczn.js +5 -0
  58. package/dist/index-grk47tgs.js +3 -0
  59. package/dist/index-gs48afpt.js +7 -0
  60. package/dist/index-qry58nj2.js +10 -0
  61. package/dist/index-zhg36tf9.js +3 -0
  62. package/dist/index.js +1 -226
  63. package/dist/operations.js +1 -28
  64. package/dist/skill-install-gx7afxsd.js +2 -0
  65. package/dist/vectorize/worker.js +1 -141
  66. package/dist/workflow.js +1 -15
  67. package/docs/README.md +5 -1
  68. package/docs/studio.md +12 -0
  69. package/package.json +7 -5
  70. package/skills/slopcamera/SKILL.md +7 -1
  71. package/skills/slopcamera/references/directed-scenes.md +48 -2
  72. package/skills/slopcamera/references/gateway-media.md +15 -1
  73. package/skills/slopcamera/references/image-galleries.md +166 -0
  74. package/skills/slopcamera/references/install.md +1 -1
  75. package/skills/slopcamera/references/music-video.md +4 -0
  76. package/skills/slopcamera/references/native-studio.md +19 -0
  77. package/skills/slopcamera/references/parametric-design.md +14 -0
  78. package/skills/slopcamera/references/scene-building.md +172 -0
  79. package/skills/slopcamera/references/social-collage-banners.md +204 -0
  80. package/skills/slopcamera/references/workflows-sdk.md +3 -3
  81. package/skills/slopcamera/scripts/compose-social-collage-banner.ts +728 -0
  82. package/src/capability-manifest.ts +361 -0
  83. package/src/cli.ts +222 -4
  84. package/src/code/canonical-json.ts +9 -0
  85. package/src/generate.ts +2 -2
  86. package/src/icon.ts +1111 -0
  87. package/src/image-gallery.ts +1049 -0
  88. package/src/index.ts +11 -0
  89. package/src/mcp/boundary.ts +11 -11
  90. package/src/mcp/index.ts +3 -0
  91. package/src/mcp/server.ts +2 -1
  92. package/src/mcp/tools.ts +1649 -132
  93. package/src/mcp/types.ts +19 -0
  94. package/src/operations.ts +412 -0
  95. package/src/portable-capability-manifest.ts +67 -0
  96. package/src/scene-gallery.ts +184 -0
  97. package/src/spatial-scene/asset-admission.ts +183 -0
  98. package/src/spatial-scene/audit-rendered.ts +692 -0
  99. package/src/spatial-scene/audit.ts +617 -0
  100. package/src/spatial-scene/behavior-audit.ts +252 -0
  101. package/src/spatial-scene/behavior-bake.ts +232 -0
  102. package/src/spatial-scene/behavior-fns.ts +585 -0
  103. package/src/spatial-scene/behavior-gallery.ts +108 -0
  104. package/src/spatial-scene/behavior-stdlib.ts +323 -0
  105. package/src/spatial-scene/behavior-trace.ts +325 -0
  106. package/src/spatial-scene/behavior.ts +617 -0
  107. package/src/spatial-scene/build.ts +632 -0
  108. package/src/spatial-scene/camera-rig.ts +125 -0
  109. package/src/spatial-scene/camera-track.ts +4 -2
  110. package/src/spatial-scene/character.ts +124 -0
  111. package/src/spatial-scene/contracts.ts +75 -7
  112. package/src/spatial-scene/design-templates.ts +351 -0
  113. package/src/spatial-scene/design.ts +327 -0
  114. package/src/spatial-scene/direction-compile.ts +584 -0
  115. package/src/spatial-scene/direction.ts +122 -0
  116. package/src/spatial-scene/effects.ts +149 -0
  117. package/src/spatial-scene/evaluate.ts +109 -29
  118. package/src/spatial-scene/gallery.ts +277 -0
  119. package/src/spatial-scene/generate.ts +239 -0
  120. package/src/spatial-scene/geometry-native.ts +165 -0
  121. package/src/spatial-scene/geometry.ts +1216 -0
  122. package/src/spatial-scene/gltf.ts +600 -77
  123. package/src/spatial-scene/identity.ts +55 -2
  124. package/src/spatial-scene/index.ts +36 -0
  125. package/src/spatial-scene/inspect.ts +35 -6
  126. package/src/spatial-scene/material-lighting.ts +609 -0
  127. package/src/spatial-scene/math.ts +21 -4
  128. package/src/spatial-scene/motion-evidence.ts +88 -0
  129. package/src/spatial-scene/parametric.ts +765 -0
  130. package/src/spatial-scene/particle-preparation.ts +183 -0
  131. package/src/spatial-scene/particle.ts +195 -0
  132. package/src/spatial-scene/patch.ts +109 -15
  133. package/src/spatial-scene/performance.ts +1613 -0
  134. package/src/spatial-scene/probe.ts +137 -0
  135. package/src/spatial-scene/recipe-pack.ts +86 -0
  136. package/src/spatial-scene/render-effects.ts +240 -0
  137. package/src/spatial-scene/review.ts +639 -0
  138. package/src/spatial-scene/simulation-bake.ts +206 -0
  139. package/src/spatial-scene/simulation.ts +187 -0
  140. package/src/spatial-scene/solve.ts +366 -0
  141. package/src/spatial-scene/temporal-audit.ts +354 -0
  142. package/src/version.ts +1 -1
  143. package/dist/index-42zsesc1.js +0 -2257
  144. package/dist/index-7131pg1c.js +0 -1820
  145. package/dist/index-7308egqr.js +0 -48
  146. package/dist/index-8txs6fkn.js +0 -712
  147. package/dist/index-fava6pge.js +0 -192
  148. package/dist/index-h1k0fnjq.js +0 -2363
  149. package/dist/index-r7gdhmsp.js +0 -424
  150. package/dist/index-sh6xbav6.js +0 -1219
  151. package/dist/index-z1w83f81.js +0 -4
  152. package/dist/index-zfnddgay.js +0 -2029
  153. package/dist/skill-install-dh0ntqba.js +0 -10
@@ -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, SpatialBehaviorBakeSchema, 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,169 @@ 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 = parseSpatialValue(SpatialBehaviorBakeSchema, await readSpatialJson(resolve(application.paths.repositoryRoot, command.bake)), "behavior bake");
94
+ const report = auditSpatialBehaviorTrace({
95
+ behaviorSha256: bake.behaviorSha256, emittedSha256: bake.receipt.emittedSha256,
96
+ emitted: bake.emitted, rangeUs: bake.rangeUs,
97
+ });
98
+ if (command.output === undefined) return report;
99
+ const output = resolve(application.paths.repositoryRoot, command.output);
100
+ await publishSpatialSource(output, report);
101
+ return { path: output, findings: report.findings.length, channels: report.channelCount, emitted: report.emittedCount };
102
+ }
58
103
  if (command.action === "camera-track") assertCameraTrackActive(signal);
59
- const sourcePath = resolve(application.paths.repositoryRoot, command.path);
104
+ const sourcePath = resolve(application.paths.repositoryRoot, "path" in command ? command.path : command.scene);
60
105
  if (command.action === "init") {
61
106
  const scene = createSpatialSceneStarter();
62
107
  await publishSpatialSource(sourcePath, scene);
63
108
  return { path: sourcePath, sceneSha256: spatialSceneSha256(scene) };
64
109
  }
110
+ if (command.action === "review") {
111
+ const acknowledgedAt = application.clock.now().toISOString();
112
+ const authorizedApplication: ApplicationContext = {
113
+ ...application,
114
+ spatialReviewAuthorization: {
115
+ authorize: async request => (typeof request.sceneSha256 === "string" && /^[a-f0-9]{64}$/u.test(request.sceneSha256)
116
+ && SpatialCameraIdSchema.safeParse(request.cameraId).success
117
+ && Array.isArray(request.timesUs)
118
+ && request.timesUs.length >= 1
119
+ && request.timesUs.length <= SPATIAL_REVIEW_LIMITS.frames
120
+ && request.maximumFrames >= 1
121
+ && request.maximumFrames <= SPATIAL_REVIEW_LIMITS.frames
122
+ && request.maximumFrameBytes <= SPATIAL_REVIEW_LIMITS.pngBytes
123
+ && request.maximumUploadBytes <= SPATIAL_REVIEW_LIMITS.uploadBytes
124
+ ? { acknowledgedAt }
125
+ : undefined),
126
+ },
127
+ };
128
+ const result = await createApplicationOperationRegistry().execute({ application: authorizedApplication, abortSignal: signal ?? new AbortController().signal }, {
129
+ kind: "scene.review", version: 1,
130
+ input: {
131
+ source: { path: relative(application.paths.repositoryRoot, sourcePath) },
132
+ request: { cameraId: command.camera, ...(command.timesUs === undefined ? {} : { timesUs: [...command.timesUs] }) },
133
+ },
134
+ });
135
+ return result.output;
136
+ }
137
+ if (command.action === "render-audit") {
138
+ const result = await createApplicationOperationRegistry().execute({ application, abortSignal: signal ?? new AbortController().signal }, {
139
+ kind: "scene.render-audit", version: 1,
140
+ input: {
141
+ source: { path: relative(application.paths.repositoryRoot, sourcePath) },
142
+ request: { cameraId: command.camera, ...(command.timesUs === undefined ? {} : { timesUs: [...command.timesUs] }) },
143
+ },
144
+ });
145
+ return result.output;
146
+ }
65
147
  const scene = parseSpatialScene(await readSpatialJson(sourcePath));
148
+ if (command.action === "diff") {
149
+ const other = parseSpatialScene(await readSpatialJson(resolve(application.paths.repositoryRoot, command.other)));
150
+ return { sceneSha256: spatialSceneSha256(scene), otherSha256: spatialSceneSha256(other), diff: diffSpatialScenes(scene, other) };
151
+ }
152
+ if (command.action === "solve") {
153
+ const goalsFile = parseSpatialValue(SpatialSolveGoalsFileSchema, await readSpatialJson(resolve(application.paths.repositoryRoot, command.goals)), "solve goals");
154
+ const assetBounds = command.assetBounds === undefined
155
+ ? {}
156
+ : normalizeSpatialAuditAssetBounds(await readSpatialJson(resolve(application.paths.repositoryRoot, command.assetBounds)));
157
+ const entities = new Map(scene.entities.map(entity => [entity.entityId, entity]));
158
+ const cameras = new Map(scene.cameras.map(camera => [camera.cameraId, camera]));
159
+ const transformOverrides = new Map(
160
+ scene.overrides.filter(override => override.property === "transform").map(override => [override.entityId, override.value]),
161
+ );
162
+ const animatedTransforms = new Map<string, string[]>();
163
+ for (const channel of scene.animations) {
164
+ if (channel.property === "position" || channel.property === "rotation" || channel.property === "scale") {
165
+ animatedTransforms.set(channel.targetId, [...(animatedTransforms.get(channel.targetId) ?? []), channel.channelId]);
166
+ }
167
+ }
168
+ const referenced = new Set<string>();
169
+ for (const goal of goalsFile.goals) {
170
+ referenced.add(goal.entityKey);
171
+ for (const relation of goal.relations) if ("target" in relation) referenced.add(relation.target);
172
+ }
173
+ for (const goal of goalsFile.goals) {
174
+ const entity = entities.get(goal.entityKey);
175
+ if (entity === undefined) throw new CliError("not-found", `Solve goal ${goal.entityKey} is not an entity in the scene.`);
176
+ if (entity.origin.kind === "generated") {
177
+ throw new CliError("conflict", `Goal entity ${entity.entityId} is generated; edit it through declared overrides or retained output replacement.`);
178
+ }
179
+ if (entity.parentId !== null) {
180
+ 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.`);
181
+ }
182
+ if (entity.placement.kind !== "world") {
183
+ throw new CliError("conflict", `Goal entity ${entity.entityId} uses view placement; solver transforms are world-space.`);
184
+ }
185
+ if (transformOverrides.has(entity.entityId)) {
186
+ throw new CliError("conflict", `Goal entity ${entity.entityId} has a transform override; a set-transform patch would stay shadowed.`);
187
+ }
188
+ const writers = animatedTransforms.get(entity.entityId);
189
+ if (writers !== undefined) {
190
+ throw new CliError("conflict", `Goal entity ${entity.entityId} is animated (${writers.join(", ")}); a solved transform would not take effect.`);
191
+ }
192
+ }
193
+ const bases: Record<string, unknown> = {};
194
+ for (const key of [...referenced].sort()) {
195
+ const entity = entities.get(key);
196
+ if (entity !== undefined) {
197
+ // Local bounds are transform-agnostic, so they seed whenever derivable;
198
+ // the authored transform only seeds a base when it is the world transform.
199
+ const explicit = goalsFile.bases?.[key]?.transform !== undefined;
200
+ if (!explicit && (entity.parentId !== null || entity.placement.kind !== "world")) {
201
+ 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.`);
202
+ }
203
+ const enclosure = spatialEntityLocalBounds(entity, assetBounds);
204
+ bases[key] = {
205
+ ...(explicit ? {} : { transform: transformOverrides.get(key) ?? entity.transform }),
206
+ ...(enclosure.status === "bounded" ? { bounds: enclosure.bounds } : {}),
207
+ };
208
+ continue;
209
+ }
210
+ const camera = cameras.get(key);
211
+ if (camera !== undefined) {
212
+ bases[key] = { transform: { position: camera.pose.position, rotation: camera.pose.rotation, scale: [1, 1, 1] } };
213
+ }
214
+ }
215
+ for (const [key, basePatch] of Object.entries(goalsFile.bases ?? {})) {
216
+ const existing = bases[key] as { transform?: unknown; bounds?: unknown } | undefined;
217
+ bases[key] = {
218
+ ...(existing ?? {}),
219
+ ...(basePatch.transform === undefined ? {} : { transform: basePatch.transform }),
220
+ ...(basePatch.bounds === undefined ? {} : { bounds: basePatch.bounds }),
221
+ };
222
+ }
223
+ let result: SpatialSolveResult;
224
+ try {
225
+ result = solveSpatialRelations({ goals: goalsFile.goals, bases });
226
+ } catch (error) {
227
+ if (error instanceof SpatialSolveError) {
228
+ throw new CliError(SOLVE_ERROR_CODES[error.code] ?? "invalid-data", `[${error.code}] ${error.message}`);
229
+ }
230
+ throw error;
231
+ }
232
+ if (command.output === undefined) return result.patch;
233
+ const output = resolve(application.paths.repositoryRoot, command.output);
234
+ await publishSpatialSource(output, result.patch);
235
+ return { path: output, sceneSha256: spatialSceneSha256(scene), solvedEntities: Object.keys(result.transforms) };
236
+ }
66
237
  if (command.action === "camera-track") {
67
238
  const fence = async () => {
68
239
  assertCameraTrackActive(signal);
@@ -78,6 +249,91 @@ export async function executeSpatialSceneCommand(application: ApplicationContext
78
249
  await fence();
79
250
  return { path: output, sceneSha256: track.sceneSha256, cameraId: track.cameraId, clock: track.clock, executed: false };
80
251
  }
252
+ if (command.action === "direction-check") {
253
+ const direction = await readSpatialJson(resolve(application.paths.repositoryRoot, command.direction));
254
+ return checkSpatialDirection({ direction, scene });
255
+ }
256
+ if (command.action === "direction-plan") {
257
+ const direction = await readSpatialJson(resolve(application.paths.repositoryRoot, command.direction));
258
+ const compilation = compileSpatialDirection({ direction, scene, ...(command.camera === undefined ? {} : { cameraId: command.camera }) });
259
+ if (command.output === undefined) return compilation;
260
+ const output = resolve(application.paths.repositoryRoot, command.output);
261
+ await publishSpatialSource(output, compilation);
262
+ return { path: output, compilationSha256: spatialDirectionCompilationSha256(compilation), proposals: compilation.proposals, advisories: compilation.advisories };
263
+ }
264
+ if (command.action === "direction-gallery") {
265
+ const direction = await readSpatialJson(resolve(application.paths.repositoryRoot, command.direction));
266
+ const plan = planSpatialDirectionGallery({ axis: command.axis, direction, scene, ...(command.camera === undefined ? {} : { cameraId: command.camera }) });
267
+ if (command.output === undefined) return plan;
268
+ const output = resolve(application.paths.repositoryRoot, command.output);
269
+ await publishSpatialSource(output, plan);
270
+ return { path: output, galleryPlanSha256: spatialGalleryPlanSha256(plan), candidates: plan.candidates.map(candidate => ({ candidateId: candidate.candidateId, documentSha256: candidate.documentSha256 })) };
271
+ }
272
+ if (command.action === "effects-check") {
273
+ const effects = await readSpatialJson(resolve(application.paths.repositoryRoot, command.effects));
274
+ return checkSpatialRenderEffects({ effects, scene });
275
+ }
276
+ if (command.action === "effects-plan") {
277
+ const draft = await readSpatialJson(resolve(application.paths.repositoryRoot, command.draft));
278
+ 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.");
279
+ const binding = planSpatialRenderEffects({ ...draft, scene });
280
+ if (command.output === undefined) return binding;
281
+ const output = resolve(application.paths.repositoryRoot, command.output);
282
+ await publishSpatialSource(output, binding);
283
+ return { path: output, documentSha256: binding.documentSha256 };
284
+ }
285
+ if (command.action === "effects-bake") {
286
+ const plan = await readSpatialJson(resolve(application.paths.repositoryRoot, command.simulation));
287
+ const result = bakeSpatialSimulation(plan);
288
+ const output = resolve(application.paths.repositoryRoot, command.output);
289
+ await publishSpatialSource(output, result.document);
290
+ return { path: output, documentSha256: spatialSimulationBakeSha256(result.document), receipt: result.receipt };
291
+ }
292
+ if (command.action === "behavior-check") {
293
+ const behavior = await readSpatialJson(resolve(application.paths.repositoryRoot, command.behavior));
294
+ return checkSpatialBehavior({ behavior, scene }, spatialBehaviorFnSignatures());
295
+ }
296
+ if (command.action === "behavior-bake" || command.action === "behavior-gallery") {
297
+ const behavior = await readSpatialJson(resolve(application.paths.repositoryRoot, command.behavior));
298
+ const channelMap = command.channelMap === undefined
299
+ ? undefined
300
+ : parseSpatialBehaviorChannelMap(await readSpatialJson(resolve(application.paths.repositoryRoot, command.channelMap)));
301
+ if (command.action === "behavior-bake") {
302
+ const { bake } = await bakeSpatialBehavior({ behavior, scene, ...(channelMap === undefined ? {} : { channelMap }) });
303
+ const output = resolve(application.paths.repositoryRoot, command.output);
304
+ await publishSpatialSource(output, bake);
305
+ return {
306
+ path: output,
307
+ bakeSha256: spatialBehaviorBakeSha256(bake),
308
+ emitted: bake.emitted.length,
309
+ directives: bake.directives.length,
310
+ unresolvedIntents: bake.unresolvedIntents.length,
311
+ receipt: bake.receipt,
312
+ };
313
+ }
314
+ const plan = await planSpatialBehaviorGallery({ behavior, scene, ...(channelMap === undefined ? {} : { channelMap }) });
315
+ if (command.output === undefined) return plan;
316
+ const output = resolve(application.paths.repositoryRoot, command.output);
317
+ await publishSpatialSource(output, plan);
318
+ return { path: output, galleryPlanSha256: spatialBehaviorGalleryPlanSha256(plan), candidates: plan.candidates.map(candidate => ({ candidateId: candidate.candidateId, documentSha256: candidate.documentSha256, parameter: candidate.parameter })) };
319
+ }
320
+ if (command.action === "temporal-audit") {
321
+ const times = command.timesUs ?? (() => {
322
+ const count = Math.min(17, Math.max(2, Math.ceil(scene.durationUs / 1_000_000) + 1));
323
+ return Array.from({ length: count }, (_, index) => Math.floor((scene.durationUs * index) / (count - 1)));
324
+ })();
325
+ if (times.length < 2) throw new CliError("invalid-data", "scene temporal-audit requires at least two sample times.");
326
+ const contacts = command.contacts === undefined
327
+ ? []
328
+ : parseSpatialValue(SpatialTemporalContactsFileSchema, await readSpatialJson(resolve(application.paths.repositoryRoot, command.contacts)), "temporal contacts").contacts;
329
+ const context = createSpatialEvaluationContext(scene);
330
+ const snapshots = [...times].sort((left, right) => left - right).map(timeUs => evaluateSpatialSceneInContext(context, { cameraId: command.camera, timeUs }));
331
+ const report = auditSpatialTemporalEvidence({
332
+ snapshots,
333
+ options: { contacts, ...(command.cutBeforeUs === undefined ? {} : { cutBeforeUs: [...command.cutBeforeUs] }) },
334
+ });
335
+ return { ...report, reportSha256: spatialTemporalAuditReportSha256(report) };
336
+ }
81
337
  if (command.action === "plan" || command.action === "render") {
82
338
  const request = bindSpatialCliExecutionProfile(await readSpatialJson(resolve(application.paths.repositoryRoot, command.request)), command.executionProfile);
83
339
  if (command.action === "plan") return planSpatialRender(scene, request);
@@ -90,6 +346,17 @@ export async function executeSpatialSceneCommand(application: ApplicationContext
90
346
  return result.output;
91
347
  }
92
348
  const registry = createApplicationOperationRegistry();
349
+ if (command.action === "audit") {
350
+ const result = await registry.execute({ application, abortSignal: new AbortController().signal }, {
351
+ kind: "scene.audit", version: 1,
352
+ input: {
353
+ scene, cameraId: command.camera,
354
+ ...(command.timesUs === undefined ? {} : { timesUs: [...command.timesUs] }),
355
+ ...(command.assetBounds === undefined ? {} : { assetBounds: normalizeSpatialAuditAssetBounds(await readSpatialJson(resolve(application.paths.repositoryRoot, command.assetBounds))) }),
356
+ },
357
+ });
358
+ return result.output;
359
+ }
93
360
  const input = command.action === "patch"
94
361
  ? { scene, patch: await readSpatialJson(resolve(application.paths.repositoryRoot, command.patch)) }
95
362
  : 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;
@@ -4,6 +4,12 @@ import type {
4
4
  CandidateProjectRenderInput,
5
5
  SpatialInspectInput, SpatialInspectOutput, SpatialPatchInput, SpatialPatchOutput,
6
6
  SpatialEvaluateInput, SpatialEvaluateOutput,
7
+ SpatialDirectionCheckInput, SpatialDirectionCheckOutput,
8
+ SpatialDirectionCompileInput, SpatialDirectionCompileOutput,
9
+ SpatialDirectionGalleryInput, SpatialDirectionGalleryOutput,
10
+ SpatialEffectsCheckInput, SpatialEffectsCheckOutput,
11
+ SpatialEffectsPlanInput, SpatialEffectsPlanOutput,
12
+ SpatialTemporalAuditInput, SpatialTemporalAuditOutput,
7
13
  BindCandidateRevisionOutput,
8
14
  CommitProjectEditsOutput,
9
15
  CreateCandidateRevisionOutput,
@@ -1036,6 +1042,24 @@ export class WorkflowBuilder {
1036
1042
  this.#graph.operationByKind(key, { input, kind: "scene.patch", version: 1 }, options),
1037
1043
  evaluate: (key: string, input: OperationInputValue<SpatialEvaluateInput>, options?: OperationNodeOptions): Ref<SpatialEvaluateOutput> =>
1038
1044
  this.#graph.operationByKind(key, { input, kind: "scene.evaluate", version: 1 }, options),
1045
+ /**
1046
+ * Pure semantic planning and review operations: direction checking and
1047
+ * compilation, bounded galleries, render-effects binding, and sampled
1048
+ * temporal audit. They propose and review documents; they never mutate a
1049
+ * project, render media, or select a candidate.
1050
+ */
1051
+ directionCheck: (key: string, input: OperationInputValue<SpatialDirectionCheckInput> | SpatialDirectionCheckInput, options?: OperationNodeOptions): Ref<SpatialDirectionCheckOutput> =>
1052
+ this.#graph.operationByKind(key, { input, kind: "scene.direction.check", version: 1 }, options),
1053
+ directionCompile: (key: string, input: OperationInputValue<SpatialDirectionCompileInput> | SpatialDirectionCompileInput, options?: OperationNodeOptions): Ref<SpatialDirectionCompileOutput> =>
1054
+ this.#graph.operationByKind(key, { input, kind: "scene.direction.compile", version: 1 }, options),
1055
+ directionGallery: (key: string, input: OperationInputValue<SpatialDirectionGalleryInput> | SpatialDirectionGalleryInput, options?: OperationNodeOptions): Ref<SpatialDirectionGalleryOutput> =>
1056
+ this.#graph.operationByKind(key, { input, kind: "scene.direction.gallery", version: 1 }, options),
1057
+ effectsCheck: (key: string, input: OperationInputValue<SpatialEffectsCheckInput> | SpatialEffectsCheckInput, options?: OperationNodeOptions): Ref<SpatialEffectsCheckOutput> =>
1058
+ this.#graph.operationByKind(key, { input, kind: "scene.effects.check", version: 1 }, options),
1059
+ effectsPlan: (key: string, input: OperationInputValue<SpatialEffectsPlanInput> | SpatialEffectsPlanInput, options?: OperationNodeOptions): Ref<SpatialEffectsPlanOutput> =>
1060
+ this.#graph.operationByKind(key, { input, kind: "scene.effects.plan", version: 1 }, options),
1061
+ temporalAudit: (key: string, input: OperationInputValue<SpatialTemporalAuditInput> | SpatialTemporalAuditInput, options?: OperationNodeOptions): Ref<SpatialTemporalAuditOutput> =>
1062
+ this.#graph.operationByKind(key, { input, kind: "scene.temporal-audit", version: 1 }, options),
1039
1063
  });
1040
1064
 
1041
1065
  readonly spatialProject = Object.freeze({