- B 轨黄金闭环具体化:创作→确定性模板填充→落包→发布→审核→feed→试玩→遥测→quality_score→排序 - 经只读代码可行性勘察:前端 Runtime 只会 clicker、后端数据链全桩+结构缺口(无FeedApi/包不落库/不算分) - 方案=后端数据链全建+前端轻经营(点击售卖,主题王蓝莓);8 组件/数据流/错误/测试/A-B验收 - 转 writing-plans 前的设计层 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
13 KiB
设计:王蓝莓的小卖部 · 黄金垂直闭环(B 轨实现设计)
文档号:HJ-DESIGN-006 | 日期:2026-06-09 | 分支:dev/2.0.0 上游:
docs/agent-specs/2026-06-09-下一阶段路线-plan.md(autoplan 三轨定稿)的 B 轨黄金闭环实现设计。 进度底账:docs/mvp/MVP进度总账.md。本文经 /superpowers:brainstorming 协作打磨 + 只读代码可行性勘察。 下一步:转 writing-plans 出实现计划。
1. 背景与动机
autoplan 双模型评审定稿"三轨并行",其中 B 轨 = 把 1 条完整垂直闭环做到真实可演示、可采数据,核心是 最小数据回路(遥测→quality_score→feed 排序)= 护城河种子。本设计把 B 轨具体化为一条端到端真链路,主题 「王蓝莓的小卖部」(经营模板,轻经营玩法)。
为什么是"轻经营 + 后端数据链全建"(方案 1,只读代码勘察实证):
- 前端 Canvas Runtime 只会跑写死的 clicker(
game-studio/src/host/runtime/index.ts:状态仅 score/running/target,manifest.entry代码加载链路未接)。跑完整经营要扩 Runtime(中→大,撞 <15KB 红线)。→ 故前端做轻经营(clicker 小扩成"点击售卖+极简库存"),不做完整经营状态机。 - 后端整条数据生产链是桩 + 结构缺口:aigc 不产 GamePackage、runtime 包从不 insert、telemetry 只
dispatchToMq(debug TODO)、feed-api 连FeedApi都没有、publish 不写 feed。→ 这条链是大而不可避免的工作,也正是护城河,本设计全建。
2. 目标 / 非目标
目标:
- 一条真实垂直闭环:创作→确定性模板填充生成→落包→发布→审核→feed 可见→试玩→遥测→quality_score→feed 排序,全程真实(非 mock、非 demo 兜底)。
- 最小数据回路真实产出:play 事件真落库、quality_score 真计算、feed 排序真读它(A/B 可证)。
- 「王蓝莓的小卖部」在玩法上真有"卖货赚钱"手感(点击售卖 + 极简库存),不只换文案。
非目标(YAGNI,本阶段明确不做):
- 不接真实 LLM/Dify(生成走确定性模板填充;真实生成是 C 轨 Spike)。
- 不做完整经营状态机(顾客 AI/进货循环/价格博弈 留后续)。
- 不接 MinIO/OSS(manifestUrl 走后端端点,GamePackage 存 DB)。
- 不接 MQ(telemetry 同步落库;MQ 只留契约 + TODO)。
- 不做 runtime 真实编译引擎(最小直接落包)。
- 不做真实鉴权/多租户(mock token;账号/权限是 P0,留下一步)。
- 其余 4 条链路只烟测登记(本设计只详化黄金闭环这一条)。
3. 架构总览
[创作] studio.createDraft → project.createProject ✅已有真实
↓ 用户选「经营」模板 + 填王蓝莓商品/价格
[生成] aigc.generate(经营模板,参数) → 确定性模板填充 → 合法 GamePackage 🔨桩→真(无LLM)
↓
[落包] runtime 最小直接落包 → game_runtime_package(READY) 🔨新建(跳过真编译)
↓ manifestUrl = 后端端点(不接OSS)
[发布] project.submitPublish 门禁 → REVIEWING ✅门禁已真
[审核] admin reviewProject(APPROVE) → PUBLISHED ──钩子──→ 写 feed rank 🔨新建 FeedApi.upsertRank
↓
[feed可见] feed.stream 读 game_feed_rank(按 sort_score 降序) ✅已有真实
↓
[试玩] 前端 getRuntimePackage→manifestUrl→Canvas Runtime「轻经营」 🔨前端小扩(点击售卖+极简库存)
↓
[遥测] game_play_start/end(duration_ms,completed)/like/share 🔨桩→真(同步落库,不接MQ)
→ /events/batch 同步写 event → upsert game_telemetry_game_stat
→ 算极简 quality_score → 窄seam 写 game_feed_rank.quality_score/sort_score
↓
[排序] feed.stream 重查 → 排序反映新 quality_score ✅已有(读端真实)
新建/改动单元(8 个):见 §4。 3 个关键架构决策(已确认):
- feed 写入时机 = 审核通过(PUBLISHED)那一刻(非提交发布 REVIEWING 时)——游戏 PUBLISHED 才该可见;A/B 验收需 2 个 PUBLISHED 游戏。
- manifestUrl = 后端端点(
/app-api/runtime/manifest/{versionId}),绕开 MinIO/OSS;GamePackage JSON 暂存 DB。 - quality_score 极简公式(给定具体默认,实现可调权重):
quality_score = clamp(60×完玩率 + 20×like率 + 20×share率, 0, 100)(完玩率=completed/play_start;like率/share率=like 或 share 数 / play_start,各截断至 1.0);sort_score = quality_score + boost。
4. 组件设计(职责 / 接口 / 依赖)
| # | 单元 | 职责 | 接口 | 依赖 |
|---|---|---|---|---|
| 1 | 经营 GameConfig schema(契约新建) | 约束 templateId=business 的 gameConfig 结构 |
contracts/templates/business.schema.json:{shopName, goods:[{id,name,cost,price,stock}], durationSec, goalMoney, customerIntervalMs} |
game-package.schema.json |
| 2 | aigc 模板填充器(桩→真) | 给 templateId+参数 → 确定性产合法 GamePackage,无 LLM | getTemplateList() 返真模板;generate task DONE 时产物=经营骨架代入参数→校验 business+package schema |
#1、project(gameId/versionId) |
| 3 | runtime 最小落包(新建) | 跳过真编译,把 GamePackage 写 game_runtime_package(READY) 存 DB |
persistPackage(versionId, pkg);GET /app-api/runtime/manifest/{versionId} 返 manifest JSON |
game_runtime_package 表(已有) |
| 4 | FeedApi.upsertRank(新建·填结构缺口) | feed-api 新增 FeedApi;upsert game_feed_rank,sort_score=quality_score+boost |
upsertRank(gameId, versionId, zoneId, qualityScore, boost)。两个调用方语义明确:project 钩子=建行写基线分(不下调已有 quality_score);telemetry=更新 quality_score。两者均重算 sort_score。因"发布先于试玩",正常无竞争;该规则是再发布/并发的安全网 |
game_feed_rank 表(已有) |
| 5 | project 发布钩子(改) | reviewProject(APPROVE)→PUBLISHED 后调 FeedApi.upsertRank 写 feed(初始基线分) |
现有 APPROVE 分支后加 seam 调用,不动门禁/状态机(D2 锚点) | #4 |
| 6 | telemetry 同步落库+算分(桩→真) | /events/batch 同步:写 event→upsert stat→算 quality_score→调 #4 更 rank |
替换 dispatchToMq 桩;同 traceId 批次幂等去重;MQ 只留契约+TODO |
event/stat 表(已有)、#4 |
| 7 | Canvas Runtime 轻经营(前端小扩) | clicker→点击售卖:读 gameConfig 商品/价/库存/时长;顾客按 interval 到;点击=卖(money+=price,stock--);时长尽/达 goal→结束。主题王蓝莓 | 扩 startRuntime;上报 game_play_start/game_play_end(duration_ms,completed)/like/share,带有效 traceId;保持 <15KB |
gameConfig(#1)、SDK postMessage(已有) |
| 8 | 6 开工前置项(前端,autoplan 已定) | VITE_API_BASE 真名 / mock 门控 / token=test1+tenant-id / sendBeacon 走 baseURL / 种子 tenant_id=1 / vite dev 连 staging | — | — |
契约新增:contracts/templates/business.schema.json、feed.yaml 加 FeedApi.upsertRank、runtime manifest 端点(GAP-2 manifestUrl 给真实现)。
单元边界自检:每个单元独立可测;改动严守——不动 project 门禁/状态机、不动 feed 读端排序逻辑、不接 MQ/OSS。
5. 数据流
主链路(一次完整跑通):
- 创作:studio.createDraft(creatorId, "王蓝莓的小卖部", templateId=
business) → project.createProject 返 gameId/versionId(✅真)。 - 生成:studio.generate → aigc.generate(templateId, params={goods:[辣条/汽水/冰棍…], 价/成本/库存, durationSec, goalMoney}) → 填充器取经营骨架代入参数 → 校验 business+package schema → 产合法 GamePackage(task DONE)。
- 落包:runtime.persistPackage(versionId, pkg) → 写
game_runtime_package(READY),package JSON 存 DB,manifestUrl=/app-api/runtime/manifest/{versionId}。 - 发布→审核:project.submitPublish 门禁(✅真)→REVIEWING;admin reviewProject(APPROVE)(✅真)→PUBLISHED →钩子 FeedApi.upsertRank(gameId, versionId, defaultZone, qualityScore=基线 0, boost=0) → 写
game_feed_rank。 - feed 可见:GET /app-api/feed/stream → 读 rank 按 sort_score 降序(✅真),含本游戏。
- 试玩:前端 getRuntimePackage→manifestUrl→fetch+sha256 校验→Canvas Runtime 轻经营跑王蓝莓小卖部(点击售卖)。
- 遥测:开局
game_play_start、结束game_play_end(props.duration_ms, completed)、点 like/share → /events/batch(带 traceId)→ 同步写 event → upsert stat → 算 quality_score → FeedApi.upsertRank 更新 quality_score/sort_score。 - 排序:重查 feed/stream → 顺序反映新分。
A/B 验收(证 quality_score 真驱动排序,Codex critical):备同 zone 同 boost 的 A/B 两个 PUBLISHED 王蓝莓游戏 → 记初始 feed 顺序 → 只向 B 上报有效 game_play_start+game_play_end(completed=true)+like+share → 断言 game_telemetry_event 有原始行、game_telemetry_game_stat.quality_score 变大、game_feed_rank.sort_score 变大 → 重查 feed B 排到 A 前 → 同 traceId 重发不重复计数 → 关 mock 仍通过。
6. 错误处理(失败路径)
| 失败点 | 处理 |
|---|---|
| aigc 填充产物 schema 不合法 | task FAILED,不落包,studio 链态 FAILED,前端提示;不污染下游 |
| runtime 包不存在(NOT_READY) | 前端现有逻辑走 demo 兜底——但黄金闭环要真包,故落包失败=闭环断,登记缺口(不靠兜底掩盖) |
| 审核 REJECT | 只 APPROVE 写 feed;REJECT 不写,游戏不可见 |
feed rank 并发写(审核钩子 vs telemetry 算分抢同一行 uk_game_zone) |
职责分离:project 钩子只在行不存在时写基线(不覆盖 quality_score);telemetry 权威更新 quality_score/sort_score。避免互相覆盖 |
| telemetry traceId 空/无效 | 后端拒(✅已有校验);前端必须生成有效 traceId(前置项) |
| telemetry 同 traceId 批次重发 | 幂等去重,不重复计数 |
| 前端 manifest fetch/sha256 失败 | 现有 fetchAndVerifyManifest 失败走兜底;黄金闭环须保证 manifest 端点返合法包(否则登记) |
| mock token 401 / 加密 / 跨域 | autoplan 已覆盖(token=test1+tenant-id、admin 关加密、CORS 已放行) |
7. 测试与验收
- A/B 验收协议(§5,已落
~/.gstack/projects/.../test-plan-20260609.md):端到端证 quality_score 驱动排序。 - 单元测试(各单元独立可测):① aigc 填充器(合法/非法参数各验产物 schema)② FeedApi.upsertRank(给分验 sort_score + 幂等)③ telemetry 同步(灌 play_start/end 验 stat 累加 + quality_score 公式 + 调 feed)④ project 钩子(APPROVE→FeedApi 被调;REJECT→不调)⑤ Runtime 轻经营(给 config 验售卖 money/stock/结束判定)。
- 前端 6 前置项验证:beacon 走 baseURL、token=test1+tenant-id、mock 门控。
- 主 agent 独立复跑(铁律,不采信子代理自报)。
完成判据:A/B 验收全绿 + 5 单元测试绿 + 黄金闭环浏览器肉眼可演示(创作→生成→发布→审核→feed→试玩→排序变化)。
8. 边界 / 回滚 / 风险
- 改动边界:不动 project 门禁/状态机(D2 锚点)、不动 feed 读端排序逻辑、不接 MQ/OSS/真实编译/真实 LLM。
- 回滚单元(各自独立 commit):① 前端联调开关+轻经营 ② aigc 填充器 ③ runtime 落包+manifest 端点 ④ FeedApi+project 钩子 ⑤ telemetry 同步落库。后端非零改动,回滚=按单元 revert + cleanup SQL。
- 数据污染清理:测试数据用固定 traceId 前缀 + 固定 gameId 清单 + cleanup SQL;验证前后表快照。
- 风险:① Runtime 轻经营撞 <15KB(缓解:状态机最小化)② 模板填充骨架与 Canvas Runtime gameConfig 解析口径需对齐(缓解:business.schema 为单一事实源,两侧都校验)③ DDL 是否需新增(game_runtime_package 存 package JSON——若现有列不够需 V10 迁移,实现时确认)。
9. 开放项 / 依赖
- DDL 确认:
game_runtime_package是否有列存完整 GamePackage JSON(manifest 端点要返它);feed/telemetry 现有表列已足够(勘察确认 stat 有 quality_score 列)。若 runtime 需加列 → V10 迁移。 - defaultZone:feed 默认专区 zoneId 取值(读 project game_zone 或约定常量)——实现时定。
- 经营骨架内容:王蓝莓商品清单/封面/文案——种子数据,实现时填(注意 IP:staging demo 可用,公开上线需换皮,撞 ip/compliance 锁风门)。