本节目标:把前十四章的产出登记成一份可判定的评审材料清单,让立项评审从「讲一遍故事」变成「逐项核对判据」。
站内有一个 PRD 原型目录 ,里面是一批可交互原型文档——每个产品一份 PRD,讲的是这个产品长什么样、有哪些页面、每个页面放什么。本章与它的分工是清楚的:那边是单品的原型文档,这边是把卷一前十四章的产出装配成一份可评审、可验收、可交付的设计案。原型回答「做出来是什么样」,设计案回答「凭什么现在投人做、做完怎么验收」。
前十四章一路把「做什么、怎么做、值不值得做」拆成了可量化的结论:品类评分、包体预算、留存假设、收益模型、开发排期。但这些结论散落在十四份文档里,如果直接丢进评审会,结果一定是制作人讲、老板听、谁印象深谁说了算。本章要做的,是把它们收束成一份能被逐条核对的东西。
15.1 立项评审材料与流程
15.1.1 评审不是汇报,而是一道闸门
大多数小团队的「立项评审」其实是汇报:主策花四十分钟讲一遍愿景,老板凭感觉点头或摇头。这种评审有两个致命问题——没有明确的输入(讲完就算过),没有明确的判据(过不过看心情)。它的产物是一句「那就做吧」,而这句话在下一次分歧出现时毫无约束力。
真正有用的评审是一道闸门(gate),它必须有三个明确的东西:
- 输入:一组材料,缺任何一项,评审会开不成。
- 判据:每项材料自带「通过」的标准,标准是可以被核对的,不是感觉。
- 输出:一个明确结论——
go(全量投入)、conditional-go(带条件的有限投入)、no-go(不做或回炉)。
闸门的意义在于可复核。三个月后项目卡住,回头看当时评审通过的判据,能回答「是我们判断错了,还是执行走样了」。没有判据的评审,事后连复盘都无从下手。
15.1.2 材料清单:三件套缺一不可
一份材料要能被评审,光有内容不够,必须同时具备三件套:
| 要素 | 回答什么问题 | 缺了会怎样 |
|---|---|---|
| 负责人 owner | 谁主答、谁兜底 | 会上无人能拍板,问题悬空 |
| 交付时点 dueMilestone | 评审会前什么时候必须到位 | 会上临时找数据,结论不可信 |
| 通过判据 passCriteria | 达到什么标准算「过」 | 无法判定,只能靠印象 |
这三件套是本节的核心主张。一份再漂亮的一页纸,如果没写「负责人是谁、什么时候交、过没过看哪几条」,它在评审会上就是不可判定的。
下面这份是 D 层配置:它把十二项评审材料、五个角色、三个里程碑登记成唯一真源。每项材料都带齐了 owner / dueMilestone / passCriteria。
{
"version": 1,
"game": "merge-island",
"note": "卷一立项评审材料清单(D 层)。每项材料必须给出负责人 owner / 交付时点 dueMilestone / 通过判据 passCriteria 三件套,缺一即不可评审。",
"gate": {
"name": "卷一立项评审",
"timing": "M3 里程碑末,玩法原型验证结论产出之后",
"decisionOptions": ["go", "conditional-go", "no-go"],
"passRule": "全部 required 材料齐备,且每项通过其全部 passCriteria"
},
"roles": [
{"id": "producer", "name": "制作人", "responsibility": "主持评审、汇总分歧、拍板 go/conditional-go/no-go"},
{"id": "design-lead", "name": "主策", "responsibility": "主答品类方向、核心循环、关卡与成长"},
{"id": "tech-lead", "name": "主程", "responsibility": "主答平台约束、包体预算、原型可行性"},
{"id": "analyst", "name": "数据分析", "responsibility": "主答收益模型、口径与情景假设"},
{"id": "finance", "name": "发行/财务", "responsibility": "主答开发预算、排期与止损线"}
],
"milestones": [
{"id": "M1", "name": "方向确认", "week": 2},
{"id": "M2", "name": "玩法定稿", "week": 6},
{"id": "M3", "name": "立项评审", "week": 10}
],
"materials": [
{"id": "market-one-pager", "name": "市场与机会一页纸", "fromChapter": 11, "owner": "design-lead", "dueMilestone": "M1", "required": true, "passCriteria": ["品类方向一句话说清", "差异化机会可被榜单数据支撑", "标注榜单观察时点"]},
{"id": "genre-selection", "name": "品类选择与加权评分", "fromChapter": 3, "owner": "design-lead", "dueMilestone": "M1", "required": true, "passCriteria": ["给出权重与打分表", "对权重做敏感度测试", "备选品类不少于 3 个"]},
{"id": "platform-constraint", "name": "平台约束与包体预算", "fromChapter": 4, "owner": "tech-lead", "dueMilestone": "M1", "required": true, "passCriteria": ["四条包体限额写成预算表", "首屏自足性有结论", "约束对玩法的裁剪点已列出"]},
{"id": "user-lifecycle", "name": "目标用户与生命周期", "fromChapter": 5, "owner": "design-lead", "dueMilestone": "M2", "required": true, "passCriteria": ["画像场景具体可验证", "付费意愿分层有依据", "留存结构给出假设值"]},
{"id": "core-loop", "name": "核心循环与最小原型", "fromChapter": 6, "owner": "design-lead", "dueMilestone": "M2", "required": true, "passCriteria": ["核心循环拆到动作级", "循环时长与反馈密度量化", "最小可玩原型范围明确"]},
{"id": "level-curve", "name": "关卡与难度曲线", "fromChapter": 7, "owner": "design-lead", "dueMilestone": "M2", "required": true, "passCriteria": ["关卡结构分级", "难度曲线与心流区间对照", "校准参数可调"]},
{"id": "progression", "name": "成长与放置收益", "fromChapter": 8, "owner": "design-lead", "dueMilestone": "M2", "required": true, "passCriteria": ["成长曲线给出锚点", "再来一局钩子明确", "离线收益有上限设计"]},
{"id": "social", "name": "社交与裂变机制", "fromChapter": 9, "owner": "design-lead", "dueMilestone": "M3", "required": false, "passCriteria": ["社交面有载体", "K 因子给出假设区间", "有社交压力保护措施"]},
{"id": "monetization-frontload", "name": "变现前置设计", "fromChapter": 10, "owner": "analyst", "dueMilestone": "M3", "required": true, "passCriteria": ["广告形态与场景清单", "内购点结构清楚", "变现与体验平衡点有依据"]},
{"id": "prototype-result", "name": "玩法原型验证结论", "fromChapter": 12, "owner": "design-lead", "dueMilestone": "M2", "required": true, "passCriteria": ["可玩性指标达标", "迭代决策有记录", "结论可复现"]},
{"id": "revenue-model", "name": "收益模型与情景分析", "fromChapter": 13, "owner": "analyst", "dueMilestone": "M3", "required": true, "passCriteria": ["DAU×ARPDAU 口径一致", "给出悲观/基准/乐观三情景", "假设参数全部标注"]},
{"id": "budget-schedule", "name": "开发预算与排期", "fromChapter": 14, "owner": "finance", "dueMilestone": "M3", "required": true, "passCriteria": ["人力工时逐项列出", "外包素材成本有报价", "止损线写进文档"]}
]
}
注意两点。其一,材料不是按文档排的,是按评审时要回答的问题排的:每一项都对应前十四章里某一章的结论,fromChapter 就是溯源字段。其二,required 区分必交与可选——社交裂变标了 false,因为《合成小岛》的社交是加分项而非立项前提,这种取舍必须显式写进配置,而不是会上临时争论。
15.1.3 校验脚本:让缺项无处藏身
配置写完了,怎么保证它真的每项都齐?靠人眼扫一遍不可靠——十二项材料、每项三个字段、还有一个角色表和里程碑表要交叉核对。下面这段是 S 层校验脚本,它读上面那份配置,逐项断言三件套齐备、owner 属于角色表、dueMilestone 属于里程碑表、fromChapter 在 1–15 之内;任何一项缺漏,脚本以非 0 退出。
#!/usr/bin/env python3
# file: sim/ch15_check_review.py
# tier: S
# verify: /opt/homebrew/bin/python3 sim/ch15_check_review.py --config config/ --out out/
"""立项评审材料清单校验:读 config/ch15_review.json,断言每项评审材料都齐备
「负责人 owner / 交付时点 dueMilestone / 通过判据 passCriteria」三件套,
并校验负责人属于角色表、来源章号在 1–15 之内。任一材料缺项即非 0 退出。
产出 out/ch15_review.json(逐项结果 + 汇总)+ out/ch15_review.csv(逐材料一行)。
全程整数与字符串,无浮点。
"""
from __future__ import annotations
import argparse
import csv
import json
import os
import sys
REQUIRED_FIELDS = ("owner", "dueMilestone", "passCriteria")
def load(cfg_dir):
with open(os.path.join(cfg_dir, "ch15_review.json"), encoding="utf-8") as f:
return json.load(f)
def check_material(m, role_ids, milestone_ids, chapter_max):
"""返回该材料的缺项与非法项清单。空列表表示该材料通过。"""
problems = []
for field in REQUIRED_FIELDS:
val = m.get(field)
if field == "passCriteria":
if not isinstance(val, list) or not val:
problems.append(f"缺 passCriteria(通过判据为空)")
elif any((not isinstance(c, str)) or (not c.strip()) for c in val):
problems.append("passCriteria 含空判据")
else:
if not isinstance(val, str) or not val.strip():
problems.append(f"缺 {field}")
if m.get("owner") and m["owner"] not in role_ids:
problems.append(f"owner {m['owner']} 不在角色表")
if m.get("dueMilestone") and m["dueMilestone"] not in milestone_ids:
problems.append(f"dueMilestone {m['dueMilestone']} 不在里程碑表")
ch = m.get("fromChapter")
if not isinstance(ch, int) or not (1 <= ch <= chapter_max):
problems.append(f"fromChapter {ch} 越界")
return problems
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--config", default="config/")
ap.add_argument("--out", default="out/")
args = ap.parse_args()
cfg = load(args.config)
role_ids = {r["id"] for r in cfg["roles"]}
milestone_ids = {m["id"] for m in cfg["milestones"]}
materials = cfg["materials"]
rows, failures = [], []
for m in materials:
problems = check_material(m, role_ids, milestone_ids, 15)
rows.append({
"id": m["id"],
"name": m["name"],
"fromChapter": m["fromChapter"],
"owner": m.get("owner"),
"dueMilestone": m.get("dueMilestone"),
"criteriaCount": len(m.get("passCriteria") or []),
"required": bool(m.get("required")),
"pass": not problems,
"problems": problems,
})
if problems:
failures.append((m["id"], problems))
covered_chapters = sorted({m["fromChapter"] for m in materials})
required_missing = [m["id"] for m in materials
if m.get("required") and check_material(m, role_ids, milestone_ids, 15)]
os.makedirs(args.out, exist_ok=True)
report = {
"source": "sim/ch15_check_review.py",
"gate": cfg["gate"]["name"],
"materialCount": len(materials),
"requiredCount": sum(1 for m in materials if m.get("required")),
"roleCount": len(cfg["roles"]),
"coveredChapters": covered_chapters,
"coveragePermille": len(covered_chapters) * 1000 // 15,
"materials": rows,
"failures": [{"id": i, "problems": p} for i, p in failures],
"ok": not failures,
}
with open(os.path.join(args.out, "ch15_review.json"), "w", encoding="utf-8") as f:
json.dump(report, f, ensure_ascii=False, indent=2)
with open(os.path.join(args.out, "ch15_review.csv"), "w", encoding="utf-8", newline="") as f:
w = csv.writer(f)
w.writerow(["id", "name", "fromChapter", "owner", "dueMilestone",
"criteriaCount", "required", "pass"])
for r in rows:
w.writerow([r["id"], r["name"], r["fromChapter"], r["owner"], r["dueMilestone"],
r["criteriaCount"], str(r["required"]).lower(), str(r["pass"]).lower()])
print(f"gate={cfg['gate']['name']} materials={len(materials)} "
f"required={report['requiredCount']} roles={len(cfg['roles'])}")
print(f"覆盖章号 {covered_chapters}({len(covered_chapters)}/15)")
for r in rows:
mark = "PASS" if r["pass"] else "FAIL"
print(f" {mark} {r['id']:<24} owner={r['owner']:<12} due={r['dueMilestone']} "
f"criteria={r['criteriaCount']}")
for mid, probs in failures:
print(f" FAIL {mid}: {'; '.join(probs)}")
if required_missing:
print(f" FAIL 必交材料缺项:{required_missing}")
print("== 评审材料清单:OK" if report["ok"] else "== 评审材料清单:FAIL")
return 0 if report["ok"] else 1
if __name__ == "__main__":
sys.exit(main())
本机实跑输出:
gate=卷一立项评审 materials=12 required=11 roles=5
覆盖章号 [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14](12/15)
PASS market-one-pager owner=design-lead due=M1 criteria=3
PASS genre-selection owner=design-lead due=M1 criteria=3
PASS platform-constraint owner=tech-lead due=M1 criteria=3
PASS user-lifecycle owner=design-lead due=M2 criteria=3
PASS core-loop owner=design-lead due=M2 criteria=3
PASS level-curve owner=design-lead due=M2 criteria=3
PASS progression owner=design-lead due=M2 criteria=3
PASS social owner=design-lead due=M3 criteria=3
PASS monetization-frontload owner=analyst due=M3 criteria=3
PASS prototype-result owner=design-lead due=M2 criteria=3
PASS revenue-model owner=analyst due=M3 criteria=3
PASS budget-schedule owner=finance due=M3 criteria=3
== 评审材料清单:OK
逐条读这份输出。十二项材料全部 PASS,required 为 11(社交那项可选)。覆盖章号是 3–14,不含 1、2、15:第 1、2 章的市场与竞品结论被并进了「市场与机会一页纸」和「品类选择」两项里,第 15 章本身是评审章,不给自己准备材料。这不是缺漏,而是材料粒度按问题而非按章节的自然结果——脚本报出覆盖率是 12/15,是让人一眼看到「哪些章的结论还没有材料承接」,而不是要求每章各配一项。
这套脚本真正的价值在失败时。把任意一项材料的 dueMilestone 删掉再跑,输出会变成:
FAIL market-one-pager owner=design-lead due=None criteria=3
FAIL market-one-pager: 缺 dueMilestone
FAIL 必交材料缺项:['market-one-pager']
== 评审材料清单:FAIL
脚本以退出码 1 结束。这意味着这份校验可以直接挂进交付流水线:材料不齐,评审会就开不成——闸门是硬的,不是靠自觉。数字层面这些都是本机相对量级,换一份材料清单结论会变;可移植的是这套「配置即真源 + 脚本断言」的流程。
15.1.4 评审流程与角色分工
材料齐了,流程也要固定下来,否则会开成漫谈。一条可复用的评审流程是四步:
- 会前核对:跑一遍校验脚本,材料不全直接推迟,不占用会议时间。
- 逐项过判据:每项材料由负责人主答,评审人逐条对照
passCriteria打勾或打叉,不做整体印象评价。 - 汇总分歧:只讨论打叉的条目,打勾的直接过。分歧点记进会议纪要。
- 给结论:制作人基于打叉数量与严重度,给
go/conditional-go/no-go。
角色分工也在配置里登记好了:主策主答品类、循环、关卡、成长;主程主答平台约束与包体;数据分析主答收益模型与口径;发行/财务主答预算与止损线;制作人主持并拍板。谁主答,谁就要为那一项的判据负责——这是把「负责人」写进材料三件套的意义。
15.1.5 三种决策与触发条件
评审的输出不是「做/不做」两分,而是三档,每档有明确的触发条件:
| 结论 | 触发条件 | 后续动作 |
|---|---|---|
go | 全部 required 材料通过,无严重打叉 | 全量投入,进入卷二引擎与实现 |
conditional-go | 少数非致命项打叉 | 带条件立项,限定范围,设定复查点 |
no-go | 致命项打叉(如收益模型不成立、预算远超止损线) | 回炉或放弃,产出物归档 |
关键在于 conditional-go 这一档。很多团队只有「做」和「不做」,于是把「有点问题但想做」硬塞进 go,问题一路带到开发中。把中间档显式定义出来,问题就被写成了条件与复查点,而不是被乐观情绪盖过去。
小结
- 立项评审是一道闸门,有明确的输入(材料)、判据(passCriteria)与输出(go / conditional-go / no-go),它的价值是可复核。
- 每项材料必须有三件套:负责人、交付时点、通过判据。缺任一即不可判定。
- D 层配置把十二项材料、五个角色、三个里程碑写成唯一真源;S 层脚本逐项断言三件套齐备,缺项以非 0 退出,可直接挂进交付流水线。
- 本机实跑:十二项材料全 PASS,覆盖章号 3–14(材料粒度按问题而非按章);删掉一项
dueMilestone后脚本报 FAIL 并以退出码 1 结束。 - 评审给三档结论,
conditional-go把「有点问题但想做」写成条件与复查点,避免问题带进开发。
材料清单是「评审要什么」,下一节把前十四章的产出装配成设计案本身——那是设计案的目录结构问题。
阅读导航:上一节:里程碑与风险缓冲 · 下一节:设计案装配 。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。