@call-me-sensei/toonlab 0.4.19 → 0.4.21

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 (123) hide show
  1. package/NPM-LIBRARY.md +230 -0
  2. package/README.md +78 -36
  3. package/agents/PROMPTS.md +91 -0
  4. package/agents/README.md +9 -14
  5. package/agents/claude/CLAUDE.md +8 -0
  6. package/agents/codex/AGENTS.md +161 -0
  7. package/agents/cursor/toonlab.mdc +37 -0
  8. package/agents/references/mcp-asset-discovery.md +12 -9
  9. package/agents/references/runtime-entry-points.md +5 -4
  10. package/agents/skills/claude/asset-sourcing/SKILL.md +9 -6
  11. package/agents/skills/claude/rock-ground-shaders/SKILL.md +16 -0
  12. package/agents/skills/claude/vegetation-sky/SKILL.md +7 -4
  13. package/agents/skills/codex/asset-sourcing/SKILL.md +9 -6
  14. package/agents/skills/codex/rock-ground-shaders/SKILL.md +16 -0
  15. package/agents/skills/codex/vegetation-sky/SKILL.md +7 -4
  16. package/cli/toonlab.mjs +3 -2
  17. package/database/fal-patina.mjs +131 -0
  18. package/database/generation-service.mjs +108 -7
  19. package/database/migrations/0007_creation_revisions.sql +80 -0
  20. package/database/migrations/0008_creation_revision_hardening.sql +98 -0
  21. package/database/migrations/0009_creation_revision_delete_integrity.sql +11 -0
  22. package/database/migrations/0010_external_catalog_assets.sql +28 -0
  23. package/database/providers.mjs +195 -16
  24. package/database/repository.mjs +518 -70
  25. package/database/seeds/catalog/0003_2026-08-open-assets.sql +159086 -0
  26. package/database/seeds/catalog/0004_2026-08-c7-rocks.sql +39405 -0
  27. package/mcp/public-catalog.mjs +4 -52
  28. package/mcp/server.mjs +175 -12
  29. package/mcp/vite-plugin.mjs +78 -2
  30. package/package.json +32 -12
  31. package/scripts/generate-catalog-seed.mjs +34 -7
  32. package/src/asset-policy/catalogLicenses.js +64 -6
  33. package/src/assetlib/loadImported.js +15 -1
  34. package/src/catalog/officialCatalogAssetRuntime.js +13 -2
  35. package/src/catalog/officialCatalogLod.js +34 -1
  36. package/src/catalog/officialCatalogPlacement.js +29 -14
  37. package/src/character/animationRetarget.js +2 -0
  38. package/src/character/characterRig.js +6 -1
  39. package/src/character/characterRuntime.js +175 -7
  40. package/src/environment/environmentMaterialAdapter.js +30 -0
  41. package/src/environment/environmentPresets.js +90 -0
  42. package/src/environment/environmentSunShadowPass.js +42 -0
  43. package/src/environment/toonLabSurfaceLighting.js +108 -10
  44. package/src/environment/urbanPropMaterial.js +104 -2
  45. package/src/lighting/lightingSystem.js +65 -10
  46. package/src/renderer/index.js +7 -0
  47. package/src/renderer/styleComparison.js +985 -0
  48. package/src/rock-shader/index.js +5 -0
  49. package/src/rock-shader/rockGeometryDetail.js +701 -0
  50. package/src/rock-shader/rockMaterial.js +782 -39
  51. package/src/rock-shader/rockRegionRuntime.js +185 -0
  52. package/src/rock-shader/rockSemanticMaterialRuntime.js +337 -0
  53. package/src/rock-shader/rockShaderRuntime.js +354 -67
  54. package/src/rock-shader/rockShaderSettings.js +246 -16
  55. package/src/rock-shader/rockTangentIntegrity.js +136 -0
  56. package/src/rock-shader/rockTextureIntegrity.js +115 -0
  57. package/src/rockgen/index.js +1 -0
  58. package/src/rockgen/rockDocument.js +27 -3
  59. package/src/rockgen/surface/c7GeologySurface.js +372 -0
  60. package/src/shaders-tsl/water.js +8 -0
  61. package/src/sky/cloudShadow.js +25 -1
  62. package/src/sky/skySystem.js +9 -0
  63. package/src/styles/index.js +23 -0
  64. package/src/styles/neutralStylePresets.js +712 -0
  65. package/src/styles/sceneStyleRuntime.js +51 -4
  66. package/src/styles/styleAdapters.js +13 -2
  67. package/src/styles/styleBundle.js +1 -1
  68. package/src/texgen/evaluateTexture.js +30 -3
  69. package/src/texgen/textureGenerators.js +63 -31
  70. package/src/texgen/textureSettings.js +25 -8
  71. package/src/toon/toonSettings.js +96 -2
  72. package/src/vegetation/branchTree.js +327 -42
  73. package/src/vegetation/index.js +24 -2
  74. package/src/vegetation/scatter.js +372 -1
  75. package/src/vegetation/stylizedTree.js +312 -126
  76. package/src/vegetation/stylizedTreeFoliage.js +229 -7
  77. package/src/vegetation/treeSurfaceTextures.js +328 -0
  78. package/src/version.js +1 -1
  79. package/src/water/waterSettings.js +15 -7
  80. package/types/asset-policy/catalogLicenses.d.ts +2 -0
  81. package/types/asset-policy/index.d.ts +2 -0
  82. package/types/assetlib/loadImported.d.ts +3 -1
  83. package/types/catalog/officialCatalogLod.d.ts +63 -5
  84. package/types/character/characterRig.d.ts +59 -0
  85. package/types/character/characterRuntime.d.ts +1 -0
  86. package/types/environment/environmentMaterialAdapter.d.ts +3 -1
  87. package/types/environment/toonLabSurfaceLighting.d.ts +20 -3
  88. package/types/index.d.ts +89 -7
  89. package/types/lighting/lightingSystem.d.ts +7 -0
  90. package/types/react/index.d.ts +20 -0
  91. package/types/renderer/index.d.ts +1 -0
  92. package/types/renderer/styleComparison.d.ts +409 -0
  93. package/types/rock-shader/index.d.ts +5 -0
  94. package/types/rock-shader/rockGeometryDetail.d.ts +300 -0
  95. package/types/rock-shader/rockMaterial.d.ts +3 -0
  96. package/types/rock-shader/rockRegionRuntime.d.ts +86 -0
  97. package/types/rock-shader/rockSemanticMaterialRuntime.d.ts +29 -0
  98. package/types/rock-shader/rockShaderRuntime.d.ts +2 -0
  99. package/types/rock-shader/rockShaderSettings.d.ts +19 -5
  100. package/types/rock-shader/rockTangentIntegrity.d.ts +51 -0
  101. package/types/rock-shader/rockTextureIntegrity.d.ts +61 -0
  102. package/types/rockgen/index.d.ts +1 -0
  103. package/types/rockgen/rockDocument.d.ts +29 -1
  104. package/types/rockgen/surface/c7GeologySurface.d.ts +8 -0
  105. package/types/sky/cloudShadow.d.ts +1 -0
  106. package/types/styles/index.d.ts +1 -0
  107. package/types/styles/neutralStylePresets.d.ts +944 -0
  108. package/types/styles/sceneStyleRuntime.d.ts +20 -0
  109. package/types/vegetation/branchTree.d.ts +4 -0
  110. package/types/vegetation/index.d.ts +9 -7
  111. package/types/vegetation/scatter.d.ts +1 -0
  112. package/types/vegetation/stylizedTree.d.ts +1 -2
  113. package/types/vegetation/stylizedTreeFoliage.d.ts +1 -0
  114. package/types/version.d.ts +1 -1
  115. package/agents/references/geology-playbook.md +0 -118
  116. package/agents/skills/claude/karst-cliff-construction/SKILL.md +0 -258
  117. package/agents/skills/claude/outdoor-world/SKILL.md +0 -492
  118. package/agents/skills/claude/outdoor-world/references/coastal-landform-formation.md +0 -100
  119. package/agents/skills/codex/karst-cliff-construction/SKILL.md +0 -258
  120. package/agents/skills/codex/outdoor-world/SKILL.md +0 -492
  121. package/agents/skills/codex/outdoor-world/references/coastal-landform-formation.md +0 -100
  122. package/src/vegetation/legacyTreePresets.js +0 -120
  123. package/types/vegetation/legacyTreePresets.d.ts +0 -22
