--- topic: 生成验收门 canonical: true date: 2026-06-24 --- # 开闸验收门 W-G1 · 生成对外放行的 6 道门 > 🚧 **架构演进中** —— 廉价线 **gameDefinition 已废(错误路线),现行 = A-model 写真 `src/`**;tier2 富游戏自治轨建设中(0 号 spike accept)。文中结论可能随推进变化,以子树 [README](README.md) / 运行时 SoT [`agentic运行时架构图说`](agentic运行时架构图说.md) 与最新裁定为准。 > **这是什么**:绘境AI 生成引擎"对外开闸"那一刻必须先架好的 6 道验收门的设计文档。它回答的是一个很具体的问题——**当"一句话生成游戏"第一次向真实创作者放开时,我们到底要先焊死哪些门、可以并行补哪些门、又有哪两道前置必须先验过才许放行?** > **给谁看**:负责生成主线开闸的工程师、质量与审核台一侧的同事、做发布放行决策的产品与创始人,以及想看清"放量前的安全/可控边界"的架构评审。 > **怎么读**:先读 §1 那张"6 门怎么排"的全景图建立整体认知,再按 §2–§4 顺着三组门看每道门具体检查什么;想确认"能不能放"读 §5 的两道开闸前置验证结果;残留待办在 §6。 生成引擎此前讲的是"一句话怎么变成一款游戏"那条技术主线(见[生成引擎主文档](README.md))。本文讲的是另一件事:那条主线建好之后,**怎么把它对真实用户安全地"开闸"**。开闸不是一个开关,而是一组前置条件——这份文档把这组条件钉成 6 道可验收的门。 --- ## 1. 一句话与一张图:开闸要先架哪 6 道门 先把"开闸"这个词说清楚。**开闸 = 把"一句话生成游戏"这条能力,从内部封闭测试状态,正式对真实创作者放开。** 这是一个有风险的动作:放开之后,陌生用户会带着各种意图涌进来,既可能用一句违规的话试探合规底线,也可能用并发请求把后台那条串行的生成流水线压垮。所以开闸不能裸奔,但也不必等到所有质量门尽善尽美才放——那样窗口期早就过了。 我们的判断是把 6 道门按"阻塞等级"分成三类,各司其职: - **2 道硬阻塞门,必须先焊死,否则一律不许放行。** 它们守的是"放开之后系统会不会被一个用户压垮"和"违规内容会不会直接进到玩家面前"这两条不可逆的红线。 - **3 道落库门,可以和开闸并行上线,且第一版全部只观测、不拦截。** 它们守的是"放开之后我们还管得住、追得清"——把每次生成的全过程数据落库、算出就绪分、查出重复创意,但都只记录、不卡人。 - **1 道首局体验门,和上面同批焊好一块"地板"。** 它守的是放进来的游戏"陌生人 10 秒钟觉不觉得能玩",但它判失败时不会推翻底层那套硬验收。 这套排布背后的设计理由,一句话就能说清:**别等 6 门全齐才开闸(只有那 2 道硬阻塞门不做,确实不能放),但也别裸奔开闸(把可控性和合规这两条焊死,其余的边放量边观测)。** 下面这张图是 6 门的全貌: ```mermaid flowchart TD subgraph A["组A · 硬阻塞 · 先做 · 已落地"] D12["D12 控制平面 v0
降级 + 配额并发 + 背压 + 记账骨架"] GP9["GP9 合规先行段
生成前 prompt 审查
10 负例 100% 阻断"] end subgraph B["组B · 落库组 · v0 并行 · 全非阻断 · 已落地"] N9D["9d trace 账本
worker 富数据落 trace_json"] D11["D11 就绪评分
读 trace 算 0-100 落库 + 审核台透出"] D9["D9 反同质化
归一查重 · 撞重只告警"] end subgraph C["组C · 同批焊地板 · 已落地"] FIRST["首局体验子门
可玩≤2s / 首反馈即时 / 60s 品类闭环"] end A -->|2 门焊死| OPEN(("对外开闸")) OPEN -.-> |"放了要能管 / 追溯"| B C -.-> |"随补 L1 driver 自产"| OPEN M4[["跨轨 · 随 M4:真实计费扣退
(网关硬编码 UserId 阻塞,推迟)"]] -.-> D12 style D12 fill:#f5b7b1 style GP9 fill:#f5b7b1 style OPEN fill:#a9dfbf ``` 图里出现了几个内部代号,在这里一次解释清楚,后面各节会逐个展开: - **D12 / GP9 / D11 / D9** 是这套生成体系里给各道质量与控制门排定的编号(D 系列是质量/控制门,GP 系列是合规门)。本文只涉及开闸这一批,完整门体系不在此展开。 - **trace(轨迹)** 指一次生成任务从头到尾产生的全过程结构化记录——走了哪几道门、花了多少成本、用了哪些模型、重试了几次。"9d trace"是其中第 9 维度的全链轨迹账本。 - **worker** 指真正跑生成的那个独立工作进程(Python 写的),它在后台一轮轮地把一句话变成游戏。 - **落库** 就是把数据写进数据库持久化保存的口语说法。 - **L1 / driver** 是生成侧的概念:L1 指"模板成游戏"那一档最轻量的生成能力;driver 指九门验收时用来真玩游戏的那个操作脚本(它知道这款游戏该点哪、该划哪)。 这张图最该记住的是那条主轴:**只有 D12 和 GP9 这两道硬阻塞门焊死了,才允许"对外开闸";开闸之后,组B 三门负责"放了还管得住"、组C 一门负责"放进来的游戏体验有地板"。** 图右下角那个虚线挂着的 M4,是一件被刻意推迟的事,§1.2 单独讲。 ### 1.1 6 门一览 | 门 | 一句话:它检查什么 | 阻塞分级 | 归属轨 | |---|---|---|---| | **D12 控制平面** | 放开前先有降级开关、配额并发限制、背压保护、记账骨架,保住后台那条全局串行的生成流水线不被压垮 | **硬阻塞** | 生成引擎(计费扣退跨网关) | | **GP9 合规先行** | 生成开始**之前**先审查用户那句 prompt,违规的不入队、不进玩家信息流 | **硬阻塞** | 生成引擎 | | **9d trace** | 把 worker 已经产出的富生成轨迹结构化落库,供 D11/D9 复用 | v0 并行,不卡门 | 生成引擎 + 契约 | | **D11 就绪评分** | 读 trace 算出一个 0-100 的"就绪度"分数,落库并在审核台可见 | v0 并行,不卡门 | 质量 + 产品 · 审核台 | | **D9 反同质化** | 把游戏配置归一化后查重,撞到重复创意只告警、不拦截 | v0 并行,不卡门 | 生成引擎 / 质量 | | **首局体验子门** | 品类化的首局服务等级目标,3 条断言,判失败也不推翻底层九门 | 不卡门 · 开闸前补 | 质量 / harness | 这三组门的取舍理由,可以浓缩成三条核心决策: 1. **D12 和 GP9 为什么必须硬阻塞?** 因为这两件事不做,后果都不可逆。后台的生成 worker 是**全局串行**的(一次只处理一个任务),不做控制平面,单个用户的并发请求就能把它压垮,所有人都生成不了;不做合规先行,违规内容会直接生成出来进入玩家信息流,这是法务红线,一旦发生无法回收。 2. **那 3 道落库门为什么第一版只观测、不拦截?** 因为底层那套"九门 harness"(在真实浏览器里跑游戏、用九道确定性检查判定能不能玩的测试夹具)已经是挡住坏游戏的**硬地板**了。这三道落库门是更上一层的质量观测,第一版若一上来就生效拦截,反而会**误杀好游戏**——所以先只记录数据,等积累够了再考虑收紧。(这里"九门硬地板"说的是**开闸放行**那一侧:一款游戏要进信息流,须过完整九门机械预筛,且 2026-07-09 起还须过独立模型玩法判定——L1 双证据口径,裁定见质量 SoT《游戏质量与爆火能力》裁定三。生成环消费口径的沿革:2026-06-20 曾收窄为"只认五道客观健康门 A–E",2026-07-02 由质量轨裁定回到 **driven 时九门全量 AND**,2026-07-09 该口径整体降位为预筛——详见 §2.4。) 3. **真实计费为什么不在这一批?** 见 §1.2。 ### 1.2 真实计费扣退为什么被拆出去(随 M4) 图里那个虚线挂着的方块写着"M4:真实计费扣退"。M4 是后续一个独立的里程碑代号。**真实的"按量扣费 / 失败退费"被刻意从开闸这一批里拆出去,推迟到 M4 再做**,原因有两个,都很实在: - **网关层有一处阻塞。** 所有模型调用统一走的 new-api 网关(new-api = 绘境AI 内部统一管理模型 key 与用量计费的网关),它的 `AddToken` 接口当前把 UserId 硬编码死了,没法按真实用户记账。 - **MVP 阶段本就没有真支付。** 既然还收不到钱,也就谈不上真扣费。 所以这一批里,D12 控制平面用的是一套"额度记账"的**骨架**先顶上:配额和背压该限的限、该挡的挡,但记账只是占位字段和日志,**不是真扣费**。等 M4 把网关那处阻塞解了、真支付也接上了,再把骨架换成真账。把这两件事拆开排期,是为了让开闸不被一个网关 bug 卡住。 --- ## 2. 组A:两道硬阻塞门(D12 控制平面 + GP9 合规先行) 这是开闸的承重墙,已落地并合入主干(命名空间 `com.wanxiang.huijing`)。两道门一道管"系统不被压垮",一道管"违规不进信息流"。 ### 2.1 D12 控制平面:在唯一入口前焊四道门 D12 的核心思路是:**在生成任务的唯一入口处,入队之前,按"从最便宜到最贵"的顺序焊四道门,层层拦截,把最贵的那道(要调大模型的合规检查)放在最后**——这样超配额、超背压的请求根本走不到调模型那一步,省钱也省算力。 这里有一个容易被绕过的坑,先说清:生成任务的提交入口是 `AigcTaskServiceImpl.submitGenerate`,但还有一个 `retryTask`(重试)路径。如果只在 `submitGenerate` 上焊门,用户走重试就能直接插库、绕过所有控制。所以实现上把控制逻辑抽成了一个共用方法 `enqueueWithControlPlane`,让提交和重试都走它——**堵死重试绕过控制的口子**。四道门的顺序如下: ```mermaid flowchart LR REQ["生成请求
submitGenerate / retryTask"] --> G1{"① 降级门
aigc.generate.paused?"} G1 -->|"已暂停"| R1["拒绝:GENERATE_PAUSED"] G1 -->|"放行"| G2{"② 配额 + 并发门
per-creator×level 当日计数"} G2 -->|"超上限"| R2["拒绝:QUOTA_EXCEEDED"] G2 -->|"放行"| G3{"③ 背压门
全局在飞 queued+running"} G3 -->|"超队列深度"| R3["拒绝:BACKPRESSURE_REJECTED"] G3 -->|"放行"| G4{"④ GP9 安全门
(最贵,放最后)"} G4 -->|"违规"| R4["拒绝:UNSAFE_PROMPT"] G4 -->|"安全"| INS["insert 入队
落 level=L1 + 额度记账骨架"] style G4 fill:#f9e79f style INS fill:#a9dfbf ``` 逐道门的含义: - **① 降级门**:读基础设施配置中心(infra `ConfigApi`)里 `aigc.generate.paused` 这个键,运营可以热改它一键暂停全站生成。读取失败时**容错放行**(fail-open on read)——配置中心抖动不该把生成全堵死。 - **② 配额 + 并发门**:按"每个创作者 × 会员档(level)"统计当日生成计数,达到或超过(`>=`)上限就拒。 - **③ 背压门**:统计全局在飞的任务数(排队中 queued + 运行中 running),达到或超过队列深度上限就拒。这道门守的就是那条全局串行 worker。 - **④ GP9 安全门**:把要调大模型的合规检查放在最后,前面已被配额/背压拒掉的请求,**不会白白付一次安全检查的成本**。 四门全过,才 insert 入队,同时落两样东西:一个 `level` 字段(会员档,v0 阶段统一记为 L1);一套额度记账骨架(只是日志和占位字段,**不是真扣费**,见 §1.2)。 ### 2.2 GP9 合规先行:为什么必须独立、且必须 fail-closed GP9 这道门有两个设计决定很关键,都源自踩过的坑: **第一,它必须用一个独立的安全检查客户端,不能复用生成执行器的那个 LLM 客户端。** 原因有两层:执行器的客户端 `ExecutorLlmClient` 被 `aigc.executor.enabled` 这个开关门控着,服务层根本注入不到它;更重要的是,如果把安全检查塞进执行器内部去做,它会卡住那条串行执行的节拍(tick),等于自己给自己制造了一个拒绝服务(DoS)。所以 GP9 在服务层用一个**独立的** `SafetyCheckClient` 调 `safety.prompt-check`,配**短超时 8 秒 + 至多 1 次重试**。 **第二,它必须 fail-closed(失败时从严)。** 这是一条方向性的安全判断: ```mermaid flowchart TD P["用户 prompt"] --> CHK["SafetyCheckClient
调 safety.prompt-check
超时 8s + 至多 1 重试"] CHK -->|"safe=false"| REJ["同步拒绝
抛 UNSAFE_PROMPT
不入队"] CHK -->|"safe=true"| PASS["放行,继续生成"] CHK -->|"超时 / 调用失败"| FC["fail-closed
抛中性 AIGC_LLM_SAFETY_ERROR
(失败归类仍记 llm_error)"] style REJ fill:#f5b7b1 style FC fill:#f9e79f style PASS fill:#a9dfbf ``` - 检查结果 `safe=false`(判定违规):同步拒绝,抛 `UNSAFE_PROMPT`,不入队。 - 检查超时或调用失败:**fail-closed**,抛一个**中性**的业务码 `AIGC_LLM_SAFETY_ERROR`(提示"安全检查暂不可用,请稍后重试")。这里有两处刻意的设计——一是合规判不出来就放过,等于让违规内容在窗口期直入信息流,风险不可逆,所以宁可从严挡掉;二是用中性提示而非复用 `UNSAFE_PROMPT`,是为了避免 new-api 网关偶尔抖动时,把一个正常用户误标成"发了违规内容"。注意这里的"不新增枚举"指的是**失败归类**——它复用既有的 `FailureReasonEnum.LLM_ERROR`(归因 tag 仍记 `llm_error`),不在那个跨契约共享枚举上单边加值;但 ErrorCode 这一侧确实新增了 `AIGC_LLM_SAFETY_ERROR` 这个可抛业务码,因为"提交侧同步拒绝"必须有一个独立的数值码、且文案要中性。 ### 2.3 组A 的错误码、回滚与契约 - **错误码**:在 aigc 模块的错误码里新增了 001 子段(`1-101-001-***`),共五个码——其中 D12 三个控制平面码 `AIGC_QUOTA_EXCEEDED`(超配额)、`AIGC_BACKPRESSURE_REJECTED`(背压拒绝)、`AIGC_GENERATE_PAUSED`(已暂停),再加上 GP9 的两个合规码 `AIGC_UNSAFE_PROMPT`(真违规同步拒绝,004)与上一节那个 fail-closed 的 `AIGC_LLM_SAFETY_ERROR`(安全检查暂不可用,005)。这五个都是**业务码、走 HTTP 200**——验收时要断言返回的是业务码,而**不是** HTTP 429 / 503 这种协议层状态码。 - **回滚**:整个控制平面挂在 `aigc.control-plane.enabled` 这个功能开关(feature-flag)后面,**默认关闭 = 现行逻辑逐字不变**(全部门加 GP9 都旁路)。 - **契约与数据迁移**:合规负例语料放在契约目录 `contracts/prompts/eval/safety.prompt-check/{inputs,labels}.jsonl`,共 **10 条负例,覆盖 10 类合规红线类目**;数据库迁移 `V15.0.0__aigc_task_add_level.sql` 给任务表加 `level` 列,只增不改(additive)、默认值 1,存量数据回填为 L1。 ### 2.4 生成环消费九门的口径:硬门收窄成客观健康门 A–E(创始人 2026-06-22 历史回收判定捡回) > **历史化注记(2026-07-02,质量轨裁定)**:本节记述的"生成环把九门收窄成只认 A–E 客观健康门",是 gamedef 时代 driver 常缺、G/H/I 三门不可靠时的消费策略。现行便宜档在门跑前自动生成 play-spec 使 driven=true,cheap 与 tier2 的判据代码都按九门全量与富游戏门取 AND(M1 达标 bake-off 15/15 正是这个口径);undriven 时 E_live/H_progress 自动降 advisory 的分级仍保留在 harness。因此 **driven 时九门全量 AND 为现行 L1 口径,"收窄 A–E"归为历史方案、不再是待选路线**;判定归属见同目录现行 canonical《游戏质量与爆火能力》(裁定 L1-a)。以下原文按历史记录保留,不删。 > > **再修订(2026-07-09,创始人拍板)**:九门全量 AND 的语义再降一级——它仍是机械层的取值口径,但其通过自此只构成「未见明显死」的**预筛**,不再单独等于 L1 通过;L1 通过 = 机械预筛 ∧ 独立模型玩法判定(依据 = 当日五路 harness 审计:H_progress 实测退化为 score 单调上升、latch 有进展即降 advisory,五份空心 pass verdict 在册)。裁定与四条细则见《游戏质量与爆火能力》裁定三,本档判据本体(九门各门怎么判)不动——动的又一次只是消费侧语义。 > > **便宜档消费口径 v2(2026-07-10,W-AXIS-V2;已裁定,随其波 1/2 兑现)**:机械取值 = A_boot/B_uncaught/C_frame/D_render 四门投影(消费层 floor 字段),E_live/G_input/H_progress/I_control 与 F_wiring 降观测——F 的期望前缀集源自 play-spec,契约退役后不再具证真力,本节下文『F_wiring 属 driver 依赖门』的归类由此坐实;验收 = 四门 ∧ 测试 agent 真玩裁决(证据落 evidence/playtest/,harness 截图在 undriven 下为 boot 屏、仅作装载观测)。九门判据代码不删,tier2 与历史复跑照旧。首局门三断言随驱动器退役由测试员转写接管(firstPlay 三字段),独立门名退役。执行与字段协议 = W-AXIS-V2 plan。 前面几节把九门 harness 讲成一道统一的"硬地板",这个说法在"开闸放行"这个语境里仍然成立——一款游戏要进信息流,确实要先过这套真玩检测。但要把一件 2026-06-20 拍下的口径变化讲清楚,否则架构档会和现行真相脱节:**生成环(SAA 管线)在迭代生成时怎么"消费"九门的裁决,已经从"九门全过才算成功"收窄成了"只认五道客观健康门 A–E"。** 这是九门收窄后、gamedef 路实测生成质量从 all-9 口径下的约 50% 做到 91.7% 的直接原因之一——实测样本 n=12:关闭 thinking 时过 8/12,启用 Anthropic 原生 thinking 分离协议后过 11/12(即 91.7%);创始人 2026-06-20 拍板以 n=12/91.7% 宣告达标(n≥30 只多给方差信息,不改"thinking-on 更好且过门"的结论)。gamedef 路虽已判错误路线废弃,但"九门收窄成 A–E 客观门"这条验收消费策略引擎无关、对现行 A-model 真 src/ 同样适用。 先把九道门按"判什么"分成两类。一类是**客观健康门**,它们不依赖 driver(driver 指真玩脚本里那段"知道这款游戏该点哪、该划哪"的操作逻辑)就能判定,纯看运行时本身健不健康:A_boot(能不能起来)、B_uncaught(有没有未捕获异常)、C_frame(跑不跑帧)、D_render(画面有没有真渲染)、E_live(进程活不活着)。另一类是**driver 依赖门**:G_input(响不响应输入)、H_progress(机制有没有进展)、I_control(可控性),这三道必须有一段能真玩这款游戏的 driver 才判得了;而 F_wiring(游戏事件有没有真触发 `rt.fx` 之类特效)同样依赖游戏被真玩起来,没有 driver 必挂。(v2 注记:便宜档消费口径下 E_live 与 G/H/I、F 一并降观测,机械取值只剩 A/B/C/D 四门投影,见本节前注「便宜档消费口径 v2」——已裁定,随 W-AXIS-V2 波 1/2 兑现。) 收窄后的口径是: - **生成环的硬门只认 A–E 五道客观健康门**。便宜模型新生成一款游戏、还没有为它写专属 driver 时,G/H/I 三道 driver 依赖门**不再否决生成、也不进救场回喂**——它们降为"参考门",失败只记录、不把这一轮判死,免得因为缺一段 driver 就把一款其实健康的游戏空跑八轮救场烧掉。 - **F_wiring 移出客观硬门**。"有没有真用引擎、有没有真特效"这层语义,改由 VLM 看截图来判(对应 README/OpenGame对照里那条"player 软门做厚"),不再让一道必然挂的事件触发门去卡生成。 - **九门 harness 本身一行不改**:它照常跑、照常把九门逐门结果写进 `verdict.json`,门的判定逻辑动都不动。变的只是"生成环如何读这份 verdict"。这条边界很关键——动的是消费侧策略,不是裁判本身,所以"开闸放行"那一侧仍可以按完整九门来要求。两套口径用 `-Dsaa.gen.gateMode=all9` 双轨可回退:默认走收窄后的客观门模式,加这个参数就退回旧的"九门全过"模式做对比。 这个收窄背后还藏着一条**必须记住的实现约束(030)**,它是客观门解耦能不能真正生效的前提。`SaaGenNodes.playNode` 判 pass 时,如果前置条件写成 `rc==0`(rc = 真玩脚本 `play.cdp.cjs` 的退出码),问题就来了:这个退出码本身是**九门聚合**的——只要任意一道门(含 driver 门)挂了,脚本就 exit 1。于是一旦某道 driver 门失败让 rc=1,它会**反向否决**掉"只认客观门 A–E"的解耦,让收窄形同虚设——2026-06-20 当天就是这个 bug 让 Phase 1 空转、白烧了 302K token。修法是在客观门模式下把判据改成 `pass = (rc==0 || rc==1) && objectiveGatesPass(verdict)`:不再拿聚合退出码当门,而是直接读 `verdict.json` 里 A–E 五道门的结果。谁要在生成环里重写 verdict 消费逻辑,这条耦合坑必须先避开,否则解耦白做。 > 口径归属:那条 rc 耦合坑(客观门解耦被九门聚合退出码反向否决)的实现教训仍然有效,是日后重写生成环 verdict 消费逻辑时必须先避开的坑。它此前蒸馏在 `.agents/skills/saa-graph-orchestration.md` 的一节里,但该 skill 已于 2026-07-01 重写为 cheap-worker 主线、原 §3.5 不复存在;"收窄 A–E" 现属历史消费策略,现行口径与四层质量模型的裁定改以同目录 canonical《游戏质量与爆火能力》为准。这条收窄的原始决策与 Phase 0–4 路线仍可 `git show 8ea97234:docs/plans/2026-06-20-关闭九门判定-全面参考OpenGame-决策与Phase0-4.md` 查阅。OpenGame对照档里那条 Phase 0–4 落地路线([OpenGame对照 §3.5](OpenGame对照.md))承接的是收窄之后 SAA 线的远期设想,现行 cheap/tier2 主线已回到九门全量 AND。 --- ## 3. 组B:三道落库门(9d trace + D11 就绪评分 + D9 反同质化) 组B 是开闸的"可控性"层,已落地、全部非阻断(已合入主干)。理解组B 的关键,是先理解一个反直觉的洞见。 ### 3.1 命门洞见:不是缺数据,是富数据在边界被丢了 做这三道门之前,直觉会以为"我们缺数据,要从零造一套生成数据采集"。真相恰恰相反:**worker 早就产出了富数据,只是在回调边界上被丢弃了。** worker 每跑完一轮,它的 `result` 里其实已经包含了非常丰富的信息——九门逐门的通过情况(guards)、成本(cost)、用了哪些模型(models)、重试了几次(attempts)、验收规格(gatespec)、裁决(verdict)、修复记录(repairs)、墙钟耗时(wall)。问题出在:worker 往后端回调时,`build_callback_payload` 这个函数只往回传了 5+2 个字段,`result` 作为一个线程内的局部变量,随着那个线程结束就消亡了,富数据再也找不回来。 ```mermaid flowchart LR subgraph W["worker(已产富数据)"] RES["result
guards/cost/models/attempts
gatespec/verdict/repairs/wall"] BCP["build_callback_payload
只塞 5+2 字段"] RES -->|"⚠️ 富数据在此被丢弃"| BCP end subgraph DB["后端落库"] TJ[("trace_json")] RS[("readiness_score")] end BCP -.->|"修法:把被丢的富数据接过来"| TJ TJ --> SCORE["D11 ReadinessScorer 读 trace 算分"] SCORE --> RS style RES fill:#f9e79f ``` 所以这三件事的本质,**都不是从零造数据,而是把那份在回调边界被丢弃的富数据接过来落库**。 ### 3.2 三道门各自做什么 - **① 9d trace(公共底座)**:这是另外两道门的数据来源,所以**契约先行**——先改契约 `contracts/api-schemas/aigc.yaml` 里的 `DifyCallbackReqVO`,给它加一个 `trace` 字段(只增不改、可选),再改 Java 侧的值对象和 worker。worker 的 `build_callback_payload` 从 `result` 抽出 9d 子集回传,后端 `DifyCallbackTxService` 在回填段尽力(best-effort)落到 `game_aigc_task.trace_json`。这里定了一个**必填子集七项**作为"轨迹完整率"的分母:`{pass, repairs, wallS, models, attempts, gameId, stage}`;其余字段因为和具体生成路径相关、worker 走哪条路尚未定死,所以 schema 放宽、不绑死任何一条路径。 - **② D11 就绪评分(`ReadinessScorer`)**:读 trace,加权算出一个 0-100 的就绪度分数。权重是:可玩性 playability 占 0.5、首局体验 firstPlay 占 0.25、稳定性 stability 占 0.15、效率 efficiency 占 0.1(**这套权重的管辖自 2026-07-02 起归质量轨;四维数值 0.5/0.25/0.15/0.1 不动,任何调整都走质量轨校准单提案 —— 见同目录现行 canonical《游戏质量与爆火能力》**)。一个重要的容错设计:**字段缺失时取中性值 0.5,而不是直接打 0 分**——免得因为某个可选字段没采到就把分数打到地板。算出的分落到 `readiness_score` 字段,并在审核台接口 `/task/page` 的返回里透出(前端那个 aigc 列表页是产品轨的后续工作,当前 game-admin 还没有这个页)。 - **③ D9 反同质化**:把生成早期 agent-loop-v1 编排器里的 `_norm_text`(归一化纯函数,几乎是原样复制)和 `mint_design_id` 的思路移植过来,在 worker 侧写一个 `dedup.py`,把游戏配置归一化后算出一个签名去查重。**撞到重复创意时只 `log.warn` 告警 + 把 `trace.similarity.dupHit` 落库,绝不拦截。** ### 3.3 组B 的非阻断硬约束、回滚与关键修复 - **非阻断硬约束(命门)**:这是组B 不可破的红线——上面三件事**任意一件**的落库、算分、查重失败,都必须 `try-catch` 把异常吞掉 + `log.error` 记录,主回调链照常走到 `PUBLISHED`(参照既有 `DifyCallbackServiceImpl` 的 best-effort 范式)。**落库这一组绝不允许因为观测失败,反过来拦截了主链。** - **回滚**:三件各有一个开关(`aigc.trace.enabled` / `aigc.dedup.enabled` 等),字段都是 additive 可空的,停止写入即等于回滚。 - **数据迁移**:`V16.0.0__aigc_task_add_trace_readiness.sql`,加 `trace_json`(JSON 类型)和 `readiness_score`(SMALLINT 类型)两列。 - **两处关键修复(已合入)**: - 提交 `703e462c` 闭合了一处 **split-brain**(直译"裂脑",指同一份事实在两条代码路径上各落各的、迟早不一致的隐患)——当派发器走 SAA 进程内路时,也要落 `trace_json` / `readiness_score`,保证 HTTP worker 路和 SAA 路两路一致,不会切了路就静默丢字段。 - 提交 `5f6cdd0c` 修了 `ReadinessScorer.firstPlay` 读 `H_progress` 嵌套对象的 `.pass` 时的一个 bug——修好后 HTTP 和 SAA 两路的 firstPlay 分能从恒为 0.5 走到可达 1.0。 --- ## 4. 组C:首局体验子门 组C 是和组A/B 同批焊好的那块"体验地板",已落地。它要验的是生成主线对外的那句口号——**"陌生人 10 秒钟觉得能玩"**——能不能兑现。 具体落地是在九门 harness 的真玩脚本(`play.cdp.cjs`)里加一道首局门,验**3 条断言**: 1. **可玩 ≤ 2 秒**:从打开到能真正操作,不超过 2 秒。 2. **首反馈即时**:第一次操作要立刻有反馈。 3. **60 秒内品类核心反馈闭环可达**:一分钟之内,能走完这个游戏品类最核心的那个反馈闭环(比如打砖块要能消掉一块砖)。 这里有一个边界必须说清:**首局门是 H 门的"additive 派生超集"**。H 门是九门里判"机制有进展"的那道门;首局门复用 H 门的裁决结果,但即使首局门判 FAIL,**也不会推翻** H 门的 pass/fail——H 门仍然是机制层的硬地板,首局门只是在它之上叠一层更贴近真实体验的观测。(v2 注记:便宜档下三断言的证据来源随驱动器退役,由测试员转写 firstPlay 三字段接管,首局门作为独立门名退役、语义并入 playtest 证据——已裁定,随 W-AXIS-V2 波 1 兑现,指针见 §2.4「便宜档消费口径 v2」;tier2 照旧。) ```mermaid flowchart LR GAME["生成产物"] --> H["H 门
机制有进展(硬地板)"] H --> FIRST["首局体验子门
(H 的 additive 超集)"] FIRST --> A1["① 可玩 ≤ 2s"] FIRST --> A2["② 首反馈即时"] FIRST --> A3["③ 60s 品类闭环可达"] FIRST -.->|"FAIL 不推翻 H"| H style H fill:#a9dfbf style FIRST fill:#f9e79f ``` 落地上还有三个要点: - **v0 走的是"路A"——零改生成侧。** 不动生成那一头,而是从现成的验收规格(gatespec)里的 `driver` 家族反推游戏品类:`tap-targets` 对应"放置类"、`+safeOnly` 对应"规避类"、`paddle-intercept` 对应"技巧类"。然后复用现成的三款游戏(打砖块 breakout / 井字棋 tictactoe / 扫雷 saolei)来验,**零新生成**。 - **阈值在真实环境校准。** 2 秒 / 60 秒这两个阈值是在 mini-desktop 的无头(headless)环境里实测校准的——无头浏览器冷启动比真机慢,所以阈值按它来定更稳。 - **回滚**:harness 上的一个开关,**默认开**作为验收门,但 FAIL 不阻断九门 pass。 --- ## 5. G0/G1 开闸前置验证(已 PASS · 无 blocker) 6 道门架好之后,放行之前,还有两道"开闸前置"必须在 mini-desktop 的预发(staging)环境里真验过——这两道叫 G0、G1(G 是 gate 的首字母,这里特指"开闸前置验证项")。两道都已 **PASS、无阻塞项(blocker)**。 ```mermaid flowchart LR GATES["6 门已焊好"] --> G0["G0 创作页核实"] G0 -->|"PASS"| G1["G1 GP9 staging 真验"] G1 -->|"PASS · 无 blocker"| READY["具备开闸条件
(放行决策待创始人)"] style READY fill:#a9dfbf ``` ### 5.1 G0:创作页核实——空模板列表是"按设计的过渡态",不是缺陷 G0 验的是创作页的状态。这里有一个很容易被误判成"断点缺陷"的现象,必须说准:**创作页的模板列表是空的(返回 HTTP 200,不是故障),这是按设计的过渡态,代号 W-CLEAN。** 它的完整形态是:一个诚实的空态提示"玩法模板体系升级中",一个被置灰(disabled)的"开始生成"按钮,外加一个"去游戏流"的出口。这是**有意为之的过渡态,不是断点缺陷**。 背后的真相是:后端的模板白名单 `SUPPORTED_TEMPLATE_IDS=["generic"]` 是**非空**的(保住 W-G1 这条一句话生成链路能跑),只是前端的创作入口暂时不暴露模板而已。需要和技术决策口径对齐的一点是:**被废掉的是"游戏模板/填参线"那套东西(W-CLEAN),而"玩法模板"这个品类引导框架并没有被废,只是等 W-G1 落地后回填**。(W-G1 = 第一批对外开闸的游戏;W-CLEAN = 把旧的"游戏模板/填参"产线清理掉的那项工作。两者别混。) ### 5.2 G1:GP9 合规门 staging 真验——10 条违规负例 100% 被挡 G1 验的是 GP9 这道合规门在真环境里到底挡不挡得住。结论很硬:**10 条违规负例经过真实的 new-api 网关(模型用 `MiniMax-M2.7`)判定,100% 返回 `safe=false`**,进而 `submitGenerate` **100% 抛出 `AIGC_UNSAFE_PROMPT`、0 条落库入队**——这一点在日志层和数据库层做了**双重实证**。这意味着 §2.2 设计的那道合规先行门,在真环境里确实做到了"违规一条都进不来"。 --- ## 6. 待办 / Follow-up(残留) 6 门已焊死、两道前置已验过,真正剩下的是放行决策本身和几项需要拍板的数值。这些都是已知的、被显式追踪的残留,不是隐藏缺口: 1. **开闸放行决策本身。** 焊死门已就绪,放行与否由创始人裁定(此决策优先于任何自动门)。 2. **待创始人拍的数值。** 组A 只落了 `level` 列加门结构,具体数值是占位的,需要拍板:会员档 L1 / L2 / L3 的每日配额与并发数(组A 占位)、D11 的评分权重(§3.2 占位)、D9 的模糊近似阈值——v0 只做了"精确撞重 + 归一化完全相等",模糊相似度怎么量,要等真实数据观察到分布之后再定。 3. **产品轨。** admin 的 aigc 任务列表页要不要近期就排上"就绪分"展示(后端契约已经透出了,前端需要新建 aigc 列表页,或在 review 页 join 取这个分)。 4. **承接自 SAA 治理(round3)的两项(已随 SAA 冻结)。** 这两项挂在 SAA 裸图那条线上:F3 = SAA 成本字段只落 token 数、未折人民币;早期 boot 阶段 SAA 图前几次派发瞬态失败需预热。SAA 已降为最低优先级远期(非 MVP runtime),这两项随之冻结、不再是活残留;现行 cheap/tier2 线的成本已按 new-api quota 折 ¥ 落 trace.cost(Anthropic 路由 `RecordingChatModel` 补齐取证,见运行时 SoT §5.7)。F5 早已修复(即 §3.3 那个 `5f6cdd0c`)。 --- ## 7. 源档导航 本文档是开闸验收门 W-G1 的策展层主档,把一份 canonical 活档(它本身又合并了 5 份历史 spec)整理成了这份给人读的全景。要看更深的门序、接线、契约细节与决策史,从下面进去: | 去处 | 回答什么 | |---|---| | `.agents/skills/cheap-model-game-generation.md` | 便宜模型造游戏的 worker loop、九门真玩 harness、成本与模型选择(深落地权威源之一) | | `.agents/skills/saa-graph-orchestration.md` | SAA 裸图编排的拓扑、加节点、checkpoint、dispatcher 契约与门(深落地权威源之一) | | `.agents/skills/contract-first-development.md` | Contract-first:如何对齐 API/DB/SDK/event 契约并解耦并行工作 | | [生成引擎主文档](README.md) | "一句话怎么变成一款游戏"的生成主线全景(本文的上游) | | [生成运行时架构](agentic运行时架构图说.md) | 生成主线的编排基建、六条不变量、两条 split-brain 治理铁律 | > **纪律**:本档是开闸验收门 W-G1 的策展层 SoT——读它 = 当前真相。门序/接线/契约的逐条落地在 `.agents/skills/`,决策史在 git;两者职责不同,不互相重复。当现行真相与某份带日期的历史 spec 冲突时,以本档与它引用的最新裁定为准。 --- > **验证状态**:本文档为架构策展文档,由开闸验收门 W-G1 canonical 活档改写而成,未改任何代码。承重硬事实——D12 四门门序、GP9 短超时 8s + 1 重试 + fail-closed、10 负例 100% 阻断、错误码 001 子段五码走 HTTP200(D12 三控制码 QUOTA_EXCEEDED/BACKPRESSURE_REJECTED/GENERATE_PAUSED + GP9 两合规码 AIGC_UNSAFE_PROMPT/AIGC_LLM_SAFETY_ERROR)、Flyway V15/V16 迁移、9d trace 必填七项、D11 权重(0.5/0.25/0.15/0.1)、首局门 3 断言(≤2s / 即时 / 60s)、G0 白名单 `["generic"]`、G1 真验 `MiniMax-M2.7` 100% safe=false / 0 落库、split-brain 修复 `703e462c` 与 firstPlay 修复 `5f6cdd0c`——均沿用源档已抽查属实的结论;品牌统一为"绘境AI",无旧名残留。