@bardway/trpg-pixi 0.0.0-stage → 0.1.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.
Files changed (111) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE +10 -0
  3. package/NOTICE.md +1 -0
  4. package/README.md +25 -2
  5. package/dist/AnimationMilestones.d.ts +17 -0
  6. package/dist/BattleActionMotionMath.d.ts +24 -0
  7. package/dist/BattleActionPresentationRecipes.d.ts +11 -0
  8. package/dist/BattleFxLibrary.d.ts +16 -0
  9. package/dist/BattlePresentationEngine.d.ts +26 -0
  10. package/dist/BattlePresentationPoseRegistry.d.ts +25 -0
  11. package/dist/BattlePresentationRecipe.d.ts +48 -0
  12. package/dist/BattleSequenceScheduler.d.ts +11 -0
  13. package/dist/DisplayEventQueue.d.ts +24 -0
  14. package/dist/PixiAnimationActivity.d.ts +7 -0
  15. package/dist/PixiAnimationDirector.d.ts +21 -0
  16. package/dist/PixiApplicationFactory.d.ts +17 -0
  17. package/dist/PixiAssetManager.d.ts +70 -0
  18. package/dist/PixiBattleActionRecipes.d.ts +75 -0
  19. package/dist/PixiBattleCinematicSurface.d.ts +23 -0
  20. package/dist/PixiBattleEntryTransition.d.ts +87 -0
  21. package/dist/PixiBattleFieldSurface.d.ts +125 -0
  22. package/dist/PixiBattleFxResources.d.ts +154 -0
  23. package/dist/PixiBattleGrid.d.ts +13 -0
  24. package/dist/PixiBattleHudPorts.d.ts +192 -0
  25. package/dist/PixiBattleHudSurface.d.ts +159 -0
  26. package/dist/PixiBattleLightningResources.d.ts +57 -0
  27. package/dist/PixiBattleMapDissolve.d.ts +38 -0
  28. package/dist/PixiBattleSceneTransition.d.ts +30 -0
  29. package/dist/PixiBattleTokenLifecycleCoordinator.d.ts +44 -0
  30. package/dist/PixiBattleVisualLayout.d.ts +284 -0
  31. package/dist/PixiBattleWhipGeometry.d.ts +18 -0
  32. package/dist/PixiBattleWhipMotion.d.ts +63 -0
  33. package/dist/PixiBattleWhipResources.d.ts +53 -0
  34. package/dist/PixiCameraFocusController.d.ts +40 -0
  35. package/dist/PixiCombatMotion.d.ts +223 -0
  36. package/dist/PixiCurrentActorCardGlow.d.ts +37 -0
  37. package/dist/PixiCurrentActorHalo.d.ts +55 -0
  38. package/dist/PixiDirectionalMotionBlur.d.ts +12 -0
  39. package/dist/PixiDisplayRenderer.d.ts +85 -0
  40. package/dist/PixiEffectPlugin.d.ts +25 -0
  41. package/dist/PixiEndingSurface.d.ts +104 -0
  42. package/dist/PixiInvestigatorDossierInsanityFilter.d.ts +14 -0
  43. package/dist/PixiLocalGlowMesh.d.ts +38 -0
  44. package/dist/PixiMapEdgeShadow.d.ts +36 -0
  45. package/dist/PixiMapMediaSurface.d.ts +71 -0
  46. package/dist/PixiMapWorldUtils.d.ts +24 -0
  47. package/dist/PixiMediaBreathRuntime.d.ts +69 -0
  48. package/dist/PixiMotion.d.ts +42 -0
  49. package/dist/PixiObjectiveBreathingGlow.d.ts +39 -0
  50. package/dist/PixiPartyHudSurface.d.ts +58 -0
  51. package/dist/PixiPersistentEffectTransition.d.ts +23 -0
  52. package/dist/PixiPortraitResourceCard.d.ts +147 -0
  53. package/dist/PixiPreludeStoryTransition.d.ts +35 -0
  54. package/dist/PixiPreludeSurface.d.ts +203 -0
  55. package/dist/PixiProgressCinematic.d.ts +65 -0
  56. package/dist/PixiResultEffectSurface.d.ts +101 -0
  57. package/dist/PixiRetainedMeterAnimator.d.ts +44 -0
  58. package/dist/PixiRuntimeContext.d.ts +21 -0
  59. package/dist/PixiSceneGraph.d.ts +42 -0
  60. package/dist/PixiSceneMotion.d.ts +41 -0
  61. package/dist/PixiSceneMotionPorts.d.ts +83 -0
  62. package/dist/PixiScenePresentationDirector.d.ts +118 -0
  63. package/dist/PixiSessionLayout.d.ts +61 -0
  64. package/dist/PixiSessionSkin.d.ts +30 -0
  65. package/dist/PixiShaderRuntime.d.ts +191 -0
  66. package/dist/PixiStageFontLoader.d.ts +2 -0
  67. package/dist/PixiStageMetrics.d.ts +25 -0
  68. package/dist/PixiStageTheme.d.ts +26 -0
  69. package/dist/PixiStoryArchiveMotion.d.ts +19 -0
  70. package/dist/PixiStoryCardCut.d.ts +58 -0
  71. package/dist/PixiStorySurface.d.ts +285 -0
  72. package/dist/PixiStoryWorkspaceSurface.d.ts +64 -0
  73. package/dist/PixiSummaryBoardSurface.d.ts +112 -0
  74. package/dist/PixiSystemHudSurface.d.ts +73 -0
  75. package/dist/PixiTeamCheckCinematic.d.ts +34 -0
  76. package/dist/PixiTextTransitionEffect.d.ts +54 -0
  77. package/dist/PixiTokenPersistentStatus.d.ts +101 -0
  78. package/dist/PixiTokenWorld.d.ts +170 -0
  79. package/dist/PixiTypography.d.ts +96 -0
  80. package/dist/PixiVideoRuntime.d.ts +61 -0
  81. package/dist/PixiViewportController.d.ts +25 -0
  82. package/dist/ResultEffectTuning.d.ts +57 -0
  83. package/dist/RetainedStageSurface.d.ts +50 -0
  84. package/dist/SessionFieldEffectRuntime.d.ts +45 -0
  85. package/dist/SessionSceneHistory.d.ts +10 -0
  86. package/dist/SessionSceneRegistry.d.ts +31 -0
  87. package/dist/StoryEventCoordinator.d.ts +61 -0
  88. package/dist/effects/PixiEffectLayerRegistry.d.ts +54 -0
  89. package/dist/effects/PixiEffectScope.d.ts +20 -0
  90. package/dist/effects/PixiStableRandom.d.ts +2 -0
  91. package/dist/index.d.ts +92 -0
  92. package/dist/index.js +24184 -0
  93. package/dist/index.js.map +7 -0
  94. package/dist/recipes/EffectPrimitiveRuntimeRegistry.d.ts +28 -0
  95. package/dist/recipes/EffectRecipeExecutor.d.ts +73 -0
  96. package/dist/recipes/primitives.d.ts +39 -0
  97. package/dist/recipes/registry.d.ts +57 -0
  98. package/dist/recipes/schema.d.ts +93 -0
  99. package/dist/recipes.d.ts +5 -0
  100. package/dist/recipes.js +1384 -0
  101. package/dist/recipes.js.map +7 -0
  102. package/dist/story/PixiStoryPresentationEngine.d.ts +90 -0
  103. package/dist/story/StoryContentSurface.d.ts +34 -0
  104. package/dist/story/StoryImageFilter.d.ts +15 -0
  105. package/dist/story/StoryTimelineClock.d.ts +22 -0
  106. package/dist/story/StoryVisualMath.d.ts +6 -0
  107. package/dist/tuning/SessionTuningResolver.d.ts +1 -0
  108. package/dist/tuning/SessionTuningTypes.d.ts +1 -0
  109. package/dist/ui/PixiStagePrimitives.d.ts +36 -0
  110. package/dist/uniforms.d.ts +1 -0
  111. package/package.json +44 -4
