@hraness/slopcamera 3.2.8 → 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 (150) 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 +268 -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-cgpkyjqa.js +3 -0
  59. package/dist/index-f0ptv8dm.js +7 -0
  60. package/dist/index-grk47tgs.js +3 -0
  61. package/dist/index-qry58nj2.js +10 -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/package.json +7 -5
  69. package/skills/slopcamera/SKILL.md +6 -1
  70. package/skills/slopcamera/references/directed-scenes.md +22 -2
  71. package/skills/slopcamera/references/gateway-media.md +15 -1
  72. package/skills/slopcamera/references/image-galleries.md +166 -0
  73. package/skills/slopcamera/references/install.md +1 -1
  74. package/skills/slopcamera/references/parametric-design.md +14 -0
  75. package/skills/slopcamera/references/scene-building.md +32 -0
  76. package/skills/slopcamera/references/social-collage-banners.md +204 -0
  77. package/skills/slopcamera/references/workflows-sdk.md +3 -3
  78. package/skills/slopcamera/scripts/compose-social-collage-banner.ts +728 -0
  79. package/src/capability-manifest.ts +361 -0
  80. package/src/cli.ts +222 -4
  81. package/src/code/canonical-json.ts +9 -0
  82. package/src/generate.ts +2 -2
  83. package/src/icon.ts +1111 -0
  84. package/src/image-gallery.ts +1049 -0
  85. package/src/index.ts +11 -0
  86. package/src/mcp/boundary.ts +11 -11
  87. package/src/mcp/index.ts +3 -0
  88. package/src/mcp/server.ts +2 -1
  89. package/src/mcp/tools.ts +1642 -130
  90. package/src/mcp/types.ts +19 -0
  91. package/src/operations.ts +412 -0
  92. package/src/portable-capability-manifest.ts +67 -0
  93. package/src/scene-gallery.ts +184 -0
  94. package/src/spatial-scene/asset-admission.ts +183 -0
  95. package/src/spatial-scene/audit-rendered.ts +692 -0
  96. package/src/spatial-scene/audit.ts +617 -0
  97. package/src/spatial-scene/behavior-audit.ts +252 -0
  98. package/src/spatial-scene/behavior-bake.ts +233 -0
  99. package/src/spatial-scene/behavior-fns.ts +585 -0
  100. package/src/spatial-scene/behavior-gallery.ts +109 -0
  101. package/src/spatial-scene/behavior-stdlib.ts +323 -0
  102. package/src/spatial-scene/behavior-trace.ts +325 -0
  103. package/src/spatial-scene/behavior.ts +618 -0
  104. package/src/spatial-scene/build.ts +632 -0
  105. package/src/spatial-scene/camera-rig.ts +125 -0
  106. package/src/spatial-scene/camera-track.ts +4 -2
  107. package/src/spatial-scene/character.ts +124 -0
  108. package/src/spatial-scene/contracts.ts +75 -7
  109. package/src/spatial-scene/design-templates.ts +351 -0
  110. package/src/spatial-scene/design.ts +327 -0
  111. package/src/spatial-scene/direction-compile.ts +584 -0
  112. package/src/spatial-scene/direction.ts +122 -0
  113. package/src/spatial-scene/effects.ts +149 -0
  114. package/src/spatial-scene/evaluate.ts +109 -29
  115. package/src/spatial-scene/gallery.ts +277 -0
  116. package/src/spatial-scene/generate.ts +239 -0
  117. package/src/spatial-scene/geometry-native.ts +165 -0
  118. package/src/spatial-scene/geometry.ts +1216 -0
  119. package/src/spatial-scene/gltf.ts +600 -77
  120. package/src/spatial-scene/identity.ts +55 -2
  121. package/src/spatial-scene/index.ts +36 -0
  122. package/src/spatial-scene/inspect.ts +35 -6
  123. package/src/spatial-scene/material-lighting.ts +609 -0
  124. package/src/spatial-scene/math.ts +21 -4
  125. package/src/spatial-scene/motion-evidence.ts +88 -0
  126. package/src/spatial-scene/parametric.ts +765 -0
  127. package/src/spatial-scene/particle-preparation.ts +183 -0
  128. package/src/spatial-scene/particle.ts +195 -0
  129. package/src/spatial-scene/patch.ts +109 -15
  130. package/src/spatial-scene/performance.ts +1613 -0
  131. package/src/spatial-scene/probe.ts +137 -0
  132. package/src/spatial-scene/recipe-pack.ts +86 -0
  133. package/src/spatial-scene/render-effects.ts +240 -0
  134. package/src/spatial-scene/review.ts +639 -0
  135. package/src/spatial-scene/simulation-bake.ts +206 -0
  136. package/src/spatial-scene/simulation.ts +187 -0
  137. package/src/spatial-scene/solve.ts +366 -0
  138. package/src/spatial-scene/temporal-audit.ts +354 -0
  139. package/src/version.ts +1 -1
  140. package/dist/index-42zsesc1.js +0 -2257
  141. package/dist/index-7131pg1c.js +0 -1820
  142. package/dist/index-7308egqr.js +0 -48
  143. package/dist/index-8txs6fkn.js +0 -712
  144. package/dist/index-fava6pge.js +0 -192
  145. package/dist/index-h1k0fnjq.js +0 -2363
  146. package/dist/index-r7gdhmsp.js +0 -424
  147. package/dist/index-sh6xbav6.js +0 -1219
  148. package/dist/index-z1w83f81.js +0 -4
  149. package/dist/index-zfnddgay.js +0 -2029
  150. 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, 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;
@@ -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({