@call-me-sensei/toonlab 0.2.0 → 0.3.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 (205) hide show
  1. package/AGENTS.md +127 -4
  2. package/ATTRIBUTION.md +41 -3
  3. package/README.md +234 -34
  4. package/docs/characters.md +144 -0
  5. package/docs/debug-panel.md +126 -0
  6. package/docs/docs.css +589 -0
  7. package/docs/environment.md +186 -0
  8. package/docs/getting-started.md +180 -0
  9. package/docs/index.html +14 -0
  10. package/docs/lab-architecture.md +100 -0
  11. package/docs/lighting.md +825 -0
  12. package/docs/main.jsx +739 -0
  13. package/docs/mcp.md +97 -0
  14. package/docs/post-processing.md +98 -0
  15. package/docs/settings-reference.md +1878 -0
  16. package/docs/shader-constants.md +77 -0
  17. package/docs/sky.md +182 -0
  18. package/docs/style-labs.md +309 -0
  19. package/docs/texture-lab.md +135 -0
  20. package/docs/toon-shading.md +183 -0
  21. package/docs/tsl-conventions.md +167 -0
  22. package/docs/vegetation-sky.md +275 -0
  23. package/docs/water.md +430 -0
  24. package/docs/weather.md +200 -0
  25. package/docs/world-scale.md +111 -0
  26. package/mcp/server.mjs +610 -0
  27. package/mcp/style-lab-tools.mjs +371 -0
  28. package/mcp/vite-plugin.mjs +174 -0
  29. package/mcp/workspace.mjs +397 -0
  30. package/package.json +64 -5
  31. package/src/ambientfx/INTEGRATION.md +164 -0
  32. package/src/ambientfx/ambientFxPresets.js +83 -0
  33. package/src/ambientfx/ambientFxSettings.js +368 -0
  34. package/src/ambientfx/emitters.js +169 -0
  35. package/src/ambientfx/index.js +5 -0
  36. package/src/ambientfx/particleBackbone.js +493 -0
  37. package/src/ambientfx/stylizedAmbientFx.js +450 -0
  38. package/src/assetlib/ambientcg.js +117 -0
  39. package/src/assetlib/assetRef.js +91 -0
  40. package/src/assetlib/importedEntry.js +40 -0
  41. package/src/assetlib/index.js +23 -0
  42. package/src/assetlib/kaykit.js +192 -0
  43. package/src/assetlib/kaykitStaticIndex.js +700 -0
  44. package/src/assetlib/loadImported.js +143 -0
  45. package/src/assetlib/opensource3d.js +113 -0
  46. package/src/assetlib/polyhaven.js +182 -0
  47. package/src/assetlib/polypizza.js +115 -0
  48. package/src/assetlib/smithsonian.js +214 -0
  49. package/src/assetlib/sources.js +279 -0
  50. package/src/assetlib/zip.js +58 -0
  51. package/src/biome/biomeGenerator.js +385 -0
  52. package/src/biome/biomeRuntime.js +299 -0
  53. package/src/biome/index.js +2 -0
  54. package/src/buildinggen/buildingAsset.js +44 -0
  55. package/src/buildinggen/buildingGrammar.js +311 -0
  56. package/src/buildinggen/buildingMesh.js +451 -0
  57. package/src/buildinggen/buildingPresets.js +46 -0
  58. package/src/buildinggen/buildingRecipe.js +100 -0
  59. package/src/buildinggen/buildingSettings.js +238 -0
  60. package/src/buildinggen/index.js +6 -0
  61. package/src/camera/cameraDirector.js +157 -0
  62. package/src/camera/cameraGenerator.js +367 -0
  63. package/src/camera/cameraRig.js +570 -0
  64. package/src/camera/cameraSettings.js +236 -0
  65. package/src/camera/index.js +7 -0
  66. package/src/catalog/builtinEntries.js +245 -0
  67. package/src/catalog/catalog.js +210 -0
  68. package/src/catalog/index.js +3 -0
  69. package/src/catalog/manifest.js +84 -0
  70. package/src/core/generation.js +529 -0
  71. package/src/environment/environmentRigs.js +21 -1
  72. package/src/environment/environmentSettings.js +4 -0
  73. package/src/environment/environmentSunShadowPass.js +8 -0
  74. package/src/fauna/INTEGRATION.md +174 -0
  75. package/src/fauna/boids.js +861 -0
  76. package/src/fauna/faunaBodies.js +492 -0
  77. package/src/fauna/faunaPresets.js +52 -0
  78. package/src/fauna/faunaSettings.js +525 -0
  79. package/src/fauna/index.js +5 -0
  80. package/src/fauna/stylizedFauna.js +395 -0
  81. package/src/game-feel/gameFeelGenerator.js +402 -0
  82. package/src/game-feel/gameFeelRuntime.js +549 -0
  83. package/src/game-feel/index.js +2 -0
  84. package/src/index.js +17 -6
  85. package/src/lighting/colorIntensity.js +177 -0
  86. package/src/lighting/index.js +161 -0
  87. package/src/lighting/lightDescriptors.js +249 -0
  88. package/src/lighting/lightingCapabilities.js +79 -0
  89. package/src/lighting/lightingDocuments.js +247 -0
  90. package/src/lighting/lightingFixtures.js +446 -0
  91. package/src/lighting/lightingGenerator.js +449 -0
  92. package/src/lighting/lightingPresets.js +319 -0
  93. package/src/lighting/lightingRuntime.js +723 -0
  94. package/src/lighting/lightingStyle.js +386 -0
  95. package/src/lighting/lightingSystem.js +774 -0
  96. package/src/lighting/unrealExport.js +186 -0
  97. package/src/lighting/utils.js +87 -0
  98. package/src/motion/index.js +5 -0
  99. package/src/motion/motionClip.js +441 -0
  100. package/src/motion/motionController.js +628 -0
  101. package/src/motion/motionDocuments.js +225 -0
  102. package/src/motion/motionGraph.js +307 -0
  103. package/src/motion/motionSettings.js +222 -0
  104. package/src/pathgen/index.js +7 -0
  105. package/src/pathgen/pathBridge.js +232 -0
  106. package/src/pathgen/pathPresets.js +35 -0
  107. package/src/pathgen/pathRibbon.js +410 -0
  108. package/src/pathgen/pathRouter.js +380 -0
  109. package/src/pathgen/pathSettings.js +335 -0
  110. package/src/pathgen/pathTextures.js +123 -0
  111. package/src/pathgen/stylizedPaths.js +453 -0
  112. package/src/post/index.js +1 -0
  113. package/src/post/postGenerator.js +177 -0
  114. package/src/post/postProcessing.js +41 -0
  115. package/src/propgen/generatorsWave1.js +379 -0
  116. package/src/propgen/generatorsWave2.js +462 -0
  117. package/src/propgen/index.js +5 -0
  118. package/src/propgen/propAsset.js +323 -0
  119. package/src/propgen/propParts.js +170 -0
  120. package/src/propgen/propPlacement.js +459 -0
  121. package/src/propgen/propPresets.js +82 -0
  122. package/src/propgen/propSettings.js +395 -0
  123. package/src/shaders-tsl/chunks/projected-water-caustics.js +242 -0
  124. package/src/shaders-tsl/chunks/vegetation-style.js +360 -0
  125. package/src/shaders-tsl/chunks/water-shore-state.js +31 -0
  126. package/src/shaders-tsl/chunks/water-waves.js +90 -11
  127. package/src/shaders-tsl/environment.js +15 -1
  128. package/src/shaders-tsl/flower.js +279 -30
  129. package/src/shaders-tsl/grass.js +60 -33
  130. package/src/shaders-tsl/sky.js +125 -31
  131. package/src/shaders-tsl/tree-leaf.js +61 -25
  132. package/src/shaders-tsl/water-breaker.js +7 -4
  133. package/src/shaders-tsl/water-shore-state-simulation.js +523 -0
  134. package/src/shaders-tsl/water.js +439 -49
  135. package/src/shaders-tsl/woody-surface.js +154 -0
  136. package/src/sky/sceneOverrideLayers.js +10 -0
  137. package/src/sky/skyQuality.js +26 -0
  138. package/src/sky/stylizedSky.js +753 -45
  139. package/src/soundscape/index.js +4 -0
  140. package/src/soundscape/soundscapeGenerator.js +179 -0
  141. package/src/soundscape/soundscapeRuntime.js +806 -0
  142. package/src/soundscape/soundscapeSettings.js +292 -0
  143. package/src/styles/index.js +13 -0
  144. package/src/styles/styleBundle.js +325 -0
  145. package/src/stylizedTerrain.js +32 -2
  146. package/src/stylizedWorld.js +423 -20
  147. package/src/texgen/evaluateTexture.js +675 -0
  148. package/src/texgen/index.js +60 -0
  149. package/src/texgen/noise2.js +210 -0
  150. package/src/texgen/textureAi.js +436 -0
  151. package/src/texgen/textureGenerators.js +516 -0
  152. package/src/texgen/texturePresets.js +490 -0
  153. package/src/texgen/textureSettings.js +342 -0
  154. package/src/texgen/textureThree.js +59 -0
  155. package/src/vegetation/flowerSpecies.js +15 -3
  156. package/src/vegetation/grassPalettes.js +153 -0
  157. package/src/vegetation/index.js +6 -0
  158. package/src/vegetation/stylizedBush.js +2 -0
  159. package/src/vegetation/stylizedFlower.js +82 -0
  160. package/src/vegetation/stylizedFlowers.js +48 -7
  161. package/src/vegetation/stylizedForest.js +29 -1
  162. package/src/vegetation/stylizedGrass.js +291 -56
  163. package/src/vegetation/stylizedTree.js +38 -2
  164. package/src/vegetation/stylizedTreeFoliage.js +2 -1
  165. package/src/vegetation/vegetationShaders.js +1110 -0
  166. package/src/vfxgen/INTEGRATION.md +145 -0
  167. package/src/vfxgen/core/burstBackbone.js +380 -0
  168. package/src/vfxgen/core/projectileCore.js +92 -0
  169. package/src/vfxgen/core/spriteShapes.js +98 -0
  170. package/src/vfxgen/core/trailRibbon.js +272 -0
  171. package/src/vfxgen/core/vfxRandom.js +29 -0
  172. package/src/vfxgen/effects/emitHelpers.js +37 -0
  173. package/src/vfxgen/effects/magicEffects.js +162 -0
  174. package/src/vfxgen/effects/movementEffects.js +87 -0
  175. package/src/vfxgen/effects/weaponEffects.js +118 -0
  176. package/src/vfxgen/index.js +18 -0
  177. package/src/vfxgen/moves/moveController.js +146 -0
  178. package/src/vfxgen/moves/moveLibrary.js +335 -0
  179. package/src/vfxgen/vfxPresets.js +98 -0
  180. package/src/vfxgen/vfxSettings.js +384 -0
  181. package/src/vfxgen/vfxSystem.js +449 -0
  182. package/src/vfxgen/weapons/stylizedWeapons.js +137 -0
  183. package/src/villagegen/index.js +4 -0
  184. package/src/villagegen/stylizedVillage.js +490 -0
  185. package/src/villagegen/villageArchetypes.js +160 -0
  186. package/src/villagegen/villageNames.js +40 -0
  187. package/src/villagegen/villageSites.js +105 -0
  188. package/src/water/sceneOverrideLayers.js +23 -0
  189. package/src/water/water.js +5 -0
  190. package/src/water/waterBreakerSystem.js +15 -1
  191. package/src/water/waterCurrentField.js +447 -0
  192. package/src/water/waterMaterial.js +12 -0
  193. package/src/water/waterNearshorePhase.js +320 -0
  194. package/src/water/waterScenePasses.js +83 -30
  195. package/src/water/waterSettings.js +325 -13
  196. package/src/water/waterShoreMaterial.js +322 -0
  197. package/src/water/waterShoreStateField.js +605 -0
  198. package/src/water/waterSurface.js +797 -28
  199. package/src/weather/index.js +6 -0
  200. package/src/weather/weatherPrecipitation.js +221 -0
  201. package/src/weather/weatherPresets.js +258 -0
  202. package/src/weather/weatherSettings.js +269 -0
  203. package/src/weather/weatherSystem.js +871 -0
  204. package/src/worldMinimap.js +62 -0
  205. package/src/worldPresets.js +4 -1
