@mcptoolshop/style-dataset-lab 1.2.0 → 2.0.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 (57) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/README.es.md +94 -54
  3. package/README.fr.md +86 -46
  4. package/README.hi.md +86 -46
  5. package/README.it.md +93 -53
  6. package/README.ja.md +86 -46
  7. package/README.md +95 -116
  8. package/README.pt-BR.md +86 -46
  9. package/README.zh.md +90 -50
  10. package/bin/sdlab.js +103 -0
  11. package/lib/args.js +106 -0
  12. package/lib/comfyui.js +109 -0
  13. package/lib/config.js +104 -0
  14. package/lib/deprecation.js +14 -0
  15. package/lib/paths.js +38 -0
  16. package/lib/records.js +69 -0
  17. package/package.json +13 -4
  18. package/scripts/bulk-curate-wave2-5.js +1 -1
  19. package/scripts/bulk-curate-waves11-18.js +1 -1
  20. package/scripts/canon-bind.js +268 -332
  21. package/scripts/compare.js +40 -30
  22. package/scripts/curate-wave25.js +1 -1
  23. package/scripts/curate.js +34 -28
  24. package/scripts/doctor.js +321 -0
  25. package/scripts/generate-controlnet.js +36 -121
  26. package/scripts/generate-identity.js +38 -106
  27. package/scripts/generate-ipadapter.js +42 -119
  28. package/scripts/generate.js +22 -81
  29. package/scripts/init.js +226 -0
  30. package/scripts/migrate-records.js +15 -21
  31. package/scripts/painterly-test.js +20 -47
  32. package/scripts/painterly.js +27 -106
  33. package/templates/domains/architecture/constitution.json +97 -0
  34. package/templates/domains/architecture/lanes.json +41 -0
  35. package/templates/domains/architecture/project.json +20 -0
  36. package/templates/domains/architecture/rubric.json +40 -0
  37. package/templates/domains/architecture/terminology.json +19 -0
  38. package/templates/domains/character-design/constitution.json +88 -0
  39. package/templates/domains/character-design/lanes.json +35 -0
  40. package/templates/domains/character-design/project.json +17 -0
  41. package/templates/domains/character-design/rubric.json +38 -0
  42. package/templates/domains/character-design/terminology.json +21 -0
  43. package/templates/domains/creature-design/constitution.json +87 -0
  44. package/templates/domains/creature-design/lanes.json +41 -0
  45. package/templates/domains/creature-design/project.json +18 -0
  46. package/templates/domains/creature-design/rubric.json +39 -0
  47. package/templates/domains/creature-design/terminology.json +20 -0
  48. package/templates/domains/game-art/constitution.json +113 -0
  49. package/templates/domains/game-art/lanes.json +47 -0
  50. package/templates/domains/game-art/project.json +17 -0
  51. package/templates/domains/game-art/rubric.json +36 -0
  52. package/templates/domains/game-art/terminology.json +30 -0
  53. package/templates/domains/vehicle-mech/constitution.json +87 -0
  54. package/templates/domains/vehicle-mech/lanes.json +41 -0
  55. package/templates/domains/vehicle-mech/project.json +17 -0
  56. package/templates/domains/vehicle-mech/rubric.json +40 -0
  57. package/templates/domains/vehicle-mech/terminology.json +21 -0
@@ -10,18 +10,17 @@
10
10
  * Usage:
11
11
  * node scripts/painterly.js [--source outputs/approved] [--limit 100] [--offset 0]
12
12
  * node scripts/painterly.js --dry-run
13
+ * sdlab painterly --project star-freight --limit 50
13
14
  *
14
15
  * Output: outputs/painterly/<original_filename>
15
16
  */
16
17
 
17
- import { readFile, writeFile, mkdir, readdir, copyFile, stat } from "node:fs/promises";
18
- import { join, basename, extname } from "node:path";
18
+ import { writeFile, mkdir, readdir } from "node:fs/promises";
19
+ import { join } from "node:path";
19
20
  import { existsSync } from "node:fs";
