@developerz.ai/ts23d 0.1.0 → 0.2.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 (162) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/CLAUDE.md +1 -1
  3. package/README.md +10 -5
  4. package/dist/{chunk-a8fxakcj.js → chunk-6b05mnmx.js} +2 -2
  5. package/dist/{chunk-kgvbpqqw.js → chunk-7rtfjh9f.js} +6 -5
  6. package/dist/chunk-7rtfjh9f.js.map +10 -0
  7. package/dist/{chunk-1ypfs7ga.js → chunk-8j4c28jt.js} +790 -276
  8. package/dist/chunk-8j4c28jt.js.map +46 -0
  9. package/dist/{chunk-wtqhq529.js → chunk-d1fv6hrm.js} +6 -6
  10. package/dist/{chunk-wtqhq529.js.map → chunk-d1fv6hrm.js.map} +1 -1
  11. package/dist/{chunk-gcyp9b4d.js → chunk-dbq4qf52.js} +362 -215
  12. package/dist/chunk-dbq4qf52.js.map +25 -0
  13. package/dist/{chunk-xnt2sdhf.js → chunk-drna2rny.js} +2 -2
  14. package/dist/{chunk-ravbb06q.js → chunk-dxvypr7y.js} +2 -2
  15. package/dist/{chunk-11v28977.js → chunk-f6yme003.js} +85 -71
  16. package/dist/{chunk-11v28977.js.map → chunk-f6yme003.js.map} +6 -6
  17. package/dist/{chunk-4ddzsqtc.js → chunk-hg7pf34j.js} +4 -3
  18. package/dist/{chunk-4ddzsqtc.js.map → chunk-hg7pf34j.js.map} +5 -5
  19. package/dist/{chunk-w1zyhj40.js → chunk-jq4e17tj.js} +2 -2
  20. package/dist/{chunk-kvcep7ft.js → chunk-y63c6zcv.js} +967 -218
  21. package/dist/chunk-y63c6zcv.js.map +53 -0
  22. package/dist/cli/args.d.ts +7 -1
  23. package/dist/cli/bin.js +66 -25
  24. package/dist/cli/bin.js.map +6 -5
  25. package/dist/cli/manifest.d.ts +5 -1
  26. package/dist/cli/texture-cache.d.ts +4 -0
  27. package/dist/core/definition.d.ts +7 -0
  28. package/dist/core/errors.d.ts +1 -1
  29. package/dist/core/index.js +1 -1
  30. package/dist/export/credits.d.ts +4 -0
  31. package/dist/export/glb-options.d.ts +48 -0
  32. package/dist/export/glb.d.ts +5 -35
  33. package/dist/export/hierarchy.d.ts +23 -0
  34. package/dist/export/index.d.ts +2 -1
  35. package/dist/export/index.js +21 -7
  36. package/dist/export/index.js.map +1 -1
  37. package/dist/export/lod-coverage.d.ts +12 -0
  38. package/dist/export/metadata.d.ts +2 -0
  39. package/dist/export/version.d.ts +2 -2
  40. package/dist/grammar/index.js +2 -2
  41. package/dist/index.js +63 -17
  42. package/dist/index.js.map +1 -1
  43. package/dist/kinds/head-armour.d.ts +3 -7
  44. package/dist/kinds/head-bust.d.ts +3 -2
  45. package/dist/kinds/head-face.d.ts +3 -1
  46. package/dist/kinds/head-garb.d.ts +4 -0
  47. package/dist/kinds/head-mesh.d.ts +4 -0
  48. package/dist/kinds/head-mouth.d.ts +4 -0
  49. package/dist/kinds/head-params.d.ts +9 -1
  50. package/dist/kinds/head-tone.d.ts +10 -0
  51. package/dist/kinds/head-torso.d.ts +35 -0
  52. package/dist/kinds/index.js +6 -6
  53. package/dist/material/cc0.d.ts +4 -0
  54. package/dist/material/define.d.ts +34 -0
  55. package/dist/material/generator.d.ts +1 -1
  56. package/dist/material/index.d.ts +8 -3
  57. package/dist/material/index.js +39 -7
  58. package/dist/material/index.js.map +1 -1
  59. package/dist/material/library.d.ts +7 -0
  60. package/dist/material/material-def.d.ts +30 -0
  61. package/dist/material/paint/canvas.d.ts +7 -0
  62. package/dist/material/paint/default-sheet.d.ts +3 -1
  63. package/dist/material/paint/fabric.d.ts +4 -0
  64. package/dist/material/paint/image-strip.d.ts +4 -0
  65. package/dist/material/paint/kit.d.ts +5 -0
  66. package/dist/material/paint/metal.d.ts +3 -0
  67. package/dist/material/paint/orm.d.ts +1 -1
  68. package/dist/material/paint/strip.d.ts +1 -1
  69. package/dist/material/paint/types.d.ts +32 -2
  70. package/dist/material/palette.d.ts +2 -0
  71. package/dist/material/slots.d.ts +5 -0
  72. package/dist/material/texture-source.d.ts +34 -0
  73. package/dist/material/trim-sheet.d.ts +20 -4
  74. package/dist/neural/index.js +2 -2
  75. package/dist/passes/index.js +2 -2
  76. package/dist/primitives/index.js +3 -3
  77. package/dist/qa/index.js +2 -2
  78. package/dist/render/beauty.d.ts +1 -1
  79. package/dist/render/index.js +3 -3
  80. package/dist/render/lighting.d.ts +7 -0
  81. package/dist/render/shade.d.ts +1 -1
  82. package/dist/testing/index.js +5 -5
  83. package/docs/README.md +4 -0
  84. package/docs/architecture/gltf-capabilities.md +38 -1
  85. package/docs/architecture/materials.md +151 -0
  86. package/docs/idea/10-sculpting.md +166 -0
  87. package/docs/idea/11-materials-authoring.md +132 -0
  88. package/docs/idea/12-blender-bridge.md +136 -0
  89. package/docs/idea/13-asset-pipeline-research.md +89 -0
  90. package/docs/plans/roadmap.md +12 -0
  91. package/package.json +2 -1
  92. package/recipes/boxcar/README.md +7 -0
  93. package/recipes/boxcar/boxcar.ts +118 -34
  94. package/recipes/goblin_head/README.md +27 -14
  95. package/recipes/goblin_head/goblin_head.ts +11 -9
  96. package/recipes/human_head/README.md +8 -4
  97. package/recipes/human_head/human_head.ts +4 -3
  98. package/recipes/material_swatches/material_swatches.ts +11 -5
  99. package/recipes/orc_head/README.md +32 -20
  100. package/recipes/orc_head/orc_head.ts +9 -7
  101. package/src/cli/args.ts +16 -3
  102. package/src/cli/manifest.ts +6 -0
  103. package/src/cli/run.ts +33 -9
  104. package/src/cli/texture-cache.ts +24 -0
  105. package/src/core/asset.ts +1 -0
  106. package/src/core/definition.ts +7 -0
  107. package/src/core/errors.ts +6 -1
  108. package/src/export/credits.ts +25 -0
  109. package/src/export/glb-options.ts +53 -0
  110. package/src/export/glb.ts +100 -113
  111. package/src/export/hierarchy.ts +122 -0
  112. package/src/export/index.ts +17 -1
  113. package/src/export/lod-coverage.ts +71 -0
  114. package/src/export/metadata.ts +3 -0
  115. package/src/export/version.ts +1 -1
  116. package/src/kinds/head-armour.ts +103 -73
  117. package/src/kinds/head-bust.ts +49 -86
  118. package/src/kinds/head-face.ts +94 -74
  119. package/src/kinds/head-garb.ts +181 -0
  120. package/src/kinds/head-hair.ts +74 -33
  121. package/src/kinds/head-mesh.ts +43 -5
  122. package/src/kinds/head-mouth.ts +111 -0
  123. package/src/kinds/head-params.ts +11 -2
  124. package/src/kinds/head-presets.ts +30 -27
  125. package/src/kinds/head-tone.ts +52 -0
  126. package/src/kinds/head-torso.ts +230 -0
  127. package/src/kinds/head.ts +8 -3
  128. package/src/material/cc0.ts +41 -0
  129. package/src/material/define.ts +130 -0
  130. package/src/material/generator.ts +15 -8
  131. package/src/material/index.ts +34 -3
  132. package/src/material/library.ts +196 -0
  133. package/src/material/material-def.ts +84 -0
  134. package/src/material/paint/canvas.ts +11 -1
  135. package/src/material/paint/compose.ts +1 -1
  136. package/src/material/paint/decode-png.ts +19 -13
  137. package/src/material/paint/default-sheet.ts +40 -28
  138. package/src/material/paint/fabric.ts +63 -0
  139. package/src/material/paint/image-strip.ts +88 -0
  140. package/src/material/paint/kit.ts +37 -0
  141. package/src/material/paint/masks.ts +3 -1
  142. package/src/material/paint/metal.ts +109 -0
  143. package/src/material/paint/orm.ts +14 -1
  144. package/src/material/paint/planks.ts +49 -4
  145. package/src/material/paint/strip.ts +35 -29
  146. package/src/material/paint/types.ts +45 -2
  147. package/src/material/paint/validate.ts +19 -3
  148. package/src/material/palette.ts +3 -0
  149. package/src/material/slots.ts +18 -0
  150. package/src/material/texture-source.ts +86 -0
  151. package/src/material/trim-sheet.ts +79 -13
  152. package/src/render/beauty.ts +2 -2
  153. package/src/render/lighting.ts +26 -0
  154. package/src/render/shade.ts +10 -5
  155. package/dist/chunk-1ypfs7ga.js.map +0 -42
  156. package/dist/chunk-gcyp9b4d.js.map +0 -22
  157. package/dist/chunk-kgvbpqqw.js.map +0 -10
  158. package/dist/chunk-kvcep7ft.js.map +0 -43
  159. /package/dist/{chunk-a8fxakcj.js.map → chunk-6b05mnmx.js.map} +0 -0
  160. /package/dist/{chunk-xnt2sdhf.js.map → chunk-drna2rny.js.map} +0 -0
  161. /package/dist/{chunk-ravbb06q.js.map → chunk-dxvypr7y.js.map} +0 -0
  162. /package/dist/{chunk-w1zyhj40.js.map → chunk-jq4e17tj.js.map} +0 -0
