Quality Merger / 品质融合器


Merge N same-quality items into 1 item of the next quality tier - deterministically, no dice roll. Chain four machines to turn 81 normal items into 1 legendary item. Recipes are generated only for items that actually benefit from quality. Specs: 5x5, crafting speed 2, 2 MW, 12 module slots. --- 上帝不掷骰子

Content
2 months ago
2.1
108
Logistics Manufacturing

Changelog

Version: 2.10.2
Date: 16. 09. 2026
  Major Features:
    - **落地投影调参**(用户反馈 2.10.1「投影偏移有些多了,颜色再深些激进点」)。
      2.10.1 为了「让影子跑出机身之外」把甩出量推到 +0.95/+0.84 tile,属于矫枉过正:
      影子离机器太远、不像压在脚下,而且甩出去那部分偏灰。
      量化(vis = 阴影alpha × (1 − 机身alpha/255),被机身遮挡后**真正看得见**的部分):
          2.10.1  vis>=120 右超 0.79 / 下超 0.64 tile,vis>=60 4.94 tile²,影核 alpha 0.66(≈168)
          2.10.2  vis>=120 右超 **0.59** / 下超 **0.47** tile,vis>=60 3.54 tile²,
                  影核 alpha **0.92(≈235)**
      改法(常量全在 tools/gen_graphics.py 的 SH_*):
        · 甩出量 (122,108) → **(82,70)** 纹理px = 右下 +0.64 / +0.55 tile(缩约三分之一);
        · 强度加深:接触影 0.80 → **0.92**、方向投影 0.66 → **0.90**;
        · 高斯半径收小:σ1 20 → **16**、σ2 22 → **16** —— 影核更实、边缘更利落;
        · 方向性渐隐斜坡左上端 0.40 → **0.22**(左上侧压得更暗,明暗对比更硬);
        · 合成 add → **max**:两个 0.9 量级的实核相加必然饱和成死黑平台(死黑占比判据会超 25%),
          改 max 后峰值 0.92 → alpha 235,又黑又保留层次(实测死黑 0.0%)。
  Changes:
    - `tools/check_graphics.py` 第 3g 节的偏移判据由**单边下限**改成**区间**:
      vis>=120 右超 0.45 ~ 0.72 tile、下超 0.35 ~ 0.58 tile。上限就是这次用户反馈的固化 ——
      2.10.1 的 0.79/0.64 会直接被判「甩太远,影子离开机器」。
    - 机身 / core / halo 三张贴图**一个像素都没动**(重新生成前后 CRC32 逐一对得上:
      BE03A95F / 6D897C8D / 98B71AC7),只重出了 quality-merger-shadow.png
      (C5709FED → 04CD41AF,77644 → 68581 B)。
    - 新增 `tools/check_dump.py`:把「真机结构核对」固化成脚本 —— 静态校验只保证源码写对了,
      引擎还会做字段搬迁/默认值填充。它从 `data-raw-dump.json` 里断言 `animation.layers` 的
      层数与 `draw_as_shadow`、`working_visualisations[].apply_recipe_tint`、兜底 tint,
      以及 632 条生成配方的 `crafting_machine_tint` 与 base 品质色**逐值**一致。
    - 顺手订正 changelog 里 2.9.0 / 2.10.0 / 2.10.1 的日期(17. 09. → 16. 09.,与文件时间戳一致)。
  Text / 文案:
    - **精简游戏内说明文本并补齐英文**(用户要求:一般玩家没必要知道的内容去掉、文本以
      中文为主、再按中文做英语本地化)。原来的设备说明把「配方生成政策」也塞了进去;
      设置说明里还有一句会误导人的「默认约 3」—— 那是**引擎默认公式**的值,而本设置
      自己的默认值是 20,放在一起看必然读错。
      现在:设备说明只留「做什么 + 怎么做 + 结果确定」;配方生成政策只留在设置说明里;
      「机器原料缓冲量」改成「原料缓冲量 = 每次融合所需数量 × 此值」这种可核对的表述。
    - 新增 `[mod-description]` 段(中 / 英各一份),游戏内 mod 列表按客户端语言显示简介。
      之前简介只存在于 `info.json` 里,且是英文的 —— base 的 `quality` / `elevated-rails`
      就是这么给 mod 简介做本地化的。
    - `info.json` 的简介改成**中文为主**,英文作为本地化版本放在 `---` 之后。
    - `tools/check_locale.py` 新增「**多语言键一致性**」校验:补中文时忘了同步英文,游戏会
      **静默退回英文原文**(界面上孤零零一条英文,日志不报);而原有的「键是否存在」只管
      Lua 显式引用的键,`mod-setting-name.*` / `mod-description.*` 这类引擎**按约定隐式取名**
      的键完全管不到。漏翻按警告列出,自带三种情形的自测,并做过真实数据反例测试
      (删 2 条漏翻 → 精确报 2 条)。
Version: 2.10.1
Date: 16. 09. 2026
  Major Features:
    - **重做落地投影的几何**(用户反馈 2.10.0「投影很不明显」)。
      诊断结论**不是**渲染没生效,而是 2.10.0 拿**机身自己的剪影**当投影形状,
      而机身底座占满 5x5 贴图的 ~88% ⇒ **85~90% 的投影被机身自己盖住了**。
      量化(vis = 阴影alpha × (1 − 机身alpha/255),即被遮挡后**真正看得见**的部分):
          2.10.0  vis>=60 仅 1.99 tile²,bbox 相对机身只多出 右 0.27 / 下 0.06 tile
          2.10.1  vis>=60   4.82 tile²,vis>=120 右超 **0.79** / 下超 **0.64** tile
      改法(常量都在 tools/gen_graphics.py 的 SH_*):
        · 画布 896 → **1024**(8 tile),给「远远甩出去」的投影留地方;
        · 方向投影的甩出量 (58,38) → **(122,108)** 纹理px = 右下 +0.95 / +0.84 tile;
        · 方向投影 σ 34 → **22**、强度 0.40 → **0.66** —— 要**实核**不要糊尾巴;
        · 接触阴影 σ 12 → 20、强度 0.85 → 0.80;
        · 新增**方向性渐隐**:接触影沿 (1,1)/√2 轴乘斜坡,左上 ×0.40 → 右下 ×1.00
          (±2 tile 线性过渡)—— 别让机器左上方糊出一圈物理上说不通的暗边。
  Changes:
    - ⚠️ **验收口径换了**:`tools/check_graphics.py` 第 3g 节不再看原始投影 bbox,
      改量**遮挡后**的 vis(上面那个公式)。2.10.0 报的「右超 137px / 下超 112px」
      是原始投影的量,不是用户看得见的量 —— 这就是本次「投影很不明显」的盲区。
      新判据:vis>=120 右超 ≥0.70 / 下超 ≥0.55 tile、vis>=60 面积 ≥2.8 tile²、
      上半三扇区 < 右下三扇区的 15%、死黑(alpha>=250)占比 ≤25%。
    - `SH_DY` 取 108 而不是规格给的 90:DY=90 实测「vis>=120 下超」只有 0.50 tile,
      够不到 0.55(机身纵向 468px 比横向 562px 短,同样平移量占比更小)。
    - 机身 / core / halo 三张贴图仍然**一个像素都没动**(CRC32 与 2.9.0 逐一对得上)。
