16.1 出海发行方案装配

卷五十六节各写了一份交付物,收官第一件事是把它们装订成一份可交付的出海发行方案:目录、每章验收清单与工具链前缀三件事缺一不可。本节给出手册的装配清单(D 层),再用一份 TypeScript 内核校验「16 章齐全、章号唯一、三要素不缺、每章前缀等于 gNN」,并给出一枚可跨语言对拍的清单指纹。

站内《上线变现与长线运营》 把国内上线与长线运营的链路写完了(渠道机制、留存漏斗、活动与版本节奏);站内 SaaS 平台创业口径的小游戏专题 (25 篇)讲的是做小游戏平台本身(编辑器、分成体系、平台运营)。本卷两者都不重做——卷五只做跨平台发行与出海,而本章只做出海发行方案的装配与验收:不重做国内运营方法论,也不写平台创业。上一卷的收官16.1 装配成一份操盘手册 装配的是国内商业运营台账;本章装配的是出海发行方案。

方案不是把 16 份文档拼起来

一份能交付的出海发行方案,光有目录不够,光有正文也不够。它至少要把三样东西对齐到同一套章号上:

组成作用缺了会怎样
目录按章号索引到交付物与配置族找不到「第 11 章的收款方式表在哪」
验收清单每章一条可核对的合格标准交付了但没人知道算不算完成
工具链前缀每章在 pebble/ 的文件族(gNN)配置散落各处,改了 A 忘了 B

三样东西必须共用同一套章号与同一份清单文件,否则方案会在第一次更新时就散架:有人加了第 13 章的投放口径却忘了在目录里登记,有人删了清单项却没人发现。所以本节的做法不是「写一份方案」,而是写一份方案的装配清单,再用一段程序校验它自洽——清单是唯一真源,目录、检查项与前缀族都从它派生。

D 层:装配清单

下面这份是 D 层装配清单 config/g16_manifest.json。它把卷五 16 章各登记一行:章号、章标题、一份主交付物、本卷工具链前缀(g01–g16)与一条验收清单。