@@ -15,9 +15,11 @@ import {
15
15
  lathe,
16
16
  type Mesh,
17
17
  merge,
18
+ NATURAL,
18
19
  noOverlap,
19
20
  place,
20
21
  prism,
22
+ type StripId,
21
23
  symmetric,
22
24
  text,
23
25
  tiled,
@@ -46,10 +48,10 @@ const DOOR_Z0 = 1.15;
46
48
  const DOOR_Z1 = 3.64;
47
49
  const JOURNAL_Y = 0.925; // axle end = journal box inner face
48
50
 
49
- const steel = hex('#34302b');
50
- const iron = hex('#1d1c1b');
51
- const wheelC = hex('#2a2826');
52
- const walk = hex('#5d4c3b');
51
+ const steel = hex('#a59f97'); // tints iron_cast: dark, sand-cast trucks and gear
52
+ const iron = hex('#a8a29c'); // tints iron_rust: weathered hardware
53
+ const wheelC = hex('#948f8a');
54
+ const walk = NATURAL; // wood_weathered carries its own silver-grey
53
55
 
54
56
  interface Params {
55
57
  /** Large road name left of the door. */
@@ -71,7 +73,7 @@ function bx(
71
73
  z0: number,
72
74
  z1: number,
73
75
  color: Vec3,
74
- strip: 'plain' | 'iron' | 'wood' = 'iron',
76
+ strip: StripId = 'iron_cast',
75
77
  bevel = 0,
76
78
  ): Mesh {
77
79
  const [a, b] = [Math.min(x0, x1), Math.max(x0, x1)];
@@ -88,7 +90,7 @@ function bx(
88
90
  }
89
91
 
90
92
  const rod = (from: Vec3, to: Vec3, w: number, color: Vec3 = iron, segments = 0): Mesh =>
91
- beam({ from, to, width: w, color, strip: 'iron', ...(segments ? { segments } : {}) });
93
+ beam({ from, to, width: w, color, strip: 'iron_rust', ...(segments ? { segments } : {}) });
92
94
 
93
95
  // ---------------------------------------------------------------- running gear
94
96
  /** One wheel's tread disc (exactly 33 in) at lateral y, outer face toward ±y. */
@@ -101,6 +103,7 @@ function tread(lod: Lod, y: number): Mesh {
101
103
  axis: 'y',
102
104
  at: [0, y, 0],
103
105
  color: wheelC,
106
+ strip: 'iron_cast',
104
107
  });
105
108
  const s = Math.sign(y);
106
109
  const segs = [24, 14, 8][lod] ?? 8;
@@ -114,7 +117,7 @@ function tread(lod: Lod, y: number): Mesh {
114
117
  segments: segs,
115
118
  axis: 'y',
116
119
  color: wheelC,
117
- strip: 'iron',
120
+ strip: 'iron_cast',
118
121
  });
119
122
  }
