@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
@@ -11,6 +11,12 @@ export interface Args {
11
11
  readonly sheetSize: 512 | 1024 | 2048;
12
12
  /** Which detail levels to build: all three by default, or e.g. only the hero mesh. */
13
13
  readonly lods: readonly (0 | 1 | 2)[];
14
+ /**
15
+ * How LODs land on disk: `split` lodN.glb per level · `single` one <asset>.glb with MSFT_lod
16
+ * alternates · `both` (default): split files import everywhere, the single file is for
17
+ * MSFT_lod-aware loaders and one-file pipelines.
18
+ */
19
+ readonly lodFiles: 'split' | 'single' | 'both';
14
20
  /** Beauty-render camera and size (hero, review, and build's hero.png); unset = defaults. */
15
21
  readonly render: {
16
22
  readonly width?: number;
@@ -20,5 +26,5 @@ export interface Args {
20
26
  readonly fovDeg?: number;
21
27
  };
22
28
  }
23
- export declare const HELP = "usage: t3d <command> <recipe.ts\u2026> [flags]\n build GLB \u00D7 3 LODs + hero + contact sheet + manifest.json per asset; exit 1 on any QA finding\n shot contact sheet + manifest.json only (fast look)\n hero beauty render only \u2192 <out>/<asset>/hero.png (3/4 product shot, textured, shadowed)\n review AI review pack \u2192 <out>/<asset>/review/: hero, sheet, close-ups, LOD strip, review.md\n (checklist prompt for a vision agent) and review.json (image metrics + warnings)\n list the assets each module exports\nflags:\n --out <dir> output root (default: out) \u2192 <dir>/<asset>/{lod0,lod1,lod2}.glb hero.png sheet.png manifest.json\n --no-animations skip the baked 'run' clip (default: baked, so viewers can play it)\n --no-shot build without rendering hero.png and the contact sheet\n --size WxH beauty render size (default 1600x900) \u00B7 --az <deg> camera azimuth from the\n front, default -35 (front-left 3/4) \u00B7 --el <deg> elevation, default 18 \u00B7 --fov <deg> 30\n --sheet <mode> external (default): one shared trim_sheet_{albedo,normal,orm}.png in <out>/,\n referenced by every GLB \u00B7 embed: self-contained GLBs \u00B7 none: vertex colour only\n --sheet-size n 512 | 1024 (default) | 2048\n --lods <list> which detail levels to build: 0,1,2 (default) \u00B7 0 = hero only \u00B7 0,2 = near + far\n --json machine-readable output";
29
+ export declare const HELP = "usage: t3d <command> <recipe.ts\u2026> [flags]\n build GLB \u00D7 3 LODs + hero + contact sheet + manifest.json per asset; exit 1 on any QA finding\n shot contact sheet + manifest.json only (fast look)\n hero beauty render only \u2192 <out>/<asset>/hero.png (3/4 product shot, textured, shadowed)\n review AI review pack \u2192 <out>/<asset>/review/: hero, sheet, close-ups, LOD strip, review.md\n (checklist prompt for a vision agent) and review.json (image metrics + warnings)\n list the assets each module exports\nflags:\n --out <dir> output root (default: out) \u2192 <dir>/<asset>/{lod0,lod1,lod2}.glb hero.png sheet.png manifest.json\n --no-animations skip the baked 'run' clip (default: baked, so viewers can play it)\n --no-shot build without rendering hero.png and the contact sheet\n --size WxH beauty render size (default 1600x900) \u00B7 --az <deg> camera azimuth from the\n front, default -35 (front-left 3/4) \u00B7 --el <deg> elevation, default 18 \u00B7 --fov <deg> 30\n --sheet <mode> external (default): one shared trim_sheet_{albedo,normal,orm}.png in <out>/,\n referenced by every GLB \u00B7 embed: self-contained GLBs \u00B7 none: vertex colour only\n --sheet-size n 512 | 1024 | 2048 (default)\n --lods <list> which detail levels to build: 0,1,2 (default) \u00B7 0 = hero only \u00B7 0,2 = near + far\n --lods <mode> how they are written: both (default) = lod0/1/2.glb + <asset>.glb \u00B7 split = lodN.glb\n only \u00B7 single = <asset>.glb only (LOD0 in the scene, LOD1/2 as MSFT_lod alternates\n with screen-coverage switch hints; viewers without MSFT_lod show LOD0 only)\n --json machine-readable output";
24
30
  export declare function parseArgs(argv: readonly string[]): Args;
package/dist/cli/bin.js CHANGED
@@ -3,21 +3,23 @@ import {
3
3
  Ts23dError2,
4
4
  isAsset2,
5
5
  compile2
6
- } from "../chunk-4ddzsqtc.js";
6
+ } from "../chunk-hg7pf34j.js";
7
7
  import {
8
+ provideTextures2,
8
9
  DEFAULT_SHEET2,
9
10
  paintTrimSheet2
10
- } from "../chunk-kvcep7ft.js";
11
- import"../chunk-xnt2sdhf.js";
11
+ } from "../chunk-y63c6zcv.js";
12
+ import"../chunk-drna2rny.js";
12
13
  import {
13
14
  SHEET_MAPS2,
14
15
  sheetFileName2,
15
16
  sheetPngs2,
16
- toGlb2
17
- } from "../chunk-gcyp9b4d.js";
17
+ toGlb2,
18
+ exportLodGlb2
19
+ } from "../chunk-dbq4qf52.js";
18
20
  import {
19
21
  runQa2
20
- } from "../chunk-w1zyhj40.js";
22
+ } from "../chunk-jq4e17tj.js";
21
23
  import {
22
24
  checkBeautyOptions2,
23
25
  parseSize2,
@@ -25,14 +27,14 @@ import {
25
27
  contactSheet2,
26
28
  reviewPack2,
27
29
  thumbnail2
28
- } from "../chunk-11v28977.js";
30
+ } from "../chunk-f6yme003.js";
29
31
  import {
30
32
  manifestOf2
31
- } from "../chunk-kgvbpqqw.js";
33
+ } from "../chunk-7rtfjh9f.js";
32
34
 
33
35
  // src/cli/run.ts
34
36
  import { mkdir as mkdir2 } from "node:fs/promises";
35
- import { join as join2 } from "node:path";
37
+ import { join as join3 } from "node:path";
36
38
 
37
39
  // src/cli/args.ts
38
40
  var HELP = `usage: t3d <command> <recipe.ts…> [flags]
@@ -50,8 +52,11 @@ flags:
50
52
  front, default -35 (front-left 3/4) · --el <deg> elevation, default 18 · --fov <deg> 30
51
53
  --sheet <mode> external (default): one shared trim_sheet_{albedo,normal,orm}.png in <out>/,
52
54
  referenced by every GLB · embed: self-contained GLBs · none: vertex colour only
53
- --sheet-size n 512 | 1024 (default) | 2048
55
+ --sheet-size n 512 | 1024 | 2048 (default)
54
56
  --lods <list> which detail levels to build: 0,1,2 (default) · 0 = hero only · 0,2 = near + far
57
+ --lods <mode> how they are written: both (default) = lod0/1/2.glb + <asset>.glb · split = lodN.glb
58
+ only · single = <asset>.glb only (LOD0 in the scene, LOD1/2 as MSFT_lod alternates
59
+ with screen-coverage switch hints; viewers without MSFT_lod show LOD0 only)
55
60
  --json machine-readable output`;