@@ -0,0 +1,1384 @@
1
+ // src/recipes/schema.ts
2
+ var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3
+ var EFFECT_RECIPE_SCHEMA_VERSION = 4;
4
+ var EFFECT_RECIPE_SCOPES = ["story", "battle"];
5
+ var EFFECT_RECIPE_CATEGORIES = ["entrance", "utility"];
6
+ var EFFECT_RECIPE_ID_MAX_LENGTH = 80;
7
+ var EFFECT_RECIPE_TRACK_ID_MAX_LENGTH = 80;
8
+ var EFFECT_RECIPE_PRIMITIVE_ID_MAX_LENGTH = 80;
9
+ var EFFECT_RECIPE_NAME_MAX_CODE_POINTS = 80;
10
+ var EFFECT_RECIPE_DESCRIPTION_MAX_CODE_POINTS = 240;
11
+ var EFFECT_RECIPE_APP_LABEL_MAX_CODE_POINTS = 48;
12
+ var EFFECT_RECIPE_APP_SHORT_DESCRIPTION_MAX_CODE_POINTS = 120;
13
+ var EFFECT_RECIPE_APP_ICON_ID_MAX_LENGTH = 80;
14
+ var EFFECT_RECIPE_MAX_DURATION_MS = 2e4;
15
+ var EFFECT_RECIPE_MAX_TRACKS = 24;
16
+ var EFFECT_RECIPE_VALUE_MAX_STRING_CODE_POINTS = 240;
17
+ var EFFECT_RECIPE_VALUE_MAX_ARRAY_LENGTH = 8;
18
+ var EFFECT_RECIPE_TUNING_PATH_MAX_LENGTH = 160;
19
+ var EFFECT_RECIPE_APP_ORDER_MAX = 9999;
20
+ var EFFECT_RECIPE_STABLE_ID_REGEX = /^[A-Za-z0-9][A-Za-z0-9._:/-]*$/;
21
+ var EFFECT_RECIPE_TUNING_PATH_REGEX = /^[A-Za-z][A-Za-z0-9]*(\.[A-Za-z0-9_-]+)+$/;
22
+ var hasOwn = (value, key) => Object.prototype.hasOwnProperty.call(value, key);
23
+ var codePointLength = (value) => Array.from(value).length;
24
+ var normalizeText = (value, maxCodePoints, allowEmpty = false) => {
25
+ if (typeof value !== "string") {
26
+ return null;
27
+ }
28
+ const normalized = value.trim();
29
+ if (!allowEmpty && !normalized || codePointLength(normalized) > maxCodePoints) {
30
+ return null;
31
+ }
32
+ return normalized;
33
+ };
34
+ var normalizeNonNegativeInteger = (value, maxValue) => {
35
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0 || value > maxValue) {
36
+ return null;
37
+ }
38
+ return value;
39
+ };
40
+ var createError = (code, path, message) => ({ code, path, message });
41
+ var EFFECT_TARGETS = ["main-content", "story-workspace", "full-stage"];
42
+ var v4Migration = {
43
+ schemaVersion: 4,
44
+ migrate: (definition) => definition
45
+ };
46
+ var EFFECT_RECIPE_MIGRATIONS = [v4Migration];
47
+ var effectRecipeMigrationMap = new Map(
48
+ EFFECT_RECIPE_MIGRATIONS.map((migration) => [migration.schemaVersion, migration])
49
+ );
50
+ var effectRecipeMigrationRegistry = Object.freeze({
51
+ all: () => EFFECT_RECIPE_MIGRATIONS,
52
+ get: (schemaVersion) => effectRecipeMigrationMap.get(schemaVersion) ?? null
53
+ });
54
+ var isEffectRecipeScope = (value) => value === "story" || value === "battle";
55
+ var isEffectRecipeCategory = (value) => value === "entrance" || value === "utility";
56
+ var isEffectRecipeId = (value) => {
57
+ const normalized = normalizeText(value, EFFECT_RECIPE_ID_MAX_LENGTH);
58
+ return normalized !== null && EFFECT_RECIPE_STABLE_ID_REGEX.test(normalized);
59
+ };
60
+ var isEffectRecipeTrackId = (value) => {
61
+ const normalized = normalizeText(value, EFFECT_RECIPE_TRACK_ID_MAX_LENGTH);
62
+ return normalized !== null && EFFECT_RECIPE_STABLE_ID_REGEX.test(normalized);
63
+ };
64
+ var isEffectPrimitiveId = (value) => {
65
+ const normalized = normalizeText(value, EFFECT_RECIPE_PRIMITIVE_ID_MAX_LENGTH);
66
+ return normalized !== null && EFFECT_RECIPE_STABLE_ID_REGEX.test(normalized);
67
+ };
68
+ var isEffectRecipeTuningPath = (value) => {
69
+ const normalized = normalizeText(value, EFFECT_RECIPE_TUNING_PATH_MAX_LENGTH);
70
+ return normalized !== null && EFFECT_RECIPE_TUNING_PATH_REGEX.test(normalized);
71
+ };
72
+ var isEffectRecipeTuningPathReference = (value) => isRecord(value) && Object.keys(value).length === 1 && hasOwn(value, "tuningPath") && isEffectRecipeTuningPath(value.tuningPath);
73
+ var isResolvedRecipeValue = (value) => {
74
+ if (typeof value === "string" || typeof value === "boolean") {
75
+ return true;
76
+ }
77
+ if (typeof value === "number") {
78
+ return Number.isFinite(value);
79
+ }
80
+ return Array.isArray(value) && value.length <= EFFECT_RECIPE_VALUE_MAX_ARRAY_LENGTH && value.every((entry) => typeof entry === "number" && Number.isFinite(entry));
81
+ };
82
+ var parseRecipeValue = (value, path, errors) => {
83
+ if (typeof value === "string") {
84
+ const normalized = normalizeText(value, EFFECT_RECIPE_VALUE_MAX_STRING_CODE_POINTS);
85
+ if (normalized === null) {
86
+ errors.push(
87
+ createError("invalid_length", path, "\u5B57\u7B26\u4E32\u53C2\u6570\u4E0D\u80FD\u4E3A\u7A7A\uFF0C\u4E14\u957F\u5EA6\u5FC5\u987B\u5728\u5141\u8BB8\u8303\u56F4\u5185\u3002")
88
+ );
89
+ return null;
90
+ }
91
+ return normalized;
92
+ }
93
+ if (typeof value === "number") {
94
+ if (!Number.isFinite(value)) {
95
+ errors.push(createError("invalid_recipe_value", path, "\u6570\u503C\u53C2\u6570\u5FC5\u987B\u662F\u6709\u9650\u6570\u5B57\u3002"));
96
+ return null;
97
+ }
98
+ return value;
99
+ }
100
+ if (typeof value === "boolean") {
101
+ return value;
102
+ }
103
+ if (Array.isArray(value)) {
104
+ if (value.length > EFFECT_RECIPE_VALUE_MAX_ARRAY_LENGTH) {
105
+ errors.push(
106
+ createError(
107
+ "too_many_items",
108
+ path,
109
+ `\u6570\u5B57\u6570\u7EC4\u957F\u5EA6\u4E0D\u80FD\u8D85\u8FC7 ${EFFECT_RECIPE_VALUE_MAX_ARRAY_LENGTH}\u3002`
110
+ )
111
+ );
112
+ return null;
113
+ }
114
+ if (!value.every((entry) => typeof entry === "number" && Number.isFinite(entry))) {
115
+ errors.push(createError("invalid_recipe_value", path, "\u6570\u5B57\u6570\u7EC4\u53EA\u80FD\u5305\u542B\u6709\u9650\u6570\u5B57\u3002"));
116
+ return null;
117
+ }
118
+ return [...value];
119
+ }
120
+ if (isRecord(value)) {
121
+ if (!hasOwn(value, "tuningPath") || Object.keys(value).length !== 1) {
122
+ errors.push(
123
+ createError("invalid_recipe_value", path, "\u5BF9\u8C61\u53C2\u6570\u53EA\u5141\u8BB8\u4F7F\u7528 tuningPath \u5F15\u7528\u3002")
124
+ );
125
+ return null;
126
+ }
127
+ if (!isEffectRecipeTuningPath(value.tuningPath)) {
128
+ errors.push(
129
+ createError("invalid_tuning_path", `${path}.tuningPath`, "tuningPath \u683C\u5F0F\u65E0\u6548\u3002")
130
+ );
131
+ return null;
132
+ }
133
+ return { tuningPath: value.tuningPath.trim() };
134
+ }
135
+ errors.push(
136
+ createError(
137
+ "invalid_recipe_value",
138
+ path,
139
+ "\u53C2\u6570\u503C\u53EA\u5141\u8BB8 string\u3001number\u3001boolean\u3001number[] \u6216 { tuningPath }\u3002"
140
+ )
141
+ );
142
+ return null;
143
+ };
144
+ var migrateEffectRecipeDefinition = (value) => {
145
+ if (!isRecord(value)) {
146
+ return {
147
+ document: null,
148
+ errors: [createError("invalid_type", "", "Recipe \u5B9A\u4E49\u5FC5\u987B\u662F\u5BF9\u8C61\u3002")]
149
+ };
150
+ }
151
+ const schemaVersion = value.schemaVersion;
152
+ if (typeof schemaVersion !== "number" || !Number.isSafeInteger(schemaVersion)) {
153
+ return {
154
+ document: null,
155
+ errors: [
156
+ createError("invalid_schema_version", "schemaVersion", "schemaVersion \u5FC5\u987B\u662F\u5B89\u5168\u6574\u6570\u3002")
157
+ ]
158
+ };
159
+ }
160
+ const migration = effectRecipeMigrationMap.get(schemaVersion);
161
+ if (!migration) {
162
+ return {
163
+ document: null,
164
+ errors: [
165
+ createError(
166
+ "unknown_schema_version",
167
+ "schemaVersion",
168
+ `\u672A\u6CE8\u518C\u7684 recipe schemaVersion: ${schemaVersion}\u3002`
169
+ )
170
+ ]
171
+ };
172
+ }
173
+ if (value.category === "sequence" || value.category === "horror")
174
+ return {
175
+ document: null,
176
+ errors: [createError("invalid_enum", "category", "\u8BE5\u7C7B Story \u6F14\u51FA\u5DF2\u9000\u5F79\u3002")]
177
+ };
178
+ const migrated = migration.migrate(value);
179
+ if (!isRecord(migrated)) {
180
+ return {
181
+ document: null,
182
+ errors: [createError("invalid_type", "", "\u8FC1\u79FB\u540E\u7684 Recipe \u5B9A\u4E49\u5FC5\u987B\u662F\u5BF9\u8C61\u3002")]
183
+ };
184
+ }
185
+ return { document: migrated, errors: [] };
186
+ };
187
+ var validateEffectRecipeDefinition = (value) => {
188
+ const migrationResult = migrateEffectRecipeDefinition(value);
189
+ if (!migrationResult.document) {
190
+ return { definition: null, errors: migrationResult.errors };
191
+ }
192
+ const document = migrationResult.document;
193
+ const errors = [];
194
+ const schemaVersion = document.schemaVersion;
195
+ if (schemaVersion !== EFFECT_RECIPE_SCHEMA_VERSION) {
196
+ errors.push(
197
+ createError(
198
+ "invalid_schema_version",
199
+ "schemaVersion",
200
+ `schemaVersion \u5FC5\u987B\u4E3A ${EFFECT_RECIPE_SCHEMA_VERSION}\u3002`
201
+ )
202
+ );
203
+ }
204
+ const id = normalizeText(document.id, EFFECT_RECIPE_ID_MAX_LENGTH);
205
+ if (!id || !EFFECT_RECIPE_STABLE_ID_REGEX.test(id)) {
206
+ errors.push(createError("invalid_id", "id", "Recipe id \u5FC5\u987B\u662F\u7A33\u5B9A\u6807\u8BC6\u7B26\u3002"));
207
+ }
208
+ const name = normalizeText(document.name, EFFECT_RECIPE_NAME_MAX_CODE_POINTS);
209
+ if (!name) {
210
+ errors.push(
211
+ createError("invalid_length", "name", "Recipe name \u4E0D\u80FD\u4E3A\u7A7A\uFF0C\u4E14\u957F\u5EA6\u5FC5\u987B\u5728\u5141\u8BB8\u8303\u56F4\u5185\u3002")
212
+ );
213
+ }
214
+ const description = normalizeText(
215
+ document.description,
216
+ EFFECT_RECIPE_DESCRIPTION_MAX_CODE_POINTS
217
+ );
218
+ if (!description) {
219
+ errors.push(
220
+ createError(
221
+ "invalid_length",
222
+ "description",
223
+ "Recipe description \u4E0D\u80FD\u4E3A\u7A7A\uFF0C\u4E14\u957F\u5EA6\u5FC5\u987B\u5728\u5141\u8BB8\u8303\u56F4\u5185\u3002"
224
+ )
225
+ );
226
+ }
227
+ const scope = document.scope;
228
+ if (!isEffectRecipeScope(scope)) {
229
+ errors.push(createError("invalid_enum", "scope", "scope \u5FC5\u987B\u662F story \u6216 battle\u3002"));
230
+ }
231
+ const category = document.category;
232
+ if (!isEffectRecipeCategory(category)) {
233
+ errors.push(createError("invalid_enum", "category", "category \u5FC5\u987B\u662F entrance \u6216 utility\u3002"));
234
+ }
235
+ const durationMs = normalizeNonNegativeInteger(
236
+ document.durationMs,
237
+ EFFECT_RECIPE_MAX_DURATION_MS
238
+ );
239
+ if (durationMs === null) {
240
+ errors.push(
241
+ createError(
242
+ "out_of_range",
243
+ "durationMs",
244
+ `durationMs \u5FC5\u987B\u662F 0 \u5230 ${EFFECT_RECIPE_MAX_DURATION_MS} \u7684\u5B89\u5168\u6574\u6570\u3002`
245
+ )
246
+ );
247
+ }
248
+ const commitAtMs = normalizeNonNegativeInteger(
249
+ document.commitAtMs,
250
+ EFFECT_RECIPE_MAX_DURATION_MS
251
+ );
252
+ if (commitAtMs === null) {
253
+ errors.push(
254
+ createError(
255
+ "out_of_range",
256
+ "commitAtMs",
257
+ `commitAtMs \u5FC5\u987B\u662F 0 \u5230 ${EFFECT_RECIPE_MAX_DURATION_MS} \u7684\u5B89\u5168\u6574\u6570\u3002`
258
+ )
259
+ );
260
+ } else if (durationMs !== null && commitAtMs > durationMs) {
261
+ errors.push(createError("out_of_range", "commitAtMs", "commitAtMs \u4E0D\u80FD\u5927\u4E8E durationMs\u3002"));
262
+ }
263
+ let app = null;
264
+ if (!isRecord(document.app)) {
265
+ errors.push(createError("invalid_type", "app", "app \u5143\u6570\u636E\u5FC5\u987B\u662F\u5BF9\u8C61\u3002"));
266
+ } else {
267
+ const label = normalizeText(document.app.label, EFFECT_RECIPE_APP_LABEL_MAX_CODE_POINTS);
268
+ if (!label) {
269
+ errors.push(
270
+ createError("invalid_length", "app.label", "app.label \u4E0D\u80FD\u4E3A\u7A7A\uFF0C\u4E14\u957F\u5EA6\u5FC5\u987B\u5728\u5141\u8BB8\u8303\u56F4\u5185\u3002")
271
+ );
272
+ }
273
+ const shortDescription = normalizeText(
274
+ document.app.shortDescription,
275
+ EFFECT_RECIPE_APP_SHORT_DESCRIPTION_MAX_CODE_POINTS
276
+ );
277
+ if (!shortDescription) {
278
+ errors.push(
279
+ createError(
280
+ "invalid_length",
281
+ "app.shortDescription",
282
+ "app.shortDescription \u4E0D\u80FD\u4E3A\u7A7A\uFF0C\u4E14\u957F\u5EA6\u5FC5\u987B\u5728\u5141\u8BB8\u8303\u56F4\u5185\u3002"
283
+ )
284
+ );
285
+ }
286
+ const iconId = normalizeText(document.app.iconId, EFFECT_RECIPE_APP_ICON_ID_MAX_LENGTH);
287
+ if (!iconId || !EFFECT_RECIPE_STABLE_ID_REGEX.test(iconId)) {
288
+ errors.push(createError("invalid_id", "app.iconId", "app.iconId \u5FC5\u987B\u662F\u7A33\u5B9A\u6807\u8BC6\u7B26\u3002"));
289
+ }
290
+ const order = normalizeNonNegativeInteger(document.app.order, EFFECT_RECIPE_APP_ORDER_MAX);
291
+ if (order === null) {
292
+ errors.push(
293
+ createError(
294
+ "out_of_range",
295
+ "app.order",
296
+ `app.order \u5FC5\u987B\u662F 0 \u5230 ${EFFECT_RECIPE_APP_ORDER_MAX} \u7684\u5B89\u5168\u6574\u6570\u3002`
297
+ )
298
+ );
299
+ }
300
+ if (label && shortDescription && iconId && EFFECT_RECIPE_STABLE_ID_REGEX.test(iconId) && order !== null) {
301
+ app = {
302
+ label,
303
+ shortDescription,
304
+ iconId,
305
+ order
306
+ };
307
+ }
308
+ }
309
+ const rawTracks = document.tracks;
310
+ const tracks = [];
311
+ const seenTrackIds = /* @__PURE__ */ new Set();
312
+ if (!Array.isArray(rawTracks)) {
313
+ errors.push(createError("invalid_type", "tracks", "tracks \u5FC5\u987B\u662F\u6570\u7EC4\u3002"));
314
+ } else if (rawTracks.length === 0) {
315
+ errors.push(createError("invalid_length", "tracks", "tracks \u81F3\u5C11\u9700\u8981\u4E00\u4E2A track\u3002"));
316
+ } else if (rawTracks.length > EFFECT_RECIPE_MAX_TRACKS) {
317
+ errors.push(
318
+ createError("too_many_items", "tracks", `tracks \u6570\u91CF\u4E0D\u80FD\u8D85\u8FC7 ${EFFECT_RECIPE_MAX_TRACKS}\u3002`)
319
+ );
320
+ } else {
321
+ rawTracks.forEach((rawTrack, trackIndex) => {
322
+ const trackPath = `tracks[${trackIndex}]`;
323
+ if (!isRecord(rawTrack)) {
324
+ errors.push(createError("invalid_type", trackPath, "track \u5FC5\u987B\u662F\u5BF9\u8C61\u3002"));
325
+ return;
326
+ }
327
+ const trackId = normalizeText(rawTrack.id, EFFECT_RECIPE_TRACK_ID_MAX_LENGTH);
328
+ if (!trackId || !EFFECT_RECIPE_STABLE_ID_REGEX.test(trackId)) {
329
+ errors.push(createError("invalid_id", `${trackPath}.id`, "track id \u5FC5\u987B\u662F\u7A33\u5B9A\u6807\u8BC6\u7B26\u3002"));
330
+ } else if (seenTrackIds.has(trackId)) {
331
+ errors.push(
332
+ createError("duplicate_track_id", `${trackPath}.id`, `\u91CD\u590D\u7684 track id: ${trackId}\u3002`)
333
+ );
334
+ } else {
335
+ seenTrackIds.add(trackId);
336
+ }
337
+ const atMs = normalizeNonNegativeInteger(rawTrack.atMs, EFFECT_RECIPE_MAX_DURATION_MS);
338
+ if (atMs === null) {
339
+ errors.push(
340
+ createError(
341
+ "out_of_range",
342
+ `${trackPath}.atMs`,
343
+ `track.atMs \u5FC5\u987B\u662F 0 \u5230 ${EFFECT_RECIPE_MAX_DURATION_MS} \u7684\u5B89\u5168\u6574\u6570\u3002`
344
+ )
345
+ );
346
+ }
347
+ const trackDurationMs = normalizeNonNegativeInteger(
348
+ rawTrack.durationMs,
349
+ EFFECT_RECIPE_MAX_DURATION_MS
350
+ );
351
+ if (trackDurationMs === null) {
352
+ errors.push(
353
+ createError(
354
+ "out_of_range",
355
+ `${trackPath}.durationMs`,
356
+ `track.durationMs \u5FC5\u987B\u662F 0 \u5230 ${EFFECT_RECIPE_MAX_DURATION_MS} \u7684\u5B89\u5168\u6574\u6570\u3002`
357
+ )
358
+ );
359
+ }
360
+ if (durationMs !== null && atMs !== null && trackDurationMs !== null && atMs + trackDurationMs > durationMs) {
361
+ errors.push(
362
+ createError(
363
+ "out_of_range",
364
+ `${trackPath}.durationMs`,
365
+ "track \u6267\u884C\u533A\u95F4\u4E0D\u80FD\u8D85\u51FA recipe.durationMs\u3002"
366
+ )
367
+ );
368
+ }
369
+ if (rawTrack.target !== void 0 && !EFFECT_TARGETS.some((target) => target === rawTrack.target)) {
370
+ errors.push(createError("invalid_enum", "tracks.target", "\u975E\u6CD5\u7279\u6548\u4F5C\u7528\u76EE\u6807\u3002"));
371
+ }
372
+ const primitiveId = normalizeText(
373
+ rawTrack.primitiveId,
374
+ EFFECT_RECIPE_PRIMITIVE_ID_MAX_LENGTH
375
+ );
376
+ if (!primitiveId || !EFFECT_RECIPE_STABLE_ID_REGEX.test(primitiveId)) {
377
+ errors.push(
378
+ createError(
379
+ "invalid_id",
380
+ `${trackPath}.primitiveId`,
381
+ "track primitiveId \u5FC5\u987B\u662F\u7A33\u5B9A\u6807\u8BC6\u7B26\u3002"
382
+ )
383
+ );
384
+ }
385
+ if (typeof rawTrack.enabled !== "boolean") {
386
+ errors.push(
387
+ createError("invalid_type", `${trackPath}.enabled`, "track.enabled \u5FC5\u987B\u662F\u5E03\u5C14\u503C\u3002")
388
+ );
389
+ }
390
+ const params = {};
391
+ if (!isRecord(rawTrack.params)) {
392
+ errors.push(
393
+ createError("invalid_type", `${trackPath}.params`, "track.params \u5FC5\u987B\u662F\u5BF9\u8C61\u3002")
394
+ );
395
+ } else {
396
+ Object.entries(rawTrack.params).forEach(([paramName, paramValue]) => {
397
+ const normalizedValue = parseRecipeValue(
398
+ paramValue,
399
+ `${trackPath}.params.${paramName}`,
400
+ errors
401
+ );
402
+ if (normalizedValue !== null) {
403
+ params[paramName] = normalizedValue;
404
+ }
405
+ });
406
+ }
407
+ if (trackId && EFFECT_RECIPE_STABLE_ID_REGEX.test(trackId) && atMs !== null && trackDurationMs !== null && primitiveId && EFFECT_RECIPE_STABLE_ID_REGEX.test(primitiveId) && typeof rawTrack.enabled === "boolean" && isRecord(rawTrack.params)) {
408
+ tracks.push({
409
+ id: trackId,
410
+ atMs,
411
+ durationMs: trackDurationMs,
412
+ ...rawTrack.target === void 0 ? {} : { target: rawTrack.target },
413
+ primitiveId,
414
+ params,
415
+ enabled: rawTrack.enabled
416
+ });
417
+ }
418
+ });
419
+ }
420
+ if (errors.length > 0 || !id || !name || !description || !isEffectRecipeScope(scope) || !isEffectRecipeCategory(category) || durationMs === null || commitAtMs === null || !app) {
421
+ return { definition: null, errors };
422
+ }
423
+ return {
424
+ definition: {
425
+ schemaVersion: EFFECT_RECIPE_SCHEMA_VERSION,
426
+ id,
427
+ name,
428
+ description,
429
+ scope,
430
+ category,
431
+ durationMs,
432
+ commitAtMs,
433
+ app,
434
+ tracks
435
+ },
436
+ errors
437
+ };
438
+ };
439
+ var createEffectRecipeValidationError = createError;
440
+
441
+ // src/recipes/primitives.ts
442
+ var EFFECT_PRIMITIVE_CATEGORIES = [
443
+ "content",
444
+ "screen",
445
+ "camera",
446
+ "shader",
447
+ "particle",
448
+ "ui",
449
+ "audio"
450
+ ];
451
+ var EFFECT_PRIMITIVE_PARAM_TYPES = ["string", "number", "boolean", "number[]"];
452
+ var EFFECT_PRIMITIVE_PREVIEW_SUPPORT = ["full", "milestone", "replay-only"];
453
+ var EffectPrimitiveRegistry = class {
454
+ descriptors;
455
+ descriptorById;
456
+ constructor(descriptors) {
457
+ const descriptorById = /* @__PURE__ */ new Map();
458
+ for (const descriptor of descriptors) {
459
+ if (descriptorById.has(descriptor.id)) {
460
+ throw new Error(`\u91CD\u590D\u7684 primitive descriptor: ${descriptor.id}`);
461
+ }
462
+ descriptorById.set(descriptor.id, descriptor);
463
+ }
464
+ this.descriptors = [...descriptors];
465
+ this.descriptorById = descriptorById;
466
+ }
467
+ all() {
468
+ return this.descriptors;
469
+ }
470
+ get(id) {
471
+ return this.descriptorById.get(id) ?? null;
472
+ }
473
+ listByScope(scope) {
474
+ return this.descriptors.filter((descriptor) => descriptor.scopes.includes(scope));
475
+ }
476
+ };
477
+
478
+ // src/recipes/registry.ts
479
+ var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
480
+ var hasOwn2 = (value, key) => Object.prototype.hasOwnProperty.call(value, key);
481
+ var prefixPath = (prefix, path) => {
482
+ if (!prefix) {
483
+ return path;
484
+ }
485
+ if (!path) {
486
+ return prefix;
487
+ }
488
+ if (path.startsWith("[")) {
489
+ return `${prefix}${path}`;
490
+ }
491
+ return `${prefix}.${path}`;
492
+ };
493
+ var normalizeString = (value) => value.trim();
494
+ var EffectRecipeRegistryError = class extends Error {
495
+ constructor(errors) {
496
+ super(errors.map((error) => `${error.path}: ${error.message}`).join("\n"));
497
+ this.errors = errors;
498
+ this.name = "EffectRecipeRegistryError";
499
+ }
500
+ errors;
501
+ };
502
+ var buildCatalog = (definition, source) => ({
503
+ id: definition.id,
504
+ name: definition.name,
505
+ description: definition.description,
506
+ scope: definition.scope,
507
+ category: definition.category,
508
+ durationMs: definition.durationMs,
509
+ commitAtMs: definition.commitAtMs,
510
+ source,
511
+ app: definition.app,
512
+ trackCount: definition.tracks.length
513
+ });
514
+ var sortRegistrations = (entries) => [...entries].sort((left, right) => {
515
+ if (left.catalog.app.order !== right.catalog.app.order) {
516
+ return left.catalog.app.order - right.catalog.app.order;
517
+ }
518
+ return left.catalog.id.localeCompare(right.catalog.id, "zh-CN");
519
+ });
520
+ var validateStringValue = (value, descriptor, path, errors) => {
521
+ const normalized = normalizeString(value);
522
+ if (descriptor.name === "color" && !/^#[0-9a-f]{6}$/i.test(normalized)) {
523
+ errors.push(
524
+ createEffectRecipeValidationError("invalid_param_value", path, "\u989C\u8272\u5FC5\u987B\u662F #RRGGBB\u3002")
525
+ );
526
+ return null;
527
+ }
528
+ if (descriptor.minLength !== void 0 && normalized.length < descriptor.minLength) {
529
+ errors.push(
530
+ createEffectRecipeValidationError(
531
+ "invalid_param_value",
532
+ path,
533
+ `${descriptor.name} \u5B57\u7B26\u4E32\u957F\u5EA6\u4E0D\u80FD\u5C0F\u4E8E ${descriptor.minLength}\u3002`
534
+ )
535
+ );
536
+ return null;
537
+ }
538
+ if (descriptor.maxLength !== void 0 && normalized.length > descriptor.maxLength) {
539
+ errors.push(
540
+ createEffectRecipeValidationError(
541
+ "invalid_param_value",
542
+ path,
543
+ `${descriptor.name} \u5B57\u7B26\u4E32\u957F\u5EA6\u4E0D\u80FD\u5927\u4E8E ${descriptor.maxLength}\u3002`
544
+ )
545
+ );
546
+ return null;
547
+ }
548
+ if (descriptor.options && !descriptor.options.includes(normalized)) {
549
+ errors.push(
550
+ createEffectRecipeValidationError(
551
+ "invalid_param_value",
552
+ path,
553
+ `${descriptor.name} \u5FC5\u987B\u662F ${descriptor.options.join("\u3001")} \u4E4B\u4E00\u3002`
554
+ )
555
+ );
556
+ return null;
557
+ }
558
+ return normalized;
559
+ };
560
+ var validateNumberValue = (value, descriptor, path, errors) => {
561
+ if (!Number.isFinite(value)) {
562
+ errors.push(
563
+ createEffectRecipeValidationError(
564
+ "invalid_param_type",
565
+ path,
566
+ `${descriptor.name} \u5FC5\u987B\u662F\u6709\u9650\u6570\u5B57\u3002`
567
+ )
568
+ );
569
+ return null;
570
+ }
571
+ if (descriptor.min !== void 0 && value < descriptor.min) {
572
+ errors.push(
573
+ createEffectRecipeValidationError(
574
+ "invalid_param_value",
575
+ path,
576
+ `${descriptor.name} \u4E0D\u80FD\u5C0F\u4E8E ${descriptor.min}\u3002`
577
+ )
578
+ );
579
+ return null;
580
+ }
581
+ if (descriptor.max !== void 0 && value > descriptor.max) {
582
+ errors.push(
583
+ createEffectRecipeValidationError(
584
+ "invalid_param_value",
585
+ path,
586
+ `${descriptor.name} \u4E0D\u80FD\u5927\u4E8E ${descriptor.max}\u3002`
587
+ )
588
+ );
589
+ return null;
590
+ }
591
+ if (descriptor.options && !descriptor.options.includes(value)) {
592
+ errors.push(
593
+ createEffectRecipeValidationError(
594
+ "invalid_param_value",
595
+ path,
596
+ `${descriptor.name} \u4E0D\u5728\u5141\u8BB8\u7684\u679A\u4E3E\u8303\u56F4\u5185\u3002`
597
+ )
598
+ );
599
+ return null;
600
+ }
601
+ return value;
602
+ };
603
+ var validateBooleanValue = (value, descriptor, path, errors) => {
604
+ if (descriptor.options && !descriptor.options.includes(value)) {
605
+ errors.push(
606
+ createEffectRecipeValidationError(
607
+ "invalid_param_value",
608
+ path,
609
+ `${descriptor.name} \u4E0D\u5728\u5141\u8BB8\u7684\u679A\u4E3E\u8303\u56F4\u5185\u3002`
610
+ )
611
+ );
612
+ return null;
613
+ }
614
+ return value;
615
+ };
616
+ var validateNumberArrayValue = (value, descriptor, path, errors) => {
617
+ if (!value.every((entry) => typeof entry === "number" && Number.isFinite(entry))) {
618
+ errors.push(
619
+ createEffectRecipeValidationError(
620
+ "invalid_param_type",
621
+ path,
622
+ `${descriptor.name} \u5FC5\u987B\u662F\u6709\u9650\u6570\u5B57\u6570\u7EC4\u3002`
623
+ )
624
+ );
625
+ return null;
626
+ }
627
+ if (descriptor.arrayMinLength !== void 0 && value.length < descriptor.arrayMinLength) {
628
+ errors.push(
629
+ createEffectRecipeValidationError(
630
+ "invalid_param_value",
631
+ path,
632
+ `${descriptor.name} \u6570\u7EC4\u957F\u5EA6\u4E0D\u80FD\u5C0F\u4E8E ${descriptor.arrayMinLength}\u3002`
633
+ )
634
+ );
635
+ return null;
636
+ }
637
+ if (descriptor.arrayMaxLength !== void 0 && value.length > descriptor.arrayMaxLength) {
638
+ errors.push(
639
+ createEffectRecipeValidationError(
640
+ "invalid_param_value",
641
+ path,
642
+ `${descriptor.name} \u6570\u7EC4\u957F\u5EA6\u4E0D\u80FD\u5927\u4E8E ${descriptor.arrayMaxLength}\u3002`
643
+ )
644
+ );
645
+ return null;
646
+ }
647
+ if ((descriptor.min !== void 0 || descriptor.max !== void 0) && value.some(
648
+ (entry) => descriptor.min !== void 0 && entry < descriptor.min || descriptor.max !== void 0 && entry > descriptor.max
649
+ )) {
650
+ errors.push(
651
+ createEffectRecipeValidationError(
652
+ "invalid_param_value",
653
+ path,
654
+ `${descriptor.name} \u6570\u7EC4\u5143\u7D20\u8D85\u51FA\u5141\u8BB8\u8303\u56F4\u3002`
655
+ )
656
+ );
657
+ return null;
658
+ }
659
+ return [...value];
660
+ };
661
+ var validateParamValue = (value, descriptor, path, errors, options) => {
662
+ if (isEffectRecipeTuningPathReference(value)) {
663
+ if (!options.allowTuningPath || descriptor.tuningPathAllowed !== true) {
664
+ errors.push(
665
+ createEffectRecipeValidationError(
666
+ "invalid_tuning_path",
667
+ path,
668
+ `${descriptor.name} \u4E0D\u5141\u8BB8\u4F7F\u7528 tuningPath \u5F15\u7528\u3002`
669
+ )
670
+ );
671
+ return null;
672
+ }
673
+ return { tuningPath: value.tuningPath };
674
+ }
675
+ switch (descriptor.type) {
676
+ case "string": {
677
+ if (typeof value !== "string") {
678
+ errors.push(
679
+ createEffectRecipeValidationError(
680
+ "invalid_param_type",
681
+ path,
682
+ `${descriptor.name} \u5FC5\u987B\u662F\u5B57\u7B26\u4E32\u3002`
683
+ )
684
+ );
685
+ return null;
686
+ }
687
+ const normalized = validateStringValue(value, descriptor, path, errors);
688
+ return normalized;
689
+ }
690
+ case "number": {
691
+ if (typeof value !== "number") {
692
+ errors.push(
693
+ createEffectRecipeValidationError(
694
+ "invalid_param_type",
695
+ path,
696
+ `${descriptor.name} \u5FC5\u987B\u662F\u6570\u5B57\u3002`
697
+ )
698
+ );
699
+ return null;
700
+ }
701
+ const normalized = validateNumberValue(value, descriptor, path, errors);
702
+ return normalized;
703
+ }
704
+ case "boolean": {
705
+ if (typeof value !== "boolean") {
706
+ errors.push(
707
+ createEffectRecipeValidationError(
708
+ "invalid_param_type",
709
+ path,
710
+ `${descriptor.name} \u5FC5\u987B\u662F\u5E03\u5C14\u503C\u3002`
711
+ )
712
+ );
713
+ return null;
714
+ }
715
+ const normalized = validateBooleanValue(value, descriptor, path, errors);
716
+ return normalized;
717
+ }
718
+ case "number[]": {
719
+ if (!Array.isArray(value)) {
720
+ errors.push(
721
+ createEffectRecipeValidationError(
722
+ "invalid_param_type",
723
+ path,
724
+ `${descriptor.name} \u5FC5\u987B\u662F\u6570\u5B57\u6570\u7EC4\u3002`
725
+ )
726
+ );
727
+ return null;
728
+ }
729
+ const normalized = validateNumberArrayValue(value, descriptor, path, errors);
730
+ return normalized;
731
+ }
732
+ }
733
+ };
734
+ var validatePrimitiveParamsInternal = (descriptor, params, options) => {
735
+ const errors = [];
736
+ const pathPrefix = options.pathPrefix ?? "params";
737
+ if (!isRecord2(params)) {
738
+ return {
739
+ params: null,
740
+ errors: [
741
+ createEffectRecipeValidationError("invalid_type", pathPrefix, "params \u5FC5\u987B\u662F\u5BF9\u8C61\u3002")
742
+ ]
743
+ };
744
+ }
745
+ const normalizedParams = {};
746
+ const descriptorMap = new Map(descriptor.paramsSchema.map((item) => [item.name, item]));
747
+ Object.keys(params).forEach((key) => {
748
+ if (!descriptorMap.has(key)) {
749
+ errors.push(
750
+ createEffectRecipeValidationError(
751
+ "unknown_param",
752
+ `${pathPrefix}.${key}`,
753
+ `\u672A\u77E5\u53C2\u6570: ${key}\u3002`
754
+ )
755
+ );
756
+ }
757
+ });
758
+ for (const paramDescriptor of descriptor.paramsSchema) {
759
+ const paramPath = `${pathPrefix}.${paramDescriptor.name}`;
760
+ const exists = hasOwn2(params, paramDescriptor.name);
761
+ if (!exists) {
762
+ if (paramDescriptor.required) {
763
+ errors.push(
764
+ createEffectRecipeValidationError(
765
+ "missing_param",
766
+ paramPath,
767
+ `\u7F3A\u5C11\u5FC5\u586B\u53C2\u6570: ${paramDescriptor.name}\u3002`
768
+ )
769
+ );
770
+ }
771
+ continue;
772
+ }
773
+ const rawValue = params[paramDescriptor.name];
774
+ const normalizedValue = validateParamValue(
775
+ rawValue,
776
+ paramDescriptor,
777
+ paramPath,
778
+ errors,
779
+ options
780
+ );
781
+ if (normalizedValue !== null) {
782
+ normalizedParams[paramDescriptor.name] = normalizedValue;
783
+ }
784
+ }
785
+ return { params: errors.length === 0 ? normalizedParams : null, errors };
786
+ };
787
+ var validateEffectRecipeTrackParams = (descriptor, params, pathPrefix = "params") => validatePrimitiveParamsInternal(descriptor, params, {
788
+ allowTuningPath: true,
789
+ pathPrefix
790
+ });
791
+ var validateResolvedEffectRecipeTrackParams = (descriptor, params, pathPrefix = "params") => validatePrimitiveParamsInternal(descriptor, params, {
792
+ allowTuningPath: false,
793
+ pathPrefix
794
+ });
795
+ var validateEffectRecipeManifest = (entries, primitiveRegistry) => {
796
+ const errors = [];
797
+ const provisional = [];
798
+ const seenRecipeIds = /* @__PURE__ */ new Set();
799
+ entries.forEach((entry, index) => {
800
+ const definitionResult = validateEffectRecipeDefinition(entry.definition);
801
+ const entryPrefix = `recipes[${index}]`;
802
+ if (!definitionResult.definition) {
803
+ errors.push(
804
+ ...definitionResult.errors.map((error) => ({
805
+ ...error,
806
+ path: prefixPath(entryPrefix, error.path)
807
+ }))
808
+ );
809
+ return;
810
+ }
811
+ const definition = definitionResult.definition;
812
+ if (seenRecipeIds.has(definition.id)) {
813
+ errors.push(
814
+ createEffectRecipeValidationError(
815
+ "duplicate_recipe_id",
816
+ `${entryPrefix}.id`,
817
+ `\u91CD\u590D\u7684 recipe id: ${definition.id}\u3002`
818
+ )
819
+ );
820
+ return;
821
+ }
822
+ seenRecipeIds.add(definition.id);
823
+ definition.tracks.forEach((track, trackIndex) => {
824
+ const trackPath = `${entryPrefix}.tracks[${trackIndex}]`;
825
+ const descriptor = primitiveRegistry.get(track.primitiveId);
826
+ if (!descriptor) {
827
+ errors.push(
828
+ createEffectRecipeValidationError(
829
+ "unknown_primitive",
830
+ `${trackPath}.primitiveId`,
831
+ `\u672A\u77E5 primitive: ${track.primitiveId}\u3002`
832
+ )
833
+ );
834
+ return;
835
+ }
836
+ if (!descriptor.scopes.includes(definition.scope)) {
837
+ errors.push(
838
+ createEffectRecipeValidationError(
839
+ "unsupported_scope",
840
+ `${trackPath}.primitiveId`,
841
+ `primitive ${track.primitiveId} \u4E0D\u652F\u6301 ${definition.scope} scope\u3002`
842
+ )
843
+ );
844
+ }
845
+ const paramsResult = validateEffectRecipeTrackParams(
846
+ descriptor,
847
+ track.params,
848
+ `${trackPath}.params`
849
+ );
850
+ errors.push(...paramsResult.errors);
851
+ });
852
+ if (!errors.some((error) => error.path.startsWith(entryPrefix))) {
853
+ provisional.push({
854
+ source: entry.source,
855
+ definition,
856
+ catalog: buildCatalog(definition, entry.source)
857
+ });
858
+ }
859
+ });
860
+ return {
861
+ entries: sortRegistrations(provisional),
862
+ errors
863
+ };
864
+ };
865
+ var EffectRecipeRegistry = class {
866
+ constructor(manifestEntries, primitiveRegistry) {
867
+ this.primitiveRegistry = primitiveRegistry;
868
+ const result = validateEffectRecipeManifest(manifestEntries, primitiveRegistry);
869
+ if (result.errors.length > 0) {
870
+ throw new EffectRecipeRegistryError(result.errors);
871
+ }
872
+ this.entries = result.entries;
873
+ this.entryById = new Map(result.entries.map((entry) => [entry.definition.id, entry]));
874
+ }
875
+ primitiveRegistry;
876
+ entries;
877
+ entryById;
878
+ all() {
879
+ return this.entries;
880
+ }
881
+ catalog() {
882
+ return this.entries.map((entry) => entry.catalog);
883
+ }
884
+ get(recipeId) {
885
+ return this.entryById.get(recipeId) ?? null;
886
+ }
887
+ listByScope(scope) {
888
+ return this.entries.filter((entry) => entry.definition.scope === scope);
889
+ }
890
+ listByCategory(category) {
891
+ return this.entries.filter((entry) => entry.definition.category === category);
892
+ }
893
+ validate(definition, source = "custom") {
894
+ const result = validateEffectRecipeManifest(
895
+ [
896
+ {
897
+ source,
898
+ definition
899
+ }
900
+ ],
901
+ this.primitiveRegistry
902
+ );
903
+ return {
904
+ entry: result.errors.length === 0 ? result.entries[0] ?? null : null,
905
+ errors: result.errors
906
+ };
907
+ }
908
+ };
909
+ var resolveEffectRecipePrimitiveDescriptor = (recipe, trackIndex, primitiveRegistry) => primitiveRegistry.get(recipe.tracks[trackIndex]?.primitiveId ?? "");
910
+
911
+ // src/recipes/EffectPrimitiveRuntimeRegistry.ts
912
+ var EffectPrimitiveRuntimeRegistry = class {
913
+ runtimes = /* @__PURE__ */ new Map();
914
+ register(runtime) {
915
+ if (this.runtimes.has(runtime.primitiveId)) {
916
+ throw new Error(`\u91CD\u590D\u7684 effect primitive runtime: ${runtime.primitiveId}`);
917
+ }
918
+ this.runtimes.set(runtime.primitiveId, runtime);
919
+ }
920
+ get(primitiveId) {
921
+ return this.runtimes.get(primitiveId) ?? null;
922
+ }
923
+ listIds() {
924
+ return [...this.runtimes.keys()].sort((left, right) => left.localeCompare(right, "zh-CN"));
925
+ }
926
+ };
927
+
928
+ // src/recipes/EffectRecipeExecutor.ts
929
+ var compareTracks = (left, right) => {
930
+ if (left.atMs !== right.atMs) {
931
+ return left.atMs - right.atMs;
932
+ }
933
+ return left.id.localeCompare(right.id, "zh-CN");
934
+ };
935
+ var isRecipeDefinition = (value) => typeof value !== "string";
936
+ var resolveTrackIdFromValidationErrors = (recipe, errors) => {
937
+ const trackPath = errors.find((error) => /^recipes\[0\]\.tracks\[\d+\]/.test(error.path))?.path;
938
+ if (!trackPath) {
939
+ return void 0;
940
+ }
941
+ const match = /^recipes\[0\]\.tracks\[(\d+)\]/.exec(trackPath);
942
+ const trackIndex = match ? Number.parseInt(match[1] ?? "", 10) : Number.NaN;
943
+ if (!Number.isSafeInteger(trackIndex) || trackIndex < 0) {
944
+ return void 0;
945
+ }
946
+ return recipe.tracks[trackIndex]?.id;
947
+ };
948
+ var EffectRecipeExecutorError = class extends Error {
949
+ constructor(code, recipeId, message, options = {}) {
950
+ super(message);
951
+ this.code = code;
952
+ this.recipeId = recipeId;
953
+ this.name = "EffectRecipeExecutorError";
954
+ this.trackId = options.trackId;
955
+ this.validationErrors = options.validationErrors ?? [];
956
+ this.cause = options.cause;
957
+ }
958
+ code;
959
+ recipeId;
960
+ cause;
961
+ trackId;
962
+ validationErrors;
963
+ };
964
+ var EffectRecipeExecutor = class {
965
+ constructor(options) {
966
+ this.options = options;
967
+ this.scheduler = options.scheduler;
968
+ this.recipeRegistry = options.recipeRegistry;
969
+ this.primitiveRegistry = options.primitiveRegistry;
970
+ }
971
+ options;
972
+ scheduler;
973
+ recipeRegistry;
974
+ primitiveRegistry;
975
+ buildPlan(recipeInput, runtimeContext) {
976
+ const recipe = this.resolveRecipe(recipeInput);
977
+ return recipe.tracks.filter((track) => track.enabled).slice().sort(compareTracks).map((track) => this.resolveTrackPlanItem(recipe, track, runtimeContext));
978
+ }
979
+ execute(runOptions) {
980
+ const recipe = this.resolveRecipe(runOptions.recipe);
981
+ const plan = this.buildPlan(recipe, runOptions.runtimeContext);
982
+ const controller = new AbortController();
983
+ const timerHandles = [];
984
+ const activeCleanups = /* @__PURE__ */ new Map();
985
+ const expiredTracks = /* @__PURE__ */ new Set();
986
+ const trackControllers = /* @__PURE__ */ new Map();
987
+ const recipeEndTracks = /* @__PURE__ */ new Set();
988
+ const pendingCleanupPromises = /* @__PURE__ */ new Set();
989
+ let committed = false;
990
+ let finished = false;
991
+ let detachExternalSignal = null;
992
+ let resolveDone;
993
+ let rejectDone;
994
+ const done = new Promise((resolve, reject) => {
995
+ resolveDone = resolve;
996
+ rejectDone = reject;
997
+ });
998
+ const clearTimers = () => {
999
+ while (timerHandles.length > 0) {
1000
+ this.scheduler.clearTimeout(timerHandles.pop());
1001
+ }
1002
+ };
1003
+ const cleanupTrack = (id) => {
1004
+ expiredTracks.add(id);
1005
+ trackControllers.get(id)?.abort("track-ended");
1006
+ trackControllers.delete(id);
1007
+ const cleanup = activeCleanups.get(id);
1008
+ activeCleanups.delete(id);
1009
+ cleanup?.();
1010
+ };
1011
+ const flushCleanups = () => {
1012
+ for (const id of [...trackControllers.keys()]) {
1013
+ try {
1014
+ cleanupTrack(id);
1015
+ } catch {
1016
+ }
1017
+ }
1018
+ };
1019
+ const finalize = (status, error) => {
1020
+ if (finished) {
1021
+ return;
1022
+ }
1023
+ finished = true;
1024
+ clearTimers();
1025
+ detachExternalSignal?.();
1026
+ if (status === "aborted" && !controller.signal.aborted) {
1027
+ controller.abort(error?.message ?? "effect-recipe-aborted");
1028
+ }
1029
+ flushCleanups();
1030
+ const settle = () => {
1031
+ const outcome = {
1032
+ recipeId: recipe.id,
1033
+ status,
1034
+ committed
1035
+ };
1036
+ try {
1037
+ runOptions.onDone?.(outcome);
1038
+ } catch {
1039
+ }
1040
+ if (error) {
1041
+ rejectDone(error);
1042
+ return;
1043
+ }
1044
+ resolveDone(outcome);
1045
+ };
1046
+ if (pendingCleanupPromises.size === 0) {
1047
+ settle();
1048
+ return;
1049
+ }
1050
+ void Promise.allSettled([...pendingCleanupPromises]).then(() => {
1051
+ settle();
1052
+ });
1053
+ };
1054
+ const requestAbort = (reason = "effect-recipe-aborted", error) => {
1055
+ if (!controller.signal.aborted) {
1056
+ controller.abort(reason);
1057
+ }
1058
+ finalize("aborted", error);
1059
+ };
1060
+ const fail = (code, trackId, message, cause) => {
1061
+ requestAbort(
1062
+ message,
1063
+ new EffectRecipeExecutorError(
1064
+ code,
1065
+ recipe.id,
1066
+ cause instanceof Error ? `${message}: ${cause.message}` : message,
1067
+ {
1068
+ trackId,
1069
+ cause
1070
+ }
1071
+ )
1072
+ );
1073
+ };
1074
+ const registerCleanup = (trackId, result) => {
1075
+ const accept = (handle) => {
1076
+ if (!handle) return;
1077
+ const cleanup = typeof handle === "function" ? handle : () => handle.cleanup();
1078
+ if (finished || expiredTracks.has(trackId) || controller.signal.aborted) {
1079
+ cleanup();
1080
+ return;
1081
+ }
1082
+ activeCleanups.set(trackId, cleanup);
1083
+ if (typeof handle !== "function") {
1084
+ if (handle.cleanupPolicy === "recipe-end") recipeEndTracks.add(trackId);
1085
+ void handle.completed?.catch((error) => {
1086
+ if (!finished && !expiredTracks.has(trackId))
1087
+ fail("runtime_error", trackId, "Primitive completion failed", error);
1088
+ });
1089
+ }
1090
+ };
1091
+ if (result instanceof Promise) {
1092
+ const pending = result.then(accept).catch((error) => {
1093
+ if (!finished) fail("runtime_error", trackId, "Primitive start failed", error);
1094
+ });
1095
+ pendingCleanupPromises.add(pending);
1096
+ void pending.finally(() => pendingCleanupPromises.delete(pending));
1097
+ } else accept(result);
1098
+ };
1099
+ const commit = () => {
1100
+ if (committed || finished || controller.signal.aborted) {
1101
+ return;
1102
+ }
1103
+ committed = true;
1104
+ try {
1105
+ runOptions.onCommit?.(recipe);
1106
+ } catch (error) {
1107
+ fail("runtime_error", void 0, "Recipe commit callback failed", error);
1108
+ }
1109
+ };
1110
+ const startTrack = (planItem) => {
1111
+ if (finished || controller.signal.aborted) {
1112
+ return;
1113
+ }
1114
+ try {
1115
+ const trackController = new AbortController();
1116
+ trackControllers.set(planItem.track.id, trackController);
1117
+ const result = planItem.runtime.execute(
1118
+ planItem.executionContext,
1119
+ planItem.params,
1120
+ trackController.signal
1121
+ );
1122
+ registerCleanup(planItem.track.id, result);
1123
+ } catch (error) {
1124
+ fail(
1125
+ "runtime_error",
1126
+ planItem.track.id,
1127
+ `Effect primitive \u540C\u6B65\u6267\u884C\u5931\u8D25: ${planItem.track.id}`,
1128
+ error
1129
+ );
1130
+ }
1131
+ };
1132
+ if (runOptions.signal) {
1133
+ const abortFromCaller = () => {
1134
+ requestAbort(String(runOptions.signal?.reason ?? "effect-recipe-aborted"));
1135
+ };
1136
+ if (runOptions.signal.aborted) {
1137
+ abortFromCaller();
1138
+ } else {
1139
+ runOptions.signal.addEventListener("abort", abortFromCaller, { once: true });
1140
+ detachExternalSignal = () => {
1141
+ runOptions.signal?.removeEventListener("abort", abortFromCaller);
1142
+ };
1143
+ }
1144
+ }
1145
+ if (!finished) {
1146
+ const groupedPlan = /* @__PURE__ */ new Map();
1147
+ for (const item of plan) {
1148
+ const existing = groupedPlan.get(item.track.atMs);
1149
+ if (existing) {
1150
+ existing.push(item);
1151
+ } else {
1152
+ groupedPlan.set(item.track.atMs, [item]);
1153
+ }
1154
+ }
1155
+ const scheduleTimes = [
1156
+ .../* @__PURE__ */ new Set([
1157
+ ...groupedPlan.keys(),
1158
+ ...plan.map((item) => item.track.atMs + item.track.durationMs),
1159
+ recipe.commitAtMs
1160
+ ])
1161
+ ].sort((left, right) => left - right);
1162
+ for (const atMs of scheduleTimes) {
1163
+ timerHandles.push(
1164
+ this.scheduler.setTimeout(() => {
1165
+ if (finished || controller.signal.aborted) {
1166
+ return;
1167
+ }
1168
+ for (const item of groupedPlan.get(atMs) ?? []) startTrack(item);
1169
+ if (atMs === recipe.commitAtMs) commit();
1170
+ for (const item of plan) {
1171
+ if (item.track.atMs + item.track.durationMs === atMs && !recipeEndTracks.has(item.track.id)) {
1172
+ try {
1173
+ cleanupTrack(item.track.id);
1174
+ } catch (error) {
1175
+ fail("runtime_error", item.track.id, "Primitive cleanup failed", error);
1176
+ }
1177
+ }
1178
+ }
1179
+ }, atMs)
1180
+ );
1181
+ }
1182
+ timerHandles.push(
1183
+ this.scheduler.setTimeout(() => {
1184
+ finalize("completed");
1185
+ }, recipe.durationMs)
1186
+ );
1187
+ }
1188
+ return {
1189
+ recipe,
1190
+ signal: controller.signal,
1191
+ done,
1192
+ abort: (reason) => {
1193
+ requestAbort(reason ?? "effect-recipe-aborted");
1194
+ }
1195
+ };
1196
+ }
1197
+ resolveRecipe(recipeInput) {
1198
+ if (!isRecipeDefinition(recipeInput)) {
1199
+ const registration = this.recipeRegistry.get(recipeInput);
1200
+ if (!registration) {
1201
+ throw new EffectRecipeExecutorError(
1202
+ "unknown_recipe",
1203
+ recipeInput,
1204
+ `\u672A\u6CE8\u518C\u7684 effect recipe: ${recipeInput}`
1205
+ );
1206
+ }
1207
+ return registration.definition;
1208
+ }
1209
+ const validation = this.recipeRegistry.validate(recipeInput, "custom");
1210
+ if (validation.entry) {
1211
+ return validation.entry.definition;
1212
+ }
1213
+ throw new EffectRecipeExecutorError(
1214
+ validation.errors.some((error) => error.code === "unknown_primitive") ? "unknown_primitive" : "invalid_params",
1215
+ recipeInput.id,
1216
+ `Effect recipe \u5B9A\u4E49\u65E0\u6548: ${recipeInput.id}`,
1217
+ {
1218
+ trackId: resolveTrackIdFromValidationErrors(recipeInput, validation.errors),
1219
+ validationErrors: validation.errors
1220
+ }
1221
+ );
1222
+ }
1223
+ resolveTrackPlanItem(recipe, track, runtimeContext) {
1224
+ const primitive = this.primitiveRegistry.get(track.primitiveId);
1225
+ if (!primitive) {
1226
+ throw new EffectRecipeExecutorError(
1227
+ "unknown_primitive",
1228
+ recipe.id,
1229
+ `\u672A\u6CE8\u518C\u7684 effect primitive descriptor: ${track.primitiveId}`,
1230
+ {
1231
+ trackId: track.id
1232
+ }
1233
+ );
1234
+ }
1235
+ const runtime = this.options.runtimeRegistry.get(track.primitiveId);
1236
+ if (!runtime) {
1237
+ throw new EffectRecipeExecutorError(
1238
+ "unknown_runtime",
1239
+ recipe.id,
1240
+ `\u672A\u6CE8\u518C\u7684 effect primitive runtime: ${track.primitiveId}`,
1241
+ {
1242
+ trackId: track.id
1243
+ }
1244
+ );
1245
+ }
1246
+ const resolvedParams = {};
1247
+ for (const [paramName, rawValue] of Object.entries(track.params)) {
1248
+ if (isEffectRecipeTuningPathReference(rawValue)) {
1249
+ const resolvedValue = this.resolveTuningReference(
1250
+ recipe,
1251
+ track,
1252
+ primitive,
1253
+ paramName,
1254
+ rawValue,
1255
+ runtimeContext
1256
+ );
1257
+ resolvedParams[paramName] = resolvedValue;
1258
+ continue;
1259
+ }
1260
+ if (!isResolvedRecipeValue(rawValue)) {
1261
+ throw new EffectRecipeExecutorError(
1262
+ "invalid_params",
1263
+ recipe.id,
1264
+ `Track \u53C2\u6570\u672A\u901A\u8FC7\u8FD0\u884C\u65F6\u503C\u6821\u9A8C: ${track.id}.${paramName}`,
1265
+ {
1266
+ trackId: track.id
1267
+ }
1268
+ );
1269
+ }
1270
+ resolvedParams[paramName] = rawValue;
1271
+ }
1272
+ const validatedParams = validateResolvedEffectRecipeTrackParams(
1273
+ primitive,
1274
+ resolvedParams,
1275
+ `tracks.${track.id}.params`
1276
+ );
1277
+ if (!validatedParams.params) {
1278
+ throw new EffectRecipeExecutorError(
1279
+ "invalid_params",
1280
+ recipe.id,
1281
+ `Track \u53C2\u6570\u672A\u901A\u8FC7 descriptor \u6821\u9A8C: ${track.id}`,
1282
+ {
1283
+ trackId: track.id,
1284
+ validationErrors: validatedParams.errors
1285
+ }
1286
+ );
1287
+ }
1288
+ return {
1289
+ track,
1290
+ primitive,
1291
+ runtime,
1292
+ params: validatedParams.params,
1293
+ executionContext: {
1294
+ recipeId: recipe.id,
1295
+ recipe,
1296
+ track,
1297
+ primitive,
1298
+ runtimeContext
1299
+ }
1300
+ };
1301
+ }
1302
+ resolveTuningReference(recipe, track, primitive, paramName, reference, runtimeContext) {
1303
+ let resolvedValue;
1304
+ try {
1305
+ resolvedValue = this.options.resolveTuningPath(reference, {
1306
+ recipe,
1307
+ track,
1308
+ primitive,
1309
+ paramName,
1310
+ runtimeContext
1311
+ });
1312
+ } catch (error) {
1313
+ throw new EffectRecipeExecutorError(
1314
+ "tuning_resolution_failed",
1315
+ recipe.id,
1316
+ `tuningPath \u89E3\u6790\u5931\u8D25: ${reference.tuningPath}`,
1317
+ {
1318
+ trackId: track.id,
1319
+ cause: error
1320
+ }
1321
+ );
1322
+ }
1323
+ if (!isResolvedRecipeValue(resolvedValue)) {
1324
+ throw new EffectRecipeExecutorError(
1325
+ "tuning_resolution_failed",
1326
+ recipe.id,
1327
+ `tuningPath \u8FD4\u56DE\u4E86\u65E0\u6548\u503C: ${reference.tuningPath}`,
1328
+ {
1329
+ trackId: track.id
1330
+ }
1331
+ );
1332
+ }
1333
+ return resolvedValue;
1334
+ }
1335
+ };
1336
+ export {
1337
+ EFFECT_PRIMITIVE_CATEGORIES,
1338
+ EFFECT_PRIMITIVE_PARAM_TYPES,
1339
+ EFFECT_PRIMITIVE_PREVIEW_SUPPORT,
1340
+ EFFECT_RECIPE_APP_ICON_ID_MAX_LENGTH,
1341
+ EFFECT_RECIPE_APP_LABEL_MAX_CODE_POINTS,
1342
+ EFFECT_RECIPE_APP_ORDER_MAX,
1343
+ EFFECT_RECIPE_APP_SHORT_DESCRIPTION_MAX_CODE_POINTS,
1344
+ EFFECT_RECIPE_CATEGORIES,
1345
+ EFFECT_RECIPE_DESCRIPTION_MAX_CODE_POINTS,
1346
+ EFFECT_RECIPE_ID_MAX_LENGTH,
1347
+ EFFECT_RECIPE_MAX_DURATION_MS,
1348
+ EFFECT_RECIPE_MAX_TRACKS,
1349
+ EFFECT_RECIPE_MIGRATIONS,
1350
+ EFFECT_RECIPE_NAME_MAX_CODE_POINTS,
1351
+ EFFECT_RECIPE_PRIMITIVE_ID_MAX_LENGTH,
1352
+ EFFECT_RECIPE_SCHEMA_VERSION,
1353
+ EFFECT_RECIPE_SCOPES,
1354
+ EFFECT_RECIPE_STABLE_ID_REGEX,
1355
+ EFFECT_RECIPE_TRACK_ID_MAX_LENGTH,
1356
+ EFFECT_RECIPE_TUNING_PATH_MAX_LENGTH,
1357
+ EFFECT_RECIPE_TUNING_PATH_REGEX,
1358
+ EFFECT_RECIPE_VALUE_MAX_ARRAY_LENGTH,
1359
+ EFFECT_RECIPE_VALUE_MAX_STRING_CODE_POINTS,
1360
+ EFFECT_TARGETS,
1361
+ EffectPrimitiveRegistry,
1362
+ EffectPrimitiveRuntimeRegistry,
1363
+ EffectRecipeExecutor,
1364
+ EffectRecipeExecutorError,
1365
+ EffectRecipeRegistry,
1366
+ EffectRecipeRegistryError,
1367
+ createEffectRecipeValidationError,
1368
+ effectRecipeMigrationRegistry,
1369
+ isEffectPrimitiveId,
1370
+ isEffectRecipeCategory,
1371
+ isEffectRecipeId,
1372
+ isEffectRecipeScope,
1373
+ isEffectRecipeTrackId,
1374
+ isEffectRecipeTuningPath,
1375
+ isEffectRecipeTuningPathReference,
1376
+ isResolvedRecipeValue,
1377
+ migrateEffectRecipeDefinition,
1378
+ resolveEffectRecipePrimitiveDescriptor,
1379
+ validateEffectRecipeDefinition,
1380
+ validateEffectRecipeManifest,
1381
+ validateEffectRecipeTrackParams,
1382
+ validateResolvedEffectRecipeTrackParams
1383
+ };
1384
+ //# sourceMappingURL=recipes.js.map