120
123
 
@@ -130,6 +133,7 @@ function wheelset(lod: Lod): Mesh {
130
133
  axis: 'y',
131
134
  at: [0, s * (WHEEL_Y + TREAD_W / 2 + 0.025), 0],
132
135
  color: wheelC,
136
+ strip: 'iron_cast',
133
137
  });
134
138
  const shaft = cylinder({
135
139
  radius: 0.075,
@@ -137,6 +141,7 @@ function wheelset(lod: Lod): Mesh {
137
141
  segments: 4,
138
142
  axis: 'y',
139
143
  color: wheelC,
144
+ strip: 'iron_cast',
140
145
  });
141
146
  return merge([shaft, hub(1), hub(-1)]);
142
147
  }
@@ -148,6 +153,7 @@ function wheelset(lod: Lod): Mesh {
148
153
  segments: Math.max(6, segs / 2),
149
154
  axis: 'y',
150
155
  color: wheelC,
156
+ strip: 'iron_cast',
151
157
  }),
152
158
  ];
153
159
  for (const s of [1, -1]) {
@@ -163,6 +169,7 @@ function wheelset(lod: Lod): Mesh {
163
169
  segments: segs,
164
170
  axis: 'y',
165
171
  color: wheelC,
172
+ strip: 'iron_cast',
166
173
  caps: false,
167
174
  }),
168
175
  );
@@ -177,6 +184,7 @@ function wheelset(lod: Lod): Mesh {
177
184
  axis: 'y',
178
185
  at: [0, yo + s * 0.02, 0],
179
186
  color: wheelC,
187
+ strip: 'iron_cast',
180
188
  }),
181
189
  );
182
190
  for (let k = 0; k < 6; k++) {
@@ -232,7 +240,7 @@ function truckFrame(lod: Lod, bevel: number): Mesh {
232
240
  r - 0.09,
233
241
  r + 0.12,
234
242
  steel,
235
- 'iron',
243
+ 'iron_cast',
236
244
  bevel,
237
245
  ),
238
246
  );
@@ -250,7 +258,17 @@ function truckFrame(lod: Lod, bevel: number): Mesh {
250
258
  ); // lid
251
259
  }
252
260
  out.push(
253
- bx(ax[0] - 0.16, ax[1] + 0.16, y - 0.06, y + 0.06, r + 0.12, zt + 0.07, steel, 'iron', bevel),
261
+ bx(
262
+ ax[0] - 0.16,
263
+ ax[1] + 0.16,
264
+ y - 0.06,
265
+ y + 0.06,
266
+ r + 0.12,
267
+ zt + 0.07,
268
+ steel,
269
+ 'iron_cast',
270
+ bevel,
271
+ ),
254
272
  ); // top member
255
273
  if (lod < 2) {
256
274
  const p: [number, number][] = [
@@ -275,6 +293,7 @@ function truckFrame(lod: Lod, bevel: number): Mesh {
275
293
  segments: coilSegs,
276
294
  at: [d, y, zb + 0.125],
277
295
  color: iron,
296
+ strip: 'iron_rust',
278
297
  }),
279
298
  );
280
299
  if (lod === 0)
@@ -286,6 +305,7 @@ function truckFrame(lod: Lod, bevel: number): Mesh {
286
305
  segments: coilSegs,
287
306
  at: [d, y, zb + 0.06 + k * 0.042],
288
307
  color: iron,
308
+ strip: 'iron_rust',
289
309
  }),
290
310
  );
291
311
  }
@@ -298,7 +318,17 @@ function truckFrame(lod: Lod, bevel: number): Mesh {
298
318
  }
299
319
  // Bolster on the spring nests, centre plate up to the car's body bolster.
300
320
  out.push(
301
- bx(-0.15, 0.15, -(JOURNAL_Y + 0.12), JOURNAL_Y + 0.12, zb + 0.21, 0.6, steel, 'iron', bevel),
321
+ bx(
322
+ -0.15,
323
+ 0.15,
324
+ -(JOURNAL_Y + 0.12),
325
+ JOURNAL_Y + 0.12,
326
+ zb + 0.21,
327
+ 0.6,
328
+ steel,
329
+ 'iron_cast',
330
+ bevel,
331
+ ),
302
332
  );
