@playdrop/playdrop-cli 0.17.5 → 0.17.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/README.md +12 -0
  2. package/config/client-meta.json +2 -2
  3. package/dist/apps/build.d.ts +2 -1
  4. package/dist/apps/build.js +6 -2
  5. package/dist/apps/index.d.ts +2 -3
  6. package/dist/apps/index.js +17 -49
  7. package/dist/apps/loadCheck.d.ts +3 -1
  8. package/dist/apps/loadCheck.js +123 -64
  9. package/dist/apps/publishingRuntime.d.ts +18 -0
  10. package/dist/apps/publishingRuntime.js +111 -0
  11. package/dist/apps/runtimeSyntax.d.ts +7 -0
  12. package/dist/apps/runtimeSyntax.js +154 -0
  13. package/dist/apps/serverBuild.d.ts +3 -3
  14. package/dist/apps/serverBuild.js +8 -43
  15. package/dist/apps/serverSdk.d.ts +23 -0
  16. package/dist/apps/serverSdk.js +157 -0
  17. package/dist/apps/upload.d.ts +9 -3
  18. package/dist/apps/upload.js +71 -25
  19. package/dist/apps/validate.d.ts +2 -1
  20. package/dist/apps/validate.js +3 -1
  21. package/dist/commandContext.js +34 -10
  22. package/dist/commands/build.js +3 -1
  23. package/dist/commands/create.js +35 -16
  24. package/dist/commands/dev.js +11 -0
  25. package/dist/commands/devGameServer.d.ts +8 -0
  26. package/dist/commands/devGameServer.js +12 -9
  27. package/dist/commands/devServer.d.ts +1 -0
  28. package/dist/commands/devServer.js +21 -3
  29. package/dist/commands/source.d.ts +11 -3
  30. package/dist/commands/source.js +82 -14
  31. package/dist/commands/taskCaptureSession.d.ts +1 -1
  32. package/dist/commands/taskCaptureSession.js +1 -1
  33. package/dist/commands/upload-content.js +13 -12
  34. package/dist/commands/upload.d.ts +2 -9
  35. package/dist/commands/upload.js +5 -21
  36. package/dist/commands/validate.js +1 -1
  37. package/dist/commands/worker/game-server-infra.d.ts +6 -0
  38. package/dist/commands/worker/game-server-infra.js +10 -0
  39. package/dist/commands/worker/game-source-git.d.ts +9 -1
  40. package/dist/commands/worker/game-source-git.js +65 -19
  41. package/dist/commands/worker/generation.d.ts +2 -0
  42. package/dist/commands/worker/generation.js +35 -5
  43. package/dist/commands/worker.d.ts +1 -1
  44. package/dist/commands/worker.js +57 -17
  45. package/dist/environment.d.ts +1 -0
  46. package/dist/environment.js +7 -0
  47. package/dist/index.js +10 -2
  48. package/dist/playwright.d.ts +6 -4
  49. package/dist/playwright.js +24 -9
  50. package/dist/proceduralValidation.d.ts +16 -0
  51. package/dist/proceduralValidation.js +227 -0
  52. package/dist/proceduralValidationBrowser.d.mts +3 -0
  53. package/dist/proceduralValidationBrowser.mjs +67 -0
  54. package/node_modules/@playdrop/api-client/dist/domains/apps.d.ts.map +1 -1
  55. package/node_modules/@playdrop/api-client/dist/domains/apps.js +11 -1
  56. package/node_modules/@playdrop/api-client/dist/domains/chat.d.ts +6 -1
  57. package/node_modules/@playdrop/api-client/dist/domains/chat.d.ts.map +1 -1
  58. package/node_modules/@playdrop/api-client/dist/domains/chat.js +13 -1
  59. package/node_modules/@playdrop/config/client-meta.json +2 -2
  60. package/node_modules/@playdrop/procedural/dist/index.d.ts +201 -0
  61. package/node_modules/@playdrop/procedural/dist/index.d.ts.map +1 -0
  62. package/node_modules/@playdrop/procedural/dist/index.js +763 -0
  63. package/node_modules/@playdrop/procedural/package.json +36 -0
  64. package/node_modules/@playdrop/types/dist/api.d.ts +37 -4
  65. package/node_modules/@playdrop/types/dist/api.d.ts.map +1 -1
  66. package/node_modules/@playdrop/types/dist/chat.d.ts +7 -1
  67. package/node_modules/@playdrop/types/dist/chat.d.ts.map +1 -1
  68. package/node_modules/@playdrop/types/dist/social.d.ts +20 -0
  69. package/node_modules/@playdrop/types/dist/social.d.ts.map +1 -1
  70. package/node_modules/@playdrop/types/dist/version.d.ts +2 -0
  71. package/node_modules/@playdrop/types/dist/version.d.ts.map +1 -1
  72. package/package.json +7 -4