Version: 2.10.0
Date: 16. 09. 2026
  Major Features:
    - **新增落地投影**(用户要求:「机器在地面上有一个阴影增加立体感」)。
      之前只有机身**自身**的明暗(2.9.0 的 `MT_*` 方向光 + 接地渐暗),
      没有**投在地面上**的影子,游戏里看着像浮在土上。
        · 用的是**引擎原生机制**:在 `graphics_set.animation.layers` 里加一条
          `draw_as_shadow = true` 的层(真机 dump 实测:原版 assembling-machine-1/2/3、
          foundry、cryogenic-plant、biochamber 全同构;`assembling-machine` 上没有别的
          shadow 字段可用)。
        · 新贴图 `graphics/entity/quality-merger/quality-merger-shadow.png`(896x896):
          机身轮廓去掉悬空的球 → 最大连通域 → ① 原地 σ12 ×0.85 贴脚接触阴影
          + ② 右下平移 (58, 38) 纹理px 后 σ34 ×0.40 的方向投影,合成用 min(255, S1+S2)
          (与实测的原版阴影一致:峰值 alpha 255、形状实心)。
        · 贴图 **RGB 恒黑、形状全在 alpha**(原版阴影就是这么画的);
          scale 0.25、周期 1 x 240 —— 与机身层完全一致,两画布同心,不需要 shift。
        · ⚠️ 阴影层放在 `animation.layers` 而不是 `working_visualisations` ——
          后者会被配方的 `crafting_machine_tint` 染色,阴影就成彩色的了。
  Changes:
    - 阴影画布取 **896x896**(不是机身的 640,也不是 768):实测 768 会把影子下缘裁掉
      (边界 alpha = 114)、832 也差一点,896 才让四条边界 alpha 全部归零。
      896 = 7 tile,仍是 128 的整数倍,方便与 5 tile 的机身同心对齐。
    - 校验脚本同步更新:
        · 新增 `tools/check_graphics.py` 第 3g 节(落地投影):纯黑(RGB ≤ 2)、alpha 峰值 ≥ 200、
          四条边界 alpha 全 0(没被裁)、影子 bbox 的右/下超出机身 bbox、
          alpha 加权质心落在机身质心的右下方;
        · `tools/package.py` 必需文件清单加入 shadow.png,尺寸 / 周期校验自动覆盖新层
          (现在是 4 层,共同周期仍是 240);
        · `tools/lua_layers.py` 新增解析 `draw_as_shadow`;
        · `tools/_layout.json` 记录 `shadow_canvas` / `shadow_body_offset`。
    - 机身 / core / halo 三张贴图**一个像素都没动**(zip 里的 CRC32 与 2.9.0 逐一对得上)。
Version: 2.9.0
Date: 16. 09. 2026
  Major Features:
    - **球体配色改为「跟随执行的配方」**(用户要求:气泡颜色要根据执行的配方变化)。
      两条融合配方的品质跃迁现在驱动机器的球体颜色:
        · 每条融合配方都带 `crafting_machine_tint = { primary = 原料品质色, secondary = 产物品质色 }`
          (颜色直接取品质原型自己的 `color`,别的 mod 改过品质色也能跟着走);
        · 球体两层从 `animation.layers` 搬进**两条 working_visualisation**,
          分别用 `apply_recipe_tint = "primary" / "secondary"` 取用 ——
          **内圈 = 原料品质色、外圈 = 产物品质色**(例如「稀有 → 史诗」= 内蓝、外紫);
        · 特效贴图(core / halo)改成**纯白强度图**(RGB 恒白、亮度全在 alpha)——
          tint 是乘法,贴图带颜色会把颜色乘浑。原来做在 RGB 上的外圈压暗已搬进 alpha。
        · 没选配方 / 配方没带 tint 时退回白色(`default_recipe_tint` / `recipe_not_set_tint`),
          不再出现「和配方无关的固定色」。
    - 由此顺手解掉用户上一版提的**「气旋的内圈和外圈变化不同步」**:
      旧版两层共用一个随时间循环的配色,时间一长看起来"不同步";
      现在内圈取原料品质色、外圈取产物品质色,**本就该不同**,无需同步。
    - **机身(球体以外的金属区)补上阴影**(用户要求:机身 / 底座也加阴影):
      用「像素位置沿主光方向的投影」近似方向光(左上亮)+「越接近地面越暗」的接地渐暗,
      光源方向与球体一致(`gen_graphics.py` 的 `MT_*` 常量)。球体本身的球面着色不变。
    - 打包时**不再把 `README.md` 放进 zip**(用户要求)—— 它是仓库自带的开发文档,游戏用不到。
      (只排除文件名恰好是 `README.md` 的那一个,不是所有 `*.md`。)
  Changes:
    - 帧数 / 周期不变:core 30 帧 x8、halo 48 帧 x5、机身 1 帧 x240 —— 三层周期仍是 **240**(16.0 秒)。
    - 校验脚本同步更新(tools/check_graphics.py):
        · 第 3b 节改为**纯白强度图**判据(RGB 三通道两两差 < 8 且均值 > 240;alpha 必须真的在变);
        · 第 3b2 节改为断言三层周期仍是 240,并静态查 machine-graphics.lua 的
          `apply_recipe_tint` 接线是否恰好一个 primary + 一个 secondary(不再要求两层同色);
        · 第 1 节(金属区保真)改看**色度**(金属被阴影乘过,绝对值已变,色度才反映偏色);
        · 新增第 3f 节:从成品机身层反查金属区的方向光 + 接地渐暗。
