diff --git a/docs/plans/2026-06-28-002-feat-tier2-n5收敛环-go-no-go-plan.md b/docs/plans/2026-06-28-002-feat-tier2-n5收敛环-go-no-go-plan.md
new file mode 100644
index 00000000..9b649166
--- /dev/null
+++ b/docs/plans/2026-06-28-002-feat-tier2-n5收敛环-go-no-go-plan.md
@@ -0,0 +1,369 @@
+---
+title: tier2 富游戏 n=5 收敛环 go/no-go 细化执行 plan
+status: 待创始人批(§6.8 双评审已过 · Codex+Opus 发现全修)
+date: 2026-06-28
+topic: tier2-富游戏生成线
+canonical: false # tier2 canonical 在 plan①(统领)与 06-22-003(复杂档子计划);本档是「切片二·收敛环」的可跑 runbook 细化,不另立 SoT
+承接:
+ - docs/plans/2026-06-25-生成引擎统一执行计划-AgentScope三档-plan.md # plan① · 切片二统领(§切片二 121-140)
+ - docs/plans/2026-06-22-003-feat-tier2-富游戏自治生成线-plan.md # 复杂档子计划 · U1-U7 主体已落
+ - tier2/HANDOFF.md # 权威基准 · spike feie-005 实证
+ - docs/architecture/架构/生成引擎/agentic运行时架构图说.md # 运行时 SoT · §4.2/§5.1/§5.3/§5.4
+---
+
+# tier2 富游戏 n=5 收敛环 go/no-go 细化执行 plan
+
+> **执行者须知**:本档是 plan① 切片二「收敛环跑作一份细化执行」的兑现,不重排 06-22-003 已跑通的 runbook,只把"开跑前要补齐什么、怎么跑、怎么判"写到可照做的颗粒度。代码改动单元(U2/U3/U4)按 TDD 走:先写失败测试、看它失败、再最小实现。真跑(U1 smoke、U5 收敛环)只在 mini-desktop(chrome+esbuild;6c6g 禁 chrome、exit 144)。
+
+## Summary(结论先行)
+
+复杂档(Phaser·tier2)的核心生成线、九门+富游戏三门+四收敛契约门、批跑底座(`batch_run`/`aggregate`/`RunRecord`/`fallback_tree`)、成本观测、服务化已全部落在 `dev/2.0.0`;0 号 spike `feie-005` 已 accept(MiniMax-M3 自治写出过全门的面包店富游戏,n=1 机制验证)。**剩下的唯一悬念是:中等模型 M3 能否跨多款稳定收敛——这要用 n=5 收敛环来定 go/no-go。**
+
+本 plan 把开跑收敛环拆成五个单元:三个前置准备(批级护栏 / 退路树 n=5 适配 / 偏脆门结构化)、一个前置阻断(harness 在当前工作树可跑的复现 smoke + DockerWorkspace 托管小验)、一个核心(跑环 + 判 go/no-go + 退路树分流)。**执行口径(创始人 2026-06-28 定):串行跑(底座现状,不改成并发)、只跑 MiniMax-M3(不引 deepseek 便宜档、不引 GLM-5.2——后者是切片一 cheap-worker 的线)。** 验收走 n=5 收敛环(并发语义按"一批 5 款/轮"理解),绝不滑向 n≥30 统计批跑。
+
+go/no-go 判据离散:某轮 5 款里失败 ≤1 即收敛 → **go**;失败 >1 就读日志、补机器门或收窄 agent 可写面(小修,不调参)、再追加一轮;最多追加 3 轮(累计 n≤20)仍不收敛,按失败模式转退路树——表现层崩→更模板化(R1)、某系统装不出→补骨架(R2)、收敛需微调→留观(KEEP)。R3(便宜档全线垮而强档能过→换档)因网关现无强档(Opus/Fable/GLM 调用返 503)当前不可测,M3 全线垮只能记 conditional no-go、待强档上线复测。
+
+## Problem Frame(问题框定)
+
+```mermaid
+flowchart LR
+ spike["spike feie-005
M3 写面包店富游戏
9门+三门过·finished·n=1"] -->|"机制已证
但只跑通一款"| Q{"M3 能否
跨多款
稳定收敛?"}
+ Q -->|"n=5 收敛环"| go["go
(收敛即止)"]
+ Q -->|"不收敛"| R["退路树
按失败模式分流"]
+
+ subgraph 底座["批跑底座(已落 · dev/2.0.0)"]
+ br["batch_run.py
串行 model×variant×n"]
+ rr["RunRecord(jsonl)
含 fail_system 分流键"]
+ ag["aggregate.py
过门率/¥/收敛中位/fail_system 分布"]
+ ft["fallback_tree.py
五出口自动判定器"]
+ br --> rr --> ag --> ft
+ end
+
+ Q -.读取.-> 底座
+```
+
+spike 证明的是"M3 一旦被机器门挡在正确契约上,有能力自治写出过全门的多系统富游戏"——但它是 n=1,且收敛强依赖那四道为收敛而补的机器门。**收敛环要回答的不是"能不能写出一款",而是"换了题材、重跑几遍,这套机器门 + M3 这条路是否稳定收敛"。** 这是全线决断价值最高、但代价不低(每款 ~610s、~¥1.3)的一步,所以判据要离散、上限要钉死、退路要预设,不能滑成无限调参或统计批跑。
+
+## 起点与边界
+
+**已落(基准 = `tier2/HANDOFF.md` + 底座代码侦察 2026-06-28):**
+
+- 核心生成线 U1-U7:种子 fork + 有界外层 resume(`agent_loop/studio.py`,上限 6 次)+ Phaser `validate`/`run`/`prompt`/`roles` 重写。
+- Phaser CDP 探针 + 九门(A_boot/B_uncaught/C_frame/D_render/E_live/F_wiring/G_input/H_progress/I_control)+ 富游戏三门(三联动/经济/latch)。
+- 四道收敛契约机器门:`finish 门`、`LOCKED_PLATFORM_FILES`、`validate_datatable`、`validate_play_scene`(前三已结构化,第四仍偏脆——见 U4)。
+- 批跑底座:`tier2/gen-worker/batch_run.py`(CLI+import 双形态)、`aggregate.py`、`worker/run_record.py`(`@dataclass RunRecord` → jsonl)、`worker/fallback_tree.py`(`decide(stats)` 五出口自动判定器 + 6 自测)。
+- 成本观测:`RecordingChatModel(Anthropic)` + `observability/newapi_pricing.py`(活读 new-api 倍率)+ `Tier2TraceMiddleware`→TraceAdapter(真跑 647 事件 dropped=0、cost_rmb=1.29)。
+- 服务化 @8200(`service/app.py`)——**收敛环不用它**,走本地 runner(`run_engine._run` in-process,决策②「本地 runner·不上 Agent Service」)。
+- 模型矩阵(`tier2/config/generation.yaml` + `worker/config.py`):中等档默认 `MiniMax-M3`(Anthropic 原生 `/v1/messages`、thinking 分离);便宜档 `deepseek-v4-flash/pro`(new-api OpenAI 兼容);base_url=`http://100.64.0.8:3000`。**网关现仅 MiniMax + deepseek-v4-pro,Opus/Fable 返 503。**
+
+**边界(不在本 plan):**
+
+- Phase B 重机器(工作室 Agent Team / 第二装载分支 / 配置外置 / 控制面 / ReMe)——gate 在 B 门(收敛环 go)之后。
+- 便宜档 ≥80% 达标门——那是切片一 cheap-worker 的统计批跑口径,与 tier2 的 n=5 收敛环是两套口径,别混。
+- feed→play 第二装载分支 + tier2 源项目契约七要素落库取回——go/no-go 之后交后端。
+- deepseek 便宜档对照、GLM-5.2——本环 M3-only,不引(创始人 2026-06-28 定)。
+
+## 执行决策(创始人 2026-06-28)
+
+| 决策点 | 现状/冲突 | 裁定 | 影响 |
+|---|---|---|---|
+| **执行模式** | 底座 `run_matrix` 是串行(三层 for 顺序 await,作者注明"真跑烧 token+占 chrome/端口"故意不并发);plan① 写"并发跑 5" | **串行跑**(底座现状,零并发改动) | "并发 5"按"一批 5 款/轮"语义落;避免 mini-desktop 多个 Phaser gen 同时占 chrome/端口/内存导致 harness 判定失真 |
+| **收敛环模型** | 底座 tier2 矩阵 = M3(中等)+ deepseek 便宜档;memory 的 M3/GLM-5.2 是切片一另一条线;强基线 Opus/Fable 网关缺失 | **M3-only n=5** | 最直接回答"中等模型能否稳定收敛";R3 换档因无强档对照当前不可测,M3 全线垮记 conditional no-go |
+
+## Key Technical Decisions
+
+- **KTD1|收敛判据 = 末轮失败 ≤1 且已跑 ≥2 轮**。一轮 5 款里失败(decision≠accept 或 未 finished 或任一门未过)≤1 = 该轮收敛;但**单独的首轮 ≤1 不直接判 go**——n=5 下"≤1"=过门率 ≥80% 观测,真实 p≈0.6 的模型首轮裸蒙 ≥4/5 概率约 1/3,故要么是"修复后的轮 ≤1"(修复本身即收敛证据)、要么"首轮 ≤1 再跑一确认轮(同 5 变体不改)仍 ≤1",才判收敛。收敛后:末轮 0 错=`go`、1 错=`KEEP_留观`(都是 go-family);末轮 >1 进入修复-追加循环。这把 plan① "有错(超过 1)就…再追加一次 5,收敛即 go" 钉成可机器判、且压住首轮运气的离散阈值。
+- **KTD2|轮间修复只许"小修",不许调参,更不许结构级改动**。允许:补一道机器门(把新出现的契约违规变成响亮可修反馈)、收窄 agent 可写面。禁止:改 prompt 平衡参数(那是调参)、改骨架/换模板/换模型(那是退路,触发即停环转退路树)。
+ - **定性边界闸(防"补门+收窄"累积漂移成无限调参换皮)**:小修 = **不减少 agent 必须自治写的核心系统数量、不把任一系统从"agent 写"降级为"平台预置"**。一旦某轮修复触及系统核心逻辑(而非表现层胶水/数据表 schema 校验),或单轮需补 ≥2 道新门,即判**结构信号 → 停环转 R1/R2**。"可写面只能微收、不能搬走系统"——否则得到的 GO 反映的是"把契约面削到 M3 能过",不是"M3 在原契约面上能收敛"。这道纪律是收敛环不滑向无限调参的闸。
+- **KTD3|上限 fail-closed,达限即停批并保全已跑台账;跨轮上限也编译成机器门**。三层上限都 fail-closed、非人工纪律(§6.10 规则必须编译成机器门):① 单 gen 级底座已有(`rmb_hard_limit=3.0`/`wall_timeout_s=1800`/四熔断);② 单轮(批级)总闸(批 ¥/批墙钟/token,U2);③ **跨轮闸**——开跑前读台账 distinct round-prefix 数 / 总记录数,`>20`(即已达 n≤20)即 fail-closed 拒跑、提示"应已停环转退路树",`decide_n5` 在 `len(rounds)>4` 时也拒裁报"超上限"(U2/U3)。达限是硬停、非优雅降级,已落盘 RunRecord 不丢(断点续跑可读)。
+- **KTD4|退路树判定 M3-only 适配,但不重建底座判定器**。复用 `fallback_tree.decide()` 的 fail_system 集中度分流(R1/R2);在其上加一层 n=5 收敛 GO gate(KTD1)与 R3 conditional 分支(无强档时不误判 KEEP)。
+- **KTD5|harness 复现 smoke 是真·可开跑闸**。feie-005 在 `feat/tier2-phaser-engine` 分支跑、当前 `dev/2.0.0` 工作树无台账(无 `spike-runs.jsonl`)。开收敛环前必须在 mini-desktop 用 dev/2.0.0 代码 M3 跑通一款、复现 accept,证明这棵树的 harness+生成链确实可跑九门可判——否则收敛环的失败可能来自环境而非模型,go/no-go 不可信。
+
+## High-Level Design(单元与依赖)
+
+```mermaid
+flowchart TD
+ U1["U1 前置阻断
dev/2.0.0 复现 smoke + DockerWorkspace 托管小验"]
+ U2["U2 批级护栏 fail-closed
批¥/批墙钟/token 总闸"]
+ U3["U3 退路树 n=5 适配
收敛 GO gate + R3 conditional + n=6→n=5"]
+ U4["U4 validate_play_scene 结构化
正则→AST(护环判定可信)"]
+ U5["U5 跑 n=5 收敛环
串行·M3-only·go/no-go·退路树分流·证据落盘"]
+
+ U1 --> U5
+ U2 --> U5
+ U3 --> U5
+ U4 --> U5
+```
+
+U1 是前置阻断(其 smoke 段 blocking);U2/U3/U4 是可并行的前置准备(各自独立 TDD、独立可评审);U5 是核心,依赖 U1-U4 全部就绪。U2/U3/U4 都是本机(6c6g 或 Mac)纯逻辑改动、可单测,不需 chrome;U1/U5 必须 mini-desktop。
+
+---
+
+## U1 · 前置阻断:dev/2.0.0 复现 smoke + DockerWorkspace 托管小验
+
+**目标**:坐实"当前工作树的 harness+生成链在 mini-desktop 真能跑九门可判"(复现 smoke,blocking),并就"收敛环用哪个沙箱跑 harness"出结论(DockerWorkspace 托管小验,timeboxed、in-process 兜底)。
+
+**文件/产物**:
+- 跑:`tier2/gen-worker/batch_run.py`(已存在,不改)
+- 落盘:`tier2/gen-worker/results/smoke-dev2.jsonl`(复现 smoke 台账)
+- 小验脚本:`tier2/gen-worker/spikes/dockerworkspace_harness_probe.py`(新建,一次性探针,非生产路)
+- 结论落档:本 plan「执行发现」节 + `tier2/HANDOFF.md` 追加一行
+
+**步骤**:
+
+- [ ] **S0(硬前置闸 · 不满足即阻塞 U1/U5、不开跑)** mini-desktop 可达 + 环境就绪:`ssh root@100.64.0.7 'echo ok'` 通;`/root/.venvs/agentscope-tier2/bin/python -c "import agentscope; print(agentscope.__version__)"` == `2.0.2`;chrome 可用(非 exit 144);new-api `http://100.64.0.8:3000` quota 够(M3 路活)。任一不满足 → 见 Risks「mini-desktop 不可达」行,不进 S1。
+- [ ] **S1(复现 smoke · blocking)** 在 mini-desktop 用 dev/2.0.0 代码跑一款 M3 面包店:
+ ```bash
+ ssh root@100.64.0.7 # 真 IP,绕系统代理 fake-ip(系统代理是 198.18.x fake-ip)
+ ls ~ && git -C rev-parse --abbrev-ref HEAD # 先确认 dev/2.0.0 工作树路径与分支
+ cd /tier2/gen-worker
+ /root/.venvs/agentscope-tier2/bin/python batch_run.py --out results/smoke-dev2.jsonl \
+ --models MiniMax-M3 --variants 面包 --n 1 --template business-sim
+ ```
+ (venv 路径 = `/root/.venvs/agentscope-tier2/bin/python`,已知;`` 在 mini-desktop 上以 S1 第二行确认的真实路径替换。)
+ 预期:落一条 RunRecord,`decision=accept`、九门 9/9、富游戏三门 3/3、`finished=true`、无熔断。墙钟应在 spike 量级(~610s±)。**这只证 harness 不误杀金标(不假阴)。**
+- [ ] **S1b(反向 smoke · 不假阳)** 在 dev/2.0.0 用一个**已知坏产物**跑 harness(spike 第 4 bug 的 `class extends Phaser.Scene` 形态、或故意删 `tick` 的空转产物),断言产出 `decision!=accept`。**金标过(S1)+ 坏游戏拒(S1b)两条都绿,才坐实当前工作树 harness 双向可判、KTD1 失败计数可信**——否则 harness 若假阳(把空转判 accept),失败计数系统性偏低 → 直接制造假 GO。
+- [ ] **S2** 若 S1 未 accept 或 S1b 未拒:这是环境/分支差异信号(非模型问题)。读 `results/smoke-dev2.jsonl` + game-log,定位 dev/2.0.0 与 `feat/tier2-phaser-engine` 的 tier2 代码差异(对账 HANDOFF:73 列五个 commit b43d59c/f8ec30c/0fd0d4f/f64dcc6/8f2bbce 是否都已并入)。修平再回 S1/S1b。**S1+S1b 不双绿不得进 U5。**
+- [ ] **S3(DockerWorkspace 托管小验 · timeboxed 2h)** 写一次性探针 `spikes/dockerworkspace_harness_probe.py`:用 agentscope 2.0.2 的 `Workspace`(DockerWorkspace 形态)托管一次 CDP 九门 harness(把现有 in-process harness 的 `page.evaluate`/CDP 调用搬进 Workspace 沙箱跑一个金标 fixture),验它能否暴露足够 CDP 让九门可判。
+- [ ] **S4** 据 S3 结论定收敛环沙箱:
+ - DockerWorkspace 能托管九门 harness(CDP 够用)→ 收敛环用它(利跨机扩容、隔离干净);
+ - 不够/timebox 内跑不起来 → **回落 in-process(spike-proven,S1 已证)**,收敛环就用本地 runner 直跑;跨机沙箱选型(§二补⑥(c))降为 go/no-go 之后的 Phase B 项,不阻塞本环。
+- [ ] **S5** 把 S1 的复现结果 + S4 的沙箱裁定写进本 plan「执行发现」节与 `tier2/HANDOFF.md`。
+
+> **设计说明(非元叙述,是边界声明)**:plan① 把 DockerWorkspace 小验列为"前置于收敛环"。但 spike 已证 in-process harness 可跑九门,故本环的真·阻塞项是 S1 复现 smoke,而非 DockerWorkspace。S3/S4 honor plan① 的小验要求、同时用 in-process 兜底解掉它的阻塞性:沙箱选型可有结论,收敛环不被它卡住。
+
+---
+
+## U2 · 批级护栏 fail-closed
+
+**目标**:补齐 plan① 强制的"开跑前上限"——单 gen 级底座已有,缺的是批级总闸。达限硬停、保全已跑台账。
+
+**文件**:
+- 改:`tier2/gen-worker/batch_run.py`(`BatchConfig` 加批级上限字段;`run_matrix` 跑前/每格后检查)
+- 测:`tier2/gen-worker/tests/test_batch_guardrails.py`(新建)
+
+**接口(Produces)**:① `BatchConfig` 新增 `batch_rmb_cap/batch_wall_cap_s/batch_tokens_cap: float|int|None`;`run_matrix` 本次调用累计超任一上限抛 `BatchBudgetExceeded`、先 flush 已跑 RunRecord;② **跨轮闸 `assert_under_round_cap(out_path, *, max_total=20)`**:开跑前读台账总记录数,`>=max_total` 抛 `RoundCapExceeded`、拒跑;③ `main()` catch 这两个异常,打印"已落 N 条、断点可续"并**非 0 退出**(M-a)。
+
+> **三层作用域**:单 gen 级(底座已有)→ 单轮=单次调用(批级闸 ①)→ 跨轮(②③ 机器闸,**非人工数 prefix**,§6.10)。单轮闸管一次调用的 ¥/墙钟/token;跨轮闸管累计 n≤20、`decide_n5` 另在 `len(rounds)>4` 拒裁(U3)——两道把"不滑向无限追加"编译成门。
+
+**默认值(单轮口径 · directional v1,U5 跑前据 S1 实测单 gen 墙钟微调)**:
+- `batch_rmb_cap = 20.0`(单轮 5 款 × 单 gen ¥3 硬限 = ¥15 上界,留余量;spike 实测 ~¥1.3/款、典型轮 ~¥6.5)
+- `batch_wall_cap_s = 7200`(2h;串行 5 款 × ~610s ≈ 51min,留 ~2× 余量,且低于 5×单 gen 1800s 超时的绝对最坏 2.5h)
+- `batch_tokens_cap = 600_000`(单轮 5 款 × ~50K out + 缓存余量;防失控)
+
+**步骤(TDD)**:
+
+- [ ] **S1(RED)** 写 `test_batch_rmb_cap_fails_closed`:mock 单格 run-one seam 返回 run-result 形状 `{"cost": {"cost_rmb": 30}, "tokens": {...}, ...}`(成本经 `result_to_record` 取 `cost.cost_rmb`→`RunRecord.cost_yuan`,batch_run.py:243),`batch_rmb_cap=80` 跑 3 格;断言第 3 格累计 90>80 抛 `BatchBudgetExceeded`,且前两格 RunRecord 已落盘(读 out_path 得 2 条)。
+- [ ] **S2** 跑测试看失败(`BatchBudgetExceeded`/字段未定义)。
+- [ ] **S3(GREEN)** `BatchConfig` 加三字段(默认 None=不限,保持旧行为);`run_matrix` 每格 `append_record` 后累加 `cost_yuan`/`wall_seconds`/`tokens_out`,超任一非 None 上限即抛 `BatchBudgetExceeded`(已落盘的不回滚)。加 `assert_under_round_cap()` 跨轮闸 + `main()` catch 两异常非 0 退出。
+- [ ] **S4** 跑测试看过;补 `test_batch_wall_cap`、`test_batch_tokens_cap`、`test_caps_none_means_unlimited`(旧行为不破)、`test_round_cap_blocks_21st`(台账已 20 条 → `assert_under_round_cap` 抛 `RoundCapExceeded`)、`test_main_catches_and_exits_nonzero`(M-a)。
+ - **M-d(意图确认,非 bug)**:token 闸只计 `tokens_out`(in/cached 由 ¥ 闸覆盖、影响小);`batch_wall_cap=7200` < 5×单 gen 1800s 绝对最坏 2.5h,意味"慢轮即停"——这是**有意**(一轮 5 款全逼近单 gen 超时即异常,该停),非误配;真实墙钟据 S1 微调。
+- [ ] **S5** 跑全套 `tier2/gen-worker/tests/` 确认无回归。
+- [ ] **S6(commit)** `feat(tier2): 收敛环批级+跨轮护栏 fail-closed(批¥/墙钟/token/n≤20 闸)(切片二 U2)`
+
+---
+
+## U3 · 退路树 n=5 适配
+
+**目标**:`fallback_tree.py` 的阈值是为 n≥30 设的统计触发线、且硬编码不读 YAML、默认 n=6。把判定适配成 n=5 M3-only 口径:加收敛 GO gate(KTD1)、R3 conditional(无强档不误判 KEEP)、扩自测;复用底座的 fail_system 分流(R1/R2)不重建。
+
+**文件**:
+- 改:`tier2/gen-worker/worker/fallback_tree.py`(加 `EXIT_R3_CONDITIONAL` 常量 + `decide_n5(rounds, *, has_strong_baseline)` + 一个 `__main__`/CLI 入口 `decide_n5_from_jsonl(path)`:读全轮台账、按 `--prefix` 分轮、调 `decide_n5`、打印裁决——供 U5 S6 取 go/no-go,不依赖 `aggregate.py` 的旧 `decide()`)
+- 改:`tier2/gen-worker/batch_run.py` 注释澄清(**不动 `BatchConfig.n` 默认值**——它语义 = 每 (model,variant) 格重复次数,与"收敛环一轮 5 款 = 5 变体 × `--n 1`"是两个 n;改默认值会让"默认 5 变体 × `--n 5`=25 格"的陷阱出现。只加注释:"收敛环的一轮 5 款靠 `--n 1` + 5 变体,与 `BatchConfig.n` 无关")
+- 测:`tier2/gen-worker/tests/test_fallback_tree_n5.py`(新建)
+- 顺带(M-f · 既有失真,非本 plan 引入):`generation.yaml` 的 `gates` 区注释称阈值"消费方 fallback_tree.py、import 时读",但代码是模块级硬编码常量、不读 YAML。改 fallback_tree.py 时顺手对齐该 YAML 注释或留一行 TODO,别让那份不被读的配置看着像生效(防阈值双写漂移)。
+
+**接口(Produces)**:`decide_n5(rounds: list[list[RunRecord]], *, has_strong_baseline: bool) -> {exit: str, reasons: list[str]}`(返回形状对齐底座 `decide()` 的 `{exit, reasons[]}`,fallback_tree.py:172,**复数 reasons**)。出口**沿用 fallback_tree.py:79-87 既有常量值**(`EXIT_GO="go"` / `EXIT_R1_TEMPLATE="R1_退模板化"` / `EXIT_R2_SCAFFOLD="R2_补骨架"` / `EXIT_KEEP="KEEP_留观"`)**+ 新增 `EXIT_R3_CONDITIONAL="R3_conditional"`**(底座原 `EXIT_R3_CEILING="R3_天花板"` 是"有强档对照下的真换档",与无强档的 conditional 语义不同,故另立)。
+
+> **关键(`decide_n5` 是对 `decide()` 的覆写、非包装)**:`decide()` 是**便宜档对照导向**——fallback_tree.py:188-200 入口 Q1 先找 `STRONG_CHEAP_KEYS`(deepseek-v4-pro),M3-only 的 `by_model` 无此键 → `return EXIT_KEEP`(数据不足),根本走不到 Q2/Q3 的集中度分流(R1/R2);且其 `EXIT_R3_CEILING` 触发是"便宜档全崩**且强基线过**",与本环"无强基线"的 `R3_conditional` 互斥。故 `decide_n5` **覆写 Q1 入口(M3 不走便宜档 GO 门、改走 KTD1 失败计数)与 R3 语义**,R1/R2 分流**直接复用本模块纯 helper `_system_concentrated()`/`_bucket_share()` + 常量 `CONCENTRATION_RATIO`/`PRESENTATION_BUCKET`/`SYSTEM_BUCKETS`**(同模块直接调、不调 `decide()`、不 import aggregate 防环)。
+
+**判定逻辑(M3-only · `has_strong_baseline=False`)**:
+1. 数每轮失败 `fails[i]`(失败 = `decision!='accept'` ∨ `not finished` ∨ `not pass_gate`)。
+2. **收敛 GO 需"末轮 ≤1 且已跑 ≥2 轮"**(防首轮裸运气——真实过门率 p≈0.6 的模型,二项分布下首轮 ≥4/5 概率 ≈0.337,即 ~1/3 会裸蒙假 GO;见 M1/KTD1):
+ - `len(rounds)>=2 and fails[-1]==0` → **`go`**;
+ - `len(rounds)>=2 and fails[-1]==1` → **`KEEP_留观`**(收敛·go·留观该款微调,对应 plan① "收敛需微调→留观");
+ - `len(rounds)<2`(只跑了 Round1 即 ≤1)→ **`INCONCLUSIVE_需确认轮`**(非终判;runbook 应再跑一确认轮,见 U5 S3)。
+3. 末轮 `fails[-1]>1`(全程不收敛)→ 遍历全部失败款的 `fail_system` 分布,**集中度优先于换档**:
+ - `_bucket_share(presentation) ≥ CONCENTRATION_RATIO`(0.50)→ **`R1_退模板化`**;
+ - `_system_concentrated()`(resource/merge/order 任一 ≥0.50)→ **`R2_补骨架`**;
+ - 无任何集中(失败分散/全线垮)且 `has_strong_baseline=False` → **`R3_conditional`**(reasons:"M3 全线不收敛、网关无强档对照,待强档上线复测")。
+ - (对照:`has_strong_baseline=True` + 无集中 + 强档过 → 底座 `R3_天花板` 真换档;M3-only 不走此支。)
+
+> **R1/R2(有集中)先于 R3 判**:有系统/表现层集中会先命中、不被 R3 吞。**表现层全崩时 R1(更模板化)优先于 R3(换档)**——spike 已证 M3 有表现层能力,故先收窄模板、而非断言模型不行就换档。
+> **GO 与 KEEP_留观 都是"tier2 go"**(KEEP = go·留观该款微调);R1/R2/R3_conditional 是"未收敛"的退路。
+
+**步骤(TDD)**:
+
+- [ ] **S1(RED)** 写 `test_round1_le1_alone_inconclusive`:`rounds=[[4 accept+finished、1 fix]]`(单轮、失败=1)→ 断言 `exit=='INCONCLUSIVE_需确认轮'`(防裸首轮);再写 `test_two_rounds_clean_returns_go`(2 轮均 0 错 → `'go'`)、`test_repair_then_clean_returns_go`(Round1 失败 2、Round2 失败 0 → `'go'`)。
+- [ ] **S2** 跑测试看失败(`decide_n5` 未定义)。
+- [ ] **S3(GREEN)** 实现 `decide_n5`(放 `fallback_tree.py`,**不 import aggregate**[防环]、**不调 `decide()`**[它无 deepseek 强便宜档即 fallback_tree.py:195 `return EXIT_KEEP`、吞掉 M3-only]):① 数每轮失败 `fails[i]`(`decision!='accept' or not finished or not pass_gate`);② **GO/KEEP 需 `len(rounds)>=2 and fails[-1]<=1`**(末轮 0→`go`、1→`KEEP_留观`);`len(rounds)<2 and fails[-1]<=1`→`INCONCLUSIVE_需确认轮`;③ `fails[-1]>1`(不收敛)→ 复用本模块 `_bucket_share()`/`_system_concentrated()` + `CONCENTRATION_RATIO`:presentation≥0.50→`R1_退模板化`、系统桶≥0.50→`R2_补骨架`、无集中且 `has_strong_baseline=False`→`R3_conditional`。
+- [ ] **S4** 跑测试看过;补:
+ - `test_presentation_concentrated_routes_R1_退模板化`(全轮失败 >1、presentation 占比 0.6)
+ - `test_resource_concentrated_routes_R2_补骨架`(order 桶 0.6)
+ - `test_diffuse_fail_no_strong_baseline_routes_R3_conditional`
+ - `test_strong_baseline_passes_routes_R3_天花板`(`has_strong_baseline=True` 的对照,确认无集中+强档过 → 真换档而非 conditional)
+ - `test_default_n_is_5_in_docstrings`(口径对账,防回退 n≥30)
+ - `test_decide_n5_from_jsonl_cli`(喂一份多轮台账 jsonl,断言 CLI 入口出正确出口)
+- [ ] **S5** 跑底座自带 6 自测 + 新测,确认 `decide()` 旧出口不破。
+- [ ] **S6(commit)** `feat(tier2): 退路树 n=5 M3-only 适配(收敛 GO gate + R3 conditional)(切片二 U3)`
+
+---
+
+## U4 · validate_play_scene 结构化
+
+**目标**:四道契约门里唯一仍偏脆的一道(`validate_play_scene`,run.py:401-460 用正则/子串扫源码文本)做成结构化校验,防它假过/假失败污染收敛环 verdict。
+
+**文件**:
+- 改:`tier2/gen-worker/worker/run.py`(`validate_play_scene`)
+- 新建:`tier2/gen-worker/tools/validate-play-scene-ast.mjs`(node 侧 AST 校验,复用已有 esbuild/node 环境)
+- 测:`tier2/gen-worker/tests/test_validate_play_scene.py`(新建,喂对抗样本)
+
+**接口(保持兼容)**:**公开签名 `validate_play_scene(game_id)` 不变**(`build()` 在 run.py:196 以 `game_id` 调它、内部按 game_id 解析文件路径,run.py:401-420)。抽出纯函数 **`validate_play_scene_src(src: str) -> {ok: bool, violations: list}`** 承载结构化校验:`validate_play_scene(game_id)` 读完文件后调它,测试直接喂源码给它。`validate_play_scene_src` 内部 shell out `node tools/validate-play-scene-ast.mjs`(stdin 喂源码、stdout 返 `{ok, violations}`);AST 校验 `createPlayScene` 是工厂函数(收 deps、return 配置对象)、真实调用 `bindInput(`/`tick(`(AST CallExpression 节点、非文本出现)、暴露 `handleNormalizedInput`。
+
+**步骤(TDD)**:
+
+- [ ] **S1(RED)** 写 `test_comment_only_bindInput_rejected`:喂 `validate_play_scene_src(src)` 一段 `bindInput` 只在注释(`// call bindInput(core) later`)、无真实调用的源码 → 当前正则会假过;断言 `ok==False`、violations 含 "bindInput 未真实调用"。再写 `test_string_literal_handleNormalizedInput_rejected`(标识符只在字符串里)。
+- [ ] **S2** 跑测试看失败(现实现假过 → 测试红)。
+- [ ] **S3(GREEN)** 写 `validate-play-scene-ast.mjs`(用 node 内置或 esbuild 的 parser 出 AST、遍历查工厂结构 + CallExpression);新增纯函数 `validate_play_scene_src(src)` 调它;把 `validate_play_scene(game_id)` 改为"读文件 → `validate_play_scene_src(读到的源码)`",公开签名不变。
+- [ ] **S4** 跑测试看过;补 `test_valid_play_scene_passes`(金标 play-scene 源码仍过)、`test_missing_tick_rejected`、`test_not_factory_rejected`(写成 `class extends Phaser.Scene` → 拒,正是 spike 第 4 个 bug 形态)。
+- [ ] **S5** 跑金标 fixture 装配链冒烟,确认门一道没放松、fixture 仍 ACCEPT(防过度收紧误杀合法)。
+- [ ] **S6(commit)** `feat(tier2): validate_play_scene 正则→AST 结构化(切片二 U4)`
+
+> **务实回落**:若 S3 的 node AST 基建在 mini-desktop 出现集成摩擦(parser 依赖/超时),降级为"多信号硬化"(要求 `bindInput(`/`tick(` 以 `identifier(` 形态出现且不在注释/字符串区间——剥注释与字符串后再匹配),并在 U5 跑中对 M3 的 5 款 play-scene 人工抽检一次假判;完整 AST 作 go/no-go 后的 robustness follow-up。无论哪条,U4 不得放松金标 fixture 的 ACCEPT。
+
+---
+
+## U5 · 跑 n=5 收敛环 + go/no-go + 退路树分流
+
+**目标**:在 mini-desktop 串行跑 M3-only n=5 收敛环,据 KTD1 判 go/no-go,不收敛按 U3 退路树分流,全程证据落盘。
+
+**前置**:U1 S1 复现 smoke 绿 + U2/U3/U4 已 commit。
+
+**runbook**:
+
+- [ ] **S1** mini-desktop 跑前自检:venv 的 agentscope==2.0.2、chrome 可用(非 exit 144)、new-api quota 够(M3 路活)、U2 批级护栏已生效(`BatchConfig` 三上限非 None)、results/ 目录可写。
+- [ ] **S2(Round 1)** 跑一批 5 款(5 题材 × n=1;题材键须与 `BRIEF_VARIANTS` 字面一致——是 `糖水店`/`面包`,不是 `糖水`/`面包店`,否则 batch_run.py:423 直接 `raise ValueError`):
+ ```bash
+ cd /tier2/gen-worker
+ /root/.venvs/agentscope-tier2/bin/python batch_run.py --out results/tier2-converge.jsonl \
+ --models MiniMax-M3 --variants 美食 水果 咖啡 面包 糖水店 --n 1 \
+ --prefix cv-r1 --template business-sim
+ # aggregate 只取描述性三图(过门率/¥/收敛中位);它内置的 decide() 对 M3-only 会 Q1 早退"无法判定",
+ # 不作本环裁决——裁决在 S6 走 decide_n5。
+ /root/.venvs/agentscope-tier2/bin/python aggregate.py --in results/tier2-converge.jsonl
+ ```
+- [ ] **S3** 数本轮失败(`decision!='accept' or not finished or 任一门未过`)。**收敛需"末轮≤1 且≥2轮"(KTD1,压首轮运气)**:
+ - **本轮失败 >1 → 进 S4(修复)。**
+ - **仅 Round1 就 ≤1(冷首轮、未经修复)→ 不直接判 go,跑一确认轮(同 5 变体、不改任何代码,`--prefix cv-r2`)再回 S3。**
+ - **修复后的轮 ≤1,或确认轮也 ≤1(此时已 ≥2 轮)→ 收敛 → 跳 S6 判 go(末轮 0 错)/ KEEP_留观(末轮 1 错)。**
+- [ ] **S4(轮间修复 · KTD2)** 读失败款的 game-log + verdict,分类失败模式:
+ - 新出现的契约违规(M3 又自创某 schema/碰某锁)→ 补一道机器门 或 微收 agent 可写面(小修,守 KTD2 定性边界闸:不搬走系统、单轮 <2 道新门),commit。
+ - 已知系统反复装不出 / 表现层反复崩 / 触及系统核心逻辑 / 需骨架级或换模板级改动 / 单轮要补 ≥2 道门 → **结构信号,不在环内修**:停环,跳 S6 走退路树。
+- [ ] **S5(追加轮)** 改 `--prefix cv-r{N}` 重跑 S2(同 5 题材,带 S4 的修复),回 S3。**累计 ≤4 轮、n≤20;跨轮闸(U2 `assert_under_round_cap`)每轮开跑前自动拦 ≥20、`decide_n5` 在 `len(rounds)>4` 拒裁(机器门、非人工自觉)。超上限 → 停环转 S6 退路树(不再追加、不再调参)。**
+- [ ] **S6(判定 + 落盘)** 取裁决走 `decide_n5` 的 CLI 入口(**不用 aggregate 的旧 `decide()`**;判据由 `decide_n5` 自数失败、含 `finished`,不复用 aggregate 只看 `pass_gate` 的过门率——见 m-1):
+ ```bash
+ /root/.venvs/agentscope-tier2/bin/python -m worker.fallback_tree \
+ --n5 --in results/tier2-converge.jsonl --no-strong-baseline
+ ```
+ 出口处置:
+ - `go` / `KEEP_留观` → **tier2 富游戏线 go**(`KEEP_留观` = go·留观该款微调);
+ - `R1_退模板化` / `R2_补骨架` / `R3_conditional` → 未收敛退路,按出口记结论与下一步。
+ 落盘证据:`results/tier2-converge.jsonl`(全轮台账)+ aggregate 三图 JSON/txt(描述性)+ `decide_n5` 裁决 + go/no-go 一句话结论,写进本 plan「执行发现」与 `tier2/HANDOFF.md`。
+
+**验收口径**:九门 + 富游戏三门(三联动/经济/latch)+ 四收敛契约门;n=5 收敛环 go/no-go;**绝不 n≥30 统计批跑**。
+
+## 退路树(go/no-go 的离散出口)
+
+```mermaid
+flowchart TD
+ R1R["跑一轮 5 款"] --> E{"本轮失败数?"}
+ E -->|"仅 Round1 ≤1
(冷首轮)"| CONF["跑确认轮
(同变体·不改码)"] --> R1R
+ E -->|">1"| FIX["S4 修复(小修·守 KTD2 闸)
或结构信号→停环"] --> R1R
+ E -->|"末轮 0 · ≥2 轮"| GO["✅ go
tier2 富游戏线走"]
+ E -->|"末轮 1 · ≥2 轮"| KEEP["✅ KEEP_留观
go · 留观该款微调"]
+ E -->|"累计 20 仍 >1"| C{"失败模式集中度
(fail_system)"}
+ C -->|"presentation ≥0.50"| RR1["R1_退模板化
表现层收窄/加骨架壳"]
+ C -->|"resource/merge/order ≥0.50"| RR2["R2_补骨架
该系统补平台侧脚手架"]
+ C -->|"无集中 + 无强档对照"| RR3["R3_conditional
no-go · 待强档上线复测换档"]
+```
+
+退路按失败模式转、不再调参(KTD2)。R3 是 conditional——网关补上强档(GLM-5.2/Opus)后,跑一次便宜/中等档 vs 强档对照才能真判"是否模型天花板、是否该换档"。
+
+## Risks(风险与缓解)
+
+| 风险 | 影响 | 缓解 |
+|---|---|---|
+| **mini-desktop 不可达 / venv 缺失 / chrome exit 144**(U1·U5 真跑全压它) | U1/U5 全阻塞 | U1 **S0 硬前置闸**(可达+agentscope 2.0.2+chrome+quota),不满足即停、不开跑 |
+| dev/2.0.0 tier2 代码与 feie-005 分支有差异,S1 smoke 不复现;或 harness 假阳把坏游戏判 accept | 收敛环失败归因不清(环境 vs 模型);失败计数偏低制造假 GO | U1 **S1(金标过·不假阴)+ S1b(坏游戏拒·不假阳)双绿**为可开跑闸;不双绿不进 U5 |
+| mini-desktop 串行跑 4 轮墙钟长(~3.4h)+ 烧 token/¥ | 资源/成本 | U2 批级 fail-closed 兜底;串行本就为省资源争用;单 gen ¥3 硬限 |
+| `repairs` 字段是 `model_calls` 近似、非真自纠轮数 | 收敛中位图偏差 | 仅作辅助观测;go/no-go 主判据是 KTD1 失败计数,不依赖 repairs |
+| R3 无强档对照不可测 | 便宜档天花板结论缺一块 | 明确记 conditional no-go、待强档;不强行判 KEEP 掩盖 |
+| U4 AST 基建在 mini-desktop 摩擦 | U4 拖累开跑 | U4 务实回落(多信号硬化 + 人工抽检),完整 AST 作 follow-up |
+| 轮间"修复"滑成调参/结构改动 | 收敛环失去离散性、无限调 | KTD2 钉死小修边界;结构级即退路信号、停环 |
+
+## Verification(验证计划)
+
+- **U2/U3/U4**:本机 `/bin/python tests/test_*.py`(底座无 pytest,逐文件跑),每单元红→绿→全套无回归。
+- **U1**:S0 环境闸通过;S1 金标款 RunRecord 断言 accept/9门/三门/finished(不假阴)+ S1b 坏产物断言 `decision!=accept`(不假阳)——**harness 双向可判才放行 U5**。
+- **U5**:收敛环台账 + aggregate + `decide_n5` 裁决三件齐,go/no-go 有据可查、可复算。
+- **门不放松证据**:U3/U4 改动后金标 fixture 装配链冒烟仍 ACCEPT(九门 9/9 + 三门 3/3)。
+
+## 迁移对账(跨设计面归属 · 防越位/揽活)
+
+按 plan① 切片二跨设计面(§切片二 138)逐单元归属,确认零越位、零揽他 WU 的活:
+
+| 单元 | 归属(plan① WU / 设计 SoT) | 性质 |
+|---|---|---|
+| U1 复现 smoke + DockerWorkspace 小验 | WU-D 的 DockerWorkspace harness 小验(ADR-6 子项)+ WU-C 5.1 运行时 | 本切片自有(前置阻断) |
+| U2 批级护栏 | WU-F(预算闸/运行安全) | 本切片自有 |
+| U3 退路树 n=5 适配 | WU-F(go/no-go 判定) | 本切片自有 |
+| U4 validate_play_scene 结构化 | WU-C 5.4(结构化门) | 本切片自有 |
+| U5 跑环 + go/no-go | WU-B(复杂档实例)+ WU-F(判定) | 本切片自有(核心) |
+| feed→play 第二装载 / 源项目落库 | WU-C 5.6 | **移交后端**(go/no-go 后) |
+| 工作室 Agent Team / 控制面 / ReMe | Phase B(决策⑤) | **gate 在 B 门后**,不在本 plan |
+
+成功定义对齐本切片本职(用 n=5 收敛环定 M3 走不走),不拔高成"tier2 全部上线"。
+
+## Sources(事实来源)
+
+- plan① 切片二:`docs/plans/2026-06-25-生成引擎统一执行计划-AgentScope三档-plan.md` §切片二 121-140。
+- 复杂档子计划:`docs/plans/2026-06-22-003-feat-tier2-富游戏自治生成线-plan.md`(U1-U7 主体已落)。
+- 权威基准 + spike 实证:`tier2/HANDOFF.md`(feie-005 accept;tokens~36K、墙钟~610s、cost_rmb=1.29、四道为收敛补的机器门)。
+- 运行时 SoT:`docs/architecture/架构/生成引擎/agentic运行时架构图说.md` §4.2/§5.1/§5.3/§5.4。
+- 底座代码事实(2026-06-28 侦察):`tier2/gen-worker/batch_run.py`(串行 run_matrix、CLI)、`worker/run_record.py`(RunRecord jsonl)、`aggregate.py`、`worker/fallback_tree.py`(五出口 decide、阈值硬编码不读 YAML)、`config/generation.yaml`(budget/model/iteration)、`worker/middleware.py`(四熔断)、`worker/run.py`(validate_play_scene:401-460 偏脆)、`worker/config.py`(M3 Anthropic / deepseek OpenAI、网关无 Opus/Fable)。
+
+## 双评审发现与处置
+
+**Codex(`codex-rescue`)· 2026-06-28 · 6 条全经代码核验属实、已全部在文档内修:**
+
+- **B-1(blocker)** U5 题材键 `糖水` 非法(`BRIEF_VARIANTS` 是 `糖水店`,未知变体 batch_run.py:423 `raise ValueError`)→ 改 `糖水店`(并核出 `面包` 是对的、非 `面包店`)。
+- **B-2(blocker)** U3 原拟调底座 `decide()` 做 R1/R2 分流,但 `decide()` 在 fallback_tree.py:195 无 deepseek 强便宜档即 Q1 早退、M3-only 被吞 → 改 `decide_n5` 自算 fail_system 集中度、直接用 `CONCENTRATION_RATIO` 判,不调 `decide()`。
+- **B-3(blocker)** U4 改 `validate_play_scene(src)` 与现有 `build()` 调用 `validate_play_scene(game_id)`(run.py:196)冲突 → 公开签名不变,抽纯函数 `validate_play_scene_src(src)` 承载 AST、测试喂它。
+- **M-1(major)** U2 测试 mock 成本字段错(`result_to_record` 取 `cost.cost_rmb`,batch_run.py:243)→ mock 改 run-result 形状 `{"cost":{"cost_rmb":30}}`。
+- **M-2(major)** U3 出口名与既有常量不一致(`go`/`R1_退模板化`/`R2_补骨架`/`R3_天花板`/`KEEP_留观`,fallback_tree.py:79-87)→ 出口对齐既有值 + 新增 `R3_conditional`;U5 分支与退路树 mermaid 同步。
+- **M-3(major)** U5 原用 `aggregate.py` 取裁决,但它固定调旧 `decide()`(aggregate.py:289)、对 M3-only 无效 → 加 `decide_n5` 的 CLI 入口,S6 走它取 go/no-go。
+- **m-1(minor)** aggregate 过门率只看 `pass_gate`、不含 `finished` → `decide_n5` 自数失败(含 finished),不复用 aggregate 的 pass_count;aggregate 三图仅作描述性。
+- 总评:**方向可执行,U3/U5 接缝需先修** —— 已修。
+
+**Opus 对抗式评审 · 2026-06-28 · 2 blocker + 5 major + 6 minor,全经代码核验、全部已折入:**
+
+- **B1(blocker)** 深化 Codex B-2:`decide()` 不仅 Q1 早退,且 `aggregate.py:32` 也调 `decide()`、对 M3-only 落档矛盾 KEEP → `decide_n5` 改为**覆写**(非包装)、复用本模块 helper `_system_concentrated`/`_bucket_share`;aggregate 三图描述性、裁决唯一以 `decide_n5` 为准并显式声明其 KEEP 系档名缺失。出口名对齐既有常量。
+- **B2(blocker)** = Codex B-1(糖水店),已修。
+- **M1(major)** KTD1"末轮"歧义 + **首轮裸过假 GO**(p≈0.6 模型首轮 ≥4/5 概率 ~1/3)→ 收敛改"末轮≤1 **且 ≥2 轮**":修复后轮≤1 或冷首轮需确认轮复现;KTD1/U3/U5/mermaid 全同步。
+- **M2(major)** U1 只验金标过(不假阴)、未验坏游戏拒(不假阳)→ 加 **S1b 反向 smoke**,双向绿才放行 U5。
+- **M3(major)** KTD2"收窄可写面"会累积漂移成无限调参换皮 → 加**定性边界闸**(不搬走系统、单轮 <2 道新门;触系统核心即结构信号停环)。
+- **M4(major)** 跨轮 n≤20 靠人工数 prefix、与 KTD3 fail-closed + §6.10 矛盾 → 加**跨轮机器闸**(`assert_under_round_cap`>20 拒跑 + `decide_n5` len>4 拒裁)。
+- **M5(major)** 改 `BatchConfig.n` 默认 6→5 制造两义性(它是每格重复次数,非"一轮5款")→ **不改默认值**、只加注释澄清。
+- **minor(6)**:M-a `main()` catch `BatchBudgetExceeded` 非 0 退出;M-b U1 加 chrome/venv 自检(并入 S0);M-c mini-desktop 可达列硬前置(S0 + Risks 行);M-d token/wall 闸"慢轮即停"标为有意;M-e `decide_n5` 返回 `{exit, reasons[]}` 复数对齐底座;M-f 对齐 generation.yaml 失真注释。
+- **正面确认**(无需改):越位/揽活 check 通过、self-surfaced 点处置得当、U4 偏脆诊断准确、R3 不会误吞(R1/R2 先判)。
+- 总评:**有 blocker,修后可收口**——B1/B2 + M1/M2/M4 关乎 go/no-go 可信度与防无限调参,已全修。
+
+**处置小结**:Codex 6 + Opus 13(去重后)发现已全部在文档内修毕,无跨文档遗留 TODO。**双评审收口。**
+
+## 执行发现
+
+(U1 复现 smoke 结果 / 沙箱裁定 / U5 收敛环 go/no-go 结论落此。)
+
+## 状态
+
+**§6.8 双评审已过**(Codex 6 + Opus 13 发现全经代码核验、全部文档内修毕,无跨文档遗留)→ **待创始人批** → 执行(U2/U3/U4 本机 TDD 可先行;U1/U5 需 mini-desktop,U1 S0 环境闸为硬前置)。