From 6add6c771a827c2d25c10a830d4e4eecb807900a Mon Sep 17 00:00:00 2001 From: zizi Date: Tue, 9 Jun 2026 08:31:01 +0000 Subject: [PATCH] =?UTF-8?q?docs(design):=20=E7=8E=8B=E8=93=9D=E8=8E=93?= =?UTF-8?q?=E5=B0=8F=E5=8D=96=E9=83=A8=E9=BB=84=E9=87=91=E5=9E=82=E7=9B=B4?= =?UTF-8?q?=E9=97=AD=E7=8E=AF=E5=AE=9E=E7=8E=B0=E8=AE=BE=E8=AE=A1(brainsto?= =?UTF-8?q?rming=20=E4=BA=A7=E5=87=BA)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - B 轨黄金闭环具体化:创作→确定性模板填充→落包→发布→审核→feed→试玩→遥测→quality_score→排序 - 经只读代码可行性勘察:前端 Runtime 只会 clicker、后端数据链全桩+结构缺口(无FeedApi/包不落库/不算分) - 方案=后端数据链全建+前端轻经营(点击售卖,主题王蓝莓);8 组件/数据流/错误/测试/A-B验收 - 转 writing-plans 前的设计层 Co-Authored-By: Claude Opus 4.8 --- .../2026-06-09-王蓝莓小卖部黄金闭环-design.md | 143 ++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 docs/superpowers/specs/2026-06-09-王蓝莓小卖部黄金闭环-design.md diff --git a/docs/superpowers/specs/2026-06-09-王蓝莓小卖部黄金闭环-design.md b/docs/superpowers/specs/2026-06-09-王蓝莓小卖部黄金闭环-design.md new file mode 100644 index 00000000..906d14cc --- /dev/null +++ b/docs/superpowers/specs/2026-06-09-王蓝莓小卖部黄金闭环-design.md @@ -0,0 +1,143 @@ +# 设计:王蓝莓的小卖部 · 黄金垂直闭环(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 个关键架构决策(已确认)**: +1. **feed 写入时机 = 审核通过(PUBLISHED)那一刻**(非提交发布 REVIEWING 时)——游戏 PUBLISHED 才该可见;A/B 验收需 2 个 PUBLISHED 游戏。 +2. **manifestUrl = 后端端点**(`/app-api/runtime/manifest/{versionId}`),**绕开 MinIO/OSS**;GamePackage JSON 暂存 DB。 +3. **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. 数据流 + +**主链路(一次完整跑通):** +1. **创作**:studio.createDraft(creatorId, "王蓝莓的小卖部", templateId=`business`) → project.createProject 返 gameId/versionId(✅真)。 +2. **生成**:studio.generate → aigc.generate(templateId, params={goods:[辣条/汽水/冰棍…], 价/成本/库存, durationSec, goalMoney}) → 填充器取经营骨架代入参数 → 校验 business+package schema → 产合法 GamePackage(task DONE)。 +3. **落包**:runtime.persistPackage(versionId, pkg) → 写 `game_runtime_package(READY)`,package JSON 存 DB,manifestUrl=`/app-api/runtime/manifest/{versionId}`。 +4. **发布→审核**:project.submitPublish 门禁(✅真)→REVIEWING;admin reviewProject(APPROVE)(✅真)→PUBLISHED →**钩子** FeedApi.upsertRank(gameId, versionId, defaultZone, qualityScore=基线 0, boost=0) → 写 `game_feed_rank`。 +5. **feed 可见**:GET /app-api/feed/stream → 读 rank 按 sort_score 降序(✅真),含本游戏。 +6. **试玩**:前端 getRuntimePackage→manifestUrl→fetch+sha256 校验→Canvas Runtime 轻经营跑王蓝莓小卖部(点击售卖)。 +7. **遥测**:开局 `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。 +8. **排序**:重查 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 锁风门)。