@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
@@ -0,0 +1,184 @@
1
+ import { z } from "zod"
2
+
3
+ import { createBoundedJsonValueSnapshot } from "./code/json-snapshot.js"
4
+ import {
5
+ SPATIAL_SCENE_LIMITS,
6
+ SpatialDigestSchema,
7
+ SpatialPatchOperationSchema,
8
+ type SpatialPatchOperation,
9
+ type SpatialSceneV1,
10
+ } from "./spatial-scene/contracts.js"
11
+ import {
12
+ parseSpatialScene,
13
+ parseSpatialValue,
14
+ SpatialSceneError,
15
+ spatialSceneSha256,
16
+ spatialValueSha256,
17
+ } from "./spatial-scene/identity.js"
18
+ import {
19
+ applySpatialScenePatch,
20
+ type SpatialSceneDiffEntry,
21
+ } from "./spatial-scene/patch.js"
22
+
23
+ /**
24
+ * Scene-variant galleries for agent review.
25
+ *
26
+ * One authored base scene plus a bounded list of typed patch variants derives
27
+ * one candidate scene per variant — no generation, no paid calls. The desktop
28
+ * host renders each derived scene into a beauty still and composes the stills
29
+ * into the shared labelled contact sheet, so an agent reviews world variants
30
+ * the same way it reviews generated image candidates. The authored scene is
31
+ * never modified: every variant is a new derived document with its own digest,
32
+ * and promotion is an explicit authored patch afterward.
33
+ */
34
+
35
+ export const slopcameraSceneGalleryLimits = Object.freeze({
36
+ variants: 16,
37
+ variantsBytes: 1024 * 1024,
38
+ idEdge: 64,
39
+ labelEdge: 256,
40
+ summaryEdge: 256,
41
+ })
42
+
43
+ const variantId = z
44
+ .string()
45
+ .min(1)
46
+ .max(slopcameraSceneGalleryLimits.idEdge)
47
+ .regex(
48
+ /^[a-z0-9][a-z0-9-]*$/u,
49
+ "Variant ids are lowercase slugs like dusk or golden-hour-2.",
50
+ )
51
+
52
+ /**
53
+ * A variant patch is the versioned spatial patch document with the expected
54
+ * base digest optional: when omitted the planner stamps the gallery's base
55
+ * scene digest, when present it must match exactly.
56
+ */
57
+ const SceneVariantPatchSchema = z.strictObject({
58
+ kind: z.literal("slopcamera.spatial-scene-patch"),
59
+ schemaVersion: z.literal(1),
60
+ expectedSceneSha256: SpatialDigestSchema.optional(),
61
+ operations: z
62
+ .array(SpatialPatchOperationSchema)
63
+ .min(1)
64
+ .max(SPATIAL_SCENE_LIMITS.patchOperations),
65
+ })
66
+
67
+ const SceneGalleryVariantSchema = z.strictObject({
68
+ id: variantId,
69
+ label: z.string().min(1).max(slopcameraSceneGalleryLimits.labelEdge).optional(),
70
+ patch: SceneVariantPatchSchema,
71
+ })
72
+
73
+ export const SlopcameraSceneVariantsSchema = z.strictObject({
74
+ kind: z.literal("slopcamera.scene-variants"),
75
+ schemaVersion: z.literal(1),
76
+ variants: z
77
+ .array(SceneGalleryVariantSchema)
78
+ .min(1)
79
+ .max(slopcameraSceneGalleryLimits.variants),
80
+ })
81
+
82
+ export type SlopcameraSceneVariantSpec = z.infer<typeof SceneGalleryVariantSchema>
83
+
84
+ /** A compact per-variant description recorded as the candidate's prompt. */
85
+ export function summarizeSceneVariantPatch(
86
+ operations: readonly SpatialPatchOperation[],
87
+ ): string {
88
+ const parts = operations.map(operation => {
89
+ const target =
90
+ ("entityId" in operation ? operation.entityId : undefined)
91
+ ?? ("camera" in operation ? operation.camera.cameraId : undefined)
92
+ ?? ("channel" in operation ? operation.channel.channelId : undefined)
93
+ ?? ("asset" in operation ? operation.asset.assetId : undefined)
94
+ ?? ("generator" in operation ? operation.generator.generatorId : undefined)
95
+ ?? ("entity" in operation ? operation.entity.entityId : undefined)
96
+ ?? ("override" in operation ? operation.override.entityId : undefined)
97
+ return target === undefined ? operation.kind : `${operation.kind} ${target}`
98
+ })
99
+ const summary = parts.join("; ")
100
+ return summary.length <= slopcameraSceneGalleryLimits.summaryEdge
101
+ ? summary
102
+ : `${summary.slice(0, slopcameraSceneGalleryLimits.summaryEdge - 1)}…`
103
+ }
104
+
105
+ export function parseSlopcameraSceneVariants(
106
+ input: unknown,
107
+ ): readonly SlopcameraSceneVariantSpec[] {
108
+ const value = createBoundedJsonValueSnapshot(
109
+ input,
110
+ slopcameraSceneGalleryLimits.variantsBytes,
111
+ "scene variants",
112
+ ).value
113
+ const document = parseSpatialValue(
114
+ SlopcameraSceneVariantsSchema,
115
+ value,
116
+ "scene variants",
117
+ )
118
+ const ids = new Set<string>()
119
+ for (const variant of document.variants) {
120
+ if (ids.has(variant.id)) {
121
+ throw new SpatialSceneError(
122
+ "invalid-data",
123
+ `Duplicate scene variant id ${variant.id}.`,
124
+ "variants",
125
+ )
126
+ }
127
+ ids.add(variant.id)
128
+ }
129
+ return document.variants
130
+ }
131
+
132
+ export interface SlopcameraSceneGalleryVariant {
133
+ readonly index: number
134
+ readonly id: string
135
+ readonly label: string
136
+ readonly prompt: string
137
+ readonly patchSha256: string
138
+ readonly scene: SpatialSceneV1
139
+ readonly sceneSha256: string
140
+ readonly diff: readonly SpatialSceneDiffEntry[]
141
+ }
142
+
143
+ export interface SlopcameraSceneGalleryPlan {
144
+ readonly scene: SpatialSceneV1
145
+ readonly baseSceneSha256: string
146
+ readonly variants: readonly SlopcameraSceneGalleryVariant[]
147
+ }
148
+
149
+ /**
150
+ * Parses and applies every variant patch before returning — invalid input is
151
+ * rejected before the host renders anything. Each derived scene is validated
152
+ * again by the patch transaction itself.
153
+ */
154
+ export function planSlopcameraSceneGallery(input: {
155
+ readonly scene: unknown
156
+ readonly variants: unknown
157
+ }): SlopcameraSceneGalleryPlan {
158
+ const scene = parseSpatialScene(input.scene)
159
+ const baseSceneSha256 = spatialSceneSha256(scene)
160
+ const specs = parseSlopcameraSceneVariants(input.variants)
161
+ const variants = specs.map((spec, offset) => {
162
+ const expected = spec.patch.expectedSceneSha256 ?? baseSceneSha256
163
+ if (expected !== baseSceneSha256) {
164
+ throw new SpatialSceneError(
165
+ "conflict",
166
+ `Variant ${spec.id} was authored against a different scene revision.`,
167
+ "variants",
168
+ )
169
+ }
170
+ const patch = { ...spec.patch, expectedSceneSha256: expected }
171
+ const result = applySpatialScenePatch(scene, patch)
172
+ return {
173
+ diff: result.diff,
174
+ id: spec.id,
175
+ index: offset + 1,
176
+ label: spec.label ?? spec.id,
177
+ patchSha256: spatialValueSha256(patch),
178
+ prompt: summarizeSceneVariantPatch(patch.operations),
179
+ scene: result.scene,
180
+ sceneSha256: result.sceneSha256,
181
+ }
182
+ })
183
+ return { baseSceneSha256, scene, variants }
184
+ }
@@ -0,0 +1,183 @@
1
+ import { z } from "zod"
2
+ import {
3
+ SPATIAL_SCENE_LIMITS,
4
+ SpatialAssetIdSchema,
5
+ SpatialAssetManifestSchema,
6
+ SpatialDigestSchema,
7
+ SpatialEntitySchema,
8
+ SpatialGeneratorIdSchema,
9
+ SpatialPatchOperationSchema,
10
+ SpatialPayloadSchema,
11
+ } from "./contracts.js"
12
+ import { SPATIAL_GLB_LIMITS, SPATIAL_GLB_PROFILE, SPATIAL_GLB_PROFILE_V1, SPATIAL_GLB_RIGGED_PROFILE } from "./gltf.js"
13
+ import { SPATIAL_GEOMETRY_PROFILE } from "./geometry.js"
14
+
15
+ /**
16
+ * Canonical document contracts produced by `scene asset admit` and consumed by
17
+ * `scene audit --asset-bounds` and the MCP `audit_scene` tool. They live in the
18
+ * portable layer so every surface shares one strict schema; hosts only read or
19
+ * write the documents.
20
+ */
21
+
22
+ /**
23
+ * Affine guard bound. Node matrices stay below math.MAX_ABS_COMPONENT (1e12)
24
+ * and decoded vertices below 1e6, so every transformed vertex stays under
25
+ * ~3e18; 1e18 bounds the reachable evaluated range with margin.
26
+ */
27
+ const boundsComponent = z.number().finite().min(-1e18).max(1e18)
28
+ const boundsVector = z.tuple([boundsComponent, boundsComponent, boundsComponent])
29
+ export const SpatialBoundsSchema = z.strictObject({ min: boundsVector, max: boundsVector })
30
+ .refine(value => value.min.every((component, index) => component <= value.max[index]!), "Bounds min must not exceed max.")
31
+ export type SpatialBounds = Readonly<z.infer<typeof SpatialBoundsSchema>>
32
+
33
+ /**
34
+ * Derived facts bound to one admitted asset payload. The document persists as a
35
+ * canonical JSON payload behind a sibling `metadata` manifest that depends on
36
+ * its subject asset, so a scene may omit it without invalidating the subject
37
+ * manifest under the unchanged `gltf` interpretation shape.
38
+ */
39
+ const factsUnit = z.number().finite().min(0).max(1)
40
+ /**
41
+ * One declared GLB material's capability row. `maps` lists present texture
42
+ * slots in fixed order; `emissiveLinear` records a declared emissiveFactor.
43
+ */
44
+ export const SpatialAssetMaterialFactSchema = z.strictObject({
45
+ name: z.string().max(1_024).optional(),
46
+ alphaMode: z.enum(["OPAQUE", "MASK", "BLEND"]),
47
+ doubleSided: z.boolean(),
48
+ maps: z.array(z.enum(["baseColor", "metallicRoughness", "normal", "occlusion", "emissive", "clearcoat", "clearcoatRoughness", "clearcoatNormal", "transmission", "sheenColor", "sheenRoughness", "anisotropy"])).max(12),
49
+ textureTransforms: z.array(z.strictObject({ map: z.string().min(1).max(64), offset: z.tuple([z.number().finite(), z.number().finite()]), rotation: z.number().finite().min(-Math.PI).max(Math.PI), scale: z.tuple([z.number().finite(), z.number().finite()]) })).max(12).default([]),
50
+ emissiveLinear: z.tuple([factsUnit, factsUnit, factsUnit]).optional(),
51
+ emissiveStrength: z.number().finite().min(0).max(100_000).optional(),
52
+ /** PBR extension indicators: present only when the GLB declares the corresponding KHR extension on this material. */
53
+ clearcoat: z.strictObject({ factor: factsUnit, roughness: factsUnit }).optional(),
54
+ transmission: z.strictObject({ factor: factsUnit }).optional(),
55
+ sheen: z.strictObject({ colorLinear: z.tuple([factsUnit, factsUnit, factsUnit]), roughness: factsUnit }).optional(),
56
+ anisotropy: z.strictObject({ strength: z.number().finite().min(-1).max(1), rotation: z.number().finite().min(0).max(2 * Math.PI) }).optional(),
57
+ ior: z.number().finite().min(1).max(5).optional(),
58
+ })
59
+ const SpatialAssetRigFactsSchema = z.strictObject({
60
+ profile: z.literal(SPATIAL_GLB_RIGGED_PROFILE),
61
+ skins: z.array(z.strictObject({
62
+ name: z.string().max(1_024).optional(),
63
+ jointNodeIndices: z.array(z.number().int().min(0).max(SPATIAL_GLB_LIMITS.nodes - 1)).min(1).max(SPATIAL_GLB_LIMITS.jointsPerSkin),
64
+ inverseBindMatricesAccessor: z.number().int().min(0).max(SPATIAL_GLB_LIMITS.accessors - 1),
65
+ })).min(1).max(SPATIAL_GLB_LIMITS.skins),
66
+ morphTargets: z.array(z.array(z.array(z.strictObject({
67
+ name: z.string().min(1).max(1_024), hasPosition: z.boolean(), hasNormal: z.boolean(),
68
+ })).max(SPATIAL_GLB_LIMITS.morphTargetsPerPrimitive)).max(SPATIAL_GLB_LIMITS.primitives)).max(SPATIAL_GLB_LIMITS.meshes),
69
+ clips: z.array(z.strictObject({
70
+ name: z.string().max(1_024).optional(),
71
+ durationSeconds: z.number().finite().min(0).max(SPATIAL_GLB_LIMITS.durationSeconds),
72
+ channels: z.array(z.strictObject({
73
+ nodeIndex: z.number().int().min(0).max(SPATIAL_GLB_LIMITS.nodes - 1),
74
+ path: z.enum(["translation", "rotation", "scale", "weights"]),
75
+ })).max(SPATIAL_GLB_LIMITS.channels),
76
+ })).max(SPATIAL_GLB_LIMITS.clips),
77
+ })
78
+ const meterBound = z.number().finite().min(0).max(1_000_000)
79
+ const lodCoordinate = z.number().finite().min(-1e6).max(1e6)
80
+ const lodVector = z.tuple([lodCoordinate, lodCoordinate, lodCoordinate])
81
+ /** One deterministic LOD level: an alternate retained GLB payload plus its selection rule. */
82
+ export const SpatialAssetLodSchema = z.strictObject({
83
+ level: z.number().int().min(0).max(7),
84
+ /** Asset id of the retained payload for this level (level 0 is the facts subject). */
85
+ assetId: SpatialAssetIdSchema,
86
+ sha256: SpatialDigestSchema,
87
+ /** Selection rule: pick the greatest level whose switch distance is met. */
88
+ switchDistanceM: meterBound,
89
+ })
90
+ export type SpatialAssetLod = Readonly<z.infer<typeof SpatialAssetLodSchema>>
91
+
92
+ /** Bounded collision proxies in the subject's model space; hulls reference a retained native artifact. */
93
+ export const SpatialCollisionProxySchema = z.discriminatedUnion("kind", [
94
+ z.strictObject({ kind: z.literal("box"), center: lodVector, halfExtents: z.tuple([meterBound, meterBound, meterBound]).refine(v => v.every(c => c > 0), "Box half extents must be positive.") }),
95
+ z.strictObject({ kind: z.literal("sphere"), center: lodVector, radius: meterBound.refine(v => v > 0, "Sphere radius must be positive.") }),
96
+ z.strictObject({ kind: z.literal("capsule"), center: lodVector, axis: z.enum(["x", "y", "z"]), radius: meterBound.refine(v => v > 0, "Capsule radius must be positive."), halfLength: meterBound }),
97
+ z.strictObject({ kind: z.literal("hull-hull"), artifactSha256: SpatialDigestSchema }),
98
+ ])
99
+ export type SpatialCollisionProxy = Readonly<z.infer<typeof SpatialCollisionProxySchema>>
100
+
101
+ /** Binding to one verified retained-native exchange: request and receipt digests plus emitted outputs. */
102
+ export const SpatialRetainedArtifactSchema = z.strictObject({
103
+ operation: z.enum(["mesh-repair", "complex-csg", "uv-atlas", "decimation", "hull-decomposition", "glb-emission"]),
104
+ requestSha256: SpatialDigestSchema,
105
+ receiptSha256: SpatialDigestSchema,
106
+ outputs: z.array(z.strictObject({ sha256: SpatialDigestSchema, bytes: z.number().int().safe().min(1).max(SPATIAL_GLB_LIMITS.bytes) })).min(1).max(16),
107
+ })
108
+ export type SpatialRetainedArtifact = Readonly<z.infer<typeof SpatialRetainedArtifactSchema>>
109
+
110
+ /** Facts for a parametric-geometry subject: generator identity, spec/parameter digests, LOD set and collision proxies. */
111
+ export const SpatialAssetGeneratorFactsSchema = z.strictObject({
112
+ generatorId: SpatialGeneratorIdSchema,
113
+ /** Parametric generator kind (wall, stairs, arch, …) as declared by the spec. */
114
+ parametricKind: z.string().min(1).max(64),
115
+ /** Canonical digest of the bounded parametric spec document. */
116
+ specSha256: SpatialDigestSchema,
117
+ /** Digest of the declared generator parameters (matches the generator record). */
118
+ parametersSha256: SpatialDigestSchema,
119
+ /** Sorted LOD set; level 0 must be present and is the facts subject. */
120
+ lods: z.array(SpatialAssetLodSchema).min(1).max(8),
121
+ /** Model-space collision proxies consumed by audits and hosts. */
122
+ collision: z.array(SpatialCollisionProxySchema).max(16),
123
+ /** Verified retained-native exchanges whose outputs this subject binds. */
124
+ retainedArtifacts: z.array(SpatialRetainedArtifactSchema).max(8).optional(),
125
+ })
126
+ export type SpatialAssetGeneratorFacts = Readonly<z.infer<typeof SpatialAssetGeneratorFactsSchema>>
127
+
128
+ export const SpatialAssetFactsV1Schema = z.strictObject({
129
+ kind: z.literal("slopcamera.spatial-asset-facts"), schemaVersion: z.literal(1),
130
+ subject: SpatialPayloadSchema,
131
+ subjectManifestSha256: SpatialDigestSchema,
132
+ profile: z.enum([SPATIAL_GLB_PROFILE, SPATIAL_GLB_PROFILE_V1, SPATIAL_GLB_RIGGED_PROFILE, SPATIAL_GEOMETRY_PROFILE]),
133
+ nodeCount: z.number().int().min(1).max(SPATIAL_GLB_LIMITS.nodes),
134
+ clipDurationsSeconds: z.array(z.number().finite().min(0).max(SPATIAL_GLB_LIMITS.durationSeconds)).max(SPATIAL_GLB_LIMITS.clips),
135
+ bounds: z.strictObject({ modelSpace: SpatialBoundsSchema, sceneSpace: SpatialBoundsSchema }),
136
+ /** Present on newly admitted assets; absent facts predate the material-facts extension. */
137
+ materials: z.array(SpatialAssetMaterialFactSchema).max(SPATIAL_GLB_LIMITS.materials).optional(),
138
+ /** Exact skeleton, morph and channel facts for the additive rigged profile. */
139
+ rig: SpatialAssetRigFactsSchema.optional(),
140
+ /** Generator identity, LOD set, collision proxies and retained artifacts for parametric subjects. */
141
+ generator: SpatialAssetGeneratorFactsSchema.optional(),
142
+ }).superRefine((facts, context) => {
143
+ if ((facts.profile === SPATIAL_GLB_RIGGED_PROFILE) !== (facts.rig !== undefined)) context.addIssue({ code: "custom", path: ["rig"], message: "Rig facts must be present exactly for the rigged GLB profile." })
144
+ if ((facts.profile === SPATIAL_GEOMETRY_PROFILE) !== (facts.generator !== undefined)) context.addIssue({ code: "custom", path: ["generator"], message: "Generator facts must be present exactly for the parametric geometry profile." })
145
+ if (facts.generator !== undefined) {
146
+ const levels = new Set(facts.generator.lods.map(lod => lod.level))
147
+ if (levels.size !== facts.generator.lods.length || !levels.has(0)) context.addIssue({ code: "custom", path: ["generator", "lods"], message: "LOD levels must be unique and include level 0." })
148
+ const distances = facts.generator.lods.map(lod => lod.switchDistanceM)
149
+ if (distances.some((value, index) => index > 0 && value <= distances[index - 1]!)) context.addIssue({ code: "custom", path: ["generator", "lods"], message: "LOD switch distances must strictly increase." })
150
+ const hulls = facts.generator.collision.filter(proxy => proxy.kind === "hull-hull")
151
+ for (const hull of hulls) {
152
+ const bound = facts.generator.retainedArtifacts?.some(artifact => artifact.outputs.some(output => output.sha256 === hull.artifactSha256)) ?? false
153
+ if (!bound) context.addIssue({ code: "custom", path: ["generator", "collision"], message: "Hull-hull proxies must reference a retained artifact output." })
154
+ }
155
+ }
156
+ })
157
+ export type SpatialAssetFactsV1 = Readonly<z.infer<typeof SpatialAssetFactsV1Schema>>
158
+
159
+ export const SpatialPublishedArtifactSchema = z.strictObject({
160
+ path: z.string().min(1).max(1_024),
161
+ sha256: SpatialDigestSchema,
162
+ bytes: z.number().int().safe().min(1).max(SPATIAL_GLB_LIMITS.bytes),
163
+ disposition: z.enum(["created", "exists"]),
164
+ })
165
+ export type SpatialPublishedArtifact = Readonly<z.infer<typeof SpatialPublishedArtifactSchema>>
166
+
167
+ /**
168
+ * The deterministic `scene asset admit` output document. Artifact paths are
169
+ * relative to the directory containing this document; `operations` apply in
170
+ * order inside one `SpatialScenePatchV1` alongside the caller's expected scene
171
+ * digest.
172
+ */
173
+ export const SpatialAssetAdmissionV1Schema = z.strictObject({
174
+ kind: z.literal("slopcamera.spatial-asset-admission"), schemaVersion: z.literal(1),
175
+ manifest: SpatialAssetManifestSchema,
176
+ factsManifest: SpatialAssetManifestSchema,
177
+ facts: SpatialAssetFactsV1Schema,
178
+ bounds: z.strictObject({ modelSpace: SpatialBoundsSchema, sceneSpace: SpatialBoundsSchema }),
179
+ entity: SpatialEntitySchema,
180
+ artifacts: z.strictObject({ payload: SpatialPublishedArtifactSchema, facts: SpatialPublishedArtifactSchema }),
181
+ operations: z.array(SpatialPatchOperationSchema).min(2).max(SPATIAL_SCENE_LIMITS.patchOperations),
182
+ })
183
+ export type SpatialAssetAdmissionV1 = Readonly<z.infer<typeof SpatialAssetAdmissionV1Schema>>