@call-me-sensei/toonlab 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. package/ATTRIBUTION.md +40 -0
  2. package/LICENSE +21 -0
  3. package/README.md +125 -0
  4. package/package.json +97 -0
  5. package/src/character/characterRig.js +353 -0
  6. package/src/character/freestyleSwimClip.js +541 -0
  7. package/src/character/index.js +4 -0
  8. package/src/character/modelLoader.js +433 -0
  9. package/src/core/materialRoles.js +438 -0
  10. package/src/core/presetDocuments.js +110 -0
  11. package/src/core/shaderBackend.js +24 -0
  12. package/src/debrisgen/debrisFields.js +544 -0
  13. package/src/debrisgen/debrisGenerator.js +1929 -0
  14. package/src/debrisgen/debrisPalettes.js +71 -0
  15. package/src/debrisgen/debrisPhysics.js +198 -0
  16. package/src/debrisgen/debrisPresets.js +155 -0
  17. package/src/debrisgen/debrisSettings.js +333 -0
  18. package/src/debrisgen/debrisTextures.js +380 -0
  19. package/src/debrisgen/index.js +5 -0
  20. package/src/debug/fieldValues.js +95 -0
  21. package/src/debug/index.js +7 -0
  22. package/src/debug/settingsPanel.js +170 -0
  23. package/src/environment/environmentAmbientProbe.js +160 -0
  24. package/src/environment/environmentMaterialAdapter.js +336 -0
  25. package/src/environment/environmentMaterialClassifier.js +178 -0
  26. package/src/environment/environmentPlanarReflection.js +201 -0
  27. package/src/environment/environmentPresets.js +443 -0
  28. package/src/environment/environmentRigs.js +684 -0
  29. package/src/environment/environmentSettings.js +504 -0
  30. package/src/environment/environmentShaderMaterials.js +318 -0
  31. package/src/environment/environmentSunShadowPass.js +268 -0
  32. package/src/environment/environmentTextureResolver.js +161 -0
  33. package/src/environment/environmentTimeOfDay.js +167 -0
  34. package/src/environment/environmentVertexAo.js +188 -0
  35. package/src/environment/index.js +8 -0
  36. package/src/environment/scanAssetStylize.js +103 -0
  37. package/src/index.js +21 -0
  38. package/src/loaders/index.js +1 -0
  39. package/src/post/index.js +2 -0
  40. package/src/post/postProcessing.js +1087 -0
  41. package/src/rockgen/export/glbExport.js +177 -0
  42. package/src/rockgen/heightfield/heightfieldErosion.js +7 -0
  43. package/src/rockgen/heightfield/heightfieldPatch.js +183 -0
  44. package/src/rockgen/heightfield/stylizedErosionSim.js +356 -0
  45. package/src/rockgen/index.js +14 -0
  46. package/src/rockgen/mesh/meshAttributes.js +374 -0
  47. package/src/rockgen/mesh/meshDocument.js +158 -0
  48. package/src/rockgen/mesh/surfaceNets.js +329 -0
  49. package/src/rockgen/noise/cellularNoise3.js +92 -0
  50. package/src/rockgen/noise/prng.js +62 -0
  51. package/src/rockgen/noise/simplexNoise3.js +89 -0
  52. package/src/rockgen/noise/valueNoise3.js +87 -0
  53. package/src/rockgen/rockDocument.js +255 -0
  54. package/src/rockgen/rockHelpers.js +8 -0
  55. package/src/rockgen/rockgenPresets.js +559 -0
  56. package/src/rockgen/rockgenSettings.js +756 -0
  57. package/src/rockgen/sdf/fieldCompiler.js +544 -0
  58. package/src/rockgen/sdf/sculptEdits.js +66 -0
  59. package/src/rockgen/sdf/sdfModifiers.js +45 -0
  60. package/src/rockgen/sdf/sdfOps.js +37 -0
  61. package/src/rockgen/sdf/sdfPrimitives.js +88 -0
  62. package/src/shaders-tsl/anime.js +963 -0
  63. package/src/shaders-tsl/chunks/character-color.js +64 -0
  64. package/src/shaders-tsl/chunks/character-highlights.js +174 -0
  65. package/src/shaders-tsl/chunks/character-lighting.js +309 -0
  66. package/src/shaders-tsl/chunks/character-material-maps.js +146 -0
  67. package/src/shaders-tsl/chunks/character-roles.js +52 -0
  68. package/src/shaders-tsl/chunks/character-scene-lights.js +270 -0
  69. package/src/shaders-tsl/chunks/character-shadow-color.js +61 -0
  70. package/src/shaders-tsl/chunks/character-skinning.js +140 -0
  71. package/src/shaders-tsl/chunks/environment-color.js +47 -0
  72. package/src/shaders-tsl/chunks/environment-debug.js +79 -0
  73. package/src/shaders-tsl/chunks/environment-lighting.js +260 -0
  74. package/src/shaders-tsl/chunks/environment-sun-shadow.js +87 -0
  75. package/src/shaders-tsl/chunks/foliage-fog.js +60 -0
  76. package/src/shaders-tsl/chunks/pass-depth-color.js +89 -0
  77. package/src/shaders-tsl/chunks/stylized-cloud-shadow.js +70 -0
  78. package/src/shaders-tsl/chunks/water-color.js +190 -0
  79. package/src/shaders-tsl/chunks/water-common.js +124 -0
  80. package/src/shaders-tsl/chunks/water-foam.js +93 -0
  81. package/src/shaders-tsl/chunks/water-lighting.js +116 -0
  82. package/src/shaders-tsl/chunks/water-ripple.js +64 -0
  83. package/src/shaders-tsl/chunks/water-waves.js +100 -0
  84. package/src/shaders-tsl/environment-ao-overlay.js +60 -0
  85. package/src/shaders-tsl/environment.js +680 -0
  86. package/src/shaders-tsl/flower.js +267 -0
  87. package/src/shaders-tsl/grass.js +193 -0
  88. package/src/shaders-tsl/post-composite.js +461 -0
  89. package/src/shaders-tsl/sky.js +175 -0
  90. package/src/shaders-tsl/tree-leaf.js +264 -0
  91. package/src/shaders-tsl/water-breaker.js +423 -0
  92. package/src/shaders-tsl/water-kelp.js +171 -0
  93. package/src/shaders-tsl/water-rain.js +107 -0
  94. package/src/shaders-tsl/water-simulation.js +140 -0
  95. package/src/shaders-tsl/water-splash.js +298 -0
  96. package/src/shaders-tsl/water.js +509 -0
  97. package/src/sky/index.js +2 -0
  98. package/src/sky/stylizedSky.js +341 -0
  99. package/src/toon/characterRenderPasses.js +646 -0
  100. package/src/toon/index.js +4 -0
  101. package/src/toon/settings/alphaSettings.js +170 -0
  102. package/src/toon/settings/averageShadowSettings.js +181 -0
  103. package/src/toon/settings/baseTextureSettings.js +133 -0
  104. package/src/toon/settings/celShadeSettings.js +96 -0
  105. package/src/toon/settings/contactShadowSettings.js +114 -0
  106. package/src/toon/settings/eyeHighlightSettings.js +187 -0
  107. package/src/toon/settings/faceLightingSettings.js +135 -0
  108. package/src/toon/settings/furSettings.js +112 -0
  109. package/src/toon/settings/glitterSettings.js +102 -0
  110. package/src/toon/settings/hairHighlightSettings.js +325 -0
  111. package/src/toon/settings/indirectLightSettings.js +244 -0
  112. package/src/toon/settings/localLightSettings.js +176 -0
  113. package/src/toon/settings/materialMapSettings.js +299 -0
  114. package/src/toon/settings/outlineSettings.js +263 -0
  115. package/src/toon/settings/perspectiveRemovalSettings.js +60 -0
  116. package/src/toon/settings/rimLightSettings.js +268 -0
  117. package/src/toon/settings/sceneShadowSettings.js +147 -0
  118. package/src/toon/settings/selfShadowSettings.js +221 -0
  119. package/src/toon/settings/shadowColorSettings.js +238 -0
  120. package/src/toon/settings/skinToneSettings.js +150 -0
  121. package/src/toon/settings/specularSettings.js +314 -0
  122. package/src/toon/settings/stickerSettings.js +102 -0
  123. package/src/toon/toonMaterialAdapter.js +2235 -0
  124. package/src/toon/toonSettings.js +860 -0
  125. package/src/vegetation/flowerSpecies.js +260 -0
  126. package/src/vegetation/index.js +10 -0
  127. package/src/vegetation/stylizedBush.js +136 -0
  128. package/src/vegetation/stylizedFlower.js +213 -0
  129. package/src/vegetation/stylizedFlowers.js +297 -0
  130. package/src/vegetation/stylizedGrass.js +491 -0
  131. package/src/vegetation/stylizedTree.js +2449 -0
  132. package/src/vegetation/stylizedTreeFoliage.js +691 -0
  133. package/src/vegetation/treeExport.js +300 -0
  134. package/src/vegetation/treeRecipe.js +647 -0
  135. package/src/water/index.js +2 -0
  136. package/src/water/water.js +20 -0
  137. package/src/water/waterBreakerSystem.js +591 -0
  138. package/src/water/waterInteraction.js +193 -0
  139. package/src/water/waterMaterial.js +263 -0
  140. package/src/water/waterRain.js +136 -0
  141. package/src/water/waterRippleSimulation.js +227 -0
  142. package/src/water/waterScenePasses.js +415 -0
  143. package/src/water/waterSettings.js +1138 -0
  144. package/src/water/waterSplashSystem.js +251 -0
  145. package/src/water/waterSurface.js +475 -0
  146. package/src/water/waterVegetation.js +83 -0