{
  "version": 1,
  "game": "merge-island",
  "observedAt": "2026-10",
  "title": "《合成小岛》出海发行方案装配清单",
  "note": "出海发行方案装配清单(D 层):把卷五第 1–16 章的交付物按章号装订成一份可交付的发行方案。每章登记一条:章号、章标题、一份主交付物、本卷工具链前缀(g01–g16)与一条验收清单。口径是「装配结构」的建模,不是任何真实发行方案的实测。前缀是本卷各章在 pebble/ 工作区的文件族(config/gNN_*.json、sim/gNN_*.py、core/src/gNN/),「前缀已落盘」指该族至少有一个配置文件存在。真实方案内容与验收结论需真实业务环境,命令行不可复现(X)。全字段为整数或字符串,无浮点。",
  "chapters": [
    { "ch": 1,  "title": "海外小游戏平台格局", "deliverable": "平台分层与组合选择表", "prefix": "g01", "check": "平台分层与流量结构写明,各平台分成与结算条款逐项对照,组合选择有依据" },
    { "ch": 2,  "title": "平台接入与上架",     "deliverable": "SDK 接入与上架资料表",   "prefix": "g02", "check": "SDK 与广告 API 差异对照,上架资料与驳回清单可查,平台专属能力已标注" },
    { "ch": 3,  "title": "一套代码多平台",     "deliverable": "平台抽象层与构建矩阵",   "prefix": "g03", "check": "抽象层与渠道配置分离,构建矩阵产物齐全,版本一致性与回滚路径已定义" },
    { "ch": 4,  "title": "海外变现",           "deliverable": "H5 广告形态与收入配比表", "prefix": "g04", "check": "Web 广告形态与广告网络对照,纯 IAA 结构闭合,多平台收入配比可复算" },
    { "ch": 5,  "title": "多语言资源管线",     "deliverable": "i18n 资源结构与语言包表", "prefix": "g05", "check": "资源键设计可扩展,语言包加载与回退明确,文案膨胀与 UI 适配有阈值" },
    { "ch": 6,  "title": "文化与美术本地化",   "deliverable": "文化禁忌与本地化变体表", "prefix": "g06", "check": "禁忌与符号审查逐条可查,美术变体按地区登记,日期/数字/货币/RTL 有落点" },
    { "ch": 7,  "title": "本地化质量",         "deliverable": "伪本地化与语言 QA 清单", "prefix": "g07", "check": "伪本地化溢出检测可跑,母语审校流程写明,本地化成本与排期成对出现" },
    { "ch": 8,  "title": "多语言运营",         "deliverable": "分语言指标与小语种取舍表", "prefix": "g08", "check": "分语言指标口径统一,活动与素材按语言登记,小语种取舍有预算约束" },
    { "ch": 9,  "title": "数据合规的工程落地", "deliverable": "合规工程落点与同意清单",   "prefix": "g09", "check": "GDPR/CCPA 落点到字段,COPPA 与儿童向限制明确,同意管理与数据出境有流程" },
    { "ch": 10, "title": "归因与隐私新政",     "deliverable": "归因链路与替代方案表",   "prefix": "g10", "check": "ATT/IDFA/SKAdNetwork 链路写明,无 IDFA 归因替代可跑,聚合上报有口径" },
    { "ch": 11, "title": "跨境结算与资金",     "deliverable": "收款方式与费率周期表",   "prefix": "g11", "check": "收款门槛逐平台写明,汇率/手续费/到账周期可复算,税务表单清单齐全" },
    { "ch": 12, "title": "出海风控",           "deliverable": "内容版权与地区可用性表", "prefix": "g12", "check": "内容与版权地区差异可查,制裁与地区可用性有门禁,反作弊与刷量有判据" },
    { "ch": 13, "title": "海外买量与素材",     "deliverable": "海外渠道与投放口径表",   "prefix": "g13", "check": "海外渠道与投放口径写明,素材本地化与合规对照,海外 ROI 与止损成对出现" },
    { "ch": 14, "title": "海外榜单与竞品",     "deliverable": "榜单结构与竞品拆解表",   "prefix": "g14", "check": "各平台榜单结构写明,竞品拆解与本地化机会可核,品类海外差异有对照" },
    { "ch": 15, "title": "案例:把《合成小岛》发到五个海外平台", "deliverable": "五平台接入时间线与数据复盘", "prefix": "g15", "check": "平台组合与接入时间线完整,本地化与合规落地可追溯,多平台数据表现可迭代" },
    { "ch": 16, "title": "出海收官",           "deliverable": "出海发行方案与五卷总收官", "prefix": "g16", "check": "方案装配完整、多平台仿真验收通过、五卷链路与剩余未知项交代清楚" }
  ]
}

清单本身是数据,它是否自洽要靠程序判定:16 章齐不齐、章号重不重、三要素有没有空、每章的前缀是不是等于 gNN——这些都是可以机械检查的,不该靠人肉扫。

P 层:装配内核

下面这段是 P 层装配内核 core/src/g16/manifest.ts。它读同一份清单,逐条校验结构,并给出一枚清单指纹(对「章号|标题|交付物|前缀|清单」逐行做 FNV-1a 32 位)。指纹的用处是:清单只要被人偷偷改一个字,指纹就变,交接时一比对就知道版本对不对。另按 availableConfigs 统计「前缀族已落盘」的章数——某章的文件族至少有一个 config/gNN_*.json 存在即算落盘。