Version: 2.8.0
Date: 16. 09. 2026
  Changes:
    - 气旋配色改为**随时间循环**:白 → 绿 → 蓝 → 紫 → 橙 →(平滑回到白)。
      原来是固定的橙红(沿半径从炽白渐变到橙红,不随时间变)。
      改法是把「沿半径的亮→暗」与「随时间的主色」解耦:中心 = 偏白高光里掺 45% 主色,
      中段 = 主色,外圈 = 暗主色;主色由环形插值函数给出(第 5 档橙平滑过渡回第 1 档白,
      不闭环的话每轮结束会跳色)。core / halo 两层共用同一个调色函数。
    -       核层图案帧数 20 → **30**(配色渐变需要更多采样点;60 帧会让相邻帧差异掉到阈值以下)。
      三层周期仍旧全部 = 240 帧(30 x 8 = 1 x 240 = 48 x 5),整体仍是 16 秒一轮;
      配色每 16 秒走完一轮五色(每档 3.2 秒),能量核与光晕**同步换色**(两者共用同一条
      240 槽位的动画帧轴,repeat_count = 每个源帧占几个槽位)。
    - 图标 / 缩略图固定用**第 0 帧**合成(原来取 0.17 倍帧号那一帧,改色后那帧是绿的)。
Version: 2.7.0
Date: 15. 09. 2026
  Changes:
    - 解锁科技大幅提价:红 / 绿 / 蓝科学包各 1000 瓶(原 100 瓶)。
    - 设备合成配方改为 **81 个品质插件 2**(81 = 3^4,呼应「81 个普通 → 1 个传说」),
      不再需要装配机 2 / 处理单元 / 电子电路 / 钢板。
    - 科技前置跟着配方一起升级为解锁 quality-module-2 的科技(原来是一级 quality-module)——
      否则会出现「配方解锁了却造不出东西」。找不到二级时依次退回一级品质模块科技 / 电子学。
Version: 2.6.4
Date: 15. 09. 2026
  Changes:
    - 精简所有玩家可见的描述文本(info.json 简介、机器描述、开局设置说明)。
      删掉游戏里已经能看到 / 无关玩家操作的内容:机器规格(占地 / 速度 / 耗电 / 模块槽,
      引擎会自己显示)、模块槽收什么、最高品质无配方、美术描述(橙色漩涡…)等。
      只保留三件事:干什么、怎么用、结果确定。
Version: 2.6.3
Date: 15. 09. 2026
  Bugfixes:
    - 修「2.6.2 逻辑单测通过,装进游戏机甲配方里**仍然**有 power-armor-mk2 x2」。
      根因(很隐蔽):`data.raw` 里 **recipe 桶与 item 桶共用名字空间** ——
      `data.raw.recipe["power-armor-mk2"]` 和 `data.raw.armor["power-armor-mk2"]` 同时存在。
      原先的 `find_proto()` 遍历 `pairs(data.raw)` 取**第一个**同名条目,而 `pairs` 顺序未定义,
      很可能先命中 **RecipePrototype** —— 它没有 equipment_grid / inventory / not-stackable,
      于是被 QR.unstackable() 判成「可堆叠」→ 该原料没被丢弃,照旧进配方。
      现在 `find_proto()` **只认物品类桶**(item / armor / module / ammo / gun / capsule /
      item-with-entity-data / repair-tool / …),recipe 桶一律不看。
    - 这类 bug 单测抓不到:Python 模拟脚本里用的是「只装物品桶」的字典,天然不会撞到 recipe 桶。
      所以从本版起**必须用真机 `--dump-data` 的日志做端到端验证**(见 README「验证」一节)。
    - 日志新增一行「已丢弃的不可堆叠原料(物品(所在桶))」,期望看到
      `power-armor-mk2(armor)`;若出现 `(recipe)` 即表示桶过滤失效。
Version: 2.6.2
Date: 15. 09. 2026
  Changes:
    - 按用户要求:**不可堆叠物品的升级配方里,不再包含原配方中的不可堆叠原料及其原材料**。
      机甲(mech-armor)是典型例子 —— 它的原配方含 1 个 power-armor-mk2(带装备网格的护甲)。
      2.6.1 曾「递归展开」动力甲MK2 的子配方(把它的处理单元 / 电动引擎 / 模块等都塞进机甲配方),
      用户不要这种形态。现在改为:不可堆叠原料**整条丢弃**,额外材料只取原配方里的**可堆叠**原料 × (N-1)。
      例:机甲(不常见) = 1 机甲 + 400 钬板 + 200 处理单元 + 100 超导 + 100 超级电容(不再有动力甲MK2)。
      代价:这条配方比严格「N 倍价值」便宜一点(少了一份护甲原料),但路径干净、能自动跑、符合预期。
    - 日志新增一行「示例原料」,直接打印机甲(或动力甲MK2)一条配方的完整原料清单,便于核对。
Version: 2.6.1
Date: 15. 09. 2026
  Bugfixes:
    - 修「机甲(mech-armor)的配方有问题」(用户反馈)。
      原因:2.6.0 按「原配方原料 × (N-1)」自动补材料,但机甲的原配方里**本身就有一个
      power-armor-mk2**(同样是带装备网格的不可堆叠护甲)。它被 ×2 后塞进配方,机器又要求
      缓存 2 个动力甲MK2 → 机械臂照样卡死 = 没修好。
      修复:展开额外材料时,**不可堆叠原料只保留 1 份本体**,把 ×(N-1) 的那部分成本改用
      「它自己的原配方原料」递归替代(含环检测 + 深度上限)。这样配方里绝不会出现 >1 个
      不可堆叠物品,成本仍然等价。
      例:机甲(传说) = 1 机甲 + 1 动力甲MK2 + 动力甲MK2 的材料 + 机甲其余材料 ×2。
Version: 2.6.0
Date: 15. 09. 2026
  Changes:
    - 重做「堆叠上限为 1 的物品」的融合配方(护甲 / 装备类,用户要求:不能是 1 合 1,那等于作弊)。
      真正根因(前两版都没对上):引擎把**带装备网格 / 带库存 / not-stackable** 的物品判定为
      「不可堆叠」——组装机为了配合机械臂,总是要求缓存「2 × 配方量」,而这类物品连 2 个都塞不进
      同一个输入槽,机械臂塞第 2 个时卡死,机器永远凑不齐原料、不合成。
      注意这与「stack_size == 1」不是一回事:stack_size=1 但没有网格/库存的物品引擎会为配方扩容,
      N 合 1 没问题;只有带网格/库存的才是真不可堆叠。
      新做法:不可堆叠物品的配方 = 「1 个本体 + 原配方的 (N-1) 倍材料」(材料按普通品质参与)。
      既保住了 ~N 倍的成本(不是白送),机器又只收 1 个本体 → 不卡死、能自动融合。
      配方名相应改为「1 个 + __4__ 种材料」;普通可堆叠物品仍是 N 合 1(默认 3 合 1)。
    - 日志新增一行「不可堆叠物品」统计,并列出「拿不到额外材料(退化成 1 合 1)」的物品名,
      方便核对(例如原配方只含流体、或该物品没有自己的制造配方)。
