@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
@@ -0,0 +1,327 @@
1
+ import { z } from "zod"
2
+ import type { JsonValue } from "../code/contracts.js"
3
+ import { createBoundedJsonValueSnapshot, deepFreezeJson } from "../code/json-snapshot.js"
4
+ import { SPATIAL_SCENE_LIMITS, SpatialMaterialSchema, type SpatialSceneV1 } from "./contracts.js"
5
+ import { createSpatialGeneratorSceneShell } from "./generate.js"
6
+ import { applySpatialScenePatch } from "./patch.js"
7
+ import { parseSpatialScene, parseSpatialValue, SpatialSceneError, spatialTopologicalIds, spatialValueSha256 } from "./identity.js"
8
+ import {
9
+ emitSpatialParametric, estimateSpatialParametric, SpatialParametricSpecSchema,
10
+ type SpatialParametricEstimate, type SpatialParametricOutput, type SpatialParametricSpec,
11
+ } from "./parametric.js"
12
+
13
+ /** A portable design is inert data: scalar dependencies feed retained geometry stages. */
14
+ export const SPATIAL_DESIGN_LIMITS = Object.freeze({
15
+ sourceBytes: 1_048_576, sourceDepth: 32, sourceValues: 100_000,
16
+ parameters: 128, values: 256, constraints: 128, stages: 32, expressionNodes: 8_192,
17
+ expressionDepth: 16, parts: 64, assets: 128, vertices: 262_144, triangles: 400_000,
18
+ outputBytes: 67_108_864, scalarMagnitude: 1e12,
19
+ })
20
+ export const SPATIAL_DESIGN_COMPILER = "slopcamera.spatial-design-v1" as const
21
+
22
+ export type SpatialDesignScalarExpression = number | { readonly $param: string } | { readonly $value: string }
23
+ | { readonly op: "add" | "sub" | "mul" | "div" | "min" | "max" | "neg" | "abs" | "sin" | "cos" | "floor" | "ceil"; readonly args: readonly SpatialDesignScalarExpression[] }
24
+
25
+ const identifier = z.string().min(1).max(64).regex(/^[a-zA-Z][a-zA-Z0-9_-]*$/u)
26
+ const scalar = z.number().finite().min(-SPATIAL_DESIGN_LIMITS.scalarMagnitude).max(SPATIAL_DESIGN_LIMITS.scalarMagnitude)
27
+ const template = z.record(z.string(), z.json())
28
+ const parameterSchema = z.strictObject({
29
+ name: identifier, label: z.string().min(1).max(160).optional(), value: scalar, min: scalar, max: scalar,
30
+ step: z.number().finite().min(1e-9).max(SPATIAL_DESIGN_LIMITS.scalarMagnitude).optional(),
31
+ unit: z.enum(["m", "rad", "count", "ratio"]).optional(),
32
+ })
33
+ const expressionSchema: z.ZodType<SpatialDesignScalarExpression> = z.lazy(() => z.union([
34
+ scalar, z.strictObject({ $param: identifier }), z.strictObject({ $value: identifier }),
35
+ z.strictObject({ op: z.enum(["add", "sub", "mul", "div", "min", "max", "neg", "abs", "sin", "cos", "floor", "ceil"]), args: z.array(expressionSchema).min(1).max(16) }),
36
+ ]))
37
+ const stageBase = { stageId: identifier, name: z.string().min(1).max(160).optional(), seed: z.number().int().min(0).max(0xffff_ffff).optional() }
38
+ const stageSchema = z.discriminatedUnion("kind", [
39
+ z.strictObject({ ...stageBase, kind: z.literal("parametric"), spec: template }),
40
+ z.strictObject({ ...stageBase, kind: z.literal("geometry"), graph: template, materials: z.array(SpatialMaterialSchema).min(1).max(16),
41
+ transform: template.optional(), editable: z.array(z.enum(["color", "opacity", "transform"])).max(3).optional(),
42
+ castShadow: z.boolean().optional(), receiveShadow: z.boolean().optional() }),
43
+ ])
44
+ export const SpatialDesignV1Schema = z.strictObject({
45
+ kind: z.literal("slopcamera.spatial-design"), schemaVersion: z.literal(1), designId: identifier,
46
+ name: z.string().min(1).max(160).optional(),
47
+ parameters: z.array(parameterSchema).max(SPATIAL_DESIGN_LIMITS.parameters),
48
+ values: z.array(z.strictObject({ name: identifier, expression: expressionSchema })).max(SPATIAL_DESIGN_LIMITS.values).optional(),
49
+ constraints: z.array(z.strictObject({ name: identifier, left: expressionSchema, operator: z.enum(["lt", "lte", "eq", "gte", "gt"]), right: expressionSchema,
50
+ message: z.string().min(1).max(512).optional() })).max(SPATIAL_DESIGN_LIMITS.constraints).optional(),
51
+ stages: z.array(stageSchema).min(1).max(SPATIAL_DESIGN_LIMITS.stages),
52
+ })
53
+ export type SpatialDesignV1 = Readonly<z.infer<typeof SpatialDesignV1Schema>>
54
+ export type SpatialDesignParameter = SpatialDesignV1["parameters"][number]
55
+ export type SpatialDesignStage = SpatialDesignV1["stages"][number]
56
+ export interface SpatialDesignDependencies { readonly parameters: readonly string[]; readonly values: readonly string[] }
57
+ export interface SpatialDesignResolvedStage {
58
+ readonly stageId: string
59
+ readonly name?: string
60
+ readonly kind: SpatialDesignStage["kind"]
61
+ readonly generatorId: string
62
+ readonly spec: SpatialParametricSpec
63
+ readonly seed?: number
64
+ readonly dependencies: SpatialDesignDependencies
65
+ readonly estimate: SpatialParametricEstimate
66
+ }
67
+ export interface SpatialDesignInspection {
68
+ readonly design: SpatialDesignV1
69
+ readonly designSha256: string
70
+ readonly parameters: Readonly<Record<string, number>>
71
+ readonly values: readonly { readonly name: string; readonly value: number; readonly dependencies: SpatialDesignDependencies }[]
72
+ readonly valueOrder: readonly string[]
73
+ readonly constraints: readonly { readonly name: string; readonly left: number; readonly operator: string; readonly right: number; readonly passed: boolean }[]
74
+ readonly stages: readonly SpatialDesignResolvedStage[]
75
+ readonly estimate: SpatialParametricEstimate
76
+ }
77
+ export interface SpatialDesignReceipt {
78
+ readonly kind: "slopcamera.spatial-design-receipt"
79
+ readonly schemaVersion: 1
80
+ readonly compiler: typeof SPATIAL_DESIGN_COMPILER
81
+ readonly designId: string
82
+ readonly designSha256: string
83
+ readonly parametersSha256: string
84
+ readonly inputSceneSha256: string | null
85
+ readonly sceneSha256: string
86
+ readonly parameters: Readonly<Record<string, number>>
87
+ readonly stages: readonly { readonly stageId: string; readonly generatorId: string; readonly specSha256: string; readonly receiptSha256: string; readonly entities: readonly string[]; readonly dependencies: SpatialDesignDependencies; readonly changed: boolean }[]
88
+ readonly estimate: SpatialParametricEstimate
89
+ readonly artifactBytes: number
90
+ }
91
+ export interface SpatialDesignCompilation {
92
+ readonly design: SpatialDesignV1
93
+ readonly scene: SpatialSceneV1
94
+ readonly outputs: readonly SpatialParametricOutput[]
95
+ readonly receipt: SpatialDesignReceipt
96
+ readonly receiptSha256: string
97
+ }
98
+
99
+ function fail(message: string, path = "design"): never { throw new SpatialSceneError("invalid-data", message, path) }
100
+ function compare(a: string, b: string): number { return a < b ? -1 : a > b ? 1 : 0 }
101
+ function byName<T extends { name: string }>(items: readonly T[]): T[] { return [...items].sort((a, b) => compare(a.name, b.name)) }
102
+ function unique<T>(items: readonly T[], key: (item: T) => string, path: string): Map<string, T> {
103
+ const result = new Map<string, T>()
104
+ for (const item of items) {
105
+ const name = key(item)
106
+ if (result.has(name)) fail(`Duplicate identity ${name}.`, path)
107
+ result.set(name, item)
108
+ }
109
+ return result
110
+ }
111
+ function capture(input: unknown, path: string): JsonValue {
112
+ try {
113
+ return createBoundedJsonValueSnapshot(input, SPATIAL_DESIGN_LIMITS.sourceBytes, path, {
114
+ maximumDepth: SPATIAL_DESIGN_LIMITS.sourceDepth, maximumValues: SPATIAL_DESIGN_LIMITS.sourceValues,
115
+ }).value
116
+ } catch (error) { return fail(error instanceof Error ? error.message : "Invalid design data.", path) }
117
+ }
118
+ function readDesign(input: unknown): SpatialDesignV1 {
119
+ const parsed = parseSpatialValue(SpatialDesignV1Schema, capture(input, "design"), "design")
120
+ return deepFreezeJson({ ...parsed, parameters: byName(parsed.parameters),
121
+ ...(parsed.values === undefined ? {} : { values: byName(parsed.values) }),
122
+ ...(parsed.constraints === undefined ? {} : { constraints: byName(parsed.constraints) }),
123
+ stages: [...parsed.stages].sort((a, b) => compare(a.stageId, b.stageId)) })
124
+ }
125
+ function validateParameter(parameter: SpatialDesignParameter, value: number): void {
126
+ const path = `design.parameters.${parameter.name}`
127
+ if (parameter.min > parameter.max) fail("Parameter min must not exceed max.", path)
128
+ if (value < parameter.min || value > parameter.max) fail(`Parameter ${parameter.name} must be between ${parameter.min} and ${parameter.max}.`, path)
129
+ if (parameter.unit === "count" && (!Number.isSafeInteger(value) || !Number.isSafeInteger(parameter.min) || !Number.isSafeInteger(parameter.max) || (parameter.step !== undefined && !Number.isSafeInteger(parameter.step)))) {
130
+ fail(`Count parameter ${parameter.name} requires integer value, bounds and step.`, path)
131
+ }
132
+ if (parameter.step !== undefined) {
133
+ const steps = (value - parameter.min) / parameter.step
134
+ if (!Number.isFinite(steps) || Math.abs(steps - Math.round(steps)) > 1e-7) fail(`Parameter ${parameter.name} must follow step ${parameter.step} from min ${parameter.min}.`, path)
135
+ }
136
+ }
137
+ function resolveParameters(design: SpatialDesignV1, input?: unknown): Readonly<Record<string, number>> {
138
+ const parameters = unique(design.parameters, item => item.name, "design.parameters")
139
+ for (const parameter of parameters.values()) validateParameter(parameter, parameter.value)
140
+ const updates = input === undefined ? {} : parseSpatialValue(z.record(identifier, scalar), capture(input, "parameter updates"), "parameter updates")
141
+ for (const name of Object.keys(updates)) {
142
+ const parameter = parameters.get(name)
143
+ if (parameter === undefined) fail(`Unknown parameter ${name}.`, "parameter updates")
144
+ validateParameter(parameter, updates[name]!)
145
+ }
146
+ return Object.fromEntries([...parameters.values()].map(parameter => [parameter.name, Object.hasOwn(updates, parameter.name) ? updates[parameter.name]! : parameter.value]))
147
+ }
148
+ interface MutableDependencies { parameters: Set<string>; values: Set<string> }
149
+ function emptyDependencies(): MutableDependencies { return { parameters: new Set(), values: new Set() } }
150
+ function dependencies(value: MutableDependencies): SpatialDesignDependencies {
151
+ return { parameters: [...value.parameters].sort(compare), values: [...value.values].sort(compare) }
152
+ }
153
+ function include(target: MutableDependencies, source: SpatialDesignDependencies): void {
154
+ for (const name of source.parameters) target.parameters.add(name)
155
+ for (const name of source.values) target.values.add(name)
156
+ }
157
+ interface ExpressionContext {
158
+ parameters: Readonly<Record<string, number>>
159
+ values: ReadonlyMap<string, { value: number; dependencies: SpatialDesignDependencies }>
160
+ budget: { nodes: number }
161
+ }
162
+ function checkedNumber(value: number, path: string): number {
163
+ if (!Number.isFinite(value) || Math.abs(value) > SPATIAL_DESIGN_LIMITS.scalarMagnitude) fail("Scalar arithmetic exceeded the finite magnitude bound.", path)
164
+ return Object.is(value, -0) ? 0 : value
165
+ }
166
+ function evaluateExpression(expression: SpatialDesignScalarExpression, context: ExpressionContext, refs: MutableDependencies, path: string, depth = 0): number {
167
+ if (++context.budget.nodes > SPATIAL_DESIGN_LIMITS.expressionNodes || depth > SPATIAL_DESIGN_LIMITS.expressionDepth) fail("Design expression budget exceeded.", path)
168
+ if (typeof expression === "number") return checkedNumber(expression, path)
169
+ if ("$param" in expression) {
170
+ if (!Object.hasOwn(context.parameters, expression.$param)) fail(`Missing parameter ${expression.$param}.`, path)
171
+ refs.parameters.add(expression.$param)
172
+ return context.parameters[expression.$param]!
173
+ }
174
+ if ("$value" in expression) {
175
+ const value = context.values.get(expression.$value)
176
+ if (value === undefined) fail(`Missing value ${expression.$value}.`, path)
177
+ refs.values.add(expression.$value); include(refs, value.dependencies)
178
+ return value.value
179
+ }
180
+ const { op, args } = expression
181
+ const unary = ["neg", "abs", "sin", "cos", "floor", "ceil"].includes(op)
182
+ if (args.length !== (unary ? 1 : 2) && op !== "min" && op !== "max") fail(`Operation ${op} requires ${unary ? 1 : 2} arguments.`, path)
183
+ if ((op === "min" || op === "max") && args.length < 2) fail(`Operation ${op} requires at least two arguments.`, path)
184
+ const operands = args.map(arg => evaluateExpression(arg, context, refs, path, depth + 1))
185
+ const a = operands[0]!, b = operands[1]!
186
+ let result: number
187
+ switch (op) {
188
+ case "add": result = a + b; break
189
+ case "sub": result = a - b; break
190
+ case "mul": result = a * b; break
191
+ case "div": if (b === 0) fail("Division by zero.", path); result = a / b; break
192
+ case "min": result = Math.min(...operands); break
193
+ case "max": result = Math.max(...operands); break
194
+ case "neg": result = -a; break
195
+ case "abs": result = Math.abs(a); break
196
+ case "sin": result = Math.sin(a); break
197
+ case "cos": result = Math.cos(a); break
198
+ case "floor": result = Math.floor(a); break
199
+ case "ceil": result = Math.ceil(a); break
200
+ }
201
+ return checkedNumber(result, path)
202
+ }
203
+ function valueReferences(expression: SpatialDesignScalarExpression, depth = 0): string[] {
204
+ if (depth > SPATIAL_DESIGN_LIMITS.expressionDepth) fail("Design expression depth exceeded.", "design.values")
205
+ if (typeof expression === "number" || "$param" in expression) return []
206
+ if ("$value" in expression) return [expression.$value]
207
+ return expression.args.flatMap(arg => valueReferences(arg, depth + 1))
208
+ }
209
+ function resolveTemplate(value: JsonValue, context: ExpressionContext, refs: MutableDependencies, path: string): JsonValue {
210
+ if (value === null || typeof value !== "object") return value
211
+ if (Array.isArray(value)) return value.map((item, index) => resolveTemplate(item, context, refs, `${path}.${index}`))
212
+ const record = value as Readonly<Record<string, JsonValue>>
213
+ if (Object.keys(record).some(key => key.startsWith("$"))) {
214
+ const sentinel = Object.hasOwn(record, "$expr")
215
+ ? parseSpatialValue(z.strictObject({ $expr: expressionSchema }), record, path).$expr
216
+ : parseSpatialValue(expressionSchema, record, path)
217
+ return evaluateExpression(sentinel, context, refs, path)
218
+ }
219
+ return Object.fromEntries(Object.entries(record).map(([key, item]) => [key, resolveTemplate(item, context, refs, `${path}.${key}`)]))
220
+ }
221
+
222
+ function generatorPrefix(designId: string): string {
223
+ return `generator_design_${spatialValueSha256({ domain: "slopcamera.spatial-design-owner.v1", designId }).slice(0, 16)}_`
224
+ }
225
+
226
+ /** Stable stage ownership survives parameter changes, stage reordering and relabeling. */
227
+ export function spatialDesignGeneratorId(designId: string, stageId: string): string {
228
+ const ids = parseSpatialValue(z.strictObject({ designId: identifier, stageId: identifier }), { designId, stageId }, "design stage identity")
229
+ return `${generatorPrefix(ids.designId)}${spatialValueSha256({ domain: "slopcamera.spatial-design-stage.v1", stageId: ids.stageId }).slice(0, 32)}`
230
+ }
231
+
232
+ /** Resolves and checks dependencies and static budgets without evaluating a mesh. */
233
+ export function inspectSpatialDesign(input: unknown, options: { readonly parameters?: unknown } = {}): SpatialDesignInspection {
234
+ const design = readDesign(input)
235
+ const parameters = resolveParameters(design, options.parameters)
236
+ const definitions = unique(design.values ?? [], item => item.name, "design.values")
237
+ unique(design.constraints ?? [], item => item.name, "design.constraints")
238
+ unique(design.stages, item => item.stageId, "design.stages")
239
+ const order = spatialTopologicalIds(new Map([...definitions].map(([name, definition]) => [name, [...new Set(valueReferences(definition.expression))].sort(compare)])), "design.values")
240
+ const values = new Map<string, { value: number; dependencies: SpatialDesignDependencies }>()
241
+ const context: ExpressionContext = { parameters, values, budget: { nodes: 0 } }
242
+ for (const name of order) {
243
+ const refs = emptyDependencies()
244
+ const value = evaluateExpression(definitions.get(name)!.expression, context, refs, `design.values.${name}`)
245
+ values.set(name, { value, dependencies: dependencies(refs) })
246
+ }
247
+ const constraints = (design.constraints ?? []).map(constraint => {
248
+ const refs = emptyDependencies(), path = `design.constraints.${constraint.name}`
249
+ const left = evaluateExpression(constraint.left, context, refs, path), right = evaluateExpression(constraint.right, context, refs, path)
250
+ const passed = constraint.operator === "lt" ? left < right : constraint.operator === "lte" ? left <= right
251
+ : constraint.operator === "eq" ? left === right : constraint.operator === "gte" ? left >= right : left > right
252
+ if (!passed) fail(constraint.message ?? `Constraint ${constraint.name} failed: ${left} ${constraint.operator} ${right}.`, path)
253
+ return { name: constraint.name, left, operator: constraint.operator, right, passed }
254
+ })
255
+ const estimate = { parts: 0, assets: 0, vertices: 0, triangles: 0, bytes: 0 }
256
+ const stages = design.stages.map(stage => {
257
+ const refs = emptyDependencies()
258
+ const raw = stage.kind === "parametric" ? stage.spec : {
259
+ kind: "geometry", graph: stage.graph, materials: stage.materials,
260
+ ...(stage.transform === undefined ? {} : { transform: stage.transform }),
261
+ ...(stage.editable === undefined ? {} : { editable: stage.editable }),
262
+ ...(stage.castShadow === undefined ? {} : { castShadow: stage.castShadow }),
263
+ ...(stage.receiveShadow === undefined ? {} : { receiveShadow: stage.receiveShadow }),
264
+ }
265
+ const spec = parseSpatialValue(SpatialParametricSpecSchema, resolveTemplate(raw as JsonValue, context, refs, `design.stages.${stage.stageId}`), `design.stages.${stage.stageId}.spec`)
266
+ const stageEstimate = estimateSpatialParametric(spec)
267
+ for (const key of ["parts", "assets", "vertices", "triangles", "bytes"] as const) estimate[key] += stageEstimate[key]
268
+ if (estimate.parts > SPATIAL_DESIGN_LIMITS.parts || estimate.assets > SPATIAL_DESIGN_LIMITS.assets || estimate.vertices > SPATIAL_DESIGN_LIMITS.vertices
269
+ || estimate.triangles > SPATIAL_DESIGN_LIMITS.triangles || estimate.bytes > SPATIAL_DESIGN_LIMITS.outputBytes) fail("Design aggregate geometry budget exceeded before emission.", "design.stages")
270
+ return { stageId: stage.stageId, ...(stage.name === undefined ? {} : { name: stage.name }), kind: stage.kind,
271
+ generatorId: spatialDesignGeneratorId(design.designId, stage.stageId), spec,
272
+ ...(stage.seed === undefined ? {} : { seed: stage.seed }), dependencies: dependencies(refs), estimate: stageEstimate }
273
+ })
274
+ return deepFreezeJson({ design, designSha256: spatialValueSha256({ domain: "slopcamera.spatial-design.v1", design }), parameters,
275
+ values: [...values].map(([name, value]) => ({ name, ...value })), valueOrder: order, constraints, stages, estimate })
276
+ }
277
+
278
+ /** Full authoring validation, including parameter, dependency, constraint and static geometry checks. */
279
+ export function parseSpatialDesign(input: unknown): SpatialDesignV1 { return inspectSpatialDesign(input).design }
280
+
281
+ /** Atomically updates named defaults, validating the complete dependent design before returning. */
282
+ export function editSpatialDesignParameters(input: unknown, updates: unknown): SpatialDesignV1 {
283
+ const design = readDesign(input), parameters = resolveParameters(design, updates)
284
+ return parseSpatialDesign({ ...design, parameters: design.parameters.map(parameter => ({ ...parameter, value: parameters[parameter.name]! })) })
285
+ }
286
+
287
+ /** Emits all validated stages and merges them through existing retained-output/override rules. */
288
+ export function compileSpatialDesign(input: unknown, options: { readonly scene?: unknown; readonly parameters?: unknown } = {}): SpatialDesignCompilation {
289
+ const inspection = inspectSpatialDesign(options.parameters === undefined ? input : editSpatialDesignParameters(input, options.parameters))
290
+ const base = options.scene === undefined ? undefined : parseSpatialScene(options.scene)
291
+ const owned = new Set(inspection.stages.map(stage => stage.generatorId))
292
+ const stale = base?.generators.find(generator => generator.generatorId.startsWith(generatorPrefix(inspection.design.designId)) && !owned.has(generator.generatorId))
293
+ if (stale !== undefined) throw new SpatialSceneError("conflict", `Design removed a retained stage (${stale.generatorId}); explicitly remove its retained output and dependent overrides before recompiling.`, "design.stages")
294
+ const retiredAssets = new Set(base?.generators.filter(generator => owned.has(generator.generatorId)).flatMap(generator => generator.assets ?? []) ?? [])
295
+ const keptAssets = base?.assets.filter(asset => !retiredAssets.has(asset.assetId)).length ?? 0
296
+ const keptEntities = base?.entities.filter(entity => entity.origin.kind !== "generated" || !owned.has(entity.origin.generatorId)).length ?? 0
297
+ const keptGenerators = base?.generators.filter(generator => !owned.has(generator.generatorId)).length ?? 0
298
+ if (keptAssets + inspection.estimate.assets > SPATIAL_SCENE_LIMITS.assets || keptEntities + inspection.estimate.parts > SPATIAL_SCENE_LIMITS.entities || keptGenerators + owned.size > 128) {
299
+ fail("Design and retained scene exceed aggregate scene capacity before emission.", "scene")
300
+ }
301
+ const outputs = inspection.stages.map(stage => emitSpatialParametric({ kind: "slopcamera.spatial-parametric-request", schemaVersion: 1,
302
+ generatorId: stage.generatorId, spec: stage.spec, ...(stage.seed === undefined ? {} : { seed: stage.seed }),
303
+ ...(stage.name === undefined ? {} : { name: stage.name }) }))
304
+ const artifactBytes = outputs.reduce((total, output) => total + output.artifacts.reduce((sum, artifact) => sum + artifact.bytes.byteLength, 0), 0)
305
+ if (artifactBytes > SPATIAL_DESIGN_LIMITS.outputBytes) fail("Design emitted artifacts exceed the byte budget.", "design.stages")
306
+ const original = base ?? parseSpatialScene(createSpatialGeneratorSceneShell())
307
+ const scene = applySpatialScenePatch(original, { kind: "slopcamera.spatial-scene-patch", schemaVersion: 1,
308
+ expectedSceneSha256: spatialValueSha256(original),
309
+ operations: outputs.map(output => ({ kind: "replace-generator-output", generator: output.generator, entities: output.entities, assets: output.manifests })),
310
+ }).scene
311
+ const receipt: SpatialDesignReceipt = {
312
+ kind: "slopcamera.spatial-design-receipt", schemaVersion: 1, compiler: SPATIAL_DESIGN_COMPILER,
313
+ designId: inspection.design.designId, designSha256: inspection.designSha256,
314
+ parametersSha256: spatialValueSha256({ domain: "slopcamera.spatial-design-parameters.v1", parameters: inspection.parameters }),
315
+ inputSceneSha256: base === undefined ? null : spatialValueSha256(base), sceneSha256: spatialValueSha256(scene),
316
+ parameters: inspection.parameters, estimate: inspection.estimate, artifactBytes,
317
+ stages: inspection.stages.map((stage, index) => {
318
+ const output = outputs[index]!
319
+ const previous = base?.generators.find(generator => generator.generatorId === stage.generatorId)
320
+ return { stageId: stage.stageId, generatorId: stage.generatorId, specSha256: output.receipt.specSha256,
321
+ receiptSha256: output.receiptSha256, entities: output.receipt.entities, dependencies: stage.dependencies,
322
+ changed: previous === undefined || spatialValueSha256(previous) !== spatialValueSha256(scene.generators.find(generator => generator.generatorId === stage.generatorId)!) }
323
+ }),
324
+ }
325
+ const metadata = deepFreezeJson({ design: inspection.design, scene, receipt, receiptSha256: spatialValueSha256({ domain: "slopcamera.spatial-design-receipt.v1", receipt }) })
326
+ return Object.freeze({ ...metadata, outputs: Object.freeze(outputs) })
327
+ }