16.3 交付物与卷五衔接

本卷最后一节做两件事:把 16 章的交付物列成一张带权重的清单(D 层),再用仿真把完成态折算成整体完成度、判定是否达可交付线(S 层)——当前 930‰ 过线,缺口只剩案例复盘与附录 E;末尾交代与尚未落盘的卷五《跨平台发行与出海》之间的衔接点。

十六节走到这里,前面的每一章都交了一份东西:投放配置集、归因与聚合配置、内购合规清单、渠道商务表、资质财务表、运营 SOP、单位账与月度经营账、操盘手册、对账台账。但「交了很多东西」和「能交付了」是两回事——没有人把这些散件列成一张可勾选、带权重的清单。本节就做这件事,并顺手把与卷五《跨平台发行与出海》的衔接点交代清楚。

先说分工:本卷与《上线变现与长线运营》 是同一套台账的上下游——卷三管从上线到长线运营的链路(留存、活动、版本节奏),卷四管商业运营的账与交付(投放、结算、单位账、交付清单),两卷不重叠;运营协作与团队侧的展开交给站内游戏行业运营 专题,本节只做交付视角的收束。

交付物清单:把 16 章拆成可勾选的项

清单是「装配」的反向操作。第 16.1 节的手册回答怎么用,本节清单回答交了什么、还差什么。每一项给一个 id、名称、归属章、权重与完成态;权重不是拍脑袋,而是按这一项在真实操盘里的缺了会有多痛来分——投放配置集与归因聚合各占 15,因为它们一旦缺,整条投放链路都跑不起来;附录 E 只占 5,缺了不影响操盘。

下面是 D 层清单 config/o16_delivery.json:

{
  "version": 1,
  "game": "merge-island",
  "observedAt": "2026-10",
  "readyThresholdPermille": 900,
  "note": "卷四交付物清单与完成度(D 层)。口径是「交付清单装配」的建模,不是任何真实项目的验收记录。每项 item:weight=权重(整数),status=完成态(done/partial/todo,对应 1000/500/0 千分比)。整体完成度 = Σ(weight×statusPermille) // Σweight(千分比),达到 readyThresholdPermille 即判可交付。真实交付物内容与验收结论需真实业务环境,命令行不可复现(X)。全整数,无浮点。",
  "items": [
    { "id": "toc",        "name": "卷四目录篇(16 章 48 节导览)",     "chapter": 16, "weight": 5,  "status": "done" },
    { "id": "ua-config",  "name": "投放配置集(账户/出价/素材,第 1–3 章)", "chapter": 3,  "weight": 15, "status": "done" },
    { "id": "attr-agg",   "name": "归因与聚合配置集(第 4–6 章)",     "chapter": 6,  "weight": 15, "status": "done" },
    { "id": "iap-comp",   "name": "内购与合规清单(第 7–8 章)",       "chapter": 8,  "weight": 12, "status": "done" },
    { "id": "channel-bd", "name": "渠道与商务表(第 9–10 章)",        "chapter": 10, "weight": 12, "status": "done" },
    { "id": "lic-fin",    "name": "资质与财务表(第 11–12 章)",       "chapter": 12, "weight": 12, "status": "done" },
    { "id": "ops-sop",    "name": "运营 SOP 与例会编排(第 13 章)",   "chapter": 13, "weight": 8,  "status": "done" },
    { "id": "unit-pnl",   "name": "单位账与月度经营账(第 14 章)",     "chapter": 14, "weight": 12, "status": "done" },
    { "id": "retro",      "name": "案例复盘与决策对照表(第 15 章)",   "chapter": 15, "weight": 6,  "status": "partial" },
    { "id": "handbook",   "name": "操盘手册(第 16 章)",              "chapter": 16, "weight": 8,  "status": "done" },
    { "id": "ledger",     "name": "全链路对账台账(第 16 章)",        "chapter": 16, "weight": 5,  "status": "done" },
    { "id": "appendix-e", "name": "附录 E(配置与清单索引)",          "chapter": 16, "weight": 5,  "status": "todo" }
  ]
}