Version: 2.5.3
Date: 15. 09. 2026
  Bugfixes:
    - 尝试修「堆叠为 1 的物品(护甲 / 装备)无法运行」。诊断有误(见 2.6.0):
      当时按「输入槽容量被 stack_size 封顶」解释,把它改成 1 合 1 —— 方向不对,用户也否掉了 1 合 1。
Version: 2.5.2
Date: 15. 09. 2026
  Bugfixes:
    - (诊断有误,见 2.6.0)曾误以为把 stack_size = 1 物品的 overload_multiplier 压回 1 即可,
      实测无效(用户反馈「没有变化」)。根因不在 overload_multiplier。
Version: 2.5.1
Date: 15. 09. 2026
  Changes:
    - 机器原料格的原料缓冲量可调(用户要求:「优化机器的原料格的堆叠限制」)。
      根因:融合配方没有显式设 overload_multiplier,引擎默认公式
      1.166 / (energy_required / crafting_speed) 对速度 2 的机器只给约 3 倍 ——
      机器囤满「3 次融合的量」就停,机械臂得一直补货,批量融合体验差。
      现在给配方显式设 overload_multiplier,机器会缓冲「每次融合数量 × 此值」个原料才停。
    - 新增开局设置 quality-merger-overload-multiplier(默认 20,范围 2–100):
      默认下机器可缓冲约 20 次融合的原料,机械臂补货频率大幅下降;
      想要更大 / 更小缓冲在开局设置里调。
Version: 2.5.0
Date: 15. 09. 2026
  Bugfixes:
    - 修「各个装甲和各个模块没有生成升级配方」(用户反馈)。两个互不相干的原因,都不报错:
      ① 装备类物品的字段名拼错。正确是 `place_as_equipment_result`,代码里写成了
         `placed_as_equipment_result`。Factorio 对**不认识的字段名不报错**,只当它没写 ——
         于是这个判断恒为 nil:插进护甲格的那一整类装备(能量护盾 / 电池 / 外骨骼 /
         个人机器人指令模块 / 聚变堆 / 个人激光防御…)既没有 place_result 也不认装备结果,
         一路掉到「没有品质增幅」被跳过。
         (官方 wiki 的 Tutorial:Localisation 正文里写的是错拼法,以 API 文档字段表为准。)
      ② 「堆叠上限 < 所需数量就跳过」这条规则本身是错的。护甲的 stack_size 就是 1,
         3 个护甲会占 3 个输入格,不是塞不进去 —— 机器的输入栏尺寸由引擎按配方自动定
         (官方 changelog 0.12.0 原文:Assembling machine input slot can hold more than the
         normal stack size when the recipe requires it)。该规则已删除。
         真正该拦的 not-stackable 物品由 flag 检查负责,不受影响。
      连带修正:item_loc() 里同一个字段名也拼错了 —— 装备类物品的配方名原本会掉到
      {"item-name.xxx"},在游戏里显示 Unknown key。现在按引擎规则解析到 equipment-name.*。
    - 修「自检测了却没测到」:分类自检原本只测 QR.benefit()(品质收益),而护甲是在它
      **前面**的 stack_size 检查里被筛掉的 —— 自检报「全部 OK」,游戏里却没有配方。
      现在整条判定链收进 QR.verdict(),配方生成器与分类自检共用同一个入口。
  Changes:
    - 分类自检的覆盖从 61 项扩到 76 项:补上五档护甲、七种装备、四种模块、五种载具。
      自检现在走生成器用的那条完整判定链(QR.verdict),不再是只测一个子函数。
    - 新增日志「有配方的物品(按原型类型)」一行 —— 「某个类型是不是整类消失了」一眼可见。
    - tools/check_locale.py 增加第三项检查:易错的原型字段名拼写(FIELD_TYPOS 表),
      并补 2 条合成样例的自测(连同原有的 5 条共 7 条)。这类错引擎不报错,只能静态拦。
Version: 2.4.1
Date: 15. 09. 2026
  Bugfixes:
    - 修 mod 加载失败(用户反馈):
        Error while loading recipe prototype "qm-merge-wooden-chest-normal-uncommon" (recipe):
        Value must be a string in property tree at
        ROOT.recipe.<名字>.localised_name[4]
      根因:LocalisedString 的类型定义是 `string | array[LocalisedString]` —— **数字不是合法元素**
      (官方示例老老实实写着 tostring(80))。2.4.0 把 required_count(数字 3)直接塞进了
      localised_name 的参数列表,改成 tostring() 之后再放进去。
      注意报错下标是 **0 基**的:localised_name[4] 指的是第 5 个元素,按 1 基数会看错位置。
    - 新增 QR.ls_sanitize()(data 阶段):递归规范化 LocalisedString,把非法的标量元素
      tostring() 掉并写日志。为什么需要运行时兜底 —— QC.loc_name 会优先返回**品质原型自己的**
      localised_name,那可能是别的 mod 写的、不受我们控制的数据。宁可日志多一行警告,
      也不要玩家那边加载失败。
    - QR.describe_ls() 现在会把非法类型直接标出来(`3 ⚠非法(number,必须 tostring)`)。
      日志里字符串带引号("3")、数字不带(3),这个差别就是 2.4.0 没读出来的地方。
    - 构建期校验:tools/check_locale.py 新增「localised_name / localised_description 的
      字面量数组里不许出现数字 / 布尔」的静态检查,并自带 5 条合成样例的自测
      (第一条就是上面那段踩过的原样代码),证明检查器真能抓到、不是个永远打印 OK 的摆设。
      package.py 会自动调用,不过就拒绝打包。
      覆盖陷阱:数组外面包一层函数调用(localised_name = QR.ls_sanitize({...}))也必须认出来,
      第一版正则只匹配 `= {`,改完代码后当场成了盲区 —— 自测里专门留了样例盯着它。
