@nodaro/shared 2.26.0 → 3.0.0

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/dist/index.cjs CHANGED
@@ -4133,7 +4133,12 @@ var COMPOSER_PLAN_MAP = {
4133
4133
  "lottie-overlay": { planType: "lottie-overlay", planField: "overlayPlan" },
4134
4134
  "3d-title": { planType: "3d-title", planField: "titlePlan" },
4135
4135
  "motion-graphics": { planType: "motion-graphics", planField: "motionPlan" },
4136
- "composite": { planType: "composite", planField: "compositePlan" }
4136
+ "composite": { planType: "composite", planField: "compositePlan" },
4137
+ // Scene3D previz (v1) — both the generator and the editor emit the SAME
4138
+ // validated `Scene3DPlan` revision on their `composition` handle, so
4139
+ // render-video routes either one to the `3d-scene` renderer unchanged.
4140
+ "generate-3d-scene": { planType: "3d-scene", planField: "scenePlan" },
4141
+ "edit-3d-scene": { planType: "3d-scene", planField: "scenePlan" }
4137
4142
  };
4138
4143
  var COMPOSER_PLAN_FIELDS = [
4139
4144
  ...new Set(Object.values(COMPOSER_PLAN_MAP).map((m) => m.planField))
@@ -5882,6 +5887,7 @@ var LLM_FEATURE_DEFAULTS = {
5882
5887
  "motion-graphics-lottie": "claude-sonnet-4.6",
5883
5888
  "lottie-overlay": "claude-sonnet-4.6",
5884
5889
  "3d-title": "claude-sonnet-4.6",
5890
+ "3d-scene": "claude-sonnet-4.6",
5885
5891
  "image-to-text": "claude-sonnet-4.6",
5886
5892
  "describe-to-picker": "claude-opus-5",
5887
5893
  "qa-check": "gemini-3.6-flash",
@@ -5982,7 +5988,10 @@ var LLM_ROUTE_DEFAULTS = {
5982
5988
  "lottie-overlay": { temperature: 0.3, maxTokens: 2048, structuredOutput: true },
5983
5989
  "motion-graphics": { temperature: 0.3, maxTokens: 2048, structuredOutput: true },
5984
5990
  "motion-graphics-lottie": { temperature: 0.3, maxTokens: 8192, structuredOutput: true },
5985
- "3d-title": { temperature: 0.4, maxTokens: 3072, structuredOutput: true }
5991
+ "3d-title": { temperature: 0.4, maxTokens: 3072, structuredOutput: true },
5992
+ // A 100-object plan with keyframe tracks is the biggest structured payload
5993
+ // any composer feature emits — 3072 (3d-title's cap) truncates it mid-array.
5994
+ "3d-scene": { temperature: 0.3, maxTokens: 8192, structuredOutput: true }
5986
5995
  };
5987
5996
  function llmRouteDefaults(feature) {
5988
5997
  return feature && LLM_ROUTE_DEFAULTS[feature] || {};
@@ -8765,6 +8774,8 @@ var NODE_MAPPABLE_FIELDS = {
8765
8774
  "after-effects": ["effectPrompt"],
8766
8775
  "lottie-overlay": ["overlayPrompt"],
8767
8776
  "3d-title": ["titlePrompt"],
8777
+ "generate-3d-scene": ["scenePrompt"],
8778
+ "edit-3d-scene": ["editPrompt"],
8768
8779
  "motion-graphics": ["motionPrompt"],
8769
8780
  "generate-script": ["styleGuide"],
8770
8781
  "speech-to-video": ["prompt", "negativePrompt"],
@@ -13142,8 +13153,22 @@ function readPromptAffixes(data) {
13142
13153
  }
13143
13154
 
13144
13155
  // src/node-preset-extract.ts
13145
- var PRESET_APPLY_CLEAR_KEYS = [...COMPOSER_PLAN_FIELDS, "lottieUrl"];
13146
- var PROMPT_CONTENT_KEYS = ["prompt", PROMPT_PREFIX_KEY, PROMPT_SUFFIX_KEY];
13156
+ var PRESET_APPLY_CLEAR_KEYS = [
13157
+ ...COMPOSER_PLAN_FIELDS,
13158
+ "lottieUrl",
13159
+ // Scene revision history contains old plans and their reference/prompt context.
13160
+ // Preserve it in workflows, never capture or resurrect it through a preset.
13161
+ "sceneHistory",
13162
+ "scenePendingPlan",
13163
+ "sceneJobBaseRevisionId",
13164
+ "expectedRevisionId",
13165
+ "changeSummary",
13166
+ "selectedObjectIds",
13167
+ "lockedObjectIds",
13168
+ "referenceObjectIds",
13169
+ "referenceRoles"
13170
+ ];
13171
+ var PROMPT_CONTENT_KEYS = ["prompt", "scenePrompt", "editPrompt", PROMPT_PREFIX_KEY, PROMPT_SUFFIX_KEY];
13147
13172
  function presetApplyClearKeys(presetData) {
13148
13173
  const ownsPromptContent = PROMPT_CONTENT_KEYS.some((k) => presetData[k] !== void 0);
13149
13174
  return ownsPromptContent ? [...PRESET_APPLY_CLEAR_KEYS, PROMPT_PREFIX_KEY, PROMPT_SUFFIX_KEY] : PRESET_APPLY_CLEAR_KEYS;
@@ -14678,6 +14703,1645 @@ function entityHydrationColumns(kind) {
14678
14703
  function entityScalarFields(kind) {
14679
14704
  return [...ENTITY_SCALAR_FIELDS, ...ENTITY_KIND_SCALAR_FIELDS[kind]];
14680
14705
  }
14706
+ var SCENE3D_PLAN_TYPE = "3d-scene";
14707
+ var SCENE3D_SCHEMA_VERSION = 1;
14708
+ var SCENE3D_DEFAULT_FPS = 24;
14709
+ var SCENE3D_DEFAULT_DURATION_SECONDS = 4;
14710
+ var SCENE3D_LIMITS = {
14711
+ minDimensionPx: 100,
14712
+ maxDimensionPx: 1920,
14713
+ minFps: 15,
14714
+ maxFps: 60,
14715
+ minDurationInFrames: 1,
14716
+ maxDurationInFrames: 3600,
14717
+ /** Shortest scene an authoring request may ask for, in seconds. One second
14718
+ * is the frozen v1 floor the SDK/MCP surface and the public docs state; the
14719
+ * route Zod and the canvas path both quote it from here so they cannot
14720
+ * drift below it. */
14721
+ minDurationSeconds: 1,
14722
+ /** Hard ceiling on wall-clock length, checked against fps × frames. */
14723
+ maxDurationSeconds: 60,
14724
+ minObjects: 1,
14725
+ maxObjects: 100,
14726
+ /** Per-object and per-camera track length. */
14727
+ maxKeyframes: 240,
14728
+ maxReferences: 8,
14729
+ maxOperations: 100,
14730
+ /** |x|, |y|, |z| ceiling for positions and camera/target coordinates. */
14731
+ maxCoordinate: 1e3,
14732
+ minSize: 1e-3,
14733
+ maxSize: 1e3,
14734
+ minScale: 1e-3,
14735
+ maxScale: 1e3,
14736
+ minFocalLengthMm: 10,
14737
+ maxFocalLengthMm: 200,
14738
+ defaultSensorWidthMm: 36,
14739
+ minSensorWidthMm: 1,
14740
+ maxSensorWidthMm: 200,
14741
+ maxIntensity: 100,
14742
+ /** How deep a parent chain may nest. Bounds the renderer's transform walk. */
14743
+ maxHierarchyDepth: 8,
14744
+ maxIdLength: 64,
14745
+ maxNameLength: 120,
14746
+ maxUrlLength: 2048,
14747
+ maxChangeSummaryLength: 2e3
14748
+ };
14749
+ var SCENE3D_PRIMITIVES = [
14750
+ "box",
14751
+ "sphere",
14752
+ "cylinder",
14753
+ "cone",
14754
+ "plane",
14755
+ "capsule",
14756
+ "group"
14757
+ ];
14758
+ function boundedVec3(max, label) {
14759
+ return zod.z.tuple([
14760
+ zod.z.number().min(-max).max(max),
14761
+ zod.z.number().min(-max).max(max),
14762
+ zod.z.number().min(-max).max(max)
14763
+ ]).describe(label);
14764
+ }
14765
+ var vec3Schema = boundedVec3(SCENE3D_LIMITS.maxCoordinate, "position triple (meters)");
14766
+ var sizeVec3Schema = zod.z.tuple([
14767
+ zod.z.number().min(SCENE3D_LIMITS.minSize).max(SCENE3D_LIMITS.maxSize),
14768
+ zod.z.number().min(SCENE3D_LIMITS.minSize).max(SCENE3D_LIMITS.maxSize),
14769
+ zod.z.number().min(SCENE3D_LIMITS.minSize).max(SCENE3D_LIMITS.maxSize)
14770
+ ]);
14771
+ var scaleVec3Schema = zod.z.tuple([
14772
+ zod.z.number().min(SCENE3D_LIMITS.minScale).max(SCENE3D_LIMITS.maxScale),
14773
+ zod.z.number().min(SCENE3D_LIMITS.minScale).max(SCENE3D_LIMITS.maxScale),
14774
+ zod.z.number().min(SCENE3D_LIMITS.minScale).max(SCENE3D_LIMITS.maxScale)
14775
+ ]);
14776
+ var rotationVec3Schema = boundedVec3(1e3, "euler XYZ (radians)");
14777
+ var HEX_COLOR = /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
14778
+ var scene3DColorSchema = zod.z.string().regex(HEX_COLOR, "color must be an opaque hex string such as #4f8ef7 (3 or 6 digits; alpha is not supported)");
14779
+ var SCENE3D_ID = /^[A-Za-z0-9][A-Za-z0-9_.-]*$/;
14780
+ var scene3DIdSchema = zod.z.string().min(1).max(SCENE3D_LIMITS.maxIdLength).regex(SCENE3D_ID, "id must start alphanumeric and contain only letters, digits, '_', '-' or '.'");
14781
+ function isScene3DHttpUrl(value) {
14782
+ try {
14783
+ const parsed = new URL(value);
14784
+ return parsed.protocol === "http:" || parsed.protocol === "https:";
14785
+ } catch {
14786
+ return false;
14787
+ }
14788
+ }
14789
+ var scene3DUrlSchema = zod.z.string().min(1).max(SCENE3D_LIMITS.maxUrlLength).refine(isScene3DHttpUrl, "url must be an http(s) URL");
14790
+ var scene3DEasingSchema = zod.z.enum(["linear", "easeInOut"]);
14791
+ var scene3DPrimitiveSchema = zod.z.enum([
14792
+ "box",
14793
+ "sphere",
14794
+ "cylinder",
14795
+ "cone",
14796
+ "plane",
14797
+ "capsule",
14798
+ "group"
14799
+ ]);
14800
+ var frameSchema = zod.z.number().int().min(0).max(SCENE3D_LIMITS.maxDurationInFrames);
14801
+ var scene3DObjectKeyframeSchema = zod.z.object({
14802
+ frame: frameSchema,
14803
+ position: vec3Schema.optional(),
14804
+ rotation: rotationVec3Schema.optional(),
14805
+ scale: scaleVec3Schema.optional(),
14806
+ easing: scene3DEasingSchema.optional()
14807
+ }).strict();
14808
+ var scene3DCameraKeyframeSchema = zod.z.object({
14809
+ frame: frameSchema,
14810
+ position: vec3Schema.optional(),
14811
+ target: vec3Schema.optional(),
14812
+ focalLengthMm: zod.z.number().min(SCENE3D_LIMITS.minFocalLengthMm).max(SCENE3D_LIMITS.maxFocalLengthMm).optional(),
14813
+ easing: scene3DEasingSchema.optional()
14814
+ }).strict();
14815
+ var scene3DObjectSchema = zod.z.object({
14816
+ id: scene3DIdSchema,
14817
+ name: zod.z.string().min(1).max(SCENE3D_LIMITS.maxNameLength),
14818
+ primitive: scene3DPrimitiveSchema,
14819
+ parentId: scene3DIdSchema.optional(),
14820
+ dimensions: sizeVec3Schema,
14821
+ position: vec3Schema,
14822
+ rotation: rotationVec3Schema,
14823
+ scale: scaleVec3Schema,
14824
+ color: scene3DColorSchema,
14825
+ keyframes: zod.z.array(scene3DObjectKeyframeSchema).max(SCENE3D_LIMITS.maxKeyframes).optional()
14826
+ }).strict();
14827
+ var scene3DCameraSchema = zod.z.object({
14828
+ position: vec3Schema,
14829
+ target: vec3Schema,
14830
+ focalLengthMm: zod.z.number().min(SCENE3D_LIMITS.minFocalLengthMm).max(SCENE3D_LIMITS.maxFocalLengthMm),
14831
+ sensorWidthMm: zod.z.number().min(SCENE3D_LIMITS.minSensorWidthMm).max(SCENE3D_LIMITS.maxSensorWidthMm).default(SCENE3D_LIMITS.defaultSensorWidthMm),
14832
+ keyframes: zod.z.array(scene3DCameraKeyframeSchema).max(SCENE3D_LIMITS.maxKeyframes).optional()
14833
+ }).strict();
14834
+ var scene3DLightingSchema = zod.z.object({
14835
+ ambientIntensity: zod.z.number().min(0).max(SCENE3D_LIMITS.maxIntensity),
14836
+ keyIntensity: zod.z.number().min(0).max(SCENE3D_LIMITS.maxIntensity),
14837
+ keyPosition: vec3Schema
14838
+ }).strict();
14839
+ var scene3DReferenceSchema = zod.z.object({
14840
+ id: scene3DIdSchema,
14841
+ url: scene3DUrlSchema,
14842
+ kind: zod.z.enum(["image", "video"]),
14843
+ role: zod.z.enum(["appearance", "layout", "motion"]),
14844
+ objectId: scene3DIdSchema.optional(),
14845
+ startSeconds: zod.z.number().min(0).max(86400).optional(),
14846
+ endSeconds: zod.z.number().min(0).max(86400).optional()
14847
+ }).strict();
14848
+ function checkKeyframeTrack(frames, durationInFrames, path, issues) {
14849
+ let previous = -1;
14850
+ frames.forEach((kf, index) => {
14851
+ if (kf.frame > durationInFrames - 1) {
14852
+ issues.push({
14853
+ path: [...path, index, "frame"],
14854
+ message: `frame ${kf.frame} is past the scene's last frame (${durationInFrames - 1})`
14855
+ });
14856
+ }
14857
+ if (kf.frame === previous) {
14858
+ issues.push({ path: [...path, index, "frame"], message: `duplicate keyframe at frame ${kf.frame}` });
14859
+ } else if (kf.frame < previous) {
14860
+ issues.push({
14861
+ path: [...path, index, "frame"],
14862
+ message: `keyframes must be sorted by frame (${kf.frame} follows ${previous})`
14863
+ });
14864
+ }
14865
+ previous = kf.frame;
14866
+ });
14867
+ }
14868
+ function scene3DPlanV1Issues(plan) {
14869
+ const issues = [];
14870
+ const seconds = plan.durationInFrames / plan.fps;
14871
+ if (seconds > SCENE3D_LIMITS.maxDurationSeconds) {
14872
+ issues.push({
14873
+ path: ["durationInFrames"],
14874
+ message: `scene is ${seconds.toFixed(2)}s; the limit is ${SCENE3D_LIMITS.maxDurationSeconds}s`
14875
+ });
14876
+ }
14877
+ const byId = /* @__PURE__ */ new Map();
14878
+ plan.objects.forEach((object, index) => {
14879
+ if (byId.has(object.id)) {
14880
+ issues.push({ path: ["objects", index, "id"], message: `duplicate object id "${object.id}"` });
14881
+ return;
14882
+ }
14883
+ byId.set(object.id, object);
14884
+ });
14885
+ plan.objects.forEach((object, index) => {
14886
+ if (object.parentId === void 0) return;
14887
+ if (object.parentId === object.id) {
14888
+ issues.push({ path: ["objects", index, "parentId"], message: `object "${object.id}" cannot parent itself` });
14889
+ return;
14890
+ }
14891
+ if (!byId.has(object.parentId)) {
14892
+ issues.push({
14893
+ path: ["objects", index, "parentId"],
14894
+ message: `object "${object.id}" references unknown parent "${object.parentId}"`
14895
+ });
14896
+ return;
14897
+ }
14898
+ const seen = /* @__PURE__ */ new Set([object.id]);
14899
+ let cursor = byId.get(object.parentId);
14900
+ let depth = 1;
14901
+ while (cursor) {
14902
+ if (seen.has(cursor.id)) {
14903
+ issues.push({
14904
+ path: ["objects", index, "parentId"],
14905
+ message: `parent cycle through object "${cursor.id}"`
14906
+ });
14907
+ break;
14908
+ }
14909
+ seen.add(cursor.id);
14910
+ depth += 1;
14911
+ if (depth > SCENE3D_LIMITS.maxHierarchyDepth) {
14912
+ issues.push({
14913
+ path: ["objects", index, "parentId"],
14914
+ message: `hierarchy deeper than ${SCENE3D_LIMITS.maxHierarchyDepth} levels`
14915
+ });
14916
+ break;
14917
+ }
14918
+ cursor = cursor.parentId === void 0 ? void 0 : byId.get(cursor.parentId);
14919
+ }
14920
+ });
14921
+ plan.objects.forEach((object, index) => {
14922
+ if (object.keyframes && object.keyframes.length > 0) {
14923
+ checkKeyframeTrack(object.keyframes, plan.durationInFrames, ["objects", index, "keyframes"], issues);
14924
+ }
14925
+ });
14926
+ if (plan.camera.keyframes && plan.camera.keyframes.length > 0) {
14927
+ checkKeyframeTrack(plan.camera.keyframes, plan.durationInFrames, ["camera", "keyframes"], issues);
14928
+ }
14929
+ const referenceIds = /* @__PURE__ */ new Set();
14930
+ (plan.references ?? []).forEach((reference, index) => {
14931
+ if (referenceIds.has(reference.id)) {
14932
+ issues.push({ path: ["references", index, "id"], message: `duplicate reference id "${reference.id}"` });
14933
+ }
14934
+ referenceIds.add(reference.id);
14935
+ if (reference.objectId !== void 0 && !byId.has(reference.objectId)) {
14936
+ issues.push({
14937
+ path: ["references", index, "objectId"],
14938
+ message: `reference "${reference.id}" points at unknown object "${reference.objectId}"`
14939
+ });
14940
+ }
14941
+ if (reference.startSeconds !== void 0 && reference.endSeconds !== void 0 && reference.endSeconds <= reference.startSeconds) {
14942
+ issues.push({
14943
+ path: ["references", index, "endSeconds"],
14944
+ message: `reference "${reference.id}" ends at or before it starts`
14945
+ });
14946
+ }
14947
+ if (reference.kind === "image" && (reference.startSeconds !== void 0 || reference.endSeconds !== void 0)) {
14948
+ issues.push({
14949
+ path: ["references", index, "startSeconds"],
14950
+ message: `reference "${reference.id}" is an image; a time window applies to video only`
14951
+ });
14952
+ }
14953
+ });
14954
+ return issues;
14955
+ }
14956
+ var scene3DPlanV1ObjectSchema = zod.z.object({
14957
+ planType: zod.z.literal(SCENE3D_PLAN_TYPE),
14958
+ schemaVersion: zod.z.literal(SCENE3D_SCHEMA_VERSION),
14959
+ revisionId: zod.z.uuid(),
14960
+ parentRevisionId: zod.z.uuid().optional(),
14961
+ width: zod.z.number().int().min(SCENE3D_LIMITS.minDimensionPx).max(SCENE3D_LIMITS.maxDimensionPx),
14962
+ height: zod.z.number().int().min(SCENE3D_LIMITS.minDimensionPx).max(SCENE3D_LIMITS.maxDimensionPx),
14963
+ fps: zod.z.number().int().min(SCENE3D_LIMITS.minFps).max(SCENE3D_LIMITS.maxFps),
14964
+ durationInFrames: zod.z.number().int().min(SCENE3D_LIMITS.minDurationInFrames).max(SCENE3D_LIMITS.maxDurationInFrames),
14965
+ backgroundColor: scene3DColorSchema,
14966
+ camera: scene3DCameraSchema,
14967
+ objects: zod.z.array(scene3DObjectSchema).min(SCENE3D_LIMITS.minObjects).max(SCENE3D_LIMITS.maxObjects),
14968
+ lighting: scene3DLightingSchema,
14969
+ references: zod.z.array(scene3DReferenceSchema).max(SCENE3D_LIMITS.maxReferences).optional()
14970
+ }).strict();
14971
+ var scene3DPlanV1Schema = scene3DPlanV1ObjectSchema.superRefine((plan, ctx) => {
14972
+ for (const issue2 of scene3DPlanV1Issues(plan)) {
14973
+ ctx.addIssue({ code: "custom", path: issue2.path, message: issue2.message });
14974
+ }
14975
+ });
14976
+ var scene3DPlanSchema = scene3DPlanV1Schema;
14977
+ var scene3DPlanIssues = scene3DPlanV1Issues;
14978
+ function scene3DDeepEqual(a, b) {
14979
+ if (a === b) return true;
14980
+ if (typeof a !== typeof b) return false;
14981
+ if (a === null || b === null || typeof a !== "object") return false;
14982
+ if (Array.isArray(a) !== Array.isArray(b)) return false;
14983
+ if (Array.isArray(a) && Array.isArray(b)) {
14984
+ if (a.length !== b.length) return false;
14985
+ return a.every((item, i) => scene3DDeepEqual(item, b[i]));
14986
+ }
14987
+ const aObj = a;
14988
+ const bObj = b;
14989
+ const aKeys = Object.keys(aObj);
14990
+ const bKeys = Object.keys(bObj);
14991
+ if (aKeys.length !== bKeys.length) return false;
14992
+ return aKeys.every((key) => key in bObj && scene3DDeepEqual(aObj[key], bObj[key]));
14993
+ }
14994
+ function newScene3DRevisionId() {
14995
+ const webCrypto = globalThis.crypto;
14996
+ if (webCrypto && typeof webCrypto.randomUUID === "function") return webCrypto.randomUUID();
14997
+ const bytes = new Uint8Array(16);
14998
+ if (webCrypto && typeof webCrypto.getRandomValues === "function") {
14999
+ webCrypto.getRandomValues(bytes);
15000
+ } else {
15001
+ for (let i = 0; i < bytes.length; i++) bytes[i] = Math.floor(Math.random() * 256);
15002
+ }
15003
+ bytes[6] = bytes[6] & 15 | 64;
15004
+ bytes[8] = bytes[8] & 63 | 128;
15005
+ const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
15006
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
15007
+ }
15008
+ function isScene3DPlanV1(value) {
15009
+ return scene3DPlanV1Schema.safeParse(value).success;
15010
+ }
15011
+ var SCENE3D_PLAN_FIELD = "scenePlan";
15012
+ var SCENE3D_GENERATE_NODE_TYPE = "generate-3d-scene";
15013
+ var SCENE3D_EDIT_NODE_TYPE = "edit-3d-scene";
15014
+ var scene3DObjectChangesSchema = zod.z.object({
15015
+ name: zod.z.string().min(1).max(SCENE3D_LIMITS.maxNameLength).optional(),
15016
+ primitive: scene3DPrimitiveSchema.optional(),
15017
+ /** `null` detaches from the parent; omitted leaves it as-is. */
15018
+ parentId: scene3DIdSchema.nullable().optional(),
15019
+ dimensions: sizeVec3Schema.optional(),
15020
+ position: vec3Schema.optional(),
15021
+ rotation: rotationVec3Schema.optional(),
15022
+ scale: scaleVec3Schema.optional(),
15023
+ color: scene3DColorSchema.optional(),
15024
+ keyframes: zod.z.array(scene3DObjectKeyframeSchema).max(SCENE3D_LIMITS.maxKeyframes).optional()
15025
+ }).strict();
15026
+ var scene3DCameraChangesSchema = zod.z.object({
15027
+ position: vec3Schema.optional(),
15028
+ target: vec3Schema.optional(),
15029
+ focalLengthMm: zod.z.number().min(SCENE3D_LIMITS.minFocalLengthMm).max(SCENE3D_LIMITS.maxFocalLengthMm).optional(),
15030
+ sensorWidthMm: zod.z.number().min(SCENE3D_LIMITS.minSensorWidthMm).max(SCENE3D_LIMITS.maxSensorWidthMm).optional(),
15031
+ keyframes: zod.z.array(scene3DCameraKeyframeSchema).max(SCENE3D_LIMITS.maxKeyframes).optional()
15032
+ }).strict();
15033
+ var scene3DLightingChangesSchema = zod.z.object({
15034
+ ambientIntensity: zod.z.number().min(0).max(SCENE3D_LIMITS.maxIntensity).optional(),
15035
+ keyIntensity: zod.z.number().min(0).max(SCENE3D_LIMITS.maxIntensity).optional(),
15036
+ keyPosition: vec3Schema.optional()
15037
+ }).strict();
15038
+ var scene3DEditOperationSchema = zod.z.discriminatedUnion("op", [
15039
+ zod.z.object({ op: zod.z.literal("set-object"), objectId: scene3DIdSchema, changes: scene3DObjectChangesSchema }).strict(),
15040
+ zod.z.object({ op: zod.z.literal("add-object"), object: scene3DObjectSchema }).strict(),
15041
+ zod.z.object({ op: zod.z.literal("remove-object"), objectId: scene3DIdSchema }).strict(),
15042
+ zod.z.object({ op: zod.z.literal("set-camera"), changes: scene3DCameraChangesSchema }).strict(),
15043
+ zod.z.object({ op: zod.z.literal("set-lighting"), changes: scene3DLightingChangesSchema }).strict(),
15044
+ zod.z.object({ op: zod.z.literal("set-background"), color: scene3DColorSchema }).strict()
15045
+ ]);
15046
+ var scene3DEditOperationsSchema = zod.z.array(scene3DEditOperationSchema).min(1).max(SCENE3D_LIMITS.maxOperations);
15047
+ function clonePlan(plan) {
15048
+ return JSON.parse(JSON.stringify(plan));
15049
+ }
15050
+ function summarizeScene3DOperations(operations) {
15051
+ const lines = operations.map((operation) => {
15052
+ switch (operation.op) {
15053
+ case "set-object": {
15054
+ const fields = Object.keys(operation.changes);
15055
+ return `Updated ${fields.length > 0 ? fields.join(", ") : "nothing"} on "${operation.objectId}"`;
15056
+ }
15057
+ case "add-object":
15058
+ return `Added ${operation.object.primitive} "${operation.object.name}" (${operation.object.id})`;
15059
+ case "remove-object":
15060
+ return `Removed "${operation.objectId}"`;
15061
+ case "set-camera":
15062
+ return `Updated camera ${Object.keys(operation.changes).join(", ") || "nothing"}`;
15063
+ case "set-lighting":
15064
+ return `Updated lighting ${Object.keys(operation.changes).join(", ") || "nothing"}`;
15065
+ case "set-background":
15066
+ return `Set background to ${operation.color}`;
15067
+ }
15068
+ });
15069
+ return lines.join("; ").slice(0, SCENE3D_LIMITS.maxChangeSummaryLength);
15070
+ }
15071
+ function firstIssueMessage(error) {
15072
+ const issue2 = error.issues[0];
15073
+ if (!issue2) return "invalid";
15074
+ const path = issue2.path.join(".");
15075
+ return path ? `${path}: ${issue2.message}` : issue2.message;
15076
+ }
15077
+ function applyScene3DEditOperations(plan, operations, options = {}) {
15078
+ const parsedPlan = scene3DPlanV1Schema.safeParse(plan);
15079
+ if (!parsedPlan.success) {
15080
+ return { ok: false, code: "invalid_plan", message: `scenePlan is invalid \u2014 ${firstIssueMessage(parsedPlan.error)}` };
15081
+ }
15082
+ const source = parsedPlan.data;
15083
+ if (options.expectedRevisionId !== void 0 && options.expectedRevisionId !== source.revisionId) {
15084
+ return {
15085
+ ok: false,
15086
+ code: "stale_revision",
15087
+ message: `This scene has moved on \u2014 expected revision ${options.expectedRevisionId}, the plan is at ${source.revisionId}.`
15088
+ };
15089
+ }
15090
+ const parsedOps = scene3DEditOperationsSchema.safeParse(operations);
15091
+ if (!parsedOps.success) {
15092
+ const issue2 = parsedOps.error.issues[0];
15093
+ const index = typeof issue2?.path[0] === "number" ? issue2.path[0] : void 0;
15094
+ return {
15095
+ ok: false,
15096
+ code: "invalid_operations",
15097
+ message: `operations are invalid \u2014 ${firstIssueMessage(parsedOps.error)}`,
15098
+ ...index === void 0 ? {} : { operationIndex: index }
15099
+ };
15100
+ }
15101
+ const ops = parsedOps.data;
15102
+ const next = clonePlan(source);
15103
+ const changed = /* @__PURE__ */ new Set();
15104
+ for (let index = 0; index < ops.length; index++) {
15105
+ const operation = ops[index];
15106
+ switch (operation.op) {
15107
+ case "set-object": {
15108
+ const target = next.objects.findIndex((o) => o.id === operation.objectId);
15109
+ if (target === -1) {
15110
+ return {
15111
+ ok: false,
15112
+ code: "unknown_object",
15113
+ message: `no object "${operation.objectId}" in this scene`,
15114
+ operationIndex: index
15115
+ };
15116
+ }
15117
+ const { parentId, ...rest } = operation.changes;
15118
+ const updated = { ...next.objects[target], ...rest };
15119
+ if (parentId !== void 0) {
15120
+ if (parentId === null) delete updated.parentId;
15121
+ else updated.parentId = parentId;
15122
+ }
15123
+ next.objects = next.objects.map((o, i) => i === target ? updated : o);
15124
+ changed.add(operation.objectId);
15125
+ break;
15126
+ }
15127
+ case "add-object": {
15128
+ if (next.objects.some((o) => o.id === operation.object.id)) {
15129
+ return {
15130
+ ok: false,
15131
+ code: "duplicate_object",
15132
+ message: `an object with id "${operation.object.id}" already exists`,
15133
+ operationIndex: index
15134
+ };
15135
+ }
15136
+ next.objects = [...next.objects, operation.object];
15137
+ changed.add(operation.object.id);
15138
+ break;
15139
+ }
15140
+ case "remove-object": {
15141
+ if (!next.objects.some((o) => o.id === operation.objectId)) {
15142
+ return {
15143
+ ok: false,
15144
+ code: "unknown_object",
15145
+ message: `no object "${operation.objectId}" in this scene`,
15146
+ operationIndex: index
15147
+ };
15148
+ }
15149
+ next.objects = next.objects.filter((o) => o.id !== operation.objectId);
15150
+ changed.add(operation.objectId);
15151
+ break;
15152
+ }
15153
+ case "set-camera":
15154
+ next.camera = { ...next.camera, ...operation.changes };
15155
+ break;
15156
+ case "set-lighting":
15157
+ next.lighting = { ...next.lighting, ...operation.changes };
15158
+ break;
15159
+ case "set-background":
15160
+ next.backgroundColor = operation.color;
15161
+ break;
15162
+ }
15163
+ }
15164
+ for (const lockedId of options.lockedObjectIds ?? []) {
15165
+ const before = source.objects.find((o) => o.id === lockedId);
15166
+ const after = next.objects.find((o) => o.id === lockedId);
15167
+ if (before === void 0) continue;
15168
+ if (after === void 0) {
15169
+ return { ok: false, code: "locked_object", message: `object "${lockedId}" is locked and cannot be removed` };
15170
+ }
15171
+ if (!scene3DDeepEqual(before, after)) {
15172
+ return { ok: false, code: "locked_object", message: `object "${lockedId}" is locked and cannot be modified` };
15173
+ }
15174
+ }
15175
+ next.parentRevisionId = source.revisionId;
15176
+ next.revisionId = options.revisionId ?? newScene3DRevisionId();
15177
+ const validated = scene3DPlanV1Schema.safeParse(next);
15178
+ if (!validated.success) {
15179
+ return {
15180
+ ok: false,
15181
+ code: "invalid_plan",
15182
+ message: `the edit would leave the scene invalid \u2014 ${firstIssueMessage(validated.error)}`
15183
+ };
15184
+ }
15185
+ return {
15186
+ ok: true,
15187
+ plan: validated.data,
15188
+ changedObjectIds: [...changed],
15189
+ changeSummary: summarizeScene3DOperations(ops)
15190
+ };
15191
+ }
15192
+ var SCENE3D_SCHEMA_VERSION_V2 = 2;
15193
+ var SCENE3D_SUPPORTED_SCHEMA_VERSIONS = [1, 2];
15194
+ var SCENE3D_V2_ENGINES = ["blender-cloud", "blender-local"];
15195
+ var SCENE3D_V2_LIMITS = {
15196
+ /** Both the seconds and the frame ceiling apply; neither waives the other. */
15197
+ maxDurationSeconds: 60,
15198
+ minDurationInFrames: 1,
15199
+ maxDurationInFrames: 3600,
15200
+ minFps: 15,
15201
+ maxFps: 60,
15202
+ defaultFps: 24,
15203
+ /** Even integers only — an odd axis breaks H.264 chroma subsampling. */
15204
+ minDimensionPx: 100,
15205
+ maxDimensionPx: 1920,
15206
+ minEntities: 1,
15207
+ /** SEMANTIC entities, not exported mesh nodes. */
15208
+ maxEntities: 100,
15209
+ /** Enforced during asset normalization, after decode — see
15210
+ * `scene3DV2NormalizationIssues` in `scene3d-v2-resources.ts`. */
15211
+ maxMeshNodes: 2e3,
15212
+ maxTriangles: 2e5,
15213
+ maxHierarchyDepth: 16,
15214
+ /** Decoded manifest JSON. Measured on the bytes, before `JSON.parse`. */
15215
+ maxManifestBytes: 512 * 1024,
15216
+ /** Decoded camera-track JSON. */
15217
+ maxCameraTrackBytes: 8 * 1024 * 1024,
15218
+ /** Total DECLARED bytes of the assets the renderer downloads. Compression
15219
+ * does not waive the decoded geometry limits above. */
15220
+ maxRendererAssetBytes: 64 * 1024 * 1024,
15221
+ /** A `blend-source` is a separately authorized download, never handed to the
15222
+ * browser renderer, and therefore not part of the renderer budget. */
15223
+ maxBlendSourceBytes: 512 * 1024 * 1024,
15224
+ maxAssets: 64,
15225
+ maxShots: 32,
15226
+ maxShotEntityIds: 16,
15227
+ /** v1's reference limit, unchanged until deliberately expanded. */
15228
+ maxReferences: SCENE3D_LIMITS.maxReferences,
15229
+ maxAnchorsPerEntity: 32,
15230
+ maxMaterialBindingsPerEntity: 16,
15231
+ maxOverrides: 200,
15232
+ minPosterDimensionPx: 16,
15233
+ maxPosterDimensionPx: 4096,
15234
+ maxIdLength: SCENE3D_LIMITS.maxIdLength,
15235
+ maxAssetIdLength: 128,
15236
+ maxNodeIdLength: 128,
15237
+ maxNameLength: SCENE3D_LIMITS.maxNameLength,
15238
+ maxLabelLength: SCENE3D_LIMITS.maxNameLength,
15239
+ maxMaterialNameLength: 120,
15240
+ maxVersionLength: 64,
15241
+ maxCoordinate: SCENE3D_LIMITS.maxCoordinate,
15242
+ minSize: SCENE3D_LIMITS.minSize,
15243
+ maxSize: SCENE3D_LIMITS.maxSize,
15244
+ maxIntensity: SCENE3D_LIMITS.maxIntensity
15245
+ };
15246
+ var SCENE3D_V2_OVERRIDE_OPERATION_VERSION = 1;
15247
+ var SCENE3D_GLB_EXTRAS_ENTITY_ID = "nodaroEntityId";
15248
+ var SCENE3D_GLB_EXTRAS_SUBPART_ID = "nodaroSubpartId";
15249
+ var SCENE3D_GLB_EXTRAS_MATERIAL_ROLE = "nodaroMaterialRole";
15250
+ var SCENE3D_GLB_EXTRAS_ALLOWLIST = [
15251
+ SCENE3D_GLB_EXTRAS_ENTITY_ID,
15252
+ SCENE3D_GLB_EXTRAS_SUBPART_ID,
15253
+ SCENE3D_GLB_EXTRAS_MATERIAL_ROLE
15254
+ ];
15255
+ var SCENE3D_ENTITY_ROLES = [
15256
+ "person",
15257
+ "vehicle",
15258
+ "prop",
15259
+ "environment",
15260
+ "other"
15261
+ ];
15262
+ var SCENE3D_V2_PRIMITIVES = [
15263
+ "box",
15264
+ "sphere",
15265
+ "cylinder",
15266
+ "cone",
15267
+ "plane",
15268
+ "capsule"
15269
+ ];
15270
+ var SCENE3D_ENTITY_CAPABILITIES = [
15271
+ "transform",
15272
+ "color",
15273
+ "visibility"
15274
+ ];
15275
+ var SCENE3D_DEFAULT_ENTITY_CAPABILITIES = SCENE3D_ENTITY_CAPABILITIES;
15276
+ var SCENE3D_ASSET_KINDS = [
15277
+ "glb",
15278
+ "camera-track-json",
15279
+ "poster",
15280
+ "validation-report",
15281
+ "blend-source"
15282
+ ];
15283
+ var SCENE3D_ASSET_ROLES = [
15284
+ "scene-geometry",
15285
+ "entity-geometry",
15286
+ "camera-track",
15287
+ "poster",
15288
+ "validation-report",
15289
+ "source"
15290
+ ];
15291
+ var SCENE3D_ASSET_ROLE_KINDS = {
15292
+ "scene-geometry": "glb",
15293
+ "entity-geometry": "glb",
15294
+ "camera-track": "camera-track-json",
15295
+ poster: "poster",
15296
+ "validation-report": "validation-report",
15297
+ source: "blend-source"
15298
+ };
15299
+ var SCENE3D_RENDERER_ASSET_KINDS = [
15300
+ "glb",
15301
+ "camera-track-json",
15302
+ "poster"
15303
+ ];
15304
+ var SCENE3D_PRIMITIVE_MATERIAL_ROLE = "identity";
15305
+ var SCENE3D_CLAY_LIGHTING_PRESETS = ["clay-studio-v1"];
15306
+ var scene3DAssetIdSchema = zod.z.string().min(1).max(SCENE3D_V2_LIMITS.maxAssetIdLength).regex(/^[A-Za-z0-9][A-Za-z0-9_.:-]*$/, "assetId must be an opaque id (letters, digits, '_', '-', '.', ':')").refine((value) => !value.includes(".."), "assetId must not contain '..'");
15307
+ var scene3DNodeIdSchema = zod.z.string().min(1).max(SCENE3D_V2_LIMITS.maxNodeIdLength).regex(/^[A-Za-z0-9][A-Za-z0-9_.:-]*$/, "node id must be an exporter-generated stable id");
15308
+ var scene3DSha256Schema = zod.z.string().regex(/^[0-9a-f]{64}$/, "sha256 must be 64 lowercase hex characters");
15309
+ var scene3DVersionTokenSchema = zod.z.string().min(1).max(SCENE3D_V2_LIMITS.maxVersionLength).regex(
15310
+ /^[A-Za-z0-9][A-Za-z0-9_.+-]*$/,
15311
+ "version must be a bounded token (letters, digits, '_', '-', '.', '+') \u2014 never a path or prose"
15312
+ );
15313
+ var scene3DEngineIdSchema = zod.z.string().min(1).max(SCENE3D_V2_LIMITS.maxVersionLength).regex(/^[a-z0-9][a-z0-9-]*$/, "engine must be a lowercase slug such as blender-cloud");
15314
+ var scene3DAnchorNameSchema = scene3DIdSchema;
15315
+ var scene3DMaterialRoleSchema = scene3DIdSchema;
15316
+ var scene3DMaterialNameSchema = zod.z.string().min(1).max(SCENE3D_V2_LIMITS.maxMaterialNameLength).regex(/^[^\u0000-\u001f\u007f]+$/, "material name must not contain control characters");
15317
+ var v2FrameSchema = zod.z.number().int().min(0).max(SCENE3D_V2_LIMITS.maxDurationInFrames);
15318
+ var scene3DEntityCapabilitySchema = zod.z.enum(["transform", "color", "visibility"]);
15319
+ var scene3DAnchorSchema = zod.z.object({
15320
+ name: scene3DAnchorNameSchema,
15321
+ position: vec3Schema,
15322
+ rotation: rotationVec3Schema.optional()
15323
+ }).strict();
15324
+ var scene3DMaterialBindingSchema = zod.z.object({
15325
+ role: scene3DMaterialRoleSchema,
15326
+ materialName: scene3DMaterialNameSchema,
15327
+ color: scene3DColorSchema.optional(),
15328
+ roughness: zod.z.number().min(0).max(1).optional()
15329
+ }).strict();
15330
+ var scene3DAssetAnimationSchema = zod.z.object({
15331
+ clipName: zod.z.string().min(1).max(SCENE3D_V2_LIMITS.maxNameLength),
15332
+ startFrame: v2FrameSchema,
15333
+ endFrameExclusive: zod.z.number().int().min(1).max(SCENE3D_V2_LIMITS.maxDurationInFrames),
15334
+ loop: zod.z.boolean().optional()
15335
+ }).strict();
15336
+ var scene3DEntityVisualSchema = zod.z.discriminatedUnion("kind", [
15337
+ zod.z.object({ kind: zod.z.literal("group") }).strict(),
15338
+ zod.z.object({
15339
+ kind: zod.z.literal("primitive"),
15340
+ primitive: zod.z.enum(["box", "sphere", "cylinder", "cone", "plane", "capsule"]),
15341
+ dimensions: sizeVec3Schema,
15342
+ color: scene3DColorSchema
15343
+ }).strict(),
15344
+ zod.z.object({
15345
+ kind: zod.z.literal("asset"),
15346
+ assetId: scene3DAssetIdSchema,
15347
+ rootNodeId: scene3DNodeIdSchema,
15348
+ animation: scene3DAssetAnimationSchema.optional()
15349
+ }).strict()
15350
+ ]);
15351
+ var scene3DEntityV2Schema = zod.z.object({
15352
+ id: scene3DIdSchema,
15353
+ name: zod.z.string().min(1).max(SCENE3D_V2_LIMITS.maxNameLength),
15354
+ parentId: scene3DIdSchema.optional(),
15355
+ role: zod.z.enum(["person", "vehicle", "prop", "environment", "other"]).optional(),
15356
+ position: vec3Schema.optional(),
15357
+ rotation: rotationVec3Schema.optional(),
15358
+ scale: scaleVec3Schema.optional(),
15359
+ identityColor: scene3DColorSchema.optional(),
15360
+ anchors: zod.z.array(scene3DAnchorSchema).max(SCENE3D_V2_LIMITS.maxAnchorsPerEntity).optional(),
15361
+ capabilities: zod.z.array(scene3DEntityCapabilitySchema).max(SCENE3D_ENTITY_CAPABILITIES.length).optional(),
15362
+ locks: zod.z.array(scene3DEntityCapabilitySchema).max(SCENE3D_ENTITY_CAPABILITIES.length).optional(),
15363
+ materialBindings: zod.z.array(scene3DMaterialBindingSchema).max(SCENE3D_V2_LIMITS.maxMaterialBindingsPerEntity).optional(),
15364
+ visual: scene3DEntityVisualSchema
15365
+ }).strict();
15366
+ var scene3DAssetRefSchema = zod.z.object({
15367
+ assetId: scene3DAssetIdSchema,
15368
+ kind: zod.z.enum(["glb", "camera-track-json", "poster", "validation-report", "blend-source"]),
15369
+ role: zod.z.enum(["scene-geometry", "entity-geometry", "camera-track", "poster", "validation-report", "source"]),
15370
+ byteLength: zod.z.number().int().min(1).max(SCENE3D_V2_LIMITS.maxBlendSourceBytes),
15371
+ sha256: scene3DSha256Schema,
15372
+ originRevisionId: zod.z.uuid().optional()
15373
+ }).strict();
15374
+ var scene3DShotSchema = zod.z.object({
15375
+ id: scene3DIdSchema,
15376
+ startFrame: v2FrameSchema,
15377
+ endFrameExclusive: zod.z.number().int().min(1).max(SCENE3D_V2_LIMITS.maxDurationInFrames),
15378
+ label: zod.z.string().min(1).max(SCENE3D_V2_LIMITS.maxLabelLength).optional(),
15379
+ subjectEntityIds: zod.z.array(scene3DIdSchema).max(SCENE3D_V2_LIMITS.maxShotEntityIds).optional(),
15380
+ foregroundEntityIds: zod.z.array(scene3DIdSchema).max(SCENE3D_V2_LIMITS.maxShotEntityIds).optional()
15381
+ }).strict();
15382
+ var scene3DClayLightingSchema = zod.z.object({
15383
+ preset: zod.z.enum(SCENE3D_CLAY_LIGHTING_PRESETS),
15384
+ ambientIntensity: zod.z.number().min(0).max(SCENE3D_V2_LIMITS.maxIntensity),
15385
+ keyIntensity: zod.z.number().min(0).max(SCENE3D_V2_LIMITS.maxIntensity),
15386
+ keyPosition: vec3Schema
15387
+ }).strict();
15388
+ var overrideProvenanceShape = {
15389
+ id: scene3DIdSchema,
15390
+ sourceRevisionId: zod.z.uuid(),
15391
+ sourceContentHash: scene3DSha256Schema,
15392
+ operationVersion: zod.z.number().int().min(1).max(255)
15393
+ };
15394
+ var scene3DOverrideSchema = zod.z.discriminatedUnion("kind", [
15395
+ zod.z.object({
15396
+ ...overrideProvenanceShape,
15397
+ kind: zod.z.literal("entity-transform"),
15398
+ entityId: scene3DIdSchema,
15399
+ space: zod.z.enum(["local", "world"]),
15400
+ position: vec3Schema.optional(),
15401
+ rotation: rotationVec3Schema.optional(),
15402
+ scale: scaleVec3Schema.optional()
15403
+ }).strict(),
15404
+ zod.z.object({
15405
+ ...overrideProvenanceShape,
15406
+ kind: zod.z.literal("entity-color"),
15407
+ entityId: scene3DIdSchema,
15408
+ materialRole: scene3DMaterialRoleSchema,
15409
+ color: scene3DColorSchema
15410
+ }).strict(),
15411
+ zod.z.object({
15412
+ ...overrideProvenanceShape,
15413
+ kind: zod.z.literal("entity-visibility"),
15414
+ entityId: scene3DIdSchema,
15415
+ visible: zod.z.boolean()
15416
+ }).strict(),
15417
+ zod.z.object({
15418
+ ...overrideProvenanceShape,
15419
+ kind: zod.z.literal("camera-shot-offset"),
15420
+ shotId: scene3DIdSchema,
15421
+ positionOffset: vec3Schema.optional(),
15422
+ targetOffset: vec3Schema.optional()
15423
+ }).strict()
15424
+ ]);
15425
+ var scene3DProvenanceSchema = zod.z.object({
15426
+ engine: scene3DEngineIdSchema,
15427
+ engineVersion: scene3DVersionTokenSchema,
15428
+ recipeVersion: scene3DVersionTokenSchema,
15429
+ compilerVersion: scene3DVersionTokenSchema,
15430
+ exporterVersion: scene3DVersionTokenSchema,
15431
+ rendererVersion: scene3DVersionTokenSchema,
15432
+ sourceRevisionId: zod.z.uuid().optional(),
15433
+ sourceArtifactId: scene3DAssetIdSchema.optional(),
15434
+ contentHash: scene3DSha256Schema
15435
+ }).strict();
15436
+ function scene3DJsonByteLength(text) {
15437
+ return new TextEncoder().encode(text).length;
15438
+ }
15439
+ function scene3DZodIssues(error) {
15440
+ return error.issues.map((issue2) => ({ path: [...issue2.path], message: issue2.message }));
15441
+ }
15442
+ function entityCapabilities(entity) {
15443
+ return entity.capabilities ?? SCENE3D_DEFAULT_ENTITY_CAPABILITIES;
15444
+ }
15445
+ function scene3DEntityAcceptsOverlay(entity, capability2) {
15446
+ return entityCapabilities(entity).includes(capability2) && !(entity.locks ?? []).includes(capability2);
15447
+ }
15448
+ function checkEntities(plan, byId, issues) {
15449
+ const rootNodeOwners = /* @__PURE__ */ new Map();
15450
+ plan.objects.forEach((entity, index) => {
15451
+ const at = (...rest) => ["objects", index, ...rest];
15452
+ if (entity.visual.kind !== "asset") {
15453
+ for (const field of ["position", "rotation", "scale"]) {
15454
+ if (entity[field] === void 0) {
15455
+ issues.push({
15456
+ path: at(field),
15457
+ message: `entity "${entity.id}" is a ${entity.visual.kind} and must declare ${field}`
15458
+ });
15459
+ }
15460
+ }
15461
+ }
15462
+ if (entity.visual.kind === "asset") {
15463
+ const owner = rootNodeOwners.get(entity.visual.rootNodeId);
15464
+ if (owner !== void 0) {
15465
+ issues.push({
15466
+ path: at("visual", "rootNodeId"),
15467
+ message: `root node "${entity.visual.rootNodeId}" is already the root of entity "${owner}"`
15468
+ });
15469
+ } else {
15470
+ rootNodeOwners.set(entity.visual.rootNodeId, entity.id);
15471
+ }
15472
+ const animation = entity.visual.animation;
15473
+ if (animation) {
15474
+ if (animation.endFrameExclusive <= animation.startFrame) {
15475
+ issues.push({
15476
+ path: at("visual", "animation", "endFrameExclusive"),
15477
+ message: `entity "${entity.id}" animation ends at or before it starts`
15478
+ });
15479
+ }
15480
+ if (animation.endFrameExclusive > plan.durationInFrames) {
15481
+ issues.push({
15482
+ path: at("visual", "animation", "endFrameExclusive"),
15483
+ message: `entity "${entity.id}" animation runs past the scene (${plan.durationInFrames} frames)`
15484
+ });
15485
+ }
15486
+ }
15487
+ } else if (entity.materialBindings && entity.materialBindings.length > 0) {
15488
+ issues.push({
15489
+ path: at("materialBindings"),
15490
+ message: `entity "${entity.id}" is a ${entity.visual.kind}; material bindings name materials in an asset root`
15491
+ });
15492
+ }
15493
+ const roles = /* @__PURE__ */ new Set();
15494
+ (entity.materialBindings ?? []).forEach((binding, bindingIndex) => {
15495
+ if (roles.has(binding.role)) {
15496
+ issues.push({
15497
+ path: at("materialBindings", bindingIndex, "role"),
15498
+ message: `entity "${entity.id}" binds material role "${binding.role}" twice`
15499
+ });
15500
+ }
15501
+ roles.add(binding.role);
15502
+ });
15503
+ const anchorNames = /* @__PURE__ */ new Set();
15504
+ (entity.anchors ?? []).forEach((anchor, anchorIndex) => {
15505
+ if (anchorNames.has(anchor.name)) {
15506
+ issues.push({
15507
+ path: at("anchors", anchorIndex, "name"),
15508
+ message: `entity "${entity.id}" declares anchor "${anchor.name}" twice`
15509
+ });
15510
+ }
15511
+ anchorNames.add(anchor.name);
15512
+ });
15513
+ });
15514
+ plan.objects.forEach((entity, index) => {
15515
+ if (entity.parentId === void 0) return;
15516
+ if (entity.parentId === entity.id) {
15517
+ issues.push({ path: ["objects", index, "parentId"], message: `entity "${entity.id}" cannot parent itself` });
15518
+ return;
15519
+ }
15520
+ if (!byId.has(entity.parentId)) {
15521
+ issues.push({
15522
+ path: ["objects", index, "parentId"],
15523
+ message: `entity "${entity.id}" references unknown parent "${entity.parentId}"`
15524
+ });
15525
+ return;
15526
+ }
15527
+ const seen = /* @__PURE__ */ new Set([entity.id]);
15528
+ let cursor = byId.get(entity.parentId);
15529
+ let depth = 1;
15530
+ while (cursor) {
15531
+ if (seen.has(cursor.id)) {
15532
+ issues.push({ path: ["objects", index, "parentId"], message: `parent cycle through entity "${cursor.id}"` });
15533
+ break;
15534
+ }
15535
+ seen.add(cursor.id);
15536
+ depth += 1;
15537
+ if (depth > SCENE3D_V2_LIMITS.maxHierarchyDepth) {
15538
+ issues.push({
15539
+ path: ["objects", index, "parentId"],
15540
+ message: `hierarchy deeper than ${SCENE3D_V2_LIMITS.maxHierarchyDepth} levels`
15541
+ });
15542
+ break;
15543
+ }
15544
+ cursor = cursor.parentId === void 0 ? void 0 : byId.get(cursor.parentId);
15545
+ }
15546
+ });
15547
+ }
15548
+ function checkAssets(plan, assetsById, issues) {
15549
+ let rendererBytes = 0;
15550
+ let sourceCount = 0;
15551
+ plan.assets.forEach((asset, index) => {
15552
+ const at = (...rest) => ["assets", index, ...rest];
15553
+ const expectedKind = SCENE3D_ASSET_ROLE_KINDS[asset.role];
15554
+ if (asset.kind !== expectedKind) {
15555
+ issues.push({
15556
+ path: at("kind"),
15557
+ message: `asset "${asset.assetId}" has role "${asset.role}", which requires kind "${expectedKind}" (got "${asset.kind}")`
15558
+ });
15559
+ }
15560
+ if (asset.kind === "camera-track-json" && asset.byteLength > SCENE3D_V2_LIMITS.maxCameraTrackBytes) {
15561
+ issues.push({
15562
+ path: at("byteLength"),
15563
+ message: `camera track "${asset.assetId}" is ${asset.byteLength} bytes; the limit is ${SCENE3D_V2_LIMITS.maxCameraTrackBytes}`
15564
+ });
15565
+ }
15566
+ if (SCENE3D_RENDERER_ASSET_KINDS.includes(asset.kind)) {
15567
+ rendererBytes += asset.byteLength;
15568
+ if (asset.byteLength > SCENE3D_V2_LIMITS.maxRendererAssetBytes) {
15569
+ issues.push({
15570
+ path: at("byteLength"),
15571
+ message: `asset "${asset.assetId}" is ${asset.byteLength} bytes; a downloaded scene asset may not exceed ${SCENE3D_V2_LIMITS.maxRendererAssetBytes}`
15572
+ });
15573
+ }
15574
+ }
15575
+ if (asset.kind === "blend-source") {
15576
+ sourceCount += 1;
15577
+ if (sourceCount > 1) {
15578
+ issues.push({ path: at("kind"), message: "a revision may retain at most one blend-source asset" });
15579
+ }
15580
+ }
15581
+ });
15582
+ if (rendererBytes > SCENE3D_V2_LIMITS.maxRendererAssetBytes) {
15583
+ issues.push({
15584
+ path: ["assets"],
15585
+ message: `downloaded scene assets total ${rendererBytes} bytes; the limit is ${SCENE3D_V2_LIMITS.maxRendererAssetBytes}`
15586
+ });
15587
+ }
15588
+ const track = assetsById.get(plan.cameraTrackAssetId);
15589
+ if (!track) {
15590
+ issues.push({
15591
+ path: ["cameraTrackAssetId"],
15592
+ message: `cameraTrackAssetId "${plan.cameraTrackAssetId}" is not in assets`
15593
+ });
15594
+ } else if (track.kind !== "camera-track-json") {
15595
+ issues.push({
15596
+ path: ["cameraTrackAssetId"],
15597
+ message: `cameraTrackAssetId "${plan.cameraTrackAssetId}" is kind "${track.kind}"; a camera track must be camera-track-json`
15598
+ });
15599
+ }
15600
+ const referencedGlbs = /* @__PURE__ */ new Set();
15601
+ for (const entity of plan.objects) {
15602
+ if (entity.visual.kind === "asset") referencedGlbs.add(entity.visual.assetId);
15603
+ }
15604
+ plan.assets.forEach((asset, index) => {
15605
+ if (asset.kind === "glb" && !referencedGlbs.has(asset.assetId)) {
15606
+ issues.push({
15607
+ path: ["assets", index, "assetId"],
15608
+ message: `glb asset "${asset.assetId}" is not referenced by any entity`
15609
+ });
15610
+ }
15611
+ });
15612
+ plan.objects.forEach((entity, index) => {
15613
+ if (entity.visual.kind !== "asset") return;
15614
+ const asset = assetsById.get(entity.visual.assetId);
15615
+ if (!asset) {
15616
+ issues.push({
15617
+ path: ["objects", index, "visual", "assetId"],
15618
+ message: `entity "${entity.id}" references unknown asset "${entity.visual.assetId}"`
15619
+ });
15620
+ } else if (asset.kind !== "glb") {
15621
+ issues.push({
15622
+ path: ["objects", index, "visual", "assetId"],
15623
+ message: `entity "${entity.id}" references asset "${asset.assetId}" of kind "${asset.kind}"; geometry must be glb`
15624
+ });
15625
+ }
15626
+ });
15627
+ if (plan.provenance.sourceArtifactId !== void 0) {
15628
+ const source = assetsById.get(plan.provenance.sourceArtifactId);
15629
+ if (!source) {
15630
+ issues.push({
15631
+ path: ["provenance", "sourceArtifactId"],
15632
+ message: `sourceArtifactId "${plan.provenance.sourceArtifactId}" is not in assets`
15633
+ });
15634
+ } else if (source.kind !== "blend-source") {
15635
+ issues.push({
15636
+ path: ["provenance", "sourceArtifactId"],
15637
+ message: `sourceArtifactId "${source.assetId}" is kind "${source.kind}"; a retained source must be blend-source`
15638
+ });
15639
+ }
15640
+ }
15641
+ }
15642
+ function checkShots(plan, byId, issues) {
15643
+ const seenIds = /* @__PURE__ */ new Set();
15644
+ let expectedStart = 0;
15645
+ plan.shots.forEach((shot, index) => {
15646
+ const at = (...rest) => ["shots", index, ...rest];
15647
+ if (seenIds.has(shot.id)) {
15648
+ issues.push({ path: at("id"), message: `duplicate shot id "${shot.id}"` });
15649
+ }
15650
+ seenIds.add(shot.id);
15651
+ if (shot.endFrameExclusive <= shot.startFrame) {
15652
+ issues.push({
15653
+ path: at("endFrameExclusive"),
15654
+ message: `shot "${shot.id}" ends at or before it starts (${shot.startFrame}..${shot.endFrameExclusive})`
15655
+ });
15656
+ }
15657
+ if (shot.startFrame !== expectedStart) {
15658
+ issues.push({
15659
+ path: at("startFrame"),
15660
+ message: index === 0 ? `shots must start at frame 0 (got ${shot.startFrame})` : `shot "${shot.id}" starts at ${shot.startFrame}; the previous shot ends at ${expectedStart} \u2014 every frame belongs to exactly one shot`
15661
+ });
15662
+ }
15663
+ expectedStart = Math.max(expectedStart, shot.endFrameExclusive);
15664
+ for (const field of ["subjectEntityIds", "foregroundEntityIds"]) {
15665
+ (shot[field] ?? []).forEach((entityId, entityIndex) => {
15666
+ if (!byId.has(entityId)) {
15667
+ issues.push({
15668
+ path: at(field, entityIndex),
15669
+ message: `shot "${shot.id}" references unknown entity "${entityId}"`
15670
+ });
15671
+ }
15672
+ });
15673
+ }
15674
+ });
15675
+ const last = plan.shots[plan.shots.length - 1];
15676
+ if (last && last.endFrameExclusive !== plan.durationInFrames) {
15677
+ issues.push({
15678
+ path: ["shots", plan.shots.length - 1, "endFrameExclusive"],
15679
+ message: `shots end at frame ${last.endFrameExclusive}; the scene is ${plan.durationInFrames} frames and must be covered completely`
15680
+ });
15681
+ }
15682
+ }
15683
+ function checkOverrides(plan, byId, shotIds, issues) {
15684
+ const overrideIds = /* @__PURE__ */ new Set();
15685
+ const transformTargets = /* @__PURE__ */ new Set();
15686
+ const visibilityTargets = /* @__PURE__ */ new Set();
15687
+ const colorTargets = /* @__PURE__ */ new Set();
15688
+ const offsetTargets = /* @__PURE__ */ new Set();
15689
+ (plan.overrides ?? []).forEach((override, index) => {
15690
+ const at = (...rest) => ["overrides", index, ...rest];
15691
+ if (overrideIds.has(override.id)) {
15692
+ issues.push({ path: at("id"), message: `duplicate override id "${override.id}"` });
15693
+ }
15694
+ overrideIds.add(override.id);
15695
+ if (override.operationVersion > SCENE3D_V2_OVERRIDE_OPERATION_VERSION) {
15696
+ issues.push({
15697
+ path: at("operationVersion"),
15698
+ message: `override "${override.id}" uses operation version ${override.operationVersion}; this reader understands up to ${SCENE3D_V2_OVERRIDE_OPERATION_VERSION}`
15699
+ });
15700
+ }
15701
+ if (override.kind === "camera-shot-offset") {
15702
+ if (!shotIds.has(override.shotId)) {
15703
+ issues.push({ path: at("shotId"), message: `override "${override.id}" targets unknown shot "${override.shotId}"` });
15704
+ } else if (offsetTargets.has(override.shotId)) {
15705
+ issues.push({
15706
+ path: at("shotId"),
15707
+ message: `shot "${override.shotId}" already has a camera offset; one owner per channel`
15708
+ });
15709
+ }
15710
+ offsetTargets.add(override.shotId);
15711
+ if (override.positionOffset === void 0 && override.targetOffset === void 0) {
15712
+ issues.push({ path: at(), message: `override "${override.id}" offsets nothing` });
15713
+ }
15714
+ return;
15715
+ }
15716
+ const entity = byId.get(override.entityId);
15717
+ if (!entity) {
15718
+ issues.push({ path: at("entityId"), message: `override "${override.id}" targets unknown entity "${override.entityId}"` });
15719
+ return;
15720
+ }
15721
+ if (override.kind === "entity-transform") {
15722
+ if (transformTargets.has(override.entityId)) {
15723
+ issues.push({
15724
+ path: at("entityId"),
15725
+ message: `entity "${override.entityId}" already has a transform override; one owner per channel`
15726
+ });
15727
+ }
15728
+ transformTargets.add(override.entityId);
15729
+ if (!scene3DEntityAcceptsOverlay(entity, "transform")) {
15730
+ issues.push({
15731
+ path: at("entityId"),
15732
+ message: `entity "${override.entityId}" does not accept a transform overlay (locked or not advertised)`
15733
+ });
15734
+ }
15735
+ if (override.position === void 0 && override.rotation === void 0 && override.scale === void 0) {
15736
+ issues.push({ path: at(), message: `override "${override.id}" changes nothing` });
15737
+ }
15738
+ return;
15739
+ }
15740
+ if (override.kind === "entity-visibility") {
15741
+ if (visibilityTargets.has(override.entityId)) {
15742
+ issues.push({
15743
+ path: at("entityId"),
15744
+ message: `entity "${override.entityId}" already has a visibility override; one owner per channel`
15745
+ });
15746
+ }
15747
+ visibilityTargets.add(override.entityId);
15748
+ if (!scene3DEntityAcceptsOverlay(entity, "visibility")) {
15749
+ issues.push({
15750
+ path: at("entityId"),
15751
+ message: `entity "${override.entityId}" does not accept a visibility overlay (locked or not advertised)`
15752
+ });
15753
+ }
15754
+ return;
15755
+ }
15756
+ const key = `${override.entityId}\0${override.materialRole}`;
15757
+ if (colorTargets.has(key)) {
15758
+ issues.push({
15759
+ path: at("materialRole"),
15760
+ message: `entity "${override.entityId}" already recolours material role "${override.materialRole}"`
15761
+ });
15762
+ }
15763
+ colorTargets.add(key);
15764
+ if (!scene3DEntityAcceptsOverlay(entity, "color")) {
15765
+ issues.push({
15766
+ path: at("entityId"),
15767
+ message: `entity "${override.entityId}" does not accept a colour overlay (locked or not advertised)`
15768
+ });
15769
+ }
15770
+ if (entity.visual.kind === "group") {
15771
+ issues.push({
15772
+ path: at("materialRole"),
15773
+ message: `entity "${override.entityId}" is a group and has no geometry to recolour`
15774
+ });
15775
+ } else if (entity.visual.kind === "primitive") {
15776
+ if (override.materialRole !== SCENE3D_PRIMITIVE_MATERIAL_ROLE) {
15777
+ issues.push({
15778
+ path: at("materialRole"),
15779
+ message: `entity "${override.entityId}" is a primitive; its only material role is "${SCENE3D_PRIMITIVE_MATERIAL_ROLE}"`
15780
+ });
15781
+ }
15782
+ } else if (!(entity.materialBindings ?? []).some((binding) => binding.role === override.materialRole)) {
15783
+ issues.push({
15784
+ path: at("materialRole"),
15785
+ message: `entity "${override.entityId}" declares no material role "${override.materialRole}"; a binding may only name materials in that entity's asset root`
15786
+ });
15787
+ }
15788
+ });
15789
+ }
15790
+ function scene3DPlanV2Issues(plan) {
15791
+ const issues = [];
15792
+ const seconds = plan.durationInFrames / plan.fps;
15793
+ if (seconds > SCENE3D_V2_LIMITS.maxDurationSeconds) {
15794
+ issues.push({
15795
+ path: ["durationInFrames"],
15796
+ message: `scene is ${seconds.toFixed(2)}s; the limit is ${SCENE3D_V2_LIMITS.maxDurationSeconds}s`
15797
+ });
15798
+ }
15799
+ for (const axis of ["width", "height"]) {
15800
+ if (plan[axis] % 2 !== 0) {
15801
+ issues.push({ path: [axis], message: `${axis} must be an even number of pixels (got ${plan[axis]})` });
15802
+ }
15803
+ }
15804
+ const byId = /* @__PURE__ */ new Map();
15805
+ plan.objects.forEach((entity, index) => {
15806
+ if (byId.has(entity.id)) {
15807
+ issues.push({ path: ["objects", index, "id"], message: `duplicate entity id "${entity.id}"` });
15808
+ return;
15809
+ }
15810
+ byId.set(entity.id, entity);
15811
+ });
15812
+ const assetsById = /* @__PURE__ */ new Map();
15813
+ plan.assets.forEach((asset, index) => {
15814
+ if (assetsById.has(asset.assetId)) {
15815
+ issues.push({ path: ["assets", index, "assetId"], message: `duplicate asset id "${asset.assetId}"` });
15816
+ return;
15817
+ }
15818
+ assetsById.set(asset.assetId, asset);
15819
+ });
15820
+ checkEntities(plan, byId, issues);
15821
+ checkAssets(plan, assetsById, issues);
15822
+ checkShots(plan, byId, issues);
15823
+ checkOverrides(plan, byId, new Set(plan.shots.map((shot) => shot.id)), issues);
15824
+ const referenceIds = /* @__PURE__ */ new Set();
15825
+ (plan.references ?? []).forEach((reference, index) => {
15826
+ if (referenceIds.has(reference.id)) {
15827
+ issues.push({ path: ["references", index, "id"], message: `duplicate reference id "${reference.id}"` });
15828
+ }
15829
+ referenceIds.add(reference.id);
15830
+ if (reference.objectId !== void 0 && !byId.has(reference.objectId)) {
15831
+ issues.push({
15832
+ path: ["references", index, "objectId"],
15833
+ message: `reference "${reference.id}" points at unknown entity "${reference.objectId}"`
15834
+ });
15835
+ }
15836
+ if (reference.startSeconds !== void 0 && reference.endSeconds !== void 0 && reference.endSeconds <= reference.startSeconds) {
15837
+ issues.push({
15838
+ path: ["references", index, "endSeconds"],
15839
+ message: `reference "${reference.id}" ends at or before it starts`
15840
+ });
15841
+ }
15842
+ if (reference.kind === "image" && (reference.startSeconds !== void 0 || reference.endSeconds !== void 0)) {
15843
+ issues.push({
15844
+ path: ["references", index, "startSeconds"],
15845
+ message: `reference "${reference.id}" is an image; a time window applies to video only`
15846
+ });
15847
+ }
15848
+ });
15849
+ return issues;
15850
+ }
15851
+ var scene3DPlanV2ObjectSchema = zod.z.object({
15852
+ planType: zod.z.literal(SCENE3D_PLAN_TYPE),
15853
+ schemaVersion: zod.z.literal(SCENE3D_SCHEMA_VERSION_V2),
15854
+ revisionId: zod.z.uuid(),
15855
+ parentRevisionId: zod.z.uuid().optional(),
15856
+ width: zod.z.number().int().min(SCENE3D_V2_LIMITS.minDimensionPx).max(SCENE3D_V2_LIMITS.maxDimensionPx),
15857
+ height: zod.z.number().int().min(SCENE3D_V2_LIMITS.minDimensionPx).max(SCENE3D_V2_LIMITS.maxDimensionPx),
15858
+ fps: zod.z.number().int().min(SCENE3D_V2_LIMITS.minFps).max(SCENE3D_V2_LIMITS.maxFps),
15859
+ durationInFrames: zod.z.number().int().min(SCENE3D_V2_LIMITS.minDurationInFrames).max(SCENE3D_V2_LIMITS.maxDurationInFrames),
15860
+ units: zod.z.literal("meters"),
15861
+ upAxis: zod.z.literal("Y"),
15862
+ handedness: zod.z.literal("right"),
15863
+ objects: zod.z.array(scene3DEntityV2Schema).min(SCENE3D_V2_LIMITS.minEntities).max(SCENE3D_V2_LIMITS.maxEntities),
15864
+ assets: zod.z.array(scene3DAssetRefSchema).min(1).max(SCENE3D_V2_LIMITS.maxAssets),
15865
+ cameraTrackAssetId: scene3DAssetIdSchema,
15866
+ shots: zod.z.array(scene3DShotSchema).min(1).max(SCENE3D_V2_LIMITS.maxShots),
15867
+ lighting: scene3DClayLightingSchema,
15868
+ backgroundColor: scene3DColorSchema,
15869
+ references: zod.z.array(scene3DReferenceSchema).max(SCENE3D_V2_LIMITS.maxReferences).optional(),
15870
+ overrides: zod.z.array(scene3DOverrideSchema).max(SCENE3D_V2_LIMITS.maxOverrides).optional(),
15871
+ provenance: scene3DProvenanceSchema
15872
+ }).strict();
15873
+ var scene3DPlanV2Schema = scene3DPlanV2ObjectSchema.superRefine((plan, ctx) => {
15874
+ for (const issue2 of scene3DPlanV2Issues(plan)) {
15875
+ ctx.addIssue({ code: "custom", path: issue2.path, message: issue2.message });
15876
+ }
15877
+ });
15878
+ var scene3DAnyPlanSchema = zod.z.discriminatedUnion("schemaVersion", [scene3DPlanV1ObjectSchema, scene3DPlanV2ObjectSchema]).superRefine((plan, ctx) => {
15879
+ const issues = plan.schemaVersion === SCENE3D_SCHEMA_VERSION_V2 ? scene3DPlanV2Issues(plan) : scene3DPlanV1Issues(plan);
15880
+ for (const issue2 of issues) {
15881
+ ctx.addIssue({ code: "custom", path: issue2.path, message: issue2.message });
15882
+ }
15883
+ });
15884
+ var scene3DAcceptedSchemaVersionsSchema = zod.z.array(zod.z.union([zod.z.literal(1), zod.z.literal(2)])).min(1).max(SCENE3D_SUPPORTED_SCHEMA_VERSIONS.length);
15885
+ function isScene3DPlanV2(value) {
15886
+ return scene3DPlanV2Schema.safeParse(value).success;
15887
+ }
15888
+ function isScene3DPlan(value) {
15889
+ return scene3DAnyPlanSchema.safeParse(value).success;
15890
+ }
15891
+ function scene3DPlanSchemaVersion(value) {
15892
+ if (typeof value !== "object" || value === null) return null;
15893
+ const record = value;
15894
+ if (record.planType !== SCENE3D_PLAN_TYPE) return null;
15895
+ if (typeof record.schemaVersion !== "number" || !Number.isInteger(record.schemaVersion)) return null;
15896
+ return record.schemaVersion;
15897
+ }
15898
+ function isScene3DSchemaVersionSupported(version) {
15899
+ return SCENE3D_SUPPORTED_SCHEMA_VERSIONS.includes(version);
15900
+ }
15901
+ function isKnownScene3DEngine(engine) {
15902
+ return SCENE3D_V2_ENGINES.includes(engine);
15903
+ }
15904
+ function scene3DShotIndexForFrame(shots, frame) {
15905
+ for (let index = 0; index < shots.length; index++) {
15906
+ const shot = shots[index];
15907
+ if (frame >= shot.startFrame && frame < shot.endFrameExclusive) return index;
15908
+ }
15909
+ return -1;
15910
+ }
15911
+ function scene3DShotForFrame(shots, frame) {
15912
+ const index = scene3DShotIndexForFrame(shots, frame);
15913
+ return index === -1 ? void 0 : shots[index];
15914
+ }
15915
+
15916
+ // src/scene3d-v2-resources.ts
15917
+ function scene3DV2HierarchyDepth(entities) {
15918
+ const byId = new Map(entities.map((entity) => [entity.id, entity]));
15919
+ let deepest = 0;
15920
+ for (const entity of entities) {
15921
+ let depth = 1;
15922
+ let cursor = entity;
15923
+ const seen = /* @__PURE__ */ new Set([entity.id]);
15924
+ while (cursor.parentId !== void 0) {
15925
+ const parent = byId.get(cursor.parentId);
15926
+ if (!parent || seen.has(parent.id)) break;
15927
+ seen.add(parent.id);
15928
+ cursor = parent;
15929
+ depth += 1;
15930
+ }
15931
+ if (depth > deepest) deepest = depth;
15932
+ }
15933
+ return deepest;
15934
+ }
15935
+ function scene3DV2ResourceUsage(plan) {
15936
+ let rendererAssetBytes = 0;
15937
+ let cameraTrackBytes = 0;
15938
+ let blendSourceBytes = 0;
15939
+ for (const asset of plan.assets) {
15940
+ if (SCENE3D_RENDERER_ASSET_KINDS.includes(asset.kind)) rendererAssetBytes += asset.byteLength;
15941
+ if (asset.kind === "camera-track-json") cameraTrackBytes += asset.byteLength;
15942
+ if (asset.kind === "blend-source") blendSourceBytes += asset.byteLength;
15943
+ }
15944
+ return {
15945
+ entities: plan.objects.length,
15946
+ assets: plan.assets.length,
15947
+ shots: plan.shots.length,
15948
+ overrides: plan.overrides?.length ?? 0,
15949
+ references: plan.references?.length ?? 0,
15950
+ frames: plan.durationInFrames,
15951
+ durationSeconds: plan.durationInFrames / plan.fps,
15952
+ hierarchyDepth: scene3DV2HierarchyDepth(plan.objects),
15953
+ rendererAssetBytes,
15954
+ cameraTrackBytes,
15955
+ blendSourceBytes
15956
+ };
15957
+ }
15958
+ function scene3DV2AdmissionIssues(plan, manifestBytes) {
15959
+ const issues = [];
15960
+ const usage = scene3DV2ResourceUsage(plan);
15961
+ const limits = SCENE3D_V2_LIMITS;
15962
+ if (manifestBytes !== void 0 && manifestBytes > limits.maxManifestBytes) {
15963
+ issues.push({
15964
+ path: [],
15965
+ message: `manifest is ${manifestBytes} bytes; the limit is ${limits.maxManifestBytes}`
15966
+ });
15967
+ }
15968
+ if (usage.frames > limits.maxDurationInFrames) {
15969
+ issues.push({
15970
+ path: ["durationInFrames"],
15971
+ message: `scene is ${usage.frames} frames; the limit is ${limits.maxDurationInFrames}`
15972
+ });
15973
+ }
15974
+ if (usage.durationSeconds > limits.maxDurationSeconds) {
15975
+ issues.push({
15976
+ path: ["durationInFrames"],
15977
+ message: `scene is ${usage.durationSeconds.toFixed(2)}s; the limit is ${limits.maxDurationSeconds}s`
15978
+ });
15979
+ }
15980
+ if (usage.entities > limits.maxEntities) {
15981
+ issues.push({ path: ["objects"], message: `${usage.entities} entities; the limit is ${limits.maxEntities}` });
15982
+ }
15983
+ if (usage.shots > limits.maxShots) {
15984
+ issues.push({ path: ["shots"], message: `${usage.shots} shots; the limit is ${limits.maxShots}` });
15985
+ }
15986
+ if (usage.hierarchyDepth > limits.maxHierarchyDepth) {
15987
+ issues.push({
15988
+ path: ["objects"],
15989
+ message: `hierarchy is ${usage.hierarchyDepth} deep; the limit is ${limits.maxHierarchyDepth}`
15990
+ });
15991
+ }
15992
+ if (usage.rendererAssetBytes > limits.maxRendererAssetBytes) {
15993
+ issues.push({
15994
+ path: ["assets"],
15995
+ message: `downloaded scene assets total ${usage.rendererAssetBytes} bytes; the limit is ${limits.maxRendererAssetBytes}`
15996
+ });
15997
+ }
15998
+ if (usage.cameraTrackBytes > limits.maxCameraTrackBytes) {
15999
+ issues.push({
16000
+ path: ["assets"],
16001
+ message: `camera track data totals ${usage.cameraTrackBytes} bytes; the limit is ${limits.maxCameraTrackBytes}`
16002
+ });
16003
+ }
16004
+ return issues;
16005
+ }
16006
+ function scene3DV2NormalizationIssues(plan, stats) {
16007
+ const issues = [];
16008
+ const limits = SCENE3D_V2_LIMITS;
16009
+ const declared = new Map(plan.assets.map((asset) => [asset.assetId, asset]));
16010
+ let meshNodes = 0;
16011
+ let triangles = 0;
16012
+ let rendererBytes = 0;
16013
+ stats.forEach((stat, index) => {
16014
+ const at = (...rest) => [index, ...rest];
16015
+ const asset = declared.get(stat.assetId);
16016
+ if (!asset) {
16017
+ issues.push({ path: at("assetId"), message: `asset "${stat.assetId}" is not declared in the manifest` });
16018
+ return;
16019
+ }
16020
+ if (stat.kind !== asset.kind) {
16021
+ issues.push({
16022
+ path: at("kind"),
16023
+ message: `asset "${stat.assetId}" decoded as "${stat.kind}" but the manifest declares "${asset.kind}"`
16024
+ });
16025
+ }
16026
+ if (stat.byteLength !== asset.byteLength) {
16027
+ issues.push({
16028
+ path: at("byteLength"),
16029
+ message: `asset "${stat.assetId}" is ${stat.byteLength} bytes; the manifest declares ${asset.byteLength}`
16030
+ });
16031
+ }
16032
+ if (stat.sha256 !== void 0 && stat.sha256 !== asset.sha256) {
16033
+ issues.push({
16034
+ path: at("sha256"),
16035
+ message: `asset "${stat.assetId}" digest does not match the manifest`
16036
+ });
16037
+ }
16038
+ if (SCENE3D_RENDERER_ASSET_KINDS.includes(stat.kind)) rendererBytes += stat.byteLength;
16039
+ if (stat.kind === "camera-track-json" && stat.byteLength > limits.maxCameraTrackBytes) {
16040
+ issues.push({
16041
+ path: at("byteLength"),
16042
+ message: `camera track "${stat.assetId}" decoded to ${stat.byteLength} bytes; the limit is ${limits.maxCameraTrackBytes}`
16043
+ });
16044
+ }
16045
+ meshNodes += stat.meshNodes ?? 0;
16046
+ triangles += stat.triangles ?? 0;
16047
+ if (stat.maxNodeDepth !== void 0 && stat.maxNodeDepth > limits.maxHierarchyDepth) {
16048
+ issues.push({
16049
+ path: at("maxNodeDepth"),
16050
+ message: `asset "${stat.assetId}" nests ${stat.maxNodeDepth} levels; the limit is ${limits.maxHierarchyDepth}`
16051
+ });
16052
+ }
16053
+ for (const [field, value] of [
16054
+ ["imageWidth", stat.imageWidth],
16055
+ ["imageHeight", stat.imageHeight]
16056
+ ]) {
16057
+ if (value === void 0) continue;
16058
+ if (!Number.isInteger(value) || value < limits.minPosterDimensionPx || value > limits.maxPosterDimensionPx) {
16059
+ issues.push({
16060
+ path: at(field),
16061
+ message: `asset "${stat.assetId}" ${field} is ${value}; it must be an integer between ${limits.minPosterDimensionPx} and ${limits.maxPosterDimensionPx}`
16062
+ });
16063
+ }
16064
+ }
16065
+ });
16066
+ if (meshNodes > limits.maxMeshNodes) {
16067
+ issues.push({ path: [], message: `resolved assets contain ${meshNodes} mesh nodes; the limit is ${limits.maxMeshNodes}` });
16068
+ }
16069
+ if (triangles > limits.maxTriangles) {
16070
+ issues.push({ path: [], message: `resolved assets contain ${triangles} triangles; the limit is ${limits.maxTriangles}` });
16071
+ }
16072
+ if (rendererBytes > limits.maxRendererAssetBytes) {
16073
+ issues.push({
16074
+ path: [],
16075
+ message: `downloaded scene assets decoded to ${rendererBytes} bytes; the limit is ${limits.maxRendererAssetBytes}`
16076
+ });
16077
+ }
16078
+ return issues;
16079
+ }
16080
+ function parseScene3DPlanV2Json(text) {
16081
+ const bytes = scene3DJsonByteLength(text);
16082
+ if (bytes > SCENE3D_V2_LIMITS.maxManifestBytes) {
16083
+ return {
16084
+ ok: false,
16085
+ issues: [{ path: [], message: `manifest is ${bytes} bytes; the limit is ${SCENE3D_V2_LIMITS.maxManifestBytes}` }]
16086
+ };
16087
+ }
16088
+ let decoded;
16089
+ try {
16090
+ decoded = JSON.parse(text);
16091
+ } catch {
16092
+ return { ok: false, issues: [{ path: [], message: "manifest is not valid JSON" }] };
16093
+ }
16094
+ const parsed = scene3DPlanV2Schema.safeParse(decoded);
16095
+ if (!parsed.success) return { ok: false, issues: scene3DZodIssues(parsed.error) };
16096
+ return { ok: true, value: parsed.data };
16097
+ }
16098
+ var SCENE3D_V2_CONTENT_HASH_EXCLUDED = ["revisionId", "parentRevisionId"];
16099
+ function canonicalize(value) {
16100
+ if (value === null) return "null";
16101
+ if (typeof value === "number") {
16102
+ if (!Number.isFinite(value)) throw new Error("cannot canonicalize a non-finite number");
16103
+ return JSON.stringify(value === 0 ? 0 : value);
16104
+ }
16105
+ if (typeof value === "string" || typeof value === "boolean") return JSON.stringify(value);
16106
+ if (Array.isArray(value)) return `[${value.map((item) => canonicalize(item)).join(",")}]`;
16107
+ if (typeof value === "object") {
16108
+ const record = value;
16109
+ const parts = [];
16110
+ for (const key of Object.keys(record).sort()) {
16111
+ const entry = record[key];
16112
+ if (entry === void 0) continue;
16113
+ parts.push(`${JSON.stringify(key)}:${canonicalize(entry)}`);
16114
+ }
16115
+ return `{${parts.join(",")}}`;
16116
+ }
16117
+ throw new Error(`cannot canonicalize ${typeof value}`);
16118
+ }
16119
+ function canonicalScene3DPlanV2Json(plan) {
16120
+ const { revisionId: _revisionId, parentRevisionId: _parentRevisionId, provenance, ...rest } = plan;
16121
+ const { contentHash: _contentHash, ...provenanceRest } = provenance;
16122
+ return canonicalize({ ...rest, provenance: provenanceRest });
16123
+ }
16124
+ function toHex(buffer) {
16125
+ return Array.from(new Uint8Array(buffer), (byte) => byte.toString(16).padStart(2, "0")).join("");
16126
+ }
16127
+ async function computeScene3DPlanV2ContentHash(plan) {
16128
+ const subtle = globalThis.crypto?.subtle;
16129
+ if (!subtle) throw new Error("WebCrypto SubtleCrypto is required to hash a Scene3D revision");
16130
+ const bytes = new TextEncoder().encode(canonicalScene3DPlanV2Json(plan));
16131
+ return toHex(await subtle.digest("SHA-256", bytes));
16132
+ }
16133
+ async function verifyScene3DPlanV2ContentHash(plan) {
16134
+ return await computeScene3DPlanV2ContentHash(plan) === plan.provenance.contentHash;
16135
+ }
16136
+ var SCENE3D_CAMERA_TRACK_FORMAT = "scene3d-camera-track";
16137
+ var SCENE3D_CAMERA_TRACK_VERSION = 1;
16138
+ var SCENE3D_CAMERA_TRACK_LIMITS = {
16139
+ maxJsonBytes: SCENE3D_V2_LIMITS.maxCameraTrackBytes,
16140
+ maxFrameCount: SCENE3D_V2_LIMITS.maxDurationInFrames,
16141
+ minFps: SCENE3D_V2_LIMITS.minFps,
16142
+ maxFps: SCENE3D_V2_LIMITS.maxFps,
16143
+ /** A unit quaternion off by more than this is a bug, not float noise. */
16144
+ quaternionTolerance: 1e-4,
16145
+ /** Absolute tolerance on the projection entries that must be exactly zero
16146
+ * (or exactly ∓1) in a perspective matrix. */
16147
+ projectionEpsilon: 1e-6,
16148
+ /** Relative tolerance when comparing declared near/far against the values the
16149
+ * projection matrix implies. */
16150
+ nearFarRelativeTolerance: 1e-3,
16151
+ /** Relative tolerance on `m[0]/m[5]` vs the manifest's `height/width`. */
16152
+ aspectRelativeTolerance: 1e-3,
16153
+ minNear: 1e-4,
16154
+ maxFar: 1e7
16155
+ };
16156
+ var coordinate = zod.z.number().min(-SCENE3D_LIMITS.maxCoordinate).max(SCENE3D_LIMITS.maxCoordinate);
16157
+ var positionSchema = zod.z.tuple([coordinate, coordinate, coordinate]);
16158
+ var scene3DCameraSampleSchema = zod.z.object({
16159
+ position: positionSchema,
16160
+ quaternion: zod.z.tuple([zod.z.number(), zod.z.number(), zod.z.number(), zod.z.number()]),
16161
+ projectionMatrix: zod.z.array(zod.z.number()).length(16),
16162
+ near: zod.z.number().min(SCENE3D_CAMERA_TRACK_LIMITS.minNear).max(SCENE3D_CAMERA_TRACK_LIMITS.maxFar),
16163
+ far: zod.z.number().min(SCENE3D_CAMERA_TRACK_LIMITS.minNear).max(SCENE3D_CAMERA_TRACK_LIMITS.maxFar),
16164
+ target: positionSchema.optional(),
16165
+ focalLengthMm: zod.z.number().min(SCENE3D_LIMITS.minFocalLengthMm).max(SCENE3D_LIMITS.maxFocalLengthMm).optional()
16166
+ }).strict();
16167
+ var scene3DCameraTrackObjectSchema = zod.z.object({
16168
+ format: zod.z.literal(SCENE3D_CAMERA_TRACK_FORMAT),
16169
+ version: zod.z.literal(SCENE3D_CAMERA_TRACK_VERSION),
16170
+ frameStart: zod.z.literal(0),
16171
+ frameCount: zod.z.number().int().min(1).max(SCENE3D_CAMERA_TRACK_LIMITS.maxFrameCount),
16172
+ fps: zod.z.number().int().min(SCENE3D_CAMERA_TRACK_LIMITS.minFps).max(SCENE3D_CAMERA_TRACK_LIMITS.maxFps),
16173
+ samples: zod.z.array(scene3DCameraSampleSchema).min(1).max(SCENE3D_CAMERA_TRACK_LIMITS.maxFrameCount)
16174
+ }).strict();
16175
+ function scene3DProjectionIssues(matrix, near, far, path) {
16176
+ const issues = [];
16177
+ const eps = SCENE3D_CAMERA_TRACK_LIMITS.projectionEpsilon;
16178
+ if (matrix.length !== 16) {
16179
+ issues.push({ path, message: `projection matrix must have exactly 16 entries (got ${matrix.length})` });
16180
+ return issues;
16181
+ }
16182
+ if (matrix.some((value) => !Number.isFinite(value))) {
16183
+ issues.push({ path, message: "projection matrix contains a non-finite entry" });
16184
+ return issues;
16185
+ }
16186
+ if (Math.abs(matrix[11]) < eps && Math.abs(matrix[15] - 1) < eps) {
16187
+ issues.push({
16188
+ path,
16189
+ message: "projection matrix is orthographic; only perspective cameras are supported by this schema version"
16190
+ });
16191
+ return issues;
16192
+ }
16193
+ for (const index of [1, 2, 3, 4, 6, 7, 12, 13, 15]) {
16194
+ if (Math.abs(matrix[index]) > eps) {
16195
+ issues.push({ path: [...path, index], message: `projection matrix entry ${index} must be 0 (got ${matrix[index]})` });
16196
+ }
16197
+ }
16198
+ if (Math.abs(matrix[11] + 1) > eps) {
16199
+ issues.push({ path: [...path, 11], message: `projection matrix entry 11 must be -1 for a perspective camera (got ${matrix[11]})` });
16200
+ }
16201
+ if (!(matrix[0] > 0)) {
16202
+ issues.push({ path: [...path, 0], message: `projection matrix entry 0 must be positive (got ${matrix[0]})` });
16203
+ }
16204
+ if (!(matrix[5] > 0)) {
16205
+ issues.push({ path: [...path, 5], message: `projection matrix entry 5 must be positive (got ${matrix[5]})` });
16206
+ }
16207
+ if (!(matrix[10] < 0)) {
16208
+ issues.push({ path: [...path, 10], message: `projection matrix entry 10 must be negative (got ${matrix[10]})` });
16209
+ }
16210
+ if (!(matrix[14] < 0)) {
16211
+ issues.push({ path: [...path, 14], message: `projection matrix entry 14 must be negative (got ${matrix[14]})` });
16212
+ }
16213
+ if (issues.length > 0) return issues;
16214
+ if (!(near > 0) || !(far > near)) {
16215
+ issues.push({ path, message: `near/far must satisfy 0 < near < far (got near ${near}, far ${far})` });
16216
+ return issues;
16217
+ }
16218
+ const tolerance = SCENE3D_CAMERA_TRACK_LIMITS.nearFarRelativeTolerance;
16219
+ const impliedNear = matrix[14] / (matrix[10] - 1);
16220
+ if (Math.abs(impliedNear - near) > Math.abs(near) * tolerance) {
16221
+ issues.push({
16222
+ path,
16223
+ message: `projection matrix implies near ${impliedNear.toPrecision(6)}, but the sample declares ${near}`
16224
+ });
16225
+ }
16226
+ const farDenominator = matrix[10] + 1;
16227
+ if (Math.abs(farDenominator) < eps) {
16228
+ issues.push({
16229
+ path,
16230
+ message: `projection matrix implies an infinite far plane, but the sample declares ${far}`
16231
+ });
16232
+ } else {
16233
+ const impliedFar = matrix[14] / farDenominator;
16234
+ if (Math.abs(impliedFar - far) > Math.abs(far) * tolerance) {
16235
+ issues.push({
16236
+ path,
16237
+ message: `projection matrix implies far ${impliedFar.toPrecision(6)}, but the sample declares ${far}`
16238
+ });
16239
+ }
16240
+ }
16241
+ return issues;
16242
+ }
16243
+ function scene3DCameraTrackIssues(track) {
16244
+ const issues = [];
16245
+ if (track.samples.length !== track.frameCount) {
16246
+ issues.push({
16247
+ path: ["samples"],
16248
+ message: `track declares ${track.frameCount} frames but carries ${track.samples.length} samples; exactly one sample per frame is required`
16249
+ });
16250
+ }
16251
+ const quaternionTolerance = SCENE3D_CAMERA_TRACK_LIMITS.quaternionTolerance;
16252
+ track.samples.forEach((sample, index) => {
16253
+ const [x, y, z22, w] = sample.quaternion;
16254
+ const norm = Math.sqrt(x * x + y * y + z22 * z22 + w * w);
16255
+ if (Math.abs(norm - 1) > quaternionTolerance) {
16256
+ issues.push({
16257
+ path: ["samples", index, "quaternion"],
16258
+ message: `quaternion at frame ${index} has length ${norm.toPrecision(6)}; it must be normalized`
16259
+ });
16260
+ }
16261
+ if (!(sample.far > sample.near)) {
16262
+ issues.push({
16263
+ path: ["samples", index, "far"],
16264
+ message: `frame ${index}: far (${sample.far}) must be greater than near (${sample.near})`
16265
+ });
16266
+ }
16267
+ for (const issue2 of scene3DProjectionIssues(
16268
+ sample.projectionMatrix,
16269
+ sample.near,
16270
+ sample.far,
16271
+ ["samples", index, "projectionMatrix"]
16272
+ )) {
16273
+ issues.push(issue2);
16274
+ }
16275
+ });
16276
+ return issues;
16277
+ }
16278
+ var scene3DCameraTrackSchema = scene3DCameraTrackObjectSchema.superRefine((track, ctx) => {
16279
+ for (const issue2 of scene3DCameraTrackIssues(track)) {
16280
+ ctx.addIssue({ code: "custom", path: issue2.path, message: issue2.message });
16281
+ }
16282
+ });
16283
+ function isScene3DCameraTrack(value) {
16284
+ return scene3DCameraTrackSchema.safeParse(value).success;
16285
+ }
16286
+ function scene3DCameraTrackPlanIssues(track, plan) {
16287
+ const issues = [];
16288
+ if (track.fps !== plan.fps) {
16289
+ issues.push({
16290
+ path: ["fps"],
16291
+ message: `camera track is ${track.fps} fps but the scene is ${plan.fps} fps; changing fps requires an explicit resample and a new revision`
16292
+ });
16293
+ }
16294
+ if (track.frameCount !== plan.durationInFrames) {
16295
+ issues.push({
16296
+ path: ["frameCount"],
16297
+ message: `camera track covers ${track.frameCount} frames but the scene is ${plan.durationInFrames} frames`
16298
+ });
16299
+ }
16300
+ const expected = plan.height / plan.width;
16301
+ const tolerance = SCENE3D_CAMERA_TRACK_LIMITS.aspectRelativeTolerance;
16302
+ track.samples.forEach((sample, index) => {
16303
+ const m0 = sample.projectionMatrix[0];
16304
+ const m5 = sample.projectionMatrix[5];
16305
+ if (!Number.isFinite(m0) || !Number.isFinite(m5) || m5 === 0) return;
16306
+ const actual = m0 / m5;
16307
+ if (Math.abs(actual - expected) > expected * tolerance) {
16308
+ issues.push({
16309
+ path: ["samples", index, "projectionMatrix"],
16310
+ message: `frame ${index}: projection is baked for aspect ${(1 / actual).toPrecision(6)} but the scene renders ${plan.width}\xD7${plan.height}; reprojection requires a new revision`
16311
+ });
16312
+ }
16313
+ });
16314
+ return issues;
16315
+ }
16316
+ function scene3DSampleForFrame(track, frame) {
16317
+ if (!Number.isInteger(frame) || frame < 0 || frame >= track.frameCount) return void 0;
16318
+ return track.samples[frame];
16319
+ }
16320
+ function parseScene3DCameraTrackJson(text) {
16321
+ const bytes = scene3DJsonByteLength(text);
16322
+ if (bytes > SCENE3D_CAMERA_TRACK_LIMITS.maxJsonBytes) {
16323
+ return {
16324
+ ok: false,
16325
+ issues: [
16326
+ {
16327
+ path: [],
16328
+ message: `camera track is ${bytes} bytes; the limit is ${SCENE3D_CAMERA_TRACK_LIMITS.maxJsonBytes}`
16329
+ }
16330
+ ]
16331
+ };
16332
+ }
16333
+ let decoded;
16334
+ try {
16335
+ decoded = JSON.parse(text);
16336
+ } catch {
16337
+ return { ok: false, issues: [{ path: [], message: "camera track is not valid JSON" }] };
16338
+ }
16339
+ const parsed = scene3DCameraTrackSchema.safeParse(decoded);
16340
+ if (!parsed.success) {
16341
+ return { ok: false, issues: scene3DZodIssues(parsed.error) };
16342
+ }
16343
+ return { ok: true, value: parsed.data };
16344
+ }
14681
16345
 
14682
16346
  // src/studio-transient.ts
14683
16347
  var STUDIO_TRANSIENT_KEYS = [
@@ -14725,6 +16389,140 @@ function stripStudioTransientSettings(settings) {
14725
16389
  }
14726
16390
  return { ...settings, studio: kept };
14727
16391
  }
16392
+ var scene3DV2OverrideInputSchema = zod.z.discriminatedUnion("kind", [
16393
+ zod.z.object({
16394
+ kind: zod.z.literal("entity-transform"),
16395
+ entityId: scene3DIdSchema,
16396
+ space: zod.z.enum(["local", "world"]),
16397
+ position: vec3Schema.optional(),
16398
+ rotation: rotationVec3Schema.optional(),
16399
+ scale: scaleVec3Schema.optional()
16400
+ }).strict(),
16401
+ zod.z.object({
16402
+ kind: zod.z.literal("entity-color"),
16403
+ entityId: scene3DIdSchema,
16404
+ materialRole: scene3DMaterialRoleSchema,
16405
+ color: scene3DColorSchema
16406
+ }).strict(),
16407
+ zod.z.object({ kind: zod.z.literal("entity-visibility"), entityId: scene3DIdSchema, visible: zod.z.boolean() }).strict(),
16408
+ zod.z.object({
16409
+ kind: zod.z.literal("camera-shot-offset"),
16410
+ shotId: scene3DIdSchema,
16411
+ positionOffset: vec3Schema.optional(),
16412
+ targetOffset: vec3Schema.optional()
16413
+ }).strict()
16414
+ ]);
16415
+ var scene3DV2EditOperationSchema = zod.z.discriminatedUnion("op", [
16416
+ zod.z.object({ op: zod.z.literal("set-override"), override: scene3DV2OverrideInputSchema }).strict(),
16417
+ zod.z.object({ op: zod.z.literal("remove-override"), overrideId: scene3DIdSchema }).strict()
16418
+ ]);
16419
+ var scene3DV2EditOperationsSchema = zod.z.array(scene3DV2EditOperationSchema).min(1).max(100);
16420
+ function channel(override) {
16421
+ switch (override.kind) {
16422
+ case "entity-transform":
16423
+ return `transform:${override.entityId}`;
16424
+ case "entity-color":
16425
+ return `color:${override.entityId}:${override.materialRole}`;
16426
+ case "entity-visibility":
16427
+ return `visibility:${override.entityId}`;
16428
+ case "camera-shot-offset":
16429
+ return `camera:${override.shotId}`;
16430
+ }
16431
+ }
16432
+ function capability(override) {
16433
+ switch (override.kind) {
16434
+ case "entity-transform":
16435
+ return "transform";
16436
+ case "entity-color":
16437
+ return "color";
16438
+ case "entity-visibility":
16439
+ return "visibility";
16440
+ case "camera-shot-offset":
16441
+ return null;
16442
+ }
16443
+ }
16444
+ function lockIssue(plan, override, externalLocks) {
16445
+ if (override.kind === "camera-shot-offset") return null;
16446
+ const target = plan.objects.find((o) => o.id === override.entityId);
16447
+ if (!target) return `Unknown entity: ${override.entityId}`;
16448
+ const cap = capability(override);
16449
+ if (target.capabilities && !target.capabilities.includes(cap)) return `Entity ${target.id} does not allow ${cap} edits`;
16450
+ if (externalLocks.has(target.id) || target.locks?.includes(cap)) return `Entity ${target.id} is locked for ${cap}`;
16451
+ if (cap !== "transform" && cap !== "visibility") return null;
16452
+ const byId = new Map(plan.objects.map((entity) => [entity.id, entity]));
16453
+ for (const entity of plan.objects) {
16454
+ if (!externalLocks.has(entity.id) && !entity.locks?.includes(cap)) continue;
16455
+ let parent = entity.parentId;
16456
+ while (parent) {
16457
+ if (parent === target.id) return `Changing ${target.id} would change locked descendant ${entity.id}`;
16458
+ parent = byId.get(parent)?.parentId;
16459
+ }
16460
+ }
16461
+ return null;
16462
+ }
16463
+ async function applyScene3DV2EditOperations(input, operations, options) {
16464
+ const parsed = scene3DPlanV2Schema.safeParse(input);
16465
+ if (!parsed.success) return { ok: false, code: "invalid_plan", message: parsed.error.issues[0]?.message ?? "Invalid scene" };
16466
+ const base = parsed.data;
16467
+ if (base.revisionId !== options.expectedRevisionId || options.expectedContentHash !== void 0 && base.provenance.contentHash !== options.expectedContentHash) {
16468
+ return { ok: false, code: "stale_revision", message: "The scene changed since this edit was prepared" };
16469
+ }
16470
+ if (!await verifyScene3DPlanV2ContentHash(base)) {
16471
+ return { ok: false, code: "invalid_plan", message: "The scene content does not match its digest" };
16472
+ }
16473
+ const ops = scene3DV2EditOperationsSchema.safeParse(operations);
16474
+ if (!ops.success) return { ok: false, code: "invalid_operations", message: ops.error.issues[0]?.message ?? "Invalid edit" };
16475
+ const revisionId = options.newRevisionId ?? newScene3DRevisionId();
16476
+ if (revisionId === base.revisionId) return { ok: false, code: "invalid_operations", message: "An edit requires a new revision identity" };
16477
+ const externalLocks = new Set(options.lockedObjectIds ?? []);
16478
+ for (const id of externalLocks) {
16479
+ if (!base.objects.some((entity) => entity.id === id)) return { ok: false, code: "invalid_operations", message: `Unknown locked entity: ${id}` };
16480
+ }
16481
+ let overrides = [...base.overrides ?? []];
16482
+ for (const [index, operation] of ops.data.entries()) {
16483
+ if (operation.op === "remove-override") {
16484
+ const existing = overrides.find((override) => override.id === operation.overrideId);
16485
+ if (!existing) return { ok: false, code: "invalid_operations", message: `Unknown override: ${operation.overrideId}` };
16486
+ const issue3 = lockIssue(base, existing, externalLocks);
16487
+ if (issue3) return { ok: false, code: "locked", message: issue3 };
16488
+ overrides = overrides.filter((override) => override.id !== existing.id);
16489
+ continue;
16490
+ }
16491
+ const issue2 = lockIssue(base, operation.override, externalLocks);
16492
+ if (issue2) return { ok: false, code: "locked", message: issue2 };
16493
+ const previous = overrides.find((override) => channel(override) === channel(operation.override));
16494
+ const compatible = previous && (previous.kind !== "entity-transform" || operation.override.kind === "entity-transform" && previous.space === operation.override.space);
16495
+ const next2 = {
16496
+ ...compatible ? previous : {},
16497
+ ...operation.override,
16498
+ id: `edit-${revisionId}-${index}`,
16499
+ sourceRevisionId: base.revisionId,
16500
+ sourceContentHash: base.provenance.contentHash,
16501
+ operationVersion: SCENE3D_V2_OVERRIDE_OPERATION_VERSION
16502
+ };
16503
+ const key = channel(next2);
16504
+ overrides = [...overrides.filter((override) => channel(override) !== key), next2];
16505
+ }
16506
+ const { sourceArtifactId: _sourceArtifactId, ...provenance } = base.provenance;
16507
+ const next = {
16508
+ ...base,
16509
+ revisionId,
16510
+ parentRevisionId: base.revisionId,
16511
+ // Geometry and cameras are reused; derived images, validation and native
16512
+ // exports describe the old revision until regenerated for these overlays.
16513
+ assets: base.assets.filter((asset) => asset.kind === "glb" || asset.kind === "camera-track-json").map((asset) => ({
16514
+ ...asset,
16515
+ originRevisionId: asset.originRevisionId ?? base.revisionId
16516
+ })),
16517
+ overrides,
16518
+ provenance: { ...provenance, sourceRevisionId: base.revisionId }
16519
+ };
16520
+ const validated = scene3DPlanV2Schema.safeParse(next);
16521
+ if (!validated.success) return { ok: false, code: "invalid_operations", message: validated.error.issues[0]?.message ?? "Invalid edited scene" };
16522
+ const plan = validated.data;
16523
+ const contentHash = await computeScene3DPlanV2ContentHash(plan);
16524
+ return { ok: true, plan: { ...plan, provenance: { ...plan.provenance, contentHash } }, changeSummary: `Applied ${ops.data.length} scene edit${ops.data.length === 1 ? "" : "s"}` };
16525
+ }
14728
16526
 
14729
16527
  exports.ACCESS_LEVELS = ACCESS_LEVELS;
14730
16528
  exports.ACTIVE_SCENE_HELPERS = ACTIVE_SCENE_HELPERS;
@@ -15053,6 +16851,38 @@ exports.REPEATABLE_NODE_TYPES = REPEATABLE_NODE_TYPES;
15053
16851
  exports.REPEAT_PLACEHOLDER = REPEAT_PLACEHOLDER;
15054
16852
  exports.REPLICATE_LIP_SYNC_PROVIDERS = REPLICATE_LIP_SYNC_PROVIDERS;
15055
16853
  exports.RESERVED_TEMPLATE_VARS = RESERVED_TEMPLATE_VARS;
16854
+ exports.SCENE3D_ASSET_KINDS = SCENE3D_ASSET_KINDS;
16855
+ exports.SCENE3D_ASSET_ROLES = SCENE3D_ASSET_ROLES;
16856
+ exports.SCENE3D_ASSET_ROLE_KINDS = SCENE3D_ASSET_ROLE_KINDS;
16857
+ exports.SCENE3D_CAMERA_TRACK_FORMAT = SCENE3D_CAMERA_TRACK_FORMAT;
16858
+ exports.SCENE3D_CAMERA_TRACK_LIMITS = SCENE3D_CAMERA_TRACK_LIMITS;
16859
+ exports.SCENE3D_CAMERA_TRACK_VERSION = SCENE3D_CAMERA_TRACK_VERSION;
16860
+ exports.SCENE3D_CLAY_LIGHTING_PRESETS = SCENE3D_CLAY_LIGHTING_PRESETS;
16861
+ exports.SCENE3D_DEFAULT_DURATION_SECONDS = SCENE3D_DEFAULT_DURATION_SECONDS;
16862
+ exports.SCENE3D_DEFAULT_ENTITY_CAPABILITIES = SCENE3D_DEFAULT_ENTITY_CAPABILITIES;
16863
+ exports.SCENE3D_DEFAULT_FPS = SCENE3D_DEFAULT_FPS;
16864
+ exports.SCENE3D_EDIT_NODE_TYPE = SCENE3D_EDIT_NODE_TYPE;
16865
+ exports.SCENE3D_ENTITY_CAPABILITIES = SCENE3D_ENTITY_CAPABILITIES;
16866
+ exports.SCENE3D_ENTITY_ROLES = SCENE3D_ENTITY_ROLES;
16867
+ exports.SCENE3D_GENERATE_NODE_TYPE = SCENE3D_GENERATE_NODE_TYPE;
16868
+ exports.SCENE3D_GLB_EXTRAS_ALLOWLIST = SCENE3D_GLB_EXTRAS_ALLOWLIST;
16869
+ exports.SCENE3D_GLB_EXTRAS_ENTITY_ID = SCENE3D_GLB_EXTRAS_ENTITY_ID;
16870
+ exports.SCENE3D_GLB_EXTRAS_MATERIAL_ROLE = SCENE3D_GLB_EXTRAS_MATERIAL_ROLE;
16871
+ exports.SCENE3D_GLB_EXTRAS_SUBPART_ID = SCENE3D_GLB_EXTRAS_SUBPART_ID;
16872
+ exports.SCENE3D_LIMITS = SCENE3D_LIMITS;
16873
+ exports.SCENE3D_PLAN_FIELD = SCENE3D_PLAN_FIELD;
16874
+ exports.SCENE3D_PLAN_TYPE = SCENE3D_PLAN_TYPE;
16875
+ exports.SCENE3D_PRIMITIVES = SCENE3D_PRIMITIVES;
16876
+ exports.SCENE3D_PRIMITIVE_MATERIAL_ROLE = SCENE3D_PRIMITIVE_MATERIAL_ROLE;
16877
+ exports.SCENE3D_RENDERER_ASSET_KINDS = SCENE3D_RENDERER_ASSET_KINDS;
16878
+ exports.SCENE3D_SCHEMA_VERSION = SCENE3D_SCHEMA_VERSION;
16879
+ exports.SCENE3D_SCHEMA_VERSION_V2 = SCENE3D_SCHEMA_VERSION_V2;
16880
+ exports.SCENE3D_SUPPORTED_SCHEMA_VERSIONS = SCENE3D_SUPPORTED_SCHEMA_VERSIONS;
16881
+ exports.SCENE3D_V2_CONTENT_HASH_EXCLUDED = SCENE3D_V2_CONTENT_HASH_EXCLUDED;
16882
+ exports.SCENE3D_V2_ENGINES = SCENE3D_V2_ENGINES;
16883
+ exports.SCENE3D_V2_LIMITS = SCENE3D_V2_LIMITS;
16884
+ exports.SCENE3D_V2_OVERRIDE_OPERATION_VERSION = SCENE3D_V2_OVERRIDE_OPERATION_VERSION;
16885
+ exports.SCENE3D_V2_PRIMITIVES = SCENE3D_V2_PRIMITIVES;
15056
16886
  exports.SCENE_HELPER_NAMES = SCENE_HELPER_NAMES;
15057
16887
  exports.SCRAPER_ACTOR_LABELS = SCRAPER_ACTOR_LABELS;
15058
16888
  exports.SCRAPER_CREDIT_COSTS = SCRAPER_CREDIT_COSTS;
@@ -15216,6 +17046,8 @@ exports.applyDefaultVideoSelection = applyDefaultVideoSelection;
15216
17046
  exports.applyHandleInputOverride = applyHandleInputOverride;
15217
17047
  exports.applyRange = applyRange;
15218
17048
  exports.applyRangeIndices = applyRangeIndices;
17049
+ exports.applyScene3DEditOperations = applyScene3DEditOperations;
17050
+ exports.applyScene3DV2EditOperations = applyScene3DV2EditOperations;
15219
17051
  exports.applySlots = applySlots;
15220
17052
  exports.applyVideoAudioToggle = applyVideoAudioToggle;
15221
17053
  exports.applyVideoNegativePrompt = applyVideoNegativePrompt;
@@ -15248,6 +17080,7 @@ exports.calculateCombinedProgress = calculateCombinedProgress;
15248
17080
  exports.calculateMonetizationMarkup = calculateMonetizationMarkup;
15249
17081
  exports.calculateMonetizedCost = calculateMonetizedCost;
15250
17082
  exports.calculateProgress = calculateProgress;
17083
+ exports.canonicalScene3DPlanV2Json = canonicalScene3DPlanV2Json;
15251
17084
  exports.canonicalVarName = canonicalVarName;
15252
17085
  exports.characterBoardItems = characterBoardItems;
15253
17086
  exports.characterBucketDisplayRank = characterBucketDisplayRank;
@@ -15267,6 +17100,7 @@ exports.clipLookSchema = clipLookSchema;
15267
17100
  exports.collectAncestorRefs = collectAncestorRefs;
15268
17101
  exports.combineSameLabelRefs = combineSameLabelRefs;
15269
17102
  exports.computeAggregateLanes = computeAggregateLanes;
17103
+ exports.computeScene3DPlanV2ContentHash = computeScene3DPlanV2ContentHash;
15270
17104
  exports.countRefModalityEdges = countRefModalityEdges;
15271
17105
  exports.creditRangesAll = creditRangesAll;
15272
17106
  exports.creditsToUsd = creditsToUsd;
@@ -15388,12 +17222,19 @@ exports.isGeminiOmniProvider = isGeminiOmniProvider;
15388
17222
  exports.isGvpSupportedProvider = isGvpSupportedProvider;
15389
17223
  exports.isHandleInputWired = isHandleInputWired;
15390
17224
  exports.isKineticCaptionStyle = isKineticCaptionStyle;
17225
+ exports.isKnownScene3DEngine = isKnownScene3DEngine;
15391
17226
  exports.isLocationUsageMode = isLocationUsageMode;
15392
17227
  exports.isMinimaxH3Provider = isMinimaxH3Provider;
15393
17228
  exports.isObjectAspectRatio = isObjectAspectRatio;
15394
17229
  exports.isOversizedScene = isOversizedScene;
15395
17230
  exports.isPaygRetentionActive = isPaygRetentionActive;
15396
17231
  exports.isPerSecondLipSyncProvider = isPerSecondLipSyncProvider;
17232
+ exports.isScene3DCameraTrack = isScene3DCameraTrack;
17233
+ exports.isScene3DHttpUrl = isScene3DHttpUrl;
17234
+ exports.isScene3DPlan = isScene3DPlan;
17235
+ exports.isScene3DPlanV1 = isScene3DPlanV1;
17236
+ exports.isScene3DPlanV2 = isScene3DPlanV2;
17237
+ exports.isScene3DSchemaVersionSupported = isScene3DSchemaVersionSupported;
15397
17238
  exports.isScraperActor = isScraperActor;
15398
17239
  exports.isSeedance2Provider = isSeedance2Provider;
15399
17240
  exports.isTiltDirection = isTiltDirection;
@@ -15428,6 +17269,7 @@ exports.modelToNodeTarget = modelToNodeTarget;
15428
17269
  exports.modelsForInputMode = modelsForInputMode;
15429
17270
  exports.modelsWithFeature = modelsWithFeature;
15430
17271
  exports.motionGraphicsFeature = motionGraphicsFeature;
17272
+ exports.newScene3DRevisionId = newScene3DRevisionId;
15431
17273
  exports.normalizeLottieLayers = normalizeLottieLayers;
15432
17274
  exports.normalizeMinimaxH3Resolution = normalizeMinimaxH3Resolution;
15433
17275
  exports.normalizeModelInput = normalizeModelInput;
@@ -15447,6 +17289,8 @@ exports.parseListExpression = parseListExpression;
15447
17289
  exports.parseLocationMentionToken = parseLocationMentionToken;
15448
17290
  exports.parseNodePresetExport = parseNodePresetExport;
15449
17291
  exports.parseNodeRef = parseNodeRef;
17292
+ exports.parseScene3DCameraTrackJson = parseScene3DCameraTrackJson;
17293
+ exports.parseScene3DPlanV2Json = parseScene3DPlanV2Json;
15450
17294
  exports.pickAiAvatarBucket = pickAiAvatarBucket;
15451
17295
  exports.pickIds = pickIds;
15452
17296
  exports.pickLipSyncBucket = pickLipSyncBucket;
@@ -15514,9 +17358,77 @@ exports.rewriteSlotTokens = rewriteSlotTokens;
15514
17358
  exports.rewriteSpeakerSlots = rewriteSpeakerSlots;
15515
17359
  exports.rgbaArrayToHex = rgbaArrayToHex;
15516
17360
  exports.roleToPhrase = roleToPhrase;
17361
+ exports.rotationVec3Schema = rotationVec3Schema;
15517
17362
  exports.runSelector = runSelector;
15518
17363
  exports.safetyRetryPolicy = safetyRetryPolicy;
15519
17364
  exports.sanitizeRole = sanitizeRole;
17365
+ exports.scaleVec3Schema = scaleVec3Schema;
17366
+ exports.scene3DAcceptedSchemaVersionsSchema = scene3DAcceptedSchemaVersionsSchema;
17367
+ exports.scene3DAnchorNameSchema = scene3DAnchorNameSchema;
17368
+ exports.scene3DAnchorSchema = scene3DAnchorSchema;
17369
+ exports.scene3DAnyPlanSchema = scene3DAnyPlanSchema;
17370
+ exports.scene3DAssetAnimationSchema = scene3DAssetAnimationSchema;
17371
+ exports.scene3DAssetIdSchema = scene3DAssetIdSchema;
17372
+ exports.scene3DAssetRefSchema = scene3DAssetRefSchema;
17373
+ exports.scene3DCameraChangesSchema = scene3DCameraChangesSchema;
17374
+ exports.scene3DCameraKeyframeSchema = scene3DCameraKeyframeSchema;
17375
+ exports.scene3DCameraSampleSchema = scene3DCameraSampleSchema;
17376
+ exports.scene3DCameraSchema = scene3DCameraSchema;
17377
+ exports.scene3DCameraTrackIssues = scene3DCameraTrackIssues;
17378
+ exports.scene3DCameraTrackObjectSchema = scene3DCameraTrackObjectSchema;
17379
+ exports.scene3DCameraTrackPlanIssues = scene3DCameraTrackPlanIssues;
17380
+ exports.scene3DCameraTrackSchema = scene3DCameraTrackSchema;
17381
+ exports.scene3DClayLightingSchema = scene3DClayLightingSchema;
17382
+ exports.scene3DColorSchema = scene3DColorSchema;
17383
+ exports.scene3DDeepEqual = scene3DDeepEqual;
17384
+ exports.scene3DEasingSchema = scene3DEasingSchema;
17385
+ exports.scene3DEditOperationSchema = scene3DEditOperationSchema;
17386
+ exports.scene3DEditOperationsSchema = scene3DEditOperationsSchema;
17387
+ exports.scene3DEngineIdSchema = scene3DEngineIdSchema;
17388
+ exports.scene3DEntityAcceptsOverlay = scene3DEntityAcceptsOverlay;
17389
+ exports.scene3DEntityCapabilitySchema = scene3DEntityCapabilitySchema;
17390
+ exports.scene3DEntityV2Schema = scene3DEntityV2Schema;
17391
+ exports.scene3DEntityVisualSchema = scene3DEntityVisualSchema;
17392
+ exports.scene3DIdSchema = scene3DIdSchema;
17393
+ exports.scene3DJsonByteLength = scene3DJsonByteLength;
17394
+ exports.scene3DLightingChangesSchema = scene3DLightingChangesSchema;
17395
+ exports.scene3DLightingSchema = scene3DLightingSchema;
17396
+ exports.scene3DMaterialBindingSchema = scene3DMaterialBindingSchema;
17397
+ exports.scene3DMaterialNameSchema = scene3DMaterialNameSchema;
17398
+ exports.scene3DMaterialRoleSchema = scene3DMaterialRoleSchema;
17399
+ exports.scene3DNodeIdSchema = scene3DNodeIdSchema;
17400
+ exports.scene3DObjectChangesSchema = scene3DObjectChangesSchema;
17401
+ exports.scene3DObjectKeyframeSchema = scene3DObjectKeyframeSchema;
17402
+ exports.scene3DObjectSchema = scene3DObjectSchema;
17403
+ exports.scene3DOverrideSchema = scene3DOverrideSchema;
17404
+ exports.scene3DPlanIssues = scene3DPlanIssues;
17405
+ exports.scene3DPlanSchema = scene3DPlanSchema;
17406
+ exports.scene3DPlanSchemaVersion = scene3DPlanSchemaVersion;
17407
+ exports.scene3DPlanV1Issues = scene3DPlanV1Issues;
17408
+ exports.scene3DPlanV1ObjectSchema = scene3DPlanV1ObjectSchema;
17409
+ exports.scene3DPlanV1Schema = scene3DPlanV1Schema;
17410
+ exports.scene3DPlanV2Issues = scene3DPlanV2Issues;
17411
+ exports.scene3DPlanV2ObjectSchema = scene3DPlanV2ObjectSchema;
17412
+ exports.scene3DPlanV2Schema = scene3DPlanV2Schema;
17413
+ exports.scene3DPrimitiveSchema = scene3DPrimitiveSchema;
17414
+ exports.scene3DProjectionIssues = scene3DProjectionIssues;
17415
+ exports.scene3DProvenanceSchema = scene3DProvenanceSchema;
17416
+ exports.scene3DReferenceSchema = scene3DReferenceSchema;
17417
+ exports.scene3DSampleForFrame = scene3DSampleForFrame;
17418
+ exports.scene3DSha256Schema = scene3DSha256Schema;
17419
+ exports.scene3DShotForFrame = scene3DShotForFrame;
17420
+ exports.scene3DShotIndexForFrame = scene3DShotIndexForFrame;
17421
+ exports.scene3DShotSchema = scene3DShotSchema;
17422
+ exports.scene3DUrlSchema = scene3DUrlSchema;
17423
+ exports.scene3DV2AdmissionIssues = scene3DV2AdmissionIssues;
17424
+ exports.scene3DV2EditOperationSchema = scene3DV2EditOperationSchema;
17425
+ exports.scene3DV2EditOperationsSchema = scene3DV2EditOperationsSchema;
17426
+ exports.scene3DV2HierarchyDepth = scene3DV2HierarchyDepth;
17427
+ exports.scene3DV2NormalizationIssues = scene3DV2NormalizationIssues;
17428
+ exports.scene3DV2OverrideInputSchema = scene3DV2OverrideInputSchema;
17429
+ exports.scene3DV2ResourceUsage = scene3DV2ResourceUsage;
17430
+ exports.scene3DVersionTokenSchema = scene3DVersionTokenSchema;
17431
+ exports.scene3DZodIssues = scene3DZodIssues;
15520
17432
  exports.searchModelVariants = searchModelVariants;
15521
17433
  exports.seedance2AudioLimitSec = seedance2AudioLimitSec;
15522
17434
  exports.segmentDurationsFor = segmentDurationsFor;
@@ -15528,6 +17440,7 @@ exports.selectLoraRoutingForMentions = selectLoraRoutingForMentions;
15528
17440
  exports.selectRandom = selectRandom;
15529
17441
  exports.setRegisteredPersonPackFields = setRegisteredPersonPackFields;
15530
17442
  exports.settledWithLimit = settledWithLimit;
17443
+ exports.sizeVec3Schema = sizeVec3Schema;
15531
17444
  exports.slotVariationSchema = slotVariationSchema;
15532
17445
  exports.sortCharacterEntriesForDisplay = sortCharacterEntriesForDisplay;
15533
17446
  exports.sortListItems = sortListItems;
@@ -15541,6 +17454,7 @@ exports.stripDerivedAnalysisFields = stripDerivedAnalysisFields;
15541
17454
  exports.stripExportContent = stripExportContent;
15542
17455
  exports.stripStudioTransientSettings = stripStudioTransientSettings;
15543
17456
  exports.stripTransientRuntimeData = stripTransientRuntimeData;
17457
+ exports.summarizeScene3DOperations = summarizeScene3DOperations;
15544
17458
  exports.sunoCreditType = sunoCreditType;
15545
17459
  exports.supportedDefaultDimensions = supportedDefaultDimensions;
15546
17460
  exports.supportsAdvancedMode = supportsAdvancedMode;
@@ -15569,6 +17483,8 @@ exports.validateObjects = validateObjects;
15569
17483
  exports.validateProviderForNodeType = validateProviderForNodeType;
15570
17484
  exports.validateSubWorkflowRoutes = validateSubWorkflowRoutes;
15571
17485
  exports.variantJobId = variantJobId;
17486
+ exports.vec3Schema = vec3Schema;
17487
+ exports.verifyScene3DPlanV2ContentHash = verifyScene3DPlanV2ContentHash;
15572
17488
  exports.videoAnalysisCreditSegment = videoAnalysisCreditSegment;
15573
17489
  exports.videoAnalysisNumWindows = videoAnalysisNumWindows;
15574
17490
  exports.videoAnalysisResultSchema = videoAnalysisResultSchema;