@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.
- package/NOTICE.md +1 -7
- package/PRIVACY.md +16 -7
- package/README.md +65 -20
- package/apps/desktop/README.md +14 -130
- package/apps/desktop/application/context.ts +6 -9
- package/apps/desktop/application/default-registry.ts +26 -10
- package/apps/desktop/application/operation.ts +14 -5
- package/apps/desktop/application/operations/index.ts +4 -1
- 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 +528 -97
- 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 +31 -2
- package/apps/desktop/cli/commands.ts +1215 -70
- package/apps/desktop/cli/gateway-review-provider.ts +334 -0
- package/apps/desktop/cli/help.ts +183 -23
- package/apps/desktop/cli/html-scene.ts +139 -28
- package/apps/desktop/cli/io.ts +2 -1
- package/apps/desktop/cli/main.ts +26 -50
- package/apps/desktop/cli/menubar.ts +244 -0
- package/apps/desktop/cli/music-analysis-service.ts +61 -0
- package/apps/desktop/cli/portable-surface.ts +18 -5
- 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 +268 -4
- package/apps/desktop/cli/workflow-code.ts +1 -1
- package/apps/desktop/code/application-node-planner.ts +0 -47
- package/apps/desktop/code/host-source-layout.ts +46 -0
- package/apps/desktop/code/public.ts +0 -1
- package/apps/desktop/code/runtime-identity.ts +50 -8
- package/apps/desktop/code/semantic-builder.ts +24 -43
- package/apps/desktop/code/source-bundle.ts +23 -6
- package/apps/desktop/code/source-typecheck.ts +131 -43
- package/apps/desktop/code/worker-client.ts +14 -2
- 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/music-analysis.ts +124 -1
- package/apps/desktop/core/storage.ts +75 -0
- package/apps/desktop/dist/cli/main.js +737 -291
- package/apps/desktop/html-overlay/audio-reactivity.ts +127 -0
- package/apps/desktop/html-overlay/index.ts +9 -0
- package/apps/desktop/html-overlay/runtime.ts +2 -0
- package/apps/desktop/html-overlay/scene.ts +29 -0
- 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 +32 -434
- 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-cgpkyjqa.js +3 -0
- package/dist/index-f0ptv8dm.js +7 -0
- package/dist/index-grk47tgs.js +3 -0
- package/dist/index-qry58nj2.js +10 -0
- package/dist/index.js +1 -240
- 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 +1 -1
- package/package.json +11 -24
- package/skills/slopcamera/SKILL.md +14 -1
- package/skills/slopcamera/references/diagrams.md +5 -14
- package/skills/slopcamera/references/directed-scenes.md +22 -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 +2 -2
- package/skills/slopcamera/references/music-video.md +23 -3
- package/skills/slopcamera/references/parametric-design.md +14 -0
- package/skills/slopcamera/references/reference-led-3d.md +1 -1
- package/skills/slopcamera/references/scene-building.md +32 -0
- package/skills/slopcamera/references/social-collage-banners.md +204 -0
- package/skills/slopcamera/references/support.md +25 -0
- package/skills/slopcamera/references/web-media-excerpts.md +120 -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 +250 -102
- 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 -23
- 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 +1642 -130
- 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 +233 -0
- package/src/spatial-scene/behavior-fns.ts +585 -0
- package/src/spatial-scene/behavior-gallery.ts +109 -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 +618 -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/support-completion.ts +16 -0
- package/src/support.ts +42 -0
- package/src/version.ts +1 -1
- package/apps/desktop/application/operations/recording/index.ts +0 -5
- package/apps/desktop/application/operations/recording/pause.ts +0 -34
- package/apps/desktop/application/operations/recording/resume.ts +0 -34
- package/apps/desktop/application/operations/recording/shared.ts +0 -199
- package/apps/desktop/application/operations/recording/start.ts +0 -169
- package/apps/desktop/application/operations/recording/stop.ts +0 -34
- package/apps/desktop/capture/protocol.ts +0 -689
- package/apps/desktop/cli/capture-bundle.ts +0 -1117
- package/apps/desktop/cli/recording-controller.ts +0 -1567
- package/apps/desktop/cli/recording-daemon.ts +0 -931
- package/dist/index-42zsesc1.js +0 -2257
- package/dist/index-7308egqr.js +0 -48
- package/dist/index-8txs6fkn.js +0 -712
- package/dist/index-mcy8z0br.js +0 -192
- package/dist/index-p63wavx0.js +0 -2029
- package/dist/index-r7gdhmsp.js +0 -424
- package/dist/index-rvghzt7b.js +0 -2013
- package/dist/index-sh6xbav6.js +0 -1219
- package/dist/index-z1w83f81.js +0 -4
- package/dist/index-z7239b4h.js +0 -2363
- package/dist/skill-install-dh0ntqba.js +0 -10
- package/src/desktop.ts +0 -253
|
@@ -16,13 +16,17 @@ import {
|
|
|
16
16
|
} from "../../../src/spatial-scene/contracts";
|
|
17
17
|
import { spatialAssetClosureDigests } from "../../../src/spatial-scene/identity";
|
|
18
18
|
import { evaluateSpatialGlb, parseSpatialGlb, type SpatialGlbModel } from "../../../src/spatial-scene/gltf";
|
|
19
|
+
import { transformBounds } from "../../../src/spatial-scene/math";
|
|
20
|
+
import { SpatialDerivationCandidateSchema, pbrMaterialMapAssetIds, type SpatialDerivationCandidate } from "../../../src/spatial-scene/material-lighting";
|
|
19
21
|
import { canonicalJson, canonicalJsonSha256 } from "../core/canonical-json";
|
|
20
22
|
import { SpatialWorldImportManifestSchema, SPATIAL_SPLAT_LIMITS } from "../contracts/spatial-world";
|
|
23
|
+
import { SpatialAssetFactsV1Schema } from "../contracts/spatial-asset";
|
|
21
24
|
import { inspectSpatialSpz } from "./spatial-spz";
|
|
25
|
+
import { extractWorldProviderMetadata } from "./spatial-world-metadata";
|
|
22
26
|
import { WorldLabsProvenanceSchema } from "./spatial-world-provenance";
|
|
23
27
|
import {
|
|
24
28
|
PreparedSpatialAssetSchema, SPATIAL_OVERLAY_LIMITS, SpatialOverlayCapabilityError, spatialTextRasterContentSha256,
|
|
25
|
-
spatialGeometryContentSha256, spatialVideoRasterContentSha256,
|
|
29
|
+
spatialGeometryContentSha256, spatialSplatContentTransform, spatialVideoRasterContentSha256,
|
|
26
30
|
type PreparedSpatialAsset,
|
|
27
31
|
} from "../html-overlay/spatial";
|
|
28
32
|
import type { ApplicationProcessRunner } from "./context";
|
|
@@ -67,6 +71,8 @@ export interface SpatialAssetPreparationPorts {
|
|
|
67
71
|
export interface SpatialAssetPreparationInput {
|
|
68
72
|
readonly snapshots: readonly EvaluatedSpatialScene[];
|
|
69
73
|
readonly exactSceneTimesUs?: readonly { readonly numerator: string; readonly denominator: string }[];
|
|
74
|
+
/** Beauty lut-grade assets not bound by any entity; decoded like other image rasters. */
|
|
75
|
+
readonly lutAssetIds?: readonly string[];
|
|
70
76
|
/** Adapter-owned contained source root and private temporary workspace parent. */
|
|
71
77
|
readonly assetRoot: string;
|
|
72
78
|
readonly workspaceParent: string;
|
|
@@ -90,9 +96,43 @@ export interface PreparedSpatialAssets {
|
|
|
90
96
|
const inputSchema = z.strictObject({
|
|
91
97
|
snapshots: z.array(EvaluatedSpatialSceneSchema).min(1).max(SPATIAL_OVERLAY_LIMITS.frames),
|
|
92
98
|
exactSceneTimesUs: z.array(z.strictObject({ numerator: z.string().regex(/^(?:0|[1-9]\d{0,23})$/u), denominator: z.string().regex(/^[1-9]\d{0,8}$/u) })).min(1).max(SPATIAL_OVERLAY_LIMITS.frames).optional(),
|
|
99
|
+
lutAssetIds: z.array(z.string().min(1).max(256)).max(64).optional(),
|
|
93
100
|
assetRoot: z.string().min(1).max(4_096), workspaceParent: z.string().min(1).max(4_096),
|
|
94
101
|
});
|
|
95
102
|
const digest = (bytes: Uint8Array): string => createHash("sha256").update(bytes).digest("hex");
|
|
103
|
+
export interface SpatialMaterialDerivationReceipt {
|
|
104
|
+
readonly kind: "slopcamera.spatial-material-derivation";
|
|
105
|
+
readonly schemaVersion: 1;
|
|
106
|
+
readonly candidate: SpatialDerivationCandidate;
|
|
107
|
+
readonly sourceSha256: string;
|
|
108
|
+
readonly outputSha256: string;
|
|
109
|
+
readonly provenanceSha256: string;
|
|
110
|
+
readonly width: number;
|
|
111
|
+
readonly height: number;
|
|
112
|
+
readonly output: Uint8Array;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Deterministic, local-only raster derivation. Output is an opaque PNG bound to exact source bytes and candidate provenance. */
|
|
116
|
+
export async function deriveSpatialMaterialRaster(input: { readonly candidate: SpatialDerivationCandidate; readonly source: Uint8Array; readonly sourceSha256: string }): Promise<SpatialMaterialDerivationReceipt> {
|
|
117
|
+
const candidate = SpatialDerivationCandidateSchema.parse(input.candidate);
|
|
118
|
+
if (!(input.source instanceof Uint8Array) || input.source.byteLength < 1 || input.source.byteLength > SPATIAL_ASSET_PREPARATION_LIMITS.sourceBytes || digest(input.source) !== input.sourceSha256) throw new RangeError("Material derivation source bytes must match their exact bounded digest.");
|
|
119
|
+
const image = sharp(input.source, { failOn: "error", limitInputPixels: SPATIAL_ASSET_PREPARATION_LIMITS.rasterPixels, sequentialRead: true }).removeAlpha().toColourspace("b-w");
|
|
120
|
+
const metadata = await image.metadata();
|
|
121
|
+
if (metadata.width === undefined || metadata.height === undefined || metadata.width * metadata.height > SPATIAL_ASSET_PREPARATION_LIMITS.rasterPixels) throw new RangeError("Material derivation exceeds the decoded pixel budget.");
|
|
122
|
+
let output: Buffer;
|
|
123
|
+
if (candidate.method === "sobel-normal-from-height") {
|
|
124
|
+
const raw = await image.raw().toBuffer(); const rgba = Buffer.alloc(metadata.width * metadata.height * 4); const sample = (x: number, y: number) => raw[Math.max(0, Math.min(metadata.height - 1, y)) * metadata.width + Math.max(0, Math.min(metadata.width - 1, x))]! / 255;
|
|
125
|
+
for (let y = 0; y < metadata.height; y++) for (let x = 0; x < metadata.width; x++) { const gx = sample(x + 1, y) - sample(x - 1, y), gy = sample(x, y + 1) - sample(x, y - 1), length = Math.hypot(gx, gy, 1), at = (y * metadata.width + x) * 4; rgba[at] = Math.round((-.5 * gx / length + .5) * 255); rgba[at + 1] = Math.round((-.5 * gy / length + .5) * 255); rgba[at + 2] = Math.round((1 / length * .5 + .5) * 255); rgba[at + 3] = 255; }
|
|
126
|
+
output = await sharp(rgba, { raw: { width: metadata.width, height: metadata.height, channels: 4 } }).png({ compressionLevel: 9, adaptiveFiltering: false, palette: false }).toBuffer();
|
|
127
|
+
} else {
|
|
128
|
+
const raw = await image.raw().toBuffer();
|
|
129
|
+
if (candidate.method === "average-luminance-roughness") for (let index = 0; index < raw.length; index++) raw[index] = 255 - raw[index]!;
|
|
130
|
+
output = await sharp(raw, { raw: { width: metadata.width, height: metadata.height, channels: 1 } }).png({ compressionLevel: 9, adaptiveFiltering: false, palette: false }).toBuffer();
|
|
131
|
+
}
|
|
132
|
+
const outputBytes = Uint8Array.from(output), outputSha256 = digest(outputBytes);
|
|
133
|
+
const provenanceSha256 = canonicalJsonSha256({ domain: "slopcamera.spatial-material-derivation/v1", candidate, sourceSha256: input.sourceSha256, outputSha256, width: metadata.width, height: metadata.height });
|
|
134
|
+
return Object.freeze({ kind: "slopcamera.spatial-material-derivation", schemaVersion: 1, candidate, sourceSha256: input.sourceSha256, outputSha256, provenanceSha256, width: metadata.width, height: metadata.height, output: outputBytes });
|
|
135
|
+
}
|
|
96
136
|
function capability(name: string, message: string): never { throw new SpatialOverlayCapabilityError(name, message); }
|
|
97
137
|
function aborted(signal: AbortSignal): void { if (signal.aborted) throw signal.reason instanceof Error ? signal.reason : new Error("Spatial asset preparation was cancelled."); }
|
|
98
138
|
function utf8(bytes: Uint8Array): string { return new TextDecoder("utf-8", { fatal: true }).decode(bytes); }
|
|
@@ -341,9 +381,15 @@ export function spatialVideoTimeUs(entity: Extract<SpatialEntity, { kind: "video
|
|
|
341
381
|
}
|
|
342
382
|
|
|
343
383
|
/** Owns every temporary/native input until the consumer and all native calls settle. */
|
|
384
|
+
export interface SpatialAssetPreparationOptions {
|
|
385
|
+
/** When true, an SPZ that fails inspection is skipped rather than aborting preparation. */
|
|
386
|
+
readonly tolerateSplatDecodeFailure?: boolean;
|
|
387
|
+
}
|
|
388
|
+
|
|
344
389
|
export async function withPreparedSpatialAssets<Result>(
|
|
345
390
|
input: unknown, ports: SpatialAssetPreparationPorts, signal: AbortSignal,
|
|
346
391
|
consume: (prepared: PreparedSpatialAssets) => Promise<Result>,
|
|
392
|
+
options: SpatialAssetPreparationOptions = {},
|
|
347
393
|
): Promise<Result> {
|
|
348
394
|
const request = inputSchema.parse(createBoundedJsonSnapshot(input, SPATIAL_OVERLAY_LIMITS.requestBytes, "Spatial asset preparation request", { maximumDepth: 48, maximumValues: SPATIAL_OVERLAY_LIMITS.requestValues }).value);
|
|
349
395
|
if (request.exactSceneTimesUs !== undefined && request.exactSceneTimesUs.length !== request.snapshots.length) throw new RangeError("Exact scene times must correspond one-to-one to snapshots.");
|
|
@@ -398,6 +444,24 @@ export async function withPreparedSpatialAssets<Result>(
|
|
|
398
444
|
resources.set(name, { ...resource, absolutePath: path });
|
|
399
445
|
return { resource, width: image.width, height: image.height, alpha: image.alpha };
|
|
400
446
|
};
|
|
447
|
+
/** Verified image manifest → normalized PNG publication; shared by surfaces, environments, and material maps. */
|
|
448
|
+
const decodeImageRaster = async (asset: SpatialVerifiedAsset, label: string) => {
|
|
449
|
+
const interpretation = asset.manifest.interpretation;
|
|
450
|
+
if (interpretation.kind !== "image") throw new RangeError(`${label} requires an image asset interpretation.`);
|
|
451
|
+
let bytes = asset.bytes;
|
|
452
|
+
if (interpretation.mimeType === "image/png" && (bytes[0] !== 137 || bytes[1] !== 80 || bytes[2] !== 78 || bytes[3] !== 71)
|
|
453
|
+
|| interpretation.mimeType === "image/jpeg" && (bytes[0] !== 255 || bytes[1] !== 216)) throw new RangeError("Image payload format differs from its declared MIME type.");
|
|
454
|
+
if (interpretation.mimeType === "image/svg+xml") {
|
|
455
|
+
const svg = inertSpatialSvg(utf8(bytes));
|
|
456
|
+
const resvg = new Resvg(svg, { font: { loadSystemFonts: false } });
|
|
457
|
+
dimensions(resvg.width, resvg.height);
|
|
458
|
+
bytes = resvg.render().asPng(); profiles.add("shape-only-svg-resvg-2.6.2");
|
|
459
|
+
}
|
|
460
|
+
const decoded = await raster(bytes, interpretation.alpha === "opaque");
|
|
461
|
+
if (decoded.width !== interpretation.width || decoded.height !== interpretation.height) throw new RangeError("Image dimensions differ from its interpretation.");
|
|
462
|
+
profiles.add("sdr-png-jpeg-sharp-0.35.3");
|
|
463
|
+
return publishRaster(decoded);
|
|
464
|
+
};
|
|
401
465
|
const native = async (argv: readonly [string, ...string[]], maxOutputBytes = 16_384) => {
|
|
402
466
|
aborted(signal);
|
|
403
467
|
const result = await ports.runner.run(argv, { abortSignal: signal, timeoutMs: SPATIAL_ASSET_PREPARATION_LIMITS.nativeTimeoutMs, maxOutputBytes, stdin: "ignore" });
|
|
@@ -426,9 +490,18 @@ export async function withPreparedSpatialAssets<Result>(
|
|
|
426
490
|
if (bytes.byteLength > SPATIAL_SPLAT_LIMITS.metadataBytes) throw new RangeError("Retained metadata exceeds its byte bound.");
|
|
427
491
|
const captured = createBoundedJsonSnapshot(JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes)), SPATIAL_SPLAT_LIMITS.metadataBytes, "Retained world metadata", { maximumDepth: 24, maximumValues: 32_768 });
|
|
428
492
|
const metadata = captured.value as Record<string, unknown>;
|
|
429
|
-
|
|
430
|
-
if (
|
|
431
|
-
|
|
493
|
+
const schema = manifest.interpretation.schema;
|
|
494
|
+
if (schema === "slopcamera.provider-metadata") {
|
|
495
|
+
// Raw provider bytes are retained verbatim — no slopcamera
|
|
496
|
+
// envelope to match. A recognized semantics_metadata shape proves
|
|
497
|
+
// the declaration; unknown keys stay tolerated and ignored.
|
|
498
|
+
extractWorldProviderMetadata(metadata);
|
|
499
|
+
} else {
|
|
500
|
+
if (metadata.kind !== schema || metadata.schemaVersion !== 1) throw new RangeError("Retained metadata does not match its declared schema.");
|
|
501
|
+
if (schema === "slopcamera.spatial-world-import") SpatialWorldImportManifestSchema.parse(metadata);
|
|
502
|
+
else if (schema === "slopcamera.spatial-asset-facts") SpatialAssetFactsV1Schema.parse(metadata);
|
|
503
|
+
else WorldLabsProvenanceSchema.parse(metadata);
|
|
504
|
+
}
|
|
432
505
|
}
|
|
433
506
|
const record = { manifest, manifestSha256: closures[manifest.assetId]!, bytes };
|
|
434
507
|
verified.set(manifest.assetId, record);
|
|
@@ -441,6 +514,31 @@ export async function withPreparedSpatialAssets<Result>(
|
|
|
441
514
|
const snapshot = request.snapshots[snapshotIndex]!, exactTime = exactTimes[snapshotIndex]!;
|
|
442
515
|
aborted(signal);
|
|
443
516
|
const entity = entry.entity;
|
|
517
|
+
// A procedural mesh's material map is a secondary binding: the entity
|
|
518
|
+
// still has no primary asset, so it bypasses the per-entity asset path.
|
|
519
|
+
const materialMaps = entity.kind !== "mesh" ? [] : entity.material.kind === "pbr"
|
|
520
|
+
? pbrMaterialMapAssetIds(entity.material) : entity.material.map === undefined ? [] : [entity.material.map];
|
|
521
|
+
if (entity.kind === "mesh" && entity.geometry.kind !== "asset" && materialMaps.length > 0) {
|
|
522
|
+
let expectedDimensions: readonly [number, number] | undefined;
|
|
523
|
+
for (const materialMap of materialMaps) {
|
|
524
|
+
const mapAsset = verified.get(materialMap);
|
|
525
|
+
if (mapAsset === undefined) throw new RangeError(`Missing manifest for spatial asset ${materialMap}.`);
|
|
526
|
+
const mapKey = `${materialMap}:${entity.entityId}:static`;
|
|
527
|
+
let prepared = preparedAssets.get(mapKey);
|
|
528
|
+
if (prepared === undefined) {
|
|
529
|
+
const output = await decodeImageRaster(mapAsset, `Material map on ${entity.entityId}`);
|
|
530
|
+
prepared = PreparedSpatialAssetSchema.parse({ kind: "raster", assetId: materialMap,
|
|
531
|
+
assetManifestSha256: mapAsset.manifestSha256, entityId: entity.entityId, timeUs: null, ...output });
|
|
532
|
+
preparedAssets.set(mapKey, prepared);
|
|
533
|
+
}
|
|
534
|
+
if (prepared.kind !== "raster") throw new RangeError("PBR maps require exact prepared rasters.");
|
|
535
|
+
const current = [prepared.width, prepared.height] as const;
|
|
536
|
+
if (expectedDimensions !== undefined && (current[0] !== expectedDimensions[0] || current[1] !== expectedDimensions[1])) {
|
|
537
|
+
throw new RangeError(`PBR maps on ${entity.entityId} must have identical decoded dimensions.`);
|
|
538
|
+
}
|
|
539
|
+
expectedDimensions = current;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
444
542
|
if (entity.kind === "group" || entity.kind === "light" || entity.kind === "mesh" && entity.geometry.kind !== "asset") continue;
|
|
445
543
|
const assetId = entity.kind === "text" ? entity.fontAssetId : entity.kind === "mesh" && entity.geometry.kind === "asset" ? entity.geometry.assetId : "assetId" in entity ? entity.assetId : "";
|
|
446
544
|
const asset = verified.get(assetId);
|
|
@@ -450,14 +548,26 @@ export async function withPreparedSpatialAssets<Result>(
|
|
|
450
548
|
if (interpretation.kind !== "splat" || interpretation.format !== "spz") capability("splat-format", "The qualified world profile accepts gzip SPZ v2/v3 only.");
|
|
451
549
|
const key = `${assetId}:${entity.entityId}:splat`;
|
|
452
550
|
if (preparedAssets.has(key)) continue;
|
|
453
|
-
const
|
|
551
|
+
const inspected = await inspectSpatialSpz(asset.bytes, signal).catch((error: unknown) => {
|
|
552
|
+
if (options.tolerateSplatDecodeFailure) return undefined;
|
|
553
|
+
throw error;
|
|
554
|
+
});
|
|
555
|
+
// Audits may skip a splat that cannot be inspected: the rendered audit
|
|
556
|
+
// analyzer reports unsupported-kind when no bounds are supplied. Beauty
|
|
557
|
+
// rendering keeps the failure so invalid source does not render silently.
|
|
558
|
+
if (inspected === undefined) continue;
|
|
559
|
+
const { facts, modelBounds } = inspected;
|
|
560
|
+
// Entity-local enclosure: the same sourceUp→Y rotation and uniform
|
|
561
|
+
// metersPerUnit scale the renderer applies ahead of the entity
|
|
562
|
+
// transform, so the object-ID proxy and the geometric audit agree.
|
|
563
|
+
const bounds = transformBounds(spatialSplatContentTransform(interpretation), modelBounds);
|
|
454
564
|
const name = `splat-${asset.manifestSha256.slice(0, 40)}`;
|
|
455
565
|
const resource = { name, sha256: asset.manifest.payload.sha256, bytes: asset.bytes.byteLength, mediaType: "application/octet-stream", urlPath: `${name}.spz` };
|
|
456
566
|
if (!resources.has(name)) {
|
|
457
567
|
if (resources.size >= 64 || outputBytes + asset.bytes.byteLength > SPATIAL_ASSET_PREPARATION_LIMITS.outputBytes) throw new RangeError("Prepared splat resources exceed their byte budget.");
|
|
458
568
|
resources.set(name, { ...resource, absolutePath: paths.get(assetId)! }); outputBytes += asset.bytes.byteLength;
|
|
459
569
|
}
|
|
460
|
-
preparedAssets.set(key, PreparedSpatialAssetSchema.parse({ kind: "splat", assetId, entityId: entity.entityId, assetManifestSha256: asset.manifestSha256, resource, facts }));
|
|
570
|
+
preparedAssets.set(key, PreparedSpatialAssetSchema.parse({ kind: "splat", assetId, entityId: entity.entityId, assetManifestSha256: asset.manifestSha256, resource, facts, bounds }));
|
|
461
571
|
profiles.add("slopcamera.spz-v2-v3-spark-2.1.0-full-resolution-v1");
|
|
462
572
|
continue;
|
|
463
573
|
}
|
|
@@ -478,12 +588,18 @@ export async function withPreparedSpatialAssets<Result>(
|
|
|
478
588
|
images.set(image.resourceName, await publishRaster(await raster(image.bytes, image.opaque), image.resourceName));
|
|
479
589
|
}
|
|
480
590
|
const used = new Set<string>();
|
|
591
|
+
const textureSlots = ["texture", "normalTexture", "metallicRoughnessTexture", "occlusionTexture", "emissiveTexture", "clearcoatTexture", "clearcoatRoughnessTexture", "clearcoatNormalTexture", "transmissionTexture", "sheenColorTexture", "sheenRoughnessTexture", "anisotropyTexture"] as const;
|
|
481
592
|
const primitives = geometry.primitives.map(primitive => {
|
|
482
|
-
|
|
483
|
-
const
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
593
|
+
const resolved: Record<string, unknown> = { ...primitive };
|
|
594
|
+
for (const slot of textureSlots) {
|
|
595
|
+
const texture = primitive[slot];
|
|
596
|
+
if (texture === undefined) continue;
|
|
597
|
+
const image = images.get(texture.resource.name);
|
|
598
|
+
if (image === undefined) throw new RangeError("Geometry texture has no exact prepared image bytes.");
|
|
599
|
+
used.add(texture.resource.name);
|
|
600
|
+
resolved[slot] = { ...texture, ...image };
|
|
601
|
+
}
|
|
602
|
+
return resolved;
|
|
487
603
|
});
|
|
488
604
|
if (used.size !== images.size) throw new RangeError("Geometry preparer returned unused image bytes.");
|
|
489
605
|
const resolved = PreparedSpatialAssetSchema.parse({ ...geometry, primitives });
|
|
@@ -497,21 +613,37 @@ export async function withPreparedSpatialAssets<Result>(
|
|
|
497
613
|
...(entity.geometry.nodeIndex === undefined ? {} : { nodeIndex: entity.geometry.nodeIndex }),
|
|
498
614
|
...(entity.geometry.materialMode === undefined ? {} : { materialMode: entity.geometry.materialMode }),
|
|
499
615
|
...(entity.geometry.clip === undefined ? {} : { clip: entity.geometry.clip }),
|
|
616
|
+
...(entity.geometry.morphWeights === undefined ? {} : { morphWeights: entity.geometry.morphWeights }),
|
|
500
617
|
});
|
|
618
|
+
const publishTexture = async (reference: { readonly imageIndex: number; readonly sampler: unknown; readonly transform?: { readonly offset: readonly [number, number]; readonly rotation: number; readonly scale: readonly [number, number] } } | undefined, opaque: boolean) => {
|
|
619
|
+
if (reference === undefined) return undefined;
|
|
620
|
+
const image = geometry.images.find(image => image.imageIndex === reference.imageIndex);
|
|
621
|
+
if (image === undefined) throw new RangeError("GLB texture image is missing.");
|
|
622
|
+
return { ...await publishRaster(await raster(image.bytes, opaque)), flipY: false, sampler: reference.sampler, ...(reference.transform === undefined ? {} : { uvTransform: reference.transform }) };
|
|
623
|
+
};
|
|
501
624
|
const primitives: unknown[] = [];
|
|
502
625
|
for (const primitive of geometry.primitives) {
|
|
503
626
|
const { bounds: _bounds, material: sourceMaterial, ...base } = primitive;
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
627
|
+
const texture = await publishTexture(sourceMaterial?.baseColorTexture, sourceMaterial?.alphaMode === "OPAQUE");
|
|
628
|
+
const metallicRoughnessTexture = await publishTexture(sourceMaterial?.metallicRoughnessTexture, false);
|
|
629
|
+
const normalTexture = await publishTexture(sourceMaterial?.normalTexture, false);
|
|
630
|
+
const occlusionTexture = await publishTexture(sourceMaterial?.occlusionTexture, false);
|
|
631
|
+
const emissiveTexture = await publishTexture(sourceMaterial?.emissiveTexture, false);
|
|
632
|
+
const clearcoatTexture = await publishTexture(sourceMaterial?.clearcoat?.texture, false), clearcoatRoughnessTexture = await publishTexture(sourceMaterial?.clearcoat?.roughnessTexture, false), clearcoatNormalTexture = await publishTexture(sourceMaterial?.clearcoat?.normalTexture, false);
|
|
633
|
+
const transmissionTexture = await publishTexture(sourceMaterial?.transmission?.texture, false), sheenColorTexture = await publishTexture(sourceMaterial?.sheen?.colorTexture, false), sheenRoughnessTexture = await publishTexture(sourceMaterial?.sheen?.roughnessTexture, false), anisotropyTexture = await publishTexture(sourceMaterial?.anisotropy?.texture, false);
|
|
634
|
+
const physical = sourceMaterial === undefined ? undefined : { ...(sourceMaterial.emissiveLinear === undefined ? {} : { emissiveLinear: sourceMaterial.emissiveLinear }), ...(sourceMaterial.emissiveStrength === undefined ? {} : { emissiveStrength: sourceMaterial.emissiveStrength }), ...(sourceMaterial.clearcoat === undefined ? {} : { clearcoat: { factor: sourceMaterial.clearcoat.factor, roughness: sourceMaterial.clearcoat.roughness } }), ...(sourceMaterial.transmission === undefined ? {} : { transmission: sourceMaterial.transmission.factor }), ...(sourceMaterial.sheen === undefined ? {} : { sheen: { colorLinear: sourceMaterial.sheen.colorLinear, roughness: sourceMaterial.sheen.roughness } }), ...(sourceMaterial.anisotropy === undefined ? {} : { anisotropy: { strength: sourceMaterial.anisotropy.strength, rotation: sourceMaterial.anisotropy.rotation } }), ...(sourceMaterial.ior === undefined ? {} : { ior: sourceMaterial.ior }) };
|
|
510
635
|
primitives.push({ ...base,
|
|
511
636
|
...(sourceMaterial === undefined ? {} : { material: { kind: "standard", color: "#ffffff", opacity: sourceMaterial.alphaMode === "OPAQUE" ? 1 : sourceMaterial.baseColorLinear[3], metalness: sourceMaterial.metalness, roughness: sourceMaterial.roughness },
|
|
512
637
|
linearColor: sourceMaterial.baseColorLinear.slice(0, 3), doubleSided: sourceMaterial.doubleSided, alphaMode: sourceMaterial.alphaMode,
|
|
513
638
|
...(sourceMaterial.alphaMode === "MASK" ? { alphaCutoff: sourceMaterial.alphaCutoff } : {}) }),
|
|
514
639
|
...(texture === undefined ? {} : { texture }),
|
|
640
|
+
...(metallicRoughnessTexture === undefined ? {} : { metallicRoughnessTexture }),
|
|
641
|
+
...(normalTexture === undefined ? {} : { normalTexture, ...(sourceMaterial?.normalTexture?.scale === undefined ? {} : { normalScale: sourceMaterial.normalTexture.scale }) }),
|
|
642
|
+
...(occlusionTexture === undefined ? {} : { occlusionTexture, ...(sourceMaterial?.occlusionTexture?.strength === undefined ? {} : { occlusionStrength: sourceMaterial.occlusionTexture.strength }) }),
|
|
643
|
+
...(emissiveTexture === undefined ? {} : { emissiveTexture }),
|
|
644
|
+
...(clearcoatTexture === undefined ? {} : { clearcoatTexture }), ...(clearcoatRoughnessTexture === undefined ? {} : { clearcoatRoughnessTexture }), ...(clearcoatNormalTexture === undefined ? {} : { clearcoatNormalTexture }),
|
|
645
|
+
...(transmissionTexture === undefined ? {} : { transmissionTexture }), ...(sheenColorTexture === undefined ? {} : { sheenColorTexture }), ...(sheenRoughnessTexture === undefined ? {} : { sheenRoughnessTexture }), ...(anisotropyTexture === undefined ? {} : { anisotropyTexture }),
|
|
646
|
+
...(physical === undefined ? {} : { physical }),
|
|
515
647
|
});
|
|
516
648
|
}
|
|
517
649
|
const resolved = PreparedSpatialAssetSchema.parse({ kind: "geometry", assetId, entityId: entity.entityId,
|
|
@@ -534,20 +666,8 @@ export async function withPreparedSpatialAssets<Result>(
|
|
|
534
666
|
if (preparedAssets.has(key)) continue;
|
|
535
667
|
let output: Awaited<ReturnType<typeof publishRaster>>, sourceTimeUs: number | undefined;
|
|
536
668
|
let videoEvidence: { sourceFrameIndex: number; sourcePresentationTimeUs: number; sourceTimestamp: string; sourcePts: number; sourceTimeBase: { numerator: string; denominator: string }; sourceExactTimeUs: { numerator: string; denominator: string } } | undefined;
|
|
537
|
-
if (entity.kind === "image") {
|
|
538
|
-
|
|
539
|
-
let bytes = asset.bytes;
|
|
540
|
-
if (interpretation.mimeType === "image/png" && (bytes[0] !== 137 || bytes[1] !== 80 || bytes[2] !== 78 || bytes[3] !== 71)
|
|
541
|
-
|| interpretation.mimeType === "image/jpeg" && (bytes[0] !== 255 || bytes[1] !== 216)) throw new RangeError("Image payload format differs from its declared MIME type.");
|
|
542
|
-
if (interpretation.mimeType === "image/svg+xml") {
|
|
543
|
-
const svg = inertSpatialSvg(utf8(bytes));
|
|
544
|
-
const resvg = new Resvg(svg, { font: { loadSystemFonts: false } });
|
|
545
|
-
dimensions(resvg.width, resvg.height);
|
|
546
|
-
bytes = resvg.render().asPng(); profiles.add("shape-only-svg-resvg-2.6.2");
|
|
547
|
-
}
|
|
548
|
-
const decoded = await raster(bytes, interpretation.alpha === "opaque");
|
|
549
|
-
if (decoded.width !== interpretation.width || decoded.height !== interpretation.height) throw new RangeError("Image dimensions differ from its interpretation.");
|
|
550
|
-
output = await publishRaster(decoded); profiles.add("sdr-png-jpeg-sharp-0.35.3");
|
|
669
|
+
if (entity.kind === "image" || entity.kind === "environment") {
|
|
670
|
+
output = await decodeImageRaster(asset, entity.kind === "image" ? "Image entity" : "Environment entity");
|
|
551
671
|
} else if (entity.kind === "video") {
|
|
552
672
|
if (interpretation.kind !== "video") throw new RangeError("Video entity requires a video asset interpretation.");
|
|
553
673
|
if (ports.ffmpegCommand === undefined || ports.ffprobeCommand === undefined) capability("video-decoder", "Video surfaces require bound FFmpeg and FFprobe capabilities.");
|
|
@@ -656,6 +776,15 @@ export async function withPreparedSpatialAssets<Result>(
|
|
|
656
776
|
...(entity.kind === "video" ? { entityContentSha256: spatialVideoRasterContentSha256(entity) } : {}),
|
|
657
777
|
}));
|
|
658
778
|
}
|
|
779
|
+
for (const assetId of request.lutAssetIds ?? []) {
|
|
780
|
+
const key = `lut:${assetId}`;
|
|
781
|
+
if (preparedAssets.has(key)) continue;
|
|
782
|
+
const asset = verified.get(assetId);
|
|
783
|
+
if (asset === undefined) throw new RangeError(`Missing manifest for LUT asset ${assetId}.`);
|
|
784
|
+
const output = await decodeImageRaster(asset, `LUT asset ${assetId}`);
|
|
785
|
+
preparedAssets.set(key, PreparedSpatialAssetSchema.parse({ kind: "lut", assetId, assetManifestSha256: asset.manifestSha256, ...output }));
|
|
786
|
+
aborted(signal);
|
|
787
|
+
}
|
|
659
788
|
aborted(signal);
|
|
660
789
|
const prepared = [...preparedAssets.values()];
|
|
661
790
|
completed = { value: await consume(Object.freeze({ preparedAssets: prepared, resources: [...resources.values()],
|