Version: 2.4.0
Date: 15. 09. 2026
  Changes:
    - 配方名不再显示 "Unknown key"(用户反馈):
      根因是**本地化键 = 段名 + "." + 条目名**。原来把 qm-merge.recipe-name 写在了
      [recipe-name] 段里,实际生成的键是 recipe-name.qm-merge.recipe-name,而代码查的是
      qm-merge.recipe-name —— 静默失配,游戏只显示 "Unknown key: ...",日志里什么都不报。
      现在条目移到 [qm-merge] 段,键与代码一致。
      顺带修同类隐患:开局设置的 4 个条目以前完全没有本地化,设置界面同样显示 Unknown key,
      现在补齐了 mod-setting-name / mod-setting-description。
      配方名格式同时优化为「物品(原料品质 → 产物品质,N 合 1)」——把每次消耗数量也写进去
      (那个数字来自开局设置,玩家不一定记得)。
      物品名改为按引擎规则解析:优先沿用 place_result / placed_as_equipment_result 的名字
      (entity-name.* / equipment-name.*),而不是写死 item-name.* —— 后者会让依赖
      place_result 取名的物品在配方名里冒出 "Unknown key: item-name.xxx"。
    - 只为「品质真的有收益」的物品生成配方(用户要求):
      · 没有品质增幅的物品(铁板、铜线、电路板、科技包这类纯材料 / 中间品)不再生成配方。
        本 mod 的融合链是「N 个普通 X -> 1 个不常见 X」,中间品永远以普通品质参与、
        不需要升档,所以给它们生成配方纯粹是列表噪音。
      · 升品质只加 max_health 的建筑(传送带、管道、墙、大门、铁轨 / 信号 / 车站、
        灯、热管、组合器、音箱、装饰物)也不再生成配方。
      判定规则在新增的 prototypes/quality-relevance.lua 里,用两张**保守**的表:
      黑名单(确定只加血的实体类型)+ 白名单(模块 / 弹药 / 枪 / 护甲 / 胶囊 / 维修工具 /
      可放置装备),不在表里的一律保留 —— 宁可多给一条配方也不漏掉有用的。
      新增开局设置 quality-merger-recipe-scope:beneficial-only(默认)/ all-items(旧行为)。
      新增 quality-merger-log-details:把逐物品判定 + 分类自检写进 factorio-current.log。
    - 新增分类自检:拿 base 里 61 个众所周知的物品核对判定结果(装配机 / 熔炉 / 矿机 /
      模块 / 弹药 / 护甲 / 装备 / 箱子 / 机器人 = 期望生成;铁板 / 电路 / 科技包 /
      传送带 / 管道 / 墙 / 铁轨 / 灯 / 组合器 = 期望跳过),不符只警告不中断加载。
      本机没装 Factorio,这份自检是「规则在真实数据上跑成什么样」的唯一证据。
    - 修复一处过时注释:merge-recipes.lua 里写「设备 crafting_speed = 10」,实际是 2。
Version: 2.3.2
Date: 15. 09. 2026
  Changes:
    - 球体阴影改画到**机身层**(用户反馈:"加点阴影提升立体感",进游戏看完追问"阴影呢?")。
      根因:2.3.1 把明暗系数只乘在了 core 层上,而 core / halo 是 draw_as_glow
      (**加法发光**)—— 加法层的"暗"只等于"不发光",**永远压不暗底下的像素**。
      而当时承载暗部的机身层球体实体是 dist 的径向对称渐变(同心圆环),零方向性,
      所以游戏里看到的仍是一张扁平的亮圆盘。
      现在机身层(普通混合,唯一能压暗的地方)的球体实体按球面光度学着色:
        方向光    lit = (0.20 + 0.82·lam) x (0.55 + 0.45·nz)      主光来自左上前方
        接触阴影  球缘一圈被凹槽壁挡住,下缘最重(凹槽底就在球的正下方)
        凹槽投影  凹槽上沿在球顶压出一道影
        座圈落影  球在背光侧座圈上的投影(仅 0.97~1.09 倍半径的环带)
      实测(从成品机身层反查,采样区限定在玻璃球分割内):
        左上/右下 = 2.51    球心/球缘 = 2.77    球缘上/球缘下 = 38.2 / 13.1
      (同样的测法在 2.3.1 上只有 1.64 / 0.93 —— 球确实是平的。)
    - core 层配合调整:球面边缘光与球外柔光改为**乘受光度 rim_lit**(暗面只留 30%),
      并把 shade 对比度拉大(SHELL_MIN 0.64 -> 0.34、LIGHT_BIAS 0.82/0.28 -> 0.50/0.58)。
      否则暗面会被加法补上一圈亮边,机身层刚做出来的阴影立刻被冲掉。
      立体感实测:core 层左上/右下 1.30 -> 1.87,球心/外环 1.52 -> 1.85。
    - 新增常驻校验(tools/check_graphics.py 第 3e 节):从**成品机身层**反查
      方向光 / 接触阴影 / 下缘最重三条,任一条不达标直接失败。
      ⚠️ 采样区必须限定在"玻璃球分割"内 —— 球体圆内约 8% 是原图保留的金属爪与尖顶
      (设计如此、本来就亮),不排除会把"球缘没暗下去"误报成失败。
    - 金属色保真仍为通道差 0.41(座圈落影限制在 1.09 倍半径内,金属保真校验采样的是
      1.15 倍半径以外,两者互不干扰);帧数 / 三层周期 / 旋转方向 / 设备参数与配方逻辑均未改动。
Version: 2.3.1
Date: 15. 09. 2026
  Changes:
    - 气旋的旋转方向翻转:2.3.0 是逆时针,现在改为**顺时针**
      ⚠️ 翻转旋转方向**不能只把时间项取反** —— 相位里 θ 项与 ln r 项是耦合的
      (旋转与缩放是对数螺线的同一个自相似变换),单独翻 2π·F·t 的符号会让"向内收缩"
      一起变成"向外喷"。正确做法是把 **θ 一起取反**(新增常量 SPIN = -1):
      螺旋旋向与旋转方向同时翻转,而径向的向内收缩保持不变(等价于把图案水平镜像)。
      新增量化校验(tools/check_graphics.py 第 3d 节):从成品图反推每帧角位移
      Δθ = +6.66°(正 = 顺时针),并与 gen_core.py 的 SPIN 声明交叉验证。
    - 给能量球加**球面着色**做出立体感。发光球在加法层上本来是平的(一个均匀亮的圆盘);
      加法层做不出"减法",所以阴影是**生成时把亮度乘小**,而不是叠一层暗色:
        法线      nz  = sqrt(1 - ux^2 - uy^2)
        方向光    lam = clip(N · LIGHT, 0, 1)          主光来自左上前方
        边缘自遮  shade = (0.64 + 0.36·nz) x (0.82 + 0.28·lam)
        凹槽投影  shade x= 1 - 0.16·smoothstep(...)    球顶被机器凹槽上沿挡住
      shade 只乘在"体积项"(亮核 / 螺旋臂 / 球体辉光)上 —— 球面边缘光与球外柔光保持原样,
      否则球会被压成一个"没有轮廓的暗斑"。螺旋臂再叠一个横向明暗 1 + 0.18·sin(ph),
      让流线看起来有厚度(不是一块贴纸)。
      实测量化:左上/右下亮度比 1.30、球心/外环亮度比 1.52、
      core 相邻帧差异最小 12751 -> 13715 px。
    - 描述文字重写:
        * 本地化里的 [entity-description] 原文写着"制造速度 10(每次融合 0.1 秒)" ——
          **这是过时数据**,实际是速度 2 / 0.5 秒。已改正,并补上"12 个模块槽"和
          "模块槽不收品质模块"这两条玩家会在意的信息(中英同步)。
        * info.json 的 description 重写:去掉帧数 / repeat_count 这类实现细节,
          改成玩家视角(怎么用 / 确定性 / 支持其它 mod 的物品),中英各一段。
    - 其余一律未动:帧数、三层周期 240、设备参数、配方逻辑