清单里有三处刻意留下的非完成态,它们不是失误,而是这套建模要暴露的东西:retro 是 partial(案例复盘的决策对照表只填了骨架),appendix-e 是 todo(配置与清单索引还没建)。保留缺口、并让缺口可见,比把清单刷成一片绿更有用。

S 层:交付完整度打分

有了清单,下一个问题自然是:现在到底能不能交? 下面这段 S 层脚本 sim/o16_delivery.py 把每个完成态折算成完成度千分比(done→1000、partial→500、todo→0),再按权重加权求和,得到一个整体完成度,与可交付线比较。产出 JSON + CSV + SVG。

#!/usr/bin/env python3
# file: sim/o16_delivery.py
# tier: S
# verify: /opt/homebrew/bin/python3 sim/o16_delivery.py --config config/ --out out/
"""卷四交付物完整性打分(S 层):把交付清单按权重折算成整体完成度,判定是否达可交付线。

读 config/o16_delivery.json 的交付项。每项 status 映射成完成度千分比:
    done → 1000    partial → 500    todo → 0
整体完成度 = Σ(weight × statusPermille) // Σweight(千分比),达到 readyThresholdPermille 即判可交付。
另列出「缺口项」(status != done)与「待办权重」。产出 JSON + CSV + SVG。全整数、无浮点。
真实交付物内容与验收结论需真实业务环境,命令行不可复现(X)。
"""
from __future__ import annotations

import argparse
import csv
import json
import os
import xml.etree.ElementTree as ET

STATUS_PERMILLE = {"done": 1000, "partial": 500, "todo": 0}
STATUS_COLOR = {"done": "#16a34a", "partial": "#f59e0b", "todo": "#dc2626"}


def load(d: str) -> dict:
    with open(os.path.join(d, "o16_delivery.json"), encoding="utf-8") as f:
        return json.load(f)


def draw_svg(path: str, items: list, score: int, threshold: int) -> int:
    """纯 Python SVG:每个交付项一根权重条(按完成度着色)+ 一条可交付线。"""
    W, PAD, ROW = 760, 300, 34
    H = 74 + ROW * len(items)
    root = ET.Element("svg", {"xmlns": "http://www.w3.org/2000/svg", "width": str(W),
                              "height": str(H), "viewBox": f"0 0 {W} {H}"})
    ET.SubElement(root, "rect", {"width": str(W), "height": str(H), "fill": "#ffffff"})
    ET.SubElement(root, "text", {"x": "16", "y": "28", "font-size": "15",
                                 "fill": "#111827"}).text = (
        f"卷四交付物完整性:整体 {score}‰(可交付线 {threshold}‰)")
    span = W - PAD - 24
    total_w = sum(it["weight"] for it in items) or 1
    for i, it in enumerate(items):
        y = 52 + i * ROW
        w = span * it["weight"] // total_w
        ET.SubElement(root, "text", {"x": "16", "y": str(y + 18), "font-size": "11",
                                     "fill": "#374151"}).text = it["name"][:26]
        ET.SubElement(root, "rect", {"x": str(PAD), "y": str(y + 4), "width": str(w),
                                     "height": "18", "rx": "3",
                                     "fill": STATUS_COLOR[it["status"]]})
        ET.SubElement(root, "text", {"x": str(PAD + w + 6), "y": str(y + 17), "font-size": "10",
                                     "fill": "#6b7280"}).text = (
            f'{it["status"]} · 权重 {it["weight"]} · {it["statusPermille"]}‰')
    os.makedirs(os.path.dirname(path), exist_ok=True)
    ET.ElementTree(root).write(path, encoding="utf-8", xml_declaration=True)
    return len(ET.tostring(root))