@@ -0,0 +1,333 @@
1
+ // Canonical settings and field metadata for the procedural debris generator.
2
+ // The generator is intentionally texture-free: geometry, vertex color, and
3
+ // ToonLab's environment shader provide the final stylized read.
4
+
5
+ export const DEBRIS_TYPES = Object.freeze({
6
+ wood: Object.freeze({
7
+ label: 'Wood',
8
+ description: 'Driftwood, branches, twigs, planks, and sawn logs.',
9
+ icon: '🪵',
10
+ variants: Object.freeze([
11
+ Object.freeze({ id: 'driftwood', label: 'Driftwood' }),
12
+ Object.freeze({ id: 'branch', label: 'Fallen branch' }),
13
+ Object.freeze({ id: 'twigPile', label: 'Twig pile' }),
14
+ Object.freeze({ id: 'planks', label: 'Broken planks' }),
15
+ Object.freeze({ id: 'logs', label: 'Sawn logs' }),
16
+ Object.freeze({ id: 'rootStump', label: 'Root stump' }),
17
+ Object.freeze({ id: 'barkChips', label: 'Bark chips' }),
18
+ ]),
19
+ }),
20
+ bone: Object.freeze({
21
+ label: 'Bone',
22
+ description: 'Animal bones, weathered skulls, and antlers.',
23
+ icon: '🦴',
24
+ variants: Object.freeze([
25
+ Object.freeze({ id: 'longBone', label: 'Long bone' }),
26
+ Object.freeze({ id: 'skull', label: 'Skull' }),
27
+ Object.freeze({ id: 'jawBone', label: 'Jaw bone' }),
28
+ Object.freeze({ id: 'antler', label: 'Antler' }),
29
+ ]),
30
+ }),
31
+ stone: Object.freeze({
32
+ label: 'Stone & masonry',
33
+ description: 'Rubble, bricks, pebbles, obsidian, meteorites, and gems.',
34
+ icon: '🪨',
35
+ variants: Object.freeze([
36
+ Object.freeze({ id: 'rubble', label: 'Rubble' }),
37
+ Object.freeze({ id: 'bricks', label: 'Broken bricks' }),
38
+ Object.freeze({ id: 'shards', label: 'Stone shards' }),
39
+ Object.freeze({ id: 'riverstones', label: 'River stones' }),
40
+ Object.freeze({ id: 'obsidian', label: 'Obsidian shards' }),
41
+ Object.freeze({ id: 'meteor', label: 'Meteorites' }),
42
+ Object.freeze({ id: 'gems', label: 'Rough gems' }),
43
+ ]),
44
+ }),
45
+ metal: Object.freeze({
46
+ label: 'Metal',
47
+ description: 'Bent sheets, discarded cans, and mixed scrap.',
48
+ icon: '⚙️',
49
+ variants: Object.freeze([
50
+ Object.freeze({ id: 'sheets', label: 'Bent sheets' }),
51
+ Object.freeze({ id: 'cans', label: 'Crushed cans' }),
52
+ Object.freeze({ id: 'scrapPile', label: 'Scrap pile' }),
53
+ ]),
54
+ }),
55
+ organic: Object.freeze({
56
+ label: 'Ground litter',
57
+ description: 'Dry leaves, pinecones, and shoreline shells.',
58
+ icon: '🍂',
59
+ variants: Object.freeze([
60
+ Object.freeze({ id: 'leafLitter', label: 'Leaf litter' }),
61
+ Object.freeze({ id: 'pinecones', label: 'Pinecones' }),
62
+ Object.freeze({ id: 'shells', label: 'Shell scatter' }),
63
+ ]),
64
+ }),
65
+ ash: Object.freeze({
66
+ label: 'Ash & piles',
67
+ description: 'Ash mounds, campfires, charcoal, and sawdust heaps.',
68
+ icon: '🔥',
69
+ variants: Object.freeze([
70
+ Object.freeze({ id: 'ashPile', label: 'Ash pile' }),
71
+ Object.freeze({ id: 'campfire', label: 'Campfire remains' }),
72
+ Object.freeze({ id: 'charcoal', label: 'Charcoal scatter' }),
73
+ Object.freeze({ id: 'sawdust', label: 'Sawdust heap' }),
74
+ ]),
75
+ }),
76
+ });
77
+
78
+ const field = (key, label, min, max, step, caption) => Object.freeze({
79
+ caption, key, label, max, min, step,
80
+ });
81
+
82
+ export const DEBRIS_TYPE_FIELDS = Object.freeze({
83
+ wood: Object.freeze([
84
+ field('length', 'Length', 0.6, 4, 0.05, 'Overall length of each branch or log.'),
85
+ field('thickness', 'Thickness', 0.04, 0.45, 0.01, 'Radius of the main wood form.'),
86
+ field('crookedness', 'Crookedness', 0, 1, 0.05, 'Smooth bend and wandering in the grain.'),
87
+ field('kinks', 'Kinks', 0, 1, 0.05, 'Sharp elbow breaks at old fork points.'),
88
+ field('branchiness', 'Branchiness', 0, 1, 0.05, 'Frequency of secondary branches and roots.'),
89
+ field('splinters', 'Splinters', 0, 1, 0.05, 'Broken fibers and structural prongs at ends.'),
90
+ field('barkStripped', 'Bark stripped', 0, 1, 0.05, 'How much bark has weathered away to pale wood.'),
91
+ ]),
92
+ bone: Object.freeze([
93
+ field('length', 'Length', 0.5, 3.2, 0.05, 'Overall length or skull scale.'),
94
+ field('thickness', 'Shaft weight', 0.05, 0.35, 0.01, 'Thickness of bone shafts and plates.'),
95
+ field('jointSize', 'Joint size', 0.7, 1.8, 0.05, 'Size of knuckles, sockets, and crown forms.'),
96
+ field('curvature', 'Curvature', 0, 1, 0.05, 'Arc in shafts, jaws, and antler tines.'),
97
+ field('damage', 'Weathering', 0, 1, 0.05, 'Cracks, chips, and missing fragments.'),
98
+ ]),
99
+ stone: Object.freeze([
100
+ field('chunkSize', 'Size', 0.12, 1.3, 0.02, 'Average footprint of each piece.'),
101
+ field('sharpness', 'Angularity', 0, 1, 0.05, 'From worn round forms to hard fractured facets and tapers.'),
102
+ field('detail', 'Surface detail', 0, 1, 0.05, 'Cellular lumps, chips, crater depth, and corner breakup.'),
103
+ field('flatness', 'Flatness', 0, 1, 0.05, 'Squashes pieces toward plates and skipping stones.'),
104
+ field('banding', 'Banding', 0, 1, 0.05, 'Conchoidal ridges, strata lines, and gem facet bevels.'),
105
+ field('brickRatio', 'Brick mix', 0, 1, 0.05, 'Share of manufactured pieces in rubble.'),
106
+ ]),
107
+ metal: Object.freeze([
108
+ field('sheetSize', 'Piece size', 0.25, 1.8, 0.05, 'Size of sheets, cans, and pipe sections.'),
109
+ field('bend', 'Bend', 0, 1, 0.05, 'Warping and crushed deformation.'),
110
+ field('corrugation', 'Corrugation', 0, 1, 0.05, 'Folded ridges across sheet pieces.'),
111
+ field('rust', 'Rust', 0, 1, 0.05, 'Amount of oxidized accent color.'),
112
+ field('wireChance', 'Wire mix', 0, 1, 0.05, 'Frequency of curled wire and narrow rods.'),
113
+ ]),
114
+ organic: Object.freeze([
115
+ field('leafSize', 'Item size', 0.08, 0.65, 0.01, 'Size of leaves, cones, or shells.'),
116
+ field('curl', 'Curl', 0, 1, 0.05, 'How much thin items lift and fold.'),
117
+ field('coneRatio', 'Heavy pieces', 0, 1, 0.05, 'Mix of cones, nuts, or thicker shells.'),
118
+ field('dryness', 'Dryness', 0, 1, 0.05, 'Shifts the palette from fresh to brittle.'),
119
+ field('coverage', 'Coverage', 0.2, 1.8, 0.05, 'Density within the scatter footprint.'),
120
+ ]),
121
+ ash: Object.freeze([
122
+ field('moundHeight', 'Mound height', 0.05, 0.8, 0.01, 'Vertical buildup of fine ash.'),
123
+ field('footprint', 'Footprint', 0.3, 2.5, 0.05, 'Width of the burned area.'),
124
+ field('charcoal', 'Charcoal', 0, 1, 0.05, 'Share of dark burned fragments.'),
125
+ field('embers', 'Ember flecks', 0, 1, 0.05, 'Warm accents among the cold remains.'),
126
+ field('rim', 'Scorched rim', 0, 1, 0.05, 'Darkened material around the perimeter.'),
127
+ ]),
128
+ });
129
+
130
+ export const DEBRIS_ARRANGEMENTS = Object.freeze([
131
+ Object.freeze({ caption: 'Loose natural spread', id: 'scatter', label: 'Scatter' }),
132
+ Object.freeze({ caption: 'Aligned pieces gathered side by side', id: 'bundle', label: 'Bundle' }),
133
+ Object.freeze({ caption: 'Dense stacked mound', id: 'heap', label: 'Heap' }),
134
+ Object.freeze({ caption: 'Flat ground coverage', id: 'patch', label: 'Patch' }),
135
+ ]);
136
+
137
+ export const DEBRIS_SCATTER_FIELDS = Object.freeze([
138
+ field('count', 'Pieces', 1, 48, 1, 'Number of primary generated pieces.'),
139
+ field('scale', 'Scale', 0.25, 3, 0.05, 'Uniform scale for the complete asset.'),
140
+ field('spread', 'Spread', 0, 4, 0.05, 'Horizontal footprint of the arrangement.'),
141
+ field('rotationJitter', 'Rotation', 0, 1, 0.05, 'Random rotation and tilt per piece.'),
142
+ field('messiness', 'Messiness', 0, 1, 0.05, 'Placement chaos: tilt, jitter, and piece-to-piece size variety. 0.5 is neutral.'),
143
+ field('damage', 'Damage', 0, 1, 0.05, 'Universal wear: splinters, chips, rust, cracks, and dryness. 0.5 is neutral.'),
144
+ ]);
145
+
146
+ export const DEBRIS_LOOK_FIELDS = Object.freeze([
147
+ field('variation', 'Color variation', 0, 0.65, 0.01, 'Per-piece and per-vertex color drift.'),
148
+ field('edgeLight', 'Edge light', 0, 1, 0.05, 'Lightens upward faces for a hand-painted read.'),
149
+ field('toonContrast', 'Toon contrast', 0, 1, 0.05, 'Depth of ToonLab environment shade bands.'),
150
+ field('roughness', 'Roughness', 0, 1, 0.05, 'Surface character retained in exported materials.'),
151
+ field('textureScale', 'Texture scale', 0.5, 4, 0.1, 'Tiling density of the detail texture.'),
152
+ ]);
153
+
154
+ export const DEFAULT_DEBRIS_SETTINGS = Object.freeze({
155
+ asset: Object.freeze({
156
+ arrangement: 'scatter',
157
+ count: 5,
158
+ damage: 0.5,
159
+ messiness: 0.5,
160
+ rotationJitter: 0.65,
161
+ scale: 1,
162
+ seed: 1827,
163
+ spread: 1.15,
164
+ type: 'wood',
165
+ variant: 'driftwood',
166
+ }),
167
+ shape: Object.freeze({
168
+ angularity: 0.7,
169
+ barkStripped: 0.3,
170
+ bend: 0.65,
171
+ branchiness: 0.42,
172
+ brickRatio: 0.4,
173
+ charcoal: 0.45,
174
+ chunkSize: 0.55,
175
+ coneRatio: 0.35,
176
+ corrugation: 0.32,
177
+ coverage: 1,
178
+ crookedness: 0.66,
179
+ curl: 0.48,
180
+ curvature: 0.34,
181
+ damage: 0.4,
182
+ dryness: 0.72,
183
+ embers: 0.08,
184
+ footprint: 1.3,
185
+ fracture: 0.58,
186
+ jointSize: 1.15,
187
+ kinks: 0.25,
188
+ leafSize: 0.25,
189
+ length: 1.8,
190
+ moundHeight: 0.28,
191
+ rim: 0.42,
192
+ rust: 0.65,
193
+ sheetSize: 0.85,
194
+ splinters: 0.5,
195
+ stacking: 0.35,
196
+ thickness: 0.16,
197
+ wireChance: 0.25,
198
+ }),
199
+ surface: Object.freeze({
200
+ accentColor: Object.freeze([0.68, 0.43, 0.2]),
201
+ customTexture: null,
202
+ edgeLight: 0.22,
203
+ primaryColor: Object.freeze([0.34, 0.2, 0.11]),
204
+ roughness: 0.9,
205
+ secondaryColor: Object.freeze([0.55, 0.36, 0.2]),
206
+ textureScale: 1,
207
+ textureStyle: 'auto',
208
+ toonContrast: 0.72,
209
+ variation: 0.18,
210
+ }),
211
+ });
212
+
213
+ export const DEBRIS_TYPE_DEFAULTS = Object.freeze({
214
+ wood: Object.freeze({
215
+ asset: Object.freeze({ count: 1, spread: 0, variant: 'driftwood' }),
216
+ shape: Object.freeze({ barkStripped: 0.35, branchiness: 0.42, crookedness: 0.66, kinks: 0.3, length: 1.8, splinters: 0.5, thickness: 0.16 }),
217
+ surface: Object.freeze({ accentColor: [0.7, 0.46, 0.24], primaryColor: [0.34, 0.2, 0.11], secondaryColor: [0.55, 0.36, 0.2] }),
218
+ }),
219
+ bone: Object.freeze({
220
+ asset: Object.freeze({ count: 5, spread: 1.3, variant: 'longBone' }),
221
+ shape: Object.freeze({ curvature: 0.25, damage: 0.42, jointSize: 1.15, length: 1.45, thickness: 0.13 }),
222
+ surface: Object.freeze({ accentColor: [0.42, 0.31, 0.19], primaryColor: [0.78, 0.71, 0.56], secondaryColor: [0.93, 0.87, 0.7] }),
223
+ }),
224
+ stone: Object.freeze({
225
+ asset: Object.freeze({ count: 12, spread: 1.5, variant: 'rubble' }),
226
+ shape: Object.freeze({ banding: 0.3, brickRatio: 0.35, chunkSize: 0.48, detail: 0.62, flatness: 0.3, sharpness: 0.78, stacking: 0.38 }),
227
+ surface: Object.freeze({ accentColor: [0.53, 0.3, 0.19], primaryColor: [0.39, 0.4, 0.38], secondaryColor: [0.58, 0.56, 0.51] }),
228
+ }),
229
+ metal: Object.freeze({
230
+ asset: Object.freeze({ count: 5, spread: 1.35, variant: 'sheets' }),
231
+ shape: Object.freeze({ bend: 0.68, corrugation: 0.32, rust: 0.64, sheetSize: 0.82, wireChance: 0.28 }),
232
+ surface: Object.freeze({ accentColor: [0.72, 0.26, 0.08], primaryColor: [0.25, 0.31, 0.32], secondaryColor: [0.49, 0.54, 0.52] }),
233
+ }),
234
+ organic: Object.freeze({
235
+ asset: Object.freeze({ count: 28, spread: 1.8, variant: 'leafLitter' }),
236
+ shape: Object.freeze({ coneRatio: 0.28, coverage: 1.1, curl: 0.54, dryness: 0.76, leafSize: 0.23 }),
237
+ surface: Object.freeze({ accentColor: [0.66, 0.23, 0.06], primaryColor: [0.34, 0.18, 0.05], secondaryColor: [0.64, 0.43, 0.12] }),
238
+ }),
239
+ ash: Object.freeze({
240
+ asset: Object.freeze({ count: 14, spread: 0.65, variant: 'ashPile' }),
241
+ shape: Object.freeze({ charcoal: 0.48, embers: 0.06, footprint: 1.35, moundHeight: 0.28, rim: 0.42 }),
242
+ surface: Object.freeze({ accentColor: [0.55, 0.06, 0.01], primaryColor: [0.035, 0.03, 0.025], secondaryColor: [0.16, 0.15, 0.14] }),
243
+ }),
244
+ });
245
+
246
+ function cloneValue(value) {
247
+ if (Array.isArray(value)) return [...value];
248
+ if (value && typeof value === 'object') return Object.fromEntries(
249
+ Object.entries(value).map(([key, entry]) => [key, cloneValue(entry)]),
250
+ );
251
+ return value;
252
+ }
253
+
254
+ export function cloneDebrisSettings(settings = DEFAULT_DEBRIS_SETTINGS) {
255
+ return cloneValue(settings);
256
+ }
257
+
258
+ // Legacy recipe migration: the 'masonry' type merged into 'stone'
259
+ // (2026-07); its angularity/fracture fields map onto the unified
260
+ // sharpness/detail sliders.
261
+ function migrateLegacyOverrides(overrides) {
262
+ if (overrides?.asset?.type !== 'masonry') return overrides;
263
+ const migrated = cloneValue(overrides);
264
+ migrated.asset.type = 'stone';
265
+ if (migrated.shape) {
266
+ if (migrated.shape.angularity !== undefined && migrated.shape.sharpness === undefined) {
267
+ migrated.shape.sharpness = migrated.shape.angularity;
268
+ }
269
+ if (migrated.shape.fracture !== undefined && migrated.shape.detail === undefined) {
270
+ migrated.shape.detail = migrated.shape.fracture;
271
+ }
272
+ }
273
+ return migrated;
274
+ }
275
+
276
+ export function createDebrisSettings(rawOverrides = {}) {
277
+ const overrides = migrateLegacyOverrides(rawOverrides);
278
+ const requestedType = overrides.asset?.type;
279
+ const type = DEBRIS_TYPES[requestedType] ? requestedType : (requestedType ? 'wood' : null);
280
+ const typeDefaults = type ? DEBRIS_TYPE_DEFAULTS[type] : null;
281
+ const result = cloneDebrisSettings(DEFAULT_DEBRIS_SETTINGS);
282
+ if (typeDefaults) {
283
+ Object.assign(result.asset, cloneValue(typeDefaults.asset));
284
+ Object.assign(result.shape, cloneValue(typeDefaults.shape));
285
+ Object.assign(result.surface, cloneValue(typeDefaults.surface));
286
+ }
287
+ Object.assign(result.asset, cloneValue(overrides.asset ?? {}));
288
+ Object.assign(result.shape, cloneValue(overrides.shape ?? {}));
289
+ Object.assign(result.surface, cloneValue(overrides.surface ?? {}));
290
+ result.asset.type = DEBRIS_TYPES[result.asset.type] ? result.asset.type : 'wood';
291
+ const variants = DEBRIS_TYPES[result.asset.type].variants;
292
+ if (!variants.some((entry) => entry.id === result.asset.variant)) {
293
+ result.asset.variant = variants[0].id;
294
+ }
295
+ if (!DEBRIS_ARRANGEMENTS.some((entry) => entry.id === result.asset.arrangement)) {
296
+ result.asset.arrangement = 'scatter';
297
+ }
298
+ result.asset.seed = Math.max(0, Math.round(Number(result.asset.seed) || 0)) >>> 0;
299
+ result.asset.count = Math.min(48, Math.max(1, Math.round(Number(result.asset.count) || 1)));
300
+ const custom = result.surface.customTexture;
301
+ result.surface.customTexture = custom && typeof custom.dataUrl === 'string'
302
+ ? { dataUrl: custom.dataUrl, name: String(custom.name ?? 'Custom texture') }
303
+ : null;
304
+ result.surface.textureStyle = typeof result.surface.textureStyle === 'string'
305
+ ? result.surface.textureStyle
306
+ : 'auto';
307
+ result.surface.textureScale = Math.min(4, Math.max(0.5, Number(result.surface.textureScale) || 1));
308
+ return result;
309
+ }
310
+
311
+ export function createDebrisRecipeDocument(settings, { name = 'Untitled debris' } = {}) {
312
+ return {
313
+ kind: 'toonlab.debrisRecipe',
314
+ name,
315
+ settings: createDebrisSettings(settings),
316
+ version: 1,
317
+ };
318
+ }
319
+
320
+ export function validateDebrisRecipeDocument(document) {
321
+ const errors = [];
322
+ if (!document || typeof document !== 'object') errors.push('Recipe must be an object.');
323
+ else {
324
+ if (document.kind !== 'toonlab.debrisRecipe') errors.push('Unknown recipe kind.');
325
+ if (document.version !== 1) errors.push('Unsupported recipe version.');
326
+ if (!document.settings?.asset) errors.push('Recipe settings are missing.');
327
+ const requestedType = document.settings?.asset?.type;
328
+ if (requestedType && requestedType !== 'masonry' && !DEBRIS_TYPES[requestedType]) {
329
+ errors.push(`Unknown debris type “${requestedType}”.`);
330
+ }
331
+ }
332
+ return { errors, ok: errors.length === 0 };
333
+ }