Version: 2.3.0
Date: 15. 09. 2026
  Changes:
    - 中央能量球的视觉重做:**橙红色的气流向中心螺旋**
      (原来是紫色亮核 + 8 条旋转放射光刺)
    - 配色换暖色系:core 层 炽白 -> 金 -> 橙 -> 橙红,halo 层 亮橙 -> 橙红 -> 深红;
      实测亮像素(alpha>128)均值 RGB:core (238.5, 111.7, 51.1)、halo (238.1, 90.0, 28.7)
      —— 饱和的橙红,不是"泛黄的白光"
    - 机身层垫底的球体实体同步从深紫换成暗橙红(成品球体区均值 R-G = +43.1、R-B = +48.8)
    - 形态改为**向心对数螺旋 + 径向行波**:
          相位 ph(r,θ,t) = ARM·θ + ARM·TW·ln r + 2π·F·t      (ARM = 3 条臂,120° 对称)
      对数螺线在「整体加相位」下同时等价于「旋转」和「缩放」(这是它的自相似性),
      所以 TW 取 2 上下能让收缩压倒旋转 —— 看起来是气流被吸进去,而不是风车在转。
      再叠一条对数径向行波 cos(2π·(NFLOW·ln r + FF·t)) 作为「气流团块」的颗粒感;
      只靠螺旋臂是"卷",加上这条才是"流"。
      实测:core 整轮向内收缩到 41.8% 半径(同时转 120°),halo 收缩到 29.2%
    - 帧数与周期完全不变:core 20 帧 x12、halo 48 帧 x5、机身 1 帧 x240
      → 三层周期都是 240、整体 16.0 秒才重复一次
    - 动画强度实测:core 相邻帧差异最小 4424 -> 12751 px(相邻帧变化更明显);
      halo 15598 px(原 17788 px,同量级,两者都远超 1% 阈值)—— 相异帧均满分(20/20、48/48)
    - 新增「特效层色相必须是橙红」校验(tools/check_graphics.py 第 3b 节):
      R > G > B 且 R-G > 40、R-B > 80 → 防止以后调色静默跑偏成黄/白/紫
    - 其余不变:机身静止、三层周期 240、设备参数与配方逻辑一律未动
Version: 2.2.1
Date: 15. 09. 2026
  Bugfixes:
    - 修复加载失败(2.2.0 装在游戏里直接报错):
        Error while loading entity prototype "quality-merger" (assembling-machine):
        Different frame counts (expected 1, but has 25) in animation layers of
        __quality-merger__/graphics/entity/quality-merger/quality-merger-core.png
        in property tree at ROOT.assembling-machine.quality-merger.graphics_set.animation.layers[1]
    - 根因:Factorio 要求同一个 animation.layers 里**每一层的动画周期相等**,
      周期 = frame_count x repeat_count(**不是 frame_count 本身**)。官方 wiki 原话:
        "if one layer is 10 frames, a second layer of 1 frame would need
         repeat_count = 10 to match the complete cycle"
      2.2.0 用「帧数互质」(机身 1 / 核 25 / 光晕 16)来做错拍,引擎直接拒载。
  Changes:
    - 改为「图案帧数互不整除 + repeat_count 对齐周期」:
        机身    frame_count  1 x repeat_count 240 = 周期 240
        能量核  frame_count 20 x repeat_count  12 = 周期 240
        光晕    frame_count 48 x repeat_count   5 = 周期 240
      20 与 48 互不整除(gcd 4)→ 两层仍然错拍;整体仍是 240 帧才重复一次
      (SPEED 0.4 -> 0.25,即 15 动画fps,240 帧 = 16.0 秒)
      repeat_count 的类型是 uint8(上限 255),所以共同周期不能超过 255 —— 这也是取 240 的原因
    - 核的图案帧数 25 -> 20(每帧转 4.5°):实测相邻帧差异从 4424 px 起,
      而 60 帧版本每帧只转 1.5°,8bit 量化会把变化抹平到 272 px,播起来会「一顿一顿」
    - 光晕的图案帧数 16 -> 48、旋转量 90° -> 45°:RAYS=8 是 45° 对称的,转 90° 等于走 2 个
      视觉周期,48 帧里会出现成对重复帧(实测相异帧只有 41/48,白占约 15% 体积)
    - 新增「三层动画周期一致」校验:tools/package.py(打包前)与 tools/check_graphics.py
    - 外观 / 行为不变:机身仍静止,中央球体仍完全是程序化能量球
Version: 2.2.0
Date: 14. 09. 2026
  Changes:
    - 中央能量球改为**完全程序化的动态特效**,机身改为静止
    - 原因(实测):原 8 帧的球心区域亮度是
      97.5 / 163.5 / 168.4 / 160.5 / 167.9 / 213.3 / 220.6 / 165.9 —— 只有第 1 帧是暗的,
      其余 7 帧全在高位。这 8 帧本来是一个「待机→充能→爆发→冷却」的**一次性过程**,
      当循环播就成了「一直亮着、每 1.67 秒莫名闪暗一下」,既呆板又像卡带。
      另外球体整个落在机身实心轮廓内部,而旧光晕层取的是「亮度>=64 且 在轮廓之外」,
      球体的光**一次加法发光都没吃到**
    - 新的三层结构(各自的画布 / scale / 帧数都独立):
        机身层 quality-merger-body.png   静态 1 帧,普通混合
        能量核 quality-merger-core.png   25 帧,加法发光:脉动亮核 + 内部旋转涡流 + 球面边缘光
        光晕层 quality-merger-halo.png   16 帧,加法发光:球外弥散光 + 8 条旋转放射光刺
    - 机身层里的球体区域换成程序化生成的深紫实体(普通混合)—— 加法发光无法遮挡背景,
      球体必须有一层实体垫底;金属爪与上方尖顶用颜色判据排除,原样保留
    - 节奏:Factorio 规定 Animation.layers 里**只有第一层的 animation_speed 生效**,
      所以错拍不能靠各层不同速度,改为**帧数互质**(25 与 16):
      整体 400 帧才重复一次,SPEED = 0.4(24 动画fps)→ 约 16.7 秒一轮,
      核 1.04 s/轮、光刺视觉一圈 2.67 s(旧版是固定 1.67 秒的重复感)
    - 核 / 光晕层各自以**球心**为中心,靠 shift 定位到机器上
      (球心相对机器中心偏移 (4.1, -14.7) 游戏px = (0.0320, -0.1147) 格)
    - 图标 / 缩略图改为三层合成后的画面(原来是单帧机身)