20
-
21
- const COMFY_URL = process.env.COMFY_URL || "http://127.0.0.1:8188";
22
- const REPO_ROOT = new URL("..", import.meta.url).pathname.replace(/^\/([A-Z]:)/, "$1");
23
- const GAME = process.argv.find((a, i) => process.argv[i - 1] === '--game') || 'star-freight';
24
- const GAME_ROOT = join(REPO_ROOT, 'games', GAME);
21
+ import { getProjectName } from "../lib/args.js";
22
+ import { REPO_ROOT } from "../lib/paths.js";
23
+ import { comfyHealth, submitAndWait, downloadImage, uploadImage } from "../lib/comfyui.js";
25
24
 
26
25
  // ── Config ──
27
26
 
@@ -51,20 +50,10 @@ const DEFAULTS = {
51
50
  seed: 42, // Fixed seed for reproducibility across batch
52
51
  };
53
52
 
54
- // ── ComfyUI helpers ──
55
-
56
- async function comfyHealth() {
57
- try {
58
- const res = await fetch(`${COMFY_URL}/system_stats`);
59
- return res.ok;
60
- } catch { return false; }
61
- }
62
-
63
53
  function buildImg2ImgWorkflow(imagePath, seed) {
64
54
  const nodes = {};
65
55
  let nextId = 1;
66
56
 
67
- // Checkpoint loader
68
57
  const ckptId = String(nextId++);
69
58
  nodes[ckptId] = {
70
59
  class_type: "CheckpointLoaderSimple",
@@ -74,7 +63,6 @@ function buildImg2ImgWorkflow(imagePath, seed) {
74
63
  let modelOut = [ckptId, 0];
75
64
  let clipOut = [ckptId, 1];
76
65
 
77
- // LoRA — ClassipeintXL at reduced weight for style shift
78
66
  const loraId = String(nextId++);
79
67
  nodes[loraId] = {
80
68
  class_type: "LoraLoader",
@@ -89,14 +77,12 @@ function buildImg2ImgWorkflow(imagePath, seed) {
89
77
  modelOut = [loraId, 0];
90
78
  clipOut = [loraId, 1];
91
79
 
92
- // Load source image
93
80
  const loadImgId = String(nextId++);
94
81
  nodes[loadImgId] = {
95
82
  class_type: "LoadImage",
96
83
  inputs: { image: imagePath },
97
84
  };
98
85
 
99
- // VAE Encode — source image to latent
100
86
  const vaeEncId = String(nextId++);
101
87
  nodes[vaeEncId] = {
102
88
  class_type: "VAEEncode",
@@ -106,21 +92,18 @@ function buildImg2ImgWorkflow(imagePath, seed) {
106
92
  },
107
93
  };
108
94
 
109
- // CLIP Text Encode — positive (painterly style)
110
95
  const posId = String(nextId++);
111
96
  nodes[posId] = {
112
97
  class_type: "CLIPTextEncode",
113
98
  inputs: { text: PAINTERLY_PROMPT, clip: clipOut },
114
99
  };
115
100
 
116
- // CLIP Text Encode — negative
117
101
  const negId = String(nextId++);
118
102
  nodes[negId] = {
119
103
  class_type: "CLIPTextEncode",
120
104
  inputs: { text: PAINTERLY_NEGATIVE, clip: clipOut },
121
105
  };
122
106
 
123
- // KSampler — img2img with low denoise
124
107
  const samplerId = String(nextId++);
125
108
  nodes[samplerId] = {
126
109
  class_type: "KSampler",
@@ -138,7 +121,6 @@ function buildImg2ImgWorkflow(imagePath, seed) {
138
121
  },
139
122
  };
140
123
 
141
- // VAE Decode
142
124
  const vaeDecId = String(nextId++);
143
125
  nodes[vaeDecId] = {
144
126
  class_type: "VAEDecode",
@@ -148,7 +130,6 @@ function buildImg2ImgWorkflow(imagePath, seed) {
148
130
  },
149
131
  };
150
132
 
151
- // Save Image
152
133
  const saveId = String(nextId++);
153
134
  nodes[saveId] = {
154
135
  class_type: "SaveImage",
@@ -161,84 +142,22 @@ function buildImg2ImgWorkflow(imagePath, seed) {
161
142
  return { nodes, saveNodeId: saveId };
162
143
  }
163
144
 
164
- const MAX_POLL_MS = 600_000; // 10 minutes
145
+ export async function run(argv = process.argv.slice(2)) {
146
+ const projectName = getProjectName(argv);
147
+ const GAME_ROOT = join(REPO_ROOT, 'projects', projectName);
148
+ const COMFY_URL = process.env.COMFY_URL || "http://127.0.0.1:8188";
165
149
 
166
- async function submitAndWait(workflow) {
167
- const clientId = `sdl-paint-${Date.now()}`;
168
- const res = await fetch(`${COMFY_URL}/prompt`, {
169
- method: "POST",
170
- headers: { "Content-Type": "application/json" },
171
- body: JSON.stringify({ prompt: workflow, client_id: clientId }),
172
- });
173
-
174
- if (!res.ok) {
175
- const text = await res.text();
176
- throw new Error(`ComfyUI submit failed: ${res.status} ${text}`);
177
- }
178
-
179
- const { prompt_id: promptId } = await res.json();
180
-
181
- const start = Date.now();
182
- while (true) {
183
- if (Date.now() - start > MAX_POLL_MS) throw new Error(`ComfyUI poll timeout after ${MAX_POLL_MS/1000}s for prompt ${promptId}`);
184
- await new Promise((r) => setTimeout(r, 1000));
185
- const histRes = await fetch(`${COMFY_URL}/history/${promptId}`);
186
- if (!histRes.ok) continue;
187
- const history = await histRes.json();
188
- const entry = history[promptId];
189
- if (!entry) continue;
190
- if (entry.status?.completed) return entry;
191
- if (entry.status?.status_str === "error") {
192
- throw new Error(`ComfyUI generation failed: ${JSON.stringify(entry.status)}`);
193
- }
194
- }
195
- }
196
-
197
- async function downloadImage(filename, subfolder, type = "output") {
198
- const url = `${COMFY_URL}/view?filename=${encodeURIComponent(filename)}&subfolder=${encodeURIComponent(subfolder || "")}&type=${type}`;
199
- const res = await fetch(url);
200
- if (!res.ok) throw new Error(`Download failed: ${res.status}`);
201
- return Buffer.from(await res.arrayBuffer());
202
- }
203
-
204
- /**
205
- * Upload an image to ComfyUI's input folder so LoadImage can find it.
206
- */
207
- async function uploadImage(filePath, filename) {
208
- const data = await readFile(filePath);
209
- const formData = new FormData();
210
- formData.append("image", new Blob([data], { type: "image/png" }), filename);
211
- formData.append("overwrite", "true");
212
-
213
- const res = await fetch(`${COMFY_URL}/upload/image`, {
214
- method: "POST",
215
- body: formData,
216
- });
217
-
218
- if (!res.ok) {
219
- const text = await res.text();
220
- throw new Error(`Upload failed: ${res.status} ${text}`);
221
- }
222
-
223
- const result = await res.json();
224
- return result.name; // The name ComfyUI assigned to the uploaded image
225
- }
226
-
227
- // ── Main ──
228
-
229
- async function main() {
230
- const args = process.argv.slice(2);
231
- const dryRun = args.includes("--dry-run");
150
+ const dryRun = argv.includes("--dry-run");
232
151
 
233
152
  // Parse args
234
153
  let sourceDir = "outputs/approved";
235
154
  let limit = 100;
236
155
  let offset = 0;
237
156
 
238
- for (let i = 0; i < args.length; i++) {
239
- if (args[i] === "--source" && args[i + 1]) sourceDir = args[++i];
240
- if (args[i] === "--limit" && args[i + 1]) limit = parseInt(args[++i]);
241
- if (args[i] === "--offset" && args[i + 1]) offset = parseInt(args[++i]);
157
+ for (let i = 0; i < argv.length; i++) {
158
+ if (argv[i] === "--source" && argv[i + 1]) sourceDir = argv[++i];
159
+ if (argv[i] === "--limit" && argv[i + 1]) limit = parseInt(argv[++i]);
160
+ if (argv[i] === "--offset" && argv[i + 1]) offset = parseInt(argv[++i]);
242
161
  }
243
162
 
244
163
  const fullSourceDir = join(GAME_ROOT, sourceDir);
@@ -274,10 +193,9 @@ async function main() {
274
193
  }
275
194
 
276
195
  if (!dryRun) {
277
- const online = await comfyHealth();
196
+ const online = await comfyHealth(COMFY_URL);
278
197
  if (!online) {
279
- console.error("\x1b[31mError:\x1b[0m ComfyUI not reachable at " + COMFY_URL);
280
- process.exit(1);
198
+ throw new Error("ComfyUI not reachable at " + COMFY_URL);
281
199
  }
282
200
  console.log("\x1b[32m✓\x1b[0m ComfyUI online");
283
201
  }
@@ -298,11 +216,11 @@ async function main() {
298
216
  const startMs = Date.now();
299
217
 
300
218
  // Upload source image to ComfyUI
301
- const uploadedName = await uploadImage(srcPath, file);
219
+ const uploadedName = await uploadImage(srcPath, file, COMFY_URL);
302
220
 
303
221
  // Build and submit img2img workflow
304
222
  const { nodes } = buildImg2ImgWorkflow(uploadedName, DEFAULTS.seed);
305
- const result = await submitAndWait(nodes);
223
+ const result = await submitAndWait(nodes, COMFY_URL, { clientPrefix: 'sdl-paint' });
306
224
  const elapsed = Date.now() - startMs;
307
225
 
308
226
  // Find output image
@@ -325,7 +243,7 @@ async function main() {
325
243
  }
326
244
 
327
245
  // Download and save with original filename
328
- const imgData = await downloadImage(imageFile, imageSubfolder);
246
+ const imgData = await downloadImage(imageFile, imageSubfolder, COMFY_URL);
329
247
  await writeFile(join(outDir, file), imgData);
330
248
 
331
249
  const kb = Math.round(imgData.length / 1024);
@@ -343,7 +261,10 @@ async function main() {
343
261
  if (dryRun) console.log(" (dry run — no images processed)");
344
262
  }
345
263
 
346
- main().catch((err) => {
347
- console.error(err);
348
- process.exit(1);
349
- });
264
+ // Direct execution guard
265
+ if (process.argv[1] && (process.argv[1].endsWith('painterly.js') || process.argv[1].endsWith('painterly'))) {
266
+ run().catch((err) => {
267
+ console.error(err.message || err);
268
+ process.exit(1);
269
+ });
270
+ }
@@ -0,0 +1,97 @@
1
+ {
2
+ "version": "1.0.0",
3
+ "rules": [
4
+ {
5
+ "id": "STR-001",
6
+ "category": "structure",
7
+ "desc": "Structural plausibility — buildings look like they could stand",
8
+ "dims": ["structural_plausibility"],
9
+ "faction_specific": false,
10
+ "rationale_pass": "Structure reads as plausible — load paths and massing are believable (${scorePct}%)",
11
+ "rationale_fail": "Structure implausible — walls, spans, or cantilevers defy credible engineering (${scorePct}%)"
12
+ },
13
+ {
14
+ "id": "MAT-001",
15
+ "category": "material",
16
+ "desc": "Material consistency — stone reads as stone, wood as wood, metal as metal",
17
+ "dims": ["material_consistency"],
18
+ "faction_specific": false,
19
+ "rationale_pass": "Materials read correctly — surfaces have appropriate weight, texture, and joinery (${scorePct}%)",
20
+ "rationale_fail": "Material reads wrong — surface lacks expected weight, grain, or construction logic (${scorePct}%)"
21
+ },
22
+ {
23
+ "id": "PER-001",
24
+ "category": "perspective",
25
+ "desc": "Perspective accuracy — no impossible geometry or broken vanishing points",
26
+ "dims": ["perspective_accuracy"],
27
+ "faction_specific": false,
28
+ "rationale_pass": "Perspective is accurate — vanishing points consistent, no spatial contradictions (${scorePct}%)",
29
+ "rationale_fail": "Perspective broken — impossible geometry, misaligned vanishing points, or spatial contradictions (${scorePct}%)"
30
+ },
31
+ {
32
+ "id": "LGT-001",
33
+ "category": "lighting",
34
+ "desc": "Lighting and mood — atmosphere serves narrative intent",
35
+ "dims": ["lighting_mood"],
36
+ "faction_specific": false,
37
+ "rationale_pass": "Lighting carries mood — shadow, bounce, and atmosphere serve the narrative intent (${scorePct}%)",
38
+ "rationale_fail": "Lighting flat or contradictory — no mood, multiple shadow sources, or atmosphere works against intent (${scorePct}%)"
39
+ },
40
+ {
41
+ "id": "SCL-001",
42
+ "category": "scale",
43
+ "desc": "Scale consistency — human-scale reference readable",
44
+ "dims": ["scale_consistency"],
45
+ "faction_specific": false,
46
+ "rationale_pass": "Scale reads correctly — doors, windows, and elements imply consistent human proportion (${scorePct}%)",
47
+ "rationale_fail": "Scale inconsistent — elements contradict each other or lack human-scale reference (${scorePct}%)"
48
+ },
49
+ {
50
+ "id": "ERA-001",
51
+ "category": "era",
52
+ "desc": "Era and tech-level coherence — no anachronisms within the depicted period",
53
+ "dims": ["era_coherence"],
54
+ "faction_specific": false,
55
+ "rationale_pass": "Era coherent — construction methods, ornament, and technology match the depicted period (${scorePct}%)",
56
+ "rationale_fail": "Anachronism present — elements from incompatible time periods or tech levels mixed (${scorePct}%)"
57
+ },
58
+ {
59
+ "id": "WEA-001",
60
+ "category": "weathering",
61
+ "desc": "Weathering and aging appropriate to era, climate, and material",
62
+ "dims": ["weathering_logic"],
63
+ "faction_specific": false,
64
+ "rationale_pass": "Weathering reads correctly — aging pattern matches material, climate, and implied age (${scorePct}%)",
65
+ "rationale_fail": "Weathering illogical — wear doesn't match material behavior, climate, or timeframe (${scorePct}%)"
66
+ },
67
+ {
68
+ "id": "STY-001",
69
+ "category": "style",
70
+ "desc": "Style consistency within the project — rendering and design language unified",
71
+ "dims": ["style_consistency"],
72
+ "faction_specific": false,
73
+ "rationale_pass": "Style consistent — rendering approach and design language match the project standard (${scorePct}%)",
74
+ "rationale_fail": "Style inconsistent — rendering or design language breaks from project standard (${scorePct}%)"
75
+ },
76
+ {
77
+ "id": "DET-001",
78
+ "category": "detail",
79
+ "desc": "Detail density appropriate to distance and scale of view",
80
+ "dims": ["detail_density"],
81
+ "faction_specific": false,
82
+ "lanes": ["structural_detail"],
83
+ "rationale_pass": "Detail density appropriate — close-ups show construction logic, wide shots stay readable (${scorePct}%)",
84
+ "rationale_fail": "Detail density wrong — close-up lacks construction detail or wide shot is noisy and unreadable (${scorePct}%)"
85
+ },
86
+ {
87
+ "id": "RUI-001",
88
+ "category": "ruin",
89
+ "desc": "Ruin logic — decay pattern follows structural weaknesses and material behavior",
90
+ "dims": ["ruin_logic"],
91
+ "faction_specific": false,
92
+ "lanes": ["ruin"],
93
+ "rationale_pass": "Ruin logic holds — collapse follows structural weakness, material decay is physically plausible (${scorePct}%)",
94
+ "rationale_fail": "Ruin logic fails — decay is arbitrary, structure collapses where it shouldn't or survives where it can't (${scorePct}%)"
95
+ }
96
+ ]
97
+ }
@@ -0,0 +1,41 @@
1
+ {
2
+ "default_lane": "exterior",
3
+ "lanes": [
4
+ {
5
+ "id": "exterior",
6
+ "label": "Building Exterior",
7
+ "description": "Building facades, elevations, full exterior views, architectural massing",
8
+ "id_patterns": ["^ext_", "^building_", "^facade_"]
9
+ },
10
+ {
11
+ "id": "interior",
12
+ "label": "Interior Space",
13
+ "description": "Rooms, halls, chambers, functional interiors with spatial atmosphere",
14
+ "id_patterns": ["^int_", "^room_", "^hall_"]
15
+ },
16
+ {
17
+ "id": "streetscape",
18
+ "label": "Streetscape / Urban",
19
+ "description": "Street-level views, district overviews, urban fabric and city scenes",
20
+ "id_patterns": ["^street_", "^district_", "^urban_"]
21
+ },
22
+ {
23
+ "id": "structural_detail",
24
+ "label": "Structural Detail",
25
+ "description": "Ornamental elements, material studies, construction details, decorative motifs",
26
+ "id_patterns": ["^detail_", "^ornament_", "^material_"]
27
+ },
28
+ {
29
+ "id": "ruin",
30
+ "label": "Ruin / Decay",
31
+ "description": "Ruined structures, abandoned buildings, post-collapse environments",
32
+ "id_patterns": ["^ruin_", "^decay_", "^abandoned_"]
33
+ },
34
+ {
35
+ "id": "landscape",
36
+ "label": "Landscape / Vista",
37
+ "description": "Wide establishing shots, panoramic views, architecture in landscape context",
38
+ "id_patterns": ["^landscape_", "^vista_", "^panorama_"]
39
+ }
40
+ ]
41
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "architecture",
3
+ "display_name": "Architecture",
4
+ "domain": "architecture",
5
+ "version": "1.0.0",
6
+ "description": "Architectural and worldbuilding concept art — building exteriors, interiors, streetscapes, structural details, ruins, and landscapes for film, game, and themed entertainment pre-visualization.",
7
+ "defaults": {
8
+ "checkpoint": "dreamshaperXL_v21TurboDPMSDE.safetensors",
9
+ "loras": [
10
+ { "name": "classipeintxl_v21.safetensors", "weight": 1.0 }
11
+ ],
12
+ "width": 1024,
13
+ "height": 1024,
14
+ "steps": 8,
15
+ "cfg": 2.0,
16
+ "sampler": "dpmpp_sde",
17
+ "scheduler": "karras",
18
+ "comfy_url": "http://127.0.0.1:8188"
19
+ }
20
+ }
@@ -0,0 +1,40 @@
1
+ {
2
+ "dimensions": [
3
+ "structural_plausibility",
4
+ "material_consistency",
5
+ "perspective_accuracy",
6
+ "lighting_mood",
7
+ "scale_consistency",
8
+ "era_coherence",
9
+ "weathering_logic",
10
+ "style_consistency",
11
+ "detail_density",
12
+ "ruin_logic"
13
+ ],
14
+ "thresholds": {
15
+ "approved": {
16
+ "pass": 0.7,
17
+ "partial": 0.5
18
+ },
19
+ "borderline": {
20
+ "pass": 0.7,
21
+ "partial": 0.5
22
+ },
23
+ "rejected": {
24
+ "fail_ceiling": 0.4,
25
+ "partial_ceiling": 0.6
26
+ }
27
+ },
28
+ "failure_to_rules": {
29
+ "impossible_structure": ["STR-001"],
30
+ "wrong_material": ["MAT-001"],
31
+ "bad_perspective": ["PER-001"],
32
+ "anachronism": ["ERA-001"],
33
+ "flat_lighting": ["LGT-001"],
34
+ "scale_mismatch": ["SCL-001"],
35
+ "illogical_weathering": ["WEA-001"],
36
+ "style_break": ["STY-001"],
37
+ "detail_mismatch": ["DET-001"],
38
+ "arbitrary_decay": ["RUI-001"]
39
+ }
40
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "group_label": "style_period",
3
+ "id_detection_order": [],
4
+ "prompt_detection_order": [],
5
+ "groups": {},
6
+ "cross_group_patterns": {},
7
+ "edge_defaults": {},
8
+ "null_faction_patterns": [],
9
+ "id_fallbacks": {},
10
+ "faction_context": {},
11
+ "training_profiles": {
12
+ "profile_id": "architecture-style",
13
+ "intended_use": "Architectural style LoRA for consistent worldbuilding",
14
+ "eligible_lanes": ["exterior", "interior", "streetscape"],
15
+ "minimum_review_threshold": 0.7,
16
+ "excluded_content": ["rejected"],
17
+ "export_format": "trl"
18
+ }
19
+ }
@@ -0,0 +1,88 @@
1
+ {
2
+ "version": "1.0.0",
3
+ "rules": [
4
+ {
5
+ "id": "PRO-001",
6
+ "category": "proportion",
7
+ "desc": "Anatomical proportion accuracy — limb length, joint placement, mass distribution",
8
+ "dims": ["proportion_accuracy"],
9
+ "rationale_pass": "Proportions are anatomically sound — limbs, joints, and mass read correctly (${scorePct}%)",
10
+ "rationale_fail": "Proportion errors — limb length, joint placement, or mass distribution incorrect (${scorePct}%)"
11
+ },
12
+ {
13
+ "id": "PRO-002",
14
+ "category": "proportion",
15
+ "desc": "Consistent head-to-body ratio across all views and poses",
16
+ "dims": ["proportion_accuracy"],
17
+ "rationale_pass": "Head-to-body ratio is consistent across views (${scorePct}%)",
18
+ "rationale_fail": "Head-to-body ratio drifts between views — character reads as different heights (${scorePct}%)"
19
+ },
20
+ {
21
+ "id": "GES-001",
22
+ "category": "gesture",
23
+ "desc": "Gesture clarity — pose communicates intent, weight, and emotion",
24
+ "dims": ["gesture_clarity"],
25
+ "rationale_pass": "Gesture reads clearly — pose communicates intent and weight (${scorePct}%)",
26
+ "rationale_fail": "Gesture is stiff or ambiguous — pose does not communicate intent (${scorePct}%)"
27
+ },
28
+ {
29
+ "id": "COS-001",
30
+ "category": "costume",
31
+ "desc": "Costume logic — layers make functional sense, nothing floats or clips irrationally",
32
+ "dims": ["costume_logic"],
33
+ "rationale_pass": "Costume logic holds — layers are functional and physically plausible (${scorePct}%)",
34
+ "rationale_fail": "Costume logic broken — floating elements, impossible layers, or nonsensical construction (${scorePct}%)"
35
+ },
36
+ {
37
+ "id": "COS-002",
38
+ "category": "costume",
39
+ "desc": "Costume consistency across poses and views — same garments, same details",
40
+ "dims": ["costume_logic", "style_consistency"],
41
+ "rationale_pass": "Costume is consistent across poses and views — details match (${scorePct}%)",
42
+ "rationale_fail": "Costume changes between views — missing details, altered garments, or inconsistent accessories (${scorePct}%)"
43
+ },
44
+ {
45
+ "id": "PER-001",
46
+ "category": "personality",
47
+ "desc": "Personality read — character is recognizable by silhouette alone",
48
+ "dims": ["personality_read"],
49
+ "rationale_pass": "Character reads at silhouette level — distinct and recognizable (${scorePct}%)",
50
+ "rationale_fail": "Character is generic — silhouette is not distinctive or recognizable (${scorePct}%)"
51
+ },
52
+ {
53
+ "id": "STY-001",
54
+ "category": "style",
55
+ "desc": "Style consistency across the project — rendering, line quality, color treatment match",
56
+ "dims": ["style_consistency"],
57
+ "rationale_pass": "Style is consistent with the project — rendering and color treatment match (${scorePct}%)",
58
+ "rationale_fail": "Style drifts from project standard — rendering or color treatment inconsistent (${scorePct}%)"
59
+ },
60
+ {
61
+ "id": "TUR-001",
62
+ "category": "turnaround",
63
+ "desc": "Turnaround accuracy — same character from all angles, proportions hold in rotation",
64
+ "dims": ["turnaround_accuracy"],
65
+ "lanes": ["turnaround"],
66
+ "rationale_pass": "Turnaround is accurate — character holds across all rotation angles (${scorePct}%)",
67
+ "rationale_fail": "Turnaround breaks — proportions, costume, or features change between angles (${scorePct}%)"
68
+ },
69
+ {
70
+ "id": "EXP-001",
71
+ "category": "expression",
72
+ "desc": "Expression range — emotions read clearly and the face stays on-model",
73
+ "dims": ["expression_clarity"],
74
+ "lanes": ["expression_sheet"],
75
+ "rationale_pass": "Expressions read clearly — face stays on-model across emotions (${scorePct}%)",
76
+ "rationale_fail": "Expressions are muddy or off-model — emotions do not read clearly (${scorePct}%)"
77
+ },
78
+ {
79
+ "id": "ACT-001",
80
+ "category": "action",
81
+ "desc": "Action readability — dynamic poses have clear silhouette and readable motion",
82
+ "dims": ["gesture_clarity", "silhouette_read"],
83
+ "lanes": ["action_pose"],
84
+ "rationale_pass": "Action reads clearly — silhouette and motion are unambiguous (${scorePct}%)",
85
+ "rationale_fail": "Action is unreadable — tangled limbs, unclear silhouette, or static feel (${scorePct}%)"
86
+ }
87
+ ]
88
+ }
@@ -0,0 +1,35 @@
1
+ {
2
+ "default_lane": "full_body",
3
+ "lanes": [
4
+ {
5
+ "id": "portrait",
6
+ "label": "Portrait / Headshot",
7
+ "description": "Head and shoulders — face detail, expression, hair, accessories",
8
+ "id_patterns": ["^portrait_", "^head_", "^face_"]
9
+ },
10
+ {
11
+ "id": "full_body",
12
+ "label": "Full Body",
13
+ "description": "Full figure standing pose — proportions, costume, silhouette",
14
+ "id_patterns": ["^full_", "^body_", "^standing_"]
15
+ },
16
+ {
17
+ "id": "turnaround",
18
+ "label": "Turnaround / Orthographic",
19
+ "description": "Multi-angle orthographic views — front, side, back, three-quarter",
20
+ "id_patterns": ["^turn_", "^ortho_", "^rotation_"]
21
+ },
22
+ {
23
+ "id": "expression_sheet",
24
+ "label": "Expression Sheet",
25
+ "description": "Emotion range grid — consistent face across multiple expressions",
26
+ "id_patterns": ["^expr_", "^emotion_", "^face_sheet_"]
27
+ },
28
+ {
29
+ "id": "action_pose",
30
+ "label": "Action Pose",
31
+ "description": "Dynamic poses — combat, movement, gesture-driven character moments",
32
+ "id_patterns": ["^action_", "^pose_", "^dynamic_"]
33
+ }
34
+ ]
35
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "TEMPLATE",
3
+ "display_name": "TEMPLATE",
4
+ "domain": "character-design",
5
+ "version": "0.1.0",
6
+ "defaults": {
7
+ "checkpoint": "",
8
+ "loras": [],
9
+ "width": 1024,
10
+ "height": 1024,
11
+ "steps": 8,
12
+ "cfg": 2.0,
13
+ "sampler": "dpmpp_sde",
14
+ "scheduler": "karras",
15
+ "comfy_url": "http://127.0.0.1:8188"
16
+ }
17
+ }
@@ -0,0 +1,38 @@
1
+ {
2
+ "dimensions": [
3
+ "proportion_accuracy",
4
+ "gesture_clarity",
5
+ "costume_logic",
6
+ "personality_read",
7
+ "style_consistency",
8
+ "silhouette_read",
9
+ "turnaround_accuracy",
10
+ "expression_clarity"
11
+ ],
12
+ "thresholds": {
13
+ "approved": {
14
+ "pass": 0.7,
15
+ "partial": 0.5
16
+ },
17
+ "borderline": {
18
+ "pass": 0.7,
19
+ "partial": 0.5
20
+ },
21
+ "rejected": {
22
+ "fail_ceiling": 0.4,
23
+ "partial_ceiling": 0.6
24
+ }
25
+ },
26
+ "failure_to_rules": {
27
+ "broken_anatomy": ["PRO-001"],
28
+ "stiff_pose": ["GES-001"],
29
+ "generic_costume": ["COS-001"],
30
+ "unrecognizable": ["PER-001"],
31
+ "inconsistent_costume": ["COS-002"],
32
+ "style_drift": ["STY-001"],
33
+ "turnaround_mismatch": ["TUR-001"],
34
+ "flat_expressions": ["EXP-001"],
35
+ "unreadable_action": ["ACT-001"],
36
+ "proportion_drift": ["PRO-002"]
37
+ }
38
+ }