14.3 工业化与质量的一致性

工业化不是一次做到合格,而是每一批都合格。本节用《星屑旅店》16 个资产的实测记录,把命名、网格、线宽、色板四个维度的合规率算成整数千分比,得到一致性指数 890‰,并把同批按小组拆开算出跨批漂移 94‰;返工的 5.60 人天正好吃掉 14.1 有效产能的 156‰。

14.3 工业化与质量的一致性

14.2 排期与美术依赖 算出了「什么时候做什么」,但排期成立还有一个隐含前提:做出来的东西是同一套规格。如果每个美术师按自己的习惯出图,那么「5 天画一批头像」这个估计就不可信——因为下一批的 5 天可能是另一种质量。这一节把「工业化」翻译成一个可算的量:一致性。

先定义清楚:工业化不是「做得好看」,而是「可重复、可预测」。 一次做到合格是手艺,每一批都合格才是工业化。判据不靠审美(审美需真实环境(真实画师与评审),命令行不可复现),而靠规格可测的维度——命名、网格、线宽、色板上限。本节的资产记录是示例参数,规格与阈值都来自可测的整数。

14.3.1 一致性:跨批稳定,而非单批合格

一个常见的误判,是拿「本批通过率」当质量指标。本批 100% 通过,不代表下一批也是——如果两批由不同的人、用不同的模板做,通过率会漂。所以工业化要看两个量:

  • 批内一致性指数:本批有多少资产在全部维度上合规;
  • 跨批漂移:两批(或两个小组)之间一致性指数的差。

漂移比绝对水平更能暴露问题:一个小组 843‰、另一个 937‰,差 94‰ 说明规格没有被同一套模板约束住——这正是「工业化」要消灭的方差。下面用《星屑旅店》一批 16 个资产的实测记录来算这两个量。

一致性指数为什么取四维合规率的整数平均?因为它要的是「可比较」而非「精确」:四个维度的量纲不同(正则匹配、集合隶属、等值、大小比较),唯一的共同语言是「合规比例」。平均的代价是维度等权——命名错 1 件和色板错 1 件对指数的影响一样大。真实项目可以给维度加权,但加权之后跨批比较的前提就变了,必须同步改;本节用等权,是为了让「同一套算法、两批数据」这件事最干净。

14.3.2 配置:规格与一批资产实测记录

下面是 D 层的规格与一批资产记录。规格有四个维度:命名正则、允许的网格尺寸集合、线宽、色板上限。16 个资产分两个小组(A = 场景与角色,B = UI 与特效),每行记下它的实测属性,squad 用来算跨批漂移。

