Repository navigation
Releases: yyfabab1/dsh-gpt-image
Release list
v1.11.0 · 面板加载 / 配音异步 / 引擎导出 / 多核换色
这个插件是什么
一个给游戏做素材的 DeepSeek Harness 插件。不是"AI 生图工具",而是一条链路:
一句话描述 → 生成(图/视频/音频)→ 机械自检 → 按游戏规格缩放 → 装进项目 → 登记进素材表 → 核对代码真的引用了
29 个工具 · 5 个侧栏面板 · 750 个测试 · 零运行时依赖
这一版让"用起来"更顺
面板不再转圈
画廊原来给每个格子发原图 —— 一张 1024×1024 PNG 约 700KB~2MB,200 个格子就是 140MB+,
而浏览器对同一域名只有 6 个并发连接:请求排队、页面长时间空白、部分超时。
现在:
- 缩略图端点按需缩放并缓存到磁盘(710KB → 52KB,省 92.6%,命中缓存 1ms),带长缓存头
- 素材扫描加 TTL 缓存(键里带目录 mtime,有增删自动失效,不会显示旧数据)
- 静态素材从"每次刷新重下"改成有缓存
配音不再像卡死
一句短台词要跑 60~90 秒,原来整个调用同步等(默认 900 秒上限)—— 界面完全没反应。
现在:提交立刻返回 job_id,进度可读(合成语音 · 45% · 已 12s),跑完一键收集产出
(status + collect)。面板改成"提交 → 显示进度 → 收集",跑多久都看得见。
精灵表能直接进引擎
新增 spritesheet(action="engine-export", engine="unity|godot|cocos"):
| 引擎 | 产出 | 关键处理 |
|---|---|---|
| Unity | .meta 多图切片 |
rect/pivot 的 y 翻成左下原点 |
| Godot | SpriteFrames .tres |
AtlasTexture 子资源 + 动画名/速度/循环 |
| Cocos | TexturePacker .plist |
frame 的 y 翻成纹理坐标 |
坐标系翻错就是"整套图错位",所以三种格式的 y 轴都按各自引擎的原点换算,并有测试钉住。
批量换色真吃多核
recolorSheet 是纯 CPU 的逐像素运算。主线程里"并发"只是排队(只快约 20%)。
搬进 worker 池后:6 个变体 2.53x、12 个 3.39x(4 核接近上限)。
三个约束:worker 结果与主线程逐字节一致(并行不改变语义,有测试);
单任务/worker 不可用时自动降级回主线程;池子常驻但不阻止进程退出。
视频限流不再白等
被上游限流(429)时,原来要等 28.8 秒才拿到失败。现在冷却期内秒失败并告知还要等多久;
冷却状态持久化,重启后仍然生效(不再重启又白等一次)。
余额查询有缓存
上游余额查询一次约 1.9 秒,连续看几次就白等。加 60 秒缓存(1348ms → 23ms),
fresh=true 可穿透。
体检合成一个工具
scan(缺什么)+ heatmap(哪些生成了没被引用)+ order(先画什么)回答的是同一个问题
——"我现在该做什么素材"。现在合成一个 audit,一次看完:
缺 1 项(另有 1 项建议补) · 零引用 13 项 · 建议分 4 批
工具数 31 → 29。
验证
发布前跑了 32 项端到端验证 + 全部单元测试:
- 缩略图:710KB → 52KB,缓存命中 1ms
- 配音:提交 233ms 返回 job_id,进度文字可读,collect 拿到文件
- 引擎导出:三种格式都生成并通过坐标系断言
- 换色:worker 结果与主线程逐字节一致;6 个 2.53x、12 个 3.39x
- 视频:首次 28.8s(真限流)→ 第二次 105ms
- 余额:1348ms → 23ms
- 750 个测试全过
安装
dsh plugin --profile web add @dsh-external/dsh-gpt-image或从 DSH 商店加索引源:
https://gcore.jsdelivr.net/gh/yyfabab1/dsh-gpt-image@main/market-index.json
v1.10.0 · 八面图相位 + 变体继承 + 动画装机 + 目录收敛 + 磁盘守卫
这个插件是什么
一个给游戏做素材的 DeepSeek Harness 插件。不是"AI 生图工具"(那种满地都是),而是一条链路:
一句话描述 → 生成 → 机械自检 → 按游戏规格缩放 → 装进项目 → 登记进素材表 → 核对代码真的引用了
中间每一环都有闸门:花钱的、风格的、尺寸的、磁盘的。
31 个工具 · 5 个侧栏面板 · 76 个模块 · 729 个测试 · 零运行时依赖
这一版新增的五个能力
1. 八面图:相位控制
问题:让模型绕角色转一圈的视频,直接取前 8 帧,第 8 帧≈第 1 帧(转回原点了),实际只有 7 个朝向。
做法:loop="auto" 先识别旋转周期,再在周期内均匀取 8 个点(不含末尾重复点)。
新增 phase(0~1)控制从哪开始 —— 归一化的,换视频不用改数字。
spritesheet(action="from-video", video="orbit.mp4", count=8, loop="auto", columns=8)2. 变体继承:同款换配色,不重跑管线
一个动画做好后,"红/蓝阵营 / 缴获版 / 受损版"是常见需求。逐变体重跑「锚点→视频→抽帧」的代价是
每变体几分钟 + 每变体细节都可能不一样。
变体继承只改颜色 —— 形状/动作/帧数/时序全部继承,秒级完成,变体之间除配色外逐像素一致:
spritesheet(action="variant", sheet="walk_sheet.png",
variants=[{name:"walk_blue", preset:"blue"},
{name:"walk_red", preset:"red"}])8 种预设;JSON 里记下 variant_of 与 recolor 配方,可复现。同配方自动去重(153ms → 24ms)。
限制:
hue是全局旋转,会连肤色/金属色一起带走。只换制服用palette精确指定颜色对。
3. 动画装机:按格子缩放,不是按整图
静态图按整图宽高缩放;动画这样干会得到 185.6 像素的格子 → 切帧必然错位。
install(action="anim") 按格子算倍率、整表乘同一倍率;宽高倍率不一致直接拒绝(拉伸会变形)并给建议尺寸;
产出缩放后的精灵表 + <名字>.anim.json(帧坐标按缩放后重算)+ 同名备份。
install(action="anim-list", project="xxx") // 有哪些动画
install(action="anim", project="xxx", root="D:\\游戏", cell_width=96, cell_height=176) // 默认 dry_run4. 产出目录收敛:哪个文件是给游戏用的
副产物太多分不清。现在按四类:
| 类别 | 去处 | 游戏引用? |
|---|---|---|
| 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 默认不动
(切片可能就是游戏在用的单张)。
5. 磁盘守卫:不再让磁盘悄悄塞满
这东西是被一次真实事故逼出来的:预览图一直是原尺寸,535 张吃掉 928MB,D 盘被塞到 0 字节可用,
而当时插件写盘失败的报错是 ENOSPC —— 完全看不出"是磁盘满了"。
现在写盘前先过一关:ok(≥5GB)放行 / low(1~5GB)先自动清理再复查 / critical(<1GB)拒绝并给修复步骤。
config(action="disk") // 看空间 + 各分类占用
config(action="disk", dry_run=false) // 一键清理可再生文件只删能重新生成的东西;源素材、精灵表、切片、配置一个都不碰。
另外两个效率改进
- 抠底改并发:粗筛帧 6 路、全尺寸帧 4 路(纯 CPU 的活不该串行)
- 结果缓存:同样的视频+参数第二次直接命中,692ms → 3ms
验证
发布前跑了 32 项端到端验证(从工具入口走完整链路,不看单元测试的脸色):
- 变体:生成成功、动画数据继承、来源与配方记录、同配方去重
- 动画装机:列出 7 个动画、dry_run 不写盘、格子 96×176、帧坐标重算正确、比例不一致被拒
- 目录收敛:清点四类、给出可清理体积、默认不动 derived
- 磁盘守卫:读空间、分级正确
- 边界:缺参数/路径错/未知 action 全部明确报错,不崩
729 个单元测试全过(新增 16 个:相位偏移、变体、装机、目录收敛、磁盘守卫)。
商店安装
DSH 商店读的是市场索引。加这个源:
设置 → 插件 → 发现(Discover)→ 添加插件源
https://raw.githubusercontent.com/yyfabab1/dsh-gpt-image/main/market-index.json
或者直接用命令装:
dsh plugin --profile web add @dsh-external/dsh-gpt-imagev1.9.0 · 两段式抽帧 + 结果缓存
1. 两段式抽帧(少干 80% 的活)
autoLoop 要抽 count×5 帧才够找周期,但只用 count 帧。老做法全抽出来 → 全抠底 → 丢掉 80%:
要 10 帧就白写 50 张全尺寸 PNG、白抠 50 次。
现在:
① 粗筛 count×5 帧 @160px 找周期(指纹本来就 16×16,小图够用)
② 一次 ffmpeg select 调用,只把那 count 帧按全尺寸抽出来
③ 只抠这 count 帧
④ 粗筛目录用完即删
磁盘写入 100 个文件 → 10 个,抠底 50 次 → 10 次。
2. 结果缓存
同样视频 + 同样参数第二次直接命中:692ms → 3ms(快 230 倍)。
判重看视频路径 + 字节数 + mtime + 参数。force=true 强制重跑。
3. 修一个真 bug:周期比帧数短会出重复帧
想从 8 帧周期取 10 帧,重采样必然重复 —— 实测要 10 帧只抽出 8 个文件,精灵表少两格。
现在按周期封顶并明确告知。想要更多帧得提高抽帧密度,不能硬凑。
712 个测试 / 31 工具 / 38 测试文件 / 72 模块
v1.8.2 · 预览图开关(*_labeled.png 不再堆积)
问题
每张素材都配一张「画面里印名字」的预览图。但它一直是原尺寸 —— 1024×1024 素材的
预览图也是完整 1024×1024 PNG(约 1MB)。实测攒了 535 张、928MB,把磁盘塞满。
三档开关
| mode | 效果 | 体积 |
|---|---|---|
small(默认) |
缩到最长边 maxSide(默认 384) | 约 2~3% |
on |
原尺寸(旧行为) | 100% |
off |
完全不生成 | 0 |
实测 1248×832 素材:on 939KB → small 124KB → off 跳过。
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) // 真删为什么默认是 small 不是 off
预览图本身有用(素材多了要认图),但没必要用原分辨率。缩到 384px 名字照样清晰,
体积只剩 2~3% —— 既不丢功能,也不再吃磁盘。
关键设计:skipped ≠ error
关掉时返回 {ok:false, skipped:true},上层不记进 label_errors。
否则每次生成都会挂一条假的"预览图失败"警告 —— 用户主动关掉的功能不该报成故障。
修了两个 bug
- label.ps1 里写了中文注释 → PowerShell 5.1 读无 BOM UTF-8 中文直接崩
(所有预览图生成失败,报 no output)。这文件开头就写着"必须纯 ASCII",是我自己犯的。 - tools.js 用了 stat / rm 却没 import → purge_labeled 报 stat is not defined。
和之前 buildRig 那次同一类 bug。
710 个测试 / 31 工具 / 38 测试文件 / 72 模块
v1.8.1 · 抽完帧自动删视频 + 清理 1GB 垃圾
抽完帧就删视频(默认)
spritesheet(action="from-video", video="walk.mp4", count=10, loop="auto")
// 默认行为:抽完帧 → 删源视频 → 删未抠底的原始帧 → 只留 .alpha.png + 精灵表 + JSON
// 想留:keep_video=true / keep_raw_frames=true只在全部成功后删:找不到 ffmpeg、抽帧失败、抠底失败、打包失败 —— 任何一步失败都
保留原文件。否则视频没了、帧也没成,等于丢素材。
结果里带 cleaned 字段,报告删了什么、省了多少 MB。
清理了 1GB(本机)
| 删了什么 | 数量 | 大小 |
|---|---|---|
*_labeled.png(印着素材名的预览图,游戏用不到) |
535 个 | 928 MB |
p_soldier_* 老逐帧尝试 |
全部 | 19 MB |
topdown-demo 程序化演示 |
整个 | 9 MB |
| 旧版本 zip v1.0.1~v1.5.0 | 7 个 | 4.4 MB |
| ComfyUI 旧输出视频 | 20 个任务 | 40 MB |
doomsday-ember-sprites 416MB → 221MB。源素材和成果全部保留。
留了个提醒:这 928MB 是插件自己产生的预览图。如果你经常批量生成,
这类文件会一直堆积 —— 以后可以定期清,或者我在工具里加个「不生成预览图」的开关。
705 个测试 / 31 工具 / 37 测试文件 / 72 模块
v1.8.0 · 锚点 + 自动循环 + 八面图(实测跑通你的士兵)
实测:图 → 视频 → 抽帧,成立
用 doomsday-ember 的 idle_soldier_1.png 真跑了一遍,全部数字都是实测:
| 产物 | 帧数 | 精灵表 |
|---|---|---|
| 走路(自动循环) | 10 | 925×680,每格 185×340 |
| 攻击 | 8 | 1360×652,含枪口闪光 + 后坐 |
| 八面图 | 8 | 1792×335,每格 224×335 |
病根消掉了:所有帧都从同一张锚点图派生,不会再出现"同一单位长得不一样"。
新工具能力
// ① 从素材表裁出一个单位 → 视频首帧
spritesheet(action="anchor", image="素材表.png", unit_index=0, target_width=608, target_height=352)
// ② 图生视频(本地 MiniMax H3)后,自动找循环周期并重采样
spritesheet(action="from-video", video="walk.mp4", count=10, loop="auto", columns=5)
// ③ 八面图:环绕视频 → 等距 8 帧
spritesheet(action="from-video", video="orbit.mp4", count=8, columns=8, loop=false)★ 八面图:别用"画一张八方向表"
试过,失败:让图模型画 8 个均匀旋转视角的转身表,它给 10 个角色但只有约 5 个不同
朝向(正视/背视/右侧反复),没有左侧、没有后 3/4。和图模型画"零件盘"同一类失败——
看起来像,但不按规格来。
有效做法:让视频模型绕角色转一圈,再等距抽 8 帧。 视频是连续插值的,角度天然均匀;
同一个 i2v 首帧保证外观统一。于是八面图 = 一次 from-video。
循环接缝
直接取前 N 帧会顿一下(末帧接不回首帧)。loop="auto" 先多抽(count×5,上限 60)→
用 16×16 灰度指纹找"和首帧最像"的那帧 → 把那一轮重采样成目标帧数。
实测走路周期 28 帧 → 重采样 10 帧,循环顺了。
三条真实限制(写进文档了)
- 本地视频要显存:RTX 5070 8GB +
--lowvram,608×352×124帧 ≈ 2 分钟 - 背景必须接近纯色(i2v 照着首帧背景画);实景背景抠底会失败,工具会明确拒绝
- 动作幅度受首帧限制:擅长"原位动",不会跑出画面 —— 位移交给游戏代码
704 个测试(新增 anchor 6)/ 31 工具 / 37 测试文件 / 72 模块
v1.7.0 · 多帧动画:视频抽帧 → 精灵表
研究结论:逐帧生图做不到连贯
这是结构性的,不是参数没调好:每帧都是独立采样,没有任何机制把"第 2 帧"约束到"第 1 帧"附近。
加 seed 只锁初始噪声,加参考图只锁外观不锁姿势与时间。
正确路线:用视频模型生成一段动画,再抽帧。
时间连贯性是视频模型训练目标的一部分(相邻帧在隐空间必须靠近,否则视频会闪)—— 连贯性是白送的。
这也是圈内主流做法(SpriteMaster / AutoSprite / video2sprites / ComfyUI sprite-sheet 工作流 /
scenario.com / sorceress.games),多来源一致。
spritesheet(action="info", video="D:\动画\walk.mp4")
spritesheet(action="from-video", video="D:\动画\walk.mp4", count=8, columns=4, scale=2, name="walk")
spritesheet(action="from-frames", frames=[...], fps=12)
spritesheet(action="from-rig", rig_config={project,name}, animation="walk_td")五个必须做对的细节(都踩过)
- ★ 所有帧用同一个 union 包围盒 —— 逐帧各自紧裁再居中会让角色每帧位置微变 → 播放抖动。
这是 AI 视频转精灵表最常见的翻车点。测试里有专项:三帧色块移动,打包后色块中心必须递增 - 抠底从边缘泛洪,不能全图颜色匹配 —— 否则白底上的白牙、黑底上的黑靴都会被挖成洞
- 边缘颜色不统一时拒绝抠 —— 硬抠会挖洞,不如说清需要分割模型
- 循环动画丢掉与首帧重复的末帧 —— 否则播完会多停一帧
- 帧率不要照搬视频原帧率 —— 游戏 sprite 8~12 帧就够,全抽出来只是文件大 10 倍
为什么这条路比拆件骨架更现实
之前那条(AI 画可拆部件 → 骨架驱动)上游就断了:实测 AI 画出来的是一整个连体士兵
(拆不开),换网格零件盘它把"上臂/前臂"理解成四顶帽子。
但这不是替代关系,是分工:
| 视频抽帧 | 拆件骨架 | |
|---|---|---|
| 连贯性 | 视频模型保证 | 程序保证(数学必然) |
| 美术上限 | 高 | 取决于分件质量 |
| 能否改动作 | 只能重生成 | 改关键帧即可 |
| 上游风险 | 模型要能生成横向动作 | AI 画不出可拆部件 |
放弃的是"让 AI 直接画出可拆部件"这个假设,rig 仍适合程序生成的素材。
修的 bug
findFfmpeg 用深度优先扫描 → D:\ 有 167 个顶层目录,DFS 一头扎进某个子树把配额烧光,
浅层的 ffmpeg(剪映/ComfyUI 里各一个)根本没机会被检查(实测 39ms 就返回"未找到")。
改按层 BFS + 时间预算 + 缓存,配额从 6000 放宽到 80000。
文档
docs/多帧动画怎么生成.md —— 结论、失败模式、四条补充路线对比、五个细节、数据来源。
698 个测试(新增 spritesheet 11)/ 31 工具 / 36 路由 / 71 模块
v1.6.0 · 正俯视士兵 + 俯视动作组 + 帧渲染器
正俯视模板(7 件)
从头顶垂直往下看:腿被身体挡住,看得见的只有身体(肩背)、头盔顶、两条上臂、两条前臂、枪。
所以是独立模板 + 独立提示词(写清"不要画腿/不要画脸/画面上方=前方")+ 独立几何命名。
俯视动作组(8 个)
待机 / 走动 / 奔跑 / 举枪瞄准 / 开火 / 装填 / 受击 / 死亡
关键差异:腿看不见,所以走路不能靠迈腿 —— 只能靠身体起伏 + 肩膀左右摆 + 胳膊轻晃。
照搬前视的迈腿动作,玩家看到的是"原地滑动的躯干"。
软件帧渲染器(lib/raster.js)
Node 里直接出图,不用开浏览器:
- 联系表(多帧一张,带分隔线)→ 一眼看动作对不对
- 精灵表(等大横排)→ 游戏直接切帧
- 单帧静帧
rig(action="render", render_kind="sheet|sprite|frame")
程序化分件(scripts/procedural-topdown-soldier.mjs)
被实测逼出来的:让 AI 画俯视分件,它给的是一个连得很好的完整士兵(好看但拆不开;
Agnes 不支持透明底);换网格零件盘,它把"上臂/前臂"理解成四顶帽子。两条路都不可用。
所以程序画一套:几何可控、部件两两不重叠(有显式布局校验)、朝向统一。
同时是实用的占位素材。
修 4 个真 bug
- 采样只挡"超出图集"没挡"超出本部件矩形" → 会采到邻居部件像素,同一部件被复制到别处
- 裁剪用局部偏移和 [0,w] 比 → 每个部件只画 1/4(头盔成四分之一圆)
walk_td用了hip但俯视模板只有body→ 通道被静默丢弃(测试抓到)- 俯视命名把腿当成 hip(细长竖条不可能是髋部)
687 个测试 / 30 工具 / 36 路由 / 70 模块
v1.5.0 · 动作动画(用人话生成关键帧)+ 双向编辑 + 片段背包
动画:核心判断是「AI 不编关节角度」
让模型生成举枪动作,角度十次有八次肢体折断 —— 不是模型笨,是**「美术调过的角度」不在语言模型里**。
所以分工改成:
AI 只做「理解你要什么动作」+「给参数」,角度由数据化动作库提供。
检索和参数化这两件事 AI 是可靠的。和代码表同一个道理:不确定性收进数据。
rig(action="motion") // 13 个动作
rig(action="animate", prompt="举枪攻击") // ← 直接说人话
rig(action="animate", prompt="我要他快速跑起来") // 自动读出 speed 1.5
rig(action="animate", prompt="左手挥一下") // 自动读出左右镜像
rig(action="preview") // 预览器左侧时间轴:播放 / 拖动内置:待机 · 走动 · 奔跑 · 举枪瞄准 · 开火 · 举枪攻击 · 挥砍 · 投掷 · 受击 · 死亡 · 跳跃 · 挥手 · 蹲下
三个设计要点:
- 组合动作:「举枪攻击」= 抬枪瞄准 → 开火后坐 → 收枪(三段拼起来)。
只播「开火」会变成手已经举着凭空抖一下 - 单字别名必须配排除表:
跳一定会被「跳舞」误命中,所以jump排除跳舞/跳绳/跳槽,
wave排除挥发/挥霍/指挥,die排除死机/死锁 - 匹配不上就如实说不上并给候选,绝不硬猜一个动作
预览器的采样器和 lib/motion.js 用同一套数学(否则预览看到的和引擎跑出来的不是同一个动作)。
校验会抓两类静默错:通道写了不存在的骨骼(引擎静默忽略)、关键帧时间不单调或超出 0~1(动作抽搐)。
双向编辑:只改目标行
那些模组编辑器的硬要求 —— 改一个数值不能把注释/缩进/空行/@指令全部重排。
保留:整行注释、行尾注释、缩进、冒号后的空格风格、空行、@指令、换行风格(CRLF/LF)。
有测试钉住往返恒等式:parse → serialize 逐字节等于原文。写盘前自动 .bak-before-edit,默认 dry_run。
修的 bug:第一版改值会把行尾注释吃掉 —— 一个声称「保留原文件」的功能丢数据,比不提供更糟。
背包:可复用配置片段
adapter(action="snippet-save", schema_id="rusted-warfare", name="坦克基础",
section="core", values={ maxHp: 500, radius: 14 })
adapter(action="emit", schema_id="rusted-warfare",
snippets=["坦克基础"], values={ core: { name: "mini_tank", maxHp: 600 } })片段先铺底、你给的值覆盖它 —— 因为最常见用法就是「用这套模板但把血量改成 600」。
(第一版顺序写反了,片段盖掉用户值,等于每次都要再改一遍。)
工程
687 个测试(新增 motion 17 + adapter 往返/背包 5)/ 30 工具 / 36 路由 / 69 模块 / 35 测试文件
v1.4.0 · 引擎适配层:素材 → 引擎真的能读的配置
参考铁锈战争生态的几个开源模组编辑器(RustedWarfareCodingTool / RustAssistantRebuild / RustedWarfare-ModEditor)。
学到的核心
它们不靠堆代码,靠一份数据化的「代码表」:538KB,段 → 分组 → 字段,
每字段带 代码名 / 中文名 / 说明 / 示例 / 类型。这份字典同时支撑:
| 用途 | |
|---|---|
| 生成 | AI 按字典填,不会瞎编字段名 |
| 校验 | 类型/必填/范围/枚举/引用可判定,带稳定错误码 |
| 文档 | 中文名+说明直接是表单标签和悬浮提示 |
| 补全 | 输入时按字典补 |
字典不数据化,这套东西就只能手写 if-else —— 游戏加个字段就要改代码。
我们的实现
adapter(action="import-schema", code_table="D:\rw_code_table.json", schema_id="rusted-warfare")
adapter(action="schema", schema_id="rusted-warfare", section="core")
adapter(action="emit", schema_id="rusted-warfare", values={ core:{ name:"ai_tank", maxHp:260 } })
adapter(action="lint", schema_id="rusted-warfare", file="D:\mod\units\tank.ini")
adapter(action="health", schema_id="rusted-warfare", game_root="D:\mod")实测导入真实代码表:21 段 / 1305 字段 / 43 枚举 / 69 取值词表 / 284 内置素材。
14 个稳定错误码 E001–E014:缺段/缺字段/类型/越界/枚举/中文冒号/中文逗号/重复键/重复段/悬空引用/颜色/放错段/语法。
一条设计红线:校验器的价值由误报率决定
所以 schema 有 tolerant 不误报清单(${...} / self.xx / max(0,sin(...)) / delay: 2.2s 这类真实写法命中就放过),
health 把引擎内置素材当白名单。假警报会让人直接关掉校验器,那它等于不存在。
不 vendor 别人的数据
只提供导入通道 scripts/import-code-table.mjs(命令行)或 adapter(action="import-schema")。
体积 / 许可 / 版本漂移都不适合把 500KB 数据抄进仓库。
修 4 个真 bug
parseIni用kv[0].slice(-1)判中文冒号 —— kv[0] 是整行(含值),取到的是值的最后一字 → E007 永不触发emitConfig没调attachEnums→ 枚举校验完全失效,非法值原样写进配置fieldIndex无条件重 normalize → 造新字段对象,枚举缓存全失效(加幂等守卫asNormalized())- 静态 import 漏
readdir→adapter(action="list")直接报错
666 个测试(新增 adapter 23)/ 30 工具 / 36 路由 / 68 模块 / 34 测试文件