yyfabab1/dsh-gpt-image

dsh-gpt-image

游戏素材生产流水线:出图、生视频与音频,把一段视频变成连贯的精灵动画,派生配色变体,适配引擎配置,装进项目并核对代码是否真的引用了。视频转精灵动画需 ffmpeg;出图/生视频需自备图像/视频 API Key(Agnes AI / OpenAI / fal)。

미분류ai-image-generationdeepseek-harnessdsh

dsh-gpt-image · GPT Image 出图台(DeepSeek Harness 插件)

把 GPT Image Playground(gpt-image2ground)的能力模型搬进 DSH:多服务商 · 多配置档 · 文生图 · 图生图/局部重绘 · 异步任务轮询 · 自定义接口模板 —— 变成 AI 自己就能调用的工具。

独立插件,零运行时依赖(只用 node: 内置模块),装进 profile 不需要再拉依赖树。


它和 Playground 的对应关系 · Feature parity

GPT Image Playground 里的能力本插件里的形态
多服务商(openai / sb2api 异步 / fal / 自定义)内置 3 个模板 + 任意自定义 provider 模板
多配置档(每个档 = 接口 + Key + 模型)profiles(配置档),工具与面板都能增删改、切换激活档
gpt-image-config.json 导入导出config 工具 import / export,格式完全兼容
文生图(Images API)generate(apiMode: images)
Responses API 模式generate(apiMode: responses,tool_choice: required + image_generation 工具)
图生图 / 刷蒙版局部重绘edit(image[] + mask,multipart;Responses 模式走 data URL)
异步提交 + 轮询任务generate ... wait_s=0 拿 task_id → job 查状态/继续等
$profile.model / $params.size 模板占位同一套占位语法 + 路径表达式 data.*.url / result.data.*.b64_json
「哪张图对应哪句提示词」落盘 <输出根>/<项目>/<素材名>_序号.png + <素材名>.json + 项目 图片清单.md
出图历史 / 预览面板 /gpt-image:缩略图 + 提示词 + 参数 + 状态,全部可回看

本插件比 Playground 多的几件事:

能力说明
自动化编排(L–R)一个 auto 工具、36 个 action:声明式流水线(N);规则引擎+置信度分流(M);自愈(O);触发(L);优化(P);治理(R)
素材工作室(F–J)一个 studio 工具、30 个 action:模板库/变体矩阵/成本(F);引擎契约·引用完整性·替换影响(G);并发锁·来源归属·损坏检测·备份回滚(H);健康度·漏斗·趋势(I);风格指南·共享库·风格迁移·A/B·图鉴(J)
质量地基(A–E)一个 quality 工具、11 个 action:spec 校验+自动修复 / 协调性 / 派生变体 / 图集 / 视觉标签 / 评分路由 / 偏好 / 回归
素材变化摘要画廊顶部横幅:新增 / 消失 / 内容变化 / 结论失效。首次只建基线;touch 不算变化;清空不刷屏
审核对比视图C 进四格对比:原图 / 实时切片 / 游戏实际尺寸 / 相邻帧。键盘流 ←/→ A/R/P
批量重处理规则变了但源图没变 → 结论继承、只重切;失败回滚、孤儿清理、文件锁互斥
素材目录 + 人工审核画廊以磁盘为准(含切片/缩放件/外部产物)。勾选后一键标 合格/不合格/待定
余额 / 花费 · 免费档无限额度 · ratio · 重试 · 蒙版守卫 · 花钱闸门见下方各节

L–R:自动化编排

⚠ 单机游戏的硬约束(已确认)

Q1 装机数据回传 / Q2 效果反馈校准 / Q4 玩家数据补货 / P4 自动 A/B / J4 真实 A/B —— 这些全部依赖游戏端回传数据。单机游戏没有回传通道,所以:

功能状态替代方案
Q1 使用分析⚠ 降级用文件级证据(代码里有没有引用)判断"死重量",并明确标注这不是玩家行为数据
Q2 评分校准❌ 不做没有真实使用数据可校准
P4/J4 A/B⚠ 降级保留实验设计 + 显著性判定(ab-design/ab-analyze),数据由你填,不编造
Q4 补货✅ 本地版只看本地库存(数量/合格数/被引用数)
Q3 淘汰✅ 本地版用"未被引用 + 低分 + 过保护期"判定,不依赖玩家数据

你指出的冲突,逐个解法

冲突解法(已实现)
M2 自动 approve ↔ 责任事后抽查(确定性 hash 抽样 + 保底至少 1 张)+ 批量撤销(review-undo,撤销本身也留痕可再撤)
Q3 自动淘汰 ↔ H3 备份淘汰计划带 requires_snapshot: true;清理先移到回收目录而不是直接删
C1 变体 ↔ 母图结论变更级联失效(cascade-plan/apply):母图 reject → 派生件一并驳回(不让再审一遍)
N5 事务性 ↔ N3 并行事务按批次划:某步失败 → 整批中止,不留"一半新一半旧"
L1 监听 ↔ H1 文件锁防抖(mtime 距今 > 5s)+ 尺寸二次确认,写完才读
L1 监听 ↔ 脚本失控限流(超量只处理前 N)+ 熔断(连续 3 轮超量就停)