{
  "project": "stardust-inn",
  "chapter": 14,
  "note": "示例参数:本套构造的一批资产实测记录,非任何真实项目的交付数据。",
  "spec": {
    "namePattern": "^[a-z][a-z0-9]*(_[a-z0-9]+)*$",
    "gridAllowed": [32, 64, 128, 256],
    "lineWidthPx": 2,
    "paletteMax": 16
  },
  "reworkCost100": 80,
  "capacityRef100": 3570,
  "assets": [
    { "id": "furn_lamp_01", "squad": "A", "role": "env", "name": "furn_lamp_01", "gridPx": 64, "lineWidthPx": 2, "paletteCount": 12 },
    { "id": "furn_lamp_01_v2", "squad": "A", "role": "env", "name": "furn_lamp_01_v2", "gridPx": 64, "lineWidthPx": 2, "paletteCount": 14 },
    { "id": "furn_counter_01", "squad": "A", "role": "env", "name": "furn_counter_01", "gridPx": 64, "lineWidthPx": 3, "paletteCount": 10 },
    { "id": "furn_chair_01", "squad": "A", "role": "env", "name": "furn_chair_01", "gridPx": 64, "lineWidthPx": 2, "paletteCount": 18 },
    { "id": "furn_rug_01", "squad": "A", "role": "env", "name": "furn_rug_01", "gridPx": 48, "lineWidthPx": 2, "paletteCount": 9 },
    { "id": "portrait_innkeeper", "squad": "A", "role": "char", "name": "portrait_innkeeper", "gridPx": 128, "lineWidthPx": 2, "paletteCount": 20 },
    { "id": "portrait_guest_a", "squad": "A", "role": "char", "name": "portrait_guest_a", "gridPx": 128, "lineWidthPx": 2, "paletteCount": 16 },
    { "id": "portrait_guest_b", "squad": "A", "role": "char", "name": "portrait_guest_b", "gridPx": 128, "lineWidthPx": 3, "paletteCount": 15 },
    { "id": "ui_icon_coin", "squad": "B", "role": "ui", "name": "ui_icon_coin", "gridPx": 64, "lineWidthPx": 2, "paletteCount": 6 },
    { "id": "ui_icon_star", "squad": "B", "role": "ui", "name": "ui_icon_star", "gridPx": 64, "lineWidthPx": 2, "paletteCount": 5 },
    { "id": "ui_icon_heart", "squad": "B", "role": "ui", "name": "UI_Icon_Heart", "gridPx": 64, "lineWidthPx": 2, "paletteCount": 7 },
    { "id": "ui_frame", "squad": "B", "role": "ui", "name": "ui_frame", "gridPx": 64, "lineWidthPx": 2, "paletteCount": 4 },
    { "id": "ui_panel", "squad": "B", "role": "ui", "name": "ui_panel", "gridPx": 64, "lineWidthPx": 2, "paletteCount": 8 },
    { "id": "vfx_burst", "squad": "B", "role": "vfx", "name": "vfx_burst", "gridPx": 64, "lineWidthPx": 2, "paletteCount": 22 },
    { "id": "vfx_sparkle", "squad": "B", "role": "vfx", "name": "vfx_sparkle", "gridPx": 64, "lineWidthPx": 2, "paletteCount": 11 },
    { "id": "vfx_smoke", "squad": "B", "role": "vfx", "name": "vfx_smoke", "gridPx": 64, "lineWidthPx": 2, "paletteCount": 13 }
  ]
}

capacityRef100: 3570 引用的是 14.1 算出的有效人天×100,用来把返工折算成产能占比。reworkCost100: 80 是「返工一件资产」的示例成本(0.80 人天)。

14.3.3 S 层:四维合规率与一致性指数

下面这段是 S 层脚本,逐资产判四个维度是否合规,汇总成四个千分比,取整数平均作一致性指数;再按 squad 分组算跨批漂移,并折算返工账:

#!/usr/bin/env python3
# file: sim/ta14_consistency.py
# tier: S
# verify: /opt/homebrew/bin/python3 sim/ta14_consistency.py
"""14.3 工业化与质量的一致性(S 层,卷二第 14 章)。

读 ``config/ta14_consistency.json`` 的规格与一批资产实测记录,算四个维度的合规率(千分比):

* 命名:匹配 ``namePattern``;
* 网格:``gridPx`` 属于 ``gridAllowed``;
* 线宽:``lineWidthPx`` 等于规格值;
* 色板:``paletteCount <= paletteMax``。

``一致性指数 = (四维合规率之和) // 4``(整数地板除)。再把同批按 ``squad`` 分组,算两组指数的
**漂移** ``|A - B|``——工业化要的不是一批内合规,而是**跨批稳定**。返工账:任一维度不合规即
``rework``,``rework100 = rework × reworkCost100``,并对比 14.1 的 ``capacityRef100``。

全整数、无浮点、无随机。落 ``out/ta14_consistency.json``。
"""
import json
import os
import re
import sys

ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
CFG = os.path.join(ROOT, "config/ta14_consistency.json")
OUT = os.path.join(ROOT, "out")
DIMS = ("name", "grid", "line", "palette")


def check_asset(spec, a):
    """四个维度的合规布尔值。"""
    return {
        "name": bool(re.match(spec["namePattern"], a["name"])),
        "grid": a["gridPx"] in spec["gridAllowed"],
        "line": a["lineWidthPx"] == spec["lineWidthPx"],
        "palette": a["paletteCount"] <= spec["paletteMax"],
    }


