@coralai/sps-cli 0.65.74 → 0.65.76

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 (107) hide show
  1. package/dist/commands/artCommand.d.ts.map +1 -1
  2. package/dist/commands/artCommand.js +13 -3
  3. package/dist/commands/artCommand.js.map +1 -1
  4. package/dist/console/index.d.ts.map +1 -1
  5. package/dist/console/index.js +8 -0
  6. package/dist/console/index.js.map +1 -1
  7. package/dist/console/lib/artPaths.d.ts +29 -0
  8. package/dist/console/lib/artPaths.d.ts.map +1 -0
  9. package/dist/console/lib/artPaths.js +170 -0
  10. package/dist/console/lib/artPaths.js.map +1 -0
  11. package/dist/console/lib/artRunRequest.d.ts +46 -0
  12. package/dist/console/lib/artRunRequest.d.ts.map +1 -0
  13. package/dist/console/lib/artRunRequest.js +107 -0
  14. package/dist/console/lib/artRunRequest.js.map +1 -0
  15. package/dist/console/routes/art.d.ts +3 -0
  16. package/dist/console/routes/art.d.ts.map +1 -0
  17. package/dist/console/routes/art.js +355 -0
  18. package/dist/console/routes/art.js.map +1 -0
  19. package/dist/console/routes/artPipeline.d.ts +15 -0
  20. package/dist/console/routes/artPipeline.d.ts.map +1 -0
  21. package/dist/console/routes/artPipeline.js +83 -0
  22. package/dist/console/routes/artPipeline.js.map +1 -0
  23. package/dist/core/art/artConcurrency.d.ts +63 -0
  24. package/dist/core/art/artConcurrency.d.ts.map +1 -0
  25. package/dist/core/art/artConcurrency.js +63 -0
  26. package/dist/core/art/artConcurrency.js.map +1 -0
  27. package/dist/core/art/artRunLauncher.d.ts +6 -0
  28. package/dist/core/art/artRunLauncher.d.ts.map +1 -0
  29. package/dist/core/art/artRunLauncher.js +91 -0
  30. package/dist/core/art/artRunLauncher.js.map +1 -0
  31. package/dist/core/art/artRunService.d.ts +102 -0
  32. package/dist/core/art/artRunService.d.ts.map +1 -0
  33. package/dist/core/art/artRunService.js +181 -0
  34. package/dist/core/art/artRunService.js.map +1 -0
  35. package/dist/core/art/assetLocator.d.ts +33 -0
  36. package/dist/core/art/assetLocator.d.ts.map +1 -0
  37. package/dist/core/art/assetLocator.js +61 -0
  38. package/dist/core/art/assetLocator.js.map +1 -0
  39. package/dist/providers/art/artDriver.d.ts +71 -0
  40. package/dist/providers/art/artDriver.d.ts.map +1 -0
  41. package/dist/providers/art/artDriver.js +119 -0
  42. package/dist/providers/art/artDriver.js.map +1 -0
  43. package/dist/providers/art/artEvents.d.ts +23 -0
  44. package/dist/providers/art/artEvents.d.ts.map +1 -0
  45. package/dist/providers/art/artEvents.js +40 -0
  46. package/dist/providers/art/artEvents.js.map +1 -0
  47. package/dist/providers/art/artGen.d.ts +253 -0
  48. package/dist/providers/art/artGen.d.ts.map +1 -0
  49. package/dist/providers/art/artGen.js +1263 -0
  50. package/dist/providers/art/artGen.js.map +1 -0
  51. package/dist/providers/art/artPrompt.d.ts +42 -0
  52. package/dist/providers/art/artPrompt.d.ts.map +1 -0
  53. package/dist/providers/art/artPrompt.js +41 -0
  54. package/dist/providers/art/artPrompt.js.map +1 -0
  55. package/dist/providers/art/artRunChild.d.ts +3 -0
  56. package/dist/providers/art/artRunChild.d.ts.map +1 -0
  57. package/dist/providers/art/artRunChild.js +127 -0
  58. package/dist/providers/art/artRunChild.js.map +1 -0
  59. package/dist/providers/art/atomicWrite.d.ts +16 -0
  60. package/dist/providers/art/atomicWrite.d.ts.map +1 -0
  61. package/dist/providers/art/atomicWrite.js +62 -0
  62. package/dist/providers/art/atomicWrite.js.map +1 -0
  63. package/dist/providers/art/cancelMarker.d.ts +9 -0
  64. package/dist/providers/art/cancelMarker.d.ts.map +1 -0
  65. package/dist/providers/art/cancelMarker.js +60 -0
  66. package/dist/providers/art/cancelMarker.js.map +1 -0
  67. package/dist/providers/art/composeAsset.d.ts +52 -0
  68. package/dist/providers/art/composeAsset.d.ts.map +1 -0
  69. package/dist/providers/art/composeAsset.js +73 -0
  70. package/dist/providers/art/composeAsset.js.map +1 -0
  71. package/dist/providers/art/imageSize.d.ts +31 -0
  72. package/dist/providers/art/imageSize.d.ts.map +1 -0
  73. package/dist/providers/art/imageSize.js +92 -0
  74. package/dist/providers/art/imageSize.js.map +1 -0
  75. package/dist/providers/art/jobContext.d.ts +25 -0
  76. package/dist/providers/art/jobContext.d.ts.map +1 -0
  77. package/dist/providers/art/jobContext.js +5 -0
  78. package/dist/providers/art/jobContext.js.map +1 -0
  79. package/dist/providers/art/rateLimitRetry.d.ts +25 -0
  80. package/dist/providers/art/rateLimitRetry.d.ts.map +1 -0
  81. package/dist/providers/art/rateLimitRetry.js +40 -0
  82. package/dist/providers/art/rateLimitRetry.js.map +1 -0
  83. package/dist/providers/art/uiKit.d.ts +61 -0
  84. package/dist/providers/art/uiKit.d.ts.map +1 -0
  85. package/dist/providers/art/uiKit.js +331 -0
  86. package/dist/providers/art/uiKit.js.map +1 -0
  87. package/dist/providers/art/visionJudge.d.ts +26 -0
  88. package/dist/providers/art/visionJudge.d.ts.map +1 -0
  89. package/dist/providers/art/visionJudge.js +127 -0
  90. package/dist/providers/art/visionJudge.js.map +1 -0
  91. package/dist/providers/art/withinDir.d.ts +2 -0
  92. package/dist/providers/art/withinDir.d.ts.map +1 -0
  93. package/dist/providers/art/withinDir.js +19 -0
  94. package/dist/providers/art/withinDir.js.map +1 -0
  95. package/dist/providers/media/gatewayMediaProviders.d.ts +58 -0
  96. package/dist/providers/media/gatewayMediaProviders.d.ts.map +1 -0
  97. package/dist/providers/media/gatewayMediaProviders.js +46 -0
  98. package/dist/providers/media/gatewayMediaProviders.js.map +1 -0
  99. package/dist/providers/media/generateAsset.d.ts +9 -0
  100. package/dist/providers/media/generateAsset.d.ts.map +1 -1
  101. package/dist/providers/media/generateAsset.js +7 -1
  102. package/dist/providers/media/generateAsset.js.map +1 -1
  103. package/dist/providers/media/mediaProviders.d.ts +31 -1
  104. package/dist/providers/media/mediaProviders.d.ts.map +1 -1
  105. package/dist/providers/media/mediaProviders.js +33 -1
  106. package/dist/providers/media/mediaProviders.js.map +1 -1
  107. package/package.json +1 -1