@@ -80,6 +80,7 @@ export function createSceneStyleRuntime({ collision, collisionAdapter, collision
80
80
  z: number;
81
81
  };
82
82
  };
83
+ setSunDirection(direction?: any): any[];
83
84
  advanceTime(hoursDelta: any): {
84
85
  accentScale: any;
85
86
  ambientScale: any;
@@ -311,6 +312,7 @@ export function createSceneStyleRuntime({ collision, collisionAdapter, collision
311
312
  z: number;
312
313
  };
313
314
  };
315
+ readonly sunDirection: any[];
314
316
  readonly manager: {
315
317
  addLight: (descriptorSource: any) => string;
316
318
  addToScene: (nextScene: any) => /*elided*/ any;
@@ -578,6 +580,7 @@ export function createSceneStyleRuntime({ collision, collisionAdapter, collision
578
580
  z: number;
579
581
  };
580
582
  };
583
+ setSunDirection(direction?: any): any[];
581
584
  advanceTime(hoursDelta: any): {
582
585
  accentScale: any;
583
586
  ambientScale: any;
@@ -809,6 +812,7 @@ export function createSceneStyleRuntime({ collision, collisionAdapter, collision
809
812
  z: number;
810
813
  };
811
814
  };
815
+ readonly sunDirection: any[];
812
816
  readonly manager: {
813
817
  addLight: (descriptorSource: any) => string;
814
818
  addToScene: (nextScene: any) => /*elided*/ any;
@@ -1083,6 +1087,7 @@ export function createSceneStyleRuntime({ collision, collisionAdapter, collision
1083
1087
  z: number;
1084
1088
  };
1085
1089
  };
1090
+ setSunDirection(direction?: any): any[];
1086
1091
  /**
1087
1092
  * Replaces the physical SkySystem condition while preserving the active
1088
1093
  * style bundle's sky/cloud presentation and coordinated lighting frame.
@@ -1352,6 +1357,7 @@ export function createSceneStyleRuntime({ collision, collisionAdapter, collision
1352
1357
  z: number;
1353
1358
  };
1354
1359
  };
1360
+ setSunDirection(direction?: any): any[];
1355
1361
  advanceTime(hoursDelta: any): {
1356
1362
  accentScale: any;
1357
1363
  ambientScale: any;
@@ -1583,6 +1589,7 @@ export function createSceneStyleRuntime({ collision, collisionAdapter, collision
1583
1589
  z: number;
1584
1590
  };
1585
1591
  };
1592
+ readonly sunDirection: any[];
1586
1593
  readonly manager: {
1587
1594
  addLight: (descriptorSource: any) => string;
1588
1595
  addToScene: (nextScene: any) => /*elided*/ any;
@@ -1751,8 +1758,21 @@ export function createSceneStyleRuntime({ collision, collisionAdapter, collision
1751
1758
  uncoveredTargetIds: readonly any[];
1752
1759
  }>;
1753
1760
  readonly ready: boolean;
1761
+ readonly health: Readonly<{
1762
+ backend: "node" | "classic-webgl-unused";
1763
+ casterCount: number;
1764
+ farReady: boolean;
1765
+ hiddenNonCasterCount: number;
1766
+ ok: boolean;
1767
+ ready: boolean;
1768
+ reason: string;
1769
+ renderCount: number;
1770
+ sunName: any;
1771
+ }>;
1754
1772
  readonly renderCount: number;
1755
1773
  readonly shadowTexture: any;
1774
+ readonly nearShadowTarget: any;
1775
+ readonly farShadowTarget: any;
1756
1776
  readonly shadowMatrix: import("three").Matrix4;
1757
1777
  readonly farShadowTexture: any;
1758
1778
  dispose: () => void;
@@ -1,10 +1,14 @@
1
1
  // Source inference fallback: public names retained with permissive signatures.
2
+ export const BRANCH_TREE_ARCHITECTURE: any;
3
+ export const BRANCH_TREE_CANOPY_ARCHITECTURES: any;
2
4
  export const BRANCH_TREE_DOCUMENT_TYPE: any;
3
5
  export const BRANCH_TREE_DOCUMENT_VERSION: any;
4
6
  export const BRANCH_TREE_LEAF_SHAPES: any;
5
7
  export class BranchTree { constructor(...args: any[]); [key: string]: any; }
6
8
  export const DEFAULT_BRANCH_TREE_SETTINGS: any;
9
+ export function branchTreeStylizedOptions(...args: any[]): any;
7
10
  export function createBranchTree(...args: any[]): any;
8
11
  export function createBranchTreeDocument(...args: any[]): any;
12
+ export function createBranchTreeRecipe(...args: any[]): any;
9
13
  export function createBranchTreeSettings(...args: any[]): any;
10
14
  export function parseBranchTreeDocument(...args: any[]): any;
@@ -1,5 +1,7 @@
1
1
  // Source inference fallback: public names retained with permissive signatures.
2
2
  export const BARK_SHADER: any;
3
+ export const BRANCH_TREE_ARCHITECTURE: any;
4
+ export const BRANCH_TREE_CANOPY_ARCHITECTURES: any;
3
5
  export const BRANCH_TREE_DOCUMENT_TYPE: any;
4
6
  export const BRANCH_TREE_DOCUMENT_VERSION: any;
5
7
  export const BRANCH_TREE_LEAF_SHAPES: any;
@@ -37,11 +39,9 @@ export const GRASS_SHADER_PROFILE: any;
37
39
  export const GRASS_SHADER_PROFILE_DOCUMENT_TYPE: any;
38
40
  export const GRASS_SHADER_PROFILE_SCHEMA_VERSION: any;
39
41
  export const LEAF_SHAPE_PRESETS: any;
40
- export const LEGACY_TREE_IDS: any;
41
- export const LEGACY_TREE_PRESETS: any;
42
+ export const NEEDLE_LEAF_SHAPES: any;
42
43
  export class RetainedGrassClumpField { constructor(...args: any[]); [key: string]: any; }
43
44
  export const STYLIZED_FOREST_IMPOSTOR_QUALITY: any;
44
- export const STYLIZED_TREE_EXAMPLES: any;
45
45
  export const STYLIZED_TREE_SETTING_FIELD_SCHEMA: any;
46
46
  export const STYLIZED_TREE_SETTING_GROUPS: any;
47
47
  export class StylizedBush { constructor(...args: any[]); [key: string]: any; }
@@ -63,6 +63,7 @@ export const TREE_SHADER_DOCUMENT_TYPE: any;
63
63
  export const TREE_SHADER_PROFILE: any;
64
64
  export const TREE_SHADER_SCHEMA_VERSION: any;
65
65
  export const TREE_SURFACE_PROFILES: any;
66
+ export const TREE_SURFACE_PROFILE_ALIASES: any;
66
67
  export const TREE_SURFACE_PROFILE_DEFAULTS: any;
67
68
  export const TREE_TRUNK_STYLES: any;
68
69
  export const UNDERSTORY_AERIAL_FADE: any;
@@ -89,6 +90,7 @@ export function applyGrassShader(...args: any[]): any;
89
90
  export function applyVegetationShader(...args: any[]): any;
90
91
  export function applyVegetationShaderScope(...args: any[]): any;
91
92
  export function bakeFoliageGeometry(...args: any[]): any;
93
+ export function branchTreeStylizedOptions(...args: any[]): any;
92
94
  export function combineMasks(...args: any[]): any;
93
95
  export function createBakedFoliageMaterial(...args: any[]): any;
94
96
  export function createBakedLeafTexture(...args: any[]): any;
@@ -96,6 +98,7 @@ export function createBarkGradientMap(...args: any[]): any;
96
98
  export function createBarkShaderSettings(...args: any[]): any;
97
99
  export function createBranchTree(...args: any[]): any;
98
100
  export function createBranchTreeDocument(...args: any[]): any;
101
+ export function createBranchTreeRecipe(...args: any[]): any;
99
102
  export function createBranchTreeSettings(...args: any[]): any;
100
103
  export function createBranchTubeGeometry(...args: any[]): any;
101
104
  export function createBranchingTreeSkeleton(...args: any[]): any;
@@ -103,6 +106,7 @@ export function createCallMeSenseiGrassField(...args: any[]): any;
103
106
  export function createCallMeSenseiGrassMaterial(...args: any[]): any;
104
107
  export function createCanopyBlobs(...args: any[]): any;
105
108
  export function createCapEdgeWeight(...args: any[]): any;
109
+ export function createCurveFrame(...args: any[]): any;
106
110
  export function createDensityWeightMask(...args: any[]): any;
107
111
  export function createFlowerBloomNodeMaterial(...args: any[]): any;
108
112
  export function createFlowerHeadBillboardNodeMaterial(...args: any[]): any;
@@ -126,7 +130,6 @@ export function createGrassShaderProfileSettings(...args: any[]): any;
126
130
  export function createGrassShaderSettings(...args: any[]): any;
127
131
  export function createImportedVegetationMaterial(...args: any[]): any;
128
132
  export function createLeafSpriteTexture(...args: any[]): any;
129
- export function createLegacyTree(...args: any[]): any;
130
133
  export function createNoisePatchMask(...args: any[]): any;
131
134
  export function createOrganLeafSpriteTexture(...args: any[]): any;
132
135
  export function createSlopeMask(...args: any[]): any;
@@ -152,8 +155,6 @@ export function deriveCanopyPalette(...args: any[]): any;
152
155
  export function disposeExportGroup(...args: any[]): any;
153
156
  export function getFlowerPresetOptions(...args: any[]): any;
154
157
  export function getGrassPresetOptions(...args: any[]): any;
155
- export function getLegacyTreePreset(...args: any[]): any;
156
- export function getLegacyTreePresetOptions(...args: any[]): any;
157
158
  export function getStylizedTreePresetOptions(...args: any[]): any;
158
159
  export function getTreeSurfaceProfileOptions(...args: any[]): any;
159
160
  export function getVegetationMaterialContract(...args: any[]): any;
@@ -162,7 +163,6 @@ export function getVegetationShaderScopeExcludedFields(...args: any[]): any;
162
163
  export function getVegetationShaderScopeFieldSchema(...args: any[]): any;
163
164
  export function getVegetationShaderScopeSettingGroups(...args: any[]): any;
164
165
  export function isVegetationSharedShaderGroup(...args: any[]): any;
165
- export function layoutTreeRow(...args: any[]): any;
166
166
  export function loadCallMeSenseiGrassClump(...args: any[]): any;
167
167
  export function loadCompiledTreeAsset(...args: any[]): any;
168
168
  export function matchGrassColorPalette(...args: any[]): any;
@@ -187,7 +187,9 @@ export function registerStylizedTreePreset(...args: any[]): any;
187
187
  export function registerVegetationShaderPreset(...args: any[]): any;
188
188
  export function resolveCanopyColor(...args: any[]): any;
189
189
  export function resolveGrassColorPalette(...args: any[]): any;
190
+ export function resolvePadPruning(...args: any[]): any;
190
191
  export function resolveTreeDitherMode(...args: any[]): any;
192
+ export function resolveTreeSurfaceProfileId(...args: any[]): any;
191
193
  export function resolveVegetationShaderPreset(...args: any[]): any;
192
194
  export function resolveVegetationShaderRoleSettings(...args: any[]): any;
193
195
  export function sanitizeGrassPresetSettings(...args: any[]): any;
@@ -1,6 +1,7 @@
1
1
  // Source inference fallback: public names retained with permissive signatures.
2
2
  export function combineMasks(...args: any[]): any;
3
3
  export function createCapEdgeWeight(...args: any[]): any;
4
+ export function createCurveFrame(...args: any[]): any;
4
5
  export function createDensityWeightMask(...args: any[]): any;
5
6
  export function createNoisePatchMask(...args: any[]): any;
6
7
  export function createSlopeMask(...args: any[]): any;
@@ -1,6 +1,5 @@
1
1
  // Source inference fallback: public names retained with permissive signatures.
2
2
  export const DEFAULT_STYLIZED_TREE_SETTINGS: any;
3
- export const STYLIZED_TREE_EXAMPLES: any;
4
3
  export const STYLIZED_TREE_SETTING_FIELD_SCHEMA: any;
5
4
  export const STYLIZED_TREE_SETTING_GROUPS: any;
6
5
  export class StylizedTree { constructor(...args: any[]); [key: string]: any; }
@@ -13,7 +12,7 @@ export function createStylizedTreeSettings(...args: any[]): any;
13
12
  export function createTreeSkeleton(...args: any[]): any;
14
13
  export function createTreeTrunkGeometry(...args: any[]): any;
15
14
  export function getStylizedTreePresetOptions(...args: any[]): any;
16
- export function layoutTreeRow(...args: any[]): any;
17
15
  export function polarProfileFromOutline(...args: any[]): any;
18
16
  export function registerStylizedTreePreset(...args: any[]): any;
17
+ export function resolvePadPruning(...args: any[]): any;
19
18
  export function serializableTreeOptions(...args: any[]): any;
@@ -1,5 +1,6 @@
1
1
  // Source inference fallback: public names retained with permissive signatures.
2
2
  export const LEAF_SHAPE_PRESETS: any;
3
+ export const NEEDLE_LEAF_SHAPES: any;
3
4
  export class StylizedTreeFoliage { constructor(...args: any[]); [key: string]: any; }
4
5
  export const TREE_FOLIAGE_ARCHITECTURES: any;
5
6
  export const TREE_FOLIAGE_BLOBS: any;
@@ -1 +1 @@
1
- export const TOONLAB_VERSION: "0.4.19";
1
+ export const TOONLAB_VERSION: "0.4.21";
@@ -1,118 +0,0 @@
1
- # Geology assembly playbook
2
-
3
- Use this reference with `karst-cliff-construction` and `outdoor-world`. It
4
- describes reusable landform craft without depending on project-specific
5
- helpers, asset IDs, or a frozen catalog count.
6
-
7
- ## 1. Establish the parent mass
8
-
9
- Approve an untextured continuous terrain/heightfield silhouette first:
10
- headlands, bays, terraces, tower envelopes, channel, beach, and submerged bed.
11
- Catalog rocks dress and articulate that parent mass; they must not be the only
12
- thing preventing the world from reading as hollow.
13
-
14
- Reject before dressing when:
15
-
16
- - the hero silhouette is wrong;
17
- - the channel or beach footprint is clogged;
18
- - the cliff is a thin wall with no top/underwater continuation;
19
- - separate towers overlap into one clump from an approved camera.
20
-
21
- ## 2. Select donors by visible role
22
-
23
- Query the connected ToonLab OSS or Pro catalog and shortlist with positive
24
- `dimensionsMeters`, taxonomy, scale class, and immutable preview/asset URLs.
25
- Family names and tags are search hints, not visual proof. Review contact sheets
26
- or finalists under one neutral material and camera azimuth.
27
-
28
- Useful morphology:
29
-
30
- | Role | Visible requirement |
31
- |---|---|
32
- | Primary face | Broad load-bearing plane, gravity-aligned relief, no prop-like outline |
33
- | Corner/termination | A turn or return face that buries cleanly into the parent mass |
34
- | Bedding ledge | A real horizontal lip with enough depth to cast a recess shadow |
35
- | Buttress/wedge | A seam bridge whose rear and toe can be buried |
36
- | Waterline notch | Rounded or undercut lip that continues below the surface |
37
- | Talus | Small jointed collapse block; free rotation is appropriate only here |
38
-
39
- Choose target dimensions in metres from the published native dimensions.
40
- Reject missing or invalid dimensions before download. Avoid solving an entire
41
- wall by stretching one small donor; report mean/max scale and worst axis ratio
42
- per structural role.
43
-
44
- ## 3. Build courses, not rubble
45
-
46
- Primary faces share a near-common up axis and facing. Vary crop, burial,
47
- neighbour overlap, tier, yaw silhouette, and moderate scale; do not tumble
48
- load-bearing panels. Free three-axis tumbling belongs to collapse products.
49
-
50
- Starting burial ranges, to be verified in silhouette:
51
-
52
- - primary faces: bury roughly 30–60% of module depth;
53
- - bedding ledges: bury the rear majority so only the lip projects;
54
- - tower cores: seat the footing/course overlap, not most of the tower;
55
- - talus: rest in contact and bury enough to remove floating toes.
56
-
57
- Build in this order:
58
-
59
- 1. primary faces at noses, re-entrants, terminations, and terrace breaks;
60
- 2. bridging ledges, buttresses, crevice wedges, and recessed corbels;
61
- 3. waterline/submerged continuation and restrained talus;
62
- 4. vegetation caps and story dressing.
63
-
64
- If a placement helper automatically splits an oversized request into a stack,
65
- make that result explicit and refusable. Subdivision must not introduce yaw,
66
- pitch, or roll jitter unless the caller deliberately requests a tumbled role.
67
-
68
- ## 4. Publish shared formation contracts
69
-
70
- Sibling modules must consume the same world-space values rather than derive
71
- private approximations:
72
-
73
- - parent height/envelope;
74
- - water level and submerged bed;
75
- - bedding elevation ladder;
76
- - visible face plane/standoff;
77
- - cliff-top and vegetation-cap polygons;
78
- - fall lips, landing pools, and exclusion zones.
79
-
80
- One useful bedding ladder is `base + courseIndex * spacing`; derive `base` and
81
- `spacing` from the terrain terracing/style rather than copying the qualifying
82
- scene's numbers. Align visible beds across neighbouring formations.
83
-
84
- ## 5. Unify the material treatment
85
-
86
- Use one intentional parent lithology. The rock shader can replace source
87
- albedo and apply one projected treatment across morphologically compatible
88
- donors, but it cannot change incompatible geometry into the same geology.
89
- Record any cross-taxonomy donor override and judge the result from silhouette,
90
- bedding, and surface response.
91
-
92
- Prefer one `applyRockShader(formationRoot, settings)` call. Verify the report's
93
- texture source and shadow defaults. Use `setRockShaderSceneState` for the
94
- current water level so the wet band follows the shoreline.
95
-
96
- ## 6. Prevent recognizable repetition
97
-
98
- Repeated asset ids are normal modular modelling. Repeated camera-facing
99
- silhouettes are the defect. For nearby copies vary at least three of: visible
100
- crop/burial, tier, neighbour overlap, meaningful scale, and camera-facing yaw.
101
- Never use regular spacing or a visible kit row.
102
-
103
- Placement metrics are rejection floors. They cannot prove that the rendered
104
- formation reads as one mass.
105
-
106
- ## 7. Verify before approval
107
-
108
- - Wait for application readiness and a stable rendered scene graph.
109
- - Capture hero, wide, close, flyover, and top-down views.
110
- - Isolate terrain, rock formation, water, and lighting when assigning a defect.
111
- - Scan world-space extents for accidental slabs, dams, or oversized caps.
112
- - Report module counts, donor dimensions, transformed dimensions, burial,
113
- per-role scale statistics, and active LODs.
114
- - Squint at the silhouette: if individual boulders or a sawtooth crest remain
115
- legible, the cliff is still a pile.
116
-
117
- Automated checks may reject gaps, invalid scale, and repeated transforms. Only
118
- rendered multi-view evidence can approve the formation.
@@ -1,258 +0,0 @@
1
- ---
2
- name: karst-cliff-construction
3
- description: Experimental research guidance for assembling a stylized cliff, gorge, karst tower, or sea stack from catalog rocks, including silhouette selection, parent geology, transform grammar, and measurable rubble-pile rejection gates; not a supported automatic cliff builder.
4
- ---
5
-
6
- # Experimental cliff construction from catalog rocks
7
-
8
- This is retained as research and review guidance. ToonLab can find rocks,
9
- report their dimensions/taxonomy, and shade supplied geometry, but neither the
10
- package nor an LLM is currently qualified to turn those assets into a polished
11
- cliff automatically. Use it only for an explicit experiment, record every
12
- manual placement/transform decision, and do not present success as a stable
13
- one-shot feature.
14
-
15
- This is the assembly craft that sits between `rockgen` (which makes a rock) and
16
- the `outdoor-world` skill (which owns the whole scene). Read
17
- `../outdoor-world/SKILL.md` first for the landform-before-props ordering and the
18
- rejection gates; this skill is how you actually execute the rock layer. Read
19
- the agent-neutral
20
- [geology playbook](../../../references/geology-playbook.md) for the compact
21
- morphology, course, burial, and verification reference.
22
-
23
- Everything here was derived by building a reference-driven karst gorge from the
24
- full catalog available during qualification and having it rejected
25
- several times. Catalog contents change; query the connected OSS or Pro MCP
26
- surface instead of assuming a fixed asset count or frozen family membership.
27
- The failure modes are listed as prominently as the method, because they are
28
- what cost the time.
29
-
30
- ## The single most important rule
31
-
32
- **A cliff is one mass. Individual rocks must not be legible as individual
33
- rocks.**
34
-
35
- Squint at the frame from the hero camera. If you can pick out separate boulders
36
- in the wall face, the formation has failed, no matter how good the rocks are or
37
- how correct the metrics look. Every rule below exists to serve that one read.
38
-
39
- The characteristic failure is an **angular rubble pile**: many modules at many
40
- orientations, each individually readable, with a sawtooth crest. It looks like
41
- quarry spoil. It happens because tumbling every rock feels like variety, and
42
- because an anti-repetition gate seems to demand it. It does not.
43
-
44
- ## Selection: look, do not read tags
45
-
46
- **Family names do not guarantee silhouette.** In the tested catalog snapshot,
47
- `column-field` contains no columnar geometry at all; `broad-wall` is mostly
48
- hourglass pedestals; the only assets that genuinely read as vertical fluting
49
- live in `vertical-face`. Selecting by `familyId` or by tag produces the wrong
50
- formation when the current previews disagree with the label.
51
-
52
- So: **render contact sheets and choose visually.** Download the catalog
53
- thumbnails, lay them out in a labelled grid per family, and look at every one
54
- before choosing. Better still, render the actual GLBs under a single flat
55
- material at the intended camera azimuth — thumbnails show baked material colour,
56
- which is discarded at runtime (see below), so silhouette is the only thing that
57
- matters and the thumbnail can mislead you about it.
58
-
59
- Budget an hour for this. It is the highest-leverage hour in the whole job.
60
-
61
- **Use the published donor dimensions.** Current OSS and Pro rock search results
62
- include positive `dimensionsMeters.width`, `.height`, and `.depth`. Reject a
63
- record with missing, zero, negative, or axis-ambiguous dimensions before
64
- download. Load a finalist only to verify silhouette/material compatibility,
65
- not to discover its size. Drive placement from **target metres**, never from a
66
- raw scale multiplier. A project helper such as `place({ height: 40 })` is
67
- reviewable; `scale: 6.3` is not a ToonLab contract or an art-direction decision.
68
-
69
- ## Geology: one parent rock, many catalog families
70
-
71
- A formation must read as **one lithology**. Limestone, sandstone and granite do
72
- not alternate between adjacent modules unless the level authors a readable fault.
73
-
74
- This can conflict with selecting by silhouette, because useful shapes may span
75
- families with different `taxonomy.geology`. Treat geology as meaningful search
76
- metadata, but allow an intentional stylized donor override when the visible
77
- morphology is compatible and one material treatment makes the formation read
78
- as a deliberate parent rock. Record that override; never imply that replacing
79
- albedo changes the source mesh's physical morphology.
80
-
81
- So the working method is:
82
-
83
- 1. Select every rock by **role and silhouette**, ignoring its geology tag.
84
- 2. Apply the rock shader to the whole formation root with one settings object.
85
- 3. Verify that every included part reports the same intended texture source and
86
- material profile. One root call is the safest way to prevent call-site drift;
87
- repeated calls with byte-identical settings are visually equivalent.
88
-
89
- Two consequences worth knowing before you are surprised by them:
90
-
91
- - Baked green grass caps on some catalog rocks **disappear** under `replace`.
92
- Re-create caps deliberately, via the shader's grass layer or via vegetation.
93
- - Per-part calls invite accidental settings drift. Prefer the formation root
94
- unless a deliberately distinct lithology needs its own documented pass.
95
-
96
- For a genuinely different stone in the same frame — a quarried quay against a
97
- natural cliff — mark those meshes `userData.rockShaderExclude = true`, run the
98
- main pass, then clear the flag and run a second pass over just that group.
99
- Order matters: setting the flag before your own pass silently disables it.
100
-
101
- ### Matching geology to real morphology
102
-
103
- Choose donors by what the landform actually does, not by name:
104
-
105
- | Landform feature | What to look for |
106
- |---|---|
107
- | Rillenkarren (vertical dissolution runnels on karst) | Parallel vertical prism relief — columnar-basalt donors read as this almost exactly |
108
- | Broad fluted wall envelope | Barrel-curved faces with wide vertical creases |
109
- | Bedding partings | Donors with true horizontal lips and shelf stacks |
110
- | Undercut karst tower | Inverted flares — wider at mid-height than at the base |
111
- | Solution notch at the waterline | River-worn blocks with a rounded overhanging lip |
112
- | Collapse product below a wall | Jointed angular blocks, tumbled freely — the one place tumbling is correct |
113
-
114
- ## The transform grammar
115
-
116
- Per role, and these ranges are the difference between a landform and a pile:
117
-
118
- **Primary wall faces.** Share a near-common up axis and a near-common facing.
119
- Yaw varies the silhouette *edge*, not the rock's orientation. Pitch and roll
120
- near zero — fluting is gravity-parallel, and a tilted flute reads as a mistake.
121
- Bury 30–60 % of module depth. Present the face, not the corners.
122
-
123
- **Corners and terminations.** Yaw is set by the turn, not jittered. Turn the
124
- broad mass toward the coastline change and bury the return face into its
125
- neighbour so it never shows a free edge.
126
-
127
- **Shelves and bedding ledges.** Rotate a useful plane upward, crop the rear into
128
- the parent mass, and support the underside with a face or buttress so the
129
- overhang is a shadowed recess rather than a lit floating slab.
130
-
131
- **Crevice wedges.** The one primary role where roll is large — 8–15° — because a
132
- wedge is *supposed* to be jammed at an angle.
133
-
134
- **Talus and waterline blocks.** Free three-axis rotation, 0.7–1.4 non-uniform
135
- scale, always resting in contact with the surface below.
136
-
137
- ### Scale discipline
138
-
139
- **Cap the uniform scale, and audit it.** Blowing a 6 m donor up 22× is what makes
140
- a wall read as stretched rather than stacked: the projected texture smears, the
141
- silhouette loses its authored detail, and one module tries to do the work of a
142
- course. Prefer more modules near native size over fewer giant ones.
143
-
144
- Instrument it. Report per-role mean and max uniform scale and the worst axis
145
- ratio, and warn loudly when a role exceeds its ceiling, so a wall module cannot
146
- silently stretch a donor. A run that measured `mean 6.35, max 22.3, worst axis
147
- ratio 4.08` was visibly stretched and nobody noticed until an art director said
148
- "why is it stretched".
149
-
150
- ### Placement helpers must not reinterpret intent
151
-
152
- An oversized authored wall module must never silently become a jittered stack.
153
- Make any over-ceiling substitution **explicit and refusable**: accept an option
154
- such as `onOverCeiling: 'reject' | 'shrink' | 'stack'`, default load-bearing wall
155
- roles to `reject`, and return/report the actual strategy and placement count.
156
- Reserve `stack` for an intentional course plan; reserve tumbling for talus.
157
-
158
- Keep course variation separate from scatter variation. Load-bearing faces use
159
- `courseJitter: 0` unless the author requests a measured, small range; they must
160
- not inherit free yaw/roll jitter from a rubble scatter helper. Likewise, label
161
- Euler rotations by their local-axis order. A field named `pitch` is not a
162
- world-space lean after yaw. Prefer a world-space contract such as
163
- `leanInto: { direction, angle }`, or document and test the exact local axis.
164
-
165
- ## Anti-repetition, correctly understood
166
-
167
- The gate is **recognizable repetition**, not a repeated asset id. Reusing a good
168
- modular rock is normal environment modelling.
169
-
170
- Two copies of one id must not present the same camera-facing silhouette. Satisfy
171
- at least three of: yaw differs ≥ 25° against the camera-facing normal; different
172
- tier; uniform scale differs ≥ 25 %; burial fraction differs ≥ 8 points so a
173
- different portion of the outline survives; different overlapping neighbour.
174
-
175
- Absolute prohibitions: no twins within ~35 m at the same tier; no regular
176
- spacing interval; no kit rows.
177
-
178
- **Audit it, but do not trust the audit.** A placement-based audit scores
179
- positions and transforms — it cannot see the rendered silhouette. Zero offences
180
- and a rubble pile are entirely compatible. The audit is a floor, not approval.
181
-
182
- ## Structural coherence
183
-
184
- **Bedding must be continuous across formations.** Beds are the same beds. Derive
185
- one elevation ladder for the whole scene — ideally from the terrain's own
186
- terracing, so rock and parent mass agree — and place every bedding ledge on it.
187
- Bands that line up across neighbouring towers at equal elevation is the single
188
- detail that sells a karst formation.
189
-
190
- **Publish it.** The wall that computes the ladder should expose it, and every
191
- other formation should read it rather than re-deriving. Be aware of build order:
192
- if a composer builds parts in sequence, a later part cannot read an earlier
193
- part's published values through a context object that is only assigned after the
194
- whole composer returns. Pass shared values through the composer explicitly, or
195
- have each part fall back to a documented constant and log which path ran.
196
-
197
- Publish a shared **face plane** as well as the terrain/height envelope. Shelves,
198
- bridges, falls, vegetation lips, and sibling wall modules must read the same
199
- world-space surface contract instead of hard-coding a standoff against a wall
200
- revision that can move later.
201
-
202
- **Bridge every seam.** After the primary masses, rasterise their footprints,
203
- find pairs with a traceable boundary, and wedge shelves, buttresses and crevice
204
- slabs across them, each with a corbel tucked beneath so the overhang is a
205
- shadowed recess. Terrain must never show through a gap between rocks.
206
-
207
- **Do not let painted detail do geometry's job.** Horizontal stratification drawn
208
- into a projected texture becomes a stretched line on a scaled rock, and thin
209
- crack layers alias into dashed "stitched" lines at any realistic tile
210
- resolution. Deliver bedding as **stacked silhouettes** — real courses with real
211
- overhangs — and reserve the texture for surface grain.
212
-
213
- ## Build order
214
-
215
- Do not start by scattering rocks.
216
-
217
- 1. **Macro landform.** A continuous heightfield with the full silhouette:
218
- headlands, bays, terraces, the channel, the bed. Approve it untextured. If
219
- the macro silhouette is wrong, no rock dressing saves it.
220
- 2. **Primary structure.** Large faces at structural events only — noses,
221
- re-entrants, terminations, terrace breaks. Subordinate to the macro mass.
222
- 3. **Secondary structure.** Bridging shelves, buttresses, crevice wedges.
223
- 4. **Tertiary dressing.** Waterline notch, submerged continuation, minimal
224
- talus, story detail.
225
-
226
- Talus quantity is a geology decision, not a dressing preference. A dissolution
227
- gorge has almost none; a frost-shattered alpine face has fans. An evenly spread
228
- scree carpet is always wrong.
229
-
230
- ## Verification
231
-
232
- Numeric checks reject obvious failure; only looking can approve.
233
-
234
- - **Isolate before blaming.** Render subsystems alone — with their light rig, or
235
- you get black — to attribute a defect. A "flowing water" texture on the cliffs
236
- turned out to be the terrain's ground shader, not the waterfalls, and only an
237
- isolation capture settled it.
238
- - **Scan for outliers.** Traverse the scene for meshes above a size threshold and
239
- print path, world extent and position. This catches the 104 m slab damming a
240
- 72 m channel that nobody sees because it reads as "background".
241
- - **Squint at the silhouette first**, before texture, before colour. One mass or
242
- a pile.
243
- - **Judge from every approved camera**: hero, wide, close, flyover, top-down. A
244
- formation can read as one mass from the hero and as a kit row from above.
245
- - **Settle the scene before screenshotting.** Modules resolve async work — GLB
246
- loads, render passes — after their factory returns. Wait for a stable scene
247
- graph, not a fixed frame count, or you will review a frame with subsystems
248
- missing while the manifest claims they are present.
249
-
250
- ## Performance notes that shape the art
251
-
252
- - The rock shader creates a **material per mesh**, so instancing is unavailable
253
- and every tertiary rock costs a draw call. Budget the tertiary layer.
254
- - Catalog GLBs carry baked LODs; use the higher indices for distant modules.
255
- - Catalog GLBs can require KTX2/Draco decoders. Create one shared transcoder set
256
- with `createModelAssetTranscoders({ decoderBasePath, renderer })`, pass it to
257
- every `loadModelAsset(url, { transcoders })` call, and dispose it once after
258
- all model loads. Do not construct decoder/transcoder resources per rock.