这是全书三卷一百五十篇的收官章。前面十五章把《合成小岛》从选品类讲到了上线门禁与长线运营,但它们各自成章——真正要交到团队手里的,不是十五份独立的分析,而是一份能被别人接住、能一路跑到上线的完整方案。收官章要回答的就是这件事:三卷怎么装配成一份方案、装配完怎么验收、验收完怎么交付。
先说分工。本站有一篇小程序小游戏开发综述 ,那是一篇约百行的单篇概览,覆盖引擎选型、性能与变现的要点罗列;本套是成体系、可运行的三卷——每一条结论背后都有一份能跑的配置与脚本,收官章更是把三卷的产物装配成一条可复现的链路。概览告诉你「小游戏开发涉及哪些方面」,本套告诉你「每一个方面具体怎么做、怎么验」。另需声明:本章综合引用卷一与卷二的白名单章节,是为了把三卷串成一条链路,不是重复它们的框架。
16.1 把三卷装配成完整方案
16.1.1 装配要解决的是「接得住」
一份方案交付失败,往往不是内容不够,而是接不住:做实现的拿到设计案,发现少了一章;做运营的拿到上线清单,发现某个门禁没有对应产出物;换个人接手,发现「这一段到底归哪一卷、哪几章」说不清。装配要做的,就是把「接得住」变成可核对的项——每段链路引用哪一卷哪几章、产出什么、谁负责、过哪道门,全部登记清楚。
这与卷一收口时的做法一脉相承:卷一的交付物验收与下一卷衔接 已经证明「设计案本身也能被脚本核对」。本节把同一套思路放大到三卷:不是核对一卷的五十篇,而是核对三卷装配后的一条完整链路。
16.1.2 装配清单(D 层)
下面这段是 D 层装配清单。它登记三卷的章号(design 15 章、engine 16 章、launch 16 章)、从立项到长线运营的十段链路(每段引用产出它的卷与章、产出什么产物、谁负责、过哪道门),以及必须齐备的八份交付物与十道验收门。它只是数据——真正逐条核对的是下一节的 S 层脚本。
{
"version": 1,
"series": "《小游戏开发实战》",
"game": "merge-island",
"observedAt": "2026-10",
"note": "三卷装配清单(D 层):把卷一设计、卷二实现、卷三运营的产出装配成一份可交付的上线方案。volumes 登记三卷的章号;stages 声明从立项到长线运营的十段链路,每段引用产出它的卷与章;deliverables 列出必须齐备的交付物与责任人、证据;gates 是装配完成后要过的验收门。章标题为摘要,真实标题以各卷正文为准。",
"volumes": [
{ "id": "design", "name": "市场品类与产品设计", "prefix": "mg-design-", "chapters": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] },
{ "id": "engine", "name": "引擎实现与体验打磨", "prefix": "mg-engine-", "chapters": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16] },
{ "id": "launch", "name": "上线变现与长线运营", "prefix": "mg-launch-", "chapters": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16] }
],
"stages": [
{ "id": "market-fit", "name": "市场与品类选择", "volume": "design", "chapters": [1, 2, 3], "artifact": "立项一页纸", "owner": "制作人", "gate": "G1" },
{ "id": "design-spec", "name": "玩法与数值设计", "volume": "design", "chapters": [6, 7, 8, 10], "artifact": "设计案", "owner": "主策", "gate": "G2" },
{ "id": "budget-plan", "name": "预算与排期", "volume": "design", "chapters": [11, 13, 14], "artifact": "预算表", "owner": "制作人", "gate": "G3" },
{ "id": "core-loop", "name": "核心循环与状态机", "volume": "engine", "chapters": [5, 6], "artifact": "可运行内核", "owner": "客户端", "gate": "G4" },
{ "id": "platform-adapter", "name": "平台适配层", "volume": "engine", "chapters": [8, 10], "artifact": "一份逻辑两种宿主", "owner": "客户端", "gate": "G5" },
{ "id": "render-perf", "name": "渲染与性能", "volume": "engine", "chapters": [3, 9, 14], "artifact": "性能基线", "owner": "客户端", "gate": "G6" },
{ "id": "monetization", "name": "变现与收入模型", "volume": "launch", "chapters": [1, 2, 3], "artifact": "收入模型", "owner": "商业化", "gate": "G7" },
{ "id": "launch-gate", "name": "上线门禁", "volume": "launch", "chapters": [9, 10], "artifact": "上线清单", "owner": "运营", "gate": "G8" },
{ "id": "growth-retention", "name": "增长与留存", "volume": "launch", "chapters": [6, 7, 8], "artifact": "漏斗看板", "owner": "增长", "gate": "G9" },
{ "id": "ops-loop", "name": "长线运营", "volume": "launch", "chapters": [12, 13], "artifact": "版本列车", "owner": "运营", "gate": "G10" }
],
"deliverables": [
{ "id": "D1", "name": "设计案", "stage": "design-spec", "owner": "主策", "evidence": "out/l16_assembly.json#design-spec", "required": true },
{ "id": "D2", "name": "预算与排期表", "stage": "budget-plan", "owner": "制作人", "evidence": "out/l16_assembly.json#budget-plan", "required": true },
{ "id": "D3", "name": "可运行内核与单测", "stage": "core-loop", "owner": "客户端", "evidence": "core/test/l16_acceptance.test.ts", "required": true },
{ "id": "D4", "name": "平台适配层与兼容矩阵", "stage": "platform-adapter", "owner": "客户端", "evidence": "out/l16_assembly.json#platform-adapter", "required": true },
{ "id": "D5", "name": "收入模型与回本测算", "stage": "monetization", "owner": "商业化", "evidence": "out/l16_fullchain.json", "required": true },
{ "id": "D6", "name": "上线检查清单", "stage": "launch-gate", "owner": "运营", "evidence": "out/l16_assembly.json#launch-gate", "required": true },
{ "id": "D7", "name": "漏斗与留存看板口径", "stage": "growth-retention", "owner": "增长", "evidence": "out/l16_assembly.csv", "required": true },
{ "id": "D8", "name": "可玩原型回放产物", "stage": "ops-loop", "owner": "客户端", "evidence": "web/dist/l16.html", "required": true }
],
"gates": [
{ "id": "G1", "name": "品类通过加权评分线", "stage": "market-fit", "hard": true },
{ "id": "G2", "name": "设计案每章有产出物", "stage": "design-spec", "hard": true },
{ "id": "G3", "name": "预算不超止损线", "stage": "budget-plan", "hard": true },
{ "id": "G4", "name": "核心循环单测全绿", "stage": "core-loop", "hard": true },
{ "id": "G5", "name": "两种宿主同摘要", "stage": "platform-adapter", "hard": true },
{ "id": "G6", "name": "首帧与包体达标", "stage": "render-perf", "hard": true },
{ "id": "G7", "name": "回本日不超窗口", "stage": "monetization", "hard": true },
{ "id": "G8", "name": "上线清单门禁项全过", "stage": "launch-gate", "hard": true },
{ "id": "G9", "name": "D1/D7/D30 达锚点", "stage": "growth-retention", "hard": true },
{ "id": "G10", "name": "版本列车排期无空窗", "stage": "ops-loop", "hard": false }
]
}
十段链路横跨三卷:design 三段(选品、设计、预算)、engine 三段(内核、适配、性能)、launch 四段(变现、门禁、增长、运营)。每一段都绑定一道门,其中九道是硬门禁、最后一道「版本列车排期无空窗」是软门禁——它不拦住上线,只提醒运营别把版本排空。
16.1.3 装配校验脚本(S 层)
下面这段是 S 层校验脚本。它读上面那份清单,跑五条检查:V1 三卷章号连续、前缀合法;V2 每段链路引用的卷与章都在对应卷范围内;V3 三卷都被链路覆盖;V4 每份必需交付物都有责任人与证据;V5 门禁与链路一一对应、无悬空。任一条不通过即非 0 退出,产出 JSON、CSV 与一张按卷着色的链路覆盖图。
#!/usr/bin/env python3
# file: sim/l16_assembly.py
# tier: S
# verify: /opt/homebrew/bin/python3 sim/l16_assembly.py --config config/ --out out/
"""三卷装配校验(S 层):读 config/l16_assembly.json,把「一份可交付的上线方案」
拆成可核对的项,逐条真跑:
V1 三卷登记完整 前缀 / 章号 / 标题齐备,章号连续 1..N
V2 链路引用合法 每段 stage 引用的卷与章都在对应卷的章号范围内
V3 链路覆盖 design / engine / launch 三卷各有 stage 引用
V4 交付物齐备 每份 required 交付物都有 owner 与 evidence,且 stage 存在
V5 门禁闭环 每个 stage 都有一个 gate,每个 gate 都指向存在的 stage
产出 out/l16_assembly.json + out/l16_assembly.csv + out/l16_assembly.svg。
全整数、无浮点;图表用纯 Python SVG。章标题为摘要,真实标题以各卷正文为准。
"""
from __future__ import annotations
import argparse
import csv
import json
import os
import xml.etree.ElementTree as ET
VOL_COLOR = {"design": "#2563eb", "engine": "#7c3aed", "launch": "#10b981"}
def txt(parent: ET.Element, x: float, y: float, s: str, size: int = 12,
fill: str = "#374151", anchor: str | None = None) -> None:
a = {"x": str(x), "y": str(y), "font-size": str(size), "fill": fill}
if anchor:
a["text-anchor"] = anchor
ET.SubElement(parent, "text", a).text = s
def svg(path: str, stages: list[dict], vol_name: dict) -> int:
"""纯 Python SVG:每段 stage 一行,长度正比于它引用的章数,颜色按所属卷区分。"""
W, PAD, ROW = 760, 240, 34
H = 96 + ROW * len(stages)
r = ET.Element("svg", {"xmlns": "http://www.w3.org/2000/svg", "width": str(W),
"height": str(H), "viewBox": f"0 0 {W} {H}"})
ET.SubElement(r, "rect", {"width": str(W), "height": str(H), "fill": "#ffffff"})
txt(r, 16, 26, "《合成小岛》上线方案装配:链路分段与引用章数(D 层清单)", 15, "#111827")
for k, (vid, c) in enumerate(VOL_COLOR.items()):
ET.SubElement(r, "rect", {"x": str(16 + k * 200), "y": "40", "width": "12",
"height": "12", "rx": "2", "fill": c})
txt(r, 33 + k * 200, 51, vol_name[vid], 11)
bmax = W - PAD - 96
hi = max((len(s["chapters"]) for s in stages), default=1) or 1
for i, s in enumerate(stages):
y = 96 + i * ROW
w = bmax * len(s["chapters"]) // hi
txt(r, 16, y + 20, s["name"])
ET.SubElement(r, "rect", {"x": str(PAD), "y": str(y + 6), "width": str(w), "height": "18",
"rx": "4", "fill": VOL_COLOR.get(s["volume"], "#6b7280")})
txt(r, PAD + w + 8, y + 20, f'{s["volume"]} 第 {",".join(str(c) for c in s["chapters"])} 章',
11, "#6b7280")
os.makedirs(os.path.dirname(path), exist_ok=True)
ET.ElementTree(r).write(path, encoding="utf-8", xml_declaration=True)
return os.path.getsize(path)
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--config", default="config/")
ap.add_argument("--out", default="out/")
a = ap.parse_args()
with open(os.path.join(a.config, "l16_assembly.json"), encoding="utf-8") as f:
cfg = json.load(f)
vols = {v["id"]: v for v in cfg["volumes"]}
vol_name = {v["id"]: v["name"] for v in cfg["volumes"]}
ch_range = {v["id"]: set(v["chapters"]) for v in cfg["volumes"]}
stage_ids = {s["id"] for s in cfg["stages"]}
gate_by_stage = {g["stage"] for g in cfg["gates"]}
problems: list[str] = []
# V1 三卷登记完整:前缀 / 章号连续 1..N / 标题非空
v1 = []
for v in cfg["volumes"]:
chs = list(v["chapters"])
if chs != list(range(1, len(chs) + 1)):
v1.append(f'{v["id"]} 章号不连续:{chs}')
if not v["prefix"].endswith("-") or len(v["prefix"]) < 6:
v1.append(f'{v["id"]} 前缀不合法:{v["prefix"]}')
problems += v1
# V2 链路引用合法
v2 = []
for s in cfg["stages"]:
if s["volume"] not in vols:
v2.append(f'{s["id"]} 引用未知卷 {s["volume"]}')
continue
for c in s["chapters"]:
if c not in ch_range[s["volume"]]:
v2.append(f'{s["id"]} 引用 {s["volume"]} 第 {c} 章越界')
problems += v2
# V3 链路覆盖:三卷各有 stage
covered = {s["volume"] for s in cfg["stages"]}
v3 = [] if covered == set(vols) else [f"未覆盖的卷:{sorted(set(vols) - covered)}"]
problems += v3
# V4 交付物齐备
v4 = []
for d in cfg["deliverables"]:
if d["stage"] not in stage_ids:
v4.append(f'{d["id"]} 指向不存在的 stage {d["stage"]}')
if d["required"] and (not d.get("owner") or not d.get("evidence")):
v4.append(f'{d["id"]} 缺 owner 或 evidence')
problems += v4
# V5 门禁闭环
v5 = []
for s in cfg["stages"]:
if s["id"] not in gate_by_stage:
v5.append(f'stage {s["id"]} 无门禁')
for g in cfg["gates"]:
if g["stage"] not in stage_ids:
v5.append(f'gate {g["id"]} 指向不存在的 stage {g["stage"]}')
problems += v5
checks = [
{"id": "V1", "name": "三卷登记完整", "pass": not v1, "problems": v1},
{"id": "V2", "name": "链路引用合法", "pass": not v2, "problems": v2},
{"id": "V3", "name": "链路覆盖三卷", "pass": not v3, "problems": v3},
{"id": "V4", "name": "交付物齐备", "pass": not v4, "problems": v4},
{"id": "V5", "name": "门禁闭环", "pass": not v5, "problems": v5},
]
ok = all(c["pass"] for c in checks)
stages = []
for s in cfg["stages"]:
stages.append({"id": s["id"], "name": s["name"], "volume": s["volume"],
"chapters": s["chapters"], "chapterCount": len(s["chapters"]),
"artifact": s["artifact"], "owner": s["owner"], "gate": s["gate"]})
vol_summary = [{"id": v["id"], "name": v["name"], "prefix": v["prefix"],
"chapterCount": len(v["chapters"]),
"stages": sum(1 for s in stages if s["volume"] == v["id"])}
for v in cfg["volumes"]]
os.makedirs(a.out, exist_ok=True)
sb = svg(os.path.join(a.out, "l16_assembly.svg"), stages, vol_name)
summary = {"version": cfg["version"], "source": "sim/l16_assembly.py", "game": cfg["game"],
"observedAt": cfg["observedAt"], "volumes": vol_summary,
"stageCount": len(stages), "deliverableCount": len(cfg["deliverables"]),
"gateCount": len(cfg["gates"]),
"hardGateCount": sum(1 for g in cfg["gates"] if g["hard"]),
"checks": checks, "problems": problems, "ok": ok, "svgBytes": sb}
with open(os.path.join(a.out, "l16_assembly.json"), "w", encoding="utf-8") as f:
json.dump(summary, f, ensure_ascii=False, indent=2)
with open(os.path.join(a.out, "l16_assembly.csv"), "w", encoding="utf-8", newline="") as f:
w = csv.writer(f)
w.writerow(["id", "name", "volume", "chapters", "chapterCount", "artifact", "owner", "gate"])
for s in stages:
w.writerow([s["id"], s["name"], s["volume"], ";".join(str(c) for c in s["chapters"]),
s["chapterCount"], s["artifact"], s["owner"], s["gate"]])
print(json.dumps({"stageCount": len(stages), "deliverableCount": len(cfg["deliverables"]),
"gateCount": len(cfg["gates"]), "hardGateCount": summary["hardGateCount"],
"volumes": vol_summary, "checks": {c["id"]: c["pass"] for c in checks},
"ok": ok, "svgBytes": sb}, ensure_ascii=False, indent=2))
return 0
if __name__ == "__main__":
raise SystemExit(main())
本机实跑输出(同机相对量级,绝对数值不可移植):
{
"stageCount": 10,
"deliverableCount": 8,
"gateCount": 10,
"hardGateCount": 9,
"volumes": [
{ "id": "design", "name": "市场品类与产品设计", "prefix": "mg-design-", "chapterCount": 15, "stages": 3 },
{ "id": "engine", "name": "引擎实现与体验打磨", "prefix": "mg-engine-", "chapterCount": 16, "stages": 3 },
{ "id": "launch", "name": "上线变现与长线运营", "prefix": "mg-launch-", "chapterCount": 16, "stages": 4 }
],
"checks": { "V1": true, "V2": true, "V3": true, "V4": true, "V5": true },
"ok": true,
"svgBytes": 3021
}
五条检查全 PASS。装配的意义在这一行 "ok": true 里:它不是「我觉得写完了」,而是「三卷的章号对得上、每一段链路都能指到具体章节、八份交付物都有主、十道门都能回到具体 stage」。任何一处笔误——把 engine 的某段误引成第 17 章、漏给一份交付物配证据——都会让对应的 V 项报 FAIL。
16.1.4 三卷在链路里的分工
把十段链路按卷归拢,三卷的分工一目了然:
| 卷 | 链路分段 | 交付的核心产物 |
|---|---|---|
| 卷一 市场品类与产品设计 | 市场与品类选择、玩法与数值设计、预算与排期 | 立项一页纸、设计案、预算表 |
| 卷二 引擎实现与体验打磨 | 核心循环与状态机、平台适配层、渲染与性能 | 可运行内核、一份逻辑两种宿主、性能基线 |
| 卷三 上线变现与长线运营 | 变现与收入模型、上线门禁、增长与留存、长线运营 | 收入模型、上线清单、漏斗看板、版本列车 |
卷一回答「做什么」,卷二回答「怎么做出来」,卷三回答「怎么赚、怎么留、怎么活得久」。三卷之间不是并列的三本书,而是一条流水线的三段:卷一的产物是卷二的输入(设计案约束选型),卷二的产物是卷三的输入(可运行内核才有留存与收入可谈),卷三的产物又反哺卷一的迭代(收入与留存数据决定下一版做什么)。下一节把这条流水线真正跑一遍。
小结
- 收官章的第一件事是装配:把三卷十五份分析变成一份能被接住、能跑到上线的完整方案。
- D 层装配清单登记三卷章号、十段链路、八份交付物与十道门;S 层脚本跑 V1–V5 五条检查,逐条真跑。
- 本机实跑:三卷 15/16/16 章、链路 10 段(design 3 / engine 3 / launch 4)、交付物 8 份、门禁 10 道(硬 9 软 1),五条检查全 PASS。
- 三卷是一条流水线的三段,不是并列的三本书;本节的装配清单把这条流水线登记成可核对的项。
阅读导航:上一节:目录 · 下一节:16.2 全链路仿真验收 。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。