def main() -> int:
    ap = argparse.ArgumentParser()
    ap.add_argument("--config", default="config/")
    ap.add_argument("--out", default="out/")
    a = ap.parse_args()
    cfg = load(a.config)
    items = []
    for it in cfg["items"]:
        sp = STATUS_PERMILLE[it["status"]]
        items.append(dict(it, statusPermille=sp, weighted=it["weight"] * sp))
    total_w = sum(it["weight"] for it in items)
    total_weighted = sum(it["weighted"] for it in items)
    score = total_weighted // total_w
    threshold = cfg["readyThresholdPermille"]
    gaps = [it for it in items if it["status"] != "done"]
    digest = f"items={len(items)},weight={total_w},score={score},ready={str(score >= threshold).lower()}" \
        + ";gaps=" + ",".join(f'{g["id"]}:{g["status"]}' for g in gaps)

    os.makedirs(a.out, exist_ok=True)
    svg_bytes = draw_svg(os.path.join(a.out, "o16_delivery.svg"), items, score, threshold)
    with open(os.path.join(a.out, "o16_delivery.csv"), "w", encoding="utf-8", newline="") as f:
        w = csv.writer(f)
        cols = ("id", "name", "chapter", "weight", "status", "statusPermille", "weighted")
        w.writerow(cols)
        for it in items:
            w.writerow([it[k] for k in cols])
    rep = {"version": cfg["version"], "game": cfg["game"], "observedAt": cfg["observedAt"],
           "itemCount": len(items), "totalWeight": total_w, "totalWeighted": total_weighted,
           "scorePermille": score, "readyThresholdPermille": threshold,
           "ready": score >= threshold, "gaps": [{"id": g["id"], "name": g["name"],
                                                  "status": g["status"], "weight": g["weight"]} for g in gaps],
           "pendingWeight": sum(g["weight"] for g in gaps), "items": items, "digest": digest,
           "svg": os.path.join(a.out, "o16_delivery.svg"), "svgBytes": svg_bytes}
    with open(os.path.join(a.out, "o16_delivery.json"), "w", encoding="utf-8") as f:
        json.dump(rep, f, ensure_ascii=False, indent=2)
    print(json.dumps({"itemCount": len(items), "totalWeight": total_w, "scorePermille": score,
                      "readyThresholdPermille": threshold, "ready": score >= threshold,
                      "gaps": rep["gaps"], "pendingWeight": rep["pendingWeight"],
                      "digest": digest, "svgBytes": svg_bytes}, ensure_ascii=False, indent=2))
    return 0


if __name__ == "__main__":
    raise SystemExit(main())

本机跑 for f in sim/o16_*.py; do /opt/homebrew/bin/python3 "$f" --config config/ --out out/; done,交付完整度打分的真实输出(同机相对量级,绝对数值不可移植):

{
  "itemCount": 12,
  "totalWeight": 115,
  "scorePermille": 930,
  "readyThresholdPermille": 900,
  "ready": true,
  "gaps": [
    {
      "id": "retro",
      "name": "案例复盘与决策对照表(第 15 章)",
      "status": "partial",
      "weight": 6
    },
    {
      "id": "appendix-e",
      "name": "附录 E(配置与清单索引)",
      "status": "todo",
      "weight": 5
    }
  ],
  "pendingWeight": 11,
  "digest": "items=12,weight=115,score=930,ready=true;gaps=retro:partial,appendix-e:todo",
  "svgBytes": 4375
}

十二项、总权重 115,整体完成度 930‰,高于可交付线 900‰——判定可交付。别被 ready=true 冲昏头:它只说「按这套权重够了」,缺口项 retro(部分)与 appendix-e(未办)仍然躺在那里,待办权重合计 11。

交付物总表

id交付物归属章权重状态完成度
toc卷四目录篇165done1000‰
ua-config投放配置集1–315done1000‰
attr-agg归因与聚合配置集4–615done1000‰
iap-comp内购与合规清单7–812done1000‰
channel-bd渠道与商务表9–1012done1000‰
lic-fin资质与财务表11–1212done1000‰
ops-sop运营 SOP 与例会编排138done1000‰
unit-pnl单位账与月度经营账1412done1000‰
retro案例复盘与决策对照表156partial500‰
handbook操盘手册168done1000‰
ledger全链路对账台账165done1000‰
appendix-e附录 E(配置与清单索引)165todo0‰

为什么按权重而不是按篇数

用「交了 10 篇 / 共 12 篇」也能算完成度,但那会得出 833‰——这个数会骗人。因为交付物的价值从来不均等:缺了投放配置集,整条买量链路直接停摆;缺了附录 E,操盘手只是翻文档时多花两分钟。按篇数计数把这两者等量齐观,按权重加权才还原了「缺哪一项更痛」。