@@ -0,0 +1,355 @@
1
+ /**
2
+ * @module console/routes/art
3
+ * @description `/api/art/*` —— 美术算子 HTTP 面(路径进路径出)。契约见 docs/design/art-http-surface.md。
4
+ * @layer console
5
+ *
6
+ * 🔴 **本文件是适配层,不是算法。** 算法在 `providers/art/{pixels,sheet,atlas,frames}.ts`,
7
+ * 由 coral-studio 整段移交,**按只读对待**。这里与 `commands/artCommand.ts` 是**并列的两个适配器**
8
+ * (CLI 面 / HTTP 面),共用同一批算子。
9
+ *
10
+ * 边界(约束来自 studio,见设计文档 §1):
11
+ * - **不认识"资源 id"**,只认路径;**不记计量**(算法调用不是模型调用);**无进程内状态**。
12
+ * - 台账(索引 / 缩略图 / DB / undo / 配额)全部留 studio。
13
+ * ⚠️ 判别式:哪天有人提「让 sps 记住上次结果好做 undo」,就是这条线被破的信号。
14
+ *
15
+ * 🔴 **`in`/`out` 一律当不透明路径**:不解析、不从中推断产品/租户/用户、不 mkdir。
16
+ * 资源库根目前叫 `game-platform`(废弃的旧产品名),改名是排着的事 ——
17
+ * 只要这里不 `split('/')` 去认产品名,改名那天**一行都不用动**。
18
+ *
19
+ * 成功响应 = CLI stdout 的 JSON(含 `ok:true`),**错误走 problem-json**(附录 A.5)——
20
+ * 这是唯一一处故意不一致:sps 的 HTTP 契约只有一种错误形状,为 art 开第二种代价更大。
21
+ *
22
+ * ─────────────────────────────────────────────────────────────────────────
23
+ * ✅ **欠账 #31 已结清(2026-08-11,搭 1.4 的车)** —— 这段留着,因为**判据比结论有用**。
24
+ *
25
+ * 曾经:本文件 + `console/lib/artPaths` 共 **19 处同步 fs**(`readFileSync`/`writeFileSync`
26
+ * + 围栏的 `existsSync`/`statSync`/`realpathSync`),**阻塞 console 的事件循环** ——
27
+ * 拖慢同进程的**每一个** HTTP 请求(含 `/api/health` 探针与 SSE 心跳),并发调用串行化。
28
+ * 现在:两个文件的同步 fs **计数为 0**(`grep -c 'Sync('`)。
29
+ *
30
+ * 🔴 **为什么移交时那么写不是缺陷**:算法原本只在 CLI 里跑,同步 IO 只阻塞它自己。
31
+ * **换了宿主(CLI → console 进程)之后性质才变的。**
32
+ * ⇒ 所以只改**这个适配层**;`providers/art/*` 那些只读算法件**一行没动**(它们在 CLI 里同步是对的)。
33
+ * ⚠️ 还剩一处在算法件里:`atlas.logicalSize(mp)` 内部同步读 manifest(`/inspect` 会走到)。
34
+ * **刻意不动** —— 动它就要动只读移交件,而它是一次几 KB 的读。记在这里,别当成漏了。
35
+ *
36
+ * ⚠️ 归因时态(值得留):这**从来不是"美术流水线迁移引入的风险"**,是 0.65.75 起就在生产上的性质。
37
+ * 写成迁移风险,它就会在排期时跟着迁移一起被延后 —— 而它跟迁移无关。
38
+ * (没有马上改的原因也留着:上次为发版重启生产打断了 **307 个在跑会话**,
39
+ * 几~几十 ms 的收益不值得单独发一版 ⇒ 决定搭下一次发版的车。**这次就是那次车。**)
40
+ * ─────────────────────────────────────────────────────────────────────────
41
+ */
42
+ import { readFile, writeFile } from 'node:fs/promises';
43
+ import { basename, dirname, join, resolve } from 'node:path';
44
+ import { Hono } from 'hono';
45
+ import { domainError, toHttpStatus, toProblemJson } from '../../shared/errors.js';
46
+ import { resolveInputPath, resolveManifestPath, resolveOutputDir, resolveOutputPath } from '../lib/artPaths.js';
47
+ function isDomainError(e) {
48
+ return typeof e === 'object' && e !== null && 'kind' in e && 'code' in e && 'message' in e;
49
+ }
50
+ const invalid = (code, msg, details) => domainError('validation', code, msg, details ? { details } : {});
51
+ function str(b, k) {
52
+ const v = b[k];
53
+ return typeof v === 'string' && v.trim() !== '' ? v.trim() : undefined;
54
+ }
55
+ function reqStr(b, k) {
56
+ const v = str(b, k);
57
+ if (!v)
58
+ throw invalid('FIELD_REQUIRED', `缺必填字段 ${k}`, { field: k });
59
+ return v;
60
+ }
61
+ function num(b, k) {
62
+ const v = b[k];
63
+ if (v === undefined || v === null)
64
+ return undefined;
65
+ const n = typeof v === 'number' ? v : Number(v);
66
+ if (!Number.isFinite(n))
67
+ throw invalid('FIELD_INVALID', `${k} 不是数字:${String(v)}`, { field: k });
68
+ return n;
69
+ }
70
+ function reqNum(b, k) {
71
+ const n = num(b, k);
72
+ if (n === undefined)
73
+ throw invalid('FIELD_REQUIRED', `缺必填字段 ${k}`, { field: k });
74
+ return n;
75
+ }
76
+ function bool(b, k) {
77
+ return b[k] === true || b[k] === 'true';
78
+ }
79
+ /** `out` 省略 = 原地覆盖 `in`(同 CLI)。**但仍要过写围栏** —— 原地覆盖也是写。 */
80
+ async function outOf(b, inPath) {
81
+ const raw = str(b, 'out');
82
+ return await resolveOutputPath(raw ?? inPath, 'out');
83
+ }
84
+ /** 统一的 body 解析 + 错误翻译。算子异常按"输入的错 vs 我们的错"分流。 */
85
+ async function run(c, fn) {
86
+ let body;
87
+ try {
88
+ body = (await c.req.json());
89
+ if (typeof body !== 'object' || body === null)
90
+ throw new Error('not an object');
91
+ }
92
+ catch {
93
+ return c.json(toProblemJson(invalid('BODY_INVALID', '请求体必须是 JSON 对象')), 422);
94
+ }
95
+ try {
96
+ return c.json({ ok: true, ...(await fn(body)) });
97
+ }
98
+ catch (e) {
99
+ if (isDomainError(e))
100
+ return c.json(toProblemJson(e), toHttpStatus(e));
101
+ const msg = e instanceof Error ? e.message : String(e);
102
+ // 🔴 EACCES 是 422 不是 500:可操作的修法在调用方那边(换一个共享树内的路径)。
103
+ // 回 500 会被读成「sps 坏了」—— 那正是我们要防的误归因。
104
+ const errno = e.code;
105
+ if (errno === 'EACCES' || errno === 'EPERM' || errno === 'EROFS') {
106
+ const de = domainError('validation', 'PATH_NOT_ACCESSIBLE', `路径不可写:${msg}`, {
107
+ details: { errno, syscall: e.syscall, path: e.path },
108
+ });
109
+ return c.json(toProblemJson(de), 422);
110
+ }
111
+ // rembg 是独立 HTTP 服务 → 它不通是 external(502),不是我们内部错。
112
+ if (/rembg/i.test(msg)) {
113
+ const de = domainError('external', 'REMBG_UNAVAILABLE', msg);
114
+ return c.json(toProblemJson(de), 502);
115
+ }
116
+ return c.json(toProblemJson(domainError('internal', 'ART_FAILED', msg)), 500);
117
+ }
118
+ }
119
+ export function createArtRoute() {
120
+ const app = new Hono();
121
+ app.post('/chroma-key', (c) => run(c, async (b) => {
122
+ const { chromaKey } = await import('../../providers/art/pixels.js');
123
+ const inPath = await resolveInputPath(reqStr(b, 'in'));
124
+ const out = await outOf(b, inPath);
125
+ const opts = {};
126
+ const t = num(b, 'tolerance');
127
+ const f = num(b, 'feather');
128
+ if (t !== undefined)
129
+ opts.tolerance = t;
130
+ if (f !== undefined)
131
+ opts.feather = f;
132
+ const buf = await chromaKey(await readFile(inPath), opts);
133
+ await writeFile(out, buf);
134
+ return { out, bytes: buf.length };
135
+ }));
136
+ app.post('/rembg', (c) => run(c, async (b) => {
137
+ const { rembgRemove } = await import('../../providers/art/pixels.js');
138
+ const inPath = await resolveInputPath(reqStr(b, 'in'));
139
+ const out = await outOf(b, inPath);
140
+ const buf = await rembgRemove(await readFile(inPath));
141
+ await writeFile(out, buf);
142
+ return { out, bytes: buf.length };
143
+ }));
144
+ app.post('/clean-alpha', (c) => run(c, async (b) => {
145
+ const { cleanAlpha } = await import('../../providers/art/pixels.js');
146
+ const inPath = await resolveInputPath(reqStr(b, 'in'));
147
+ const out = await outOf(b, inPath);
148
+ const buf = await cleanAlpha(await readFile(inPath), num(b, 'lo') ?? 30, num(b, 'hi') ?? 200);
149
+ await writeFile(out, buf);
150
+ return { out, bytes: buf.length };
151
+ }));
152
+ /** 只读判据:这张图值不值得交给模型(不是"抠得干不干净")。 */
153
+ app.post('/residue', (c) => run(c, async (b) => {
154
+ const { magentaResidue, alreadyTransparent } = await import('../../providers/art/pixels.js');
155
+ const buf = await readFile(await resolveInputPath(reqStr(b, 'in')));
156
+ const [residue, transparent] = await Promise.all([magentaResidue(buf), alreadyTransparent(buf)]);
157
+ // needsRembg 直接给结论,省得调用方自己比阈值比错。
158
+ return { residue, alreadyTransparent: transparent, threshold: 0.005, needsRembg: !transparent && residue > 0.005 };
159
+ }));
160
+ app.post('/inspect', (c) => run(c, async (b) => {
161
+ const sharp = (await import('sharp')).default;
162
+ const { alreadyTransparent } = await import('../../providers/art/pixels.js');
163
+ const { logicalSize } = await import('../../providers/art/atlas.js');
164
+ const buf = await readFile(await resolveInputPath(reqStr(b, 'in')));
165
+ const meta = await sharp(buf).metadata();
166
+ const width = meta.width ?? 0;
167
+ const height = meta.height ?? 0;
168
+ const out = {
169
+ width,
170
+ height,
171
+ channels: meta.channels ?? 0,
172
+ hasAlpha: meta.hasAlpha ?? false,
173
+ alreadyTransparent: await alreadyTransparent(buf),
174
+ };
175
+ // 🔴 「合法缺席」vs「故障」要分开(studio 修正的那条):
176
+ // 路径②(资源库)的图**压根不属于任何一局游戏**,没有 manifest 也不该有 ——
177
+ // 按"缺就抛"会让 inspect 在那条路上**永远抛**。
178
+ // 所以:不给 manifest = 合法缺席 → 占屏比**字段整个不出现**;
179
+ // 给了但读不到/缺字段 = 故障 → 抛。
180
+ // ⚠️ 不返回 null 占位:「字段不存在」调用方拿不到会自己想办法,
181
+ // 「字段是 null」会被 `?? 0` / `< 50` 这类判断悄悄吃掉。
182
+ // **不能提供的答案,不要给一个形状正确的占位。**
183
+ const manifest = str(b, 'manifest');
184
+ if (manifest) {
185
+ const mp = await resolveManifestPath(manifest);
186
+ let logical;
187
+ try {
188
+ logical = logicalSize(mp);
189
+ }
190
+ catch (e) {
191
+ // 🔴 **不透传原始异常消息**:JSON.parse 失败的消息会带文件原文片段。
192
+ // manifest 是唯一按非图片解析的入参,这是它单独需要处理的原因。
193
+ throw invalid('MANIFEST_INVALID', `manifest 无法解析或缺 width/height:${mp}`, {
194
+ field: 'manifest',
195
+ path: mp,
196
+ reason: e instanceof Error && /缺 width\/height/.test(e.message) ? 'missing-fields' : 'unreadable',
197
+ });
198
+ }
199
+ out.logicalWidth = logical.width;
200
+ out.logicalHeight = logical.height;
201
+ out.widthPctOfScreen = logical.width ? +((width / logical.width) * 100).toFixed(1) : null;
202
+ out.heightPctOfScreen = logical.height ? +((height / logical.height) * 100).toFixed(1) : null;
203
+ }
204
+ return out;
205
+ }));
206
+ app.post('/slice-sheet', (c) => run(c, async (b) => {
207
+ const { sliceSheet } = await import('../../providers/art/sheet.js');
208
+ const inPath = await resolveInputPath(reqStr(b, 'in'));
209
+ const cols = reqNum(b, 'cols');
210
+ const rows = reqNum(b, 'rows');
211
+ const r = await sliceSheet(await readFile(inPath), cols, rows, { grid: bool(b, 'grid') });
212
+ const outDirRaw = str(b, 'outDir');
213
+ const stem = str(b, 'name') ?? 'frame';
214
+ const outDir = outDirRaw ? await resolveOutputDir(outDirRaw) : undefined;
215
+ // 🔴 清单:**次序是数据,不是排序约定**。`frame_10.png` 字典序排在 `frame_2.png` 前面 ——
216
+ // 调用方靠文件名排 = 帧序乱、而每张图都对(最难看出的那种错)。
217
+ // 🔴 **空格占一个 index 并出现在清单里**(w/h 为 0 + empty:true)。跳过 = 其后所有帧索引前移,
218
+ // 而游戏按 `<id>_0/_1` 引用 —— 一个空格错位整套动画。
219
+ // ⚠️ 这条在 packSheet 内部保住过一次,**清单是新面,同一个坑要再堵一次**:
220
+ // 移交时保住的不变量不会自动跟到 HTTP 面上。
221
+ // Promise.all 保序 —— 见上:**次序是数据**,不是排序约定。
222
+ const items = await Promise.all(r.frames.map(async (buf, i) => {
223
+ const size = r.sizes[i] ?? { w: 0, h: 0 };
224
+ const file = `${stem}_${i}.png`;
225
+ if (outDir)
226
+ await writeFile(join(outDir, file), buf);
227
+ return {
228
+ index: i,
229
+ ...(outDir ? { file } : {}),
230
+ w: size.w,
231
+ h: size.h,
232
+ ...(size.w === 0 && size.h === 0 ? { empty: true } : {}),
233
+ };
234
+ }));
235
+ // ⚠️ `detected:true`(内容感知切)时 index 是**连通域顺序**,不是网格格位:
236
+ // 空区域根本不产生 cell,所以那条路上空格是**缺席**而非占位,frames 可能 ≠ cols×rows。
237
+ // 这正是 frames vs expectedFrames 要给出来的原因 —— 它是"要不要 grid:true 重来"的信号。
238
+ return {
239
+ frames: r.frames.length,
240
+ expectedFrames: cols * rows,
241
+ detected: r.detected,
242
+ sizes: r.sizes,
243
+ items,
244
+ ...(outDir ? { outDir } : {}),
245
+ };
246
+ }));
247
+ app.post('/pack-atlas', (c) => run(c, async (b) => {
248
+ const { packSheet } = await import('../../providers/art/sheet.js');
249
+ const { writeAtlasJson } = await import('../../providers/art/atlas.js');
250
+ const inPath = await resolveInputPath(reqStr(b, 'in'));
251
+ const cols = reqNum(b, 'cols');
252
+ const rows = reqNum(b, 'rows');
253
+ const opts = { grid: bool(b, 'grid') };
254
+ // 🔴 限制帧高**只能**走这里(切分后逐帧缩)。事后 resize 整张图集会把
255
+ // 「单帧上限」误当整图上限 —— 8 帧图集每帧变 1/8,而一切看起来都自洽。
256
+ const fmh = num(b, 'frameMaxHeight');
257
+ if (fmh !== undefined)
258
+ opts.frameMaxHeight = fmh;
259
+ const packed = await packSheet(await readFile(inPath), cols, rows, opts);
260
+ const out = await outOf(b, inPath);
261
+ const id = str(b, 'id');
262
+ const dir = id ? await resolveOutputDir(str(b, 'assetsDir') ?? dirname(out), 'assetsDir') : undefined;
263
+ // 🔴 与 CLI 面同一道断言(cbc3454):atlas 的 `meta.image` 由 `id` 推出(`${id}.png`),
264
+ // 而图落在 `out`。不自洽时 atlas 结构合法、坐标自洽,只有引擎加载时报「找不到图」。
265
+ // **先断言再落盘** —— 宁可什么都不写,也不写出一个指向空气的 atlas。
266
+ if (id && dir && resolve(dir, `${id}.png`) !== out) {
267
+ throw invalid('ATLAS_ID_OUT_MISMATCH', `out 与 id 不自洽:图落在 ${out},而 atlas 的 meta.image 会写 ${resolve(dir, `${id}.png`)}`, {
268
+ out,
269
+ expectedImage: resolve(dir, `${id}.png`),
270
+ });
271
+ }
272
+ await writeFile(out, packed.buf);
273
+ let atlas;
274
+ if (id && dir) {
275
+ writeAtlasJson(dir, id, packed);
276
+ atlas = resolve(dir, `${id}.json`);
277
+ }
278
+ return {
279
+ out,
280
+ width: packed.width,
281
+ height: packed.height,
282
+ frames: packed.frames.length,
283
+ expectedFrames: cols * rows,
284
+ detected: packed.detected,
285
+ ...(atlas ? { atlas } : {}),
286
+ };
287
+ }));
288
+ // 🔴 两种 normalize 拆成两个端点(CLI 是 `--mode`):
289
+ // sheet 收单个 `in`,frames 收 `in: string[]` —— 同名字段两种类型正是我们刚在
290
+ // `pack-atlas` 的 `id` 上踩过的那类坑,分开就不存在。
291
+ app.post('/normalize-sheet', (c) => run(c, async (b) => {
292
+ const { normalizeSheet } = await import('../../providers/art/sheet.js');
293
+ const inPath = await resolveInputPath(reqStr(b, 'in'));
294
+ const out = await outOf(b, inPath);
295
+ const r = await normalizeSheet(await readFile(inPath), reqNum(b, 'cols'), reqNum(b, 'rows'));
296
+ await writeFile(out, r.buf);
297
+ return {
298
+ out,
299
+ mode: 'sheet',
300
+ frames: r.frames,
301
+ cols: r.cols,
302
+ rows: r.rows,
303
+ cellWidth: r.cellWidth,
304
+ cellHeight: r.cellHeight,
305
+ detected: r.detected,
306
+ };
307
+ }));
308
+ app.post('/normalize-frames', (c) => run(c, async (b) => {
309
+ const { normalizeFrames } = await import('../../providers/art/frames.js');
310
+ const raw = b.in;
311
+ if (!Array.isArray(raw) || raw.length === 0) {
312
+ throw invalid('FIELD_REQUIRED', 'in 必须是非空的绝对路径数组', { field: 'in' });
313
+ }
314
+ const inputs = await Promise.all(raw.map((p, i) => resolveInputPath(String(p), `in[${i}]`)));
315
+ const opts = {};
316
+ const tw = num(b, 'targetWidth');
317
+ const th = num(b, 'targetHeight');
318
+ const pd = num(b, 'pad');
319
+ if (tw !== undefined)
320
+ opts.targetWidth = tw;
321
+ if (th !== undefined)
322
+ opts.targetHeight = th;
323
+ if (pd !== undefined)
324
+ opts.pad = pd;
325
+ const outDirRaw = str(b, 'outDir');
326
+ const outDir = outDirRaw ? await resolveOutputDir(outDirRaw) : undefined;
327
+ // outDir 省略 = 原地覆盖各输入 —— 逐个过写围栏(资源库可读不可写,原地覆盖会在那里被挡住,
328
+ // 这正是我们要的:studio 该把结果写进共享树再自己搬回资源库)。
329
+ const targets = await Promise.all(inputs.map((p, i) => outDir ? Promise.resolve(join(outDir, `frame_${i}.png`)) : resolveOutputPath(p, `in[${i}]`)));
330
+ const r = await normalizeFrames(await Promise.all(inputs.map((p) => readFile(p))), opts);
331
+ // 逐帧目标先按 index 定好再并发写 —— written 的**次序是数据**(调用方按它对帧)。
332
+ const written = r.frames.map((_, i) => targets[i] ?? join(outDir ?? dirname(inputs[0]), `frame_${i}.png`));
333
+ await Promise.all(r.frames.map((buf, i) => writeFile(written[i], buf)));
334
+ return {
335
+ mode: 'frames',
336
+ width: r.width,
337
+ height: r.height,
338
+ frames: r.frames.length,
339
+ written,
340
+ items: written.map((p, i) => ({ index: i, file: basename(p) })),
341
+ };
342
+ }));
343
+ // 🔴 不适用于图集!图集必须走 pack-atlas 的 frameMaxHeight(见上)。
344
+ app.post('/resize', (c) => run(c, async (b) => {
345
+ const { resizeToFit } = await import('../../providers/art/frames.js');
346
+ const inPath = await resolveInputPath(reqStr(b, 'in'));
347
+ const out = await outOf(b, inPath);
348
+ const r = await resizeToFit(await readFile(inPath), reqNum(b, 'maxWidth'), reqNum(b, 'maxHeight'));
349
+ if (r.changed)
350
+ await writeFile(out, r.buf);
351
+ return { out, changed: r.changed, width: r.width, height: r.height };
352
+ }));
353
+ return app;
354
+ }
355
+ //# sourceMappingURL=art.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"art.js","sourceRoot":"","sources":["../../../src/console/routes/art.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7D,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,aAAa,EAAoB,MAAM,wBAAwB,CAAC;AACpG,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAIhH,SAAS,aAAa,CAAC,CAAU;IAC/B,OAAO,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,IAAI,IAAI,MAAM,IAAI,CAAC,IAAI,MAAM,IAAI,CAAC,IAAI,SAAS,IAAI,CAAC,CAAC;AAC7F,CAAC;AAED,MAAM,OAAO,GAAG,CAAC,IAAY,EAAE,GAAW,EAAE,OAAiC,EAAe,EAAE,CAC5F,WAAW,CAAC,YAAY,EAAE,IAAI,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;AAEnE,SAAS,GAAG,CAAC,CAAO,EAAE,CAAS;IAC7B,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACf,OAAO,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;AACzE,CAAC;AACD,SAAS,MAAM,CAAC,CAAO,EAAE,CAAS;IAChC,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACpB,IAAI,CAAC,CAAC;QAAE,MAAM,OAAO,CAAC,gBAAgB,EAAE,SAAS,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;IACpE,OAAO,CAAC,CAAC;AACX,CAAC;AACD,SAAS,GAAG,CAAC,CAAO,EAAE,CAAS;IAC7B,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACf,IAAI,CAAC,KAAK,SAAS,IAAI,CAAC,KAAK,IAAI;QAAE,OAAO,SAAS,CAAC;IACpD,MAAM,CAAC,GAAG,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IAChD,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;QAAE,MAAM,OAAO,CAAC,eAAe,EAAE,GAAG,CAAC,SAAS,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;IAChG,OAAO,CAAC,CAAC;AACX,CAAC;AACD,SAAS,MAAM,CAAC,CAAO,EAAE,CAAS;IAChC,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACpB,IAAI,CAAC,KAAK,SAAS;QAAE,MAAM,OAAO,CAAC,gBAAgB,EAAE,SAAS,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;IACjF,OAAO,CAAC,CAAC;AACX,CAAC;AACD,SAAS,IAAI,CAAC,CAAO,EAAE,CAAS;IAC9B,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC;AAC1C,CAAC;AAED,0DAA0D;AAC1D,KAAK,UAAU,KAAK,CAAC,CAAO,EAAE,MAAc;IAC1C,MAAM,GAAG,GAAG,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;IAC1B,OAAO,MAAM,iBAAiB,CAAC,GAAG,IAAI,MAAM,EAAE,KAAK,CAAC,CAAC;AACvD,CAAC;AAED,gDAAgD;AAChD,KAAK,UAAU,GAAG,CAAC,CAAU,EAAE,EAAiC;IAC9D,IAAI,IAAU,CAAC;IACf,IAAI,CAAC;QACH,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,CAAS,CAAC;QACpC,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,IAAI;YAAE,MAAM,IAAI,KAAK,CAAC,eAAe,CAAC,CAAC;IAClF,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,cAAc,EAAE,gBAAgB,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAC/E,CAAC;IACD,IAAI,CAAC;QACH,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,CAA4B,EAAE,CAAC,CAAC;IAC9E,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,IAAI,aAAa,CAAC,CAAC,CAAC;YAAE,OAAO,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC,CAAQ,CAAC,CAAC;QAC9E,MAAM,GAAG,GAAG,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QACvD,mDAAmD;QACnD,uCAAuC;QACvC,MAAM,KAAK,GAAI,CAA2B,CAAC,IAAI,CAAC;QAChD,IAAI,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,OAAO,IAAI,KAAK,KAAK,OAAO,EAAE,CAAC;YACjE,MAAM,EAAE,GAAG,WAAW,CAAC,YAAY,EAAE,qBAAqB,EAAE,SAAS,GAAG,EAAE,EAAE;gBAC1E,OAAO,EAAE,EAAE,KAAK,EAAE,OAAO,EAAG,CAA2B,CAAC,OAAO,EAAE,IAAI,EAAG,CAA2B,CAAC,IAAI,EAAE;aAC3G,CAAC,CAAC;YACH,OAAO,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC;QACxC,CAAC;QACD,kDAAkD;QAClD,IAAI,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YACvB,MAAM,EAAE,GAAG,WAAW,CAAC,UAAU,EAAE,mBAAmB,EAAE,GAAG,CAAC,CAAC;YAC7D,OAAO,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC;QACxC,CAAC;QACD,OAAO,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,WAAW,CAAC,UAAU,EAAE,YAAY,EAAE,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAChF,CAAC;AACH,CAAC;AAED,MAAM,UAAU,cAAc;IAC5B,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;IAEvB,GAAG,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC,CAAC,EAAE,EAAE,CAC5B,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QACjB,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,MAAM,CAAC,+BAA+B,CAAC,CAAC;QACpE,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;QACvD,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;QACnC,MAAM,IAAI,GAA6C,EAAE,CAAC;QAC1D,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC;QAC9B,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;QAC5B,IAAI,CAAC,KAAK,SAAS;YAAE,IAAI,CAAC,SAAS,GAAG,CAAC,CAAC;QACxC,IAAI,CAAC,KAAK,SAAS;YAAE,IAAI,CAAC,OAAO,GAAG,CAAC,CAAC;QACtC,MAAM,GAAG,GAAG,MAAM,SAAS,CAAC,MAAM,QAAQ,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC;QAC1D,MAAM,SAAS,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QAC1B,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC;IACpC,CAAC,CAAC,CACH,CAAC;IAEF,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CACvB,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QACjB,MAAM,EAAE,WAAW,EAAE,GAAG,MAAM,MAAM,CAAC,+BAA+B,CAAC,CAAC;QACtE,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;QACvD,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;QACnC,MAAM,GAAG,GAAG,MAAM,WAAW,CAAC,MAAM,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;QACtD,MAAM,SAAS,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QAC1B,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC;IACpC,CAAC,CAAC,CACH,CAAC;IAEF,GAAG,CAAC,IAAI,CAAC,cAAc,EAAE,CAAC,CAAC,EAAE,EAAE,CAC7B,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QACjB,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC,+BAA+B,CAAC,CAAC;QACrE,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;QACvD,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;QACnC,MAAM,GAAG,GAAG,MAAM,UAAU,CAAC,MAAM,QAAQ,CAAC,MAAM,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,EAAE,EAAE,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC;QAC9F,MAAM,SAAS,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QAC1B,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC;IACpC,CAAC,CAAC,CACH,CAAC;IAEF,oCAAoC;IACpC,GAAG,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,EAAE,EAAE,CACzB,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QACjB,MAAM,EAAE,cAAc,EAAE,kBAAkB,EAAE,GAAG,MAAM,MAAM,CAAC,+BAA+B,CAAC,CAAC;QAC7F,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,MAAM,gBAAgB,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;QACpE,MAAM,CAAC,OAAO,EAAE,WAAW,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,cAAc,CAAC,GAAG,CAAC,EAAE,kBAAkB,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACjG,iCAAiC;QACjC,OAAO,EAAE,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,SAAS,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,WAAW,IAAI,OAAO,GAAG,KAAK,EAAE,CAAC;IACrH,CAAC,CAAC,CACH,CAAC;IAEF,GAAG,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,EAAE,EAAE,CACzB,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QACjB,MAAM,KAAK,GAAG,CAAC,MAAM,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC;QAC9C,MAAM,EAAE,kBAAkB,EAAE,GAAG,MAAM,MAAM,CAAC,+BAA+B,CAAC,CAAC;QAC7E,MAAM,EAAE,WAAW,EAAE,GAAG,MAAM,MAAM,CAAC,8BAA8B,CAAC,CAAC;QACrE,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,MAAM,gBAAgB,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;QACpE,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,CAAC;QACzC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,CAAC,CAAC;QAC9B,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC;QAChC,MAAM,GAAG,GAA4B;YACnC,KAAK;YACL,MAAM;YACN,QAAQ,EAAE,IAAI,CAAC,QAAQ,IAAI,CAAC;YAC5B,QAAQ,EAAE,IAAI,CAAC,QAAQ,IAAI,KAAK;YAChC,kBAAkB,EAAE,MAAM,kBAAkB,CAAC,GAAG,CAAC;SAClD,CAAC;QACF,oCAAoC;QACpC,mDAAmD;QACnD,oCAAoC;QACpC,6CAA6C;QAC7C,gCAAgC;QAChC,yCAAyC;QACzC,+CAA+C;QAC/C,kCAAkC;QAClC,MAAM,QAAQ,GAAG,GAAG,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC;QACpC,IAAI,QAAQ,EAAE,CAAC;YACb,MAAM,EAAE,GAAG,MAAM,mBAAmB,CAAC,QAAQ,CAAC,CAAC;YAC/C,IAAI,OAA0C,CAAC;YAC/C,IAAI,CAAC;gBACH,OAAO,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;YAC5B,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,6CAA6C;gBAC7C,yCAAyC;gBACzC,MAAM,OAAO,CAAC,kBAAkB,EAAE,gCAAgC,EAAE,EAAE,EAAE;oBACtE,KAAK,EAAE,UAAU;oBACjB,IAAI,EAAE,EAAE;oBACR,MAAM,EAAE,CAAC,YAAY,KAAK,IAAI,iBAAiB,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,YAAY;iBAClG,CAAC,CAAC;YACL,CAAC;YACD,GAAG,CAAC,YAAY,GAAG,OAAO,CAAC,KAAK,CAAC;YACjC,GAAG,CAAC,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC;YACnC,GAAG,CAAC,gBAAgB,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;YAC1F,GAAG,CAAC,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QAChG,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC,CAAC,CACH,CAAC;IAEF,GAAG,CAAC,IAAI,CAAC,cAAc,EAAE,CAAC,CAAC,EAAE,EAAE,CAC7B,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QACjB,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC,8BAA8B,CAAC,CAAC;QACpE,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;QACvD,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;QAC/B,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;QAC/B,MAAM,CAAC,GAAG,MAAM,UAAU,CAAC,MAAM,QAAQ,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;QAC1F,MAAM,SAAS,GAAG,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;QACnC,MAAM,IAAI,GAAG,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,IAAI,OAAO,CAAC;QACvC,MAAM,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC,MAAM,gBAAgB,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAEzE,kEAAkE;QAClE,sCAAsC;QACtC,mEAAmE;QACnE,wCAAwC;QACxC,mDAAmD;QACnD,iCAAiC;QACjC,yCAAyC;QACzC,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,EAAE,EAAE;YAC5D,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC;YAC1C,MAAM,IAAI,GAAG,GAAG,IAAI,IAAI,CAAC,MAAM,CAAC;YAChC,IAAI,MAAM;gBAAE,MAAM,SAAS,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC;YACrD,OAAO;gBACL,KAAK,EAAE,CAAC;gBACR,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC3B,CAAC,EAAE,IAAI,CAAC,CAAC;gBACT,CAAC,EAAE,IAAI,CAAC,CAAC;gBACT,GAAG,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACzD,CAAC;QACJ,CAAC,CAAC,CAAC,CAAC;QAEJ,sDAAsD;QACtD,8DAA8D;QAC9D,sEAAsE;QACtE,OAAO;YACL,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM;YACvB,cAAc,EAAE,IAAI,GAAG,IAAI;YAC3B,QAAQ,EAAE,CAAC,CAAC,QAAQ;YACpB,KAAK,EAAE,CAAC,CAAC,KAAK;YACd,KAAK;YACL,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC9B,CAAC;IACJ,CAAC,CAAC,CACH,CAAC;IAEF,GAAG,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC,CAAC,EAAE,EAAE,CAC5B,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QACjB,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,MAAM,CAAC,8BAA8B,CAAC,CAAC;QACnE,MAAM,EAAE,cAAc,EAAE,GAAG,MAAM,MAAM,CAAC,8BAA8B,CAAC,CAAC;QACxE,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;QACvD,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;QAC/B,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;QAC/B,MAAM,IAAI,GAAgD,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,EAAE,MAAM,CAAC,EAAE,CAAC;QACpF,4CAA4C;QAC5C,6CAA6C;QAC7C,MAAM,GAAG,GAAG,GAAG,CAAC,CAAC,EAAE,gBAAgB,CAAC,CAAC;QACrC,IAAI,GAAG,KAAK,SAAS;YAAE,IAAI,CAAC,cAAc,GAAG,GAAG,CAAC;QAEjD,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,MAAM,QAAQ,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;QACzE,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;QACnC,MAAM,EAAE,GAAG,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;QACxB,MAAM,GAAG,GAAG,EAAE,CAAC,CAAC,CAAC,MAAM,gBAAgB,CAAC,GAAG,CAAC,CAAC,EAAE,WAAW,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACtG,wEAAwE;QACxE,qDAAqD;QACrD,8CAA8C;QAC9C,IAAI,EAAE,IAAI,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,CAAC,KAAK,GAAG,EAAE,CAAC;YACnD,MAAM,OAAO,CAAC,uBAAuB,EAAE,oBAAoB,GAAG,4BAA4B,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,CAAC,EAAE,EAAE;gBACrH,GAAG;gBACH,aAAa,EAAE,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,CAAC;aACzC,CAAC,CAAC;QACL,CAAC;QACD,MAAM,SAAS,CAAC,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;QACjC,IAAI,KAAyB,CAAC;QAC9B,IAAI,EAAE,IAAI,GAAG,EAAE,CAAC;YACd,cAAc,CAAC,GAAG,EAAE,EAAE,EAAE,MAAM,CAAC,CAAC;YAChC,KAAK,GAAG,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC;QACrC,CAAC;QACD,OAAO;YACL,GAAG;YACH,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM;YAC5B,cAAc,EAAE,IAAI,GAAG,IAAI;YAC3B,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC5B,CAAC;IACJ,CAAC,CAAC,CACH,CAAC;IAEF,0CAA0C;IAC1C,8DAA8D;IAC9D,yCAAyC;IACzC,GAAG,CAAC,IAAI,CAAC,kBAAkB,EAAE,CAAC,CAAC,EAAE,EAAE,CACjC,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QACjB,MAAM,EAAE,cAAc,EAAE,GAAG,MAAM,MAAM,CAAC,8BAA8B,CAAC,CAAC;QACxE,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;QACvD,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;QACnC,MAAM,CAAC,GAAG,MAAM,cAAc,CAAC,MAAM,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;QAC7F,MAAM,SAAS,CAAC,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC;QAC5B,OAAO;YACL,GAAG;YACH,IAAI,EAAE,OAAO;YACb,MAAM,EAAE,CAAC,CAAC,MAAM;YAChB,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,SAAS,EAAE,CAAC,CAAC,SAAS;YACtB,UAAU,EAAE,CAAC,CAAC,UAAU;YACxB,QAAQ,EAAE,CAAC,CAAC,QAAQ;SACrB,CAAC;IACJ,CAAC,CAAC,CACH,CAAC;IAEF,GAAG,CAAC,IAAI,CAAC,mBAAmB,EAAE,CAAC,CAAC,EAAE,EAAE,CAClC,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QACjB,MAAM,EAAE,eAAe,EAAE,GAAG,MAAM,MAAM,CAAC,+BAA+B,CAAC,CAAC;QAC1E,MAAM,GAAG,GAAG,CAAC,CAAC,EAAE,CAAC;QACjB,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC5C,MAAM,OAAO,CAAC,gBAAgB,EAAE,iBAAiB,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACtE,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QAC7F,MAAM,IAAI,GAAkE,EAAE,CAAC;QAC/E,MAAM,EAAE,GAAG,GAAG,CAAC,CAAC,EAAE,aAAa,CAAC,CAAC;QACjC,MAAM,EAAE,GAAG,GAAG,CAAC,CAAC,EAAE,cAAc,CAAC,CAAC;QAClC,MAAM,EAAE,GAAG,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;QACzB,IAAI,EAAE,KAAK,SAAS;YAAE,IAAI,CAAC,WAAW,GAAG,EAAE,CAAC;QAC5C,IAAI,EAAE,KAAK,SAAS;YAAE,IAAI,CAAC,YAAY,GAAG,EAAE,CAAC;QAC7C,IAAI,EAAE,KAAK,SAAS;YAAE,IAAI,CAAC,GAAG,GAAG,EAAE,CAAC;QAEpC,MAAM,SAAS,GAAG,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;QACnC,MAAM,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC,MAAM,gBAAgB,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACzE,sDAAsD;QACtD,qCAAqC;QACrC,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CACpD,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,EAAE,MAAM,CAAC,GAAG,CAAC,CAC5F,CAAC,CAAC;QAEH,MAAM,CAAC,GAAG,MAAM,eAAe,CAAC,MAAM,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;QACzF,sDAAsD;QACtD,MAAM,OAAO,GAAG,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC,CAAW,CAAC,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;QACrH,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;QACxE,OAAO;YACL,IAAI,EAAE,QAAQ;YACd,KAAK,EAAE,CAAC,CAAC,KAAK;YACd,MAAM,EAAE,CAAC,CAAC,MAAM;YAChB,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM;YACvB,OAAO;YACP,KAAK,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;SAChE,CAAC;IACJ,CAAC,CAAC,CACH,CAAC;IAEF,mDAAmD;IACnD,GAAG,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC,EAAE,EAAE,CACxB,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QACjB,MAAM,EAAE,WAAW,EAAE,GAAG,MAAM,MAAM,CAAC,+BAA+B,CAAC,CAAC;QACtE,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;QACvD,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;QACnC,MAAM,CAAC,GAAG,MAAM,WAAW,CAAC,MAAM,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC,EAAE,UAAU,CAAC,EAAE,MAAM,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC;QACnG,IAAI,CAAC,CAAC,OAAO;YAAE,MAAM,SAAS,CAAC,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC;QAC3C,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC;IACvE,CAAC,CAAC,CACH,CAAC;IAEF,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * @module console/routes/artPipeline
3
+ * @description `/api/art/pipeline/runs` —— 美术流水线的 run 面(1.7 契约 + 1.8 并发闸)。
4
+ * @layer console
5
+ *
6
+ * 🔴 **这一层不干活**:校验 → 交给 `ArtRunService`(它把活丢进子进程)→ 立刻 202。
7
+ * 路由里**一次同步 fs 都没有**,也不等图跑完 —— 事件循环是所有请求共用的。
8
+ *
9
+ * 三条来自 1.7、写死在这里不许被"顺手优化"的:
10
+ * `verify` 缺省显式 false · `jobId` 禁 `:` · **拒收一切路径字段** —— 见 `artRunRequest`。
11
+ */
12
+ import { Hono } from 'hono';
13
+ import type { ArtRunService } from '../../core/art/artRunService.js';
14
+ export declare function createArtPipelineRoute(runs: ArtRunService): Hono;
15
+ //# sourceMappingURL=artPipeline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"artPipeline.d.ts","sourceRoot":"","sources":["../../../src/console/routes/artPipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAG5B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAMrE,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,aAAa,GAAG,IAAI,CAoEhE"}
@@ -0,0 +1,83 @@
1
+ /**
2
+ * @module console/routes/artPipeline
3
+ * @description `/api/art/pipeline/runs` —— 美术流水线的 run 面(1.7 契约 + 1.8 并发闸)。
4
+ * @layer console
5
+ *
6
+ * 🔴 **这一层不干活**:校验 → 交给 `ArtRunService`(它把活丢进子进程)→ 立刻 202。
7
+ * 路由里**一次同步 fs 都没有**,也不等图跑完 —— 事件循环是所有请求共用的。
8
+ *
9
+ * 三条来自 1.7、写死在这里不许被"顺手优化"的:
10
+ * `verify` 缺省显式 false · `jobId` 禁 `:` · **拒收一切路径字段** —— 见 `artRunRequest`。
11
+ */
12
+ import { Hono } from 'hono';
13
+ import { domainError, toHttpStatus, toProblemJson } from '../../shared/errors.js';
14
+ import { parseArtRunRequest } from '../lib/artRunRequest.js';
15
+ function isDomainError(e) {
16
+ return typeof e === 'object' && e !== null && 'kind' in e && 'code' in e && 'message' in e;
17
+ }
18
+ export function createArtPipelineRoute(runs) {
19
+ const app = new Hono();
20
+ app.post('/runs', async (c) => {
21
+ let body;
22
+ try {
23
+ body = await c.req.json();
24
+ }
25
+ catch {
26
+ return c.json(toProblemJson(domainError('validation', 'BODY_INVALID', '请求体必须是 JSON 对象')), 422);
27
+ }
28
+ let req;
29
+ try {
30
+ req = parseArtRunRequest(body);
31
+ }
32
+ catch (e) {
33
+ if (isDomainError(e))
34
+ return c.json(toProblemJson(e), toHttpStatus(e));
35
+ throw e;
36
+ }
37
+ // 🔴 `verify:true` **当场拒绝,不是静默忽略**:验证棒(评委→docs/asset-issues.json)
38
+ // 还留在 studio 的 artPipeline 里,没搬。静默忽略的话调用方会以为它跑了 ——
39
+ // 而"没验证"这件事**不会有任何症状**,只会在某天被当成"验证没抓到"。
40
+ if (req.verify) {
41
+ return c.json(toProblemJson(domainError('validation', 'VERIFY_NOT_SUPPORTED', 'sps 本版不支持 verify:true(验证棒尚未迁移)——请显式传 false', { details: { field: 'verify' } })), 422);
42
+ }
43
+ // 🔴 凭据必须一路带到子进程(1.7b)。曾经这里只传前四个字段,
44
+ // `gatewayToken`/`gatewayBaseUrl` 校验完就没人要了 —— 子进程回落全局 provider,
45
+ // 归因落平台账、**不报错**。别再"精简"这个对象。
46
+ const r = runs.start({
47
+ locator: req.locator,
48
+ jobId: req.jobId,
49
+ manifestFile: req.manifestFile,
50
+ artPrompts: req.artPrompts,
51
+ gatewayToken: req.gatewayToken,
52
+ gatewayBaseUrl: req.gatewayBaseUrl,
53
+ ...(req.imageModel ? { imageModel: req.imageModel } : {}),
54
+ });
55
+ if (!r.ok) {
56
+ // 🔴 429 直接写在这里,**不给共享错误模型加一种 kind**:`toHttpStatus` 是全仓公用的,
57
+ // 为一条路径改它会波及所有消费方的分流。Retry-After 是"什么时候再来问"的建议,不是 ETA。
58
+ const de = domainError('precondition', 'ART_RUNS_BUSY', `美术并发已满(在跑 ${r.running},排队 ${r.queued})——请稍后重试`, {
59
+ details: { running: r.running, queued: r.queued, retryAfterS: r.retryAfterS },
60
+ });
61
+ c.header('Retry-After', String(r.retryAfterS));
62
+ return c.json(toProblemJson(de), 429);
63
+ }
64
+ // 202:活已收下、还没做完(排队与在跑都是 202 —— 调用方该做的事一样:去查状态)。
65
+ return c.json({ runId: r.runId, status: r.status }, 202);
66
+ });
67
+ app.get('/runs/:id', (c) => {
68
+ const v = runs.get(c.req.param('id'));
69
+ if (!v)
70
+ return c.json(toProblemJson(domainError('not-found', 'RUN_NOT_FOUND', 'run 不存在(注册表是内存的:console 重启后旧 run 查不到)')), 404);
71
+ return c.json(v);
72
+ });
73
+ app.post('/runs/:id/cancel', (c) => {
74
+ const v = runs.cancel(c.req.param('id'));
75
+ if (!v)
76
+ return c.json(toProblemJson(domainError('not-found', 'RUN_NOT_FOUND', 'run 不存在')), 404);
77
+ // 🔴 **202 不是 204**(1.7 写死):取消是"已受理",不是"已取消" ——
78
+ // 真正停下要等子进程走到下一个资产边界。204 会被读成"停了",而它可能还在画最后一张。
79
+ return c.json(v, 202);
80
+ });
81
+ return app;
82
+ }
83
+ //# sourceMappingURL=artPipeline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"artPipeline.js","sourceRoot":"","sources":["../../../src/console/routes/artPipeline.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * @module core/art/artConcurrency
3
+ * @description 美术流水线的**两个并发数**,以及它们为什么必须写在一起(1.5 / 1.8)。
4
+ * @layer core
5
+ * @boundedContext art
6
+ *
7
+ * ══════════════════════════════════════════════════════════════════════════
8
+ * 🔴 **瓶颈是 libuv 线程池,不是内存。**
9
+ *
10
+ * 每张图都要过 sharp(解码/抠图/缩放/编码),而 sharp 的活落在 **libuv 线程池**上,
11
+ * 池是**全进程共享**的。gameforge 并行生 8 张就能吃满它 —— 症状是
12
+ * **"sps 整体变慢"而不是"美术慢"**(同进程的每个 HTTP 请求、SSE 心跳一起卡),
13
+ * 归因会指向完全错的地方。
14
+ *
15
+ * ⚠️ 所以**不要复用 `RunGovernor` 的数字**:它是**内存刻度**(≈400MB/run),
16
+ * 按它算美术并发会得到"没满"的**假信号** —— 内存确实没满,卡住的是线程池。
17
+ * **两个闸量的是不同的东西,数字不可互借。**
18
+ *
19
+ * 🔴 **`UV_THREADPOOL_SIZE` 只在进程启动、线程池首次使用【之前】设才有效。**
20
+ * 池是懒初始化的:**定型之后改 `process.env` 静默无效**(没有报错,只是不生效)。
21
+ * ⇒ 唯一可靠的设法是 **spawn 子进程时放进它的 env**,
22
+ * 而不是写进 systemd(那会"影响无关的一切,且读代码的人看不见")。
23
+ *
24
+ * 所以这两个数**必须在同一处定义**:池的大小和"同时有几张图在处理"是同一个数的两面,
25
+ * 分开写的话它们会各自漂,而漂了之后**没有任何报错** —— 只是变慢或线程饿死。
26
+ * ══════════════════════════════════════════════════════════════════════════
27
+ */
28
+ /**
29
+ * 一个 run 内同时处理的资产数**上限**(= 子进程的 `UV_THREADPOOL_SIZE`)。
30
+ *
31
+ * 两个使用点,改一个必须改另一个(它们 import 的是同一个常量,所以改不散):
32
+ * ```
33
+ * providers/art/artGen manifest.concurrency 的封顶
34
+ * core/art/artRunLauncher spawn 子进程时的 UV_THREADPOOL_SIZE
35
+ * ```
36
+ * 8 是 studio 侧既有的封顶值(`Math.min(8, manifest.concurrency ?? 4)`),原样搬 ——
37
+ * **不是新调的参数**。真要调,判据在 1.11 的实测,不在这里拍脑袋。
38
+ */
39
+ export declare const ART_ASSET_CONCURRENCY_MAX = 8;
40
+ /**
41
+ * 并发闸的缺省值(1.8)。全局总闸 + 租户配额,**不做项目级**。
42
+ *
43
+ * ⚠️ 这三个数今天是**保守的猜测,不是测量结果** —— 唯一有把握的是"它们与
44
+ * RunGovernor 的内存刻度无关"。1.11 实测之后按线程池/CPU 的真实表现调,
45
+ * 调之前它们至少保证:**一台机器上不会同时跑起十几个吃满线程池的子进程**。
46
+ */
47
+ export declare const ART_RUN_LIMITS: {
48
+ /** 全机同时在跑的 run 数。 */
49
+ readonly maxRuns: 2;
50
+ /** 同一租户同时在跑的 run 数(**不做项目级**:项目级配额是产品策略,不是资源保护)。 */
51
+ readonly maxRunsPerTenant: 1;
52
+ /** 排队深度(全局)。**超了就 429 + Retry-After,不无限积压** —— 无限队列
53
+ * 只是把"现在失败"变成"过很久之后失败",而调用方拿不到任何可操作的信号。 */
54
+ readonly maxQueued: 8;
55
+ /**
56
+ * `Retry-After` 的秒数。
57
+ *
58
+ * 🔴 **是"什么时候再来问"的建议,不是承诺**。刻意不按"队列深度 × 平均时长"估:
59
+ * 那个平均时长今天**没有测量过**,估出来的数会被调用方当成 ETA 用。
60
+ */
61
+ readonly retryAfterS: 30;
62
+ };
63
+ //# sourceMappingURL=artConcurrency.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"artConcurrency.d.ts","sourceRoot":"","sources":["../../../src/core/art/artConcurrency.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH;;;;;;;;;;GAUG;AACH,eAAO,MAAM,yBAAyB,IAAI,CAAC;AAE3C;;;;;;GAMG;AACH,eAAO,MAAM,cAAc;IACzB,qBAAqB;;IAErB,oDAAoD;;IAEpD;gDAC4C;;IAE5C;;;;;OAKG;;CAEK,CAAC"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * @module core/art/artConcurrency
3
+ * @description 美术流水线的**两个并发数**,以及它们为什么必须写在一起(1.5 / 1.8)。
4
+ * @layer core
5
+ * @boundedContext art
6
+ *
7
+ * ══════════════════════════════════════════════════════════════════════════
8
+ * 🔴 **瓶颈是 libuv 线程池,不是内存。**
9
+ *
10
+ * 每张图都要过 sharp(解码/抠图/缩放/编码),而 sharp 的活落在 **libuv 线程池**上,
11
+ * 池是**全进程共享**的。gameforge 并行生 8 张就能吃满它 —— 症状是
12
+ * **"sps 整体变慢"而不是"美术慢"**(同进程的每个 HTTP 请求、SSE 心跳一起卡),
13
+ * 归因会指向完全错的地方。
14
+ *
15
+ * ⚠️ 所以**不要复用 `RunGovernor` 的数字**:它是**内存刻度**(≈400MB/run),
16
+ * 按它算美术并发会得到"没满"的**假信号** —— 内存确实没满,卡住的是线程池。
17
+ * **两个闸量的是不同的东西,数字不可互借。**
18
+ *
19
+ * 🔴 **`UV_THREADPOOL_SIZE` 只在进程启动、线程池首次使用【之前】设才有效。**
20
+ * 池是懒初始化的:**定型之后改 `process.env` 静默无效**(没有报错,只是不生效)。
21
+ * ⇒ 唯一可靠的设法是 **spawn 子进程时放进它的 env**,
22
+ * 而不是写进 systemd(那会"影响无关的一切,且读代码的人看不见")。
23
+ *
24
+ * 所以这两个数**必须在同一处定义**:池的大小和"同时有几张图在处理"是同一个数的两面,
25
+ * 分开写的话它们会各自漂,而漂了之后**没有任何报错** —— 只是变慢或线程饿死。
26
+ * ══════════════════════════════════════════════════════════════════════════
27
+ */
28
+ /**
29
+ * 一个 run 内同时处理的资产数**上限**(= 子进程的 `UV_THREADPOOL_SIZE`)。
30
+ *
31
+ * 两个使用点,改一个必须改另一个(它们 import 的是同一个常量,所以改不散):
32
+ * ```
33
+ * providers/art/artGen manifest.concurrency 的封顶
34
+ * core/art/artRunLauncher spawn 子进程时的 UV_THREADPOOL_SIZE
35
+ * ```
36
+ * 8 是 studio 侧既有的封顶值(`Math.min(8, manifest.concurrency ?? 4)`),原样搬 ——
37
+ * **不是新调的参数**。真要调,判据在 1.11 的实测,不在这里拍脑袋。
38
+ */
39
+ export const ART_ASSET_CONCURRENCY_MAX = 8;
40
+ /**
41
+ * 并发闸的缺省值(1.8)。全局总闸 + 租户配额,**不做项目级**。
42
+ *
43
+ * ⚠️ 这三个数今天是**保守的猜测,不是测量结果** —— 唯一有把握的是"它们与
44
+ * RunGovernor 的内存刻度无关"。1.11 实测之后按线程池/CPU 的真实表现调,
45
+ * 调之前它们至少保证:**一台机器上不会同时跑起十几个吃满线程池的子进程**。
46
+ */
47
+ export const ART_RUN_LIMITS = {
48
+ /** 全机同时在跑的 run 数。 */
49
+ maxRuns: 2,
50
+ /** 同一租户同时在跑的 run 数(**不做项目级**:项目级配额是产品策略,不是资源保护)。 */
51
+ maxRunsPerTenant: 1,
52
+ /** 排队深度(全局)。**超了就 429 + Retry-After,不无限积压** —— 无限队列
53
+ * 只是把"现在失败"变成"过很久之后失败",而调用方拿不到任何可操作的信号。 */
54
+ maxQueued: 8,
55
+ /**
56
+ * `Retry-After` 的秒数。
57
+ *
58
+ * 🔴 **是"什么时候再来问"的建议,不是承诺**。刻意不按"队列深度 × 平均时长"估:
59
+ * 那个平均时长今天**没有测量过**,估出来的数会被调用方当成 ETA 用。
60
+ */
61
+ retryAfterS: 30,
62
+ };
63
+ //# sourceMappingURL=artConcurrency.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"artConcurrency.js","sourceRoot":"","sources":["../../../src/core/art/artConcurrency.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,CAAC;AAE3C;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,qBAAqB;IACrB,OAAO,EAAE,CAAC;IACV,oDAAoD;IACpD,gBAAgB,EAAE,CAAC;IACnB;gDAC4C;IAC5C,SAAS,EAAE,CAAC;IACZ;;;;;OAKG;IACH,WAAW,EAAE,EAAE;CACP,CAAC"}
@@ -0,0 +1,6 @@
1
+ import type { ArtRunLauncher } from './artRunService.js';
2
+ /** 结果行前缀 —— 子进程的 stdout 上可能还有别的输出(算子的 warn 等),靠它挑出结果。 */
3
+ export declare const ART_RESULT_PREFIX = "SPS_ART_RESULT ";
4
+ /** 生产用 launcher。测试注入自己的(见 artRunService.test)。 */
5
+ export declare const spawnArtRun: ArtRunLauncher;
6
+ //# sourceMappingURL=artRunLauncher.d.ts.map