Version: 2.1.8
Date: 14. 09. 2026
  Bugfixes:
    - 各帧定位不准:原做法只用「每行底缘中位数」做行间平移修正,修不了逐帧漂移。
      实测各帧底座(底部 30 行)外接框中心相对帧1 为 -14.5 ~ +8.7 px,第二行还整体高 12 px,
      播放时机器会左右/上下抖。现改为**逐帧锚点对齐**(锚点 = 底座外接框中心 x + 底缘 y,
      全部对到 8 帧中位数),对齐残差 ≤ ±0.5 源像素;裁切窗对锚点对称,
      机器中心落画布中心、底缘落画布底 8px → 8 帧落点完全一致
      (复核:机身层 8 帧包围盒均为 x[37..598] y[164..631])
    - 机器太透明:alpha 切割线原为 64,但实测参考图背景亮度中位 5 / p99 15 / 最大仅 18,
      切割线高得离谱 —— 帧1(待机、无光晕)有 28.1% 的机器表面(暗玻璃球 + 外壳暗面)
      被挖成透明。非待机帧更糟:机身层只收「亮度 >= 100」的像素,暗部全丢进加法层,
      机身层像素保留率仅 63.5% ~ 81.1%,整机发虚。
      现改为:从**无光晕的帧1**提取机器实体轮廓 T(切割线降到 20,
      实测漏边 0 px、越界仅 2.5% 均值),T 由 8 帧共用且 100% 不透明;
      轮廓外才是真光晕,仍走加法发光。机身层不透明率 100.0%
  Changes:
    - 机身层与光晕层的合并改用预乘 alpha 缩放,去掉缩放时混入透明像素颜色产生的边缘杂色
    - 机身层 8 帧共用同一轮廓:轮廓原本逐帧重算会让边缘差几个像素、播放时轻微闪烁
Version: 2.1.7
Date: 14. 09. 2026
  Changes:
    - 基础制造速度 10 → 2
    - 模块槽 2 → 12
Version: 2.1.6
Date: 14. 09. 2026
  Changes:
    - 配方排序:同一物品的多条品质配方,order 追加品质跃迁序号(零填充,-q01/-q02/...),
      在设备里按品质从低到高排(普通→不常见→稀有→史诗→传说),
      不再退回按配方名字母序乱排
Version: 2.1.5
Date: 14. 09. 2026
  Major Features:
    - 画面质量优化:贴图从单层拆回「机身 + 光晕」两层,光晕走 draw_as_glow 加法发光。
      参考图是纯黑底渲染,发光弥散区(亮度 64~100 的暗紫)此前被当"实体暗紫"画出来,
      落到浅色地面是一圈脏晕;现在改成加法后真正"加光",脏晕消失,光束亮起来
  Changes:
    - 机身层 quality-merger-body.png:普通混合、实心不透明,颜色与参考图完全一致(零改动)
    - 光晕层 quality-merger-glow.png:加法发光,alpha 按亮度 64→0 / 100→满 渐变
    - 分离原则:待机帧机身整体偏暗、结构是碎块,若按轮廓分会整台被吃掉(实测 77.7% 误判),
      故待机帧整帧不透明、不拆光晕;其余帧按亮度分(亮结构做机身、暗弥散区做光晕)
    - 光晕剥离量:充能/全亮/紫焰帧约 29~31%,迸发/超载帧约 19~21%,冷却帧约 37%
    - 图标 / 缩略图仍取机身层第 2 帧
Version: 2.1.4
Date: 14. 09. 2026
  Major Features:
    - 贴图换成用户提供的 8 帧 Sprite-Sheet(1920x1440,4 列 x 2 行),
      材质从单帧静态变回 **8 帧动画**:待机 → 充能 → 全亮 → 紫焰 → 迸发 →
      超载(红)→ 冷却(蓝烟),animation_speed 0.08(4.8fps,一轮约 1.67 秒)
  Changes:
    - 依旧**原样直贴**:切格(避开分隔线)、涂掉格内数字标注、去纯黑底(切割线仍为 64)、
      等比缩放,无任何效果调整
    - 两行机位差实测 12px,第二行整体平移对齐(纯平移,帧间不跳动)
    - 8 帧共用一个裁切窗(474x510)→ 580x624 放进 640 画布,
      机体横向 4.53 格、纵向 4.88 格,水平居中、底边贴地
    - 精灵表 2560x1280(4 帧一行 x 2 行,line_length = 4);图标 / 缩略图取第 2 帧
      (待机帧球体是暗的,过不了黑底切割,做图标会千疮百孔)
    - 不再生成任何预览 / QA 图
Version: 2.1.3
Date: 14. 09. 2026
  Changes:
    - 贴图改为**参考图原样直贴**:只做裁切 + 去纯黑底 + 等比缩放,不再做任何效果处理
      (去掉了 2.1.2 里的图层拆分、加法发光层、开 / 闭运算修复等全部后期)
    - 贴图从两层合回一层:graphics/entity/quality-merger/quality-merger.png(640x640 单帧静态),
      删除 quality-merger-anim.png 与 quality-merger-glow.png
    - 去黑底只留一条切割线 ALPHA_CUT = 64:比它暗的当纯黑背景切掉,比它亮的原样保留。
      实测 32 时机体身后会留一片黑晕,80 以上机体本身开始变透明,64 是唯一两者兼顾的值
    - 材质与图标尺寸不变;机体横向 4.94 格、纵向 3.88 格,水平居中、底边对齐落地范围下沿
  Notes:
    - 参考图里光束之间的暗区(亮度 64~90 的暗紫)会作为半暗的晕保留下来 ——
      它就是原图长这样,不做后期就不会被去掉
