@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,236 @@
1
+ // Canonical, serializable settings for composable camera rigs. These values
2
+ // deliberately describe behaviour rather than named game genres: the named
3
+ // archetypes in cameraGenerator.js are only editable starting points.
4
+
5
+ export const DEFAULT_CAMERA_SETTINGS = Object.freeze({
6
+ follow: Object.freeze({
7
+ offset: Object.freeze([1.15, 2.8, 6.4]),
8
+ targetOffset: Object.freeze([0, 1.45, 0]),
9
+ yawOnly: true,
10
+ }),
11
+ framing: Object.freeze({
12
+ screenX: 0.5,
13
+ screenY: 0.54,
14
+ horizontalScale: 0.72,
15
+ verticalScale: 0.58,
16
+ }),
17
+ lens: Object.freeze({
18
+ fov: 48,
19
+ near: 0.1,
20
+ far: 1200,
21
+ speedFov: 3.5,
22
+ speedReference: 8,
23
+ }),
24
+ damping: Object.freeze({
25
+ position: 7.5,
26
+ aim: 10,
27
+ lens: 8,
28
+ teleportDistance: 35,
29
+ }),
30
+ lookAhead: Object.freeze({
31
+ enabled: true,
32
+ time: 0.32,
33
+ smoothing: 7,
34
+ maxDistance: 3.2,
35
+ horizontalOnly: true,
36
+ }),
37
+ collision: Object.freeze({
38
+ enabled: true,
39
+ radius: 0.28,
40
+ padding: 0.14,
41
+ minimumDistance: 0.75,
42
+ recoveryDamping: 5.5,
43
+ }),
44
+ noise: Object.freeze({
45
+ enabled: false,
46
+ seed: 1,
47
+ frequency: 0.8,
48
+ positionAmplitude: 0.025,
49
+ rotationAmplitude: 0.004,
50
+ octaves: 2,
51
+ persistence: 0.5,
52
+ lacunarity: 1.9,
53
+ }),
54
+ impulse: Object.freeze({
55
+ positionScale: 1,
56
+ rotationScale: 1,
57
+ decay: 12,
58
+ maxPosition: 1.25,
59
+ maxRotation: 0.18,
60
+ }),
61
+ });
62
+
63
+ export const CAMERA_SETTING_GROUPS = Object.freeze([
64
+ Object.freeze({ id: 'follow', label: 'Follow', description: 'Camera and aim offsets relative to the target.' }),
65
+ Object.freeze({ id: 'framing', label: 'Framing', description: 'Screen-space composition without baking a camera genre.' }),
66
+ Object.freeze({ id: 'lens', label: 'Lens', description: 'Perspective and speed-sensitive field of view.' }),
67
+ Object.freeze({ id: 'damping', label: 'Damping', description: 'Frame-rate-independent response rates.' }),
68
+ Object.freeze({ id: 'lookAhead', label: 'Look Ahead', description: 'Velocity prediction that remains independent of frame rate.' }),
69
+ Object.freeze({ id: 'collision', label: 'Collision', description: 'Optional host-query camera obstruction response.' }),
70
+ Object.freeze({ id: 'noise', label: 'Noise', description: 'Seeded, time-based procedural drift for handheld motion and environmental vibration.' }),
71
+ Object.freeze({ id: 'impulse', label: 'Impulse', description: 'Layered recoil and shake limits.' }),
72
+ ]);
73
+
74
+ const FIELD_DEFINITIONS = {
75
+ follow: {
76
+ offset: { label: 'Camera Offset', type: 'vector3', ranges: [[-8, 8, 0.05], [0, 12, 0.05], [0.2, 20, 0.05]], description: 'Right, up and backward offset in target-local meters.' },
77
+ targetOffset: { label: 'Target Offset', type: 'vector3', ranges: [[-5, 5, 0.05], [-2, 8, 0.05], [-5, 5, 0.05]], description: 'Aim point relative to the target origin.' },
78
+ yawOnly: { label: 'Yaw Only', type: 'boolean', description: 'Ignore target pitch and roll when rotating the follow offset.' },
79
+ },
80
+ framing: {
81
+ screenX: { label: 'Screen X', range: [0.1, 0.9, 0.01], description: 'Desired horizontal target position in normalized screen space.' },
82
+ screenY: { label: 'Screen Y', range: [0.1, 0.9, 0.01], description: 'Desired vertical target position in normalized screen space.' },
83
+ horizontalScale: { label: 'Horizontal Strength', range: [0, 2, 0.01], description: 'World-space strength of horizontal composition.' },
84
+ verticalScale: { label: 'Vertical Strength', range: [0, 2, 0.01], description: 'World-space strength of vertical composition.' },
85
+ },
86
+ lens: {
87
+ fov: { label: 'Field of View', range: [15, 110, 0.1], unit: '°', description: 'Vertical perspective field of view.' },
88
+ near: { label: 'Near Plane', range: [0.01, 5, 0.01], unit: 'm', description: 'Near clipping plane.' },
89
+ far: { label: 'Far Plane', range: [50, 10000, 10], unit: 'm', description: 'Far clipping plane.' },
90
+ speedFov: { label: 'Speed FOV', range: [-10, 30, 0.1], unit: '°', description: 'Additional FOV at reference speed.' },
91
+ speedReference: { label: 'Reference Speed', range: [0.1, 50, 0.1], unit: 'm/s', description: 'Speed at which Speed FOV is fully applied.' },
92
+ },
93
+ damping: {
94
+ position: { label: 'Position Response', range: [0, 40, 0.1], description: 'Exponential position response; zero snaps.' },
95
+ aim: { label: 'Aim Response', range: [0, 40, 0.1], description: 'Exponential aim response; zero snaps.' },
96
+ lens: { label: 'Lens Response', range: [0, 40, 0.1], description: 'Exponential field-of-view response; zero snaps.' },
97
+ teleportDistance: { label: 'Teleport Threshold', range: [1, 500, 0.5], unit: 'm', description: 'Target movement that forces a clean rig reset.' },
98
+ },
99
+ lookAhead: {
100
+ enabled: { label: 'Enabled', type: 'boolean', description: 'Enable velocity-based prediction.' },
101
+ time: { label: 'Prediction Time', range: [0, 3, 0.01], unit: 's', description: 'Seconds ahead to predict the target.' },
102
+ smoothing: { label: 'Velocity Response', range: [0, 40, 0.1], description: 'Exponential smoothing of measured target velocity.' },
103
+ maxDistance: { label: 'Maximum Distance', range: [0, 30, 0.05], unit: 'm', description: 'Maximum look-ahead displacement.' },
104
+ horizontalOnly: { label: 'Horizontal Only', type: 'boolean', description: 'Remove vertical velocity from prediction.' },
105
+ },
106
+ collision: {
107
+ enabled: { label: 'Enabled', type: 'boolean', description: 'Call the optional host collision query.' },
108
+ radius: { label: 'Camera Radius', range: [0, 2, 0.01], unit: 'm', description: 'Radius passed to sphere-aware host queries.' },
109
+ padding: { label: 'Surface Padding', range: [0, 2, 0.01], unit: 'm', description: 'Extra distance kept from an obstruction.' },
110
+ minimumDistance: { label: 'Minimum Distance', range: [0.05, 10, 0.01], unit: 'm', description: 'Closest allowed camera distance from the aim point.' },
111
+ recoveryDamping: { label: 'Recovery Response', range: [0, 40, 0.1], description: 'Speed at which the camera moves back after an obstruction clears.' },
112
+ },
113
+ noise: {
114
+ enabled: { label: 'Enabled', type: 'boolean', description: 'Enable continuous deterministic camera noise.' },
115
+ seed: { label: 'Seed', range: [1, 1000000, 1], description: 'Stable phase seed; equal settings and elapsed time produce equal offsets.' },
116
+ frequency: { label: 'Base Frequency', range: [0.01, 20, 0.01], unit: 'Hz', description: 'Base speed of the procedural noise.' },
117
+ positionAmplitude: { label: 'Position Amplitude', range: [0, 1, 0.001], unit: 'm', description: 'Maximum normalized positional drift.' },
118
+ rotationAmplitude: { label: 'Rotation Amplitude', range: [0, 0.4, 0.001], unit: 'rad', description: 'Maximum normalized angular drift.' },
119
+ octaves: { label: 'Octaves', range: [1, 8, 1], description: 'Number of deterministic sine bands.' },
120
+ persistence: { label: 'Persistence', range: [0, 1, 0.01], description: 'Amplitude retained by each higher-frequency band.' },
121
+ lacunarity: { label: 'Lacunarity', range: [1, 4, 0.01], description: 'Frequency multiplier between bands.' },
122
+ },
123
+ impulse: {
124
+ positionScale: { label: 'Position Scale', range: [0, 5, 0.01], description: 'Global multiplier for positional impulses.' },
125
+ rotationScale: { label: 'Rotation Scale', range: [0, 5, 0.01], description: 'Global multiplier for rotational impulses.' },
126
+ decay: { label: 'Default Decay', range: [0.1, 60, 0.1], description: 'Default exponential impulse decay.' },
127
+ maxPosition: { label: 'Position Limit', range: [0, 10, 0.01], unit: 'm', description: 'Maximum summed positional shake.' },
128
+ maxRotation: { label: 'Rotation Limit', range: [0, 1.2, 0.01], unit: 'rad', description: 'Maximum summed rotational shake.' },
129
+ },
130
+ };
131
+
132
+ export const CAMERA_SETTING_FIELD_SCHEMA = Object.freeze(Object.fromEntries(
133
+ CAMERA_SETTING_GROUPS.map((group) => [group.id, Object.freeze(Object.fromEntries(
134
+ Object.entries(FIELD_DEFINITIONS[group.id]).map(([key, field]) => [key, Object.freeze({
135
+ ...field,
136
+ defaultValue: DEFAULT_CAMERA_SETTINGS[group.id][key],
137
+ group: group.id,
138
+ id: `${group.id}.${key}`,
139
+ key,
140
+ type: field.type ?? 'number',
141
+ })]),
142
+ ))]),
143
+ ));
144
+
145
+ const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
146
+ const finite = (value, fallback) => Number.isFinite(Number(value)) ? Number(value) : fallback;
147
+ const object = (value) => value && typeof value === 'object' && !Array.isArray(value) ? value : {};
148
+
149
+ function number(source, key, fallback, min, max) {
150
+ return clamp(finite(source[key], fallback), min, max);
151
+ }
152
+
153
+ function vector(value, fallback, ranges) {
154
+ if (!Array.isArray(value) || value.length < ranges.length) return [...fallback];
155
+ return ranges.map(([min, max], index) => clamp(finite(value[index], fallback[index]), min, max));
156
+ }
157
+
158
+ /** Normalizes partial settings into a complete mutable plain object. */
159
+ export function createCameraSettings(options = {}) {
160
+ const follow = object(options.follow);
161
+ const framing = object(options.framing);
162
+ const lens = object(options.lens);
163
+ const damping = object(options.damping);
164
+ const lookAhead = object(options.lookAhead);
165
+ const collision = object(options.collision);
166
+ const noise = object(options.noise);
167
+ const impulse = object(options.impulse);
168
+ const d = DEFAULT_CAMERA_SETTINGS;
169
+
170
+ return {
171
+ follow: {
172
+ offset: vector(follow.offset, d.follow.offset, [[-50, 50], [-20, 50], [0.01, 100]]),
173
+ targetOffset: vector(follow.targetOffset, d.follow.targetOffset, [[-50, 50], [-20, 50], [-50, 50]]),
174
+ yawOnly: follow.yawOnly === undefined ? d.follow.yawOnly : Boolean(follow.yawOnly),
175
+ },
176
+ framing: {
177
+ screenX: number(framing, 'screenX', d.framing.screenX, 0, 1),
178
+ screenY: number(framing, 'screenY', d.framing.screenY, 0, 1),
179
+ horizontalScale: number(framing, 'horizontalScale', d.framing.horizontalScale, 0, 4),
180
+ verticalScale: number(framing, 'verticalScale', d.framing.verticalScale, 0, 4),
181
+ },
182
+ lens: {
183
+ fov: number(lens, 'fov', d.lens.fov, 5, 150),
184
+ near: number(lens, 'near', d.lens.near, 0.001, 20),
185
+ far: number(lens, 'far', d.lens.far, 1, 100000),
186
+ speedFov: number(lens, 'speedFov', d.lens.speedFov, -60, 90),
187
+ speedReference: number(lens, 'speedReference', d.lens.speedReference, 0.01, 500),
188
+ },
189
+ damping: {
190
+ position: number(damping, 'position', d.damping.position, 0, 100),
191
+ aim: number(damping, 'aim', d.damping.aim, 0, 100),
192
+ lens: number(damping, 'lens', d.damping.lens, 0, 100),
193
+ teleportDistance: number(damping, 'teleportDistance', d.damping.teleportDistance, 0.01, 10000),
194
+ },
195
+ lookAhead: {
196
+ enabled: lookAhead.enabled === undefined ? d.lookAhead.enabled : Boolean(lookAhead.enabled),
197
+ time: number(lookAhead, 'time', d.lookAhead.time, 0, 10),
198
+ smoothing: number(lookAhead, 'smoothing', d.lookAhead.smoothing, 0, 100),
199
+ maxDistance: number(lookAhead, 'maxDistance', d.lookAhead.maxDistance, 0, 100),
200
+ horizontalOnly: lookAhead.horizontalOnly === undefined ? d.lookAhead.horizontalOnly : Boolean(lookAhead.horizontalOnly),
201
+ },
202
+ collision: {
203
+ enabled: collision.enabled === undefined ? d.collision.enabled : Boolean(collision.enabled),
204
+ radius: number(collision, 'radius', d.collision.radius, 0, 20),
205
+ padding: number(collision, 'padding', d.collision.padding, 0, 20),
206
+ minimumDistance: number(collision, 'minimumDistance', d.collision.minimumDistance, 0.01, 100),
207
+ recoveryDamping: number(collision, 'recoveryDamping', d.collision.recoveryDamping, 0, 100),
208
+ },
209
+ noise: {
210
+ enabled: noise.enabled === undefined ? d.noise.enabled : Boolean(noise.enabled),
211
+ seed: Math.round(number(noise, 'seed', d.noise.seed, 0, 0xffffffff)),
212
+ frequency: number(noise, 'frequency', d.noise.frequency, 0.001, 100),
213
+ positionAmplitude: number(noise, 'positionAmplitude', d.noise.positionAmplitude, 0, 20),
214
+ rotationAmplitude: number(noise, 'rotationAmplitude', d.noise.rotationAmplitude, 0, Math.PI),
215
+ octaves: Math.round(number(noise, 'octaves', d.noise.octaves, 1, 12)),
216
+ persistence: number(noise, 'persistence', d.noise.persistence, 0, 1),
217
+ lacunarity: number(noise, 'lacunarity', d.noise.lacunarity, 1, 8),
218
+ },
219
+ impulse: {
220
+ positionScale: number(impulse, 'positionScale', d.impulse.positionScale, 0, 20),
221
+ rotationScale: number(impulse, 'rotationScale', d.impulse.rotationScale, 0, 20),
222
+ decay: number(impulse, 'decay', d.impulse.decay, 0.01, 200),
223
+ maxPosition: number(impulse, 'maxPosition', d.impulse.maxPosition, 0, 100),
224
+ maxRotation: number(impulse, 'maxRotation', d.impulse.maxRotation, 0, Math.PI),
225
+ },
226
+ };
227
+ }
228
+
229
+ export function mergeCameraSettings(base, overrides = {}) {
230
+ const source = createCameraSettings(base);
231
+ const merged = {};
232
+ for (const group of CAMERA_SETTING_GROUPS) {
233
+ merged[group.id] = { ...source[group.id], ...object(overrides[group.id]) };
234
+ }
235
+ return createCameraSettings(merged);
236
+ }
@@ -0,0 +1,7 @@
1
+ // Composable camera runtime and deterministic generator documents.
2
+ // Import from '@call-me-sensei/toonlab/camera' once the package subpath is wired.
3
+ export * from './cameraSettings.js';
4
+ export * from './cameraGenerator.js';
5
+ export * from './cameraRig.js';
6
+ export * from './cameraDirector.js';
7
+
@@ -0,0 +1,245 @@
1
+ // The built-in catalog: every shipped preset/recipe of every cluster as a
2
+ // manifest entry. Generated by WALKING the preset modules — content added to
3
+ // any cluster accretes here without catalog changes (the roadmap's "manifest
4
+ // frozen during 02" promise, paying off).
5
+
6
+ import { createCatalogEntry } from './manifest.js';
7
+ import { BUILT_IN_PROP_PRESETS } from '../propgen/propPresets.js';
8
+ import { BUILT_IN_BUILDING_PRESETS } from '../buildinggen/buildingPresets.js';
9
+ import { BUILT_IN_PATH_PRESETS } from '../pathgen/pathPresets.js';
10
+ import { BUILT_IN_DEBRIS_PRESETS } from '../debrisgen/debrisPresets.js';
11
+ import { getRockgenPresetOptions } from '../rockgen/rockgenPresets.js';
12
+ import { getWaterPresetOptions } from '../water/waterSettings.js';
13
+ import { getSkyPresetOptions } from '../sky/stylizedSky.js';
14
+ import { getLightingPresetOptions } from '../lighting/lightingPresets.js';
15
+ import { POST_PROCESSING_PRESETS } from '../post/postProcessing.js';
16
+ import { getToonPresetOptions } from '../toon/toonSettings.js';
17
+
18
+ const slug = (value) => String(value)
19
+ .replace(/[^a-zA-Z0-9]+/g, '-')
20
+ .replace(/^-+|-+$/g, '')
21
+ .toLowerCase();
22
+
23
+ const thumbFor = (id) => `thumbs/${id.replaceAll('/', '-')}.webp`;
24
+
25
+ // Curated tree recipes — the vegetation cluster's presets live in tree-lab
26
+ // documents rather than a module registry, so the catalog carries a small
27
+ // signature set inline (same recipe schema tree-lab exports).
28
+ const CURATED_TREES = [
29
+ {
30
+ description: 'Mid-size broadleaf with the studio green canopy.',
31
+ id: 'tree/broadleaf/sensei',
32
+ label: 'Sensei broadleaf',
33
+ recipe: {
34
+ options: { seed: 3, size: 3.2 },
35
+ schema: 'treeRecipe',
36
+ type: 'tree',
37
+ version: 1,
38
+ },
39
+ tags: ['tree', 'vegetation', 'call-me-sensei'],
40
+ },
41
+ {
42
+ description: 'Golden accent tree — one per grove, per the canopy rules.',
43
+ id: 'tree/broadleaf/gold-accent',
44
+ label: 'Gold accent tree',
45
+ recipe: {
46
+ options: { canopyColor: [0.91, 0.73, 0.31], seed: 11, size: 3 },
47
+ schema: 'treeRecipe',
48
+ type: 'tree',
49
+ version: 1,
50
+ },
51
+ tags: ['tree', 'vegetation', 'accent'],
52
+ },
53
+ {
54
+ description: 'Ground-hugging foliage mass for yards and path edges.',
55
+ id: 'tree/bush/hedge',
56
+ label: 'Hedge bush',
57
+ recipe: {
58
+ options: { seed: 5, size: 1.2 },
59
+ schema: 'treeRecipe',
60
+ type: 'bush',
61
+ version: 1,
62
+ },
63
+ tags: ['bush', 'vegetation', 'garden'],
64
+ },
65
+ {
66
+ description: 'Flowering plant with daisy heads.',
67
+ id: 'tree/flower/daisy',
68
+ label: 'Daisy flower',
69
+ recipe: {
70
+ options: { seed: 9, size: 0.5, species: 'daisy' },
71
+ schema: 'treeRecipe',
72
+ type: 'flower',
73
+ version: 1,
74
+ },
75
+ tags: ['flower', 'vegetation', 'meadow'],
76
+ },
77
+ ];
78
+
79
+ let cached = null;
80
+
81
+ /** Assembles (and caches) the full built-in entry list. */
82
+ export function builtinCatalogEntries() {
83
+ if (cached) return cached;
84
+ const entries = [];
85
+
86
+ for (const preset of BUILT_IN_PROP_PRESETS) {
87
+ entries.push(createCatalogEntry({
88
+ cluster: 'propgen',
89
+ description: preset.description,
90
+ id: preset.id,
91
+ kind: 'recipe',
92
+ label: preset.label,
93
+ recipe: preset.recipe,
94
+ spawn: 'createPropAssetFromRecipe(entry.recipe)',
95
+ tags: preset.tags,
96
+ thumbnail: thumbFor(preset.id),
97
+ }));
98
+ }
99
+
100
+ for (const preset of BUILT_IN_BUILDING_PRESETS) {
101
+ entries.push(createCatalogEntry({
102
+ cluster: 'buildinggen',
103
+ description: preset.description,
104
+ id: preset.id,
105
+ kind: 'recipe',
106
+ label: preset.label,
107
+ recipe: preset.recipe,
108
+ spawn: 'buildingAsset(entry.recipe)',
109
+ tags: preset.tags,
110
+ thumbnail: thumbFor(preset.id),
111
+ }));
112
+ }
113
+
114
+ for (const tree of CURATED_TREES) {
115
+ entries.push(createCatalogEntry({
116
+ cluster: 'vegetation',
117
+ description: tree.description,
118
+ id: tree.id,
119
+ kind: 'recipe',
120
+ label: tree.label,
121
+ recipe: tree.recipe,
122
+ spawn: 'propAssetFromObject(createPlantFromRecipe(entry.recipe))',
123
+ tags: tree.tags,
124
+ thumbnail: thumbFor(tree.id),
125
+ }));
126
+ }
127
+
128
+ for (const option of getRockgenPresetOptions()) {
129
+ const id = `rock/${slug(option.value)}`;
130
+ entries.push(createCatalogEntry({
131
+ cluster: 'rockgen',
132
+ id,
133
+ kind: 'preset',
134
+ label: option.label,
135
+ recipe: { preset: option.value, schema: 'rockgenPresetRef', version: 1 },
136
+ spawn: "propAssetFromObject(new THREE.Mesh(meshDocument(createRockDocument({ preset: entry.recipe.preset, seed })), new THREE.MeshStandardMaterial({ vertexColors: true })))",
137
+ tags: ['rock', 'nature', option.value === 'call_me_sensei' ? 'call-me-sensei' : 'stone'],
138
+ thumbnail: thumbFor(id),
139
+ }));
140
+ }
141
+
142
+ for (const preset of BUILT_IN_DEBRIS_PRESETS) {
143
+ const id = `debris/${slug(preset.id)}`;
144
+ entries.push(createCatalogEntry({
145
+ cluster: 'debrisgen',
146
+ description: preset.description,
147
+ id,
148
+ kind: 'recipe',
149
+ label: preset.label,
150
+ recipe: { schema: 'debrisSettings', settings: preset.settings, version: 1 },
151
+ spawn: 'propAssetFromObject(createDebrisAsset(entry.recipe.settings))',
152
+ tags: ['debris', preset.type, slug(preset.variant)],
153
+ thumbnail: thumbFor(id),
154
+ }));
155
+ }
156
+
157
+ for (const preset of BUILT_IN_PATH_PRESETS) {
158
+ entries.push(createCatalogEntry({
159
+ cluster: 'pathgen',
160
+ description: preset.description,
161
+ id: `path/${slug(preset.id)}`,
162
+ kind: 'preset',
163
+ label: preset.label,
164
+ recipe: preset.recipe,
165
+ spawn: 'createStylizedPathsFromRecipe(entry.recipe, { heightAt, waterLevel, size })',
166
+ tags: ['path', 'network', slug(preset.id) === 'call-me-sensei' ? 'call-me-sensei' : 'roads'],
167
+ }));
168
+ }
169
+
170
+ // Settings presets: browsable + copy-paste, not spawnable objects.
171
+ for (const option of getWaterPresetOptions()) {
172
+ entries.push(createCatalogEntry({
173
+ cluster: 'water',
174
+ description: option.description,
175
+ id: `water/${slug(option.id)}`,
176
+ kind: 'preset',
177
+ label: option.label,
178
+ recipe: { preset: option.id, schema: 'waterPresetRef', version: 1 },
179
+ spawn: `new WaterSurface({ preset: '${option.id}', width, depth, bedHeight: heightAt })`,
180
+ tags: ['water', 'settings'],
181
+ }));
182
+ }
183
+ for (const option of getSkyPresetOptions()) {
184
+ entries.push(createCatalogEntry({
185
+ cluster: 'sky',
186
+ description: option.description,
187
+ id: `sky/${slug(option.id)}`,
188
+ kind: 'preset',
189
+ label: option.label,
190
+ recipe: { preset: option.id, schema: 'skyPresetRef', version: 1 },
191
+ spawn: `new StylizedSky({ preset: '${option.id}' })`,
192
+ tags: ['sky', 'settings'],
193
+ }));
194
+ }
195
+ for (const option of getLightingPresetOptions()) {
196
+ const resolverByKind = {
197
+ look: 'resolveLightingLookPreset',
198
+ luminaire: 'resolveLuminairePreset',
199
+ quality: 'resolveLightingQualityPreset',
200
+ rig: 'resolveLightingRigPreset',
201
+ };
202
+ const resolver = resolverByKind[option.kind];
203
+ entries.push(createCatalogEntry({
204
+ cluster: 'lighting',
205
+ description: option.description,
206
+ id: `lighting/${option.kind}/${slug(option.id)}`,
207
+ kind: 'preset',
208
+ label: option.label,
209
+ recipe: {
210
+ kind: option.kind,
211
+ preset: option.id,
212
+ schema: 'lightingPresetRef',
213
+ version: 1,
214
+ },
215
+ spawn: `${resolver}('${option.id}')`,
216
+ tags: ['lighting', option.kind, 'settings'],
217
+ }));
218
+ }
219
+ for (const id of Object.keys(POST_PROCESSING_PRESETS)) {
220
+ entries.push(createCatalogEntry({
221
+ cluster: 'post',
222
+ id: `post/${slug(id)}`,
223
+ kind: 'preset',
224
+ label: id,
225
+ recipe: { preset: id, schema: 'postPresetRef', version: 1 },
226
+ spawn: `createPostProcessingPipeline({ renderer, scene, camera, preset: '${id}' })`,
227
+ tags: ['post', 'settings'],
228
+ }));
229
+ }
230
+ for (const option of getToonPresetOptions()) {
231
+ entries.push(createCatalogEntry({
232
+ cluster: 'toon',
233
+ description: option.description,
234
+ id: `toon/${slug(option.id)}`,
235
+ kind: 'preset',
236
+ label: option.label,
237
+ recipe: { preset: option.id, schema: 'toonPresetRef', version: 1 },
238
+ spawn: `applyToonShader(characterRoot, { settings: createToonSettings({ preset: '${option.id}' }) })`,
239
+ tags: ['toon', 'character', 'settings'],
240
+ }));
241
+ }
242
+
243
+ cached = entries;
244
+ return entries;
245
+ }