权重本身也是可争议的口径,不是真理。这套权重是建模者按「缺了有多痛」给的,换一个操盘团队、换一个品类,权重会不同。所以清单里的 weight 字段是数据、是可改的输入,而不是写死在代码里的常量——改权重只需要改 config/o16_delivery.json,打分逻辑一行不用动。这正是「一份 config,多处消费」在交付管理上的复用。

与卷五《跨平台发行与出海》的衔接

本卷把国内市场的账跑通了:买量投放、归因聚合、内购合规、渠道结算、单位账与月度经营账,全部从一份共享 economy.json 复算,并且能对账、能打分。卷五要接的正是这套交付物的输出端——把国内跑通的账搬到多地区、多币种的发行环境里。

具体衔接点有四个。其一,货币与汇率:本卷所有金额都是「分」,单币种;卷五要引入多币种与汇率折算,economy.json 的整分口径需要扩展成「以某个基准币记账 + 各币种汇率」。其二,渠道分成:本卷的综合扣点 373‰ 是国内渠道加权折叠出来的一个数;卷五的每个海外渠道分成比例、税务预扣(withholding)规则都不同,第 12 章的结算台账要按地区重算。其三,合规差异:本卷第 7–8 章的内购与合规清单是中国大陆口径(版号、实名、未成年人保护);卷五面对的是 GDPR、COPPA、各地区的年龄分级与隐私同意,同一份合规清单要分叉成多套。其四,单位账的换算:第 14 章的单位账以「分」计,卷五要面对美元、欧元、日元、韩元等,LTV/CAC 的回本判断必须在统一基准币下做,否则汇率波动会把结论带偏。

需要说明的是:卷五《跨平台发行与出海》目前尚未落盘,其目录页与全部章节都还不存在,所以本节不给任何指向卷五的站内链接——现在链过去只会得到死链。以上衔接点先以文字交代,等卷五落地后再由那一卷的引言反向链回本节。本卷自身的入口与全部书稿可从书架全部书稿 进入;卷三《上线变现与长线运营》见《上线变现与长线运营》 ,两卷的差别是:卷三管从上线到长线运营的链路,卷四管商业运营的账与交付,卷五管跨平台与出海。

缺口处置:两项怎么办

清单里剩两个缺口,处置方式不同。appendix-e(附录 E:配置与清单索引,todo)是纯工作量缺口——它只是把全卷散落的 config/*.json 与各章清单汇成一张索引表,没有不确定性,补齐即可,权重 5 也印证它「缺了不致命」。补齐它能把完成度再抬一档。

retro(案例复盘与决策对照表,partial)则是认知缺口,不能靠堆时间解决。它的骨架已经填了,缺的是把真实操盘里的关键决策(为什么这版素材被砍、为什么这个出价策略中止)与当时的判断依据对照起来——这依赖真实业务环境,命令行不可复现(X),故本节只能把它标成 partial 而不是 done。

对这两个缺口的正确态度是:不要为了把分数刷到 1000‰ 而把 partial 改成 done。清单的价值在于如实,一旦为了好看而虚报完成态,整张清单就失去了「看清缺口」的功能——这比缺口本身更危险。

自检表

检查项合格标准
清单完整16 章每章至少一项交付物,权重为正整数
状态合法status 只取 done / partial / todo,映射 1000/500/0
缺口可见非完成态不被抹平,缺口与待办权重单独列出
口径可改权重是配置数据,打分逻辑不写死权重
判定有据整体完成度与可交付线都来自同一份配置
边界真实交付物内容与验收结论标 X

至此卷四收束。十六节从「账户与出价」一路走到「交付与出海」,留下了一套能跑、能对账、能打分的商业运营台账——它不保证你赚钱,但能让你在每一步都算得清账、看得见缺口。

阅读导航:上一节:16.2 全链路对账与仿真验收 · 下一节:回到目录 。

继续阅读

探索更多技术文章

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

全部文章 返回首页

「minigame」更多文章

  1. 附录 F:全书公式、参数与数据集索引
  2. 9.3 同意管理与数据出境
  3. 9.2 COPPA 与儿童向游戏的限制