Version: 2.1.2
Date: 14. 09. 2026
  Major Features:
    - 贴图整套换成新的参考图(机甲核心改造设计 · 特写版):
      机体 + 放射光束,紫色聚变核心,橙色履带板,金色包边
    - 材质改为单帧静态(新参考图只有一张,而需求是"只裁切 / 拉伸,不重绘"),
      不再有 6 帧状态动画
  Changes:
    - 机体横向占 4.88 格、纵向约 3.8 格,底边贴着 5x5 落地范围的下沿,上方留给放射光束;
      美术仍然 100% 落在画布内,不越界、不需要 shift
    - 抠图流程改为「开运算取机体主体 → 闭运算补形 → 填洞」:
      新参考图的机体上有大量很暗的表面(腿上的格栅、底盘下沿),
      只按亮度阈值切会把它们啃成透明缺口,必须靠形态学补回来
    - 开运算半径从 26 提到 42:新图的光束约 47px 粗,半径 26 抹不掉,
      光束会被误判成机体、以不透明方式绘制;42 才能把它们交给加法发光层
    - 光晕 / 光晕层的亮度下限从 12 提到 120(配合实测的亮度分布):
      底座周围的地面辉光(亮度 60~100,约 20 万像素)照单全收会在草地上
      显出一个幽灵般的蝴蝶形漏光;提到 120 后只留下光束核心(约 4 万像素)
    - 光晕强度 GLOW_GAIN 设为 1.0
  Notes:
    - 机体上的放射光束同时在机体层(不透明的清晰光束核心)和加法发光层
      (bloom)里,这样在浅色地面上既有形状又有光感
    - 依旧没有独立的落地阴影层(参考图没有投影,又不允许重绘)
Version: 2.1.1
Date: 14. 09. 2026
  Major Features:
    - 贴图整套换成参考图版本:不再有向上支出的塔体,美术 100% 落在 5x5 落地范围内(不要超框)
    - 设备改为 6 帧动画(待机 / 启动中 / 充能中 / 超载预警 / 紧急冷却 / 停机维护),
      一轮约 1.25 秒;机身画布 640x640、scale = 0.25 → 游戏内正好 160x160 像素 = 5 格 x 5 格
    - 光晕单独拆成加法发光层(draw_as_glow),在任何地面上都是"加光"而不是"抹黑"
    - 新的 64x64 物品 / 科技图标,以及新的 mod 缩略图(取自参考图特写)
  Changes:
    - 贴图改为由 tools/gen_graphics.py 从 tools/ref/ 的两张参考图
      **裁切 + 拉伸**生成(黑底抠成透明),不再程序化绘制
    - 原来的 4 个贴图层(机身 / 核心 / 辉光 / 阴影)合并成「机身 + 光晕」两层
    - 抠黑底阈值提到 92,并加了一次形态学修复(闭运算 → 填洞 → 取最大连通域),
      否则强光球外围那圈光晕会留在机身层,贴到浅色地面上是一团暗紫
    - drawing_box_vertical_extension 由 1 改为 0(美术不再向上支出)
  Notes:
    - 参考图自带的左右两侧机臂在源图里就被格子边框切平了,切出来的贴图同样如此 ——
      切口正好落在 5x5 边界上,看上去就是机臂顶到落地范围边缘
    - 不再有独立的落地阴影层(参考图没有投影,又不允许重绘)
Version: 2.1.0
Date: 14. 09. 2026
  Major Features:
    - 设备改为 5x5 占地(碰撞盒 ±2.4、选择框 ±2.5)
    - 基础制造速度 10(crafting_speed = 10)
    - 所有合并配方耗时统一为 1(energy_required = 1)→ 实际 0.1 秒 / 次
    - 全新自定义贴图:工业机甲外壳 + 紫色反应腔 + 16 帧等离子核心动画 + 工作时加色辉光
      贴图由 tools/gen_graphics.py 程序化生成,不依赖任何外部美术资源
    - 全新的 64x64 物品 / 科技图标,以及新的 mod 缩略图
  Changes:
    - 耗电从 500kW 提到 2MW(速度涨了 10 倍,耗电同步跟上;可在 machine.lua 里改)
    - 开局设置移除「制造耗时」(已固定为 1)
  Notes:
    - 尺寸变大,旧存档里已放置的融合器如果和别的建筑挨得太近,可能需要挪一下
Version: 2.0.1
Date: 14. 09. 2026
  Bugfixes:
    - 修复加载失败:Error in assignID: item with name 'defender' does not exist
      (At ROOT, recipe.qm-merge-defender-normal-uncommon.ingredients[0].name)
      原因:生成配方用的原型类型白名单里混进了实体类型 combat-robot。
      defender / distractor / destroyer 是战斗机器人**实体**,对应的物品是 *-capsule 胶囊。
    - 白名单里的 mine 同样不是有效类型名(地雷实体叫 land-mine),一并移除;
      改为按 2.1.13 官方 prototypes.html 核对过的物品类型清单
      (item / ammo / capsule / gun / armor / tool / module / item-with-entity-data /
        item-with-tags / item-with-label / item-with-inventory / rail-planner /
        repair-tool / spidertron-remote / selection-tool / space-platform-starter-pack)
    - 加了两道防御,避免同类问题再出现:
      1) 只处理有 stack_size 的原型(实体原型没有该字段),堆叠上限恒为物品专属;
      2) 遍历 data.raw 时先校验 proto / name 的类型,防止非表值直接抛异常
Version: 2.0.0
Date: 14. 09. 2026
  Major Features:
    - 移植到 Factorio 2.1(info.json 的 factorio_version 改为 2.1,依赖 base >= 2.1.0)
    - 「品质 +1」改为引擎原生实现:用 2.1 新增的
      ItemIngredientPrototype::quality_min/quality_max 与 ItemProductPrototype::quality_min/quality_max
      把原料品质和产物品质两头钉死,结果完全确定
    - 删除了 control 阶段的脚本(scripts/merger.lua)——不再需要逐 tick 扫描设备做快照差分
  Changes:
    - 配方改用 categories(2.1 已移除 RecipePrototype::category 单数形式)
    - 每种物品的每一段品质跃迁各生成一条配方,配方名带上品质(例如「铁板(稀有 → 史诗)」)
    - 配方生成移到 data-final-fixes,以覆盖其它 mod 添加的物品
    - 开局设置:移除「达到最高品质时是否退回原料」(纯 data 无法退回),
      新增「允许最后一段品质跃迁(史诗 -> 传说)」
    - 设备描述与本地化文案同步更新
  Notes:
    - 旧存档里已放置的融合器需要重新选择配方
Version: 1.0.0
Date: 14. 09. 2026
  Features:
    - 新增设备「品质融合器 / Quality Merger」
    - 新增制造分类 quality-merging,以及配套科技「品质融合」
    - 自动为游戏内几乎所有物品生成「N 合 1、品质 +1」的配方(N 默认 3,可在开局设置里改)
    - 启动设置:融合所需数量、制造耗时、达到最高品质时是否退回原料