@@ -0,0 +1,763 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __export = (target, all) => {
6
+ for (var name in all)
7
+ __defProp(target, name, { get: all[name], enumerable: true });
8
+ };
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
+ var dist_exports = {};
19
+ __export(dist_exports, {
20
+ PLAYDROP_PROCEDURAL_MANIFEST_PROTOCOL: () => PLAYDROP_PROCEDURAL_MANIFEST_PROTOCOL,
21
+ PLAYDROP_PROCEDURAL_PACK_PROTOCOL: () => PLAYDROP_PROCEDURAL_PACK_PROTOCOL,
22
+ PLAYDROP_PROCEDURAL_PROTOCOL: () => PLAYDROP_PROCEDURAL_PROTOCOL,
23
+ PROCEDURAL_PARAMETER_ICON_IDS: () => PROCEDURAL_PARAMETER_ICON_IDS,
24
+ requireResolvedAssetParameter: () => requireResolvedAssetParameter,
25
+ validateCompleteProceduralParameters: () => validateCompleteProceduralParameters,
26
+ validateProceduralManifest: () => validateProceduralManifest,
27
+ validateProceduralModule: () => validateProceduralModule,
28
+ validateProceduralParameters: () => validateProceduralParameters,
29
+ validateResolvedAssetParameter: () => validateResolvedAssetParameter
30
+ });
31
+ module.exports = __toCommonJS(dist_exports);
32
+ const PLAYDROP_PROCEDURAL_PROTOCOL = "playdrop-procedural@1";
33
+ const PLAYDROP_PROCEDURAL_PACK_PROTOCOL = "playdrop-procedural-pack@1";
34
+ const PLAYDROP_PROCEDURAL_MANIFEST_PROTOCOL = "playdrop-procedural-manifest@1";
35
+ const PROCEDURAL_PARAMETER_ICON_IDS = [
36
+ "adjust",
37
+ "asset",
38
+ "angle",
39
+ "color",
40
+ "count",
41
+ "depth",
42
+ "height",
43
+ "length",
44
+ "light",
45
+ "material",
46
+ "radius",
47
+ "scale",
48
+ "size",
49
+ "toggle",
50
+ "variation",
51
+ "visibility",
52
+ "width"
53
+ ];
54
+ const SLUG_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
55
+ const CAPABILITY_ID_PATTERN = /^[A-Za-z][A-Za-z0-9._-]*$/;
56
+ const EXACT_ASSET_REF_PATTERN = /^asset:[a-z0-9]+(?:-[a-z0-9]+)*\/[a-z0-9]+(?:-[a-z0-9]+)*@r[1-9][0-9]*$/;
57
+ const COLOR_PATTERN = /^#[0-9a-f]{6}$/i;
58
+ const SAFE_RELATIVE_PATH_PATTERN = /^(?!\/)(?!.*\\)(?!.*(?:^|\/)\.\.(?:\/|$))[^\0]+$/;
59
+ const ASSET_SPEC_FAMILY_PATTERN = /^asset-spec:[a-z0-9]+(?:-[a-z0-9]+)*\/[a-z0-9]+(?:-[a-z0-9]+)*$/;
60
+ const PARAMETER_ICON_IDS = new Set(PROCEDURAL_PARAMETER_ICON_IDS);
61
+ const PARAMETER_AFFECTS = /* @__PURE__ */ new Set(["geometry", "material", "animation"]);
62
+ const PARAMETER_CONTROLS = /* @__PURE__ */ new Set([
63
+ "auto",
64
+ "slider",
65
+ "stepper",
66
+ "toggle",
67
+ "select",
68
+ "segmented",
69
+ "color",
70
+ "palette",
71
+ "asset"
72
+ ]);
73
+ const PARAMETER_CONTROLS_BY_TYPE = {
74
+ number: /* @__PURE__ */ new Set(["auto", "slider", "stepper"]),
75
+ boolean: /* @__PURE__ */ new Set(["auto", "toggle"]),
76
+ color: /* @__PURE__ */ new Set(["auto", "color"]),
77
+ select: /* @__PURE__ */ new Set(["auto", "select", "segmented", "palette"]),
78
+ asset: /* @__PURE__ */ new Set(["auto", "asset"])
79
+ };
80
+ const ASSET_CATEGORIES = /* @__PURE__ */ new Set(["IMAGE", "VIDEO", "AUDIO", "SPRITESHEET", "MODEL_3D", "CUSTOM"]);
81
+ const PRESENTATION_SCENE_PRESETS = /* @__PURE__ */ new Set(["studio", "light", "dark", "outdoor"]);
82
+ const PRESENTATION_CAMERA_VIEWS = /* @__PURE__ */ new Set(["perspective", "front", "side", "top"]);
83
+ function isRecord(value) {
84
+ return typeof value === "object" && value !== null && !Array.isArray(value);
85
+ }
86
+ function nonEmptyString(value) {
87
+ return typeof value === "string" && value.trim().length > 0;
88
+ }
89
+ function finitePositive(value) {
90
+ return typeof value === "number" && Number.isFinite(value) && value > 0;
91
+ }
92
+ function validateUniqueStrings(value, label, errors) {
93
+ if (!Array.isArray(value)) {
94
+ errors.push(`${label} must be an array`);
95
+ return [];
96
+ }
97
+ const strings = [];
98
+ const seen = /* @__PURE__ */ new Set();
99
+ for (const item of value) {
100
+ if (!nonEmptyString(item)) {
101
+ errors.push(`${label} entries must be non-empty strings`);
102
+ continue;
103
+ }
104
+ if (seen.has(item))
105
+ errors.push(`${label} entries must be unique: ${item}`);
106
+ else {
107
+ seen.add(item);
108
+ strings.push(item);
109
+ }
110
+ }
111
+ return strings;
112
+ }
113
+ function selectOptionValue(option) {
114
+ return typeof option === "string" ? option : option.value;
115
+ }
116
+ function validateSelectOptions(value, label, errors) {
117
+ if (!Array.isArray(value)) {
118
+ errors.push(`${label} must be an array`);
119
+ return [];
120
+ }
121
+ const values = [];
122
+ const seen = /* @__PURE__ */ new Set();
123
+ for (const option of value) {
124
+ let optionValue;
125
+ if (typeof option === "string") {
126
+ optionValue = option;
127
+ } else if (isRecord(option)) {
128
+ optionValue = option.value;
129
+ if (!nonEmptyString(option.label))
130
+ errors.push(`${label} entries must have a label`);
131
+ if (option.swatches !== void 0) {
132
+ const swatches = validateUniqueStrings(option.swatches, `${label} swatches`, errors);
133
+ if (swatches.length === 0 || swatches.length > 4) {
134
+ errors.push(`${label} swatches must contain between one and four colors`);
135
+ }
136
+ for (const swatch of swatches) {
137
+ if (!COLOR_PATTERN.test(swatch))
138
+ errors.push(`${label} swatches must be six-digit hex colors`);
139
+ }
140
+ }
141
+ } else {
142
+ errors.push(`${label} entries must be strings or option objects`);
143
+ continue;
144
+ }
145
+ if (!nonEmptyString(optionValue)) {
146
+ errors.push(`${label} entries must have a non-empty value`);
147
+ continue;
148
+ }
149
+ if (seen.has(optionValue))
150
+ errors.push(`${label} entries must be unique: ${optionValue}`);
151
+ else {
152
+ seen.add(optionValue);
153
+ values.push(optionValue);
154
+ }
155
+ }
156
+ return values;
157
+ }
158
+ function validateParameterPresentation(id, type, value, errors) {
159
+ if (value === void 0)
160
+ return;
161
+ if (!isRecord(value)) {
162
+ errors.push(`Parameter ${id} presentation must be an object`);
163
+ return;
164
+ }
165
+ if (value.icon !== void 0 && (typeof value.icon !== "string" || !PARAMETER_ICON_IDS.has(value.icon))) {
166
+ errors.push(`Parameter ${id} presentation.icon is unsupported`);
167
+ }
168
+ if (value.control !== void 0) {
169
+ if (typeof value.control !== "string" || !PARAMETER_CONTROLS.has(value.control)) {
170
+ errors.push(`Parameter ${id} presentation.control is unsupported`);
171
+ } else if (!PARAMETER_CONTROLS_BY_TYPE[type].has(value.control)) {
172
+ errors.push(`Parameter ${id} presentation.control does not match ${type}`);
173
+ }
174
+ }
175
+ if (value.unit !== void 0 && !nonEmptyString(value.unit)) {
176
+ errors.push(`Parameter ${id} presentation.unit must be a non-empty string`);
177
+ }
178
+ if (value.precision !== void 0 && (!Number.isInteger(value.precision) || Number(value.precision) < 0 || Number(value.precision) > 6)) {
179
+ errors.push(`Parameter ${id} presentation.precision must be an integer from 0 to 6`);
180
+ }
181
+ if (value.order !== void 0 && (typeof value.order !== "number" || !Number.isFinite(value.order))) {
182
+ errors.push(`Parameter ${id} presentation.order must be finite`);
183
+ }
184
+ }
185
+ function validateParameterDefinitions(value, errors) {
186
+ if (!isRecord(value)) {
187
+ errors.push("parameters must be an object");
188
+ return {};
189
+ }
190
+ const definitions = {};
191
+ for (const [id, candidate] of Object.entries(value)) {
192
+ if (!CAPABILITY_ID_PATTERN.test(id))
193
+ errors.push(`Parameter id is invalid: ${id}`);
194
+ if (!isRecord(candidate)) {
195
+ errors.push(`Parameter ${id} must be an object`);
196
+ continue;
197
+ }
198
+ if (!nonEmptyString(candidate.label))
199
+ errors.push(`Parameter ${id} must have a label`);
200
+ if (candidate.description !== void 0 && !nonEmptyString(candidate.description)) {
201
+ errors.push(`Parameter ${id} description must be a non-empty string`);
202
+ }
203
+ if (candidate.group !== void 0 && !nonEmptyString(candidate.group)) {
204
+ errors.push(`Parameter ${id} group must be a non-empty string`);
205
+ }
206
+ if (candidate.affects !== void 0 && (typeof candidate.affects !== "string" || !PARAMETER_AFFECTS.has(candidate.affects))) {
207
+ errors.push(`Parameter ${id} affects is unsupported`);
208
+ }
209
+ if (candidate.type === "number") {
210
+ validateParameterPresentation(id, "number", candidate.presentation, errors);
211
+ if (typeof candidate.min !== "number" || !Number.isFinite(candidate.min))
212
+ errors.push(`Parameter ${id} min must be finite`);
213
+ if (typeof candidate.max !== "number" || !Number.isFinite(candidate.max))
214
+ errors.push(`Parameter ${id} max must be finite`);
215
+ if (!finitePositive(candidate.step))
216
+ errors.push(`Parameter ${id} step must be greater than zero`);
217
+ if (typeof candidate.min === "number" && Number.isFinite(candidate.min) && typeof candidate.max === "number" && Number.isFinite(candidate.max) && candidate.min > candidate.max)
218
+ errors.push(`Parameter ${id} min must not exceed max`);
219
+ } else if (candidate.type === "select") {
220
+ validateParameterPresentation(id, "select", candidate.presentation, errors);
221
+ const options = validateSelectOptions(candidate.options, `Parameter ${id} options`, errors);
222
+ if (options.length === 0)
223
+ errors.push(`Parameter ${id} must have at least one option`);
224
+ } else if (candidate.type === "boolean") {
225
+ validateParameterPresentation(id, "boolean", candidate.presentation, errors);
226
+ } else if (candidate.type === "color") {
227
+ validateParameterPresentation(id, "color", candidate.presentation, errors);
228
+ } else if (candidate.type === "asset") {
229
+ validateParameterPresentation(id, "asset", candidate.presentation, errors);
230
+ if (candidate.default !== null && (typeof candidate.default !== "string" || !EXACT_ASSET_REF_PATTERN.test(candidate.default))) {
231
+ errors.push(`Parameter ${id} default must be null or an exact asset revision ref`);
232
+ }
233
+ if (candidate.required !== void 0 && typeof candidate.required !== "boolean") {
234
+ errors.push(`Parameter ${id} required must be a boolean`);
235
+ }
236
+ if (!isRecord(candidate.accepts)) {
237
+ errors.push(`Parameter ${id} accepts must be an object`);
238
+ } else {
239
+ const accepts = candidate.accepts;
240
+ validateKnownKeys(accepts, /* @__PURE__ */ new Set(["category", "subcategory", "formats", "assetSpec"]), `Parameter ${id} accepts`, errors);
241
+ if (accepts.category !== void 0 && (typeof accepts.category !== "string" || !ASSET_CATEGORIES.has(accepts.category))) {
242
+ errors.push(`Parameter ${id} accepts.category is unsupported`);
243
+ }
244
+ if (accepts.subcategory !== void 0 && (typeof accepts.subcategory !== "string" || !SLUG_PATTERN.test(accepts.subcategory))) {
245
+ errors.push(`Parameter ${id} accepts.subcategory must be a slug`);
246
+ }
247
+ if (accepts.formats !== void 0) {
248
+ const formats = validateUniqueStrings(accepts.formats, `Parameter ${id} accepts.formats`, errors);
249
+ for (const format of formats) {
250
+ if (!/^[A-Z0-9_]+$/.test(format))
251
+ errors.push(`Parameter ${id} accepts.formats entries must be uppercase format ids`);
252
+ }
253
+ }
254
+ if (accepts.assetSpec !== void 0 && (typeof accepts.assetSpec !== "string" || !ASSET_SPEC_FAMILY_PATTERN.test(accepts.assetSpec))) {
255
+ errors.push(`Parameter ${id} accepts.assetSpec must be an asset spec family ref`);
256
+ }
257
+ if (Object.keys(accepts).length === 0)
258
+ errors.push(`Parameter ${id} accepts must declare a compatibility filter`);
259
+ }
260
+ } else {
261
+ errors.push(`Parameter ${id} has an unsupported type`);
262
+ }
263
+ definitions[id] = candidate;
264
+ }
265
+ return definitions;
266
+ }
267
+ function validateDefinitions(value, label, errors, validate) {
268
+ if (!Array.isArray(value)) {
269
+ errors.push(`${label} must be an array`);
270
+ return [];
271
+ }
272
+ const result = [];
273
+ const ids = /* @__PURE__ */ new Set();
274
+ for (const item of value) {
275
+ if (!isRecord(item) || !nonEmptyString(item.id)) {
276
+ errors.push(`${label} entries must have a non-empty id`);
277
+ continue;
278
+ }
279
+ if (!CAPABILITY_ID_PATTERN.test(item.id))
280
+ errors.push(`${label} id is invalid: ${item.id}`);
281
+ if (ids.has(item.id))
282
+ errors.push(`${label} ids must be unique: ${item.id}`);
283
+ else
284
+ ids.add(item.id);
285
+ validate(item, item.id);
286
+ result.push(item);
287
+ }
288
+ return result;
289
+ }
290
+ function parameterError(id, definition, value) {
291
+ if (definition.type === "number") {
292
+ if (typeof value !== "number" || !Number.isFinite(value))
293
+ return `${id} must be a finite number`;
294
+ if (value < definition.min || value > definition.max)
295
+ return `${id} must be between ${definition.min} and ${definition.max}`;
296
+ return null;
297
+ }
298
+ if (definition.type === "boolean")
299
+ return typeof value === "boolean" ? null : `${id} must be a boolean`;
300
+ if (definition.type === "color")
301
+ return typeof value === "string" && COLOR_PATTERN.test(value) ? null : `${id} must be a six-digit hex color`;
302
+ if (definition.type === "select" && Array.isArray(definition.options)) {
303
+ const optionValues = definition.options.map(selectOptionValue);
304
+ return typeof value === "string" && optionValues.includes(value) ? null : `${id} must be one of ${optionValues.join(", ")}`;
305
+ }
306
+ if (definition.type === "asset") {
307
+ if (value === null && !definition.required)
308
+ return null;
309
+ return typeof value === "string" && EXACT_ASSET_REF_PATTERN.test(value) ? null : `${id} must be an exact asset revision ref`;
310
+ }
311
+ return `${id} has an invalid definition`;
312
+ }
313
+ function validateProceduralParameters(definitions, values) {
314
+ const errors = [];
315
+ if (!isRecord(definitions))
316
+ return { valid: false, errors: ["parameters must be an object"] };
317
+ if (!isRecord(values))
318
+ return { valid: false, errors: ["parameter values must be an object"] };
319
+ for (const [id, value] of Object.entries(values)) {
320
+ const definition = definitions[id];
321
+ if (!isRecord(definition)) {
322
+ errors.push(`Unknown parameter: ${id}`);
323
+ continue;
324
+ }
325
+ const error = parameterError(id, definition, value);
326
+ if (error)
327
+ errors.push(error);
328
+ }
329
+ return { valid: errors.length === 0, errors };
330
+ }
331
+ function validateCompleteProceduralParameters(definitions, values) {
332
+ const validation = validateProceduralParameters(definitions, values);
333
+ const errors = [...validation.errors];
334
+ if (!isRecord(definitions) || !isRecord(values))
335
+ return validation;
336
+ for (const [id, definition] of Object.entries(definitions)) {
337
+ if (isRecord(definition) && definition.type === "asset" && definition.required === true && !(id in values)) {
338
+ errors.push(`${id} must be an exact asset revision ref`);
339
+ }
340
+ }
341
+ return { valid: errors.length === 0, errors };
342
+ }
343
+ function assetSpecFamily(reference) {
344
+ const versionIndex = reference.indexOf("@");
345
+ return versionIndex >= 0 ? reference.slice(0, versionIndex) : reference;
346
+ }
347
+ function validateResolvedAssetParameter(definition, asset) {
348
+ const errors = [];
349
+ const accepts = definition.accepts;
350
+ if (!EXACT_ASSET_REF_PATTERN.test(asset.reference))
351
+ errors.push("Resolved asset reference must be exact");
352
+ if (accepts.category && asset.category !== accepts.category)
353
+ errors.push(`Resolved asset category must be ${accepts.category}`);
354
+ if (accepts.subcategory && asset.subcategory !== accepts.subcategory) {
355
+ errors.push(`Resolved asset subcategory must be ${accepts.subcategory}`);
356
+ }
357
+ if (accepts.formats && !accepts.formats.includes(asset.format)) {
358
+ errors.push(`Resolved asset format must be one of ${accepts.formats.join(", ")}`);
359
+ }
360
+ if (accepts.assetSpec && (!asset.assetSpec || assetSpecFamily(asset.assetSpec) !== accepts.assetSpec)) {
361
+ errors.push(`Resolved asset must use ${accepts.assetSpec}`);
362
+ }
363
+ if (!nonEmptyString(asset.primary?.url))
364
+ errors.push("Resolved asset primary URL is required");
365
+ return { valid: errors.length === 0, errors };
366
+ }
367
+ function requireResolvedAssetParameter(context, id, definition) {
368
+ const reference = context.parameters?.[id];
369
+ const valueError = parameterError(id, definition, reference);
370
+ if (valueError)
371
+ throw new Error(valueError);
372
+ if (typeof reference !== "string")
373
+ throw new Error(`${id} must be selected`);
374
+ const asset = context.resolveAsset?.(reference) ?? null;
375
+ if (!asset)
376
+ throw new Error(`Asset parameter ${id} was not resolved: ${reference}`);
377
+ if (asset.reference !== reference)
378
+ throw new Error(`Resolved asset reference mismatch for ${id}`);
379
+ const validation = validateResolvedAssetParameter(definition, asset);
380
+ if (!validation.valid)
381
+ throw new Error(`Asset parameter ${id} is incompatible: ${validation.errors.join("; ")}`);
382
+ return asset;
383
+ }
384
+ function validateKnownKeys(value, allowed, label, errors) {
385
+ for (const key of Object.keys(value)) {
386
+ if (!allowed.has(key))
387
+ errors.push(`${label}.${key} is unsupported`);
388
+ }
389
+ }
390
+ function validateProceduralPresentation(value, parameters, animations, errors) {
391
+ if (value === void 0)
392
+ return;
393
+ if (!isRecord(value)) {
394
+ errors.push("presentation must be an object");
395
+ return;
396
+ }
397
+ validateKnownKeys(value, /* @__PURE__ */ new Set(["parameters", "animation", "scene", "camera"]), "presentation", errors);
398
+ if (Object.keys(value).length === 0)
399
+ errors.push("presentation must not be empty");
400
+ if (value.parameters !== void 0) {
401
+ const result = validateProceduralParameters(parameters, value.parameters);
402
+ errors.push(...result.errors.map((error) => `presentation.parameters: ${error}`));
403
+ }
404
+ if (value.animation !== void 0) {
405
+ if (!isRecord(value.animation))
406
+ errors.push("presentation.animation must be an object");
407
+ else {
408
+ const animationRecipe = value.animation;
409
+ validateKnownKeys(animationRecipe, /* @__PURE__ */ new Set(["id", "time"]), "presentation.animation", errors);
410
+ const animation = animations.find((candidate) => candidate.id === animationRecipe.id);
411
+ if (!animation)
412
+ errors.push("presentation.animation.id must match a declared animation");
413
+ if (typeof animationRecipe.time !== "number" || !Number.isFinite(animationRecipe.time) || animationRecipe.time < 0) {
414
+ errors.push("presentation.animation.time must be a finite number at or above zero");
415
+ } else if (animation && animationRecipe.time > animation.duration) {
416
+ errors.push("presentation.animation.time must not exceed the animation duration");
417
+ }
418
+ }
419
+ }
420
+ if (value.scene !== void 0 && (typeof value.scene !== "string" || !PRESENTATION_SCENE_PRESETS.has(value.scene))) {
421
+ errors.push("presentation.scene is unsupported");
422
+ }
423
+ if (value.camera !== void 0) {
424
+ if (!isRecord(value.camera))
425
+ errors.push("presentation.camera must be an object");
426
+ else {
427
+ validateKnownKeys(value.camera, /* @__PURE__ */ new Set(["view", "azimuth", "elevation", "padding"]), "presentation.camera", errors);
428
+ const view = value.camera.view;
429
+ if (view !== void 0 && (typeof view !== "string" || !PRESENTATION_CAMERA_VIEWS.has(view))) {
430
+ errors.push("presentation.camera.view is unsupported");
431
+ }
432
+ if (value.camera.azimuth !== void 0 && (typeof value.camera.azimuth !== "number" || !Number.isFinite(value.camera.azimuth) || value.camera.azimuth < -180 || value.camera.azimuth > 180)) {
433
+ errors.push("presentation.camera.azimuth must be between -180 and 180 degrees");
434
+ }
435
+ if (value.camera.elevation !== void 0 && (typeof value.camera.elevation !== "number" || !Number.isFinite(value.camera.elevation) || value.camera.elevation < -80 || value.camera.elevation > 80)) {
436
+ errors.push("presentation.camera.elevation must be between -80 and 80 degrees");
437
+ }
438
+ if (value.camera.padding !== void 0 && (typeof value.camera.padding !== "number" || !Number.isFinite(value.camera.padding) || value.camera.padding < 1 || value.camera.padding > 3)) {
439
+ errors.push("presentation.camera.padding must be between 1 and 3");
440
+ }
441
+ if (view !== void 0 && view !== "perspective" && (value.camera.azimuth !== void 0 || value.camera.elevation !== void 0)) {
442
+ errors.push("presentation.camera angles require the perspective view");
443
+ }
444
+ }
445
+ }
446
+ }
447
+ function validateProceduralManifest(value) {
448
+ const errors = [];
449
+ if (!isRecord(value))
450
+ return { valid: false, errors: ["Manifest must be an object"] };
451
+ const manifest = value;
452
+ if (manifest.manifestProtocol !== PLAYDROP_PROCEDURAL_MANIFEST_PROTOCOL)
453
+ errors.push("Unsupported manifest protocol");
454
+ if (manifest.protocol !== PLAYDROP_PROCEDURAL_PROTOCOL)
455
+ errors.push("Unsupported procedural protocol");
456
+ if (typeof manifest.id !== "string" || !SLUG_PATTERN.test(manifest.id))
457
+ errors.push("Asset id must be a slug");
458
+ for (const field of ["title", "version", "summary"]) {
459
+ if (!nonEmptyString(manifest[field]))
460
+ errors.push(`${field} must be a non-empty string`);
461
+ }
462
+ if (!["model", "vfx", "character"].includes(manifest.kind))
463
+ errors.push("kind must be model, vfx, or character");
464
+ validateUniqueStrings(manifest.tags, "tags", errors);
465
+ if (!isRecord(manifest.pack))
466
+ errors.push("pack must be an object");
467
+ else {
468
+ if (!nonEmptyString(manifest.pack.id) || !SLUG_PATTERN.test(manifest.pack.id))
469
+ errors.push("pack.id must be a slug");
470
+ if (!nonEmptyString(manifest.pack.title))
471
+ errors.push("pack.title must be a non-empty string");
472
+ if (!nonEmptyString(manifest.pack.sourceProject))
473
+ errors.push("pack.sourceProject must be a non-empty string");
474
+ }
475
+ if (typeof manifest.sourceEntry !== "string" || !SAFE_RELATIVE_PATH_PATTERN.test(manifest.sourceEntry) || !manifest.sourceEntry.endsWith(".ts"))
476
+ errors.push("Source entry must be a safe relative TypeScript path");
477
+ if (typeof manifest.distFile !== "string" || !SAFE_RELATIVE_PATH_PATTERN.test(manifest.distFile) || !manifest.distFile.endsWith(".mjs"))
478
+ errors.push("Dist artifact must be a safe relative ES module path");
479
+ if (!isRecord(manifest.requirements) || manifest.requirements.renderer !== "webgl")
480
+ errors.push("Procedural v1 requires WebGL");
481
+ if (!isRecord(manifest.requirements) || manifest.requirements.three !== "0.183.2")
482
+ errors.push("Procedural v1 requires Three.js 0.183.2");
483
+ if (manifest.platformAssetRef !== null && (typeof manifest.platformAssetRef !== "string" || !EXACT_ASSET_REF_PATTERN.test(manifest.platformAssetRef))) {
484
+ errors.push("platformAssetRef must be null or an exact asset revision ref");
485
+ }
486
+ const parameters = validateParameterDefinitions(manifest.parameters, errors);
487
+ const defaults = Object.fromEntries(Object.entries(parameters).filter(([, definition]) => definition.type !== "asset" || definition.default !== null).map(([id, definition]) => [id, definition.default]));
488
+ errors.push(...validateProceduralParameters(parameters, defaults).errors);
489
+ if ("actions" in manifest) {
490
+ errors.push("actions are unsupported; use non-looping animations");
491
+ }
492
+ if ("states" in manifest) {
493
+ errors.push("states are unsupported; declare a select parameter");
494
+ }
495
+ const animations = validateDefinitions(manifest.animations, "animations", errors, (candidate, id) => {
496
+ if (!nonEmptyString(candidate.label))
497
+ errors.push(`Animation ${id} must have a label`);
498
+ if (!finitePositive(candidate.duration))
499
+ errors.push(`Animation ${id} duration must be greater than zero`);
500
+ if (typeof candidate.loop !== "boolean")
501
+ errors.push(`Animation ${id} loop must be a boolean`);
502
+ });
503
+ validateProceduralPresentation(manifest.presentation, parameters, animations, errors);
504
+ validateUniqueStrings(manifest.parts, "parts", errors);
505
+ validateUniqueStrings(manifest.sockets, "sockets", errors);
506
+ if (!isRecord(manifest.capabilities))
507
+ errors.push("capabilities must be an object");
508
+ else {
509
+ for (const key of ["controls", "selection", "timeline", "character"]) {
510
+ if (typeof manifest.capabilities[key] !== "boolean")
511
+ errors.push(`capabilities.${key} must be a boolean`);
512
+ }
513
+ }
514
+ if ("configuration" in manifest)
515
+ errors.push("configuration is unsupported; declare typed parameters instead");
516
+ if (manifest.character !== void 0) {
517
+ if (!isRecord(manifest.character))
518
+ errors.push("character must be an object");
519
+ else {
520
+ if (!nonEmptyString(manifest.character.rig))
521
+ errors.push("character.rig must be a non-empty string");
522
+ if (!nonEmptyString(manifest.character.skinProtocol))
523
+ errors.push("character.skinProtocol must be a non-empty string");
524
+ const templates = validateUniqueStrings(manifest.character.supportedTemplates, "character.supportedTemplates", errors);
525
+ if (templates.length === 0)
526
+ errors.push("character.supportedTemplates must not be empty");
527
+ if (!finitePositive(manifest.character.unitsPerMeter))
528
+ errors.push("character.unitsPerMeter must be greater than zero");
529
+ if (!Number.isInteger(manifest.character.boneCount) || Number(manifest.character.boneCount) <= 0) {
530
+ errors.push("character.boneCount must be a positive integer");
531
+ }
532
+ }
533
+ }
534
+ if (manifest.kind === "character" && !manifest.character)
535
+ errors.push("Character manifests must declare character metadata");
536
+ return { valid: errors.length === 0, errors };
537
+ }
538
+ function validateFiniteObjectHierarchy(object, errors, label = "object") {
539
+ object.updateMatrixWorld(true);
540
+ object.traverse((node) => {
541
+ const nodeLabel = node.name ? `${label}.${node.name}` : `${label}.${node.type}`;
542
+ const transformValues = [
543
+ node.position.x,
544
+ node.position.y,
545
+ node.position.z,
546
+ node.quaternion.x,
547
+ node.quaternion.y,
548
+ node.quaternion.z,
549
+ node.quaternion.w,
550
+ node.scale.x,
551
+ node.scale.y,
552
+ node.scale.z
553
+ ];
554
+ if (transformValues.some((entry) => !Number.isFinite(entry))) {
555
+ errors.push(`${nodeLabel} has a non-finite transform`);
556
+ }
557
+ const geometry = node.geometry;
558
+ if (!geometry?.attributes)
559
+ return;
560
+ for (const [attributeId, attribute] of Object.entries(geometry.attributes)) {
561
+ if (!attribute?.array)
562
+ continue;
563
+ for (let index = 0; index < attribute.array.length; index += 1) {
564
+ if (!Number.isFinite(attribute.array[index])) {
565
+ errors.push(`${nodeLabel} geometry.${attributeId} contains a non-finite value`);
566
+ break;
567
+ }
568
+ }
569
+ }
570
+ if (geometry.index?.array) {
571
+ for (let index = 0; index < geometry.index.array.length; index += 1) {
572
+ if (!Number.isFinite(geometry.index.array[index])) {
573
+ errors.push(`${nodeLabel} geometry.index contains a non-finite value`);
574
+ break;
575
+ }
576
+ }
577
+ }
578
+ });
579
+ }
580
+ function isDescendantOf(entry, root) {
581
+ let current = entry;
582
+ while (current) {
583
+ if (current === root)
584
+ return true;
585
+ current = current.parent;
586
+ }
587
+ return false;
588
+ }
589
+ function parameterValidationSamples(id, definition, context) {
590
+ if (definition.type === "number")
591
+ return [definition.default, definition.min, definition.max];
592
+ if (definition.type === "boolean")
593
+ return [definition.default, !definition.default];
594
+ if (definition.type === "color")
595
+ return [definition.default, "#000000", "#ffffff"];
596
+ if (definition.type === "select")
597
+ return definition.options.map(selectOptionValue);
598
+ if (definition.default !== null)
599
+ return [definition.default];
600
+ const supplied = context.parameters?.[id];
601
+ return supplied ? [supplied] : [];
602
+ }
603
+ function validateRuntimeControlValues(definitions, values) {
604
+ const validation = validateCompleteProceduralParameters(definitions, values);
605
+ const errors = [...validation.errors];
606
+ if (!isRecord(values))
607
+ return validation;
608
+ for (const id of Object.keys(definitions)) {
609
+ if (!(id in values))
610
+ errors.push(`controls.values() is missing ${id}`);
611
+ }
612
+ return { valid: errors.length === 0, errors };
613
+ }
614
+ function sameDeclaration(left, right) {
615
+ if (left === right)
616
+ return true;
617
+ if (Array.isArray(left) && Array.isArray(right)) {
618
+ return left.length === right.length && left.every((item, index) => sameDeclaration(item, right[index]));
619
+ }
620
+ if (!isRecord(left) || !isRecord(right))
621
+ return false;
622
+ const keys = Object.keys(left);
623
+ return keys.length === Object.keys(right).length && keys.every((key) => key in right && sameDeclaration(left[key], right[key]));
624
+ }
625
+ function validateProceduralModule(value, context = {}, manifest) {
626
+ const errors = [];
627
+ if (!isRecord(value) || !isRecord(value.info) || typeof value.create !== "function") {
628
+ return { valid: false, errors: ["Module must expose info and create()"] };
629
+ }
630
+ const module2 = value;
631
+ if (module2.info.protocol !== PLAYDROP_PROCEDURAL_PROTOCOL)
632
+ errors.push("Unsupported procedural protocol");
633
+ if (!SLUG_PATTERN.test(module2.info.id))
634
+ errors.push("Asset id must be a slug");
635
+ if (!isRecord(module2.info.requirements) || module2.info.requirements.renderer !== "webgl")
636
+ errors.push("Procedural v1 requires WebGL");
637
+ if (!isRecord(module2.info.requirements) || module2.info.requirements.three !== "0.183.2")
638
+ errors.push("Procedural v1 requires Three.js 0.183.2");
639
+ if (manifest) {
640
+ for (const field of ["protocol", "id", "title", "version", "kind", "summary", "tags", "pack", "requirements", "presentation", "character"]) {
641
+ if (!sameDeclaration(module2.info[field], manifest[field]))
642
+ errors.push(`manifest.${field} does not match module.info.${field}`);
643
+ }
644
+ }
645
+ let instance;
646
+ try {
647
+ instance = module2.create(context);
648
+ if (manifest) {
649
+ const capabilities = instance.capabilities;
650
+ for (const name of ["controls", "selection", "timeline", "character"]) {
651
+ if (Boolean(capabilities?.[name]) !== manifest.capabilities[name])
652
+ errors.push(`manifest.capabilities.${name} does not match create().capabilities.${name}`);
653
+ }
654
+ if (!sameDeclaration(capabilities?.controls?.parameters ?? {}, manifest.parameters))
655
+ errors.push("manifest.parameters does not match controls.parameters");
656
+ if (!sameDeclaration(capabilities?.timeline?.animations ?? [], manifest.animations))
657
+ errors.push("manifest.animations does not match timeline.animations");
658
+ for (const name of ["parts", "sockets"]) {
659
+ const entries = capabilities?.selection?.[name] ?? {};
660
+ for (const id of manifest[name]) {
661
+ if (!(id in entries))
662
+ errors.push(`manifest.${name}.${id} is missing from selection.${name}`);
663
+ }
664
+ }
665
+ }
666
+ if (!instance.object?.isObject3D)
667
+ errors.push("create() must expose a THREE.Object3D");
668
+ else
669
+ validateFiniteObjectHierarchy(instance.object, errors);
670
+ const controls = instance.capabilities?.controls;
671
+ if (controls) {
672
+ const unsafeControls = controls;
673
+ if ("actions" in unsafeControls || "trigger" in unsafeControls) {
674
+ errors.push("actions are unsupported; use non-looping animations");
675
+ }
676
+ if ("states" in unsafeControls || "stateValues" in unsafeControls || "setState" in unsafeControls) {
677
+ errors.push("states are unsupported; declare a select parameter");
678
+ }
679
+ const definitions = validateParameterDefinitions(controls.parameters, errors);
680
+ const currentValues = controls.values();
681
+ errors.push(...validateRuntimeControlValues(definitions, currentValues).errors);
682
+ for (const [id, definition] of Object.entries(definitions)) {
683
+ for (const sample of parameterValidationSamples(id, definition, context)) {
684
+ try {
685
+ instance.reset?.();
686
+ controls.configure({ [id]: sample });
687
+ const configuredValues = controls.values();
688
+ errors.push(...validateRuntimeControlValues(definitions, configuredValues).errors);
689
+ if (configuredValues[id] !== sample)
690
+ errors.push(`configure(${id}) did not apply the requested value`);
691
+ validateFiniteObjectHierarchy(instance.object, errors, `configured.${id}`);
692
+ } catch (error) {
693
+ errors.push(`configure(${id}) failed: ${error instanceof Error ? error.message : String(error)}`);
694
+ }
695
+ }
696
+ }
697
+ }
698
+ const selection = instance.capabilities?.selection;
699
+ if (selection) {
700
+ for (const [kind, entries] of [["parts", selection.parts], ["sockets", selection.sockets]]) {
701
+ for (const [id, entry] of Object.entries(entries)) {
702
+ if (!CAPABILITY_ID_PATTERN.test(id))
703
+ errors.push(`${kind} id is invalid: ${id}`);
704
+ if (!entry?.isObject3D)
705
+ errors.push(`${kind}.${id} must be a THREE.Object3D`);
706
+ else if (!isDescendantOf(entry, instance.object))
707
+ errors.push(`${kind}.${id} must belong to the asset object`);
708
+ }
709
+ }
710
+ }
711
+ const timeline = instance.capabilities?.timeline;
712
+ if (timeline) {
713
+ validateDefinitions(timeline.animations, "animations", errors, (candidate, id) => {
714
+ if (!nonEmptyString(candidate.label))
715
+ errors.push(`Animation ${id} must have a label`);
716
+ if (!finitePositive(candidate.duration))
717
+ errors.push(`Animation ${id} duration must be greater than zero`);
718
+ if (typeof candidate.loop !== "boolean")
719
+ errors.push(`Animation ${id} loop must be a boolean`);
720
+ });
721
+ for (const animation of timeline.animations) {
722
+ try {
723
+ timeline.play(animation.id);
724
+ for (const time of [0, animation.duration / 2, animation.duration]) {
725
+ timeline.seek(time);
726
+ validateFiniteObjectHierarchy(instance.object, errors, `animation.${animation.id}`);
727
+ }
728
+ timeline.pause();
729
+ } catch (error) {
730
+ errors.push(`Animation ${animation.id} failed: ${error instanceof Error ? error.message : String(error)}`);
731
+ }
732
+ }
733
+ }
734
+ instance.reset?.();
735
+ instance.update?.(1 / 60, 1 / 60);
736
+ if (instance.object?.isObject3D)
737
+ validateFiniteObjectHierarchy(instance.object, errors, "updated");
738
+ } catch (error) {
739
+ errors.push(error instanceof Error ? error.message : String(error));
740
+ } finally {
741
+ for (let attempt = 0; attempt < 2; attempt += 1) {
742
+ try {
743
+ instance?.dispose();
744
+ } catch (error) {
745
+ errors.push(`dispose() failed on call ${attempt + 1}: ${error instanceof Error ? error.message : String(error)}`);
746
+ }
747
+ }
748
+ }
749
+ return { valid: errors.length === 0, errors };
750
+ }
751
+ // Annotate the CommonJS export names for ESM import in node:
752
+ 0 && (module.exports = {
753
+ PLAYDROP_PROCEDURAL_MANIFEST_PROTOCOL,
754
+ PLAYDROP_PROCEDURAL_PACK_PROTOCOL,
755
+ PLAYDROP_PROCEDURAL_PROTOCOL,
756
+ PROCEDURAL_PARAMETER_ICON_IDS,
757
+ requireResolvedAssetParameter,
758
+ validateCompleteProceduralParameters,
759
+ validateProceduralManifest,
760
+ validateProceduralModule,
761
+ validateProceduralParameters,
762
+ validateResolvedAssetParameter
763
+ });