你自检的 7 条,全部补上

漏项实现
批量撤销review-undo:按批次恢复,且撤销本身也留痕(可再撤回来)
试运行模式所有破坏性 action 默认 dry_run=true;流水线有 require_dry_run_first
审核的 undo同上(人的操作和机器的一样都进日志)
素材库版本backup / restore(只备份状态文件,不备份几万张图)
并发审核协调H1 心跳锁 + R3 权限;冲突由日志可追溯
权限R3:外部脚本不能改审核结论(未知身份 fail closed)
自动化冷启动M2 降级:历史样本 < 20 时阈值提高,全部转人工,不在没数据时硬猜

诚实说明:三件"做不到"的事

想做的为什么做不到正确落点
L3 定时任务插件无常驻进程,DSH 关了就没用 DSH 任务看板的 cron 卡每天调一次 auto action=maintenance
L1 永久目录监听同上(fs.watch 只在插件活着时有效)webhook 主动上报 / git hook / cron 触发防抖扫描
R2 自动推送告警插件没有推送通道(发不了邮件/IM)checkAlerts 出清单,由 cron 卡消费

auto action=maintenance 会给出完整维护清单(扫变更→清孤儿→查损坏→悬空结论→健康→备份→告警), 配合 DSH 的定时任务就是无人值守的日常维护。

声明式流水线(N1)

name: 我的素材流水线
on: [manual, new_asset]
steps:
  - id: postprocess
    use: postprocess
    opts: {defringe: true, sharpen: true}
  - id: slice
    use: slice
  - id: validate
    use: validate
  - id: route
    use: route
  - id: auto_review
    use: auto_review
    opts: {min_confidence: 0.85, sample_after_approve: 0.1}
  - id: install
    use: install

改流程改这个配置,不改代码。pipeline-validate 会检查步骤依赖顺序(install 需要 review 前置)。

F–J:从生成到演进的闭环

F1 模板 ──→ F2 变体矩阵 ──→ batch 执行 ──→ F3 成本汇总
   ↑                                            │
   │                                            ↓
J1 风格指南 ←── 审核通过 ←── A2 规格/A1 后处理 ←──┘
   │                    │
   ↓                    ↓
J2 共享库          C1 派生变体 → C2 图集
                        │
                   H2 来源登记 / H3 备份 / H1 并发锁
组关键设计(都是实测踩出来的)
F1 模板缺变量保留 {占位符} 而不是静默留空 —— 否则 prompt 少一截却看不出来
F2 调度变体是乘法的(prompt×尺寸×质量×seed),>50 张给成本预警。每个变体带 seed,好结果能精确复现
F3 成本均单价只看付费张数(免费图不稀释);性价比按「记录里被装机名字的比例」折算,而不是"命中一个就算整条都用上"
F4 反向提取反推的是可复用风格特征(颜色/明暗/线条/构图),不猜"画的是什么"
G2 契约自己读文件核尺寸与透明(不是等调用方传)
G3 引用完整性误报比漏报更糟:实测被 diag 脚本的产出名(cdpperf.png)误报过 36 次。现按相对游戏目录的路径判定用途;冗余必须遍历「磁盘上的图」,只遍历代码引用会让它恒为空
G4 影响分析给出具体文件+行号与风险等级,不说"有引用"这种空话
H1 并发锁用心跳而不是 exists() —— 进程被强杀后锁能自动失效,不会永久卡死
H2 来源归属归一化路径(大小写/斜杠);补登记按「目录+日期」聚类,并声明这是启发式
H4 损坏检测三级:文件头 → IEND 结尾(写一半的典型特征)→ 解码
H3 备份回滚只备份状态文件(审核结论/快照/账本),不备份几万张图。回滚前先给当前状态留一份 —— 回错了还能回来
I1–I4 可观测审都还没审时优先提醒这一点;样本不足就明说"看不出趋势",不硬给方向
J4 A/B不采集游戏数据(那要埋点)。只做实验设计与显著性判定,不编造留存/点击数字
J5 图鉴自包含 HTML,可按状态/类别筛选;名字转义(防 <img onerror> 注入)

质量地基(A–E)

核心思路:把「能不能用」从审美问题降级成技术问题。