def index_of(spec, assets):
    """返回 (合规率 dict, 一致性指数, 返工 id 列表)。"""
    if not assets:
        return {d: 0 for d in DIMS}, 0, []
    hits = {d: 0 for d in DIMS}
    rework = []
    for a in assets:
        ok = check_asset(spec, a)
        for d in DIMS:
            hits[d] += 1 if ok[d] else 0
        if not all(ok.values()):
            rework.append(a["id"])
    rates = {d: hits[d] * 1000 // len(assets) for d in DIMS}
    idx = sum(rates[d] for d in DIMS) // len(DIMS)
    return rates, idx, sorted(rework)


def main() -> int:
    cfg = json.load(open(CFG, encoding="utf-8"))
    spec, assets = cfg["spec"], cfg["assets"]
    rates, idx, rework = index_of(spec, assets)
    squads = sorted({a["squad"] for a in assets})
    per = {}
    for s in squads:
        r, i, rw = index_of(spec, [a for a in assets if a["squad"] == s])
        per[s] = {"rates": r, "index": i, "rework": rw, "count": sum(1 for a in assets if a["squad"] == s)}
    drift = abs(per[squads[0]]["index"] - per[squads[-1]]["index"])
    rework100 = len(rework) * cfg["reworkCost100"]
    cap = cfg["capacityRef100"]
    report = {
        "project": cfg["project"], "assetCount": len(assets),
        "rates": rates, "consistencyIndex": idx, "reworkIds": rework,
        "reworkCount": len(rework), "reworkCost100": cfg["reworkCost100"],
        "rework100": rework100,
        "reworkSharePermille": rework100 * 1000 // cap if cap else 0,
        "capacityRef100": cap,
        "squads": per, "driftPermille": drift,
        "lineWidths": sorted({a["lineWidthPx"] for a in assets}),
        "paletteRange": [min(a["paletteCount"] for a in assets),
                         max(a["paletteCount"] for a in assets)],
    }
    os.makedirs(OUT, exist_ok=True)
    with open(os.path.join(OUT, "ta14_consistency.json"), "w", encoding="utf-8") as f:
        json.dump(report, f, ensure_ascii=False, indent=2, sort_keys=True)
        f.write("\n")
    print(f"assets={len(assets)} index={idx}permille rework={len(rework)} rework100={rework100}")
    print("rates " + " ".join(f"{d}={rates[d]}" for d in DIMS))
    for s in squads:
        print(f"  squad {s}: n={per[s]['count']} index={per[s]['index']}permille "
              f"rework={len(per[s]['rework'])}")
    print(f"drift={drift}permille reworkShare={report['reworkSharePermille']}permille "
          f"lineWidths={report['lineWidths']} paletteRange={report['paletteRange']}")
    print(f"reworkIds={rework}")
    print("wrote out/ta14_consistency.json")
    return 0


if __name__ == "__main__":
    sys.exit(main())

实跑 /opt/homebrew/bin/python3 sim/ta14_consistency.py:

assets=16 index=890permille rework=7 rework100=560
rates name=937 grid=937 line=875 palette=812
  squad A: n=8 index=843permille rework=5
  squad B: n=8 index=937permille rework=2
drift=94permille reworkShare=156permille lineWidths=[2, 3] paletteRange=[4, 22]
reworkIds=['furn_chair_01', 'furn_counter_01', 'furn_rug_01', 'portrait_guest_b', 'portrait_innkeeper', 'ui_icon_heart', 'vfx_burst']
wrote out/ta14_consistency.json

14.3.4 读账:指数、漂移与返工

  • 批内一致性指数 = 890‰。四个维度里色板最松(812‰,3 件超上限:furn_chair_01 18 色、portrait_innkeeper 20 色、vfx_burst 22 色),线宽次之(875‰,furn_counter_01 与 portrait_guest_b 用了 3px),命名与网格各 937‰(各 1 件:UI_Icon_Heart 大小写错、furn_rug_01 网格 48 不在允许集里)。
  • 跨批漂移 = 94‰。A 组(场景与角色)843‰,B 组(UI 与特效)937‰。差 94‰ 说明两组用的不是同一套模板——色板与线宽是 A 组的重灾区,命名是 B 组的问题。工业化要压的就是这 94‰:把规格写进工具(12.1 的规则引擎)让两组共用同一套闸门,而不是靠人自觉。
  • 返工 7 件(任一维度不合规即计入),折算 7 × 0.80 = 5.60 人天(rework100=560)。lineWidths=[2, 3] 与 paletteRange=[4, 22] 这两个分布,直观地说明「同一批资产」在规格上的实际跨度有多大。

把 7 件不合规按维度拆开,看得更清楚:

维度不合规件数合规率不合规资产(实测值)
命名1937‰ui_icon_heart(写成 UI_Icon_Heart)
网格1937‰furn_rug_01(48px 不在允许集)
线宽2875‰furn_counter_01、portrait_guest_b(3px)
色板3812‰furn_chair_01(18)、portrait_innkeeper(20)、vfx_burst(22)
合计7—7 件资产,各错一个维度

这张表还说明一件事:7 件资产恰好各错一个维度(没有任何一件同时错两项),所以「不合规件数 7」与「不合规维度命中 7」数值相同——若换成「一错就错一片」的数据,两者就会分叉,返工率也会随之变化。返工成本按「件」计,所以真正决定返工账的是不合规资产数,而不是维度命中数。

14.3.5 一致性如何吃掉产能:回到 14.1

把返工折算回 14.1 的产能账:5.60 人天 ÷ 35.70 有效人天 = 156‰(reworkSharePermille)。也就是说,这一批的返工吃掉了整支团队有效产能的 15.6%。如果这 7 件在第一次就合规,那 5.60 人天本可以投给 14.2 关键路径上最紧的 furniture。

这条链把本章三节串起来了:14.1 算产能 → 14.2 排依赖 → 14.3 把「不合规造成的返工」折回产能。一致性差不是审美问题,是产能泄漏:reworkSharePermille 越高,14.1 里那份利用率就越乐观。所以工业化真正的收益,是把返工率压下去,让 14.1 的有效人天尽可能落在有效产出上。

换个角度看这笔账:14.1 的利用率 1058‰ 已经说明计划排不下,返工又额外吃掉 156‰ 的产能——两者叠加,才是这支团队真实的工作强度。一致性差的项目,产能账永远比纸面更紧:你以为有效人天有 35.70,实际能落在「第一次就对的产出」上的只有 35.70 × (1 − 0.156) ≈ 30.13 人天。把返工率从 156‰ 压到 60‰,等于凭空多出 35.70 × 0.096 ≈ 3.43 人天——这是工业化最容易被低估的收益。

所以三节的关系也可以倒过来读:14.3 的返工率是 14.1 产能账的隐藏折扣,也是 14.2 关键路径的隐藏风险——返工一旦落在关键路径上(比如 furniture),整个工期会直接顺延。工业化要做的,就是把这个折扣和这个风险一起压小。

14.3.6 质量门:把一致性变成闸门

一致性指数是事后的度量。要把 94‰ 的漂移压下去,得把它变成事前的闸门:在资产进管线的路上设一道自动检查,不合规就拦住——把 spec 的四条规则写成管线里的一步,让 A、B 两组共用同一套判据,而不是各自凭习惯。

闸门放在哪里,决定它拦住什么:

闸门位置拦住什么对应维度
提交前(本地)命名、网格namePattern、gridAllowed
导入时(管线)线宽、色板lineWidthPx、paletteMax
出图前(评审)审美与构图需真实环境,命令行不可复现

前两道能自动跑(就是本节脚本判的四个维度),第三道必须人看。闸门越靠前,返工越便宜:提交前拦住,改一个字段;进了管线才发现,要重导整条链路。把可自动的部分尽量前置,才能把 14.3.5 那 156‰ 的产能泄漏压下来。注意闸门本身也要维护——规格变了闸门要跟着变,否则它会从「保护」变成「误拦」,这正是 13.3 讨论的采纳率与维护成本问题。

14.3.7 边界

  • 判据只覆盖可测维度。命名、网格、线宽、色板上限都能被脚本判定,但「这张图好不好看」不能——审美需真实环境(真实画师与评审),命令行不可复现。本节的一致性指数是必要条件,不是充分条件:全合规不等于好看。
  • 阈值是示例参数。paletteMax=16、reworkCost100=80 都是本套构造的,换成别的值结论会变。真实项目要把阈值写进规范(卷一第 6 章的清单 schema),而不是散落在脚本里。
  • X 层:真实交付验收要人看、要评审、要打回重做,这一步需真实环境,命令行不可复现;本节只做规格维度的整数合规率,不替人做审美判断。

阅读导航:上一节:14.2 排期与美术依赖 · 下一节:15.1 外包切分与计价模型 。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「gameart」更多文章

  1. 附录 A:风格参数表、量化规则与数据集索引
  2. 9.3 等距资产的制作约束
  3. 9.2 等距 tile 与遮挡规则