站内游戏产业专题 有 81 篇,覆盖客服、社区、舆情、团队、外包、排期与复盘——那是「运营团队怎么组织、舆情怎么应对」的口径。本节不碰组织,只做一件更窄的事:把投放、结算、资质三条线上最常见的失败模式列成一张可判定的清单,让「这次是不是踩坑了」从一句感觉变成一条条能对照阈值的判定。它与卷三失败模式与避坑 那章分工不同:卷三讲的是玩法数值、买量留存、运营合规的失败,本节聚焦卷四自己这条线——投放后台、结算对账、资质办理。
三类失败,各有各的死法
失败不是一种,而是三类,每类的责任人和救命手段都不同:
| 域 | 谁负责 | 典型死法 | 救命手段 |
|---|---|---|---|
| 投放失败 | 投放操盘手 | CAC 高于 LTV、放量过猛、素材疲劳 | 关停、降量、换素材 |
| 结算失败 | 财务 / 商务 | 账期超现金缓冲、对账差异过大 | 砍账期、对账、调整渠道 |
| 资质失败 | 法务 / 发行 | 倒排不足、材料缺项 | 提前办、补材料 |
三类的共同点是:它们都不是「突然发生」,而是「指标早就越界了,只是没人看」。所以对付它们的办法不是事后救火,而是把每条失败模式写成一个可观测指标 + 一条阈值,定期体检。
失败判定:一个指标、一条阈值、一个方向
每条失败模式都写成同一个结构:一个可观测指标 observed、一条阈值 threshold、一个比较方向 comparator。判定规则简单到可以背下来:
triggered = (comparator == "gt") ? observed > threshold : observed < threshold
gap = |observed − threshold| # 越过阈值的绝对量
gapPermille = gap × 1000 // max(threshold, 1) # 越界幅度(千分比)
gt 是「越高越糟」(如 CPI、账期、对账差异),lt 是「越低越糟」(如点击到安装转化率、资质倒排天数)。一条场景触发失败,就说明这个指标已经越过红线;gapPermille 告诉你越了多少,用来排优先级。
S 层仿真:把清单跑成一份体检报告
下面这段是 S 层仿真脚本 sim/o15_failure.py。它读 config/o15_failure.json 的十条场景,逐条判定是否触发失败,算出越界幅度,再按域汇总成一份「失败体检报告」,产出 JSON + CSV + SVG。
#!/usr/bin/env python3
# file: sim/o15_failure.py
# tier: S
# verify: /opt/homebrew/bin/python3 sim/o15_failure.py --config config/ --out out/
"""投放、结算与资质三类典型失败的判定仿真:把「失败模式清单」变成可复算的布尔判定。
读 config/o15_failure.json 的每条场景,按比较方向判定是否触发失败:
triggered(s) = (comparator == "gt") ? observed > threshold : observed < threshold
gap(s) = |observed - threshold| # 越过阈值的绝对量
gapPermille(s) = gap * 1000 // max(threshold, 1) # 相对阈值的越界幅度
severity(s) = triggered ? "HIGH" : "OK"
再按 domain 汇总:某域内任一条触发,则该域判为 RISK,否则 OK。给出一份「失败体检报告」——
它不预测未来,只把复盘时填进去的观测值逐条对照阈值,把「拍脑袋觉得有问题」变成「这一条越界了多少」。
口径:observed 是模型侧填值,不是任何真实后台 / 结算单 / 审批结果的实测;真实结算差异与
真实审批结果需真实业务环境,命令行不可复现(X)。产出 out/o15_failure.json + .csv + .svg。
全整数、地板除,无浮点。
"""
from __future__ import annotations
import argparse
import csv
import json
import os
import xml.etree.ElementTree as ET
def judge(s: dict) -> dict:
obs, th, cmp = s["observed"], s["threshold"], s["comparator"]
triggered = (obs > th) if cmp == "gt" else (obs < th)
gap = abs(obs - th)
gap_permille = gap * 1000 // max(th, 1)
return dict(s, triggered=triggered, gap=gap, gapPermille=gap_permille,
severity="HIGH" if triggered else "OK")
def draw_svg(path: str, rows: list, domains: list) -> int:
"""纯 Python SVG:每个域一根横条——该域触发的失败条数 / 该域场景总数。"""
W, H, PAD, ROW = 760, 90 + 48 * len(domains), 200, 48
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 = "三类失败的体检:各域触发条数 / 场景总数"
span = W - PAD - 160
for i, d in enumerate(domains):
total = sum(1 for r in rows if r["domain"] == d["id"])
hit = sum(1 for r in rows if r["domain"] == d["id"] and r["triggered"])
y = 56 + i * ROW
w = span * hit // max(total, 1)
color = "#ef4444" if hit else "#16a34a"
ET.SubElement(root, "text", {"x": "16", "y": str(y + 20), "font-size": "12",
"fill": "#374151"}).text = d["name"]
ET.SubElement(root, "rect", {"x": str(PAD), "y": str(y + 6), "width": str(max(w, 1)),
"height": "22", "rx": "4", "fill": color})
ET.SubElement(root, "text", {"x": str(PAD + max(w, 1) + 8), "y": str(y + 22),
"font-size": "12", "fill": "#6b7280"}).text = f"{hit} / {total} 触发"
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()
with open(os.path.join(a.config, "o15_failure.json"), encoding="utf-8") as f:
cfg = json.load(f)
rows = [judge(s) for s in cfg["scenarios"]]
domain_summary = []
for d in cfg["domains"]:
in_domain = [r for r in rows if r["domain"] == d["id"]]
hit = sum(1 for r in in_domain if r["triggered"])
domain_summary.append({"id": d["id"], "name": d["name"], "total": len(in_domain),
"triggered": hit, "verdict": "RISK" if hit else "OK"})
triggered_ids = [r["id"] for r in rows if r["triggered"]]
digest = ",".join(f'{r["id"]}:{"1" if r["triggered"] else "0"}:{r["gapPermille"]}'
for r in rows) + f';hit={len(triggered_ids)}'
os.makedirs(a.out, exist_ok=True)
svg = draw_svg(os.path.join(a.out, "o15_failure.svg"), rows, cfg["domains"])
with open(os.path.join(a.out, "o15_failure.csv"), "w", encoding="utf-8", newline="") as f:
w = csv.writer(f)
cols = ("id", "domain", "name", "metric", "observed", "threshold", "comparator",
"unit", "triggered", "gap", "gapPermille", "severity", "note")
w.writerow(cols)
for r in rows:
w.writerow([r[k] for k in cols])
rep = {"version": cfg["version"], "game": cfg["game"], "observedAt": cfg["observedAt"],
"scenarioCount": len(rows), "triggeredCount": len(triggered_ids),
"triggeredIds": triggered_ids, "domainSummary": domain_summary, "scenarios": rows,
"worstScenario": max(rows, key=lambda r: (r["triggered"], r["gapPermille"]))["id"],
"digest": digest, "csv": os.path.join(a.out, "o15_failure.csv"),
"svg": os.path.join(a.out, "o15_failure.svg"), "svgBytes": svg}
with open(os.path.join(a.out, "o15_failure.json"), "w", encoding="utf-8") as f:
json.dump(rep, f, ensure_ascii=False, indent=2)
print(json.dumps({"domainSummary": domain_summary, "triggeredCount": len(triggered_ids),
"triggeredIds": triggered_ids,
"scenarios": [(r["id"], "FAIL" if r["triggered"] else "OK", r["gapPermille"])
for r in rows],
"worstScenario": rep["worstScenario"], "digest": digest},
ensure_ascii=False, indent=2))
return 0
if __name__ == "__main__":
raise SystemExit(main())
失败清单放在 config/o15_failure.json。observed 是复盘时在模型侧填进去的值,不是任何真实后台、结算单或审批结果的实测:
{
"version": 1,
"game": "merge-island",
"observedAt": "2026-10",
"note": "投放、结算与资质三类典型失败的判定清单。口径是「公开可观察机制」的建模假设:每条失败模式给出一个可观测指标 observed、一条阈值 threshold 与比较方向 comparator(gt=observed 超过阈值即判失败;lt=observed 低于阈值即判失败)。observed 是「复盘时在模型侧填进去的值」,不是任何真实后台、结算单或审批结果的实测;真实结算差异、真实审批结果需真实业务环境,命令行不可复现(X)。全整数,无浮点。",
"domains": [
{ "id": "ua", "name": "投放失败", "owner": "投放操盘手" },
{ "id": "settle", "name": "结算失败", "owner": "财务 / 商务" },
{ "id": "qual", "name": "资质失败", "owner": "法务 / 发行" }
],
"scenarios": [
{ "id": "ua-cac-over-ltv", "domain": "ua", "name": "单用户成本高于生命周期价值", "metric": "cacFen", "observed": 520, "threshold": 430, "comparator": "gt", "unit": "分", "note": "CAC 高于 LTV30,越投越亏;应关停或降量" },
{ "id": "ua-ramp-too-fast", "domain": "ua", "name": "放量节奏过猛", "metric": "rampPct", "observed": 200, "threshold": 130, "comparator": "gt", "unit": "%", "note": "单次预算爬坡超过学习期承载,易触发重新学习" },
{ "id": "ua-creative-fatigue", "domain": "ua", "name": "素材疲劳未及时替换", "metric": "creativeAgeDays", "observed": 21, "threshold": 14, "comparator": "gt", "unit": "天", "note": "同组素材在线超过衰减窗口,CPI 抬头" },
{ "id": "ua-funnel-drop", "domain": "ua", "name": "点击到安装漏斗异常", "metric": "ctiPermille", "observed": 145, "threshold": 180, "comparator": "lt", "unit": "‰", "note": "点击到安装转化率偏低,落地页或包体有问题" },
{ "id": "settle-long-cash-cycle", "domain": "settle", "name": "账期超过现金缓冲", "metric": "cashDays", "observed": 75, "threshold": 45, "comparator": "gt", "unit": "天", "note": "结算周期加冻结期超过可垫付天数,现金流断裂风险" },
{ "id": "settle-recon-gap", "domain": "settle", "name": "三方对账差异超阈", "metric": "reconDiffPermille", "observed": 85, "threshold": 30, "comparator": "gt", "unit": "‰", "note": "归因口径差异过大,结算金额对不上" },
{ "id": "settle-refund-spike", "domain": "settle", "name": "退款率异常", "metric": "refundPermille", "observed": 45, "threshold": 80, "comparator": "gt", "unit": "‰", "note": "退款率在阈值内,本项不判失败" },
{ "id": "qual-lead-short", "domain": "qual", "name": "资质倒排时间不足", "metric": "qualLeadDays", "observed": 45, "threshold": 90, "comparator": "lt", "unit": "天", "note": "距上线预留的资质办理天数不足,会卡上线" },
{ "id": "qual-missing-material", "domain": "qual", "name": "必填材料缺项", "metric": "missingMandatory", "observed": 2, "threshold": 0, "comparator": "gt", "unit": "项", "note": "版号/文网文/软著必填材料缺项,提审必被退回" },
{ "id": "qual-ready", "domain": "qual", "name": "资质齐备", "metric": "missingMandatory", "observed": 0, "threshold": 0, "comparator": "gt", "unit": "项", "note": "材料齐备,本项不判失败" }
]
}
在本机跑 for f in sim/o15_*.py; do /opt/homebrew/bin/python3 "$f" --config config/ --out out/; done,体检结果是(同机相对量级,绝对数值不可移植):
| 域 | 场景数 | 触发 | 判定 |
|---|---|---|---|
| 投放失败 | 4 | 4 | RISK |
| 结算失败 | 3 | 2 | RISK |
| 资质失败 | 3 | 2 | RISK |
十条场景里 8 条触发失败,只有 settle-refund-spike(退款率 45‰,低于 80‰ 阈值)与 qual-ready(材料齐备)两条安全。越界最狠的是 qual-missing-material:阈值 0、观测 2,gapPermille 达到 2000‰——必填材料缺一项就是硬失败,因为提审必被退回,没有商量余地。其次是 settle-recon-gap(对账差异 85‰ 对阈值 30‰,越界 1833‰),这条最隐蔽:它不直接让你亏钱,但会让「后台收入」和「到手现金」对不上,第 4.3 节的对账工具就是为它准备的。投放域的 4 条全中,说明一个不设止损的投放计划,几乎必然同时踩中 CPI、放量、素材三条线。
逐条摊开看,体检报告长这样(同机相对量级,绝对数值不可移植):
| 场景 | 指标 | 观测 | 阈值 | 方向 | 越界‰ | 判定 |
|---|---|---|---|---|---|---|
| ua-cac-over-ltv | cacFen | 520 | 430 | gt | 209 | FAIL |
| ua-ramp-too-fast | rampPct | 200 | 130 | gt | 538 | FAIL |
| ua-creative-fatigue | creativeAgeDays | 21 | 14 | gt | 500 | FAIL |
| ua-funnel-drop | ctiPermille | 145 | 180 | lt | 194 | FAIL |
| settle-long-cash-cycle | cashDays | 75 | 45 | gt | 666 | FAIL |
| settle-recon-gap | reconDiffPermille | 85 | 30 | gt | 1833 | FAIL |
| settle-refund-spike | refundPermille | 45 | 80 | gt | 437 | OK |
| qual-lead-short | qualLeadDays | 45 | 90 | lt | 500 | FAIL |
| qual-missing-material | missingMandatory | 2 | 0 | gt | 2000 | FAIL |
| qual-ready | missingMandatory | 0 | 0 | gt | 0 | OK |
这张表最有用的不是「谁 FAIL」,而是方向一栏:ua-funnel-drop 与 qual-lead-short 是 lt(越低越糟),其余都是 gt(越高越糟)。同一个「变差了」的感觉,在指标上可能是变大也可能是变小——方向写错,体检结论会完全反过来。所以清单里每条都必须显式写下 comparator,不能靠默认。
X 层:真实结算与审批结果不可复现
边界必须划清。上面算的是判定侧:给定观测值,某条失败模式是否触发。下面这些属于 X 层,需真实业务环境,命令行不可复现:
- 真实的结算差异(由平台侧逐笔核算、退款扣减与作弊扣减共同决定,不同账期对不上是常态);
- 真实的资质审批结果与时长(版号、文网文、软著的受理与审批节奏由主管部门决定);
- 真实的投放后台数据(学习期状态、计划消耗、素材衰减曲线都在平台侧,且口径常变)。
工程上正确的做法是:把可复现的判定规则与不可复现的真实数据分开——判定规则是清单(本节),真实观测值靠后台报表反标定(把实际 CPI、账期、对账差异、审批时长填回 config,体检立刻反映现实)。
自检表
| 检查项 | 合格标准 |
|---|---|
| 一指标一阈值 | 每条失败模式都有可观测指标 + 阈值,不含糊 |
| 方向明确 | gt / lt 写死在配置,不靠口头约定 |
| 越界量化 | 给出 gapPermille,能排优先级 |
| 分域汇总 | 按投放 / 结算 / 资质分别判 RISK / OK |
| 全整数 | 判定与越界幅度全为整数地板除,无浮点 |
| 边界清晰 | 真实结算与审批结果标 X,不写成「实测」 |
清单把「哪里会失败」摊开了,但同一个岔路口,往往有多个选择,而每个选择都各有代价。下一节我们把全卷的关键决策收成一张对照表,用一段可查询的 TS 内核把「该选哪个」变成可复算的加权打分。
阅读导航:上一节:15.1 《合成小岛》投放与变现复盘 · 下一节:15.3 关键决策对照表 。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。