组能力关键设计
A2规格即代码resolveSpec:内置默认 → <游戏目录>/asset-specs.json → 「待画素材清单.md」→ 显式传入。能修的修(尺寸/透明/留白/毛边),不能修的才报人(空图/全黑/背景无法确定)
A1出厂后处理applySpecFixes 固定顺序:抠背景 → 去毛边 → 裁留白 → 调色板吸附 → 锐化 → 尺寸归一。先抠再裁(背景透明了包围盒才准),最后缩放(避免锐化放大噪点)
B1调色板锁定 + 漂移buildPalette 按覆盖张数排序(跨多张都出现的色才是"这套的底色");paletteDrift 按「离群色的占比」判 ok/watch/off。取样 <2% 的噪点不参与
B2小尺寸可读性先缩到游戏尺寸再算(大图上一切清楚,问题只在缩小后暴露):对比度 p90−p10、有效像素占比、边缘密度
B3动画连贯性相邻帧差 + 首尾闭合 + 全一样。判"突变"用中位数而非平均值 —— 平均值会被突变本身拉高(实测 0.09/0.08/0.74 → avg 0.30,0.74 只有 2.5 倍,卡在阈值上检不出来;中位数 0.09 → 8 倍,稳定检出)
C1派生变体翻转/灰度/染色/受损/高亮。染色保留明暗结构(按亮度用目标色重建,不是简单叠色);受损态用 xorshift32 确定性噪点(同 seed 同结果,可复现可测)。继承源图审核结论 —— 人审 1 张 = N 份产出
C2图集打包同一次计算产出图集与 manifest,天然一致(手工拼容易改了顺序忘改 manifest)。注意用 fitted.drawn 而不是 fitted.width —— 后者是画布尺寸,小图会被误报成"填满整格",引擎据此放大就错了
D1视觉标签用确定性图像特征给可检索标签(横/竖、透明底、主色系、密度、线条感),不调视觉模型猜"画的是什么"(慢且不稳)。语义描述只来自 prompt
D2prompt 血缘lineageOf 记录可复现参数;planRegenerate 派生相邻 seed。没有 seed 就坦白说"随机、无法复现",不假装确定性。注意 seed: 0 是合法值,不能用 `
D3评分路由分数构成全可解释(硬伤 −60 / 可读性 −25 / 留白 −15 / 漂移 −20 / 尺寸 −5)。auto_pass ≥80 / normal 50-79 / priority <50。auto_pass 不等于"好看",只保证技术层面无硬伤
E1偏好学习从合格/不合格记录统计最常用的 provider/model/尺寸;样本 <5 就说"别急着学",不硬给结论
E2回归检测重画后与 .bak-before-install 对比质量分;差 ≤3 判持平(避免噪声假报变好/变差)

工具:quality(check/batch/spec/fix/coherence/animation/variants/atlas/tags/preference/regression)

审核一次、之后自动到底

人只出现在两个地方:审核、冲突。其余全自动。

生成(本插件 / 外部脚本)
      ↓
  磁盘上的素材
      ↓
  审过?─否→ needsReview ──→ 【人:画廊勾选/对比视图拍板】
      │是                          ↓
      ↓                        approved
  直接用                          ↓
      ↓                   自动切片(继承源图结论)
      ↓                          ↓
      └────────→ needsInstall ←──┘
                     ↓
              install 自动装(无人值守)

队列(assets.json.queue,派生数据、可重算):

队列含义谁处理
needsInstall已合格、待装install 自动
needsReview从未审核人(唯一必须人的地方)
needsReReview审过但内容变了 → 结论失效人(重审一次)

needsReview / needsReReview 不阻塞已合格部分的安装。

切片规则的歧义(实测踩了三个坑)

网格从哪来?名字 + 清单 + 磁盘观测三者定,优先级从低到高:

例子陷阱处理
t_path_64x1616 是尺寸高度,不是 16 格排除 _WxH 后缀
i_skull_4848 是清单声明的规格(48×48)结尾数字等于清单宽/高 → 当尺寸
r67tpath_dark22 是版本号,不是 2 格名字只作候选,以观测为准
r67_frames_8名字对,且磁盘上真有 8 个切片观测优先(_slices/ 里文件数)

所以 <name>_slices/ 里实际有几个文件才是事实来源;名字解析只用于"还没切过"的情况。

工具:assets(scan/review/clear/pending/changes/queue)、reprocess、quality 接口:/api/assets、/api/changes、/api/compare、/api/review、/api/reprocess

素材目录与审核(为什么以前看不到很多图)

根因:history.json 只记录本插件生成的图。而输出目录里还有大量图来自别处:

来源例子以前
切片产物hero_slices/hero_01.png看不见
缩放件hero_64x16.png看不见
带名预览图hero_1_labeled.png部分可见
截图shot_xxx.png看不见
别的脚本/agent 批量产出r67rest_cardart_28.png看不见

实测本机:目录里 916 张,本插件只记了 235 张 → 681 张是"只在文件夹里"的。

现在画廊改为扫磁盘,全部列出来,并给每张分类(source / slice / resized / preview / screenshot), 顶部两排筛选条可按「类别」与「审核状态」过滤。

一件勾选:

按钮作用
全选本页勾选当前筛选结果的全部
选未审核只勾还没拍板的(逐批清最快)
选外部产物只勾不是本插件生成的(那批历史遗留)
✓ 标为合格 / ✕ 标为不合格 / ? 标为待定对已勾选项批量写结论
清空选择取消勾选

审核结论按素材名也记一份,所以「重画同名的图」会继承你之前的判断(按文件路径的单独结论优先)。

数据落在 ~/.dsh/gpt-image/assets.json,与 history.json 分开 —— 前者是"人工判断",后者是"生成记录", 生命周期不同(重画不清掉结论,改结论也不动生成记录)。

给 AI 的约定(assets 工具 guidance 里会明说):

  • 挑素材给游戏用 → 优先 review=approved
  • unreviewed 未经人确认,rejected 不要用
  • kind=preview(画面里带名字)与 kind=screenshot 是给人看的,不要装进游戏

工具:_dsh_external_dsh_gpt_image_assets(scan / review / clear / pending) 接口:GET /gpt-image/api/assets、POST /gpt-image/api/review、POST /gpt-image/api/review-clear

花钱闸门:免费与付费分开算

免费档(单价 0)的语义是「无限额度」 —— 这是刻意的,因为 0 元没有可防的东西:

锁付费档免费档
dailyBudget 日预算超了就拒不参与(付费额度用尽也照样跑)
confirmAboveImages 确认锁超过要 confirm不触发(0 元没什么可确认的)
spendDisabled 总闸拒绝不受影响(它是"付费总闸")
perRunMaxImages 单次张数生效同样生效 —— 这是请求体量保护,与钱无关

免费档在 check() 返回值里会明说 free: true、quota: 'unlimited'、remaining_today: null, 并在 skipped_locks 里列出「本次跳过了哪些锁」,避免用户疑惑「为什么没拦我」。

记账也是分开的(spend.json):

{
  "records": [ { "images": 5, "cost": 0,    "free": true  },   // 免费:只记张数
               { "images": 3, "cost": 0.12, "free": false } ],  // 付费:记张数 + 金额
  "totals": { "free": { "images": 5, "runs": 1 },
              "paid": { "images": 3, "runs": 1, "cost": 0.12 } }
}
  • todayFree() / todayPaid() 分开取;todaySpent() 只算付费(免费恒 0 元,天然不挤占预算)。
  • 旧记录没有 free 字段时按金额推断,所以历史数据不会算错。

⚠️ 闸门按「本次实际使用的配置档」算价,不是激活档。 否则「激活档是免费 + 显式指定付费档」会绕过日预算与确认锁 = 漏钱。 7 处闸门调用都传 pricePerImage: priceForProfile(state, args.profile),并有回归测试钉住。

余额显示怎么看

看到含义
🎁 免费档 ∞ · 3 张当前是免费档:无限额度,今日已免费出 3 张(不占付费预算)
💰 0.20 / 1 CNY + 进度条付费档:今日已花 / 日预算;条子变黄 = 用到 80%,变红 = 用尽
状态「预算用尽」付费档会被拦下;等明天、调高 locks.dailyBudget,或切免费档
状态「总闸已关」spendDisabled=true,付费调用被拒(免费档不受影响)
上游「无真实余额(免费/内部额度)」该账户额度上限是占位值(如 1e8),不是真余额 —— 插件不会把它伪装成余额

为什么分两块看:本地闸门才是真正会拦住出图的(真实生效);上游账户计费多数中转站不提供接口, 且免费档的「额度」是占位数字。所以插件以本地为主、上游为参考,并在详情里如实标注来源。

工具:_dsh_external_dsh_gpt_image_balance(check_remote=false 只看本地不联网) 接口:GET /gpt-image/api/balance(?remote=0 跳过上游查询,面板轮询用)

自动纠错:baseUrl 少写 /v1(中转站最常见的坑)时,先按原样请求,404 自动补 /v1 重试; probe 还会直接告诉你「建议把 baseUrl 改成 xxx」。


省钱工作流:攒够一批再生成 · Accumulate, then spend

单价按 0.04 元/张 计(pool.json 里可改 pricePerImage),插件的工作方式是先攒后花:

scan(root="D:\AHC\doomsday-ember")
  → 体检:还缺 16 张素材 → 只登记进「需求池」,一张都不生成
pool(action="show")            → 待生成 16 张 · 阈值 8(卡牌类 12)· 现在跑要花 0.64 元
pool(action="flush", dry_run=true)  → 只规划:冷启动先 1 张探针、本轮最多几张、预计花费
pool(action="flush", confirm=true)  → 到阈值才一次性跑,跑完自动从池子划掉
install(root="D:\AHC\doomsday-ember")  → 按规格缩放(保留透明) → 写进游戏 → 立刻复核尺寸与引用
screenshot(mode="window", window="<游戏窗口>", attach=true)  → 重大改动后截图给用户看

两道花费闸门:①没到阈值不给跑(除非 force=true);②一批超过 autoSpendLimit(默认 0.5 元) 必须显式 confirm=true。所有预估都写在返回里,先看数字再决定。

install 解决的正是黑屏:模型只能出 1024 级别大图,游戏要 40×60 这种规格;手工缩放很容易做出 几百字节的空图(wx_clear_tile_40x60.png 就是 450 字节 → 黑屏)。install 统一按规格缩放 (保留 alpha,实测 1024×1536 → 40×60 透明 3.4KB)→ 落到游戏真正读的位置(同名文件先备份 .bak-before-install)→ 复核,未登记的还会告诉你差哪一步。


多花钱的三个真 bug(已修 · Money-leak fixes)

自查真实使用数据时发现的三处「钱会漏掉」的地方,都已修好并有回归测试:

问题后果现在
判停用「已成功张数」失败的张数不算数 → 整轮顶出上限,多花改成按已尝试张数判停(失败也烧钱),并在返回里给出 images_attempted
时间预算用平均耗时实测 141s/最慢 214s 时,按 141s 排量 → 超时那部分白花取最慢值估算(estimateLatencyMs),排不完整的不排
普通失败不会停批只认限流;连续报错时会一直撞着烧钱连续 3 波有失败就停(repeated-failures),并退回 1 路并发
单张生成不查重同一句提示词再发一次 = 白付一次生成前指纹预检,命中直接跳过(skipped_duplicate, spent: 0),要重画才带 force=true

实测:拿历史里 starcraft-rts / flag_terran 的原参数再要一次 → skipped_duplicate = true, spent = 0,直接复用已有产物。

另外:硬规定不允许静默失效 —— 「画面内带名字」的预览图如果没做出来, 返回里会明确给出 label_warning + label_errors,不再悄悄跳过(你的历史里曾有 3/8 张没标签)。


第四轮:审批流 + 跨项目风格 + 图标集批量生成

1. 资产预算审批流(approval)

四道锁拦的是「单次张数/日预算」,但一次 5 元、10 元的大批采购属于"要跟人商量"的量级,单独拎出来:

batch(assets=[...6 个...], confirm=true)
  → needs_approval: true,返回待批准单(没花任何钱)
【待批准】batch · demo
  要花:0.24 CNY(6 张)
  用途:塔防第一批通用素材
  画什么:tower_fire、tower_ice、enemy_grunt、enemy_boss、icon_coin、icon_heart
  影响范围:src/game.js
  单号:apr_861af39046ac6d8c

approval(action="approve", token="apr_861af39046ac6d8c")
  → 已批准。把 approved_token 传给对应工具就会执行。
  • 面板上点确认:新增「待批准」标签页,直接点批准/驳回;超 24h 未处理会标红
  • 单子有签名:approvalToken() 由参数决定 —— 参数变了单子自动作废(防止批了 A 跑了 B)。实测:6 张批准后放行;改成 7 张立刻出新单号
  • 阈值默认 5 元,approval action=set_threshold 可改;待批准单文本可直接贴进对话

2. 跨项目风格对比(cross_style)

你怀疑的「两个项目风格差异大」已经量化了,实测输出:

doomsday-ember  ↔ starcraft-rts      = 0.243  ← 两套美术语言,别互相参考
starcraft-rts   ↔ workbuddy-deploy   = 0.164  ← 差异最大
workbuddy-deploy↔ 连通性测试          = 0.496  ← 风格接近,可共享参考
警告:检测到跨项目风格差异明显:生成时请只用「同项目」的素材当风格参考。

配套强制约束:挑参考图时若没指定项目,直接拒绝跨项目挑(cross_project_blocked)——宁可没有参考图,也不要串味。面板新增「跨项目风格」页。

3. 图标集批量生成(sheet)· 切图后自动对齐

你说得对,你们本来就有这个习惯(147_whole、r42_cardart_sheet)。现在做成工具:

sheet(items=[9 个图标描述], project="tower-defense")
  → 1 个请求出 1536×1024 合集图 → 自动按 3×3 切开 → 每个图标自动对齐 → 缩放到游戏规格
  → requests_used: 1, requests_saved: 8

切图默认自动对齐(trim: true),交付的是「能直接用的图标」而不是「格子」:

步骤说明
① 连通域分析洪水填充找出每格里的图形块(4 邻域,迭代式不爆栈)
② 抗噪丢掉落单的孤立像素/碎块(实测:角落 1 个杂点被识别为 dropped_specks: 1,不会把包围盒撑满整格)
③ 取主体包围盒用最大连通域 + 附近的大碎块合并,得到图标真实边界
④ 居中缩放裁掉多余留白 → 按 target_width/target_height 等比 contain 居中(或 stretch),四周补透明
⑤ 质量判断主体占比过小/没找到主体的格子给 warnings,不硬裁成垃圾

实测(故意做了一张「图标大小不一、位置歪、还有空格子」的合集图):

icon_1  格内 100×100 → 内容 20×20  → 输出 48×48  居中偏差 0.0/0.0
icon_2  格内 100×100 → 内容 60×40  → 输出 48×48  内容画成 48×32 垂直居中
icon_3  格内 100×100 → 内容 30×15  → 输出 48×48
icon_4  格内 100×100 → 内容 40×60  → 输出 48×48  内容画成 32×48 水平居中
icon_5  空间格子                       → 警告「没找到图标主体」
icon_6  格内 100×100 → 内容 25×25  → 输出 48×48

过程中修掉一个真 bug(坐标系):contentBox 返回的是「格内相对坐标」, 跨格裁剪必须先用 absoluteBox() 转成整图绝对坐标 —— 第一版混用了,导致 图标被裁到角落甚至整张全透明(测试图里 icon_3/icon_6 空白)。现在两个坐标系显式分开, 并有专门的回归测试(tests/autotrim.test.js)锁死。

参数:trim(默认 true)、target_width/target_height(缩放规格)、mode(contain/stretch)、 padding(格子内缩);items 支持逐格指定尺寸([{name:"icon",width:48,height:48}])。

本轮新增工具(3 个,共 20 个)

工具作用
_dsh_external_dsh_gpt_image_approval待批准清单:list / approve / reject / set_threshold
_dsh_external_dsh_gpt_image_sheet图标集:generate(出图+切)/ slice / grid
_dsh_external_dsh_gpt_image_cross_style跨项目风格对比与污染预警

优化

#优化说明
1修好参考图打分的死代码之前 resolveStyleRefs 里有个空分支,指纹匹配从没真正生效。现在用统一的 scoreReference 打分:分类一致(2.5) + 宽高比(1.5) + 风格指纹(4) + 尺寸接近(1) —— 竖版卡牌不会再被塞一张横版背景当参考
2使用热度去噪_labeled 预览图、_fixed 副本、.bak 不再算成独立素材(否则会误报"白花钱");同一素材的多尺寸去重成一个
3热度视图给全新增 warm(被引用但不多)与 all(完整列表)。之前只有 hot(≥5次) 和 cold(0次),用到的素材可能两个视图都找不到
4自检结果回流提示词库废图自动进避坑清单(带原因),好图进可复用模板 —— 实测你项目已自动记下 2 条避坑(全黑/空图那两条提示词)

新扩展

扩展工具价值
一键工作台pipeline把「取素材 → 生成 → 自检 → 装机 → 登记」串成一条命令。素材来源自动选(需求池 → 顺序计划)。自检发现废图直接停下不装机(装进去只会黑屏),并给该重画哪几张 + 原提示词。实测 dry_run:assets-from-pool → 规划 4 张 / 313s
提示词库prompts记录每条提示词的成功/失败次数与原因。action=lookup 生成前先查;good 拿可复用模板;pitfalls 看避坑清单
使用热度heatmap哪些生成出来的图真被游戏代码用到、哪些零引用(=白花钱)。实测你项目:5 个去重素材里 3 个零引用(waste_ratio: 0.6)
花费预测config action=spend 里的 plan_forecast「这批 24 张约 0.96 元,今天只够 20 张,建议分 2 天」+ 预算还能撑几天

本轮修掉的 3 个真 bug

  1. heatmap 缺 import → checkProject is not defined,工具直接报错
  2. review 用错字段 → result.reviewed is not iterable(实际字段是 suspects/good_files)
  3. 热度把预览图当素材 → 会把 xxx_labeled.png 报成"白花钱"

#优化效果
1热启动学习粒度从「模型+尺寸+质量」放宽到「服务商+模型」:换尺寸/质量不再重新冷启动 1 张探针,只有真·第一次才探针
2真实单价校准config action=calibrate images=12 actual_cost=0.48 → 反推单价并写回,之后所有预算/估算都按真实价算;也能从上游 usage.total_cost 等字段自动采信
3失败重试上限同素材最多重试 1 次(可重试错误才重试:限流/超时/5xx),带退避;重试同样计入已尝试张数,不会偷偷超额
4风格指纹扫全项目给每张图算指纹(主色板/明暗/饱和度/线条密度/透明占比),生成时按「像不像」挑参考图,并把量化风格拼进提示词
5order 生成顺序从代码里解析素材依赖,按「先通用件(UI框/图标底板/地形)、后具体件(立绘/卡面/特效)」排批次 —— 避免通用件没定稿就画一堆具体图导致全部返工
6并行度caps.maxConcurrency 可调到 3~4;默认从 2 起,成功会自己加、失败自己退

三项扩展 · Three extensions

#扩展说明
7review 出图自检不等模型"看一眼",先用确定性规则筛废图:几乎单色 / 全黑 / 全白 / 对比度极低 / 没有边缘(画面是空的)/ 该透明却不透明 / 尺寸不符。实测在你项目里揪出 3 张(t_path_1、wx_clear_tile_1、wx_clear_tile_40x60)—— 正是黑屏元凶
8order 素材依赖图你的项目实测:169 个素材排成 3 批通用件(ui×3、terrain×22)+ 2 批具体件(effect×14、misc×130)
9spend 预算日历按天聚合花费 + 趋势 + 预测日均 + 文本条形图;config action=spend 直接看

新增工具

工具作用
_dsh_external_dsh_gpt_image_styleindex 建/更新风格索引 · show 看风格档案 · outliers 看跑偏素材
_dsh_external_dsh_gpt_image_order生成顺序计划(先通用后具体)
_dsh_external_dsh_gpt_image_review出图自检,只列可疑的
_dsh_external_dsh_gpt_image_register写进游戏登记表(action=install)/ 审计缺图(action=audit)

register 的一个关键保护

你的 src/84_assetmap.js 和 src/96_rpgasset.js 文件头写着「由 tools/build.mjs 自动生成,请勿手改」。 写它们等于白写(下次构建覆盖),所以 register 会识别并拒绝,同时告诉你该改哪个源文件:

84_assetmap.js skipped: 这是构建生成物(文件头写着「build.mjs」),改了会被下次构建覆盖

另外 register 兼容你项目的真实登记格式: "key":{"s":"data:image/png;base64,...","w":40,"h":60,"base":0,"g":"wx","label":"晴天叠加层"} —— 只替换 s/w/h,保留 base/g/label 等字段,写前备份 .bak-before-register。 action=audit 实测:96_rpgasset.js 里 323 项全部填了真图,没有破图。


每次要花钱的调用(generate / edit / batch / pool.flush)都必须先过 spend.js 的闸门, 记账落在 ~/.dsh/gpt-image/spend.json:

锁默认行为
① 总闸 spendDisabledfalse置 true = 一切付费调用直接拒绝(急停开关)
② 单次张数 perRunMaxImages12超过直接拒绝并让你拆批(实测:16 张被拦)
③ 确认锁 confirmAboveImages1一次要花 >1 张就必须带 confirm=true(实测拦住)
④ 日预算 dailyBudget1.0 元/天当天累计 + 本次估算超了直接拒绝(实测拦住)

查账 / 改锁:

config(action="spend")                                  → 今天几张、多少钱、预算、日历、趋势
config(action="calibrate", images=12, actual_cost=0.48) → 用真实扣费校准单价
config(action="set_locks", patch={"dailyBudget":2})     → 改预算
config(action="set_locks", patch={"pricePerImage":0.03})→ 单价按 3 分算
config(action="set_locks", patch={"spendDisabled":true})→ 急停,别想花我一分钱

需求池只收真缺口:报告文档、tools/、scripts/、tests/、out/ 以及 _ 开头的临时名 全部过滤掉(实测清掉了 36 条垃圾,否则会为文档里的截图白花钱)。

风格参考:generate/batch/pool.flush 带 use_style_refs=true 时,会自动从 游戏现有素材(同分类优先)→ 本项目已生成的图里挑 1~2 张当参考图走图生图, 并追加「严格对齐参考图的画风/线条/配色」的提示词;也可显式传 style_refs=[路径]。


DSH 里只保留这一个生图插件(原来的「素材锻造炉」已整体并入本插件,不再单独安装):

入口位置内容
左侧栏「生图」图标左侧图标栏(与其它主面板并列)点开是主区域整页:项目生图画廊 + 核对结果
会话头部「生图」按钮对话 / 轨迹那一行的 actions 区同一个画廊的浮层版,Esc 关闭

画廊里每条都带:

  • 缩略图(透明图用棋盘底,一眼看出有没有抠干净)+ 完整提示词 + 模型/尺寸/质量/耗时
  • 核对徽章:达标 / 规格不符 / 没被引用 / 用不上
  • 核对明细:游戏要求(尺寸/透明/可平铺)↔ 实际生成,以及游戏代码里哪些文件引用了它
  • 顶部可选项目、填游戏目录(会记住)、「体检」按钮跑一遍缺什么素材
  • 每 5 秒自动刷新,AI 正在出图时能看着一张张冒出来

素材核对是怎么判的

结论含义
达标规格符合项目里的清单要求,且游戏代码/工具里确实引用了
规格不符尺寸对不上(例如清单要 40×60,生成的是 1024×1536)——缩放裁剪后再登记
用不上缺透明通道,或文件小到只有几百字节(空图/纯色图)——游戏里就是黑屏/空白的直接原因
没被引用图本身没问题,但游戏代码里没有任何引用,或只在清单/报告文档里被提到

规格要求从项目里的 待画素材清单.md 这类表格自动读(| 素材 | \key` | 40×60,透明、可平铺 |`), 引用统计只看代码/配置(md/txt 清单与报告不算「在用」)。


安装 · Installation

从 DSH 商店装(推荐)

设置 → 插件 → 发现(Discover)→ 添加插件源,填:

https://gcore.jsdelivr.net/gh/yyfabab1/dsh-gpt-image@main/market-index.json

索引格式是 DSH 商店的 format 1;生成/校验方式见 docs/商店发布.md。

Windows 一键安装

:: 先只检查环境(不改任何文件、不重启)
一键安装.bat check

:: 确认没问题后正式装(会重启 DSH)
一键安装.bat

一键脚本会:检查 Windows / Node 20+ / 定位 DSH profile → 自检插件本体(工具数、侧栏配对)→ 建目录链接(失败自动改用复制)→ 重启 DSH → 打开 安装说明.html。 跳过重启:set AUTO_NORESTART=1。脚本本体是 install.ps1(UTF-8 BOM), .bat 只做纯 ASCII 启动 —— 因为 .bat 里写 UTF-8 中文在 chcp 65001 下会被拆成碎命令(实测)。

⚠️ 跨机器风险:只在作者本机(Windows + Node 22)验证过。 完整风险清单见 安装说明.html(浏览器打开,含可点击的 API 申请入口)。

手动安装

# 本地路径(目录链接,改代码即时生效)
mklink /J "%USERPROFILE%\.dsh\profiles\web\node_modules\@dsh-external\dsh-gpt-image" "D:\你的路径\dsh-gpt-image"

# 或 npm 包
dsh plugin --profile web add @dsh-external/dsh-gpt-image

装完重启 DSH,模型侧会多出 _dsh_external_dsh_gpt_image_* 工具,浏览器可开 http://127.0.0.1:3080/gpt-image 面板。

自检

npm test                        :: 687 个测试应全过
node scripts\ inventory.mjs      :: 27 工具 / 32 路由 / 4 侧栏页配对

API Key 去哪申请

完整清单(含全部网址、每家文档、免费图床可达性实测)见 docs/API_KEYS.md, 或者直接浏览器打开 安装说明.html —— 那里的按钮点了就跳到官网注册页。

速查:

服务商申请地址baseUrl / 模型
Agnes AI(作者在用,图+视频当前免费)https://platform.agnes-ai.comhttps://apihub.agnes-ai.com/v1 · agnes-image-2.5-flash / agnes-video-2.5-flash
OpenAI 官方https://platform.openai.com/api-keyshttps://api.openai.com/v1 · gpt-image-2
fal.aihttps://fal.ai/dashboard/keyshttps://fal.run · openai/gpt-image-2

预览图开关(*_labeled.png)

每张生成的素材都会配一张「画面里印着素材名」的预览图 —— 素材一多就知道哪张是哪张。 但预览图默认一直是原尺寸,一张 1024×1024 素材的预览图也是完整 1024×1024 PNG(约 1MB)。 实测攒了 535 张、吃掉 928MB,直接把磁盘塞满。

现在有三档(config(action="set_label")):

mode效果体积
small(默认)缩到最长边 maxSide 像素(默认 384),名字照样看清约原尺寸的 2~3%
on原尺寸(旧行为)100%
off完全不生成(明确关掉,不是"悄悄没做")0
config(action="set_label", mode="small", max_side=384)   // 默认
config(action="set_label", mode="off")                   // 完全不要预览图
config(action="purge_labeled", dry_run=true)             // 先看有多少历史预览图
config(action="purge_labeled", dry_run=false)            // 真删

关掉时插件返回 skipped: true 而不是报错 —— 「跳过」和「失败」必须分开, 否则每次生成都会挂一条假的"预览图失败"警告。

动画变体 · Variants(同款换配色,不重跑管线)

一个动画做好后,"红/蓝阵营 / 缴获版 / 受损版"是常见需求。逐变体重跑「锚点→视频→抽帧」的代价是 每变体几分钟 + 每变体细节都可能不一样。变体继承只改颜色:

spritesheet(action="variant", sheet="soldier_walk_loop_sheet.png",
            variants=[{name:"walk_blue", preset:"blue"},
                      {name:"walk_red",  preset:"red"}])

预设 red/blue/green/purple/yellow/captured/damaged/ghost;也可手调 hue/saturation/ brightness/grayscale/tint/palette。只改颜色,alpha 与几何一个像素都不动; 帧数/格子/帧率/时序全部继承源表,JSON 里记下 variant_of 与 recolor 配方。

限制:hue 是全局旋转,会连肤色/金属色一起带走。只换制服用 palette 精确指定颜色对。

动画装机 · Install animations(按格子缩放)

静态图按整图宽高缩放;动画必须按格子算倍率(5×2 的表按整图缩放会得到 185.6 像素的格子 → 切帧错位)。

install(action="anim-list", project="xxx")                       // 有哪些动画
install(action="anim", project="xxx", root="D:\游戏",
        cell_width=96, cell_height=176)                          // 默认 dry_run

宽高倍率不一致会拒绝(拉伸会变形)并给出建议尺寸。产出:缩放后的精灵表 + <名字>.anim.json(帧坐标按缩放后重算)+ 同名备份 .bak-before-install。

产出目录收敛 · Output layout

副产物太多分不清哪个给游戏用。现在四类:

类别去处游戏引用?
source(干净素材/精灵表)原地不动✅ 只有这类该被引用
preview(*_labeled.png)_preview/❌
derived(.post/.pixel/_slices/缩放件/.alpha)_derived/可能
intermediate(frames//*.cache.json/*.bak)_tmp/❌
assets(action="layout",   project="xxx")   // 清点
assets(action="organize", project="xxx")   // 演练,默认 dry_run

整理只动 preview + intermediate,source 一个都不动(被引用着,移动即断引用); derived 默认不动(切片可能就是游戏在用的单张),要动得显式 `include_derived=true