@@ -0,0 +1,186 @@
1
+ import { resolveLightColor } from './colorIntensity.js';
2
+ import { createLightingRecipe } from './lightingDocuments.js';
3
+ import { cloneJson, finite, isPlainObject } from './utils.js';
4
+
5
+ export const UNREAL_LIGHTING_MANIFEST_TYPE = 'toonlab/unreal-lighting-manifest';
6
+ export const UNREAL_LIGHTING_MANIFEST_SCHEMA_VERSION = 1;
7
+
8
+ const UNREAL_CLASS_BY_TYPE = Object.freeze({
9
+ ambient: 'SkyLight',
10
+ directional: 'DirectionalLight',
11
+ discArea: 'RectLight',
12
+ hemisphere: 'SkyLight',
13
+ point: 'PointLight',
14
+ rectArea: 'RectLight',
15
+ spot: 'SpotLight',
16
+ tubeArea: 'RectLight',
17
+ });
18
+
19
+ const LOCAL_TYPES = new Set(['point', 'spot', 'rectArea', 'discArea', 'tubeArea']);
20
+
21
+ function normalizeIntent(value, fallback = 'prefer') {
22
+ if (value === true) return 'prefer';
23
+ if (value === false) return 'disabled';
24
+ return ['disabled', 'prefer', 'require'].includes(value) ? value : fallback;
25
+ }
26
+
27
+ /** Converts Three.js Y-up meters to Unreal Z-up centimeters. */
28
+ export function threePositionToUnreal(position, worldScale = 100) {
29
+ const [x = 0, y = 0, z = 0] = Array.isArray(position) ? position.map(Number) : [];
30
+ const scale = Math.max(finite(worldScale, 100), 0.001);
31
+ return [-z * scale, x * scale, y * scale];
32
+ }
33
+
34
+ function directionFromPoints(position, target) {
35
+ const start = threePositionToUnreal(position, 1);
36
+ const end = threePositionToUnreal(target, 1);
37
+ const direction = end.map((value, index) => value - start[index]);
38
+ const length = Math.hypot(...direction) || 1;
39
+ return direction.map((value) => value / length);
40
+ }
41
+
42
+ function intensityMapping(descriptor) {
43
+ const nativeUnits = {
44
+ directional: ['lux'],
45
+ discArea: ['lumens', 'candela', 'ev'],
46
+ point: ['lumens', 'candela', 'ev'],
47
+ rectArea: ['lumens', 'candela', 'ev'],
48
+ spot: ['lumens', 'candela', 'ev'],
49
+ tubeArea: ['lumens', 'candela', 'ev'],
50
+ }[descriptor.type] ?? ['unitless'];
51
+ return {
52
+ artisticMultiplier: descriptor.intensity.artisticMultiplier,
53
+ conversionRequired: !nativeUnits.includes(descriptor.intensity.unit),
54
+ nativeUnitOptions: nativeUnits,
55
+ referenceDistanceMeters: descriptor.intensity.referenceDistance,
56
+ sourceUnit: descriptor.intensity.unit,
57
+ sourceValue: descriptor.intensity.value,
58
+ };
59
+ }
60
+
61
+ function mapLight(descriptor, options) {
62
+ const isLocal = LOCAL_TYPES.has(descriptor.type);
63
+ const warnings = [];
64
+ if (descriptor.type === 'ambient') warnings.push('AmbientLight maps approximately to a SkyLight intensity contribution.');
65
+ if (descriptor.type === 'hemisphere') warnings.push('HemisphereLight maps approximately to SkyLight upper/lower hemisphere settings.');
66
+ if (descriptor.type === 'discArea') warnings.push('Disc shape intent requires adapter-side RectLight/source-shape tuning.');
67
+ if (descriptor.type === 'tubeArea') warnings.push('Tube shape intent requires adapter-side RectLight/source-width tuning.');
68
+ if (descriptor.cookie) warnings.push('Cookie reference requires generation or assignment of an Unreal Light Function material.');
69
+ if (descriptor.layers.some((layer) => layer > 2)) warnings.push('Unreal exposes only three standard Lighting Channels; higher Three.js layers need adapter policy.');
70
+
71
+ return {
72
+ color: {
73
+ resolvedSrgb: resolveLightColor(descriptor.color),
74
+ temperatureKelvin: descriptor.color.temperatureKelvin,
75
+ tint: cloneJson(descriptor.color.tint),
76
+ useTemperature: descriptor.color.temperatureKelvin !== null,
77
+ },
78
+ enabled: descriptor.enabled,
79
+ id: descriptor.id,
80
+ intensity: intensityMapping(descriptor),
81
+ lightFunction: descriptor.cookie ? cloneJson(descriptor.cookie) : null,
82
+ lightLinking: {
83
+ excludeTags: cloneJson(descriptor.linking.excludeTags),
84
+ includeTags: cloneJson(descriptor.linking.includeTags),
85
+ lightingChannels: [0, 1, 2].map((channel) => descriptor.layers.includes(channel)),
86
+ },
87
+ megaLights: {
88
+ eligibleIntent: isLocal,
89
+ requested: options.megaLights !== 'disabled' && isLocal,
90
+ validation: isLocal ? 'Validate material, translucency, and platform support in Unreal.' : 'Not a local-light MegaLights mapping.',
91
+ },
92
+ name: descriptor.name,
93
+ photometricProfile: descriptor.ies ? cloneJson(descriptor.ies) : null,
94
+ shape: {
95
+ angleRadians: descriptor.angle,
96
+ attenuationRadiusMeters: descriptor.distance,
97
+ decay: descriptor.decay,
98
+ heightMeters: descriptor.height,
99
+ penumbra: descriptor.penumbra,
100
+ sourceIntent: descriptor.type,
101
+ widthMeters: descriptor.width,
102
+ },
103
+ shadow: {
104
+ bias: descriptor.shadow.bias,
105
+ castShadow: descriptor.castShadow && descriptor.shadow.enabled,
106
+ mapSizeIntent: descriptor.shadow.mapSize,
107
+ normalBias: descriptor.shadow.normalBias,
108
+ priority: descriptor.shadow.priority,
109
+ },
110
+ toon: cloneJson(descriptor.artistic),
111
+ toonlabType: descriptor.type,
112
+ transform: {
113
+ direction: directionFromPoints(descriptor.position, descriptor.target),
114
+ locationCm: threePositionToUnreal(descriptor.position, options.worldScale),
115
+ targetCm: threePositionToUnreal(descriptor.target, options.worldScale),
116
+ },
117
+ unrealClass: UNREAL_CLASS_BY_TYPE[descriptor.type],
118
+ warnings,
119
+ };
120
+ }
121
+
122
+ /**
123
+ * Exports a data-only Unreal Engine 5.8 handoff manifest.
124
+ *
125
+ * It does not generate `.uasset` files and does not implement MegaLights or
126
+ * Lumen. An Unreal editor/plugin adapter must validate and realize the intent.
127
+ */
128
+ export function exportLightingRecipeToUnreal58(recipeOptions, exportOptions = {}) {
129
+ const recipe = createLightingRecipe(recipeOptions);
130
+ const source = isPlainObject(exportOptions) ? exportOptions : {};
131
+ const options = {
132
+ lumen: normalizeIntent(source.lumen, 'prefer'),
133
+ megaLights: normalizeIntent(source.megaLights, 'prefer'),
134
+ worldScale: Math.max(finite(source.worldScale, 100), 0.001),
135
+ };
136
+ const lights = recipe.lights.map((descriptor) => mapLight(descriptor, options));
137
+ const warnings = [
138
+ 'This manifest carries authoring intent only; Unreal must create and validate engine assets.',
139
+ 'MegaLights is an Unreal renderer feature, not implemented by ToonLab or this export.',
140
+ ];
141
+ if (options.megaLights !== 'disabled' && lights.every((light) => !light.megaLights.eligibleIntent)) {
142
+ warnings.push('The recipe contains no local light types marked as MegaLights-eligible intent.');
143
+ }
144
+
145
+ return {
146
+ coordinateSystem: {
147
+ handedness: 'Unreal left-handed',
148
+ mapping: 'UnrealXYZcm = [-ThreeZ, ThreeX, ThreeY] * worldScale',
149
+ sourceUnits: 'meters',
150
+ worldScale: options.worldScale,
151
+ },
152
+ engine: {
153
+ name: 'Unreal Engine',
154
+ targetVersion: '5.8',
155
+ },
156
+ lights,
157
+ rendererIntent: {
158
+ lumen: {
159
+ intent: options.lumen,
160
+ scope: 'global illumination and reflections',
161
+ },
162
+ megaLights: {
163
+ implementation: 'unreal-native',
164
+ intent: options.megaLights,
165
+ scope: 'eligible local direct lights',
166
+ },
167
+ },
168
+ schemaVersion: UNREAL_LIGHTING_MANIFEST_SCHEMA_VERSION,
169
+ source: {
170
+ recipeId: recipe.id,
171
+ recipeName: recipe.name,
172
+ recipeSchemaVersion: recipe.schemaVersion,
173
+ shadowPolicy: cloneJson(recipe.shadowPolicy),
174
+ },
175
+ type: UNREAL_LIGHTING_MANIFEST_TYPE,
176
+ warnings,
177
+ };
178
+ }
179
+
180
+ /** Serializes a previously generated Unreal lighting manifest. */
181
+ export function serializeUnrealLightingManifest(manifest, { pretty = false } = {}) {
182
+ if (!isPlainObject(manifest) || manifest.type !== UNREAL_LIGHTING_MANIFEST_TYPE) {
183
+ throw new Error(`Unreal lighting manifest type must be "${UNREAL_LIGHTING_MANIFEST_TYPE}".`);
184
+ }
185
+ return JSON.stringify(manifest, null, pretty ? 2 : undefined);
186
+ }
@@ -0,0 +1,87 @@
1
+ // Small dependency-free helpers shared by the lighting document, preset, and
2
+ // runtime modules. Lighting recipes stay plain JSON; Three.js objects only
3
+ // enter the system in the realization layer.
4
+
5
+ export const clamp = (value, min, max) => Math.min(Math.max(Number(value), min), max);
6
+
7
+ export function finite(value, fallback = 0) {
8
+ const number = Number(value);
9
+ return Number.isFinite(number) ? number : fallback;
10
+ }
11
+
12
+ export function isPlainObject(value) {
13
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
14
+ const prototype = Object.getPrototypeOf(value);
15
+ return prototype === Object.prototype || prototype === null;
16
+ }
17
+
18
+ export function vector(value, fallback, size = fallback.length) {
19
+ if (!Array.isArray(value) || value.length < size) return [...fallback];
20
+ const next = value.slice(0, size).map(Number);
21
+ return next.every(Number.isFinite) ? next : [...fallback];
22
+ }
23
+
24
+ export function slug(value, fallback = 'lighting') {
25
+ const normalized = String(value ?? '')
26
+ .trim()
27
+ .toLowerCase()
28
+ .replace(/[^a-z0-9]+/g, '-')
29
+ .replace(/^-+|-+$/g, '');
30
+ return normalized || fallback;
31
+ }
32
+
33
+ /** Deep-clones a JSON-compatible value without requiring structuredClone. */
34
+ export function cloneJson(value) {
35
+ if (Array.isArray(value)) return value.map(cloneJson);
36
+ if (isPlainObject(value)) {
37
+ return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, cloneJson(entry)]));
38
+ }
39
+ return value;
40
+ }
41
+
42
+ /** Recursively freezes an object registry exposed as a public constant. */
43
+ export function deepFreeze(value) {
44
+ if (!value || typeof value !== 'object' || Object.isFrozen(value)) return value;
45
+ Object.freeze(value);
46
+ for (const entry of Object.values(value)) deepFreeze(entry);
47
+ return value;
48
+ }
49
+
50
+ export function mergePlain(base, overrides) {
51
+ const output = cloneJson(isPlainObject(base) ? base : {});
52
+ if (!isPlainObject(overrides)) return output;
53
+ for (const [key, value] of Object.entries(overrides)) {
54
+ output[key] = isPlainObject(value) && isPlainObject(output[key])
55
+ ? mergePlain(output[key], value)
56
+ : cloneJson(value);
57
+ }
58
+ return output;
59
+ }
60
+
61
+ export function uniqueId(preferred, used, fallbackPrefix = 'light') {
62
+ const base = slug(preferred, fallbackPrefix);
63
+ if (!used.has(base)) {
64
+ used.add(base);
65
+ return base;
66
+ }
67
+ let suffix = 2;
68
+ while (used.has(`${base}-${suffix}`)) suffix += 1;
69
+ const id = `${base}-${suffix}`;
70
+ used.add(id);
71
+ return id;
72
+ }
73
+
74
+ export function createValidationResult(errors, warnings = []) {
75
+ const valid = errors.length === 0;
76
+ return Object.freeze({
77
+ errors: Object.freeze([...errors]),
78
+ ok: valid,
79
+ valid,
80
+ warnings: Object.freeze([...warnings]),
81
+ });
82
+ }
83
+
84
+ export function formatValidationErrors(label, result) {
85
+ const details = result.errors.map((entry) => `${entry.path}: ${entry.message}`).join('; ');
86
+ return `${label} is invalid${details ? `: ${details}` : '.'}`;
87
+ }
@@ -0,0 +1,5 @@
1
+ export * from './motionSettings.js';
2
+ export * from './motionGraph.js';
3
+ export * from './motionClip.js';
4
+ export * from './motionController.js';
5
+ export * from './motionDocuments.js';