// file: core/src/g16/manifest.ts
// tier: P
// verify: node --test 'core/test/g16_*.test.ts'
//
// 出海发行方案装配内核:读 config/g16_manifest.json 的装配清单,校验「第 1–16 章齐全、
// 章号唯一、章标题/交付物/验收清单不缺、每章工具链前缀等于 gNN」,并给出一枚清单指纹
// (FNV-1a 32 位,与 Python 侧同法可对拍)。另按 availableConfigs 统计「前缀已落盘」的章数
// (该族至少有一个 config/gNN_*.json 存在)。只用可擦除语法(禁 enum / namespace / 参数属性 / 装饰器)。

export interface PlanChapter {
  readonly ch: number;
  readonly title: string;
  readonly deliverable: string;
  readonly prefix: string;
  readonly check: string;
}
export interface PlanManifest {
  readonly version: number;
  readonly game: string;
  readonly title: string;
  readonly chapters: readonly PlanChapter[];
}
export interface PlanReport {
  readonly chapterCount: number;
  readonly expected: number;
  readonly missing: number[];
  readonly dupChapters: number[];
  readonly emptyFields: string[];
  readonly prefixMismatch: string[];
  readonly landedFamilies: number;
  readonly pendingFamilies: string[];
  readonly fingerprint: string;
  readonly valid: boolean;
}

/** 章号 → 期望的工具链前缀,如 3 → "g03"。 */
export function expectedPrefix(ch: number): string {
  return `g${String(ch).padStart(2, "0")}`;
}

/** 清单指纹:对「章号|标题|交付物|前缀|验收清单」逐行拼串做 FNV-1a 32 位(UTF-8 字节)。 */
export function fingerprint(mf: PlanManifest): string {
  const norm = mf.chapters
    .map((c) => `${c.ch}|${c.title}|${c.deliverable}|${c.prefix}|${c.check}`)
    .join("\n");
  const bytes = new TextEncoder().encode(norm);
  let h = 0x811c9dc5;
  for (const b of bytes) {
    h ^= b;
    h = Math.imul(h, 0x01000193) >>> 0;
  }
  return h.toString(16).padStart(8, "0");
}

/** 某前缀是否已有落盘的配置文件(config/ 下存在 gNN_*.json)。 */
export function familyLanded(prefix: string, availableConfigs: readonly string[]): boolean {
  const head = `${prefix}_`;
  return availableConfigs.some((f) => f.startsWith(head) && f.endsWith(".json"));
}

/** 校验装配清单:章号 1..expected 齐全且唯一、三要素非空、前缀等于 gNN、统计已落盘前缀。 */
export function validateManifest(
  mf: PlanManifest,
  availableConfigs: readonly string[],
  expected = 16,
): PlanReport {
  const chs = mf.chapters.map((c) => c.ch);
  const missing: number[] = [];
  for (let i = 1; i <= expected; i += 1) if (!chs.includes(i)) missing.push(i);
  const dupChapters = chs.filter((c, i) => chs.indexOf(c) !== i);
  const emptyFields: string[] = [];
  const prefixMismatch: string[] = [];
  for (const c of mf.chapters) {
    if (!c.title.trim()) emptyFields.push(`${c.ch}:title`);
    if (!c.deliverable.trim()) emptyFields.push(`${c.ch}:deliverable`);
    if (!c.check.trim()) emptyFields.push(`${c.ch}:check`);
    if (c.prefix !== expectedPrefix(c.ch)) prefixMismatch.push(`${c.ch}:${c.prefix}`);
  }
  const pendingFamilies = mf.chapters
    .map((c) => c.prefix)
    .filter((p) => !familyLanded(p, availableConfigs));
  const landedFamilies = mf.chapters.length - pendingFamilies.length;
  const valid = mf.chapters.length === expected && missing.length === 0
    && dupChapters.length === 0 && emptyFields.length === 0 && prefixMismatch.length === 0;
  return {
    chapterCount: mf.chapters.length, expected, missing, dupChapters, emptyFields,
    prefixMismatch, landedFamilies, pendingFamilies, fingerprint: fingerprint(mf), valid,
  };
}

配套测试 core/test/g16_manifest.test.ts 把清单指纹写成金标准,并验「删一章」「前缀写错」两种破坏都能被判无效:

// file: core/test/g16_manifest.test.ts
// tier: P
// verify: node --test 'core/test/g16_*.test.ts'
//
// 硬断言:出海发行方案装配清单校验通过,且清单指纹与 Python 侧 FNV-1a 实现逐位一致。

import { test } from "node:test";
import assert from "node:assert/strict";
import { readFileSync, readdirSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { dirname, join } from "node:path";

import { validateManifest, fingerprint, expectedPrefix, familyLanded } from "../src/g16/manifest.ts";

const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
const mf = JSON.parse(readFileSync(join(ROOT, "config", "g16_manifest.json"), "utf8"));
const available = readdirSync(join(ROOT, "config")).filter((f) => f.endsWith(".json"));

test("清单指纹与 Python FNV-1a 一致(跨语言对拍)", () => {
  assert.equal(fingerprint(mf), "1e96e2f1");
});

test("装配清单校验通过:16 章齐全、章号唯一、三要素不缺、前缀等于 gNN", () => {
  const rep = validateManifest(mf, available);
  assert.equal(rep.chapterCount, 16);
  assert.deepEqual(rep.missing, []);
  assert.deepEqual(rep.dupChapters, []);
  assert.deepEqual(rep.emptyFields, []);
  assert.deepEqual(rep.prefixMismatch, []);
  assert.equal(rep.valid, true);
});

test("工具链前缀族:g01–g12 均已落盘,g15 章允许仍待落盘", () => {
  const rep = validateManifest(mf, available);
  assert.equal(expectedPrefix(1), "g01");
  assert.equal(expectedPrefix(16), "g16");
  assert.ok(rep.landedFamilies >= 13, `已落盘前缀族应 >= 13,实测 ${rep.landedFamilies}`);
  assert.ok(familyLanded("g01", available));
  assert.ok(familyLanded("g16", available));
});

test("缺一章即判无效:删掉第 9 章后 missing 命中", () => {
  const broken = { ...mf, chapters: mf.chapters.filter((c: { ch: number }) => c.ch !== 9) };
  const rep = validateManifest(broken, available);
  assert.deepEqual(rep.missing, [9]);
  assert.equal(rep.valid, false);
});

test("前缀写错即判无效:第 3 章前缀改成 g99", () => {
  const broken = { ...mf, chapters: mf.chapters.map((c: { ch: number; prefix: string }) =>
    c.ch === 3 ? { ...c, prefix: "g99" } : c) };
  const rep = validateManifest(broken, available);
  assert.deepEqual(rep.prefixMismatch, ["3:g99"]);
  assert.equal(rep.valid, false);
});

在本机跑 node --test 'core/test/g16_*.test.ts',11 个用例全过(含 16.2 的 6 条):

✔ 清单指纹与 Python FNV-1a 一致(跨语言对拍) (1.236209ms)
✔ 装配清单校验通过:16 章齐全、章号唯一、三要素不缺、前缀等于 gNN (0.694458ms)
✔ 工具链前缀族:g01–g12 均已落盘,g15 章允许仍待落盘 (0.234042ms)
✔ 缺一章即判无效:删掉第 9 章后 missing 命中 (0.212042ms)
✔ 前缀写错即判无效:第 3 章前缀改成 g99 (0.165042ms)
ℹ tests 11   ℹ pass 11   ℹ fail 0
fingerprint = 1e96e2f1

fingerprint = 1e96e2f1 是清单的指纹:同一份清单在 Node 的 manifest.ts 与 Python 的 FNV-1a 实现里算出同一个值,说明「装配清单」这层没有跨语言的口径分叉。这与卷二一份逻辑两种宿主 是同一个信念:清单不是散文,是可以被程序读、被程序判的契约。

为什么前缀族是唯一真源

「写一份方案」和「写一份装配清单」的区别,在第一次更新时就会暴露。假设第 13 章的海外投放口径多了一项「当地币种出价上限」,如果方案是一份 Word 文档,你得手工翻到第 13 章改正文、改目录、改检查表——三处漏一处就是一次事故。而清单是数据,正文与目录都从它渲染:

  • 目录:按 ch 排序直接生成,不会漏章;
  • 检查表:每章的 check 就是一条可勾选项,不用另抄一遍;
  • 前缀族:prefix 一列,谁的配置改了、有没有落盘,一比对就知道。

更关键的是,清单可以被程序判对错。人手写的方案没法自动验证「16 章齐不齐」,但 validateManifest 可以在每次提交前跑一遍:缺章、重复、空字段、前缀错位——四类错误全部机械可查。这就是把「方案」从文档降格成数据的投影换来的好处。

一个容易忽略的细节是「前缀族」的语义:它不校验某个具体配置文件的内容,只校验该章的文件族是否已经落地。第 15 章是五平台案例,它的配置文件族(g15_*)在这一卷全部章节写完前可能只有一个;校验器对此的处理是「已落盘的计数是数据、不是硬门槛」——计数进报告,断言只要求 >= 13(g01–g12 必然在)。这是为了让清单在滚动写作的过程中始终可用:早写好的章先落盘,晚写好的章补齐后再跑一次,计数自然上升。

方案的边界:内容不可复现(X 层)

必须把边界划清。本节能机械保证的是结构:章号齐、字段全、前缀对、指纹稳定。下面这些属于 X 层,需真实业务环境,命令行不可复现:

  • 每份交付物的实际内容(平台组合、接入资料、合规落点、结算费率),要靠真实平台条款、真实审核流程与真实监管环境产出;
  • 每章 check 的实际通过与否,取决于真实发行结果,不是清单能判的;
  • 方案交付给谁、对方是否认可,是组织与商务问题,命令行不可复现。

工程上正确的切法与前几章一致:结构层可复现、内容层不可复现。结构层保证「方案没漏东西、没登记错东西」,内容层靠拿到真实材料后反填进清单(把实际交付物挂到对应章号上),结构校验立刻重跑一遍。这也正是下一节多平台仿真验收要接的活。

16 章装订表

校验通过后,方案的目录就是清单的一个投影。下表是装配结果的速览(完整清单见落盘 config/g16_manifest.json):

章交付物前缀族验收清单要点
1–3平台组合 / 接入资料 / 构建矩阵g01–g03分层与条款对照、上架资料可查、抽象层与产物齐全
4–6收入配比 / 语言包 / 本地化变体g04–g06纯 IAA 闭合、语言包回退明确、禁忌与 RTL 有落点
7–9语言 QA / 分语言指标 / 合规清单g07–g09伪本地化可跑、指标口径统一、合规落点到字段
10–12归因链路 / 收款费率 / 风控表g10–g12归因替代可跑、费率周期可复算、地区可用性有门禁
13–15投放口径 / 榜单拆解 / 五平台复盘g13–g15海外 ROI 与止损成对、竞品机会可核、时间线可追溯
16发行方案与总收官g16方案自洽、仿真验收通过、五卷链路交代清楚

自检表

检查项合格标准
章号覆盖1–16 齐全、无缺号、无重复
三要素每章章标题/交付物/验收清单非空
前缀一致每章前缀等于 gNN,与工作区文件族一致
指纹稳定清单未改则指纹不变,交接可比对
边界清晰交付物内容与验收结论标 X,不写成「实测」

方案装配解决了「东西在哪、算不算完成」,但清单自洽只说明结构没问题,不代表账算得对。下一节我们把五个海外平台的收入账拉到同一张台账上,用共享的经济模型复算一遍,看跨平台的净额与验收线对不对得上。

阅读导航:上一节:目录 · 下一节:16.2 多平台仿真与验收 。

继续阅读

探索更多技术文章

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

全部文章 返回首页

「minigame」更多文章

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