@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.
- package/README.md +29 -9
- package/apps/desktop/application/context.ts +6 -0
- package/apps/desktop/application/default-registry.ts +26 -0
- package/apps/desktop/application/operation.ts +14 -1
- package/apps/desktop/application/operations/index.ts +4 -0
- package/apps/desktop/application/operations/spatial-behavior.ts +151 -0
- package/apps/desktop/application/operations/spatial-direction.ts +222 -0
- package/apps/desktop/application/operations/spatial-rendered-audit.ts +174 -0
- package/apps/desktop/application/operations/spatial-review.ts +194 -0
- package/apps/desktop/application/operations/spatial-scene.ts +25 -1
- package/apps/desktop/application/registry.ts +2 -2
- package/apps/desktop/application/spatial-asset-admission.ts +201 -0
- package/apps/desktop/application/spatial-assets.ts +160 -31
- package/apps/desktop/application/spatial-render.ts +217 -20
- package/apps/desktop/application/spatial-rendered-audit.ts +273 -0
- package/apps/desktop/application/spatial-review.ts +361 -0
- package/apps/desktop/application/spatial-spz.ts +48 -3
- package/apps/desktop/application/spatial-world-import.ts +26 -5
- package/apps/desktop/application/spatial-world-metadata.ts +105 -0
- package/apps/desktop/cli/args.ts +514 -11
- package/apps/desktop/cli/capability-manifest.ts +374 -0
- package/apps/desktop/cli/cinema-renderer.ts +608 -0
- package/apps/desktop/cli/command-host-resources.ts +29 -1
- package/apps/desktop/cli/commands.ts +1188 -4
- package/apps/desktop/cli/gateway-review-provider.ts +334 -0
- package/apps/desktop/cli/help.ts +172 -6
- package/apps/desktop/cli/main.ts +3 -3
- package/apps/desktop/cli/portable-surface.ts +6 -1
- package/apps/desktop/cli/spatial-asset-service.ts +124 -0
- package/apps/desktop/cli/spatial-design-service.ts +237 -0
- package/apps/desktop/cli/spatial-generate-service.ts +370 -0
- package/apps/desktop/cli/spatial-scene-service.ts +271 -4
- package/apps/desktop/cli/workflow-code.ts +1 -1
- package/apps/desktop/code/semantic-builder.ts +24 -0
- package/apps/desktop/contracts/cinema.ts +736 -0
- package/apps/desktop/contracts/index.ts +1 -0
- package/apps/desktop/contracts/spatial-asset.ts +16 -0
- package/apps/desktop/contracts/spatial-world.ts +26 -3
- package/apps/desktop/core/cinema-plan.ts +1320 -0
- package/apps/desktop/core/index.ts +1 -0
- package/apps/desktop/core/storage.ts +75 -0
- package/apps/desktop/dist/cli/main.js +725 -273
- package/apps/desktop/html-overlay/spatial.ts +619 -57
- package/apps/desktop/workflows/cinematic-world.ts +127 -0
- package/apps/desktop/workflows/index.ts +6 -0
- package/dist/cli.js +25 -429
- package/dist/code/advanced.js +1 -269
- package/dist/code/index.js +16 -2239
- package/dist/generate.js +1 -23
- package/dist/host-resources.js +1 -25
- package/dist/index-0bbesn4m.js +35 -0
- package/dist/index-296qhpf2.js +3 -0
- package/dist/index-3cxf2c58.js +3 -0
- package/dist/index-77fjfg9f.js +3 -0
- package/dist/index-80a36bc0.js +3 -0
- package/dist/index-a3vvs3rf.js +3 -0
- package/dist/index-b3geqczn.js +5 -0
- package/dist/index-grk47tgs.js +3 -0
- package/dist/index-gs48afpt.js +7 -0
- package/dist/index-qry58nj2.js +10 -0
- package/dist/index-zhg36tf9.js +3 -0
- package/dist/index.js +1 -226
- package/dist/operations.js +1 -28
- package/dist/skill-install-gx7afxsd.js +2 -0
- package/dist/vectorize/worker.js +1 -141
- package/dist/workflow.js +1 -15
- package/docs/README.md +5 -1
- package/docs/studio.md +12 -0
- package/package.json +7 -5
- package/skills/slopcamera/SKILL.md +7 -1
- package/skills/slopcamera/references/directed-scenes.md +48 -2
- package/skills/slopcamera/references/gateway-media.md +15 -1
- package/skills/slopcamera/references/image-galleries.md +166 -0
- package/skills/slopcamera/references/install.md +1 -1
- package/skills/slopcamera/references/music-video.md +4 -0
- package/skills/slopcamera/references/native-studio.md +19 -0
- package/skills/slopcamera/references/parametric-design.md +14 -0
- package/skills/slopcamera/references/scene-building.md +172 -0
- package/skills/slopcamera/references/social-collage-banners.md +204 -0
- package/skills/slopcamera/references/workflows-sdk.md +3 -3
- package/skills/slopcamera/scripts/compose-social-collage-banner.ts +728 -0
- package/src/capability-manifest.ts +361 -0
- package/src/cli.ts +222 -4
- package/src/code/canonical-json.ts +9 -0
- package/src/generate.ts +2 -2
- package/src/icon.ts +1111 -0
- package/src/image-gallery.ts +1049 -0
- package/src/index.ts +11 -0
- package/src/mcp/boundary.ts +11 -11
- package/src/mcp/index.ts +3 -0
- package/src/mcp/server.ts +2 -1
- package/src/mcp/tools.ts +1649 -132
- package/src/mcp/types.ts +19 -0
- package/src/operations.ts +412 -0
- package/src/portable-capability-manifest.ts +67 -0
- package/src/scene-gallery.ts +184 -0
- package/src/spatial-scene/asset-admission.ts +183 -0
- package/src/spatial-scene/audit-rendered.ts +692 -0
- package/src/spatial-scene/audit.ts +617 -0
- package/src/spatial-scene/behavior-audit.ts +252 -0
- package/src/spatial-scene/behavior-bake.ts +232 -0
- package/src/spatial-scene/behavior-fns.ts +585 -0
- package/src/spatial-scene/behavior-gallery.ts +108 -0
- package/src/spatial-scene/behavior-stdlib.ts +323 -0
- package/src/spatial-scene/behavior-trace.ts +325 -0
- package/src/spatial-scene/behavior.ts +617 -0
- package/src/spatial-scene/build.ts +632 -0
- package/src/spatial-scene/camera-rig.ts +125 -0
- package/src/spatial-scene/camera-track.ts +4 -2
- package/src/spatial-scene/character.ts +124 -0
- package/src/spatial-scene/contracts.ts +75 -7
- package/src/spatial-scene/design-templates.ts +351 -0
- package/src/spatial-scene/design.ts +327 -0
- package/src/spatial-scene/direction-compile.ts +584 -0
- package/src/spatial-scene/direction.ts +122 -0
- package/src/spatial-scene/effects.ts +149 -0
- package/src/spatial-scene/evaluate.ts +109 -29
- package/src/spatial-scene/gallery.ts +277 -0
- package/src/spatial-scene/generate.ts +239 -0
- package/src/spatial-scene/geometry-native.ts +165 -0
- package/src/spatial-scene/geometry.ts +1216 -0
- package/src/spatial-scene/gltf.ts +600 -77
- package/src/spatial-scene/identity.ts +55 -2
- package/src/spatial-scene/index.ts +36 -0
- package/src/spatial-scene/inspect.ts +35 -6
- package/src/spatial-scene/material-lighting.ts +609 -0
- package/src/spatial-scene/math.ts +21 -4
- package/src/spatial-scene/motion-evidence.ts +88 -0
- package/src/spatial-scene/parametric.ts +765 -0
- package/src/spatial-scene/particle-preparation.ts +183 -0
- package/src/spatial-scene/particle.ts +195 -0
- package/src/spatial-scene/patch.ts +109 -15
- package/src/spatial-scene/performance.ts +1613 -0
- package/src/spatial-scene/probe.ts +137 -0
- package/src/spatial-scene/recipe-pack.ts +86 -0
- package/src/spatial-scene/render-effects.ts +240 -0
- package/src/spatial-scene/review.ts +639 -0
- package/src/spatial-scene/simulation-bake.ts +206 -0
- package/src/spatial-scene/simulation.ts +187 -0
- package/src/spatial-scene/solve.ts +366 -0
- package/src/spatial-scene/temporal-audit.ts +354 -0
- package/src/version.ts +1 -1
- package/dist/index-42zsesc1.js +0 -2257
- package/dist/index-7131pg1c.js +0 -1820
- package/dist/index-7308egqr.js +0 -48
- package/dist/index-8txs6fkn.js +0 -712
- package/dist/index-fava6pge.js +0 -192
- package/dist/index-h1k0fnjq.js +0 -2363
- package/dist/index-r7gdhmsp.js +0 -424
- package/dist/index-sh6xbav6.js +0 -1219
- package/dist/index-z1w83f81.js +0 -4
- package/dist/index-zfnddgay.js +0 -2029
- package/dist/skill-install-dh0ntqba.js +0 -10
|
@@ -12,18 +12,23 @@ import {
|
|
|
12
12
|
SpatialTimeUsSchema, SpatialShotV1Schema, type SpatialSceneV1,
|
|
13
13
|
} from "../../../src/spatial-scene/contracts";
|
|
14
14
|
import { evaluateSpatialScene } from "../../../src/spatial-scene/evaluate";
|
|
15
|
+
import { parseSpatialMotionEvidence, spatialMotionEvidenceSha256, SPATIAL_MOTION_EVIDENCE_LIMITS } from "../../../src/spatial-scene/motion-evidence";
|
|
16
|
+
import { SpatialRenderEffectsBindingSchema, spatialRenderEffectsAssetIds } from "../../../src/spatial-scene/render-effects";
|
|
17
|
+
import { prepareSpatialParticleInstances } from "../../../src/spatial-scene/particle-preparation";
|
|
18
|
+
import type { SpatialParticleSystem } from "../../../src/spatial-scene/particle";
|
|
15
19
|
import { parseSpatialScene, parseSpatialValue, spatialAssetClosureDigests, spatialSceneSha256 } from "../../../src/spatial-scene/identity";
|
|
16
20
|
import { reduceSpatialFrameRate, spatialFrameCount, spatialFrameSample, spatialOutputDuration, type SpatialRational } from "../../../src/spatial-scene/time";
|
|
17
21
|
import { canonicalJson, canonicalJsonSha256 } from "../core/canonical-json";
|
|
18
22
|
import { createNodeSpatialDurability, type SpatialDurabilityPort } from "../core/spatial-durability";
|
|
19
23
|
import { SpatialShotRenderClockSchema, spatialShotSceneTime } from "../core/spatial-shot-clock";
|
|
20
24
|
import { createSpatialOverlayBatch, SpatialRenderModeSchema, SPATIAL_OVERLAY_LIMITS, type SpatialOverlayBatchInput, type PreparedSpatialAsset } from "../html-overlay/spatial";
|
|
21
|
-
import { HTML_OVERLAY_MAX_HTML_BYTES } from "../html-overlay/contracts";
|
|
25
|
+
import { HTML_OVERLAY_MAX_HTML_BYTES, HTML_OVERLAY_MAX_RESOURCES, HTML_OVERLAY_MAX_TOTAL_RESOURCE_BYTES } from "../html-overlay/contracts";
|
|
22
26
|
import { assertHtmlOverlayGpuEvidenceProfile, HtmlOverlayExecutionProfileSchema, HtmlOverlayGpuEvidenceSchema, type HtmlOverlayExecutionProfile, type HtmlOverlayGpuEvidence } from "../html-overlay/execution-profile";
|
|
23
27
|
import { exactCapabilityByName } from "./capability-binding";
|
|
24
28
|
import { ApplicationError } from "./errors";
|
|
25
29
|
import { bindHtmlOverlayBrowserRuntime, HtmlOverlayBrowserRuntimeBindingSchema, type HtmlOverlayBrowserRuntimeBinding } from "./html-overlay-browser-runtime";
|
|
26
30
|
import { createHtmlOverlayExecutionBundle, HtmlOverlayExecutionIntegritySchema } from "./html-overlay-integrity";
|
|
31
|
+
import type { BoundHtmlOverlayResource } from "./html-overlay-renderer";
|
|
27
32
|
import type { OperationExecutionContext } from "./operation";
|
|
28
33
|
import { assertMediaCapabilities, bindExpectedMediaCapabilities, MediaCapabilityBindingsSchema, mediaCapabilityCommand, mediaCapabilityRunner, type MediaCapabilityName } from "./operations/media/capabilities";
|
|
29
34
|
import { AbortBoundApplicationRunner, createMediaOperationWorkspace, MediaArtifactReferenceSchema, publishContentAddressedMedia, publishContentAddressedReceipt, type MediaArtifactReference } from "./operations/media/shared";
|
|
@@ -41,9 +46,14 @@ export const SPATIAL_RENDER_LIMITS = Object.freeze({
|
|
|
41
46
|
});
|
|
42
47
|
|
|
43
48
|
export function spatialBatchSerializationLimit(error: unknown): boolean {
|
|
44
|
-
if (error instanceof z.ZodError) return error.issues.length > 0 && error.issues.every(issue =>
|
|
45
|
-
|
|
46
|
-
|
|
49
|
+
if (error instanceof z.ZodError) return error.issues.length > 0 && error.issues.every(issue => (
|
|
50
|
+
issue.path.length === 1 && issue.path[0] === "html"
|
|
51
|
+
&& (issue.code === "too_big" && issue.maximum === HTML_OVERLAY_MAX_HTML_BYTES
|
|
52
|
+
|| issue.code === "custom" && issue.message === `HTML overlay documents may not exceed ${HTML_OVERLAY_MAX_HTML_BYTES} UTF-8 bytes.`)
|
|
53
|
+
|| issue.path.length === 1 && issue.path[0] === "resources"
|
|
54
|
+
&& (issue.code === "too_big" && issue.maximum === HTML_OVERLAY_MAX_RESOURCES
|
|
55
|
+
|| issue.code === "custom" && issue.message === `HTML overlay resources exceed ${HTML_OVERLAY_MAX_TOTAL_RESOURCE_BYTES} total bytes.`)
|
|
56
|
+
));
|
|
47
57
|
return error instanceof Error && ["Spatial overlay request", "Spatial overlay metadata"].some(name => error.message === `${name} contains more than ${SPATIAL_OVERLAY_LIMITS.requestBytes} bytes.`);
|
|
48
58
|
}
|
|
49
59
|
|
|
@@ -51,13 +61,16 @@ export function spatialBatchSerializationLimit(error: unknown): boolean {
|
|
|
51
61
|
export function partitionSpatialRenderWindow(input: SpatialOverlayBatchInput) {
|
|
52
62
|
type Partition = { readonly offset: number; readonly length: number; readonly preparedAssets: readonly PreparedSpatialAsset[]; readonly batch: ReturnType<typeof createSpatialOverlayBatch> };
|
|
53
63
|
const partitions: Partition[] = [];
|
|
64
|
+
const needsVelocity = input.mode.kind === "motion"
|
|
65
|
+
|| input.effects?.document.renderPlan.postProcess?.steps.some(step => step.kind === "motion-blur") === true;
|
|
54
66
|
const visit = (offset: number, length: number): void => {
|
|
55
67
|
const snapshots = input.snapshots.slice(offset, offset + length);
|
|
56
68
|
const times = new Set(snapshots.map(snapshot => snapshot.timeUs));
|
|
57
69
|
const preparedAssets = input.executionProfile === undefined ? input.preparedAssets ?? []
|
|
58
|
-
: (input.preparedAssets ?? []).filter(asset => asset.kind === "splat" || asset.timeUs === null || times.has(asset.timeUs));
|
|
70
|
+
: (input.preparedAssets ?? []).filter(asset => asset.kind === "splat" || asset.kind === "lut" || asset.timeUs === null || times.has(asset.timeUs));
|
|
59
71
|
try {
|
|
60
|
-
const batch = createSpatialOverlayBatch({ ...input, snapshots, preparedAssets
|
|
72
|
+
const batch = createSpatialOverlayBatch({ ...input, snapshots, preparedAssets,
|
|
73
|
+
...(needsVelocity && offset > 0 ? { previousSnapshot: input.snapshots[offset - 1]! } : {}) });
|
|
61
74
|
partitions.push({ offset, length, preparedAssets, batch });
|
|
62
75
|
} catch (error) {
|
|
63
76
|
if (input.executionProfile === undefined || length <= 1 || !spatialBatchSerializationLimit(error)) throw error;
|
|
@@ -72,6 +85,7 @@ export function partitionSpatialRenderWindow(input: SpatialOverlayBatchInput) {
|
|
|
72
85
|
|
|
73
86
|
export const SpatialRenderRequestSchema = z.strictObject({
|
|
74
87
|
executionProfile: HtmlOverlayExecutionProfileSchema.optional(),
|
|
88
|
+
effects: SpatialRenderEffectsBindingSchema.optional(),
|
|
75
89
|
cameraId: SpatialCameraIdSchema,
|
|
76
90
|
overrides: z.array(SpatialOverrideSchema).max(4_096).optional(),
|
|
77
91
|
cameraPoseOverride: SpatialPoseSchema.optional(),
|
|
@@ -124,7 +138,20 @@ export interface SpatialRenderPlan {
|
|
|
124
138
|
readonly outputWidth: number;
|
|
125
139
|
readonly outputHeight: number;
|
|
126
140
|
readonly outputDurationUs?: SpatialRational;
|
|
127
|
-
readonly costs: {
|
|
141
|
+
readonly costs: {
|
|
142
|
+
readonly sourceBytes: number;
|
|
143
|
+
readonly renderPixels: number;
|
|
144
|
+
readonly renderTargetPixels: number;
|
|
145
|
+
readonly renderTargetBytes: number;
|
|
146
|
+
readonly texturePixels: number;
|
|
147
|
+
readonly particleStates: number;
|
|
148
|
+
readonly particleStateBytes: number;
|
|
149
|
+
readonly particleBufferBytesBound: number;
|
|
150
|
+
readonly simulationSteps: number;
|
|
151
|
+
readonly pngBytesBound: number;
|
|
152
|
+
readonly outputBytesBound: number;
|
|
153
|
+
readonly stagingBytesBound: number;
|
|
154
|
+
};
|
|
128
155
|
}
|
|
129
156
|
|
|
130
157
|
function rational(numerator: bigint, denominator: bigint): SpatialRational {
|
|
@@ -137,6 +164,38 @@ function checked(condition: boolean, message: string): asserts condition {
|
|
|
137
164
|
}
|
|
138
165
|
const sha256 = (bytes: Uint8Array): string => createHash("sha256").update(bytes).digest("hex");
|
|
139
166
|
|
|
167
|
+
function particleSurfaceInputs(system: SpatialParticleSystem, preparedAssets: readonly PreparedSpatialAsset[]) {
|
|
168
|
+
const surfaceIds = new Set(system.emitters.flatMap(emitter => emitter.shape.kind === "surface" ? [emitter.shape.assetId] : []));
|
|
169
|
+
return [...surfaceIds].map(assetId => {
|
|
170
|
+
const triangles: number[][][] = [];
|
|
171
|
+
for (const asset of preparedAssets) {
|
|
172
|
+
if (asset.kind !== "geometry" || asset.assetId !== assetId) continue;
|
|
173
|
+
for (const primitive of asset.primitives) {
|
|
174
|
+
const indices = primitive.indices ?? Array.from({ length: primitive.positions.length / 3 }, (_, index) => index);
|
|
175
|
+
for (let index = 0; index < indices.length; index += 3) triangles.push([indices[index]!, indices[index + 1]!, indices[index + 2]!].map(vertex => (
|
|
176
|
+
[primitive.positions[vertex * 3]!, primitive.positions[vertex * 3 + 1]!, primitive.positions[vertex * 3 + 2]!]
|
|
177
|
+
)));
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return { assetId, triangles };
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function postProcessRenderTargets(kind: string): number {
|
|
185
|
+
switch (kind) {
|
|
186
|
+
case "bloom": return 3;
|
|
187
|
+
case "depth-of-field":
|
|
188
|
+
case "motion-blur": return 2;
|
|
189
|
+
case "flare": return 3;
|
|
190
|
+
case "tone-map":
|
|
191
|
+
case "vignette":
|
|
192
|
+
case "chromatic-aberration":
|
|
193
|
+
case "grain":
|
|
194
|
+
case "lut-grade": return 1;
|
|
195
|
+
default: throw new ApplicationError("invalid-data", `Unsupported post-process step ${kind}.`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
140
199
|
/** Source-clock samples are computed independently in BigInt and quantized exactly once. */
|
|
141
200
|
export function planSpatialRender(sceneInput: unknown, requestInput: unknown): SpatialRenderPlan {
|
|
142
201
|
const scene = parseSpatialScene(sceneInput);
|
|
@@ -145,8 +204,50 @@ export function planSpatialRender(sceneInput: unknown, requestInput: unknown): S
|
|
|
145
204
|
checked(request.executionProfile !== "three-spark-webgl2-hardware-v1" || request.mode.kind === "beauty", "The Three/Spark hardware profile supports beauty rendering only; splat selection and depth semantics are not qualified.");
|
|
146
205
|
const camera = scene.cameras.find(item => item.cameraId === request.cameraId);
|
|
147
206
|
checked(camera !== undefined, "Spatial render camera is absent from the scene.");
|
|
207
|
+
const requestMode = request.mode;
|
|
208
|
+
if (requestMode.kind === "motion") {
|
|
209
|
+
const entityId = requestMode.entityId;
|
|
210
|
+
checked(scene.entities.some(entity => entity.entityId === entityId), "Motion target is absent from the scene.");
|
|
211
|
+
checked(request.executionProfile === undefined, "Motion evidence renders through the deterministic snapshot renderer only.");
|
|
212
|
+
}
|
|
148
213
|
const { width, height } = camera.projection;
|
|
149
214
|
checked(width <= SPATIAL_RENDER_LIMITS.dimension && height <= SPATIAL_RENDER_LIMITS.dimension, "Spatial render dimensions exceed the renderer profile.");
|
|
215
|
+
const sceneDigest = spatialSceneSha256(scene);
|
|
216
|
+
const effects = request.effects?.document;
|
|
217
|
+
const texturePixels = scene.assets.reduce((sum, asset) => (
|
|
218
|
+
asset.interpretation.kind === "image" || asset.interpretation.kind === "video"
|
|
219
|
+
? sum + asset.interpretation.width * asset.interpretation.height
|
|
220
|
+
: sum
|
|
221
|
+
), 0);
|
|
222
|
+
let particleStates = 0, simulationSteps = 0, renderTargetCount = request.mode.kind === "motion" ? 3 : 1;
|
|
223
|
+
if (effects !== undefined) {
|
|
224
|
+
checked(effects.sceneSha256 === sceneDigest, "Render effects belong to a different scene source.");
|
|
225
|
+
const assets = new Map(scene.assets.map(asset => [asset.assetId, asset]));
|
|
226
|
+
for (const assetId of spatialRenderEffectsAssetIds(effects)) checked(assets.has(assetId), `Render effects reference missing asset ${assetId}.`);
|
|
227
|
+
for (const step of effects.renderPlan.postProcess?.steps ?? []) {
|
|
228
|
+
if (step.kind === "lut-grade") checked(assets.get(step.assetId)?.interpretation.kind === "image", `LUT ${step.assetId} must bind an image asset.`);
|
|
229
|
+
if (request.mode.kind === "beauty") renderTargetCount += postProcessRenderTargets(step.kind);
|
|
230
|
+
}
|
|
231
|
+
for (const binding of effects.particleSystems) {
|
|
232
|
+
checked(scene.entities.some(entity => entity.entityId === binding.system.entityId), `Particle system target ${binding.system.entityId} is absent from the scene.`);
|
|
233
|
+
const renderer = binding.system.renderer;
|
|
234
|
+
if (renderer.assetId !== undefined) {
|
|
235
|
+
const interpretation = assets.get(renderer.assetId)?.interpretation.kind;
|
|
236
|
+
checked(renderer.kind === "sprite" ? interpretation === "image" : interpretation === "gltf", `Particle renderer asset ${renderer.assetId} has an incompatible interpretation.`);
|
|
237
|
+
}
|
|
238
|
+
for (const emitter of binding.system.emitters) {
|
|
239
|
+
if (emitter.shape.kind === "surface") checked(assets.get(emitter.shape.assetId)?.interpretation.kind === "gltf", `Particle surface ${emitter.shape.assetId} must bind a glTF asset.`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
particleStates = effects.renderPlan.quality.particleCount;
|
|
243
|
+
simulationSteps = effects.renderPlan.quality.simulationSteps;
|
|
244
|
+
checked(width * height <= effects.renderPlan.quality.pixelBudget, "Render dimensions exceed the effect pixel budget.");
|
|
245
|
+
checked(texturePixels <= effects.renderPlan.quality.texturePixelBudget, "Scene textures exceed the effect texture-pixel budget.");
|
|
246
|
+
}
|
|
247
|
+
const renderTargetPixels = width * height * renderTargetCount;
|
|
248
|
+
const renderTargetBytes = renderTargetPixels * (request.mode.kind === "object-id" || request.mode.kind === "axial-depth" ? 4 : 8) + width * height * 4;
|
|
249
|
+
const particleStateBytes = particleStates * 64;
|
|
250
|
+
checked(renderTargetBytes + particleStateBytes <= SPATIAL_RENDER_LIMITS.stagingBytes, "Effect GPU working-set estimate exceeds its byte budget.");
|
|
150
251
|
const selection = request.selection;
|
|
151
252
|
let samples: SpatialRenderSample[], outputDurationUs: SpatialRational | undefined;
|
|
152
253
|
if (selection.kind === "video") {
|
|
@@ -173,6 +274,14 @@ export function planSpatialRender(sceneInput: unknown, requestInput: unknown): S
|
|
|
173
274
|
checked(times.every(time => time <= scene.durationUs), "A selected frame is outside the scene clock.");
|
|
174
275
|
samples = times.map((timeUs, index) => ({ index, timeUs, exactTimeUs: { numerator: String(timeUs), denominator: "1" } }));
|
|
175
276
|
}
|
|
277
|
+
if (request.mode.kind === "motion") {
|
|
278
|
+
checked(samples.length >= 2, "Motion evidence requires at least two ordered samples.");
|
|
279
|
+
checked(samples.length - 1 <= SPATIAL_MOTION_EVIDENCE_LIMITS.evidenceSamples, "Motion evidence exceeds its published sample budget.");
|
|
280
|
+
for (let index = 1; index < samples.length; index++) {
|
|
281
|
+
const gap = samples[index]!.timeUs - samples[index - 1]!.timeUs;
|
|
282
|
+
checked(gap >= 1 && gap <= 1_000_000, "Motion evidence requires strictly increasing samples within the one-second exposure bound.");
|
|
283
|
+
}
|
|
284
|
+
}
|
|
176
285
|
const outputWidth = selection.kind === "contact-sheet" ? selection.columns * selection.cellWidth : width;
|
|
177
286
|
const outputHeight = selection.kind === "contact-sheet" ? Math.ceil(samples.length / selection.columns) * selection.cellHeight : height;
|
|
178
287
|
checked(outputWidth <= SPATIAL_RENDER_LIMITS.dimension && outputHeight <= SPATIAL_RENDER_LIMITS.dimension
|
|
@@ -186,18 +295,21 @@ export function planSpatialRender(sceneInput: unknown, requestInput: unknown): S
|
|
|
186
295
|
// not a promise that every admitted scene compresses below it. A truncated
|
|
187
296
|
// encode cannot publish: complete encoded count/timestamps are verified.
|
|
188
297
|
const outputBytesBound = selection.kind === "video" ? Math.min(SPATIAL_RENDER_LIMITS.outputBytes, pngBytesBound + 1024 * 1024) : outputWidth * outputHeight * 5 + outputHeight * 8 + 65_536;
|
|
189
|
-
const
|
|
298
|
+
const particleBufferBytesBound = particleStateBytes * samples.length;
|
|
299
|
+
const stagingBytesBound = sourceBytes + pngBytesBound + particleBufferBytesBound + outputBytesBound + SPATIAL_RENDER_LIMITS.metadataBytes;
|
|
190
300
|
checked(sourceBytes <= SPATIAL_RENDER_LIMITS.sourceBytes, "Spatial source closure exceeds its byte budget.");
|
|
191
301
|
checked(renderPixels <= SPATIAL_RENDER_LIMITS.totalPixels && pngBytesBound <= SPATIAL_RENDER_LIMITS.pngBytes, "Spatial frame staging exceeds its pixel or byte budget; reduce resolution or range.");
|
|
192
302
|
checked(outputBytesBound <= SPATIAL_RENDER_LIMITS.outputBytes && stagingBytesBound <= SPATIAL_RENDER_LIMITS.stagingBytes, "Spatial output exceeds its conservative byte budget.");
|
|
303
|
+
if (effects !== undefined) checked(outputBytesBound <= effects.renderPlan.quality.outputBytes, "Spatial output exceeds the effect output-byte budget.");
|
|
193
304
|
// Semantic override validation is pure and precedes capability or resource admission.
|
|
194
305
|
evaluateSpatialScene(scene, { cameraId: request.cameraId, timeUs: samples[0]!.timeUs,
|
|
195
306
|
...(request.overrides === undefined ? {} : { overrides: request.overrides }),
|
|
196
307
|
...(request.cameraPoseOverride === undefined ? {} : { cameraPoseOverride: request.cameraPoseOverride }),
|
|
197
308
|
});
|
|
198
|
-
return deepFreezeJson({ scene, sceneSha256:
|
|
309
|
+
return deepFreezeJson({ scene, sceneSha256: sceneDigest, request, requestSha256: canonicalJsonSha256(request), samples,
|
|
199
310
|
width, height, outputWidth, outputHeight, ...(outputDurationUs === undefined ? {} : { outputDurationUs }),
|
|
200
|
-
costs: { sourceBytes, renderPixels,
|
|
311
|
+
costs: { sourceBytes, renderPixels, renderTargetPixels, renderTargetBytes, texturePixels, particleStates, particleStateBytes, particleBufferBytesBound, simulationSteps,
|
|
312
|
+
pngBytesBound, outputBytesBound, stagingBytesBound },
|
|
201
313
|
});
|
|
202
314
|
}
|
|
203
315
|
|
|
@@ -272,6 +384,9 @@ export const SpatialRenderReceiptSchema = z.strictObject({
|
|
|
272
384
|
source: z.strictObject({ canonicalScene: MediaArtifactReferenceSchema, canonicalization: z.literal("parsed-spatial-scene-v1"),
|
|
273
385
|
originalSceneArtifact: MediaArtifactReferenceSchema.optional(), retainedAssets: z.array(retainedAssetSchema).max(128), sourceManifests: z.record(SpatialAssetIdSchema, SpatialDigestSchema) }),
|
|
274
386
|
request: SpatialRenderRequestSchema, requestSha256: SpatialDigestSchema,
|
|
387
|
+
effects: z.strictObject({ appliedToBeauty: z.boolean(), documentSha256: SpatialDigestSchema, renderPlanSha256: SpatialDigestSchema,
|
|
388
|
+
particleSystemSha256s: z.array(SpatialDigestSchema).max(64), simulationBakeReceiptSha256s: z.array(SpatialDigestSchema).max(64) }).optional(),
|
|
389
|
+
motionEvidence: z.strictObject({ artifact: MediaArtifactReferenceSchema, evidenceSha256: SpatialDigestSchema }).optional(),
|
|
275
390
|
samples: z.array(z.strictObject({ sample: z.strictObject({ index: z.number().int().min(0).max(SPATIAL_RENDER_LIMITS.frames - 1), timeUs: SpatialTimeUsSchema, exactTimeUs: rationalSchema }),
|
|
276
391
|
stateSha256: SpatialDigestSchema, viewSha256: SpatialDigestSchema, pngSha256: SpatialDigestSchema, pngBytes: byteCountSchema })).min(1).max(SPATIAL_RENDER_LIMITS.frames),
|
|
277
392
|
render: renderSummarySchema, output: MediaArtifactReferenceSchema,
|
|
@@ -287,9 +402,26 @@ export const SpatialRenderReceiptSchema = z.strictObject({
|
|
|
287
402
|
contactSheet: z.strictObject({ fit: z.literal("contain"), resampling: z.enum(["lanczos3", "nearest-data"]), calibratedProjectionResized: z.literal(false) }).optional(),
|
|
288
403
|
encodedProbe: MediaArtifactReferenceSchema.optional(),
|
|
289
404
|
workflow: z.strictObject({ nodeKey: z.string().min(1).max(255), nodePlanSha256: SpatialDigestSchema, runId: z.string().min(1).max(128) }).optional(),
|
|
290
|
-
costs: z.strictObject({ sourceBytes: byteCountSchema, renderPixels: z.number().int().min(1).max(SPATIAL_RENDER_LIMITS.totalPixels),
|
|
405
|
+
costs: z.strictObject({ sourceBytes: byteCountSchema, renderPixels: z.number().int().min(1).max(SPATIAL_RENDER_LIMITS.totalPixels),
|
|
406
|
+
renderTargetPixels: z.number().int().safe().min(1).max(SPATIAL_RENDER_LIMITS.totalPixels * 32).optional(), renderTargetBytes: byteCountSchema.optional(),
|
|
407
|
+
texturePixels: z.number().int().safe().min(0).max(SPATIAL_RENDER_LIMITS.totalPixels).optional(), particleStates: z.number().int().safe().min(0).max(1_000_000).optional(),
|
|
408
|
+
particleStateBytes: byteCountSchema.optional(), particleBufferBytesBound: byteCountSchema.optional(), simulationSteps: z.number().int().safe().min(0).max(10_000).optional(), pngBytesBound: byteCountSchema,
|
|
291
409
|
outputBytesBound: byteCountSchema, stagingBytesBound: byteCountSchema, actualPngBytes: byteCountSchema, actualBatchMetadataBytes: byteCountSchema }),
|
|
292
410
|
}).superRefine((receipt, context) => {
|
|
411
|
+
const expectedEffects = receipt.request.effects === undefined ? undefined : {
|
|
412
|
+
appliedToBeauty: receipt.request.mode.kind === "beauty",
|
|
413
|
+
documentSha256: receipt.request.effects.documentSha256,
|
|
414
|
+
renderPlanSha256: receipt.request.effects.document.renderPlanSha256,
|
|
415
|
+
particleSystemSha256s: receipt.request.effects.document.particleSystems.map(binding => binding.systemSha256),
|
|
416
|
+
simulationBakeReceiptSha256s: receipt.request.effects.document.simulationBakes.map(item => item.receiptSha256),
|
|
417
|
+
};
|
|
418
|
+
if (expectedEffects === undefined ? receipt.effects !== undefined
|
|
419
|
+
: receipt.effects === undefined || canonicalJson(receipt.effects) !== canonicalJson(expectedEffects)) {
|
|
420
|
+
context.addIssue({ code: "custom", path: ["effects"], message: "Spatial receipt must bind the exact requested effects and diagnostic bypass state." });
|
|
421
|
+
}
|
|
422
|
+
if ((receipt.request.mode.kind === "motion") !== (receipt.motionEvidence !== undefined)) {
|
|
423
|
+
context.addIssue({ code: "custom", path: ["motionEvidence"], message: "Spatial receipt must bind motion evidence exactly when its mode is motion." });
|
|
424
|
+
}
|
|
293
425
|
if (receipt.batches.length > (receipt.request.executionProfile === undefined ? Math.ceil(SPATIAL_RENDER_LIMITS.frames / SPATIAL_RENDER_LIMITS.batchFrames) : receipt.samples.length)) {
|
|
294
426
|
context.addIssue({ code: "custom", path: ["batches"], message: "Spatial batch count exceeds its selected profile's bounded sample partitions." });
|
|
295
427
|
}
|
|
@@ -336,7 +468,7 @@ export class SpatialRenderFailure extends ApplicationError {
|
|
|
336
468
|
}
|
|
337
469
|
|
|
338
470
|
/** Descriptor-bound, bounded read. Parent paths and the named leaf must retain physical identity. */
|
|
339
|
-
async function readPhysical(path: string, maximumBytes: number, signal: AbortSignal): Promise<Uint8Array> {
|
|
471
|
+
export async function readPhysical(path: string, maximumBytes: number, signal: AbortSignal): Promise<Uint8Array> {
|
|
340
472
|
checked(await realpath(path) === path, "Spatial render files must have physical paths.");
|
|
341
473
|
const parents: { path: string; dev: number; ino: number }[] = [];
|
|
342
474
|
for (let parent = dirname(path);; parent = dirname(parent)) {
|
|
@@ -378,7 +510,8 @@ async function stageJson(path: string, input: unknown, maximumBytes: number): Pr
|
|
|
378
510
|
await stageBytes(path, bytes);
|
|
379
511
|
return bytes;
|
|
380
512
|
}
|
|
381
|
-
|
|
513
|
+
/** Returns the fully decoded RGBA8 raster; the rendered audit consumes it as data. */
|
|
514
|
+
export async function verifyPng(bytes: Uint8Array, width: number, height: number): Promise<Uint8Array> {
|
|
382
515
|
const decoder = sharp(bytes, { failOn: "warning", limitInputPixels: SPATIAL_RENDER_LIMITS.framePixels });
|
|
383
516
|
const metadata = await decoder.metadata();
|
|
384
517
|
checked(metadata.format === "png" && metadata.width === width && metadata.height === height && metadata.depth === "uchar"
|
|
@@ -387,6 +520,7 @@ async function verifyPng(bytes: Uint8Array, width: number, height: number): Prom
|
|
|
387
520
|
// Fully decode before accepting renderer bytes. This preserves data-pass channel values.
|
|
388
521
|
const decoded = await decoder.ensureAlpha().raw().toBuffer({ resolveWithObject: true });
|
|
389
522
|
checked(decoded.info.channels === 4 && decoded.data.length === width * height * 4, "Renderer PNG did not decode to its declared RGBA raster.");
|
|
523
|
+
return decoded.data;
|
|
390
524
|
}
|
|
391
525
|
|
|
392
526
|
const integerText = z.union([z.number().int().safe(), z.string().regex(/^-?(?:0|[1-9]\d{0,23})$/u)]).transform(String);
|
|
@@ -482,6 +616,8 @@ export async function renderSpatialScene(context: OperationExecutionContext, inp
|
|
|
482
616
|
const ports = { runner, ...(names.includes("ffmpeg") ? { ffmpegCommand: mediaCapabilityCommand(capabilities, "ffmpeg"), ffprobeCommand: mediaCapabilityCommand(capabilities, "ffprobe") } : {}) };
|
|
483
617
|
const retainedSources: { assetId: string; manifestSha256: string; originalPath: string; stagedPath: string; bytes: number }[] = [];
|
|
484
618
|
const batchFiles: string[] = [], sampleEvidence: { sample: SpatialRenderSample; stateSha256: string; viewSha256: string; pngSha256: string; pngBytes: number }[] = [];
|
|
619
|
+
const motionSamples: { id: string; entityId: string; exposureUs: number; previousTimeUs: number; sampleTimeUs: number; width: number; height: number;
|
|
620
|
+
viewport: readonly [number, number, number, number]; byteLength: number; encoding: "rg16un"; motionSha256: string; motionScale: number; samplesPerPixel: number }[] = [];
|
|
485
621
|
let pngBytes = 0, metadataBytes = 0;
|
|
486
622
|
let gpuEvidence: HtmlOverlayGpuEvidence | undefined;
|
|
487
623
|
for (let offset = 0; offset < plan.samples.length; offset += SPATIAL_RENDER_LIMITS.batchFrames) {
|
|
@@ -492,7 +628,35 @@ export async function renderSpatialScene(context: OperationExecutionContext, inp
|
|
|
492
628
|
...(plan.request.cameraPoseOverride === undefined ? {} : { cameraPoseOverride: plan.request.cameraPoseOverride }),
|
|
493
629
|
}));
|
|
494
630
|
stage = "preparation";
|
|
495
|
-
|
|
631
|
+
const lutAssetIds = plan.request.mode.kind === "beauty" && plan.request.effects !== undefined
|
|
632
|
+
? plan.request.effects.document.renderPlan.postProcess?.steps.flatMap(step => step.kind === "lut-grade" ? [step.assetId] : []) : undefined;
|
|
633
|
+
await withPreparedSpatialAssets({ snapshots, exactSceneTimesUs: samples.map(sample => sample.exactTimeUs), assetRoot, workspaceParent: directory,
|
|
634
|
+
...(lutAssetIds === undefined || lutAssetIds.length === 0 ? {} : { lutAssetIds }) }, ports, context.abortSignal, async prepared => {
|
|
635
|
+
const particleAssets = new Map<string, PreparedSpatialAsset>();
|
|
636
|
+
const particleResources = new Map<string, BoundHtmlOverlayResource>();
|
|
637
|
+
if (plan.request.mode.kind === "beauty" && plan.request.effects !== undefined) {
|
|
638
|
+
for (const binding of plan.request.effects.document.particleSystems) {
|
|
639
|
+
const surfaces = particleSurfaceInputs(binding.system, prepared.preparedAssets);
|
|
640
|
+
for (const sample of samples) {
|
|
641
|
+
const key = `${binding.systemSha256}:${binding.system.entityId}:${sample.timeUs}`;
|
|
642
|
+
if (particleAssets.has(key)) continue;
|
|
643
|
+
const instances = prepareSpatialParticleInstances({ sampleTimeUs: sample.timeUs, surfaces, system: binding.system });
|
|
644
|
+
const name = `particles-${instances.sha256.slice(0, 40)}`;
|
|
645
|
+
const path = join(directory!, `${name}.bin`);
|
|
646
|
+
if (!particleResources.has(name)) {
|
|
647
|
+
await stageBytes(path, instances.bytes);
|
|
648
|
+
const resource = { name, sha256: instances.sha256, bytes: instances.byteLength, mediaType: "application/octet-stream", transport: "fetch" as const, urlPath: `${name}.bin` };
|
|
649
|
+
particleResources.set(name, { ...resource, absolutePath: path });
|
|
650
|
+
}
|
|
651
|
+
particleAssets.set(key, { kind: "particle-instances", entityId: binding.system.entityId, instanceCount: instances.instanceCount,
|
|
652
|
+
resource: { name, sha256: instances.sha256, bytes: instances.byteLength, mediaType: "application/octet-stream", transport: "fetch", urlPath: `${name}.bin` },
|
|
653
|
+
strideBytes: instances.strideBytes, systemSha256: instances.systemSha256, timeUs: sample.timeUs });
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
const allPreparedAssets = [...prepared.preparedAssets, ...particleAssets.values()];
|
|
658
|
+
const allResources = [...prepared.resources, ...particleResources.values()];
|
|
659
|
+
checked(allResources.reduce((sum, resource) => sum + resource.bytes, 0) <= plan.costs.stagingBytesBound, "Prepared particle resources exceeded the admitted staging budget.");
|
|
496
660
|
checked(canonicalJson(prepared.receipt.sourceManifests) === canonicalJson(sourceManifests), "Asset preparation did not bind the complete source closure.");
|
|
497
661
|
if (offset === 0) {
|
|
498
662
|
for (const source of prepared.sources) {
|
|
@@ -505,17 +669,23 @@ export async function renderSpatialScene(context: OperationExecutionContext, inp
|
|
|
505
669
|
}
|
|
506
670
|
checked(retainedSources.length === plan.scene.assets.length && new Set(retainedSources.map(source => source.assetId)).size === retainedSources.length, "Retained source closure is incomplete or duplicated.");
|
|
507
671
|
}
|
|
672
|
+
const needsVelocity = plan.request.mode.kind === "motion"
|
|
673
|
+
|| plan.request.effects?.document.renderPlan.postProcess?.steps.some(step => step.kind === "motion-blur") === true;
|
|
508
674
|
const partitions = partitionSpatialRenderWindow({ snapshots, mode: plan.request.mode,
|
|
675
|
+
...(plan.request.effects === undefined ? {} : { effects: plan.request.effects }),
|
|
509
676
|
...(plan.request.executionProfile === undefined ? {} : { executionProfile: plan.request.executionProfile }),
|
|
510
|
-
|
|
677
|
+
...(needsVelocity && offset > 0 ? { previousSnapshot: evaluateSpatialScene(plan.scene, { cameraId: plan.request.cameraId, timeUs: plan.samples[offset - 1]!.timeUs,
|
|
678
|
+
...(plan.request.overrides === undefined ? {} : { overrides: plan.request.overrides }),
|
|
679
|
+
...(plan.request.cameraPoseOverride === undefined ? {} : { cameraPoseOverride: plan.request.cameraPoseOverride }) }) } : {}),
|
|
680
|
+
frameRate: plan.request.selection.kind === "video" ? plan.request.selection.frameRate : { numerator: 1, denominator: 1 }, preparedAssets: allPreparedAssets });
|
|
511
681
|
for (const partition of partitions) {
|
|
512
682
|
const batchOffset = offset + partition.offset;
|
|
513
683
|
const batchSamples = samples.slice(partition.offset, partition.offset + partition.length);
|
|
514
684
|
const batchSnapshots = snapshots.slice(partition.offset, partition.offset + partition.length);
|
|
515
685
|
const batch = partition.batch;
|
|
516
686
|
const resourceNames = new Set(batch.authoring.resources.map(resource => resource.name));
|
|
517
|
-
const batchResources =
|
|
518
|
-
const batchPreparation = plan.request.executionProfile === undefined ? prepared.receipt : { ...prepared.receipt,
|
|
687
|
+
const batchResources = allResources.filter(resource => resourceNames.has(resource.name));
|
|
688
|
+
const batchPreparation = particleAssets.size === 0 && plan.request.executionProfile === undefined ? prepared.receipt : { ...prepared.receipt,
|
|
519
689
|
preparedSha256: canonicalJsonSha256(partition.preparedAssets), outputBytes: batchResources.reduce((sum, resource) => sum + resource.bytes, 0) };
|
|
520
690
|
const batchDirectory = join(directory!, `batch-${batchOffset}`);
|
|
521
691
|
await mkdir(batchDirectory, { mode: 0o700 });
|
|
@@ -541,7 +711,15 @@ export async function renderSpatialScene(context: OperationExecutionContext, inp
|
|
|
541
711
|
const bytes = await readPhysical(join(batchDirectory, "frames", frameName(index)), plan.costs.pngBytesBound, context.abortSignal);
|
|
542
712
|
pngBytes += bytes.length;
|
|
543
713
|
checked(pngBytes <= plan.costs.pngBytesBound, "Rendered PNG bytes exceeded the admitted staging estimate.");
|
|
544
|
-
await verifyPng(bytes, plan.width, plan.height);
|
|
714
|
+
const decoded = await verifyPng(bytes, plan.width, plan.height);
|
|
715
|
+
if (plan.request.mode.kind === "motion" && sample.index > 0) {
|
|
716
|
+
const previous = plan.samples[sample.index - 1]!;
|
|
717
|
+
motionSamples.push({ id: `sample_${sample.index}`, entityId: plan.request.mode.entityId,
|
|
718
|
+
exposureUs: sample.timeUs - previous.timeUs, previousTimeUs: previous.timeUs, sampleTimeUs: sample.timeUs,
|
|
719
|
+
width: plan.width, height: plan.height, viewport: [0, 0, plan.width, plan.height],
|
|
720
|
+
byteLength: decoded.length, encoding: "rg16un", motionSha256: sha256(decoded),
|
|
721
|
+
motionScale: plan.request.mode.motionScale, samplesPerPixel: 1 });
|
|
722
|
+
}
|
|
545
723
|
await stageBytes(join(frameDirectory, frameName(sample.index)), bytes);
|
|
546
724
|
sampleEvidence.push({ sample, stateSha256: batchSnapshots[index]!.stateSha256, viewSha256: batchSnapshots[index]!.viewSha256, pngSha256: sha256(bytes), pngBytes: bytes.length });
|
|
547
725
|
}
|
|
@@ -595,6 +773,13 @@ export async function renderSpatialScene(context: OperationExecutionContext, inp
|
|
|
595
773
|
const scenePath = join(directory, "scene.json"), runtimePath = join(directory, "runtime.json");
|
|
596
774
|
const sceneBytes = await stageJson(scenePath, plan.scene, 2_097_153);
|
|
597
775
|
const runtimeBytes = await stageJson(runtimePath, runtime, SPATIAL_RENDER_LIMITS.metadataBytes);
|
|
776
|
+
let motionEvidence: { evidence: ReturnType<typeof parseSpatialMotionEvidence>; path: string } | undefined;
|
|
777
|
+
if (plan.request.mode.kind === "motion") {
|
|
778
|
+
motionEvidence = { evidence: parseSpatialMotionEvidence({ kind: "slopcamera.spatial-motion-evidence", schemaVersion: 1,
|
|
779
|
+
entityId: plan.request.mode.entityId, renderRequestSha256: plan.requestSha256,
|
|
780
|
+
rendererSha256: runtime.manifest.rootSha256, samples: motionSamples }), path: join(directory, "motion-evidence.json") };
|
|
781
|
+
metadataBytes += (await stageJson(motionEvidence.path, motionEvidence.evidence, SPATIAL_RENDER_LIMITS.metadataBytes)).length;
|
|
782
|
+
}
|
|
598
783
|
checked(metadataBytes + sceneBytes.length + runtimeBytes.length <= SPATIAL_RENDER_LIMITS.metadataBytes, "Combined spatial source/runtime/batch evidence exceeds the admitted metadata budget.");
|
|
599
784
|
stage = "publication";
|
|
600
785
|
const publish = async (stagedPath: string, extension: string, maximumBytes: number): Promise<MediaArtifactReference> => {
|
|
@@ -621,6 +806,9 @@ export async function renderSpatialScene(context: OperationExecutionContext, inp
|
|
|
621
806
|
const batches: MediaArtifactReference[] = [];
|
|
622
807
|
for (const path of batchFiles) batches.push(await publish(path, ".json", SPATIAL_RENDER_LIMITS.metadataBytes));
|
|
623
808
|
const encodedProbe = probePath === undefined ? undefined : await publish(probePath, ".json", SPATIAL_RENDER_LIMITS.probeBytes);
|
|
809
|
+
const motionEvidenceArtifact = motionEvidence === undefined ? undefined : {
|
|
810
|
+
artifact: await publish(motionEvidence.path, ".json", SPATIAL_RENDER_LIMITS.metadataBytes),
|
|
811
|
+
evidenceSha256: spatialMotionEvidenceSha256(motionEvidence.evidence) };
|
|
624
812
|
const artifact = await publish(outputPath, plan.request.selection.kind === "video" ? ".mov" : ".png", plan.costs.outputBytesBound);
|
|
625
813
|
const frameArtifacts: MediaArtifactReference[] = [];
|
|
626
814
|
if (plan.request.selection.kind === "frame") frameArtifacts.push(artifact);
|
|
@@ -632,7 +820,16 @@ export async function renderSpatialScene(context: OperationExecutionContext, inp
|
|
|
632
820
|
const receiptValue = SpatialRenderReceiptSchema.parse({
|
|
633
821
|
kind: "slopcamera.spatial-render-receipt", schemaVersion: 1, attemptId, sceneSha256: plan.sceneSha256,
|
|
634
822
|
source: { canonicalScene: sceneSource, canonicalization: "parsed-spatial-scene-v1", ...(originalSceneArtifact === undefined ? {} : { originalSceneArtifact }), retainedAssets, sourceManifests },
|
|
635
|
-
request: plan.request, requestSha256: plan.requestSha256,
|
|
823
|
+
request: plan.request, requestSha256: plan.requestSha256,
|
|
824
|
+
...(plan.request.effects === undefined ? {} : { effects: {
|
|
825
|
+
appliedToBeauty: plan.request.mode.kind === "beauty",
|
|
826
|
+
documentSha256: plan.request.effects.documentSha256,
|
|
827
|
+
renderPlanSha256: plan.request.effects.document.renderPlanSha256,
|
|
828
|
+
particleSystemSha256s: plan.request.effects.document.particleSystems.map(binding => binding.systemSha256),
|
|
829
|
+
simulationBakeReceiptSha256s: plan.request.effects.document.simulationBakes.map(item => item.receiptSha256),
|
|
830
|
+
} }),
|
|
831
|
+
...(motionEvidenceArtifact === undefined ? {} : { motionEvidence: motionEvidenceArtifact }),
|
|
832
|
+
samples: sampleEvidence, render, output: artifact, batches, frameArtifacts,
|
|
636
833
|
runtime: { artifact: runtimeArtifact, rootSha256: runtime.manifest.rootSha256, capabilities, renderer: "three-webgl2-snapshot-v1", threeVersion: "0.185.1",
|
|
637
834
|
...(gpuEvidence === undefined ? {} : { gpuEvidence }) },
|
|
638
835
|
color: { output: plan.request.mode.kind === "beauty" ? "srgb" : "rgba8-data", alpha: plan.request.mode.kind === "beauty" ? "straight" : "binary-validity", toneMapping: "none" },
|
|
@@ -668,7 +865,7 @@ export async function renderSpatialScene(context: OperationExecutionContext, inp
|
|
|
668
865
|
return completed;
|
|
669
866
|
}
|
|
670
867
|
|
|
671
|
-
function frameName(index: number): string { return `frame-${String(index).padStart(8, "0")}.png`; }
|
|
868
|
+
export function frameName(index: number): string { return `frame-${String(index).padStart(8, "0")}.png`; }
|
|
672
869
|
function prospective(context: OperationExecutionContext, category: "outputs" | "receipts", extension: string, bytes: Uint8Array): MediaArtifactReference {
|
|
673
870
|
const digest = sha256(bytes);
|
|
674
871
|
return MediaArtifactReferenceSchema.parse({ sha256: digest, bytes: bytes.length,
|