303
333
  out.push(
304
334
  cylinder({
@@ -307,6 +337,7 @@ function truckFrame(lod: Lod, bevel: number): Mesh {
307
337
  segments: [12, 8, 6][lod] ?? 6,
308
338
  at: [0, 0, 0.62],
309
339
  color: steel,
340
+ strip: 'iron_cast',
310
341
  }),
311
342
  );
312
343
  if (lod < 2)
@@ -454,15 +485,29 @@ const usra = defineFamily({
454
485
 
455
486
  // Underframe: straight centre sill, side sills, end sills, body bolsters, crossbearers, floor.
456
487
  const uf: Mesh[] = [
457
- bx(-XB - 0.04, XB + 0.04, -0.2, 0.2, 0.72, 1.1, steel, 'iron', bevel),
458
- bx(-XB, XB, -SHEATH_Y + 0.02, SHEATH_Y - 0.02, 1.1, FLOOR_Z, walk, 'wood'),
488
+ bx(-XB - 0.04, XB + 0.04, -0.2, 0.2, 0.72, 1.1, steel, 'iron_cast', bevel),
489
+ bx(-XB, XB, -SHEATH_Y + 0.02, SHEATH_Y - 0.02, 1.1, FLOOR_Z, walk, 'wood_weathered'),
459
490
  ];
460
491
  for (const s of [-1, 1])
461
- uf.push(bx(-XB, XB, s * SHEATH_Y, s * (FRAME_Y - 0.01), 0.95, 1.18, steel, 'iron', bevel));
492
+ uf.push(
493
+ bx(-XB, XB, s * SHEATH_Y, s * (FRAME_Y - 0.01), 0.95, 1.18, steel, 'iron_cast', bevel),
494
+ );
462
495
  for (const d of [-1, 1]) {
463
- uf.push(bx(d * XB, d * (XB + 0.05), -FRAME_Y, FRAME_Y, 0.74, 1.18, steel, 'iron', bevel)); // end sill / striker
464
496
  uf.push(
465
- bx(d * (TC / 2) - 0.2, d * (TC / 2) + 0.2, -1.25, 1.25, 0.64, 1.1, steel, 'iron', bevel),
497
+ bx(d * XB, d * (XB + 0.05), -FRAME_Y, FRAME_Y, 0.74, 1.18, steel, 'iron_cast', bevel),
498
+ ); // end sill / striker
499
+ uf.push(
500
+ bx(
501
+ d * (TC / 2) - 0.2,
502
+ d * (TC / 2) + 0.2,
503
+ -1.25,
504
+ 1.25,
505
+ 0.64,
506
+ 1.1,
507
+ steel,
508
+ 'iron_cast',
509
+ bevel,
510
+ ),
466
511
  ); // body bolster
467
512
  if (lod < 2)
468
513
  for (const x of [1.6, 3.0])
@@ -482,7 +527,7 @@ const usra = defineFamily({
482
527
  u: [0, 0, EAVE_Z - boardsZ0],
483
528
  v,
484
529
  color: paint,
485
- strip: lod === 2 ? 'plain' : 'wood',
530
+ strip: lod === 2 ? 'plain' : 'wood_painted',
486
531
  vMetres: lod === 2 ? BODY_L : 0.6,
487
532
  }),
488
533
  );
@@ -501,7 +546,9 @@ const usra = defineFamily({
501
546
  );
502
547
  }
503
548
  for (const d of [-1, 1])
504
- bodyM.push(bx(d * (XB - 0.05), d * XB, -SHEATH_Y, SHEATH_Y, 1.0, EAVE_Z, paint, 'iron'));
549
+ bodyM.push(
550
+ bx(d * (XB - 0.05), d * XB, -SHEATH_Y, SHEATH_Y, 1.0, EAVE_Z, paint, 'steel_painted'),
551
+ );
505
552
  ctx.part('body', merge(bodyM));
506
553
 
507
554
  // Outside steel framing: posts, diagonal braces leaning toward the door, top plate, end ribs.
@@ -515,11 +562,20 @@ const usra = defineFamily({
515
562
  for (const d of [-1, 1]) {
516
563
  for (const px of lod === 2 ? [] : posts) {
517
564
  const x = d * px;
518
- fr.push(bx(x - postW / 2, x + postW / 2, y0, y1, 1.0, EAVE_Z, paint, 'iron'));
565
+ fr.push(bx(x - postW / 2, x + postW / 2, y0, y1, 1.0, EAVE_Z, paint, 'steel_painted'));
519
566
  }
520
567
  // Door posts.
521
568
  fr.push(
522
- bx(d * (DOOR_W / 2 + 0.01), d * (DOOR_W / 2 + 0.1), y0, y1, 1.0, EAVE_Z, paint, 'iron'),
569
+ bx(
570
+ d * (DOOR_W / 2 + 0.01),
571
+ d * (DOOR_W / 2 + 0.1),
572
+ y0,
573
+ y1,
574
+ 1.0,
575
+ EAVE_Z,
576
+ paint,
577
+ 'steel_painted',
578
+ ),
523
579
  );
524
580
  if (lod < 2)
525
581
  for (let i = 0; i < 4; i++) {
@@ -533,13 +589,15 @@ const usra = defineFamily({
533
589
  width: 0.075,
534
590
  height: 0.06,
535
591
  color: paint,
536
- strip: 'iron',
592
+ strip: 'steel_painted',
537
593
  up: [0, s, 0],
538
594
  }),
539
595
  );
540
596
  }
541
597
  }
542
- fr.push(bx(-XB, XB, y0, s * (FRAME_Y + 0.01), EAVE_Z - 0.16, EAVE_Z, paint, 'iron', bevel)); // side plate
598
+ fr.push(
599
+ bx(-XB, XB, y0, s * (FRAME_Y + 0.01), EAVE_Z - 0.16, EAVE_Z, paint, 'steel_painted', bevel),
600
+ ); // side plate
543
601
  if (lod === 0) {
544
602
  // Rivet heads where braces meet posts and sills.
545
603
  const rivet = cylinder({
@@ -569,7 +627,7 @@ const usra = defineFamily({
569
627
  1.0,
570
628
  EAVE_Z,
571
629
  paint,
572
- 'iron',
630
+ 'steel_painted',
573
631
  ),
574
632
  );
575
633
  if (lod < 2)
@@ -584,7 +642,7 @@ const usra = defineFamily({
584
642
  z - 0.05,
585
643
  z + 0.05,
586
644
  paint,
587
- 'iron',
645
+ 'steel_painted',
588
646
  ),
589
647
  );
590
648
  }
@@ -606,7 +664,7 @@ const usra = defineFamily({
606
664
  depth: BODY_L + 0.1,
607
665
  plane: 'yz',
608
666
  color: paintDark,
609
- strip: 'iron',
667
+ strip: 'steel_painted',
610
668
  }),
611
669
  ];
612
670
  if (lod === 0) {
@@ -627,12 +685,14 @@ const usra = defineFamily({
627
685
  ctx.part('roof', merge(roofM));
628
686
 
629
687
  const walkM: Mesh[] = [
630
- bx(-XB - 0.12, XB + 0.12, -0.255, 0.255, WALK_TOP - 0.045, WALK_TOP, walk, 'wood'),
688
+ bx(-XB - 0.12, XB + 0.12, -0.255, 0.255, WALK_TOP - 0.045, WALK_TOP, walk, 'wood_weathered'),
631
689
  ];
632
690
  const saddles = lod === 2 ? 3 : 11;
633
691
  for (let i = 0; i < saddles; i++) {
634
692
  const x = -XB + 0.3 + ((BODY_L - 0.6) * i) / (saddles - 1);
635
- walkM.push(bx(x - 0.05, x + 0.05, -0.2, 0.2, RIDGE_Z - 0.03, WALK_TOP - 0.045, walk, 'wood'));
693
+ walkM.push(
694
+ bx(x - 0.05, x + 0.05, -0.2, 0.2, RIDGE_Z - 0.03, WALK_TOP - 0.045, walk, 'wood_weathered'),
695
+ );
636
696
  }
637
697
  if (lod < 2)
638
698
  for (const d of [-1, 1]) {
@@ -647,7 +707,7 @@ const usra = defineFamily({
647
707
  WALK_TOP - 0.045,
648
708
  WALK_TOP,
649
709
  walk,
650
- 'wood',
710
+ 'wood_weathered',
651
711
  ),
652
712
  );
653
713
  walkM.push(
@@ -659,7 +719,7 @@ const usra = defineFamily({
659
719
  EAVE_Z,
660
720
  WALK_TOP - 0.045,
661
721
  walk,
662
- 'wood',
722
+ 'wood_weathered',
663
723
  ),
664
724
  );
665
725
  }
@@ -679,15 +739,26 @@ const usra = defineFamily({
679
739
  u: [0, 0, DOOR_Z1 - DOOR_Z0],
680
740
  v: [s > 0 ? -DOOR_W : DOOR_W, 0, 0],
681
741
  color: paint,
682
- strip: lod === 2 ? 'plain' : 'wood',
742
+ strip: lod === 2 ? 'plain' : 'wood_painted',
683
743
  }),
684
- bx(-DOOR_W / 2, DOOR_W / 2, y0, y1 - s * 0.003, DOOR_Z0, DOOR_Z1, paintDark, 'plain'),
744
+ bx(
745
+ -DOOR_W / 2,
746
+ DOOR_W / 2,
747
+ y0,
748
+ y1 - s * 0.003,
749
+ DOOR_Z0,
750
+ DOOR_Z1,
751
+ paintDark,
752
+ 'wood_painted',
753
+ ),
685
754
  ];
686
755
  if (lod < 2) {
687
756
  for (const z of [DOOR_Z0, (DOOR_Z0 + DOOR_Z1) / 2 - 0.05, DOOR_Z1 - 0.1])
688
- dm.push(bx(-DOOR_W / 2, DOOR_W / 2, y1, y1 + s * 0.015, z, z + 0.1, paint, 'iron')); // steel straps
757
+ dm.push(
758
+ bx(-DOOR_W / 2, DOOR_W / 2, y1, y1 + s * 0.015, z, z + 0.1, paint, 'steel_painted'),
759
+ ); // steel straps
689
760
  for (const x of [-DOOR_W / 2, DOOR_W / 2 - 0.08])
690
- dm.push(bx(x, x + 0.08, y1, y1 + s * 0.015, DOOR_Z0, DOOR_Z1, paint, 'iron'));
761
+ dm.push(bx(x, x + 0.08, y1, y1 + s * 0.015, DOOR_Z0, DOOR_Z1, paint, 'steel_painted'));
691
762
  }
692
763
  if (lod === 0) dm.push(bx(-0.75, -0.65, y1 + s * 0.015, y1 + s * 0.06, 2.0, 2.2, iron)); // handle / latch
693
764
  ctx.part(`door_${side}`, dirt(merge(dm)), {
@@ -733,10 +804,20 @@ const usra = defineFamily({
733
804
  COUPLER_Z - 0.07,
734
805
  COUPLER_Z + 0.07,
735
806
  iron,
736
- 'iron',
807
+ 'iron_rust',
808
+ bevel,
809
+ ),
810
+ bx(
811
+ d * 6.5,
812
+ d * 6.7,
813
+ -0.17,
814
+ 0.12,
815
+ COUPLER_Z - 0.13,
816
+ COUPLER_Z + 0.13,
817
+ iron,
818
+ 'iron_rust',
737
819
  bevel,
738
820
  ),
739
- bx(d * 6.5, d * 6.7, -0.17, 0.12, COUPLER_Z - 0.13, COUPLER_Z + 0.13, iron, 'iron', bevel),
740
821
  ];
741
822
  if (lod < 2) cm.push(bx(d * 6.6, d * 6.7, 0.12, 0.2, COUPLER_Z - 0.1, COUPLER_Z + 0.1, iron)); // knuckle
742
823
  if (lod === 0) {
@@ -810,6 +891,7 @@ const usra = defineFamily({
810
891
  ],
811
892
  segments: wheelSegs,
812
893
  color: iron,
894
+ strip: 'iron_rust',
813
895
  at: [-(XB + 0.13), -0.85, 0],
814
896
  }),
815
897
  );
@@ -836,6 +918,7 @@ const usra = defineFamily({
836
918
  axis: 'x',
837
919
  at: [-0.9, 0.55, 0.9],
838
920
  color: iron,
921
+ strip: 'iron_rust',
839
922
  }),
840
923
  cylinder({
841
924
  radius: 0.16,
@@ -844,6 +927,7 @@ const usra = defineFamily({
844
927
  axis: 'x',
845
928
  at: [0.4, -0.5, 0.92],
846
929
  color: iron,
930
+ strip: 'iron_rust',
847
931
  }),
848
932
  bx(-0.25, -0.05, 0.2, 0.42, 0.85, 1.1, iron),
849
933
  rod([-0.35, 0.55, 1.08], [-0.35, 0.2, 1.08], 0.05, iron),
@@ -2,20 +2,25 @@
2
2
 
3
3
  ## Identity
4
4
  A sly, scrawny goblin tinker, read from the 3/4 view by: **huge leaf-shaped ears** wider than its
5
- shoulders, a **long humped nose**, a **wide toothy grin**, **big amber eyes** under a light brow,
6
- a few **wiry hair tufts** and warts on a bald dome, and a **draped hood** on narrow shoulders.
5
+ shoulders, a **long hooked nose** (one form drawn out to a drooping point, nostrils tucked at its
6
+ base), a **wide grin** of small pointed teeth with the cheeks bunched up beside it, **big amber eyes
7
+ under heavy upper lids**, a **worried creased brow**, a **sharp little chin**, **pale wispy hair**
8
+ straggling off a warty dome, a **skinny corded neck** on **narrow bony shoulders** in a linen shirt,
9
+ leather vest, strap and buckle, a **knotted scarf** and a **hood worn down** on the back — cut as a
10
+ collectible bust on a turned plinth.
7
11
 
8
12
  ## Design spec (head units: 1 = chin to crown = 0.19 m, ≈ 0.8 × human)
9
13
  | Feature | Value (units) | Metres | Note |
10
14
  |---|---|---|---|
11
15
  | chin → crown | 1.00 | 0.190 | the `height` param |
12
- | overall bust L × W × H | — | 0.236 × 0.322 × 0.366 | `real`, ±5 %; width is the ears |
16
+ | overall bust L × W × H | — | 0.227 × 0.342 × 0.372 | `real`, ±5 %; width is the ears |
13
17
  | cranium width / depth / above eye line | 0.66 / 0.84 / 0.50 | 0.125 / 0.160 / 0.095 | |
14
- | eyeball radius / spacing | 0.072 / 0.15 | 0.014 / pupils 0.057 apart | eyes 1.4 × human relative size |
15
- | ears length / width / sweep / lift | 0.62 / 0.30 / 22° / 18° | 0.118 long | two tapered blades + hollow |
16
- | nose length / bridge / hook | 0.20 / 0.12 / 0.07 | tip ≈ 0.05 past the face | cone from between the eyes |
17
- | mouth half-width / grin | 0.20 / 0.75 | | corners lifted, teeth rows |
18
- | neck radius / shoulders | 0.20 / 0.75 | 0.038 / 0.143 | |
18
+ | eyeball radius / spacing / upper lid | 0.08 / 0.155 / 30° | 0.015 / pupils 0.059 apart | eyes 1.5 × human relative size, heavy lids |
19
+ | ears length / width / sweep / lift | 0.66 / 0.30 / 24° / 6° | 0.125 long | two tapered blades + hollow, near level |
20
+ | nose length / bridge / hook / tip | 0.20 / 0.13 / 0.065 / 0.046 | | bridge cone + hump + tip + drooping point cone |
21
+ | mouth half-width / grin | 0.21 / 0.80 | | crescent cut, 3 + 3 pointed teeth a side, tongue |
22
+ | forehead creases / chin | 3 / 0.72 | | creases painted (thinner than a cell) |
23
+ | neck radius / shoulders / mass | 0.14 / 0.52 / 0 | 0.027 / 0.099 | bony: clavicles under the shirt |
19
24
 
20
25
  ## Palette (sRGB)
21
26
  | Role | Hex |
@@ -23,8 +28,9 @@ a few **wiry hair tufts** and warts on a bald dome, and a **draped hood** on nar
23
28
  | skin / shade / lips | `#9cb84a` / `#6a8a2c` / `#7a8e36` |
24
29
  | eyes: sclera / iris | `#f0e6a0` / `#e0a020` |
25
30
  | teeth / mouth | `#e8dcae` / `#3a1010` |
26
- | hair tufts | `#3a3024` |
27
- | hood cloth / patch + cord leather | `#6a4a7a` / `#5a3a22` |
31
+ | wispy hair | `#c8c0b0` |
32
+ | linen shirt / vest + strap (×0.65) / scarf / hood / buckle | `#b8a47c` / `#6b4428` / `#9a3a24` / `#4e5e3a` / `#a88a4a` |
33
+ | plinth / cut faces | `#6f6c6c` (linear 0.16, 0.15, 0.15) / ×1.3 |
28
34
 
29
35
  ## Parts the engine needs
30
36
  | Part | Pivot | Motion |
@@ -39,8 +45,11 @@ along the lips, the marionette lines and under the chin. Its cut faces are paint
39
45
  when it opens; it laps 0.75 cells over the head so no gap shows.
40
46
 
41
47
  ## LODs
42
- Sculpt cell = 2 % / 4.5 % / 14 % of head height. LOD0/1 bake vertex AO across all parts (16/10 rays); LOD2 drops
43
- warts, wiry tufts and the AO bake, and floors ear thickness at 0.7 cells so ears stay attached.
48
+ Sculpt cell = 2 % / 4.5 % / 14 % of head height (bust 1.6 ×). LOD0/1 bake vertex AO across all
49
+ parts (16/10 rays); every LOD carries the painted tone. LOD1/2 carve the grin as a plain wide line
50
+ (no teeth or tongue: a crescent that thin folds over at coarse cells); LOD2 drops warts, hair and
51
+ the AO bake, floors ear thickness and the nose's radii at about a cell, and makes the plinth one
52
+ drum so the bust stays on the ground.
44
53
 
45
54
  ## References (stylised anatomy, not likeness)
46
55
  | Topic | Link | Used for |
@@ -48,6 +57,9 @@ warts, wiry tufts and the AO bake, and floors ear thickness at 0.7 cells so ears
48
57
  | Loomis head: eye line at half height, brow/nose/chin thirds | https://finearttutorials.com/guide/loomis-method/ | human eye line 0.5, nose base 0.32, mouth 0.22 |
49
58
  | Proportions of the head (ears between brow and nose base, head ≈ 5 eyes wide) | https://artyfactory.com/portraits/pencil-portraits/proportions-of-a-head.html | ear placement, eye size vs head width |
50
59
  | Primary → secondary → tertiary forms (block brow/cheek/jaw first, then cut lids and folds) | https://www.creativebloq.com/how-to/how-to-sculpt-and-pose-a-cartoon-head-in-zbrush | the edit order in `head-face.ts` |
60
+ | Goblin from a base head: enlarge the top of the head, elongate the ears, broaden their base | https://create.roblox.com/docs/art/characters/creating/sculpting | cranium and ear roots |
61
+ | Goblin sculpt breakdown: big nose, cracked grin, ears, sparse hair | https://www.foxrenderfarm.com/share/how-to-create-goblin-in-zbrush | feature priorities |
62
+ | Goblin features: large pointed ears, mischievous grin with visible teeth | https://sketchok.com/other/monsters-and-fantasy-creatures/how-to-draw-a-goblin/ | grin + teeth |
51
63
  | Orc: widen head and jawline to break human proportion | https://www.creativebloq.com/3d/how-create-orc-zbrush-61411834 | orc jaw width 0.37 vs human 0.22 |
52
64
  | Stylised orc head study (silhouette, form language) | https://blenderartists.org/t/orc-head-study-stylized-characters/1655542 | massing check |
53
65
  | Stylised hair: large clumps first, hairline → crown, one primary flow direction | https://gamesartist.co.uk/curly-hair-ls/ · https://www.cgchannel.com/2023/03/tutorial-creating-stylized-hairstyles-using-zbrush | `swept` locks in head-hair.ts |
@@ -58,5 +70,6 @@ hand-painted stylised fantasy" is the idiom (exaggerated primary forms, generous
58
70
  vertex colour with baked AO), not a specific source.
59
71
 
60
72
  ## Known gaps
61
- Teeth sit deep in the grin and read only up close; no earrings or wrinkles; hood is a smooth roll
62
- with one patch.
73
+ Teeth are small at this scale and read best in the 3/4 view; the wisps are cone pairs, not hair
74
+ cards; the jaw part's seam shows as a faint line at the mouth corners up close; garment edges are
75
+ vertex-colour soft and the strap/buckle are simple shells.
@@ -1,7 +1,9 @@
1
1
  // Stylised goblin bust — an original design in the chunky hand-painted fantasy idiom: huge leaf
2
- // ears, long humped nose, wide toothy grin, big amber eyes, a few wiry hair tufts, warts, and a
3
- // draped hood on narrow shoulders. The `head` kind sculpts it (race preset `goblin`, chin to crown
4
- // 0.19 m); this file is the spec. Design dossier: recipes/goblin_head/README.md.
2
+ // ears, a long hooked nose, a wide grin of small pointed teeth, big amber eyes under heavy lids, a
3
+ // worried creased brow, pale wispy hair, warts; a skinny neck and bony shoulders in a linen shirt,
4
+ // leather vest, strap, knotted scarf and a hood worn down, cut as a collectible bust on a turned
5
+ // plinth. The `head` kind sculpts it (race preset `goblin`, chin to crown 0.19 m); this file is the
6
+ // spec. Design dossier: recipes/goblin_head/README.md.
5
7
 
6
8
  import { defineAsset, head } from '@developerz.ai/ts23d';
7
9
 
@@ -9,22 +11,22 @@ export const goblin_head = defineAsset({
9
11
  name: 'goblin_head',
10
12
  kind: 'character',
11
13
  description:
12
- 'Stylised goblin bust: huge pointed ears, long humped nose, toothy grin, amber eyes, hood',
14
+ 'Stylised goblin bust on a plinth: huge pointed ears, long hooked nose, toothy grin, heavy-lidded amber eyes, wispy hair, leather vest and scarf',
13
15
  tags: ['character', 'fantasy', 'goblin', 'bust', 'sculpt'],
14
16
  license: 'CC0-1.0',
15
17
  sources: ['recipes/goblin_head/README.md (design dossier: proportions, palette, references)'],
16
18
  // Design spec, not a real-world one: overall bust size and eye spacing from the dossier.
17
19
  real: {
18
- length: 0.236,
19
- width: 0.322,
20
- height: 0.366,
20
+ length: 0.227,
21
+ width: 0.342,
22
+ height: 0.372,
21
23
  measures: [
22
24
  {
23
25
  label: 'eye spacing (pupil to pupil)',
24
- value: 0.057,
26
+ value: 0.0589,
25
27
  axis: 'y',
26
28
  between: ['eye_l', 'eye_r'],
27
- source: '0.15 × 2 × 0.19 m',
29
+ source: '0.155 × 2 × 0.19 m',
28
30
  },
29
31
  ],
30
32
  },
@@ -5,13 +5,14 @@ A young adventurer, read from the 3/4 view by: **classical proportions pushed sl
5
5
  (square jaw, strong brow, thick neck), **defined arched eyebrows**, **swept-back hair** in chunky
6
6
  tapered two-tone locks over a rounded hairline, a **clipped beard with chin volume and a drooping
7
7
  moustache**, clear blue eyes, a **blue tunic** with a standing leather collar, placket and brass
8
- buttons, and a **red mantle** over the shoulders pinned with a brass brooch.
8
+ buttons, and a **red mantle** over the shoulders pinned with a brass brooch — cut as a collectible
9
+ bust (U-cut under the chest) on a turned plinth.
9
10
 
10
11
  ## Design spec (head units: 1 = chin to crown = 0.23 m, adult human)
11
12
  | Feature | Value (units) | Metres | Note |
12
13
  |---|---|---|---|
13
14
  | chin → crown | 1.00 | 0.230 | adult menton–vertex ≈ 0.22–0.24 m |
14
- | overall bust L × W × H | — | 0.247 × 0.464 × 0.439 | `real`, ±5 % |
15
+ | overall bust L × W × H | — | 0.247 × 0.428 × 0.439 | `real`, ±5 % |
15
16
  | cranium width / depth | 0.66 / 0.86 | 0.152 / 0.198 | adult head breadth ≈ 0.15 m, length ≈ 0.19–0.20 m |
16
17
  | eye line | 0.50 | — | Loomis: half height |
17
18
  | eyeball radius / spacing | 0.052 / 0.135 | 0.012 / pupils 0.062 apart | human eyeball ≈ 24 mm; IPD ≈ 62 mm |
@@ -19,6 +20,7 @@ buttons, and a **red mantle** over the shoulders pinned with a brass brooch.
19
20
  | jaw half-width | 0.22 | 0.051 | bigonial ≈ 0.10 m |
20
21
  | ears | round, 0.27 long | 0.062 | between brow and nose base |
21
22
  | neck radius | 0.27 | 0.062 | heroic: thicker than average |
23
+ | shoulders / mass | 0.95 / 0.5 (default) | 0.219 | pectorals, deltoids, trapezius under the tunic |
22
24
 
23
25
  ## Palette (sRGB)
24
26
  | Role | Hex |
@@ -27,6 +29,7 @@ buttons, and a **red mantle** over the shoulders pinned with a brass brooch.
27
29
  | eyes: sclera / iris | `#ece6dc` / `#3d6e9e` |
28
30
  | hair + beard (two-tone locks ×1.5) | `#5a3a22` |
29
31
  | tunic / mantle / collar, placket / brooch, buttons | `#2e4f8a` / `#7a2420` / `#6b4428` / `#c9a24a` |
32
+ | plinth / cut faces | `#6f6c6c` (linear 0.16, 0.15, 0.15) / ×1.3 |
30
33
 
31
34
  ## Parts the engine needs
32
35
  | Part | Pivot | Motion |
@@ -41,8 +44,9 @@ along the lips, the marionette lines and under the chin. Its cut faces are paint
41
44
  when it opens; it laps 0.75 cells over the head so no gap shows.
42
45
 
43
46
  ## LODs
44
- Sculpt cell = 2 % / 4.5 % / 14 % of head height. LOD0/1 bake vertex AO across all parts (16/10 rays); LOD2 drops
45
- warts, wiry tufts and the AO bake, and floors ear thickness at 0.7 cells so ears stay attached.
47
+ Sculpt cell = 2 % / 4.5 % / 14 % of head height (bust 1.6 ×). LOD0/1 bake vertex AO across all
48
+ parts (16/10 rays); every LOD carries the painted tone (cavity, warm top, cool under). LOD2 drops
49
+ the AO bake, floors ear thickness at 0.7 cells and makes the plinth one drum.
46
50
 
47
51
  ## References (stylised anatomy, not likeness)
48
52
  | Topic | Link | Used for |
@@ -1,8 +1,9 @@
1
1
  // Stylised human hero bust — an original design in the chunky hand-painted fantasy idiom:
2
2
  // classical proportions (eye line at half height, brow/nose/chin thirds) pushed slightly heroic —
3
3
  // square jaw, strong brow — with short swept hair, a short beard and moustache, a blue tunic,
4
- // leather collar and cloak roll. The `head` kind sculpts it (race preset `human`, chin to crown
5
- // 0.23 m); this file is the spec. Design dossier: recipes/human_head/README.md.
4
+ // leather collar and mantle, cut as a collectible bust on a turned plinth. The `head` kind sculpts
5
+ // it (race preset `human`, chin to crown 0.23 m); this file is the spec. Design dossier:
6
+ // recipes/human_head/README.md.
6
7
 
7
8
  import { defineAsset, head } from '@developerz.ai/ts23d';
8
9
 
@@ -17,7 +18,7 @@ export const human_head = defineAsset({
17
18
  // Design spec, not a real-world one: overall bust size and eye spacing from the dossier.
18
19
  real: {
19
20
  length: 0.247,
20
- width: 0.464,
21
+ width: 0.428,
21
22
  height: 0.439,
22
23
  measures: [
23
24
  {
@@ -19,6 +19,7 @@ import {
19
19
  type Mesh,
20
20
  MeshBuilder,
21
21
  merge,
22
+ NATURAL,
22
23
  STRIPS,
23
24
  type StripId,
24
25
  type Vec3,
@@ -26,7 +27,7 @@ import {
26
27
 
27
28
  const STONE = hex('#9c968c');
28
29
  const INK = hex('#1c1c1c');
29
- const TINT: Readonly<Record<StripId, Vec3>> = {
30
+ const TINT: Readonly<Partial<Record<StripId, Vec3>>> = {
30
31
  plain: hex('#e8e4dc'),
31
32
  brick: hex('#9a4a32'),
32
33
  stone: hex('#c8bba0'),
@@ -128,7 +129,8 @@ export const material_swatches = defineAsset({
128
129
  kind: 'prop',
129
130
  description:
130
131
  'Every trim-sheet strip and cell on labelled panels: the material generator test card.',
131
- budget: { triangles: [16000, 1200, 800] },
132
+ // 24 strip panels (library v2) + decal and foliage cells.
133
+ budget: { triangles: [24000, 1200, 800] },
132
134
  build(ctx) {
133
135
  const stripSpan = STRIPS.length * 2 + (STRIPS.length - 1) * FRONT.gap;
134
136
  const cells: readonly CellId[] = [...DECAL_CELLS, ...FOLIAGE_CELLS];
@@ -151,7 +153,7 @@ export const material_swatches = defineAsset({
151
153
  const panel = box({
152
154
  size: [0.06, 2, h],
153
155
  at: [FRONT.x, y, FRONT.height],
154
- color: TINT[id],
156
+ color: TINT[id] ?? NATURAL,
155
157
  strip: id,
156
158
  });
157
159
  ctx.part(`strip_${id}`, panel, id === 'glass' ? { surface: 'glass' } : {});
@@ -167,10 +169,14 @@ export const material_swatches = defineAsset({
167
169
  surface: 'cutout',
168
170
  });
169
171
  } else {
170
- const back = box({ size: [0.06, 1, 1], at: [REAR.x, y, REAR.height], color: TINT.plain });
172
+ const back = box({
173
+ size: [0.06, 1, 1],
174
+ at: [REAR.x, y, REAR.height],
175
+ color: TINT.plain ?? NATURAL,
176
+ });
171
177
  ctx.part(
172
178
  `cell_${id}`,
173
- merge([back, cellQuad(id, REAR.x + 0.032, y, REAR.height, 1, TINT.plain)]),
179
+ merge([back, cellQuad(id, REAR.x + 0.032, y, REAR.height, 1, TINT.plain ?? NATURAL)]),
174
180
  );
175
181
  }
176
182
  if (ctx.lod === 0)