56
61
  function parseArgs(argv) {
57
62
  const files = [];
@@ -60,8 +65,9 @@ function parseArgs(argv) {
60
65
  let animations = true;
61
66
  let shot = true;
62
67
  let sheet = "external";
63
- let sheetSize = 1024;
68
+ let sheetSize = 2048;
64
69
  let lods = [0, 1, 2];
70
+ let lodFiles = "both";
65
71
  const render = {};
66
72
  const num = (flag, v) => {
67
73
  const n = Number(v);
@@ -87,12 +93,14 @@ function parseArgs(argv) {
87
93
  else if (a === "--sheet") {
88
94
  const m = argv[++i];
89
95
  sheet = m === "embed" || m === "none" ? m : "external";
96
+ } else if (a === "--lods" && /^(split|single|both)$/.test(argv[i + 1] ?? "")) {
97
+ lodFiles = argv[++i];
90
98
  } else if (a === "--lods") {
91
99
  const picked = (argv[++i] ?? "").split(",").map(Number).filter((n) => n === 0 || n === 1 || n === 2);
92
100
  lods = picked.length ? [...new Set(picked)].sort() : lods;
93
101
  } else if (a === "--sheet-size") {
94
102
  const n = Number(argv[++i]);
95
- sheetSize = n === 512 || n === 2048 ? n : 1024;
103
+ sheetSize = n === 512 || n === 1024 ? n : 2048;
96
104
  } else if (a === "--size")
97
105
  Object.assign(render, parseSize2(argv[++i] ?? ""));
98
106
  else if (a === "--az")
@@ -115,6 +123,7 @@ function parseArgs(argv) {
115
123
  sheet,
116
124
  sheetSize,
117
125
  lods,
126
+ lodFiles,
118
127
  render
119
128
  };
120
129
  }
@@ -175,41 +184,70 @@ function printRendered(r) {
175
184
  fix: ${w.fix}`);
176
185
  }
177
186
 
187
+ // src/cli/texture-cache.ts
188
+ import { readdir } from "node:fs/promises";
189
+ import { join as join2 } from "node:path";
190
+ var TEXTURE_CACHE = join2("node_modules", ".cache", "ts23d", "textures");
191
+ var sha256 = (b) => new Bun.CryptoHasher("sha256").update(b).digest("hex");
192
+ async function loadTextureCache(dir = TEXTURE_CACHE) {
193
+ const out = new Map;
194
+ const names = await readdir(dir).catch(() => []);
195
+ for (const name of names.sort()) {
196
+ const m = /^([0-9a-f]{64})\.png$/.exec(name);
197
+ if (!m?.[1])
198
+ continue;
199
+ const bytes = new Uint8Array(await Bun.file(join2(dir, name)).arrayBuffer());
200
+ if (sha256(bytes) === m[1])
201
+ out.set(m[1], bytes);
202
+ }
203
+ return out;
204
+ }
205
+
178
206
  // src/cli/run.ts
179
207
  async function buildOne(asset, args, texture, painted) {
180
- const dir = join2(args.out, asset.name);
208
+ const dir = join3(args.out, asset.name);
181
209
  await mkdir2(dir, { recursive: true });
182
210
  const lods = args.lods.map((lod) => compile2(asset, { lod }));
183
211
  const findings = lods.flatMap((a) => runQa2(a).map((f) => ({ ...f, message: `LOD${a.lod}: ${f.message}` })));
184
212
  const glbs = [];
185
- if (args.cmd === "build") {
213
+ const glbOptions = { animations: args.animations, ...texture ? { sheet: texture } : {} };
214
+ let single;
215
+ let coverage = [];
216
+ if (args.cmd === "build" && args.lodFiles !== "single") {
186
217
  for (const a of lods) {
187
- const path = join2(dir, `lod${a.lod}.glb`);
188
- await Bun.write(path, toGlb2(a, { animations: args.animations, ...texture ? { sheet: texture } : {} }));
218
+ const path = join3(dir, `lod${a.lod}.glb`);
219
+ await Bun.write(path, toGlb2(a, glbOptions));
189
220
  glbs.push(path);
190
221
  }
191
222
  }
223
+ if (args.cmd === "build" && args.lodFiles !== "split") {
224
+ single = join3(dir, `${asset.name}.glb`);
225
+ const packed = exportLodGlb2(lods, glbOptions);
226
+ coverage = packed.coverage;
227
+ await Bun.write(single, packed.glb);
228
+ }
192
229
  let sheet;
193
230
  let thumb;
194
231
  let hero;
195
232
  if (args.cmd === "build" && args.shot) {
196
- hero = join2(dir, "hero.png");
233
+ hero = join3(dir, "hero.png");
197
234
  const top = lods[0] ?? compile2(asset);
198
235
  await Bun.write(hero, beauty2(top, { ...args.render, ...painted ? { sheet: painted } : {} }));
199
236
  }
200
237
  if (args.cmd === "shot" || args.shot) {
201
- thumb = join2(dir, "thumb.png");
238
+ thumb = join3(dir, "thumb.png");
202
239
  await Bun.write(thumb, thumbnail2(lods[0] ?? compile2(asset)));
203
- sheet = join2(dir, "sheet.png");
240
+ sheet = join3(dir, "sheet.png");
204
241
  await Bun.write(sheet, contactSheet2(lods[0] ?? compile2(asset), { far: lods.at(-1) ?? compile2(asset, { lod: 2 }) }));
205
242
  }
206
243
  const manifest = manifestOf2(asset, lods, {
207
244
  lods: glbs,
245
+ ...single ? { single } : {},
208
246
  ...hero ? { hero } : {},
209
247
  ...sheet ? { sheet } : {},
210
248
  ...thumb ? { thumb } : {}
211
- }, findings);
212
- await Bun.write(join2(dir, "manifest.json"), `${JSON.stringify(manifest, null, 2)}
249
+ }, findings, coverage);
250
+ await Bun.write(join3(dir, "manifest.json"), `${JSON.stringify(manifest, null, 2)}
213
251
  `);
214
252
  return manifest;
215
253
  }
@@ -217,6 +255,7 @@ function print(m) {
217
255
  console.log(`${m.name} QA ${m.qa.pass ? "PASS" : "FAIL"} tris ${m.triangles.join("/")} draws ${m.drawCalls.join("/")}`);
218
256
  for (const f of [
219
257
  ...m.files.lods,
258
+ ...m.files.single ? [m.files.single] : [],
220
259
  ...m.files.hero ? [m.files.hero] : [],
221
260
  ...m.files.sheet ? [m.files.sheet] : []
222
261
  ])
@@ -248,6 +287,8 @@ async function run(argv) {
248
287
  const rendered = [];
249
288
  const renders = args.cmd === "hero" || args.cmd === "review";
250
289
  const wantsSheet = args.cmd === "build" && args.sheet !== "none" || renders;
290
+ if (wantsSheet)
291
+ provideTextures2(await loadTextureCache());
251
292
  const painted = wantsSheet ? paintTrimSheet2({ ...DEFAULT_SHEET2, size: args.sheetSize }) : undefined;
252
293
  let texture;
253
294
  if (args.cmd === "build" && args.sheet !== "none" && painted) {
@@ -257,7 +298,7 @@ async function run(argv) {
257
298
  const pngs = sheetPngs2(painted);
258
299
  await mkdir2(args.out, { recursive: true });
259
300
  for (const map of SHEET_MAPS2)
260
- await Bun.write(join2(args.out, sheetFileName2(map)), pngs[map]);
301
+ await Bun.write(join3(args.out, sheetFileName2(map)), pngs[map]);
261
302
  texture = { external: true, prefix: "../trim_sheet" };
262
303
  }
263
304
  }
@@ -289,9 +330,9 @@ async function run(argv) {
289
330
  name: m.name,
290
331
  qa: m.qa.pass ? "PASS" : "FAIL",
291
332
  triangles: m.triangles,
292
- manifest: join2(m.name, "manifest.json")
333
+ manifest: join3(m.name, "manifest.json")
293
334
  }));
294
- await Bun.write(join2(args.out, "index.json"), `${JSON.stringify({ ok, assets: index }, null, 2)}
335
+ await Bun.write(join3(args.out, "index.json"), `${JSON.stringify({ ok, assets: index }, null, 2)}
295
336
  `);
296
337
  if (args.json)
297
338
  console.log(JSON.stringify({ ok, data: built }));
@@ -303,5 +344,5 @@ async function run(argv) {
303
344
  // src/cli/bin.ts
304
345
  process.exitCode = await run(process.argv.slice(2));
305
346
 
306
- //# debugId=0183AEABD246D46664756E2164756E21
347
+ //# debugId=BFD08E21916B124164756E2164756E21
307
348
  //# sourceMappingURL=bin.js.map
@@ -1,14 +1,15 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../src/cli/run.ts", "../src/cli/args.ts", "../src/cli/load.ts", "../src/cli/render-cmds.ts", "../src/cli/bin.ts"],
3
+ "sources": ["../src/cli/run.ts", "../src/cli/args.ts", "../src/cli/load.ts", "../src/cli/render-cmds.ts", "../src/cli/texture-cache.ts", "../src/cli/bin.ts"],
4
4
  "sourcesContent": [
5
- "// Command dispatch for `t3d`. Pure function of argv → exit code, so it is testable in-process.\n// Output layout, one folder per asset (stable paths agents and pipelines can rely on):\n// <out>/<asset>/lod0.glb lod1.glb lod2.glb hero.png thumb.png sheet.png manifest.json\n// <out>/<asset>/review/… (t3d review) · <out>/index.json — every asset built, with its QA verdict\n\nimport { mkdir } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { type Asset, compile, type Lod, Ts23dError } from '../core/index.ts';\nimport { SHEET_MAPS, type SheetBinding, sheetFileName, sheetPngs, toGlb } from '../export/index.ts';\nimport { DEFAULT_SHEET, type PaintedSheet, paintTrimSheet } from '../material/index.ts';\nimport { type Finding, runQa } from '../qa/index.ts';\nimport { beauty, contactSheet, thumbnail } from '../render/index.ts';\nimport { type Args, HELP, parseArgs } from './args.ts';\nimport { loadAssets } from './load.ts';\nimport { type AssetManifest, manifestOf } from './manifest.ts';\nimport { heroOne, printRendered, type RenderResult, reviewOne } from './render-cmds.ts';\n\nasync function buildOne(\n asset: Asset,\n args: Args,\n texture: SheetBinding | undefined,\n painted: PaintedSheet | undefined,\n): Promise<AssetManifest> {\n const dir = join(args.out, asset.name);\n await mkdir(dir, { recursive: true });\n // Only the LODs the user asked for: QA, GLBs and the manifest cover exactly those.\n const lods = args.lods.map((lod: Lod) => compile(asset, { lod }));\n const findings: Finding[] = lods.flatMap((a) =>\n runQa(a).map((f) => ({ ...f, message: `LOD${a.lod}: ${f.message}` })),\n );\n const glbs: string[] = [];\n if (args.cmd === 'build') {\n for (const a of lods) {\n const path = join(dir, `lod${a.lod}.glb`);\n await Bun.write(\n path,\n toGlb(a, { animations: args.animations, ...(texture ? { sheet: texture } : {}) }),\n );\n glbs.push(path);\n }\n }\n let sheet: string | undefined;\n let thumb: string | undefined;\n let hero: string | undefined;\n if (args.cmd === 'build' && args.shot) {\n hero = join(dir, 'hero.png');\n const top = lods[0] ?? compile(asset);\n await Bun.write(hero, beauty(top, { ...args.render, ...(painted ? { sheet: painted } : {}) }));\n }\n if (args.cmd === 'shot' || args.shot) {\n thumb = join(dir, 'thumb.png');\n await Bun.write(thumb, thumbnail(lods[0] ?? compile(asset)));\n sheet = join(dir, 'sheet.png');\n await Bun.write(\n sheet,\n contactSheet(lods[0] ?? compile(asset), { far: lods.at(-1) ?? compile(asset, { lod: 2 }) }),\n );\n }\n const manifest = manifestOf(\n asset,\n lods,\n {\n lods: glbs,\n ...(hero ? { hero } : {}),\n ...(sheet ? { sheet } : {}),\n ...(thumb ? { thumb } : {}),\n },\n findings,\n );\n await Bun.write(join(dir, 'manifest.json'), `${JSON.stringify(manifest, null, 2)}\\n`);\n return manifest;\n}\n\nfunction print(m: AssetManifest): void {\n console.log(\n `${m.name} QA ${m.qa.pass ? 'PASS' : 'FAIL'} tris ${m.triangles.join('/')} draws ${m.drawCalls.join('/')}`,\n );\n for (const f of [\n ...m.files.lods,\n ...(m.files.hero ? [m.files.hero] : []),\n ...(m.files.sheet ? [m.files.sheet] : []),\n ])\n console.log(` → ${f}`);\n for (const f of m.qa.findings)\n console.log(` ✗ ${f.gate}${f.part ? ` [${f.part}]` : ''}: ${f.message}\\n fix: ${f.fix}`);\n}\n\nconst COMMANDS = ['build', 'shot', 'list', 'hero', 'review'];\n\nfunction fail(e: unknown, json: boolean): number {\n if (!(e instanceof Ts23dError)) throw e;\n console.error(\n json ? JSON.stringify({ ok: false, error: e.toJSON() }) : `${e.message}\\n fix: ${e.fix}`,\n );\n return 1;\n}\n\nexport async function run(argv: readonly string[]): Promise<number> {\n let args: Args;\n try {\n args = parseArgs(argv);\n } catch (e) {\n return fail(e, argv.includes('--json'));\n }\n if (!COMMANDS.includes(args.cmd) || args.files.length === 0) {\n console.log(HELP);\n return args.cmd === 'help' ? 0 : 2;\n }\n const built: AssetManifest[] = [];\n const rendered: RenderResult[] = [];\n // Paint the trim sheet once per run (~1 s at 1024²). External mode writes it once into <out>/\n // and every GLB in <out>/<asset>/ references ../trim_sheet_*.png — one texture set for all.\n // The beauty renders sample the same painted sheet.\n const renders = args.cmd === 'hero' || args.cmd === 'review';\n const wantsSheet = (args.cmd === 'build' && args.sheet !== 'none') || renders;\n const painted = wantsSheet\n ? paintTrimSheet({ ...DEFAULT_SHEET, size: args.sheetSize })\n : undefined;\n let texture: SheetBinding | undefined;\n if (args.cmd === 'build' && args.sheet !== 'none' && painted) {\n if (args.sheet === 'embed') texture = painted;\n else {\n const pngs = sheetPngs(painted);\n await mkdir(args.out, { recursive: true });\n for (const map of SHEET_MAPS) await Bun.write(join(args.out, sheetFileName(map)), pngs[map]);\n texture = { external: true, prefix: '../trim_sheet' };\n }\n }\n try {\n for (const file of args.files) {\n for (const asset of await loadAssets(file)) {\n if (args.cmd === 'list')\n console.log(\n args.json ? JSON.stringify({ asset: asset.name, file }) : `${asset.name} ${file}`,\n );\n else if (renders && painted)\n rendered.push(\n args.cmd === 'hero'\n ? await heroOne(asset, args, painted)\n : await reviewOne(asset, file, args, painted),\n );\n else built.push(await buildOne(asset, args, texture, painted));\n }\n }\n } catch (e) {\n return fail(e, args.json);\n }\n if (args.cmd === 'list') return 0;\n if (renders) {\n if (args.json) console.log(JSON.stringify({ ok: true, data: rendered }));\n else rendered.forEach(printRendered);\n return 0;\n }\n const ok = built.every((m) => m.qa.pass);\n const index = built.map((m) => ({\n name: m.name,\n qa: m.qa.pass ? 'PASS' : 'FAIL',\n triangles: m.triangles,\n manifest: join(m.name, 'manifest.json'),\n }));\n await Bun.write(\n join(args.out, 'index.json'),\n `${JSON.stringify({ ok, assets: index }, null, 2)}\\n`,\n );\n if (args.json) console.log(JSON.stringify({ ok, data: built }));\n else built.forEach(print);\n return ok ? 0 : 1;\n}\n",
6
- "// argv → Args for `t3d`. No dependency, no magic: flags are few and documented in HELP.\n// Bad render flags (--size/--az/--el/--fov) throw T3D_RENDER_OPTIONS with the fix.\n\nimport { Ts23dError } from '../core/index.ts';\nimport { checkBeautyOptions, parseSize } from '../render/index.ts';\n\nexport interface Args {\n readonly cmd: string;\n readonly files: string[];\n readonly out: string;\n readonly json: boolean;\n readonly animations: boolean;\n /** `build` also renders the contact sheet unless --no-shot. */\n readonly shot: boolean;\n /** Trim-sheet textures: shared files in <out>/ (default), embedded per GLB, or none. */\n readonly sheet: 'external' | 'embed' | 'none';\n readonly sheetSize: 512 | 1024 | 2048;\n /** Which detail levels to build: all three by default, or e.g. only the hero mesh. */\n readonly lods: readonly (0 | 1 | 2)[];\n /** Beauty-render camera and size (hero, review, and build's hero.png); unset = defaults. */\n readonly render: {\n readonly width?: number;\n readonly height?: number;\n readonly azDeg?: number;\n readonly elDeg?: number;\n readonly fovDeg?: number;\n };\n}\n\nexport const HELP = `usage: t3d <command> <recipe.ts…> [flags]\n build GLB × 3 LODs + hero + contact sheet + manifest.json per asset; exit 1 on any QA finding\n shot contact sheet + manifest.json only (fast look)\n hero beauty render only → <out>/<asset>/hero.png (3/4 product shot, textured, shadowed)\n review AI review pack → <out>/<asset>/review/: hero, sheet, close-ups, LOD strip, review.md\n (checklist prompt for a vision agent) and review.json (image metrics + warnings)\n list the assets each module exports\nflags:\n --out <dir> output root (default: out) → <dir>/<asset>/{lod0,lod1,lod2}.glb hero.png sheet.png manifest.json\n --no-animations skip the baked 'run' clip (default: baked, so viewers can play it)\n --no-shot build without rendering hero.png and the contact sheet\n --size WxH beauty render size (default 1600x900) · --az <deg> camera azimuth from the\n front, default -35 (front-left 3/4) · --el <deg> elevation, default 18 · --fov <deg> 30\n --sheet <mode> external (default): one shared trim_sheet_{albedo,normal,orm}.png in <out>/,\n referenced by every GLB · embed: self-contained GLBs · none: vertex colour only\n --sheet-size n 512 | 1024 (default) | 2048\n --lods <list> which detail levels to build: 0,1,2 (default) · 0 = hero only · 0,2 = near + far\n --json machine-readable output`;\n\nexport function parseArgs(argv: readonly string[]): Args {\n const files: string[] = [];\n let out = 'out';\n let json = false;\n let animations = true;\n let shot = true;\n let sheet: Args['sheet'] = 'external';\n let sheetSize: Args['sheetSize'] = 1024;\n let lods: Args['lods'] = [0, 1, 2];\n const render: {\n width?: number;\n height?: number;\n azDeg?: number;\n elDeg?: number;\n fovDeg?: number;\n } = {};\n const num = (flag: string, v: string | undefined): number => {\n const n = Number(v);\n if (v === undefined || v.trim() === '' || !Number.isFinite(n))\n throw new Ts23dError('T3D_RENDER_OPTIONS', {\n cause: `${flag} needs a number of degrees, got ${v === undefined ? 'nothing' : `\"${v}\"`}`,\n fix: `pass e.g. ${flag} ${flag === '--fov' ? 30 : flag === '--el' ? 18 : -35}`,\n });\n return n;\n };\n for (let i = 1; i < argv.length; i++) {\n const a = argv[i] ?? '';\n if (a === '--json') json = true;\n else if (a === '--no-animations') animations = false;\n else if (a === '--animations') animations = true;\n else if (a === '--no-shot') shot = false;\n else if (a === '--out') out = argv[++i] ?? out;\n else if (a === '--sheet') {\n const m = argv[++i];\n sheet = m === 'embed' || m === 'none' ? m : 'external';\n } else if (a === '--lods') {\n const picked = (argv[++i] ?? '')\n .split(',')\n .map(Number)\n .filter((n): n is 0 | 1 | 2 => n === 0 || n === 1 || n === 2);\n lods = picked.length ? [...new Set(picked)].sort() : lods;\n } else if (a === '--sheet-size') {\n const n = Number(argv[++i]);\n sheetSize = n === 512 || n === 2048 ? n : 1024;\n } else if (a === '--size') Object.assign(render, parseSize(argv[++i] ?? ''));\n else if (a === '--az') render.azDeg = num(a, argv[++i]);\n else if (a === '--el') render.elDeg = num(a, argv[++i]);\n else if (a === '--fov') render.fovDeg = num(a, argv[++i]);\n else files.push(a);\n }\n checkBeautyOptions(render);\n return {\n cmd: argv[0] ?? 'help',\n files,\n out,\n json,\n animations,\n shot,\n sheet,\n sheetSize,\n lods,\n render,\n };\n}\n",
5
+ "// Command dispatch for `t3d`. Pure function of argv → exit code, so it is testable in-process.\n// Output layout, one folder per asset (stable paths agents and pipelines can rely on):\n// <out>/<asset>/lod0.glb lod1.glb lod2.glb <asset>.glb (all LODs, MSFT_lod) hero.png thumb.png\n// sheet.png manifest.json\n// <out>/<asset>/review/… (t3d review) · <out>/index.json — every asset built, with its QA verdict\n\nimport { mkdir } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { type Asset, compile, type Lod, Ts23dError } from '../core/index.ts';\nimport {\n exportLodGlb,\n SHEET_MAPS,\n type SheetBinding,\n sheetFileName,\n sheetPngs,\n toGlb,\n} from '../export/index.ts';\nimport {\n DEFAULT_SHEET,\n type PaintedSheet,\n paintTrimSheet,\n provideTextures,\n} from '../material/index.ts';\nimport { type Finding, runQa } from '../qa/index.ts';\nimport { beauty, contactSheet, thumbnail } from '../render/index.ts';\nimport { type Args, HELP, parseArgs } from './args.ts';\nimport { loadAssets } from './load.ts';\nimport { type AssetManifest, manifestOf } from './manifest.ts';\nimport { heroOne, printRendered, type RenderResult, reviewOne } from './render-cmds.ts';\nimport { loadTextureCache } from './texture-cache.ts';\n\nasync function buildOne(\n asset: Asset,\n args: Args,\n texture: SheetBinding | undefined,\n painted: PaintedSheet | undefined,\n): Promise<AssetManifest> {\n const dir = join(args.out, asset.name);\n await mkdir(dir, { recursive: true });\n // Only the LODs the user asked for: QA, GLBs and the manifest cover exactly those.\n const lods = args.lods.map((lod: Lod) => compile(asset, { lod }));\n const findings: Finding[] = lods.flatMap((a) =>\n runQa(a).map((f) => ({ ...f, message: `LOD${a.lod}: ${f.message}` })),\n );\n const glbs: string[] = [];\n const glbOptions = { animations: args.animations, ...(texture ? { sheet: texture } : {}) };\n let single: string | undefined;\n let coverage: readonly number[] = [];\n if (args.cmd === 'build' && args.lodFiles !== 'single') {\n for (const a of lods) {\n const path = join(dir, `lod${a.lod}.glb`);\n await Bun.write(path, toGlb(a, glbOptions));\n glbs.push(path);\n }\n }\n if (args.cmd === 'build' && args.lodFiles !== 'split') {\n single = join(dir, `${asset.name}.glb`);\n const packed = exportLodGlb(lods, glbOptions);\n coverage = packed.coverage;\n await Bun.write(single, packed.glb);\n }\n let sheet: string | undefined;\n let thumb: string | undefined;\n let hero: string | undefined;\n if (args.cmd === 'build' && args.shot) {\n hero = join(dir, 'hero.png');\n const top = lods[0] ?? compile(asset);\n await Bun.write(hero, beauty(top, { ...args.render, ...(painted ? { sheet: painted } : {}) }));\n }\n if (args.cmd === 'shot' || args.shot) {\n thumb = join(dir, 'thumb.png');\n await Bun.write(thumb, thumbnail(lods[0] ?? compile(asset)));\n sheet = join(dir, 'sheet.png');\n await Bun.write(\n sheet,\n contactSheet(lods[0] ?? compile(asset), { far: lods.at(-1) ?? compile(asset, { lod: 2 }) }),\n );\n }\n const manifest = manifestOf(\n asset,\n lods,\n {\n lods: glbs,\n ...(single ? { single } : {}),\n ...(hero ? { hero } : {}),\n ...(sheet ? { sheet } : {}),\n ...(thumb ? { thumb } : {}),\n },\n findings,\n coverage,\n );\n await Bun.write(join(dir, 'manifest.json'), `${JSON.stringify(manifest, null, 2)}\\n`);\n return manifest;\n}\n\nfunction print(m: AssetManifest): void {\n console.log(\n `${m.name} QA ${m.qa.pass ? 'PASS' : 'FAIL'} tris ${m.triangles.join('/')} draws ${m.drawCalls.join('/')}`,\n );\n for (const f of [\n ...m.files.lods,\n ...(m.files.single ? [m.files.single] : []),\n ...(m.files.hero ? [m.files.hero] : []),\n ...(m.files.sheet ? [m.files.sheet] : []),\n ])\n console.log(` → ${f}`);\n for (const f of m.qa.findings)\n console.log(` ✗ ${f.gate}${f.part ? ` [${f.part}]` : ''}: ${f.message}\\n fix: ${f.fix}`);\n}\n\nconst COMMANDS = ['build', 'shot', 'list', 'hero', 'review'];\n\nfunction fail(e: unknown, json: boolean): number {\n if (!(e instanceof Ts23dError)) throw e;\n console.error(\n json ? JSON.stringify({ ok: false, error: e.toJSON() }) : `${e.message}\\n fix: ${e.fix}`,\n );\n return 1;\n}\n\nexport async function run(argv: readonly string[]): Promise<number> {\n let args: Args;\n try {\n args = parseArgs(argv);\n } catch (e) {\n return fail(e, argv.includes('--json'));\n }\n if (!COMMANDS.includes(args.cmd) || args.files.length === 0) {\n console.log(HELP);\n return args.cmd === 'help' ? 0 : 2;\n }\n const built: AssetManifest[] = [];\n const rendered: RenderResult[] = [];\n // Paint the trim sheet once per run (~4 s at 2048²). External mode writes it once into <out>/\n // and every GLB in <out>/<asset>/ references ../trim_sheet_*.png — one texture set for all.\n // The beauty renders sample the same painted sheet.\n const renders = args.cmd === 'hero' || args.cmd === 'review';\n const wantsSheet = (args.cmd === 'build' && args.sheet !== 'none') || renders;\n if (wantsSheet) provideTextures(await loadTextureCache());\n const painted = wantsSheet\n ? paintTrimSheet({ ...DEFAULT_SHEET, size: args.sheetSize })\n : undefined;\n let texture: SheetBinding | undefined;\n if (args.cmd === 'build' && args.sheet !== 'none' && painted) {\n if (args.sheet === 'embed') texture = painted;\n else {\n const pngs = sheetPngs(painted);\n await mkdir(args.out, { recursive: true });\n for (const map of SHEET_MAPS) await Bun.write(join(args.out, sheetFileName(map)), pngs[map]);\n texture = { external: true, prefix: '../trim_sheet' };\n }\n }\n try {\n for (const file of args.files) {\n for (const asset of await loadAssets(file)) {\n if (args.cmd === 'list')\n console.log(\n args.json ? JSON.stringify({ asset: asset.name, file }) : `${asset.name} ${file}`,\n );\n else if (renders && painted)\n rendered.push(\n args.cmd === 'hero'\n ? await heroOne(asset, args, painted)\n : await reviewOne(asset, file, args, painted),\n );\n else built.push(await buildOne(asset, args, texture, painted));\n }\n }\n } catch (e) {\n return fail(e, args.json);\n }\n if (args.cmd === 'list') return 0;\n if (renders) {\n if (args.json) console.log(JSON.stringify({ ok: true, data: rendered }));\n else rendered.forEach(printRendered);\n return 0;\n }\n const ok = built.every((m) => m.qa.pass);\n const index = built.map((m) => ({\n name: m.name,\n qa: m.qa.pass ? 'PASS' : 'FAIL',\n triangles: m.triangles,\n manifest: join(m.name, 'manifest.json'),\n }));\n await Bun.write(\n join(args.out, 'index.json'),\n `${JSON.stringify({ ok, assets: index }, null, 2)}\\n`,\n );\n if (args.json) console.log(JSON.stringify({ ok, data: built }));\n else built.forEach(print);\n return ok ? 0 : 1;\n}\n",
6
+ "// argv → Args for `t3d`. No dependency, no magic: flags are few and documented in HELP.\n// Bad render flags (--size/--az/--el/--fov) throw T3D_RENDER_OPTIONS with the fix.\n\nimport { Ts23dError } from '../core/index.ts';\nimport { checkBeautyOptions, parseSize } from '../render/index.ts';\n\nexport interface Args {\n readonly cmd: string;\n readonly files: string[];\n readonly out: string;\n readonly json: boolean;\n readonly animations: boolean;\n /** `build` also renders the contact sheet unless --no-shot. */\n readonly shot: boolean;\n /** Trim-sheet textures: shared files in <out>/ (default), embedded per GLB, or none. */\n readonly sheet: 'external' | 'embed' | 'none';\n readonly sheetSize: 512 | 1024 | 2048;\n /** Which detail levels to build: all three by default, or e.g. only the hero mesh. */\n readonly lods: readonly (0 | 1 | 2)[];\n /**\n * How LODs land on disk: `split` lodN.glb per level · `single` one <asset>.glb with MSFT_lod\n * alternates · `both` (default): split files import everywhere, the single file is for\n * MSFT_lod-aware loaders and one-file pipelines.\n */\n readonly lodFiles: 'split' | 'single' | 'both';\n /** Beauty-render camera and size (hero, review, and build's hero.png); unset = defaults. */\n readonly render: {\n readonly width?: number;\n readonly height?: number;\n readonly azDeg?: number;\n readonly elDeg?: number;\n readonly fovDeg?: number;\n };\n}\n\nexport const HELP = `usage: t3d <command> <recipe.ts…> [flags]\n build GLB × 3 LODs + hero + contact sheet + manifest.json per asset; exit 1 on any QA finding\n shot contact sheet + manifest.json only (fast look)\n hero beauty render only → <out>/<asset>/hero.png (3/4 product shot, textured, shadowed)\n review AI review pack → <out>/<asset>/review/: hero, sheet, close-ups, LOD strip, review.md\n (checklist prompt for a vision agent) and review.json (image metrics + warnings)\n list the assets each module exports\nflags:\n --out <dir> output root (default: out) → <dir>/<asset>/{lod0,lod1,lod2}.glb hero.png sheet.png manifest.json\n --no-animations skip the baked 'run' clip (default: baked, so viewers can play it)\n --no-shot build without rendering hero.png and the contact sheet\n --size WxH beauty render size (default 1600x900) · --az <deg> camera azimuth from the\n front, default -35 (front-left 3/4) · --el <deg> elevation, default 18 · --fov <deg> 30\n --sheet <mode> external (default): one shared trim_sheet_{albedo,normal,orm}.png in <out>/,\n referenced by every GLB · embed: self-contained GLBs · none: vertex colour only\n --sheet-size n 512 | 1024 | 2048 (default)\n --lods <list> which detail levels to build: 0,1,2 (default) · 0 = hero only · 0,2 = near + far\n --lods <mode> how they are written: both (default) = lod0/1/2.glb + <asset>.glb · split = lodN.glb\n only · single = <asset>.glb only (LOD0 in the scene, LOD1/2 as MSFT_lod alternates\n with screen-coverage switch hints; viewers without MSFT_lod show LOD0 only)\n --json machine-readable output`;\n\nexport function parseArgs(argv: readonly string[]): Args {\n const files: string[] = [];\n let out = 'out';\n let json = false;\n let animations = true;\n let shot = true;\n let sheet: Args['sheet'] = 'external';\n let sheetSize: Args['sheetSize'] = 2048;\n let lods: Args['lods'] = [0, 1, 2];\n let lodFiles: Args['lodFiles'] = 'both';\n const render: {\n width?: number;\n height?: number;\n azDeg?: number;\n elDeg?: number;\n fovDeg?: number;\n } = {};\n const num = (flag: string, v: string | undefined): number => {\n const n = Number(v);\n if (v === undefined || v.trim() === '' || !Number.isFinite(n))\n throw new Ts23dError('T3D_RENDER_OPTIONS', {\n cause: `${flag} needs a number of degrees, got ${v === undefined ? 'nothing' : `\"${v}\"`}`,\n fix: `pass e.g. ${flag} ${flag === '--fov' ? 30 : flag === '--el' ? 18 : -35}`,\n });\n return n;\n };\n for (let i = 1; i < argv.length; i++) {\n const a = argv[i] ?? '';\n if (a === '--json') json = true;\n else if (a === '--no-animations') animations = false;\n else if (a === '--animations') animations = true;\n else if (a === '--no-shot') shot = false;\n else if (a === '--out') out = argv[++i] ?? out;\n else if (a === '--sheet') {\n const m = argv[++i];\n sheet = m === 'embed' || m === 'none' ? m : 'external';\n } else if (a === '--lods' && /^(split|single|both)$/.test(argv[i + 1] ?? '')) {\n lodFiles = argv[++i] as Args['lodFiles'];\n } else if (a === '--lods') {\n const picked = (argv[++i] ?? '')\n .split(',')\n .map(Number)\n .filter((n): n is 0 | 1 | 2 => n === 0 || n === 1 || n === 2);\n lods = picked.length ? [...new Set(picked)].sort() : lods;\n } else if (a === '--sheet-size') {\n const n = Number(argv[++i]);\n sheetSize = n === 512 || n === 1024 ? n : 2048;\n } else if (a === '--size') Object.assign(render, parseSize(argv[++i] ?? ''));\n else if (a === '--az') render.azDeg = num(a, argv[++i]);\n else if (a === '--el') render.elDeg = num(a, argv[++i]);\n else if (a === '--fov') render.fovDeg = num(a, argv[++i]);\n else files.push(a);\n }\n checkBeautyOptions(render);\n return {\n cmd: argv[0] ?? 'help',\n files,\n out,\n json,\n animations,\n shot,\n sheet,\n sheetSize,\n lods,\n lodFiles,\n render,\n };\n}\n",
7
7
  "// Load a recipe module and collect every exported Asset. Recipes are plain TS modules: the CLI,\n// the site and tests all import them the same way.\n\nimport { resolve } from 'node:path';\nimport { type Asset, isAsset, Ts23dError } from '../core/index.ts';\n\nexport async function loadAssets(file: string): Promise<Asset[]> {\n const mod: Record<string, unknown> = await import(resolve(file));\n const assets = Object.values(mod).filter(isAsset);\n if (assets.length === 0) {\n throw new Ts23dError('T3D_NO_ASSET_EXPORT', {\n cause: `${file} exports no asset`,\n fix: 'export const my_thing = defineAsset({ … }) — named export, no default',\n });\n }\n return assets;\n}\n",
8
8
  "// `t3d hero` and `t3d review`: beauty renders and the AI review pack, one folder per asset.\n// <out>/<asset>/hero.png\n// <out>/<asset>/review/{hero,sheet,detail_1,detail_2,lods}.png review.md review.json\n\nimport { mkdir } from 'node:fs/promises';\nimport { dirname, join, relative, resolve } from 'node:path';\nimport { type Asset, compile } from '../core/index.ts';\nimport type { PaintedSheet } from '../material/index.ts';\nimport { beauty, type ReviewWarning, reviewPack } from '../render/index.ts';\nimport type { Args } from './args.ts';\n\nexport interface RenderResult {\n readonly name: string;\n readonly files: readonly string[];\n readonly warnings: readonly ReviewWarning[];\n}\n\nexport async function heroOne(\n asset: Asset,\n args: Args,\n sheet: PaintedSheet,\n): Promise<RenderResult> {\n const dir = join(args.out, asset.name);\n await mkdir(dir, { recursive: true });\n const path = join(dir, 'hero.png');\n await Bun.write(path, beauty(compile(asset, { lod: 0 }), { ...args.render, sheet }));\n return { name: asset.name, files: [path], warnings: [] };\n}\n\n/** The dossier: README.md next to the recipe (recipes/<name>/), else a legacy refs/<name>.md. */\nasync function refsFor(name: string, recipe: string): Promise<string | undefined> {\n for (const p of [\n join(dirname(resolve(recipe)), 'README.md'),\n join(dirname(resolve(recipe)), '..', 'refs', `${name}.md`),\n resolve('refs', `${name}.md`),\n ])\n if (await Bun.file(p).exists()) return relative(process.cwd(), p) || p;\n return undefined;\n}\n\nexport async function reviewOne(\n asset: Asset,\n recipe: string,\n args: Args,\n sheet: PaintedSheet,\n): Promise<RenderResult> {\n const dir = join(args.out, asset.name, 'review');\n await mkdir(dir, { recursive: true });\n const refs = await refsFor(asset.name, recipe);\n const pack = reviewPack(asset, { ...args.render, sheet, ...(refs ? { refs } : {}) });\n const files: string[] = [];\n for (const [name, bytes] of pack.files) {\n const path = join(dir, name);\n await Bun.write(path, bytes);\n files.push(path);\n }\n return { name: asset.name, files, warnings: pack.metrics.warnings };\n}\n\nexport function printRendered(r: RenderResult): void {\n console.log(\n `${r.name} ${r.warnings.length ? `${r.warnings.length} warning(s)` : 'no warnings'}`,\n );\n for (const f of r.files) console.log(` → ${f}`);\n for (const w of r.warnings) console.log(` ! ${w.code}: ${w.message}\\n fix: ${w.fix}`);\n}\n",
9
+ "// The local CC0 texture cache (`bun run textures` fills it): every file re-verified by its sha256\n// name, handed to `provideTextures` before painting. Missing files are fine here; painting a\n// material that needs one throws T3D_TEXTURE_MISSING with the fetch command.\n\nimport { readdir } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { TextureCache } from '../material/index.ts';\n\nexport const TEXTURE_CACHE = join('node_modules', '.cache', 'ts23d', 'textures');\n\nconst sha256 = (b: Uint8Array): string => new Bun.CryptoHasher('sha256').update(b).digest('hex');\n\n/** Every cached map whose bytes still match its sha256 name. */\nexport async function loadTextureCache(dir = TEXTURE_CACHE): Promise<TextureCache> {\n const out = new Map<string, Uint8Array>();\n const names = await readdir(dir).catch(() => [] as string[]);\n for (const name of names.sort()) {\n const m = /^([0-9a-f]{64})\\.png$/.exec(name);\n if (!m?.[1]) continue;\n const bytes = new Uint8Array(await Bun.file(join(dir, name)).arrayBuffer());\n if (sha256(bytes) === m[1]) out.set(m[1], bytes);\n }\n return out;\n}\n",
9
10
  "#!/usr/bin/env bun\n// `t3d` — compile TypeScript recipes to GLB, gate them, and shoot contact sheets. `t3d help`\n// prints the commands; src/cli/args.ts holds the flags, src/cli/run.ts the output layout.\n\nimport { run } from './run.ts';\n\nprocess.exitCode = await run(process.argv.slice(2));\n"
10
11
  ],
11
- "mappings": "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",
12
- "debugId": "0183AEABD246D46664756E2164756E21",
12
+ "mappings": "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",
13
+ "debugId": "BFD08E21916B124164756E2164756E21",
13
14
  "names": []
14
15
  }
@@ -7,11 +7,15 @@ export interface AssetManifest {
7
7
  readonly tags: readonly string[];
8
8
  readonly files: {
9
9
  readonly lods: readonly string[];
10
+ /** All built LODs in one GLB (MSFT_lod), when `--lods single|both`. */
11
+ readonly single?: string;
10
12
  readonly hero?: string;
11
13
  readonly sheet?: string;
12
14
  readonly thumb?: string;
13
15
  };
14
16
  readonly triangles: readonly number[];
17
+ /** MSFT_screencoverage written into `files.single`: where each LOD hands over (last = cull). */
18
+ readonly lodCoverage?: readonly number[];
15
19
  readonly drawCalls: readonly number[];
16
20
  readonly pivots: readonly {
17
21
  name: string;
@@ -46,4 +50,4 @@ export interface AssetManifest {
46
50
  readonly findings: readonly Finding[];
47
51
  };
48
52
  }
49
- export declare function manifestOf(asset: Asset, lods: readonly CompiledAsset[], files: AssetManifest['files'], findings: readonly Finding[]): AssetManifest;
53
+ export declare function manifestOf(asset: Asset, lods: readonly CompiledAsset[], files: AssetManifest['files'], findings: readonly Finding[], lodCoverage?: readonly number[]): AssetManifest;
@@ -0,0 +1,4 @@
1
+ import type { TextureCache } from '../material/index.js';
2
+ export declare const TEXTURE_CACHE: string;
3
+ /** Every cached map whose bytes still match its sha256 name. */
4
+ export declare function loadTextureCache(dir?: string): Promise<TextureCache>;
@@ -129,6 +129,12 @@ export interface AssetDefinition {
129
129
  readonly creators?: readonly string[];
130
130
  /** Emissive part sets; `t3d build` writes them as glowing materials. */
131
131
  readonly glow?: readonly Glow[];
132
+ /**
133
+ * View-distance hints for the single-file GLB (MSFT_lod `MSFT_screencoverage`): the screen-height
134
+ * fraction below which LOD0 → LOD1, LOD1 → LOD2, and LOD2 is culled (0 = never). Strictly
135
+ * descending, 1 > a > b > c >= 0. Omitted: derived from the triangle counts and size.
136
+ */
137
+ readonly lodCoverage?: readonly [number, number, number];
132
138
  build(ctx: BuildContext): void;
133
139
  }
134
140
  export interface Asset extends AssetDefinition {
@@ -145,4 +151,5 @@ export interface CompiledAsset {
145
151
  /** Provenance carried from the definition (absent when the recipe declares none). */
146
152
  readonly meta?: AssetMeta;
147
153
  readonly glow?: readonly Glow[];
154
+ readonly lodCoverage?: readonly [number, number, number];
148
155
  }
@@ -1,4 +1,4 @@
1
- export type ErrorCode = 'T3D_NOT_IMPLEMENTED' | 'T3D_GRAMMAR_SYNTAX' | 'T3D_GRAMMAR_OVERFLOW' | 'T3D_PART_DUPLICATE' | 'T3D_PART_UNKNOWN' | 'T3D_RECIPE_INVALID' | 'T3D_NO_ASSET_EXPORT' | 'T3D_QA_FAILED' | 'T3D_RENDER_OPTIONS' | 'T3D_AMBIENT_RANDOM' | 'T3D_RELEASE' | 'T3D_BENCH';
1
+ export type ErrorCode = 'T3D_NOT_IMPLEMENTED' | 'T3D_GRAMMAR_SYNTAX' | 'T3D_GRAMMAR_OVERFLOW' | 'T3D_PART_DUPLICATE' | 'T3D_PART_UNKNOWN' | 'T3D_RECIPE_INVALID' | 'T3D_NO_ASSET_EXPORT' | 'T3D_QA_FAILED' | 'T3D_RENDER_OPTIONS' | 'T3D_AMBIENT_RANDOM' | 'T3D_RELEASE' | 'T3D_BENCH' | 'T3D_MATERIAL_INVALID' | 'T3D_MATERIAL_UNKNOWN' | 'T3D_MATERIAL_CONFLICT' | 'T3D_SHEET_FULL' | 'T3D_TEXTURE_MISSING';
2
2
  export interface ErrorDetails {
3
3
  readonly cause: string;
4
4
  readonly fix: string;
@@ -30,7 +30,7 @@ import {
30
30
  rotateZ2,
31
31
  worldMesh2,
32
32
  worldMeshes2
33
- } from "../chunk-4ddzsqtc.js";
33
+ } from "../chunk-hg7pf34j.js";
34
34
  export {
35
35
  BUDGETS2 as BUDGETS,
36
36
  MeshBuilder2 as MeshBuilder,
@@ -0,0 +1,4 @@
1
+ import type { CompiledAsset } from '../core/index.js';
2
+ import type { GlbMeta } from './metadata.js';
3
+ export declare function creditsOf(asset: CompiledAsset): string[];
4
+ export declare function withCredits(asset: CompiledAsset, meta: GlbMeta | undefined): GlbMeta | undefined;
@@ -0,0 +1,48 @@
1
+ import type { OptimizeOptions } from '../passes/index.js';
2
+ import type { LodCoverage } from './lod-coverage.js';
3
+ import type { GlowRule } from './materials.js';
4
+ import type { GlbMeta } from './metadata.js';
5
+ import type { ExportReport } from './optimize-parts.js';
6
+ import type { SheetBinding } from './textures.js';
7
+ export interface GlbOptions {
8
+ /** Keep every part as its own node (debugging, per-part picking). Default merges to pivots. */
9
+ readonly keepParts?: boolean;
10
+ /**
11
+ * Bake each part's `motion` into a looping `run` clip. Off by default: glTF has no autoplay
12
+ * flag and most viewers play the first clip, while engines usually drive the named pivots
13
+ * from game code. Turn it on for previews, cutscenes and ambient props.
14
+ */
15
+ readonly animations?: boolean;
16
+ /**
17
+ * Bind the painted trim sheet to every material: a `PaintedSheet` embeds the three PNGs (one
18
+ * self-contained file); `{ external: true, prefix? }` references shared `trim_sheet_*.png` by
19
+ * URI. Omitted: no textures, byte-identical to the untextured output.
20
+ */
21
+ readonly sheet?: SheetBinding;
22
+ /**
23
+ * Weld + vertex-cache + vertex-fetch optimisation per primitive (passes/optimize). On by
24
+ * default; `false` writes the meshes exactly as compiled (debugging the IR).
25
+ */
26
+ readonly optimize?: boolean | OptimizeOptions;
27
+ /** Parts that emit light (lamp lenses, lit windows): one emissive material per rule. */
28
+ readonly glow?: readonly GlowRule[];
29
+ /** Provenance: asset.copyright + an XMP packet (title, creators, licence, sources). */
30
+ readonly meta?: GlbMeta;
31
+ /**
32
+ * Single-file LODs only: [LOD0→LOD1, LOD1→LOD2, cull] screen-height fractions written as
33
+ * `MSFT_screencoverage`. Default: the asset's `lodCoverage`, else derived (lod-coverage.ts).
34
+ */
35
+ readonly lodCoverage?: LodCoverage;
36
+ }
37
+ /** The GLB bytes plus what the optimiser did (vertex counts, ACMR before/after, per part). */
38
+ export interface GlbResult {
39
+ readonly glb: Uint8Array;
40
+ readonly report: ExportReport;
41
+ }
42
+ /** One GLB holding several LODs, plus one optimiser report per LOD (same order as the input). */
43
+ export interface LodGlbResult {
44
+ readonly glb: Uint8Array;
45
+ readonly reports: readonly ExportReport[];
46
+ /** The `MSFT_screencoverage` written (empty for a single LOD). */
47
+ readonly coverage: readonly number[];
48
+ }
@@ -1,38 +1,8 @@
1
1
  import type { CompiledAsset } from '../core/index.js';
2
- import type { OptimizeOptions } from '../passes/index.js';
3
- import { type GlowRule } from './materials.js';
4
- import { type GlbMeta } from './metadata.js';
5
- import { type ExportReport } from './optimize-parts.js';
6
- import { type SheetBinding } from './textures.js';
7
- export interface GlbOptions {
8
- /** Keep every part as its own node (debugging, per-part picking). Default merges to pivots. */
9
- readonly keepParts?: boolean;
10
- /**
11
- * Bake each part's `motion` into a looping `run` clip. Off by default: glTF has no autoplay
12
- * flag and most viewers play the first clip, while engines usually drive the named pivots
13
- * from game code. Turn it on for previews, cutscenes and ambient props.
14
- */
15
- readonly animations?: boolean;
16
- /**
17
- * Bind the painted trim sheet to every material: a `PaintedSheet` embeds the three PNGs (one
18
- * self-contained file); `{ external: true, prefix? }` references shared `trim_sheet_*.png` by
19
- * URI. Omitted: no textures, byte-identical to the untextured output.
20
- */
21
- readonly sheet?: SheetBinding;
22
- /**
23
- * Weld + vertex-cache + vertex-fetch optimisation per primitive (passes/optimize). On by
24
- * default; `false` writes the meshes exactly as compiled (debugging the IR).
25
- */
26
- readonly optimize?: boolean | OptimizeOptions;
27
- /** Parts that emit light (lamp lenses, lit windows): one emissive material per rule. */
28
- readonly glow?: readonly GlowRule[];
29
- /** Provenance: asset.copyright + an XMP packet (title, creators, licence, sources). */
30
- readonly meta?: GlbMeta;
31
- }
32
- /** The GLB bytes plus what the optimiser did (vertex counts, ACMR before/after, per part). */
33
- export interface GlbResult {
34
- readonly glb: Uint8Array;
35
- readonly report: ExportReport;
36
- }
2
+ import type { GlbOptions, GlbResult, LodGlbResult } from './glb-options.js';
3
+ export type { GlbOptions, GlbResult, LodGlbResult } from './glb-options.js';
37
4
  export declare function toGlb(input: CompiledAsset, o?: GlbOptions): Uint8Array;
38
5
  export declare function exportGlb(input: CompiledAsset, o?: GlbOptions): GlbResult;
6
+ /** All LODs in one GLB: LOD0 in the scene, the rest as MSFT_lod alternates outside it. */
7
+ export declare function toLodGlb(lods: readonly CompiledAsset[], o?: GlbOptions): Uint8Array;
8
+ export declare function exportLodGlb(lods: readonly CompiledAsset[], o?: GlbOptions): LodGlbResult;
@@ -0,0 +1,23 @@
1
+ import type { CompiledAsset, Part } from '../core/index.js';
2
+ import type { BinWriter, Json } from './bin-writer.js';
3
+ /** The growing glTF arrays every LOD appends to. */
4
+ export interface GltfDoc {
5
+ readonly bin: BinWriter;
6
+ readonly nodes: Json[];
7
+ readonly meshes: Json[];
8
+ readonly lights: Json[];
9
+ readonly samplers: Json[];
10
+ readonly channels: Json[];
11
+ /** motion + rest pose → sampler index: the same wheel spin at LOD0 and LOD1 is one sampler. */
12
+ readonly motionSamplers: Map<string, number>;
13
+ readonly lightIndex: Map<string, number>;
14
+ }
15
+ export declare const newDoc: (bin: BinWriter) => GltfDoc;
16
+ export interface HierarchyOptions {
17
+ /** Material index for a part (trim sheet by surface, or its glow layer). */
18
+ readonly materialOf: (part: Part, i: number) => number;
19
+ readonly withSheet: boolean;
20
+ readonly animations: boolean;
21
+ }
22
+ /** Appends `asset`'s tree under a new root named `rootName`; returns the root node index. */
23
+ export declare function writeHierarchy(doc: GltfDoc, asset: CompiledAsset, rootName: string, o: HierarchyOptions): number;
@@ -1,4 +1,5 @@
1
- export { exportGlb, type GlbOptions, type GlbResult, toGlb } from './glb.js';
1
+ export { exportGlb, exportLodGlb, type GlbOptions, type GlbResult, type LodGlbResult, toGlb, toLodGlb, } from './glb.js';
2
+ export { checkCoverage, coverageFor, defaultCoverage, type LodCoverage, MSFT_LOD, SCREEN_COVERAGE, } from './lod-coverage.js';
2
3
  export { mergeToPivots } from './merge-pivots.js';
3
4
  export { type ExportReport, optimizeParts, type PartOptimizeReport, summarize, } from './optimize-parts.js';
4
5
  export { type ExternalSheet, SHEET_MAPS, type SheetBinding, type SheetMap, sheetFileName, sheetPngs, } from './textures.js';
@@ -1,4 +1,9 @@
1
1
  import {
2
+ MSFT_LOD2,
3
+ SCREEN_COVERAGE2,
4
+ defaultCoverage2,
5
+ checkCoverage2,
6
+ coverageFor2,
2
7
  mergeToPivots2,
3
8
  summarize2,
4
9
  optimizeParts2,
@@ -6,21 +11,30 @@ import {
6
11
  sheetFileName2,
7
12
  sheetPngs2,
8
13
  toGlb2,
9
- exportGlb2
10
- } from "../chunk-gcyp9b4d.js";
11
- import"../chunk-kvcep7ft.js";
12
- import"../chunk-xnt2sdhf.js";
13
- import"../chunk-4ddzsqtc.js";
14
+ exportGlb2,
15
+ toLodGlb2,
16
+ exportLodGlb2
17
+ } from "../chunk-dbq4qf52.js";
18
+ import"../chunk-y63c6zcv.js";
19
+ import"../chunk-drna2rny.js";
20
+ import"../chunk-hg7pf34j.js";
14
21
  export {
22
+ MSFT_LOD2 as MSFT_LOD,
23
+ SCREEN_COVERAGE2 as SCREEN_COVERAGE,
15
24
  SHEET_MAPS2 as SHEET_MAPS,
25
+ checkCoverage2 as checkCoverage,
26
+ coverageFor2 as coverageFor,
27
+ defaultCoverage2 as defaultCoverage,
16
28
  exportGlb2 as exportGlb,
29
+ exportLodGlb2 as exportLodGlb,
17
30
  mergeToPivots2 as mergeToPivots,
18
31
  optimizeParts2 as optimizeParts,
19
32
  sheetFileName2 as sheetFileName,
20
33
  sheetPngs2 as sheetPngs,
21
34
  summarize2 as summarize,
22
- toGlb2 as toGlb
35
+ toGlb2 as toGlb,
36
+ toLodGlb2 as toLodGlb
23
37
  };
24
38
 
25
- //# debugId=E49FD25380C5362E64756E2164756E21
39
+ //# debugId=8BD3B1362543914364756E2164756E21
26
40
  //# sourceMappingURL=index.js.map
@@ -4,6 +4,6 @@
4
4
  "sourcesContent": [
5
5
  ],
6
6
  "mappings": "",
7
- "debugId": "E49FD25380C5362E64756E2164756E21",
7
+ "debugId": "8BD3B1362543914364756E2164756E21",
8
8
  "names": []
9
9
  }