Merge remote-tracking branch 'origin/master' into codex/puzzle-clear-template-runtime-fixes
# Conflicts: # .hermes/shared-memory/decision-log.md # src/index.test.ts
This commit is contained in:
@@ -37,6 +37,74 @@ SpacetimeDB 表结构变更、自动迁移边界和保留旧数据的分阶段
|
||||
|
||||
AI 文字游戏模板接入以 [AI_NATIVE_TEXT_GAME_TEMPLATE_MOKU_REFERENCE_PRD_2026-05-05.md](./prd/AI_NATIVE_TEXT_GAME_TEMPLATE_MOKU_REFERENCE_PRD_2026-05-05.md) 为最新口径:只吸收 MOKU / 幕间类 AI 文游的剧本游乐场、自由行动、AI GM、记忆和模拟器强反馈经验,禁止迁入外部社区、支付、榜单、私有存档或回放。
|
||||
|
||||
前端 Server-Sent Events 客户端传输层收口到 `src/services/sseStream.ts`,事件边界、UTF-8 flush、JSON 解析跳过和提前取消约定见 [【前端架构】SSE客户端传输层收口约定-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91SSE%E5%AE%A2%E6%88%B7%E7%AB%AF%E4%BC%A0%E8%BE%93%E5%B1%82%E6%94%B6%E5%8F%A3%E7%BA%A6%E5%AE%9A-2026-06-03.md)。
|
||||
|
||||
平台入口公开作品身份、跨玩法去重、公开作品流聚合、推荐运行态 kind 判定、推荐 runtime 启动意图、ready 判定和最新排序收口到 `src/components/platform-entry/platformPublicGalleryFlow.ts`,规则见 [【前端架构】平台入口PublicGalleryFlowModule收口计划-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91%E5%B9%B3%E5%8F%B0%E5%85%A5%E5%8F%A3PublicGalleryFlowModule%E6%94%B6%E5%8F%A3%E8%AE%A1%E5%88%92-2026-06-03.md)。
|
||||
|
||||
统一作品详情页的玩法 kind、详情打开策略、自有作品动作模式、编辑 / 点赞 / 改造 / 启动意图和公开详情映射收口到 `src/components/platform-entry/platformPublicWorkDetailFlow.ts`;抓大鹅公开详情映射与启动 / 编辑 Adapter 的素材归一仍归 `platformMatch3DRuntimeProfile.ts`,规则见 [【前端架构】PlatformPublicWorkDetailFlow收口计划-2026-06-03.md](./technical/【前端架构】PlatformPublicWorkDetailFlow收口计划-2026-06-03.md)。
|
||||
|
||||
创作中心作品架打开动作由 `CreationWorkShelfItem.actions.open` 统一承载,生产 Hub 只接收 `CreationWorkShelfItem[]` 与 UI 状态,不再接收各玩法 raw items 和回调列阵,规则见 [【前端架构】WorkShelfModule收口计划-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91WorkShelfModule%E6%94%B6%E5%8F%A3%E8%AE%A1%E5%88%92-2026-06-03.md)。
|
||||
|
||||
作品架删除确认的标题、删除说明、草稿 notice key 和拼图派生稳定 ID 收口到 `src/components/platform-entry/platformCreationWorkDeleteFlow.ts`,平台壳只保留删除 API、刷新、错误和页面跳转副作用,规则见 [【前端架构】CreationWorkDeleteFlow收口计划-2026-06-04.md](./technical/【前端架构】CreationWorkDeleteFlow收口计划-2026-06-04.md)。
|
||||
|
||||
创作入口点击的占位、隐藏模板拦截、未知入口 no-op 与工作台启动目标收口到 `src/components/platform-entry/platformCreationLaunchModel.ts`,壳层只执行启动前准备、错误提示和受保护动作,规则见 [【前端架构】PlatformCreationLaunchModel收口计划-2026-06-04.md](./technical/【前端架构】PlatformCreationLaunchModel收口计划-2026-06-04.md)。
|
||||
|
||||
平台入口公开码搜索的用户 ID、陶泥号、RPG 作品号、各玩法作品号前缀、per-play 公开码匹配、详情卡 DTO 映射和失败回退顺序收口到 `src/components/platform-entry/platformPublicCodeSearchModel.ts`,壳层只按计划执行网络读取、详情打开和错误归航副作用,规则见 [【前端架构】PlatformPublicCodeSearchModel收口计划-2026-06-04.md](./technical/【前端架构】PlatformPublicCodeSearchModel收口计划-2026-06-04.md)。
|
||||
|
||||
个人“玩过作品”面板的玩法别名、`worldKey` 前缀兜底、RPG 公开详情 payload 和大鱼吃小鱼 gallery miss fallback 收口到 `src/components/platform-entry/platformPlayedWorkOpenModel.ts`,壳层只执行面板关闭、gallery 读取、详情打开和错误提示副作用,规则见 [【前端架构】PlatformPlayedWorkOpenModel收口计划-2026-06-04.md](./technical/【前端架构】PlatformPlayedWorkOpenModel收口计划-2026-06-04.md)。
|
||||
|
||||
平台入口生成页进度 tick 的 stage 到生成状态映射、终态判定和视觉小说轻量生成特例收口到 `src/components/platform-entry/platformGenerationProgressTickModel.ts`,壳层只保留 `Date.now()`、`setInterval` 和 cleanup 副作用,规则见 [【前端架构】PlatformGenerationProgressTickModel收口计划-2026-06-04.md](./technical/【前端架构】PlatformGenerationProgressTickModel收口计划-2026-06-04.md)。
|
||||
|
||||
平台壳的拼图 runtime 恢复 work、方洞 session draft 转 profile、视觉小说 work detail 转 Agent session、跳一跳 pending session、敲木鱼 detail 恢复 session、敲木鱼生成中作品摘要和敲木鱼 pending session DTO 映射收口到 `src/components/platform-entry/platformMiniGameSessionMappingModel.ts`,壳层只保留网络、状态、URL 与 stage 副作用,规则见 [【前端架构】PlatformMiniGameSessionMappingModel收口计划-2026-06-04.md](./technical/【前端架构】PlatformMiniGameSessionMappingModel收口计划-2026-06-04.md)。
|
||||
|
||||
平台小游戏生成状态的恢复、失败 / 完成收尾、展示 rebase、拼图后端进度合并、抓大鹅生成资产旁路进度合并和 ready / generating 判定收口到 `src/components/platform-entry/platformMiniGameDraftGenerationStateModel.ts`,壳层只保留 API、后台任务、React state、URL 与 stage 副作用,规则见 [【前端架构】PlatformMiniGameDraftGenerationStateModel收口计划-2026-06-04.md](./technical/【前端架构】PlatformMiniGameDraftGenerationStateModel收口计划-2026-06-04.md)。
|
||||
|
||||
平台小游戏草稿恢复和提交所需的拼图 / 抓大鹅表单 payload、拼图作品更新 payload、拼图编译 action、跳一跳 / 敲木鱼生成 action、pending metadata 与拼图 form-only 草稿判定收口到 `src/components/platform-entry/platformMiniGameDraftPayloadModel.ts`,壳层只保留 API、Action 执行、background task 与状态副作用,规则见 [【前端架构】PlatformMiniGameDraftPayloadModel收口计划-2026-06-04.md](./technical/【前端架构】PlatformMiniGameDraftPayloadModel收口计划-2026-06-04.md)。
|
||||
|
||||
平台拼图生成完成后刷新恢复的草稿归一化与可恢复完成态判定收口到 `src/components/platform-entry/platformPuzzleDraftRecoveryModel.ts`,恢复链路只有在首图、关卡画面、UI spritesheet 与关卡背景资产包完整时才抬为 ready,规则见 [【前端架构】PlatformPuzzleDraftRecoveryModel收口计划-2026-06-04.md](./technical/【前端架构】PlatformPuzzleDraftRecoveryModel收口计划-2026-06-04.md)。
|
||||
|
||||
拼图排行榜提交回包后的服务端 run 快照合并收口到 `src/components/platform-entry/platformPuzzleRuntimeStateModel.ts`,只合并排行榜、run 身份、通关数上限和下一关 handoff,保留前端即时裁决的关卡状态与棋盘,规则见 [【前端架构】PlatformPuzzleRuntimeStateModel收口计划-2026-06-04.md](./technical/【前端架构】PlatformPuzzleRuntimeStateModel收口计划-2026-06-04.md)。
|
||||
|
||||
后端拼图发布 / 待发布门槛收紧到首图、关卡画面、UI spritesheet 与关卡背景资产包完整,`module-puzzle` 的 preview blockers 与 `api-server` 的 session stage 判定保持同一规则,方案见 [【后端架构】PuzzlePublishAssetGate收紧计划-2026-06-04.md](./technical/【后端架构】PuzzlePublishAssetGate收紧计划-2026-06-04.md)。
|
||||
|
||||
平台入口个人钱包本地 delta、dashboard 乐观更新与服务端快照对账规则收口到 `src/components/platform-entry/platformProfileWalletDeltaModel.ts`,平台壳只保留 API、ref 与 state 副作用,规则见 [【前端架构】PlatformProfileWalletDeltaModel收口计划-2026-06-04.md](./technical/【前端架构】PlatformProfileWalletDeltaModel收口计划-2026-06-04.md)。
|
||||
|
||||
Bark Battle 草稿三图完整性、生成状态归一、作品架摘要恢复草稿配置、发布快照 / 发布回包资产兜底和草稿 / 已发布作品进入 runtime 前的 `BarkBattlePublishedConfig` 映射收口到 `src/components/platform-entry/barkBattleWorkCache.ts`,规则见 [【前端架构】BarkBattleWorkCache草稿状态收口计划-2026-06-04.md](./technical/【前端架构】BarkBattleWorkCache草稿状态收口计划-2026-06-04.md)。
|
||||
|
||||
平台首页推荐 runtime 的匿名 Runtime Guest Token、已登录 background auth、非 embedded no-op 和拼图 isolated/default auth mode 计划收口到 `src/components/platform-entry/platformRecommendRuntimeAuthModel.ts`,规则见 [【前端架构】PlatformRecommendRuntimeAuthModel收口计划-2026-06-04.md](./technical/【前端架构】PlatformRecommendRuntimeAuthModel收口计划-2026-06-04.md)。
|
||||
|
||||
平台首页推荐 runtime 自动启动的桌面 / Tab / stage / loading gate、active entry 查找、ready 判定和 clear/start/noop 决策收口到 `src/components/platform-entry/platformPublicGalleryFlow.ts`,规则见 [【前端架构】PlatformRecommendRuntimeAutoStart收口计划-2026-06-04.md](./technical/【前端架构】PlatformRecommendRuntimeAutoStart收口计划-2026-06-04.md)。
|
||||
|
||||
RPG Agent 结果页发布门禁展示和预览来源 label 收口到 `src/components/platform-entry/platformRpgAgentResultPreviewModel.ts`,壳层只保留 session/profile 编排和结果页 props 传递,规则见 [【前端架构】PlatformRpgAgentResultPreviewModel收口计划-2026-06-04.md](./technical/【前端架构】PlatformRpgAgentResultPreviewModel收口计划-2026-06-04.md)。
|
||||
|
||||
平台入口创作生成通知、pending 作品架占位、作品详情更新回填、失败覆盖、跨玩法草稿打开优先级、拼图稳定 ID 和草稿 Tab 未读点收口到 `src/components/platform-entry/platformDraftGenerationShelfModel.ts`,规则见 [【前端架构】DraftGenerationShelfModel收口计划-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91DraftGenerationShelfModel%E6%94%B6%E5%8F%A3%E8%AE%A1%E5%88%92-2026-06-03.md)。
|
||||
|
||||
平台入口创作恢复 URL 私有 query、初始恢复判定、创作直达恢复目标解析、恢复目标身份匹配、跳一跳 / 敲木鱼恢复阶段落点、拼图 runtime query 与拼图稳定身份互推收口到 `src/components/platform-entry/platformCreationUrlStateModel.ts` 和 `src/components/platform-entry/platformPuzzleIdentityModel.ts`,规则见 [【前端架构】CreationUrlStateModel收口计划-2026-06-03.md](./technical/【前端架构】CreationUrlStateModel收口计划-2026-06-03.md)。
|
||||
|
||||
平台入口错误 / 完成弹窗的文案归一、来源格式、候选择一、dismiss key 与任务完成文案收口到 `src/components/platform-entry/platformDialogStateModel.ts`,规则见 [【前端架构】PlatformDialogStateModel收口计划-2026-06-03.md](./technical/【前端架构】PlatformDialogStateModel收口计划-2026-06-03.md)。
|
||||
|
||||
平台入口受保护数据失效后的 stage 去留判定,以及缺失草稿 / 作品 / run 时的阶段回退,收口到 `src/components/platform-entry/platformSelectionStageModel.ts`,壳层只执行缓存清空、布尔事实汇总和必要跳转,规则见 [【前端架构】PlatformSelectionStageModel收口计划-2026-06-04.md](./technical/【前端架构】PlatformSelectionStageModel收口计划-2026-06-04.md)。
|
||||
|
||||
小游戏 runtime client 的路径编码、JSON 请求、runtime guest auth 与 retry 选项收口到 `src/services/runtimeRequest.ts`,Match3D、SquareHole、Big Fish、Bark Battle、Puzzle 公开 / 推荐运行态请求、Jump Hop / Wooden Fish 正式 run 请求和 Visual Novel 局部 JSON runtime 请求已先迁移,规则见 [【前端架构】RuntimeClientFamily收口计划-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91RuntimeClientFamily%E6%94%B6%E5%8F%A3%E8%AE%A1%E5%88%92-2026-06-03.md)。
|
||||
|
||||
抓大鹅 runtime profile 的公开详情转 work、session draft 转 profile、生成背景资产提升和 run/profile/public detail 素材优先级收口到 `src/components/platform-entry/platformMatch3DRuntimeProfile.ts`,规则见 [【前端架构】Match3DRuntimeProfile收口计划-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91Match3DRuntimeProfile%E6%94%B6%E5%8F%A3%E8%AE%A1%E5%88%92-2026-06-03.md)。
|
||||
|
||||
公开作品分类选项、搜索、跨来源去重、今日筛选、排行排序和时间戳解析收口到 `src/components/rpg-entry/rpgEntryPublicGalleryViewModel.ts`,规则见 [【前端架构】PublicGalleryViewModel收口计划-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91PublicGalleryViewModel%E6%94%B6%E5%8F%A3%E8%AE%A1%E5%88%92-2026-06-03.md)。
|
||||
|
||||
公开作品的玩法类型 label、公开作者 lookup 与游玩 / 改造 / 点赞等紧凑计数格式收口到 `src/components/rpg-entry/rpgEntryWorldPresentation.ts`,规则见 [【前端架构】PublicWorkPresentation收口计划-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91PublicWorkPresentation%E6%94%B6%E5%8F%A3%E8%AE%A1%E5%88%92-2026-06-03.md)。
|
||||
|
||||
推荐 feed 的公开作品去重、普通内容过滤、active 窗口与上一条 / 下一条回环选择也收口到 `src/components/rpg-entry/rpgEntryPublicGalleryViewModel.ts`,规则见 [【前端架构】RecommendFeedViewModel收口计划-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91RecommendFeedViewModel%E6%94%B6%E5%8F%A3%E8%AE%A1%E5%88%92-2026-06-03.md)。
|
||||
|
||||
移动端推荐首页 swipe deck 的拖拽阈值、offset clamp、commit 方向、rail class 和分享文案收口到 `src/components/rpg-entry/rpgEntryRecommendSwipeDeckModel.ts`,规则见 [【前端架构】RecommendSwipeDeckModel收口计划-2026-06-03.md](./technical/【前端架构】RecommendSwipeDeckModel收口计划-2026-06-03.md)。
|
||||
|
||||
排行频道的默认 tab、tab 文案、空态文案、排序字段与指标 label/value 收口到 `src/components/rpg-entry/rpgEntryPublicGalleryViewModel.ts`,规则见 [【前端架构】RankingViewModel收口计划-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91RankingViewModel%E6%94%B6%E5%8F%A3%E8%AE%A1%E5%88%92-2026-06-03.md)。
|
||||
|
||||
每日任务卡片与任务中心弹窗的任务选择、进度、状态标签和按钮文案收口到 `src/components/rpg-entry/rpgEntryProfileTaskViewModel.ts`,规则见 [【前端架构】ProfileTaskViewModel收口计划-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91ProfileTaskViewModel%E6%94%B6%E5%8F%A3%E8%AE%A1%E5%88%92-2026-06-03.md)。
|
||||
|
||||
个人数据卡、钱包 chip 与“玩过”弹窗的计数、时长、作品类型和作品号展示收口到 `src/components/rpg-entry/rpgEntryProfileDashboardPresentation.ts`,规则见 [【前端架构】ProfileDashboardPresentation收口计划-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91ProfileDashboardPresentation%E6%94%B6%E5%8F%A3%E8%AE%A1%E5%88%92-2026-06-03.md)。
|
||||
|
||||
个人资金展示的账单来源、金额正负号、余额兜底、充值价格、商品主值和会员摘要收口到 `src/components/rpg-entry/rpgEntryProfileFundsViewModel.ts`,规则见 [【前端架构】ProfileFundsViewModel收口计划-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91ProfileFundsViewModel%E6%94%B6%E5%8F%A3%E8%AE%A1%E5%88%92-2026-06-03.md)。
|
||||
|
||||
## 推荐阅读顺序
|
||||
|
||||
1. 先看 [经验沉淀](./experience/README.md),快速建立这个项目的开发共识。
|
||||
|
||||
@@ -205,10 +205,11 @@ WF-*
|
||||
|
||||
1. 若 payload 已包含上传/录音音频资产,`compile-draft` 跳过音效生成,直接持久化该资产;
|
||||
2. 若 payload 已上传或录制音频,则直接写回 `hitSoundAsset`;
|
||||
3. 若两者都没有,后端写回默认木鱼音 `/wooden-fish/default-hit-sound.mp3`;
|
||||
4. 音效资产必须包含可播放地址、对象键、asset object id、来源和可选时长;
|
||||
5. 通用创作音频接口当前对 `wooden_fish` 的 `hit_sound` 目标返回 `410 Gone`,不得在创作流程中按提示词生成音效;
|
||||
6. `spacetime-client` 不得自行合成 `/generated-wooden-fish-assets/...` 音效占位路径;缺少真实 `hitSoundAsset` 时应使用默认木鱼音兜底展示与播放。
|
||||
3. 麦克风录制音频在保存前由前端自动裁掉开头连续静音段;上传音频不做裁剪,裁剪失败时保留原始录音继续保存;
|
||||
4. 若两者都没有,后端写回默认木鱼音 `/wooden-fish/default-hit-sound.mp3`;
|
||||
5. 音效资产必须包含可播放地址、对象键、asset object id、来源和可选时长;
|
||||
6. 通用创作音频接口当前对 `wooden_fish` 的 `hit_sound` 目标返回 `410 Gone`,不得在创作流程中按提示词生成音效;
|
||||
7. `spacetime-client` 不得自行合成 `/generated-wooden-fish-assets/...` 音效占位路径;缺少真实 `hitSoundAsset` 时应使用默认木鱼音兜底展示与播放。
|
||||
|
||||
### 6.3 封面
|
||||
|
||||
@@ -371,7 +372,7 @@ finish
|
||||
|
||||
音频播放:
|
||||
|
||||
1. 前端使用小复音池;
|
||||
1. 前端使用 10 路小复音池;
|
||||
2. 设置最小播放间隔,避免极端连点导致浏览器抖动;
|
||||
3. 点击计数不能因为音频节流而丢失;
|
||||
4. 签名 URL 未就绪时先静音表现,不请求裸 generated 私有路径。
|
||||
|
||||
@@ -2,491 +2,198 @@
|
||||
|
||||
## 1. 目标
|
||||
|
||||
新增一个可创作、可试玩、可发布的玩法模板:
|
||||
`jump-hop` 重定义为竖屏俯视角平台跳跃游戏。创作者只输入主题,系统生成一张该主题的 `1024x1536` 立方体主题物体 UV 展开图集,按 `3列*6行` 容纳 18 个方块,每个方块再按固定 `4列*3行` UV 网切成 top/front/right/back/left/bottom 六张面贴图;运行态使用 Three.js 复用标准 `1x1x1` 等比极小倒角立方体几何体,把六面贴图贴到立方体地板上组成无限平台流,同时使用陶泥儿 logo 透明 PNG 作为玩家角色。
|
||||
|
||||
```text
|
||||
跳一跳
|
||||
```
|
||||
首版目标:
|
||||
|
||||
本模板参考拼图模板的创作闭环,沿用“创作入口 -> 生成过程页 -> 结果页 -> 试玩 -> 发布”的平台链路,但玩法本体改为俯视角 / 等距视角的跳跃闯关。
|
||||
|
||||
首版要求:
|
||||
|
||||
1. 初始草稿生成时,角色形象单独调用一次生图;
|
||||
2. 初始草稿生成时,地块只调用一次生图,输出 3D 视图的 2D 图片图集;
|
||||
3. 运行态不接真实 3D 网格,不生成 GLB / glTF;
|
||||
4. 作品可以直接进入试玩和发布。
|
||||
1. 创作输入只保留主题,标题、简介、标签和提示词由系统派生;
|
||||
2. image2 只生成一张 `1024x1536` 地板 UV 展开图集,后端切成 18 组、共 108 张面贴图 PNG;
|
||||
3. 角色不再单独生图,v1 使用 `public/branding/jump-hop-taonier-character.png` 陶泥儿 logo 透明 PNG;
|
||||
4. 运行态每屏只展示 3 个地块:当前地块、目标地块、下一预览地块;
|
||||
5. 操作方式为长按屏幕蓄力并按拖拽方向起跳,松手后角色按前端提交的后端方向向量弹出;
|
||||
6. 只要落点未命中下一个地块,本局立即失败并冻结计时;
|
||||
7. 成绩记录成功跳跃次数和游戏时长;
|
||||
8. 排行榜按作品维度展示玩家 ID、成功跳跃次数和游戏时长,排序为成功跳跃次数降序、游戏时长升序、更新时间升序。
|
||||
|
||||
## 2. 模板定位
|
||||
|
||||
模板 ID:
|
||||
- 模板 ID:`jump-hop`
|
||||
- 展示名:`跳一跳`
|
||||
- 工程域:`jump-hop`
|
||||
- 创作入口卡:`subtitle = 主题驱动平台跳跃`,`imageSrc = /creation-type-references/jump-hop.webp`
|
||||
- 运行态:`Three.js 标准 1x1x1 等比极小倒角立方体地板 + DOM 角色 + DOM HUD`
|
||||
- 画面比例:移动端竖屏优先,桌面端居中承载 `9:16`
|
||||
- 素材策略:18 个立方体主题物体 UV 展开包装 + Three.js 复用标准 1x1x1 等比立方体几何 + 陶泥儿 logo 透明角色
|
||||
- 渲染分层:Three.js 平台层复用一份标准 `1x1x1` 等比极小倒角立方体几何体,`tileAssets[]` 切片只作为主题身份方块包装贴图;单块立方体必须正轴向摆放,不做 Y 轴偏航或 Z 轴歪斜旋转,也不得用不同 x/y/z scale 压成扁盒子;运行态视角采用约 `1.3x` 近距相机和 45° 下压视角,当前脚下地块基准位于屏幕中线略下方,后续两块向上展开且保持紧凑的纵向 / 横向间距;Three.js 平台层与 DOM 角色层必须保持屏幕 X 轴同向,禁止通过反向相机 `up` 或镜像容器把平台左右翻转;DOM 地块图片层只用于换签、预加载、WebGL 不可用和测试 fallback,Three.js 平台层 ready 后必须隐藏 DOM 地块图片和 DOM 阴影,退出地块只随相机推进自然离屏,不播放独立飞走动画,超过屏幕后再销毁,避免旧地块退出期露出被放大的平面 DOM 贴图;角色必须由 DOM 透明 PNG 层渲染并保持在 Three.js 平台层之上
|
||||
|
||||
本玩法不是横版平台跳跃,也不是关卡制闯关。平台从屏幕下方向上无限延展,目标地块在当前地块上方不同 x 轴位置随机出现。
|
||||
|
||||
## 3. 创作工具平台接入声明
|
||||
|
||||
- 工作台模式:表单输入创作工作台
|
||||
- 创作链路:入口 -> 工作台 -> 生成页 -> 结果页 -> 试玩 -> 发布 -> 运行态
|
||||
- 单图资产槽位:无独立角色图槽位;v1 固定使用陶泥儿 logo 透明 PNG 角色
|
||||
- 系列素材槽位:
|
||||
- `batchId = jump-hop-tile-atlas`
|
||||
- `sheetSpec = 1024x1536 / 3列*6行大单元 / 每格4列*3行UV网 / PNG / 纯洋红 #FF00FF 安全缝与外圈背景 / 后端切图为面贴图 PNG`
|
||||
- `slotSpecs = tile-01 ... tile-18`,每个 tile 再包含 `top/front/right/back/left/bottom` 六个面 slot,所有 slot 必须对应唯一 OSS path / `assetObjectId`
|
||||
- 切图规则:先按原图宽高均分为 3 列 6 行,从上到下、从左到右得到 18 个大单元;每个大单元内部固定 4 列 3 行 UV 网,`top` 在第 1 行第 2 列,`left/front/right/back` 在第 2 行第 1-4 列,`bottom` 在第 3 行第 2 列;每个面输出 `256x256` 不透明 PNG
|
||||
- 透明化规则:生成时要求纯洋红 key 安全缝和 UV 空位,后端不做透明化抠图,只把裁切后残留的洋红 key 色转为不透明材质底色,保留绿色、白色、雪地、云朵、草地、花朵、果肉粉色和浅黄色等主题纹理
|
||||
- 失败回写:生成失败时 session 保持 failed,可从生成页重试
|
||||
- 局部重生成:结果页允许重生成地板贴图图集,仍只调用一次 image2;前端展示生成图时以 `assetObjectId` 作为刷新键,避免同一路径重写后的旧签名或旧缓存
|
||||
- API 命名空间:`/api/creation/jump-hop/*`、`/api/runtime/jump-hop/*`
|
||||
- 业务真相:后端裁决落点、失败、成功跳跃次数、冻结时长和排行榜
|
||||
- 创作工具模式例外:无
|
||||
- 验证命令:`npm run check:encoding`、`npm run typecheck`、`cargo test -p module-jump-hop --manifest-path server-rs/Cargo.toml`
|
||||
|
||||
## 4. 创作输入
|
||||
|
||||
主题是唯一必填项。工作台不展示角色提示词、地块提示词、风格卡、难度卡、终点氛围或规则说明。
|
||||
|
||||
提交后系统自动派生:
|
||||
|
||||
1. 作品标题:主题为空白修剪后的短标题,默认前缀不外露;
|
||||
2. 作品简介:基于主题生成一句短简介;
|
||||
3. 标签:`跳一跳`、`休闲` 和主题关键词;
|
||||
4. 地板贴图提示词:围绕主题生成 18 个风格一致的立方体主题物体 UV 展开包装,每个包装由 top/front/right/back/left/bottom 六面组成,供 Three.js 标准 1x1x1 等比极小倒角立方体地板复用;实际 image2 prompt 使用“立方体主题物体 UV 展开包装图集 / cube object UV unwrap atlas”措辞,要求六面共同表达同一个完整方块化主题物体,例如水果主题要生成可一眼辨认的方块苹果、方块香蕉、方块橙子、方块西瓜等,而不是单纯生成平铺材质、抽象纹理、平台、跳台、地块成品、单张图重复六面或游戏界面资源;
|
||||
5. 初始平台流参数:固定 v1 标准参数,不让创作者手工调规则。
|
||||
|
||||
## 5. 地板贴图图集
|
||||
|
||||
image2 只生成一张 `1024x1536` 竖版图片,画面为 `3列*6行` 均匀分布的立方体主题物体 UV 展开包装;实际提示词必须先约束“画面只包含 18 个用于跳一跳地板的立方体主题物体 UV 展开包装图”,并明确这是供 Three.js 标准 1x1x1 等比极小倒角立方体使用的 cube object UV unwrap atlas。每个大单元格代表一个完整方块化主题物体,并在固定 `4列*3行` UV 网中提供六张面贴图;不是单纯材质贴片、单张图重复六面、地块成品图、跳板、物体剪影、游戏界面、棋盘、背包、装备栏或图标集页面。
|
||||
|
||||
图集要求:
|
||||
|
||||
1. 每个大单元内部固定使用 `4列*3行` UV 网,只有六个位置有贴图:第 1 行第 2 列是 `top`;第 2 行第 1-4 列依次是 `left / front / right / back`;第 3 行第 2 列是 `bottom`;其它位置保持纯洋红 `#FF00FF`;
|
||||
2. 每个面都是 full-bleed 不透明正方形贴图,四角、边缘和中心都要有可识别内容;六个面共同组成同一个完整方块化主题物体,不能把同一张纹理重复六次,也不能六面各画互不相关的小图标;
|
||||
3. 贴图不生成已经渲染好的透视 3D 块体成品,不包含摄像机角度、已烘焙侧壁、已烘焙厚度、自身投影、接触阴影或烘焙高光;真实倒角、侧壁、透视和阴影由运行态 Three.js 生成;
|
||||
4. 18 个方块来自同一主题、同一哑光手绘包装体系,但应表达不同方块化主题物体或明显不同的包装识别特征;水果主题要混排方块苹果、方块香蕉、方块橙子、方块西瓜、方块草莓、方块葡萄、方块奇异果、方块菠萝、方块柠檬、方块桃子、方块梨、方块蓝莓、方块芒果、方块椰子、方块火龙果、方块樱桃、方块哈密瓜、方块石榴,不要 18 个方块都只是同一种果皮、果肉或叶脉纹理;
|
||||
5. 大单元之间、UV 空位、六面之间和画布外圈为纯洋红 `#FF00FF`,方便后端安全切图;
|
||||
6. 不包含角色、文字、水印、UI、游戏面板、棋盘、背包、装备栏、按钮、标题、外层边框、可见网格线、场景背景、落地投影、接触阴影、方形阴影、方形底板、白底、灰底或黑底;
|
||||
7. 贴图不能跨格、贴边串色或进入相邻格;每个面贴图应尽量铺满自己的 UV 面,纯洋红只作为安全缝、UV 空位和外圈 key 色。
|
||||
|
||||
大单元切片顺序固定为:
|
||||
|
||||
```text
|
||||
jump-hop
|
||||
tile-01 tile-02 tile-03
|
||||
tile-04 tile-05 tile-06
|
||||
tile-07 tile-08 tile-09
|
||||
tile-10 tile-11 tile-12
|
||||
tile-13 tile-14 tile-15
|
||||
tile-16 tile-17 tile-18
|
||||
```
|
||||
|
||||
用户展示名:
|
||||
每个 `tile-XX` 再切出 `top/front/right/back/left/bottom` 六个面贴图并写入 `tileAssets[].faceAssets`。历史兼容字段 `imageSrc/imageObjectKey/assetObjectId` 保存 top 面,旧作品没有 `faceAssets` 时运行态仍可把单张旧贴图应用到立方体所有面。运行态随机使用这 18 个地块作为后续平台外观。起点地块可复用第一个切片,其余平台从完整池中随机选择。
|
||||
|
||||
## 6. 运行态规则
|
||||
|
||||
### 6.1 平台流
|
||||
|
||||
运行态从底部初始地块开始,后续地块持续向屏幕上方生成。每次相机窗口只保留 3 个地块可见:
|
||||
|
||||
1. 当前地块;
|
||||
2. 目标地块;
|
||||
3. 下一预览地块。
|
||||
|
||||
服务端保存当前 run 的路径缓冲,并在每次成功落地后按同一 seed 补齐后续地块。前端只展示服务端快照,不自行生成正式路径。
|
||||
|
||||
### 6.2 操作
|
||||
|
||||
1. 用户按住当前地块或画面开始蓄力;
|
||||
2. 长按时长形成蓄力值,达到 `maxChargeMs` 后封顶;
|
||||
3. 松手后角色按本次输入方向弹出;
|
||||
4. 蓄力值决定跳跃距离,拖拽方向决定跳跃方向;
|
||||
5. 前端必须同时提交 `dragDistance` 与换算到后端世界坐标的 `dragVectorX/dragVectorY`,后端以这两个方向字段裁决真实落点;旧客户端缺失方向或方向非法时,后端才 fallback 到当前地块中心指向下一块地块中心。
|
||||
|
||||
手感参数固定由后端 `module-jump-hop` 提供:`chargeToDistanceRatio = 0.004`。该值表示蓄力时长到世界跳跃距离的换算系数;旧作品运行时若仍携带其它系数,开局归一化为 `0.004`。契约中的 `dragDistance` 语义是前端提交的蓄力值;`dragVectorX/dragVectorY` 是正式方向输入契约,不能在前端提交或后端裁决中丢弃。
|
||||
|
||||
松手后前端必须立即生成 `visualJump`,用当前角色位置作为起点、前端预测真实落点作为终点,播放约 `560ms` 的角色飞行动画;视觉预测必须使用当前显示窗口的 current/next 地块作为方向来源,即使后端最新 run 已提前返回,也不能拿新 run 目标配旧窗口角色导致下一跳反向;角色从当前地块沿下一块地块中心方向弹向预测真实落点,蓄力阶段角色只做垂直压缩,不沿目标方向拉长。成功落地后必须保留 `lastJump.landedX/landedY` 对应的真实落点偏移,不得强制吸附回目标地块中心;落地后可以轻量回弹,但不能把角色位置拉离真实落点。动画期间 DOM 地块窗口保持在本次起跳前的 3 块布局,动画路径不得等待后端新 run。若后端新 run 晚于飞行动画返回,角色必须停在预测真实落点等待;新 run 到达后应先使用后端真实落点对齐显示态,再进入约 `1440ms` 的相机推进过渡,避免角色先飞过很远再瞬间拉回地块。推进过渡中,地块 DOM 层和 DOM 角色层必须放在同一个相机层里统一位移,不允许 p1/p2 单独改 `top/left` 做过渡;旧当前地块只随相机推进保留在屏幕后方,不单独执行飞走动画,玩家继续向前跳时再被新的相机推进自然带出屏幕并销毁,新预览地块从上方自然露出,避免角色和地块不同步或闪现。相机推进必须同时携带 X/Y 偏移,从旧真实落点位置斜向滑到新当前地块聚焦位置,不允许先横向瞬切居中后再只做纵向滑动。地块可以保留当前 / 目标 / 预览的深度尺寸差异,但该差异必须通过固定基准宽高上的 CSS `transform: scale(...)` 表达,并在相机推进期间用同一 `1440ms` 缓动过渡;不得通过直接改宽高造成瞬切变大。当前地块高亮不得额外通过 CSS `scale` 放大。该动画只属于表现层,命中、失败、成功跳跃次数和冻结时长仍以后端裁决为准。
|
||||
|
||||
### 6.3 判定
|
||||
|
||||
1. 目标永远是当前地块后的下一个地块;
|
||||
2. 真实落点沿前端提交的 `dragVectorX/dragVectorY` 归一化方向计算;仅当方向缺失、非有限数或长度过小时,才沿当前地块中心到下一块地块中心方向兼容计算;
|
||||
3. 落点进入下一个地块可见顶面 footprint,则成功;footprint 使用当前路径里该地块 `width/height` 的收缩矩形模拟 45° 视角下的可见顶面,当前命中区约为宽度 72% 和高度 52%;
|
||||
4. 落点未进入下一个地块可见顶面 footprint,则失败;旧 `landingRadius/perfectRadius` 字段仅保留兼容读写,不再作为当前 v1 成功判定;
|
||||
5. 失败后状态改为 `failed`,计时冻结;
|
||||
6. v1 没有通关状态、combo、perfect 或生命数。
|
||||
|
||||
### 6.4 计分与时间
|
||||
|
||||
- 成功跳跃次数:每成功落到下一个地块后 `+1`;
|
||||
- 游戏时长:`startedAtMs` 到 `finishedAtMs`,失败时冻结;
|
||||
- 运行中时长由前端根据服务端 `startedAtMs` 展示;
|
||||
- 失败后只展示冻结时长。
|
||||
|
||||
## 7. 排行榜
|
||||
|
||||
排行榜按作品维度生成。每位玩家只保留 1 条最佳记录。
|
||||
|
||||
排序规则固定为:
|
||||
|
||||
```text
|
||||
跳一跳
|
||||
successfulJumpCount desc -> durationMs asc -> updatedAt asc
|
||||
```
|
||||
|
||||
体验关键词:
|
||||
|
||||
1. 俯视角;
|
||||
2. 等距感地块;
|
||||
3. 单局闯关;
|
||||
4. 长按蓄力,松手起跳;
|
||||
5. 轻量休闲。
|
||||
|
||||
首版采用竖屏优先的移动端体验,桌面端保持居中展示,画面比例以 `9:16` 为主。参考图的核心视觉要点是:
|
||||
|
||||
1. 大面积留白或浅色渐变背景;
|
||||
2. 角色站在单个地块上;
|
||||
3. 地块有明显顶面、侧面和投影;
|
||||
4. 整体是俯视角 / 等距视角,而不是横版平台跳跃;
|
||||
5. UI 克制,只保留必要控制,不堆说明文案。
|
||||
|
||||
## 3. 与拼图模板的复用边界
|
||||
|
||||
可以复用:
|
||||
|
||||
1. 创作入口和模板分流;
|
||||
2. 生成过程页;
|
||||
3. 结果页的草稿保存、返回编辑、试玩、发布、分享链路;
|
||||
4. 作品架展示和草稿恢复口径;
|
||||
5. 平台统一的发布与公开展示流程。
|
||||
|
||||
不复用:
|
||||
|
||||
1. 拼图关卡切片逻辑;
|
||||
2. 拼图拖拽拼块逻辑;
|
||||
3. 拼图 UI 背景和多关卡编辑结构;
|
||||
4. 任何方格拼合语义。
|
||||
|
||||
## 4. 工程接入范围
|
||||
|
||||
首版需要做到完整玩法闭环,不只做入口占位。
|
||||
|
||||
新增前端阶段:
|
||||
|
||||
```text
|
||||
jump-hop-workspace
|
||||
jump-hop-generating
|
||||
jump-hop-result
|
||||
jump-hop-runtime
|
||||
jump-hop-gallery-detail
|
||||
```
|
||||
|
||||
新增前端组件建议:
|
||||
|
||||
1. `src/components/unified-creation/workspaces/JumpHopCreationWorkspace.tsx`;
|
||||
2. `src/components/jump-hop-result/JumpHopResultView.tsx`;
|
||||
3. `src/components/jump-hop-runtime/JumpHopRuntimeShell.tsx`;
|
||||
4. `src/services/jump-hop/jumpHopClient.ts`。
|
||||
|
||||
新增共享契约建议:
|
||||
|
||||
1. `packages/shared/src/contracts/jumpHop.ts`;
|
||||
2. `server-rs/crates/shared-contracts/src/jump_hop.rs`。
|
||||
|
||||
新增后端模块建议:
|
||||
|
||||
1. `server-rs/crates/module-jump-hop`:纯领域规则,包含路径生成、蓄力换算、落点判定、通关 / 失败状态机;
|
||||
2. `server-rs/crates/api-server/src/jump_hop.rs` 和 `src/jump_hop/` 子模块:HTTP handler、生成编排、资产保存和 DTO 映射;
|
||||
3. `server-rs/crates/spacetime-module/src/jump_hop.rs`:session、work profile、runtime run、公开 view 和 reducer / procedure;
|
||||
4. `server-rs/crates/spacetime-client/src/jump_hop.rs`:api-server 访问 SpacetimeDB 的 facade;
|
||||
5. `server-rs/crates/api-server/src/modules/jump_hop.rs`:路由挂载。
|
||||
|
||||
入口配置事实源必须走 SpacetimeDB `creation_entry_type_config` 默认种子和后台配置接口,不新增前端硬编码入口配置。
|
||||
|
||||
## 5. 创作输入
|
||||
|
||||
创作者需要填写以下内容:
|
||||
|
||||
1. 作品主题描述,必填;
|
||||
2. 角色形象描述,必填;
|
||||
3. 地块风格卡,必选;
|
||||
4. 难度,必选;
|
||||
5. 可选的终点氛围或节奏偏好。
|
||||
|
||||
推荐的最小输入形态是:
|
||||
|
||||
1. 一句话主题;
|
||||
2. 角色一句话描述;
|
||||
3. 风格卡;
|
||||
4. 难度卡。
|
||||
|
||||
不在首版开放手工拖拽平台编辑器。平台路径、地块间距和终点位置由系统自动生成,创作者只负责风格与难度选择。
|
||||
|
||||
### 5.1 地块风格卡
|
||||
|
||||
建议提供以下风格:
|
||||
|
||||
1. 极简积木;
|
||||
2. 纸模玩具;
|
||||
3. 霓虹玻璃;
|
||||
4. 森林石块;
|
||||
5. 未来金属;
|
||||
6. 自定义。
|
||||
|
||||
### 5.2 难度
|
||||
|
||||
建议提供以下离散档位:
|
||||
|
||||
1. 轻松;
|
||||
2. 标准;
|
||||
3. 进阶;
|
||||
4. 挑战。
|
||||
|
||||
难度主要影响:
|
||||
|
||||
1. 平台路径长度;
|
||||
2. 平台间距;
|
||||
3. 可落点容差;
|
||||
4. 完美落点窗口;
|
||||
5. 终点前的节奏变化。
|
||||
|
||||
## 6. 生成规则
|
||||
|
||||
本模板必须把生图责任拆成两条独立链路:
|
||||
|
||||
### 6.1 角色形象只生一次
|
||||
|
||||
角色形象必须只调用一次生图,输出一张可直接进入运行态的主角色图。
|
||||
|
||||
角色图要求:
|
||||
|
||||
1. 单人主角;
|
||||
2. 全身可见;
|
||||
3. 透明背景;
|
||||
4. 角色站姿或轻微前倾姿态;
|
||||
5. 镜头和透视必须匹配俯视角场景;
|
||||
6. 不要求多视角,不要求多帧动画图集。
|
||||
|
||||
角色图生成后作为作品级锚点资产使用,结果页、封面合成、试玩和发布都复用同一张图。后续如果只修改标题、标签、难度或路径,不应默认重新生角色。只有用户在结果页明确点击“重生成角色”时,才允许再调用一次角色生图。
|
||||
|
||||
### 6.2 地块只生一次图集
|
||||
|
||||
地块必须只调用一次生图,输出一张 3D 视图的 2D 图片图集,再由后端切成运行态可用的地块资产。该图集使用跳一跳专用 `2行*3列` 六格布局,不套用通用“每个物品一行、每行 n 个不同视图”的系列素材模型。
|
||||
|
||||
地块图集要求:
|
||||
|
||||
1. 统一使用等距 / 俯视角;
|
||||
2. 必须表现出顶面、侧面和投影;
|
||||
3. 必须与角色图保持同一光向;
|
||||
4. 必须有清晰的立体层次,但仍然是 2D 图片;
|
||||
5. 六格必须按固定顺序包含以下地块类型:
|
||||
- 起点地块;
|
||||
- 普通地块;
|
||||
- 目标地块;
|
||||
- 终点地块;
|
||||
- 奖励地块;
|
||||
- 视觉强调地块。
|
||||
|
||||
固定格位为:
|
||||
|
||||
| 格位 | tileType | 语义 |
|
||||
| --- | --- | --- |
|
||||
| 第 1 行第 1 列 | `start` | 起点地块 |
|
||||
| 第 1 行第 2 列 | `normal` | 普通地块 |
|
||||
| 第 1 行第 3 列 | `target` | 目标地块 |
|
||||
| 第 2 行第 1 列 | `finish` | 终点地块 |
|
||||
| 第 2 行第 2 列 | `bonus` | 奖励地块 |
|
||||
| 第 2 行第 3 列 | `accent` | 视觉强调地块 |
|
||||
|
||||
图集生成后按地块类型切分并去掉背景,运行态直接消费切好的 PNG,不在前端做复杂拼接。只有用户在结果页明确点击“重生成地块”时,才允许再调用一次地块图集生图。
|
||||
|
||||
### 6.3 不新增第三次生成
|
||||
|
||||
首版不把封面、分享海报、路径预览再拆成第三次图像生成。封面和分享图必须由角色图 + 地块图集在本地或后端轻量合成,不额外增加新的角色生图次数。
|
||||
|
||||
### 6.4 路径元数据
|
||||
|
||||
除图片资产外,系统还必须生成跳跃路径元数据:
|
||||
|
||||
1. 平台序列;
|
||||
2. 平台中心点;
|
||||
3. 平台宽度;
|
||||
4. 平台间距;
|
||||
5. 终点索引;
|
||||
6. 评分和容差参数。
|
||||
|
||||
路径由领域规则自动生成,创作者不直接编辑坐标。路径元数据不依赖 LLM 或图片生成。
|
||||
|
||||
### 6.5 推荐的难度区间
|
||||
|
||||
| 难度 | 平台数量 | 平台间距 | 节奏 |
|
||||
| --- | ---: | --- | --- |
|
||||
| 轻松 | 12 - 14 | 短 | 宽容 |
|
||||
| 标准 | 16 - 18 | 中 | 稳定 |
|
||||
| 进阶 | 20 - 24 | 中长 | 紧凑 |
|
||||
| 挑战 | 26 - 32 | 长 | 高压 |
|
||||
|
||||
平台宽度和容差由系统按难度自动缩放,不要求创作者手工填写。
|
||||
|
||||
## 7. 契约草案
|
||||
|
||||
### 7.1 草稿结构
|
||||
|
||||
`JumpHopDraft` 至少包含:
|
||||
|
||||
1. `templateId = "jump-hop"`;
|
||||
2. `templateName = "跳一跳"`;
|
||||
3. `profileId`;
|
||||
4. `workTitle`;
|
||||
5. `workDescription`;
|
||||
6. `themeTags`;
|
||||
7. `difficulty`;
|
||||
8. `stylePreset`;
|
||||
9. `characterPrompt`;
|
||||
10. `tilePrompt`;
|
||||
11. `characterAsset`;
|
||||
12. `tileAtlasAsset`;
|
||||
13. `tileAssets[]`;
|
||||
14. `path`;
|
||||
15. `coverComposite`;
|
||||
16. `generationStatus`。
|
||||
|
||||
### 7.2 资产结构
|
||||
|
||||
`JumpHopCharacterAsset` 至少包含:
|
||||
|
||||
1. `assetId`;
|
||||
2. `imageSrc`;
|
||||
3. `imageObjectKey`;
|
||||
4. `assetObjectId`;
|
||||
5. `generationProvider`;
|
||||
6. `prompt`;
|
||||
7. `width`;
|
||||
8. `height`。
|
||||
|
||||
`JumpHopTileAsset` 至少包含:
|
||||
|
||||
1. `tileType`;
|
||||
2. `imageSrc`;
|
||||
3. `imageObjectKey`;
|
||||
4. `assetObjectId`;
|
||||
5. `sourceAtlasCell`;
|
||||
6. `visualWidth`;
|
||||
7. `visualHeight`;
|
||||
8. `topSurfaceRadius`;
|
||||
9. `landingRadius`。
|
||||
|
||||
`tileType` 首版限定:
|
||||
|
||||
```text
|
||||
start | normal | target | finish | bonus | accent
|
||||
```
|
||||
|
||||
### 7.3 路径结构
|
||||
|
||||
`JumpHopPath` 至少包含:
|
||||
|
||||
1. `seed`;
|
||||
2. `difficulty`;
|
||||
3. `platforms[]`;
|
||||
4. `finishIndex`;
|
||||
5. `cameraPreset`;
|
||||
6. `scoring`。
|
||||
|
||||
`JumpHopPlatform` 至少包含:
|
||||
|
||||
1. `platformId`;
|
||||
2. `tileType`;
|
||||
3. `x`;
|
||||
4. `y`;
|
||||
5. `width`;
|
||||
6. `height`;
|
||||
7. `landingRadius`;
|
||||
8. `perfectRadius`;
|
||||
9. `scoreValue`。
|
||||
|
||||
### 7.4 运行态快照
|
||||
|
||||
`JumpHopRunSnapshot` 至少包含:
|
||||
|
||||
1. `runId`;
|
||||
2. `profileId`;
|
||||
3. `status = playing | failed | cleared`;
|
||||
4. `currentPlatformIndex`;
|
||||
5. `score`;
|
||||
6. `combo`;
|
||||
7. `lastJump`;
|
||||
8. `startedAtMs`;
|
||||
9. `finishedAtMs`。
|
||||
|
||||
`lastJump` 至少包含:
|
||||
|
||||
1. `chargeMs`;
|
||||
2. `jumpDistance`;
|
||||
3. `targetPlatformIndex`;
|
||||
4. `landedX`;
|
||||
5. `landedY`;
|
||||
6. `result = miss | hit | perfect | finish`。
|
||||
|
||||
## 8. API 草案
|
||||
|
||||
HTTP 路由建议:
|
||||
|
||||
```text
|
||||
POST /api/creation/jump-hop/sessions
|
||||
GET /api/creation/jump-hop/sessions/{sessionId}
|
||||
POST /api/creation/jump-hop/sessions/{sessionId}/actions
|
||||
POST /api/creation/jump-hop/works/{profileId}/publish
|
||||
GET /api/runtime/jump-hop/works/{profileId}
|
||||
POST /api/runtime/jump-hop/runs
|
||||
POST /api/runtime/jump-hop/runs/{runId}/jump
|
||||
POST /api/runtime/jump-hop/runs/{runId}/restart
|
||||
GET /api/runtime/jump-hop/gallery
|
||||
GET /api/runtime/jump-hop/gallery/{publicWorkCode}
|
||||
```
|
||||
|
||||
动作类型建议:
|
||||
|
||||
```text
|
||||
compile-draft
|
||||
regenerate-character
|
||||
regenerate-tiles
|
||||
update-work-meta
|
||||
update-difficulty
|
||||
```
|
||||
|
||||
`compile-draft` 是长耗时动作。前端进入生成页后必须持久化 `generationStatus=generating`,刷新后能从作品架恢复生成页。失败前需要复读 session;如果后端已经完成草稿并写回资产,前端按成功收尾。
|
||||
|
||||
## 9. SpacetimeDB 表和 view
|
||||
|
||||
建议新增表:
|
||||
|
||||
1. `jump_hop_agent_session`;
|
||||
2. `jump_hop_work_profile`;
|
||||
3. `jump_hop_runtime_run`;
|
||||
4. `jump_hop_event`;
|
||||
5. `jump_hop_leaderboard_entry`,首版可暂不对外展示;
|
||||
6. `jump_hop_gallery_view`;
|
||||
7. `jump_hop_gallery_card_view`。
|
||||
|
||||
表结构新增字段必须按 SpacetimeDB 迁移规则放在结构体末尾并设置明确默认值。新增或调整表、reducer、procedure、view 后必须同步 `migration.rs`、表目录、生成 bindings,并执行 `npm run check:spacetime-schema`。
|
||||
|
||||
公开列表主路径应优先订阅 `jump_hop_gallery_card_view` 后在 `api-server` 本地 cache 构造列表响应,不要让每个 HTTP 请求都调用 SpacetimeDB procedure 组装全量列表。
|
||||
|
||||
## 10. 结果页能力
|
||||
|
||||
结果页必须展示:
|
||||
|
||||
1. 作品标题;
|
||||
2. 作品简介;
|
||||
3. 角色形象;
|
||||
4. 地块图集;
|
||||
5. 路径预览;
|
||||
6. 标签;
|
||||
7. 试玩;
|
||||
8. 发布;
|
||||
9. 返回编辑。
|
||||
|
||||
结果页还必须支持:
|
||||
|
||||
1. 单独重生成角色;
|
||||
2. 单独重生成地块图集;
|
||||
3. 单独修改标题和简介;
|
||||
4. 单独调整标签和难度。
|
||||
|
||||
结果页不应强制再走一次封面生图。封面只做合成,不新增图像生成调用。
|
||||
|
||||
## 11. 运行态规则
|
||||
|
||||
运行态采用 2D 表现,但画面视觉上必须保留参考图那种俯视角 / 等距感。
|
||||
|
||||
### 11.1 核心玩法
|
||||
|
||||
1. 玩家长按蓄力;
|
||||
2. 松手后角色按蓄力长度起跳;
|
||||
3. 跳跃距离决定是否落到下一个地块;
|
||||
4. 落在目标区域内判定成功;
|
||||
5. 落在地块外或越界判定失败;
|
||||
6. 到达终点地块判定通关。
|
||||
|
||||
### 11.2 判定规则
|
||||
|
||||
1. 只做一个当前局面的起跳判定;
|
||||
2. 不做复杂连招动作树;
|
||||
3. 不新增生命数、体力、回合数;
|
||||
4. 不新增计时赛作为首版核心规则;
|
||||
5. 不把前端动画结果当成最终真相,通关与失败必须能回写运行态状态。
|
||||
|
||||
### 11.3 角色动画
|
||||
|
||||
角色不需要多帧生图,运行态只通过位移、缩放、轻微旋转和投影变化表达:
|
||||
|
||||
1. 蓄力时轻微压缩;
|
||||
2. 起跳时向上抬升;
|
||||
3. 空中保持可读轮廓;
|
||||
4. 落地时轻微弹性回弹;
|
||||
5. 失败时从地块边缘跌落。
|
||||
|
||||
### 11.4 摄像机与构图
|
||||
|
||||
1. 相机以当前角色和下一地块为中心;
|
||||
2. 至少保证下一个落点一直可见;
|
||||
3. 画面要留出顶部和底部的 UI 安全区;
|
||||
4. 不要把地块做得太满,保留参考图那种疏朗感。
|
||||
|
||||
### 11.5 UI
|
||||
|
||||
运行态 UI 只保留必要元素:
|
||||
|
||||
1. 分数;
|
||||
2. 暂停;
|
||||
3. 重新开始;
|
||||
4. 分享;
|
||||
5. 结算按钮。
|
||||
|
||||
不默认展示大段规则说明。首进如果需要引导,只能用一次轻量提示,不允许常驻一屏的说明文案。
|
||||
|
||||
## 12. 视觉规范
|
||||
|
||||
本模板的视觉目标是“像 3D,但仍是 2D 图片”。
|
||||
|
||||
必须遵守:
|
||||
|
||||
1. 平台有明确厚度;
|
||||
2. 侧面可见分层或材质变化;
|
||||
3. 投影统一且方向一致;
|
||||
4. 背景干净,颜色克制;
|
||||
5. 角色尺寸在小屏上依然可读;
|
||||
6. 地块不能出现过多文字、按钮或装饰信息;
|
||||
7. 不能把运行态做成重 UI 面板。
|
||||
|
||||
建议的背景策略:
|
||||
|
||||
1. 以静态浅色渐变或纯色背景为主;
|
||||
2. 不把背景也做成每次都生成的重资产;
|
||||
3. 让地块和角色成为画面的第一视觉焦点。
|
||||
|
||||
## 13. 发布后体验
|
||||
|
||||
发布后的作品必须支持:
|
||||
|
||||
1. 进入作品架和公开展示;
|
||||
2. 分享;
|
||||
3. 试玩;
|
||||
4. 重新进入结果页编辑。
|
||||
|
||||
发布后的卡片封面应优先由角色图和地块图合成,不要求单独再生成封面图。
|
||||
|
||||
首版不新增排行榜、回放和对局对抗。后续如要扩展排行,可另起版本,不要塞进首版模板范围。
|
||||
|
||||
## 14. 验收
|
||||
|
||||
1. 创作入口能看到 `跳一跳` 模板;
|
||||
2. 创作者可以填写主题、角色描述、风格和难度;
|
||||
3. 提交后只生成一次角色图和一次地块图集;
|
||||
4. 结果页能看到角色图、地块图集和路径预览;
|
||||
5. 结果页可单独重生成角色或地块;
|
||||
6. 试玩进入跳一跳运行态;
|
||||
7. 长按蓄力、松手起跳、落点判定、失败和通关都可用;
|
||||
8. 作品可以保存、发布和分享;
|
||||
9. 前端不直接读取或暴露生图密钥;
|
||||
10. 发布后的封面不依赖第三次额外生图。
|
||||
11. `npm run check:spacetime-schema` 在 schema 变更后通过;
|
||||
12. `npm run check:encoding` 通过。
|
||||
展示字段:
|
||||
|
||||
1. rank;
|
||||
2. displayName;
|
||||
3. successfulJumpCount;
|
||||
4. durationMs;
|
||||
5. updatedAt。
|
||||
|
||||
排行榜 UI 禁止展示 `user_id` / `playerId` 这类内部身份键。后端可以继续用 `playerId` 做作品维度最佳成绩去重和 `viewerBest` 匹配,但 HTTP 响应必须补齐 `displayName`;已登录用户读取账号 `displayName`,匿名游客展示为“游客玩家”,账号失效或无法解析时展示为“失效玩家”。
|
||||
|
||||
草稿试玩可以展示本地结果,但正式排行榜只消费后端 run 记录。匿名 runtime guest 也按 guest subject 作为 playerId 参与当次作品维度排行。
|
||||
|
||||
## 8. 结果页
|
||||
|
||||
结果页展示:
|
||||
|
||||
1. 陶泥儿 logo 透明角色预览;
|
||||
2. 18 个地块资源池预览;
|
||||
3. 首屏 3 块平台预览;
|
||||
4. 试玩;
|
||||
5. 发布;
|
||||
6. 返回编辑;
|
||||
7. 重生成地块。
|
||||
|
||||
结果页不再展示角色图片生成槽位,也不提供独立角色重生成。
|
||||
|
||||
## 9. 契约要点
|
||||
|
||||
公开语义保留:
|
||||
|
||||
1. `themeText`;
|
||||
2. `tileAtlasAsset`;
|
||||
3. `tileAssets[]`;
|
||||
4. `defaultCharacter`;
|
||||
5. `path.platforms[]` 作为服务端路径缓冲;
|
||||
6. `currentPlatformIndex`;
|
||||
7. `successfulJumpCount`;
|
||||
8. `startedAtMs` / `finishedAtMs` / `durationMs`;
|
||||
9. `leaderboard`。
|
||||
|
||||
旧语义处理:
|
||||
|
||||
1. `characterAsset` 仅作为角色描述兼容字段,不再表示生成图片;前端固定使用陶泥儿 logo 透明 PNG;
|
||||
2. `score` 兼容映射为成功跳跃次数;
|
||||
3. `combo` 固定为 0,不作为公开玩法语义;
|
||||
4. `cleared` 状态不再由 v1 产生;
|
||||
5. 旧 finite path 只作为服务端路径缓冲兼容形态。
|
||||
|
||||
## 10. 验收
|
||||
|
||||
1. 创作页只显示主题输入;
|
||||
2. 生成链路只调用一次地板贴图图集 image2,不再调用角色生图;
|
||||
3. 地板贴图图集为 `1024x1536 / 3列*6行 / 每格4列*3行UV网`,后端切出 18 组、共 108 张面贴图 PNG;
|
||||
4. 结果页不依赖旧角色图片槽;
|
||||
5. 运行态为竖屏俯视角,首屏保持 3 个地块可见;
|
||||
6. 长按蓄力值影响落点距离,`dragVectorX/dragVectorY` 影响正式落点方向;
|
||||
7. 未落到下一个地块立即失败;
|
||||
8. 成功跳跃次数累加,失败后计时冻结;
|
||||
9. 排行榜按成功跳跃次数优先排序;
|
||||
10. 作品可保存、发布、分享并从公开入口启动。
|
||||
11. 运行态 Three.js 地板必须优先把 `tileAssets[].faceAssets` 六面贴图按 right/left/top/bottom/front/back 材质顺序贴到标准 `1x1x1` 等比立方体上;旧作品没有 `faceAssets` 时才使用 `tileAssets[].imageSrc` 单贴图 fallback。六面贴图通过换签或 blob 异步解析时,Three.js 平台 mesh 的刷新签名必须包含 top/front/right/back/left/bottom 六个 texture URL,任一面 URL 变化都要触发材质重建,不能只监听旧单图 `imageSrc`。立方体正轴向摆放,不做 Y 轴偏航或 Z 轴歪斜旋转,不得把 x/y/z 缩放成扁盒子;相机保持近距 45° 下压视角,当前脚下地块基准位于屏幕中线略下方,可见三块地板之间的屏幕间距必须偏紧凑;长按蓄力、计时刷新和角色位置更新不得销毁重建透明画布、平台贴图预加载层或 DOM 角色层。
|
||||
12. 同等世界距离的蓄力换算必须使用 `0.004` 系数,松手后必须先看到角色飞行动画,再看到地块窗口前移;成功落地显示必须保留真实落点偏移。
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
# 【前端架构】Bark Battle Work Cache 草稿状态收口计划
|
||||
|
||||
## 背景
|
||||
|
||||
`PlatformEntryFlowShellImpl.tsx` 仍内联维护 Bark Battle 草稿三图完整性、生成状态归一、作品架摘要恢复草稿配置,以及草稿 / 已发布作品进入 runtime 前的 `BarkBattlePublishedConfig` 映射。壳层因此需要同时理解三图资产字段、`partial_failed` 与 `pending_assets` 的差异、`publishedAt` 兜底、作品摘要字段和草稿试玩配置默认值。
|
||||
|
||||
这些规则属于 Bark Battle 作品摘要与草稿缓存的纯模型。若留在平台壳层,后续发布、作品架刷新、公开详情启动或草稿试玩都容易重复一份字段清单。
|
||||
|
||||
## 决策
|
||||
|
||||
扩展 `src/components/platform-entry/barkBattleWorkCache.ts`,作为 Bark Battle Work Cache **Module** 继续承接作品摘要缓存和草稿 runtime 配置规则。新增公开 **Interface**:
|
||||
|
||||
- `hasBarkBattleDraftRequiredImages(draft)`:判断草稿是否已具备玩家形象、对手形象和竞技背景三图。
|
||||
- `resolveBarkBattleDraftGenerationStatus(draft, partialFailed)`:三图齐备返回 `ready`,否则按是否部分失败返回 `partial_failed` 或 `pending_assets`。
|
||||
- `buildBarkBattleDraftConfigFromWorkSummary(work)`:把作品架摘要恢复成可编辑 / 可试玩的 `BarkBattleDraftConfig`。
|
||||
- `buildBarkBattlePublishedConfigFromDraft(draft)`:把草稿结果页试玩所需配置映射为 `BarkBattlePublishedConfig`。
|
||||
- `buildBarkBattlePublishedConfigFromWork(work)`:把作品架 / 公开详情启动正式 runtime 所需配置映射为 `BarkBattlePublishedConfig`。
|
||||
- `buildBarkBattlePublishSnapshot(draft)`:拼装发布接口所需的最终草稿快照。
|
||||
- `mergeBarkBattlePublishedConfigAssets(published, draft)`:发布回包缺少三图字段时沿用结果页草稿图。
|
||||
|
||||
`PlatformEntryFlowShellImpl.tsx` 继续作为 **Adapter**:它只负责 API 请求、React state、URL、运行态 stage 切换和错误提示,不再持有 Bark Battle 三图完整性与 runtime config 字段清单。
|
||||
|
||||
## Interface 约束
|
||||
|
||||
- 草稿三图必须同时具备 `playerCharacterImageSrc`、`opponentCharacterImageSrc` 和 `uiBackgroundImageSrc` 的非空值,才视为 `ready`。
|
||||
- 未齐三图且 `partialFailed=true` 时返回 `partial_failed`,否则返回 `pending_assets`。
|
||||
- 作品摘要恢复草稿时,`draftId` 缺失回退 `workId`,`description` 来自 summary,三图 null 归一为 `undefined`,`configVersion=1` 且 `rulesetVersion='bark-battle-ruleset-v1'`。
|
||||
- 草稿试玩配置的 `workId` 优先使用草稿稳定 `workId`,缺失时回退 `draftId`。
|
||||
- 草稿试玩配置的 `configVersion` 与 `rulesetVersion` 使用草稿值,缺失时回退 `1` 与 `bark-battle-ruleset-v1`。
|
||||
- 已发布作品配置的 `publishedAt` 缺失时回退 `updatedAt`,保持旧 runtime 启动语义。
|
||||
- 发布快照只携带草稿已有的三图字段,不凭空补空字符串。
|
||||
- 发布接口回包缺少三图字段时,结果页草稿图继续作为 runtime 和作品摘要的兜底。
|
||||
|
||||
## Depth / Leverage / Locality
|
||||
|
||||
- **Depth**:壳层传入草稿或作品摘要,即可得到生成状态、草稿配置或 runtime 配置;字段归一、默认值和三图完整性藏入 Module Implementation。
|
||||
- **Leverage**:作品架草稿恢复、结果页试玩、作品架启动、公开详情启动和缓存刷新可复用同一组 Bark Battle 规则。
|
||||
- **Locality**:Bark Battle 资产完整性与配置映射集中到纯测试面,后续变更三图字段或规则集默认值时无需搜索巨型平台壳。
|
||||
|
||||
## 验收
|
||||
|
||||
- `npm run test -- src/components/platform-entry/barkBattleWorkCache.test.ts`
|
||||
- `npx eslint --max-warnings 0 src/components/platform-entry/barkBattleWorkCache.ts src/components/platform-entry/barkBattleWorkCache.test.ts`
|
||||
- `npx eslint src/components/platform-entry/PlatformEntryFlowShellImpl.tsx --quiet`
|
||||
- `npm run typecheck`
|
||||
- `npm run check:encoding`
|
||||
41
docs/technical/【前端架构】CreationUrlStateModel收口计划-2026-06-03.md
Normal file
41
docs/technical/【前端架构】CreationUrlStateModel收口计划-2026-06-03.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# CreationUrlStateModel 收口计划
|
||||
|
||||
## 背景
|
||||
|
||||
`PlatformEntryFlowShellImpl.tsx` 曾直接承载多玩法创作恢复 URL 的拼装规则:`sessionId`、`profileId`、`draftId`、`workId` 的优先级、拼图草稿 runtime query、以及空值归一化散在壳层 Implementation 内。平台壳因此需要理解各玩法快照结构,新增玩法或修复刷新恢复时缺少稳定测试面。
|
||||
|
||||
## 决策
|
||||
|
||||
- 新增 `src/components/platform-entry/platformCreationUrlStateModel.ts` 作为 Creation URL State Module。
|
||||
- 该 Module 的 Interface 收口为各玩法 `build*CreationUrlState`、拼图 `buildPuzzle*RuntimeUrlState`、`normalizeCreationUrlValue`、`hasCreationUrlStateValue`、`hasPuzzleRuntimeUrlStateValue`、`buildPuzzleRuntimeUrlStateKey`、初始创作 URL 恢复判定 `resolveInitialCreationUrlRestoreDecision`、创作直达恢复目标解析 `resolveCreationUrlRestoreTarget`、恢复目标身份匹配谓词,以及跳一跳 / 敲木鱼恢复后的阶段落点判定。
|
||||
- 新增 `src/components/platform-entry/platformPuzzleIdentityModel.ts` 作为拼图稳定身份 Module,统一 `puzzle-session-*`、`puzzle-profile-*`、`puzzle-work-*` 的互推规则。
|
||||
- `PlatformEntryFlowShellImpl.tsx` 保留 React state、路由、登录门禁、网络请求和 URL 写入副作用 Adapter;不再在壳层内定义各玩法 URL 状态构造函数,也不直接内联初始恢复的已处理 / 等待 / 可恢复判定。
|
||||
|
||||
## Interface 约束
|
||||
|
||||
- 创作恢复私有 query 只使用 `sessionId`、`profileId`、`draftId`、`workId`;不得新增说明性 query 字段。
|
||||
- 空字符串、全空白字符串统一视为 `null`,避免刷新恢复时写入无效私有参数。
|
||||
- work-backed 玩法优先使用后端 work summary 的公开 `workId` / `profileId`;仅缺失时才回退 session draft。
|
||||
- 拼图 runtime query 独立使用 `mode`、`runtimeSessionId`、`runtimeProfileId`、`runtimeLevelId`、`publicWorkCode`,不与创作恢复 query 混写。
|
||||
- 拼图 draft runtime 若没有 `sourceSessionId`,只允许从 `puzzle-profile-*` 反推出 `puzzle-session-*`。
|
||||
- 初始创作 URL 恢复只在未处理、当前路径属于创作恢复路径、私有 query 有值、平台配置加载完成且受保护数据可读时执行;非创作路径或无私有 query 时标记已处理,加载中或暂不可读时等待。
|
||||
- 创作直达恢复目标由 `resolveCreationUrlRestoreTarget(pathname, state)` 统一识别;它只返回玩法 kind、归一化后的四个私有 query、生成路径标记和大鱼吃小鱼 session 兜底,不执行网络请求、草稿打开、stage 切换或 URL 写回。
|
||||
- 作品 / 草稿身份匹配只允许非空目标值命中,避免 query 缺失时用 `null` / 空值误匹配到无效草稿。匹配谓词仍只判断身份,不触发列表读取或打开动作。
|
||||
- 跳一跳和敲木鱼的恢复阶段落点由 `resolveJumpHopCreationUrlRestoreStage` 与 `resolveWoodenFishCreationUrlRestoreStage` 决定;生成路径优先进入生成页,否则按是否恢复到 draft / work 落到结果页或工作台。
|
||||
- `/creation/rpg` 当前仍不归入具体恢复目标;若后续要恢复 RPG 直达,需要先补明确恢复规则和测试,不得让壳层重新内联路径判定。
|
||||
|
||||
## Depth / Leverage / Locality
|
||||
|
||||
- **Depth**:调用方只传玩法快照或作品摘要,即可得到规范化 URL state;各玩法字段优先级藏在 Module Implementation 内。
|
||||
- **Leverage**:新增或调整玩法恢复规则、恢复目标或恢复等待条件时,优先补 Module Interface 测试,再接壳层 Adapter。
|
||||
- **Locality**:恢复 query、拼图 runtime query 和拼图稳定身份规则集中在两个小 Module,避免散落在页面壳、作品架和 runtime 打开逻辑中。
|
||||
|
||||
## 验收
|
||||
|
||||
- `npm run test -- src/components/platform-entry/platformCreationUrlStateModel.test.ts src/components/platform-entry/platformPuzzleIdentityModel.test.ts`
|
||||
- `npm run test -- src/services/creationUrlState.test.ts`
|
||||
- `npm run test -- src/components/platform-entry/platformDraftGenerationShelfModel.test.ts`
|
||||
- `npx eslint src/components/platform-entry/platformCreationUrlStateModel.ts src/components/platform-entry/platformCreationUrlStateModel.test.ts src/components/platform-entry/platformPuzzleIdentityModel.ts src/components/platform-entry/platformPuzzleIdentityModel.test.ts --max-warnings 0`
|
||||
- `npx eslint src/components/platform-entry/PlatformEntryFlowShellImpl.tsx src/components/platform-entry/platformDraftGenerationShelfModel.ts --quiet`
|
||||
- `npm run typecheck`
|
||||
- `npm run check:encoding`
|
||||
@@ -0,0 +1,33 @@
|
||||
# 【前端架构】Creation Work Delete Flow 收口计划
|
||||
|
||||
## 背景
|
||||
|
||||
平台入口作品架的删除入口覆盖 RPG、拼图、抓大鹅、方洞挑战、大鱼吃小鱼、视觉小说和宝贝识物。此前 `PlatformEntryFlowShellImpl.tsx` 在每个删除 handler 内重复计算确认框标题、删除说明、草稿 notice key 和拼图派生稳定 ID。壳层既要理解每种玩法的作品身份,又要承接异步删除、刷新列表、错误状态和页面跳转,导致删除确认规则缺少稳定测试面。
|
||||
|
||||
该 **Interface** 过浅:页面只想展示“删除哪个作品、会从哪里移除、删除成功后清哪些生成 notice”,却必须知道 `workId` / `profileId` / `sourceSessionId` / `draftId`、`status` / `publicationStatus` / `publishStatus` 和宝贝识物特殊公开去向。
|
||||
|
||||
## 决策
|
||||
|
||||
新增 `src/components/platform-entry/platformCreationWorkDeleteFlow.ts` 作为 Creation Work Delete Flow **Module**。其唯一公开 **Interface** 是 `resolvePlatformCreationWorkDeleteConfirmationModel(input)`,输入为带 `kind` 的 union,输出:
|
||||
|
||||
- `id`:确认框和删除 busy 使用的稳定作品 ID。
|
||||
- `title`:确认框标题,含拼图、视觉小说和宝贝识物标题兜底。
|
||||
- `detail`:草稿 / 已发布删除说明,宝贝识物已发布使用“寓教于乐板块”文案。
|
||||
- `noticeKeys`:删除成功后应标记已读的草稿生成 notice keys,拼图包含 `buildPuzzleResultWorkId` / `buildPuzzleResultProfileId` 派生 key。
|
||||
|
||||
`PlatformEntryFlowShellImpl.tsx` 仍作为副作用 **Adapter**:负责鉴权保护、确认框 state、调用各玩法删除 API、清错误、刷新作品架 / 公开广场、`markDraftNoticeSeen` 和必要的页面跳转。`run` 不进入纯 **Module**,避免把网络副作用和 React state 写入藏入模型层。
|
||||
|
||||
## 约定
|
||||
|
||||
- 新玩法接入作品架删除时,先补齐后端删除链路、作品架 action 和本 **Module** 的确认模型,再开放删除按钮。
|
||||
- Jump Hop、Wooden Fish 和 Bark Battle 当前仅有作品架 action 预留,平台壳不传删除 handler;不得因本 Module 存在而默认开放删除。
|
||||
- 删除确认文案不得散回平台壳;若公开去向不是公开广场,应在本 **Module** 明确分支。
|
||||
- 草稿 notice key 的身份扩展必须复用 `collectDraftNoticeKeys`,保持 trim、去空和去重语义一致。
|
||||
|
||||
## 验证
|
||||
|
||||
- `npm run test -- src/components/platform-entry/platformCreationWorkDeleteFlow.test.ts`
|
||||
- `npm run test -- src/components/platform-entry/platformDraftGenerationShelfModel.test.ts`
|
||||
- `npx eslint src/components/platform-entry/platformCreationWorkDeleteFlow.ts src/components/platform-entry/platformCreationWorkDeleteFlow.test.ts src/components/platform-entry/PlatformEntryFlowShellImpl.tsx --quiet`
|
||||
- `npm run typecheck`
|
||||
- `npm run check:encoding`
|
||||
@@ -0,0 +1,40 @@
|
||||
# 【前端架构】Draft Generation Shelf Model 收口计划
|
||||
|
||||
## 背景
|
||||
|
||||
`PlatformEntryFlowShellImpl.tsx` 同时承载创作生成状态、草稿 Tab 未读点、pending 作品架占位、失败文案覆盖、作品详情更新回填和跨玩法 notice key。拼图、抓大鹅、方洞、跳一跳、敲木鱼、视觉小说、汪汪声浪、大鱼吃小鱼和宝贝识物各有不同的 `workId` / `profileId` / `sourceSessionId` / `draftId`,这些规则散在平台壳 **Implementation** 内,导致调用方必须理解每种玩法的草稿身份形状。
|
||||
|
||||
该 **Interface** 过浅:页面看似只关心“生成中 / 已完成未读 / 失败”,却要知道多 ID 去重、pending 草稿去重、失败摘要、拼图空标题兜底和持久化 generating 覆盖规则。
|
||||
|
||||
## 决策
|
||||
|
||||
新增 `src/components/platform-entry/platformDraftGenerationShelfModel.ts` 作为 Draft Generation Shelf **Module**。其 **Interface** 收口为:
|
||||
|
||||
- `collectDraftNoticeKeys(kind, ids)` / `getGenerationNoticeShelfKeys(item)`:统一把玩法草稿身份映射为 notice key。
|
||||
- `createPendingDraftShelfState(...)` 与 `buildPending*Works(...)`:统一把本地 pending 生成状态映射成作品架占位,并避免与后端已有草稿重复。
|
||||
- `buildCreationWorkShelfRuntimeState({ item, notices, pendingShelfItems })`:统一输出 `CreationWorkShelfRuntimeState`,处理失败覆盖、拼图空标题 `拼图草稿` 兜底、summary 占位覆盖、生成中遮罩和 ready 未读点。
|
||||
- `collectVisibleDraftNoticeKeys(...)` / `hasUnreadDraftGenerationUpdates(...)`:统一草稿 Tab 顶部未读点规则。
|
||||
- `mergePuzzleWorkSummary(current, updated)` 与 `mergeBigFishWorkSummary(current, updated)`:统一作品详情更新后回填作品架和当前详情的身份匹配规则。
|
||||
- `resolvePuzzleDraftOpenIntent(...)`、`resolveMatch3DDraftOpenIntent(...)`、`resolveSquareHoleDraftOpenIntent(...)`、`resolveBigFishDraftOpenIntent(...)`、`resolveVisualNovelDraftOpenIntent(...)`、`resolveJumpHopDraftOpenIntent(...)` 与 `resolveWoodenFishDraftOpenIntent(...)`:统一拼图、抓大鹅、方洞挑战、大鱼吃小鱼、视觉小说、跳一跳和敲木鱼草稿打开时的已发布详情、缺 session、ready 未读试玩、失败 / active / background 生成页、当前结果页、持久化 generating 恢复、失败 fallback stage 和普通草稿恢复优先级。
|
||||
- `buildPuzzleResultWorkId(...)` / `buildPuzzleResultProfileId(...)`、`isPersistedDraftGenerating(...)` / `isPersistedDraftFailed(...)`:把拼图稳定 ID 与持久化状态判断收在同一 **Seam**。
|
||||
|
||||
`PlatformEntryFlowShellImpl.tsx` 仍作为 React state 与副作用 **Adapter**:负责写入 `draftGenerationNotices` / `pendingDraftShelfItems`、读取生成 session、启动 ready 草稿试玩、刷新后端列表、打开结果页和弹窗;它不再内联 pending shelf row shape、notice key 汇总、作品架 runtime state 和上述玩法草稿打开优先级。
|
||||
|
||||
## 约定
|
||||
|
||||
- 新玩法若需进入草稿生成通知,必须在此 **Module** 补 notice key、pending 占位和 visible key 映射,避免在平台壳里新增散落 switch。
|
||||
- pending 作品只用于本地生成任务尚未被后端作品架返回时的临时展示;一旦后端已有同一 `sourceSessionId` / `profileId` / `workId`,pending 占位必须让位。
|
||||
- 拼图作品详情更新只以 `profileId` 匹配回填;大鱼吃小鱼作品详情更新只以 `sourceSessionId` 匹配回填。
|
||||
- 失败 notice 优先级高于持久化 generating,且可通过 pending metadata 提供更具体 summary;否则回退玩法默认失败摘要。
|
||||
- 已有封面的拼图草稿即使局部关卡仍在后台生成,也不得被整卡遮罩为不可打开的生成中状态。
|
||||
- 草稿打开 intent 只返回纯计划、notice keys 与必要稳定 ID,不创建失败生成态、不请求详情、不写 stage;这些仍由壳层 Adapter 执行。
|
||||
- 本 **Module** 不做网络请求、路由切换、弹窗副作用或 React state 写入,只保留纯 **Implementation**,以提高 **Depth**、**Leverage** 与 **Locality**。
|
||||
|
||||
## 验证
|
||||
|
||||
- `npm run test -- src/components/platform-entry/platformDraftGenerationShelfModel.test.ts`
|
||||
- `npm run test -- src/components/custom-world-home/creationWorkShelf.test.ts -t "generation state|failure notice|failed puzzle"`
|
||||
- `npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "persisted generating puzzle draft|persisted generating match3d draft|completed baby object match draft"`
|
||||
- 针对新 **Module** 与测试执行 ESLint;`PlatformEntryFlowShellImpl.tsx` 保留既有 hook dependency warnings,不在本切片扩大处理。
|
||||
- `npm run typecheck`
|
||||
- `npm run check:encoding`
|
||||
34
docs/technical/【前端架构】Match3DRuntimeProfile收口计划-2026-06-03.md
Normal file
34
docs/technical/【前端架构】Match3DRuntimeProfile收口计划-2026-06-03.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# 【前端架构】Match3D Runtime Profile 收口计划
|
||||
|
||||
## 背景
|
||||
|
||||
`PlatformEntryFlowShellImpl.tsx` 同时编排抓大鹅创作、作品详情、推荐 runtime 和正式 runtime。运行态启动前的 profile 规范化、公开详情转 work、生成背景资产提升、run / profile / public detail 优先级和 runtime 素材选择原本都在平台壳 **Implementation** 内,导致平台壳必须理解抓大鹅生成素材的内部结构。
|
||||
|
||||
## 决策
|
||||
|
||||
新增 `src/components/platform-entry/platformMatch3DRuntimeProfile.ts`,作为抓大鹅 runtime profile **Module**。该 **Module** 的 **Interface** 收口为:
|
||||
|
||||
- `mapPublicWorkDetailToMatch3DWork(entry)`:把公开作品详情映射为可启动 runtime 的 Match3D work,并补齐生成背景资产。
|
||||
- `buildMatch3DProfileFromSession(session)`:从创作 session draft 生成 runtime profile。
|
||||
- `normalizeMatch3DWorkForRuntimeUi(profile)` / `mapMatch3DWorksForRuntimeUi(profiles)`:统一作品列表进入 UI / runtime 前的素材规范化。
|
||||
- `promoteMatch3DGeneratedBackgroundAsset(profile)`:从 `generatedBackgroundAsset` 或 `generatedItemAssets[].backgroundAsset` 提升背景图、对象 key 与 prompt。
|
||||
- `hasMatch3DRuntimeAsset(profile.generatedItemAssets)` / `hasMatch3DRuntimeBackgroundAsset(profile)`:统一判断 runtime 是否具备物品与背景素材。
|
||||
- `resolveActiveMatch3DRuntimeProfile(run, runtimeProfile, profile)`:按 run 的 `profileId` 选择当前 profile,避免切屏时误用旧草稿。
|
||||
- `resolveMatch3DRuntimeGeneratedItemAssets(...)`、`resolveMatch3DRuntimeGeneratedBackgroundAsset(...)`、`resolveMatch3DRuntimeBackgroundImageSrc(...)`:统一 run / profile / public detail 的素材优先级。
|
||||
|
||||
`PlatformEntryFlowShellImpl.tsx` 只保留启动 run、预加载、路由、错误和 state 编排;抓大鹅素材规则集中到该 **Module**,提升 **Locality** 与测试 **Leverage**。
|
||||
|
||||
## 约定
|
||||
|
||||
- 公开详情补 runtime 素材时,只有 `profileId` 与 run 匹配才优先使用公开详情;错配时不得污染当前 run。
|
||||
- 当前启动时拿到的 `runtimeProfile` 优先于旧草稿 profile;若 run 指向旧草稿 profile,才使用草稿 profile。
|
||||
- 背景资产提升不得覆盖已有显式 `backgroundImageSrc` / `backgroundImageObjectKey` / `generatedBackgroundAsset`,只补缺。
|
||||
- 本 **Module** 只放纯 profile / asset 规则,不引入启动 run、预加载、URL、状态机或 UI 副作用。
|
||||
|
||||
## 验证
|
||||
|
||||
- `npm run test -- src/components/platform-entry/platformMatch3DRuntimeProfile.test.ts`
|
||||
- `npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "match3d|抓大鹅"`
|
||||
- `npm run typecheck`
|
||||
- `npm run check:encoding`
|
||||
- 针对新 Module 与测试执行 ESLint;`PlatformEntryFlowShellImpl.tsx` 保留既有 hook dependency warnings,不在本切片扩大处理。
|
||||
@@ -0,0 +1,29 @@
|
||||
# 【前端架构】Platform Creation Launch Model 收口计划
|
||||
|
||||
## 背景
|
||||
|
||||
`PlatformEntryFlowShellImpl.tsx` 的创作入口点击回调曾直接以内联 `if` 链判断 `airp` 占位、隐藏的 `baby-object-match`、RPG 与各小游戏工作台启动目标。壳层因此同时理解入口 ID、是否需要执行启动前准备、隐藏模板错误文案和具体工作台分流。
|
||||
|
||||
这类规则属于创作入口启动意图。壳层应只执行准备、错误提示和受保护动作,不应持有入口 ID 到工作台目标的长链判定。
|
||||
|
||||
## 决策
|
||||
|
||||
新增 `src/components/platform-entry/platformCreationLaunchModel.ts` 作为 Platform Creation Launch **Module**。其公开 **Interface** 为:
|
||||
|
||||
- `resolvePlatformCreationLaunchIntent({ type, isBabyObjectMatchVisible })`:输入后端入口配置下发的模板 ID 与幼教入口可见性,输出 `noop`、`blocked` 或 `launch` 意图。
|
||||
|
||||
`PlatformEntryFlowShellImpl.tsx` 仍作为副作用 **Adapter**:根据 intent 决定是否调用 `prepareCreationLaunch()`,对 blocked intent 写入 `sessionController.setCreationTypeError(...)`,对 launch intent 进入 `runProtectedAction(...)` 并调用具体工作台打开函数。
|
||||
|
||||
## 约定
|
||||
|
||||
- `airp` 是占位入口,必须在 `prepareCreationLaunch()` 之前返回 `noop`,避免触发新游戏初始化、返回目标复位或错误清理。
|
||||
- 隐藏的 `baby-object-match` 必须在 `prepareCreationLaunch()` 之后返回 blocked intent,错误文案仍使用 `EDUTAINMENT_HIDDEN_MESSAGE`。
|
||||
- 未知入口 ID 保持旧语义:先允许壳层执行启动前准备,再作为 `noop` 结束,避免改变未来后端配置异常时的准备流程。
|
||||
- 新增可启动模板时,先在本 **Module** 的 launch target union、目标集合和测试中列明,再在壳层 Adapter 中补具体启动函数。
|
||||
|
||||
## 验收
|
||||
|
||||
- `npm run test -- src/components/platform-entry/platformCreationLaunchModel.test.ts`
|
||||
- `npx eslint src/components/platform-entry/platformCreationLaunchModel.ts src/components/platform-entry/platformCreationLaunchModel.test.ts src/components/platform-entry/PlatformEntryFlowShellImpl.tsx --quiet`
|
||||
- `npm run typecheck`
|
||||
- `npm run check:encoding`
|
||||
@@ -0,0 +1,46 @@
|
||||
# PlatformDialogStateModel 收口计划
|
||||
|
||||
## 背景
|
||||
|
||||
`PlatformEntryFlowShellImpl.tsx` 曾直接承载平台级错误 / 完成弹窗的纯状态规则:错误文案 trim、来源 label 与 id 拼接、后台生成仍在处理的识别、错误候选优先级、dismiss key 与生成完成文案都散在壳层 Implementation 内。壳层因此既要管理 React state 与副作用清理,又要记住弹窗判定细则;新增玩法错误或调整弹窗展示时缺少稳定测试面。
|
||||
|
||||
## 决策
|
||||
|
||||
- 新增 `src/components/platform-entry/platformDialogStateModel.ts` 作为 Platform Dialog State Module。
|
||||
- Module Interface 收口:
|
||||
- `normalizePlatformDialogMessage`
|
||||
- `formatPlatformDialogSource`
|
||||
- `isBackgroundGenerationStillRunningMessage`
|
||||
- `resolvePlatformErrorDialog`
|
||||
- `buildPlatformErrorDialogDismissKey`
|
||||
- `buildPlatformTaskCompletionDialogDismissKey`
|
||||
- `resolveActivePlatformDialog`
|
||||
- `PLATFORM_TASK_COMPLETION_MESSAGE`
|
||||
- `PlatformErrorDialogState`、`PlatformTaskFailureDialogState` 与 `PlatformTaskCompletionDialogState`
|
||||
- `PlatformEntryFlowShellImpl.tsx` 继续作为 Adapter:汇总各玩法候选、持有 React state、关闭弹窗时清理对应 setter。副作用清理不下沉到 Module,避免把大量壳层 setter 变成浅 Interface。
|
||||
|
||||
## Interface 约束
|
||||
|
||||
- 错误与完成弹窗文案先 trim;空字符串或全空白字符串统一视为 `null`。
|
||||
- 来源格式固定为 `label + 空格 + trimmed id`;缺 id 时只返回 label。
|
||||
- 平台错误候选按数组顺序取第一个有效文案;候选本身只描述 `key/source/message`。
|
||||
- 错误 dismiss key 固定为 `key:source:message`;完成 dismiss key 固定为 `key:source:message:completedAtMs`,缺完成时间时补 `0`。
|
||||
- `resolveActivePlatformDialog` 只根据当前弹窗 dismiss key 与已记录 dismiss key 决定是否隐藏,不修改底层错误或完成状态。
|
||||
- 任务完成弹窗文案统一使用 `PLATFORM_TASK_COMPLETION_MESSAGE`,不得在壳层重复写同一中文 literal。
|
||||
- `closePlatformErrorDialog` 保持在壳层 Adapter;它负责按错误来源清理 `creationEntryConfigError`、玩法 error、作品详情 error 等副作用状态,不属于纯状态 Module。
|
||||
|
||||
## Depth / Leverage / Locality
|
||||
|
||||
- **Depth**:壳层传入候选和 dismiss 记录,即可得到当前平台弹窗状态;文案归一、来源格式和 dismiss 规则藏在 Module Implementation 内。
|
||||
- **Leverage**:新增玩法错误来源时只需补候选;调整弹窗纯规则时优先改 Module 与单测。
|
||||
- **Locality**:平台错误弹窗、任务完成弹窗和后台生成 still-running 识别集中在一个小 Module,避免继续散落在大型平台壳 Implementation 内。
|
||||
|
||||
## 验收
|
||||
|
||||
- `npm run test -- src/components/platform-entry/platformDialogStateModel.test.ts`
|
||||
- `npm run test -- src/components/platform-entry/PlatformErrorDialog.test.tsx`
|
||||
- `npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "background match3d draft failure notifies and reopens failed retry page|completed match3d draft notice first opens trial then reopens result|puzzle compile timeout shows failure dialog when reread session is still generating"`
|
||||
- `npx eslint src/components/platform-entry/platformDialogStateModel.ts src/components/platform-entry/platformDialogStateModel.test.ts --max-warnings 0`
|
||||
- `npx eslint src/components/platform-entry/PlatformEntryFlowShellImpl.tsx --quiet`
|
||||
- `npm run typecheck`
|
||||
- `npm run check:encoding`
|
||||
@@ -0,0 +1,37 @@
|
||||
# 【前端架构】Platform Generation Progress Tick Model 收口计划
|
||||
|
||||
## 背景
|
||||
|
||||
`PlatformEntryFlowShellImpl.tsx` 的生成页进度 tick effect 曾以内联三元链按 `selectionStage` 选择拼图、抓大鹅、大鱼吃小鱼、方洞挑战、跳一跳、敲木鱼和宝贝识物的生成状态,并额外手写视觉小说的 `startedAtMs` / `phase` 判定。壳层因此既要维护 `setInterval` 副作用,又要记住每个生成页 stage 对应哪份进度状态。
|
||||
|
||||
生成进度是否需要 tick 是纯判定;`Date.now()`、`window.setInterval` 和进度时间 state 写入仍属于 React 壳层副作用。
|
||||
|
||||
## 决策
|
||||
|
||||
新增 `src/components/platform-entry/platformGenerationProgressTickModel.ts` 作为 Platform Generation Progress Tick **Module**。其公开 **Interface** 为:
|
||||
|
||||
- `resolvePlatformGenerationProgressTickDecision(input)`:输入当前 `selectionStage`、各小游戏 `MiniGameDraftGenerationState` 和视觉小说轻量生成状态,输出 `{ activeKind, shouldTick }`。
|
||||
- `PlatformGenerationProgressTickKind`:枚举可 tick 的生成类型,包含已有小游戏生成 kind 与 `visual-novel`。
|
||||
|
||||
`PlatformEntryFlowShellImpl.tsx` 仍作为 **Adapter**:它把当前 state 组装给 Module,若 `shouldTick=false` 则不启动 interval;若为真,仍按旧逻辑立即写一次 `Date.now()`,再每 `500ms` 更新并在 effect cleanup 中清理 timer。
|
||||
|
||||
## Interface 约束
|
||||
|
||||
- 小游戏生成 stage 只读取匹配 kind 的 `MiniGameDraftGenerationState`;stage 与 state 不匹配时不 tick。
|
||||
- 小游戏状态缺失、`phase='ready'` 或 `phase='failed'` 时不 tick;其它 phase 按进行中处理。
|
||||
- `visual-novel-generating` 不强行转成 `MiniGameDraftGenerationState`,只在 `startedAtMs != null` 且 phase 非 `ready` / `failed` 时 tick。
|
||||
- 非生成 stage 即使传入可运行 state 也不 tick。
|
||||
- 本 Module 不计算进度、不重建 view state、不处理拼图 / 抓大鹅 background task 覆盖;这些仍按既有生成页和作品架模型处理。
|
||||
|
||||
## Depth / Leverage / Locality
|
||||
|
||||
- **Depth**:壳层只消费 `shouldTick`,stage 到 state 的映射和终态判定藏入 Module Implementation。
|
||||
- **Leverage**:新增生成页玩法时,先扩展 stage-to-kind 映射和单测,再让壳层 Adapter 传入对应 state。
|
||||
- **Locality**:生成进度 tick 规则集中到一个纯测试面,interval 副作用继续局部留在 React effect,避免把 timer 控制做成浅 Interface。
|
||||
|
||||
## 验收
|
||||
|
||||
- `npm run test -- src/components/platform-entry/platformGenerationProgressTickModel.test.ts`
|
||||
- `npx eslint src/components/platform-entry/platformGenerationProgressTickModel.ts src/components/platform-entry/platformGenerationProgressTickModel.test.ts src/components/platform-entry/PlatformEntryFlowShellImpl.tsx --quiet`
|
||||
- `npm run typecheck`
|
||||
- `npm run check:encoding`
|
||||
@@ -0,0 +1,47 @@
|
||||
# 【前端架构】Platform Mini Game Draft Generation State Model 收口计划
|
||||
|
||||
## 背景
|
||||
|
||||
`PlatformEntryFlowShellImpl.tsx` 曾内联维护小游戏生成状态的恢复、失败/完成收尾、展示 rebase、拼图后端进度合并、抓大鹅生成资产旁路进度合并和生成中 / ready 判定。壳层因此既要处理 API 回包、React state、后台任务、URL 和 stage,又要记住 `MiniGameDraftGenerationState` 的生命周期细节。
|
||||
|
||||
这些状态变换不读取 DOM,不请求网络,也不写 React state;它们属于平台层小游戏草稿生成状态 **Module**。壳层只应决定何时调用、把返回值写入对应 state。
|
||||
|
||||
## 决策
|
||||
|
||||
新增 `src/components/platform-entry/platformMiniGameDraftGenerationStateModel.ts` 作为 Platform Mini Game Draft Generation State **Module**。其公开 **Interface** 为:
|
||||
|
||||
- `createMiniGameDraftGenerationStateForRestoredDraft(kind, metadata?, startedAtMs?)`:为恢复的草稿重建生成态,并保留后端开始时间作为进度事实源。
|
||||
- `createFailedMiniGameDraftGenerationStateForRestoredDraft(kind, updatedAt, error, metadata?)`:恢复失败草稿时按后端 `updatedAt` 建立失败态。
|
||||
- `rebaseMiniGameDraftGenerationStateForDisplay(state)` 与 `rebaseMiniGameDraftBackgroundCompileTaskForDisplay(task)`:清理展示用 `finishedAtMs`,避免返回生成页后沿用结束态计时。
|
||||
- `createPuzzleDraftGenerationStateFromPayload(payload, session?)`、`resolvePuzzlePhaseFromSessionProgress(state, session)`、`mergePuzzleSessionProgressIntoGenerationState(state, session)`:集中处理拼图生成的 aiRedraw、后端进度百分比和 phase 推进。
|
||||
- `mergeMatch3DGeneratedAssetsIntoGenerationState(state, assets)`:抓大鹅轮询到作品素材后,按可用图片数量推进生成页资产计数,并把首个素材错误传播到生成态。
|
||||
- `resolveFinishedMiniGameDraftGenerationState(state, phase, options?)`:统一完成 / 失败收尾的 `finishedAtMs`、错误与资产计数合并。
|
||||
- `isMiniGameDraftReady(state)` 与 `isMiniGameDraftGenerating(state)`:统一生成态轻量判定。
|
||||
|
||||
`PlatformEntryFlowShellImpl.tsx` 仍作为 **Adapter**:它继续负责 API、background task、React state 写入、作品架刷新、URL 与 stage 切换。
|
||||
|
||||
## Interface 约束
|
||||
|
||||
- 恢复草稿状态必须允许调用方传入 `startedAtMs`;未传时使用当前时间,与旧逻辑一致。
|
||||
- 恢复失败状态必须通过 `resolveMiniGameDraftGenerationStartedAtMs(updatedAt)` 解析后端时间,并保留传入 metadata。
|
||||
- `resolveFinishedMiniGameDraftGenerationState` 只覆盖显式传入的 `error`、`completedAssetCount`、`totalAssetCount`;未传时沿用原 state。
|
||||
- 拼图 session 只有在 `draft` 存在且不是 `formDraft` 时才视为后端编译生成中 session,才写入 `puzzleProgressPercent` 并推进 phase。
|
||||
- 拼图进度阈值保持旧值:`>=96` 到 `puzzle-select-image`,`>=94` 到 `puzzle-ui-assets`,`>=88` 时按 `puzzleAiRedraw=false` 进入 `puzzle-level-scene`,否则进入 `puzzle-cover-image`。
|
||||
- phase 变化时 `puzzleActiveStepStartedAtMs` 使用 session `updatedAt` 解析值;phase 不变时保留旧值。
|
||||
- 抓大鹅资产旁路进度不得覆盖 `ready` 或 `failed` 终态;非终态下只统计有 `imageViews[].imageObjectKey` / `imageViews[].imageSrc`、顶层 `imageObjectKey` 或顶层 `imageSrc` 的素材。
|
||||
- 抓大鹅资产旁路进度的 `totalAssetCount` 至少为 `5`,保留当前五物品首批生成节奏;已有素材数量超过 `5` 时按真实素材数量展示。
|
||||
- 抓大鹅已有可用素材时 phase 推进到 `match3d-generate-views`;无可用素材时保留原 phase;首个素材错误写入 `error`,无素材错误时保留原错误。
|
||||
- 展示 rebase 只清理 `finishedAtMs`,不得修改 phase、error、资产计数或 metadata。
|
||||
|
||||
## Depth / Leverage / Locality
|
||||
|
||||
- **Depth**:壳层以状态变换函数表达意图;生成态字段、拼图阈值、抓大鹅素材计数、时间解析与计数合并藏入 Module Implementation。
|
||||
- **Leverage**:后续新增小游戏生成恢复、调整拼图后端进度阈值或改变抓大鹅素材批次展示时,先改 Module 与单测,再让壳层 Adapter 保持调用点不变。
|
||||
- **Locality**:小游戏生成状态规则集中到一个纯测试面,避免在大型壳层的 API callback、background task 和恢复流程中重复推理 `MiniGameDraftGenerationState`。
|
||||
|
||||
## 验收
|
||||
|
||||
- `npm run test -- src/components/platform-entry/platformMiniGameDraftGenerationStateModel.test.ts`
|
||||
- `npx eslint src/components/platform-entry/platformMiniGameDraftGenerationStateModel.ts src/components/platform-entry/platformMiniGameDraftGenerationStateModel.test.ts src/components/platform-entry/PlatformEntryFlowShellImpl.tsx --quiet`
|
||||
- `npm run typecheck`
|
||||
- `npm run check:encoding`
|
||||
@@ -0,0 +1,52 @@
|
||||
# 【前端架构】Platform Mini Game Draft Payload Model 收口计划
|
||||
|
||||
## 背景
|
||||
|
||||
`PlatformEntryFlowShellImpl.tsx` 曾内联维护拼图和抓大鹅草稿恢复所需的表单 payload、拼图编译 action payload、拼图作品更新 payload、跳一跳 / 敲木鱼生成 action payload、作品摘要回填 payload 和 pending 草稿 metadata。壳层因此需要理解拼图描述字段优先级、formDraft 回退、结果页 draft 到作品更新字段的映射、Match3D config / draft / anchorPack 优先级、跳一跳 / 敲木鱼 payload 与 session draft 优先级,以及 pending 作品架标题摘要如何从 payload 派生。后续还残留拼图 form-only 草稿判定,影响 action 分流、草稿恢复阶段和结果页渲染。
|
||||
|
||||
这些逻辑都是 DTO 变换;不读取 React state,不请求网络,也不写 URL。壳层只应决定何时恢复、何时提交 action、何时写入生成状态。
|
||||
|
||||
## 决策
|
||||
|
||||
新增 `src/components/platform-entry/platformMiniGameDraftPayloadModel.ts` 作为 Platform Mini Game Draft Payload **Module**。其公开 **Interface** 为:
|
||||
|
||||
- `buildPuzzleFormPayloadFromWork(item)`:从拼图作品摘要恢复创作表单 payload。
|
||||
- `buildPuzzleFormPayloadFromSession(session)`:从拼图 session 恢复创作表单 payload。
|
||||
- `buildPuzzleFormPayloadFromAction(payload)`:从拼图 action 还原表单 payload,仅接受 `compile_puzzle_draft` 与 `save_puzzle_form_draft`。
|
||||
- `buildPuzzleCompileActionFromFormPayload(payload)`:从表单 payload 构造拼图编译 action。
|
||||
- `buildPuzzleWorkUpdatePayloadFromDraft(draft)`:从拼图结果 draft 构造 `updatePuzzleWork(...)` 所需 payload。
|
||||
- `buildJumpHopDraftActionPayload(actionType, { payload, draft })`:从跳一跳表单 payload / session draft 构造生成或重生成 action。
|
||||
- `buildWoodenFishDraftActionPayload(actionType, { payload, draft })`:从敲木鱼表单 payload / session draft 构造生成或重生成 action。
|
||||
- `buildPendingPuzzleDraftMetadata(payload)`:从拼图 payload 派生 pending 作品架 metadata。
|
||||
- `isPuzzleFormOnlyDraft(session)` 与 `isEmptyPuzzleFormOnlyDraft(session)`:判断拼图 session 是否仍只是表单草稿,以及表单草稿是否没有任何可提交内容。
|
||||
- `buildMatch3DFormPayloadFromSession(session)` 与 `buildMatch3DFormPayloadFromWork(item)`:从抓大鹅 session / work 恢复表单 payload。
|
||||
- `buildPendingMatch3DDraftMetadata(payload)`:从抓大鹅 payload 派生 pending metadata。
|
||||
|
||||
`PlatformEntryFlowShellImpl.tsx` 仍作为 **Adapter**:它继续负责 API、Action 执行、background task、生成状态、错误提示、作品架和阶段切换。
|
||||
|
||||
## Interface 约束
|
||||
|
||||
- 拼图 work payload 的 `pictureDescription` 优先级固定为 `workDescription > summary > first level pictureDescription > levelName > workTitle > ''`。
|
||||
- 拼图 session payload 的 `pictureDescription` 优先级固定为 `formDraft.pictureDescription > first level pictureDescription > anchorPack.visualSubject.value > seedText > ''`。
|
||||
- 拼图编译 action 的 `promptText` 来自 `pictureDescription || seedText`;`workDescription` 缺省回退到图片描述;`candidateCount` 固定为 `1`。
|
||||
- 拼图 action 还原只接受 `compile_puzzle_draft` 与 `save_puzzle_form_draft`;其它 action 返回 `null`。
|
||||
- 拼图作品更新 payload 必须直接映射 `workTitle`、`workDescription`、`levelName`、`summary`、`themeTags`、`coverImageSrc`、`coverAssetId`,`levels` 缺失时回退空数组。
|
||||
- 跳一跳和敲木鱼生成 action payload 的字段优先级固定为表单 payload 优先,其次 session draft;重生成 action 只传 session draft 字段。
|
||||
- 拼图 form-only 草稿只在 `session.stage === 'collecting_anchors'` 且存在 `draft.formDraft` 时成立。
|
||||
- 空 form-only 草稿必须同时缺少 `seedText`、`formDraft.workTitle`、`formDraft.workDescription` 与 `formDraft.pictureDescription`。
|
||||
- 抓大鹅 session payload 优先读取 `config`,其次 `draft`,最后 `anchorPack`;`anchorPack.clearCount` 与 `anchorPack.difficulty` 只接受有限数字字符串或数字。
|
||||
- 抓大鹅 work payload 的 `themeText` 优先 `themeText`,缺失回退 `gameName`。
|
||||
- pending metadata 只收非空 trim 后标题和摘要;抓大鹅 metadata 用 `themeText || seedText` 同时作为 title 和 summary。
|
||||
|
||||
## Depth / Leverage / Locality
|
||||
|
||||
- **Depth**:壳层以一组表意函数取得 payload / metadata;字段优先级、结果页 draft 更新字段、跳一跳 / 敲木鱼 action 字段、默认空资产和数字解析藏入 Module Implementation。
|
||||
- **Leverage**:后续调整拼图或抓大鹅草稿恢复表单、拼图作品更新字段、跳一跳 / 敲木鱼生成 action 字段时,先改 Module 与单测,再保持壳层 API / state 副作用不变。
|
||||
- **Locality**:表单恢复、作品更新与 action payload 规则集中到一个纯测试面,避免在大型平台壳的生成、重试和恢复流程里重复散落 DTO 拼装。
|
||||
|
||||
## 验收
|
||||
|
||||
- `npm run test -- src/components/platform-entry/platformMiniGameDraftPayloadModel.test.ts`
|
||||
- `npx eslint src/components/platform-entry/platformMiniGameDraftPayloadModel.ts src/components/platform-entry/platformMiniGameDraftPayloadModel.test.ts src/components/platform-entry/PlatformEntryFlowShellImpl.tsx --quiet`
|
||||
- `npm run typecheck`
|
||||
- `npm run check:encoding`
|
||||
@@ -0,0 +1,47 @@
|
||||
# 【前端架构】Platform Mini Game Session Mapping Model 收口计划
|
||||
|
||||
## 背景
|
||||
|
||||
`PlatformEntryFlowShellImpl.tsx` 顶部曾保留拼图 runtime 恢复、方洞挑战 session draft 转 profile、视觉小说 work detail 转 Agent session、跳一跳 pending session、敲木鱼 work detail 恢复、敲木鱼生成中作品摘要和敲木鱼 pending session 多段纯 DTO 映射。它们没有 React state、网络请求、路由、弹窗或计时副作用,却住在大型平台壳内;新增或修正生成中草稿恢复时,需要在壳层里理解 sessionId 优先级、拼图稳定 ID、方洞 profile 默认值、视觉小说 work/session fallback、pending draft 默认值和木鱼 fallback 规则。
|
||||
|
||||
这些规则属于平台壳 session / work 恢复映射,应成为可测试的 **Module**。壳层只负责调用网络、写 React state、写 URL 和切换 stage。
|
||||
|
||||
## 决策
|
||||
|
||||
新增 `src/components/platform-entry/platformMiniGameSessionMappingModel.ts` 作为 Platform Mini Game Session Mapping **Module**。其公开 **Interface** 为:
|
||||
|
||||
- `buildPuzzleRuntimeWorkFromSession(session, owner)`:从拼图 Agent session 构造可进入 runtime 的 draft `PuzzleWorkSummary`,缺草稿、缺 profile 或缺封面时返回 `null`。
|
||||
- `buildSquareHoleProfileFromSession(session)`:从方洞挑战 Agent session draft 构造草稿 `SquareHoleWorkProfile`,缺 session、缺 draft 或缺 profileId 时返回 `null`。
|
||||
- `buildVisualNovelSessionFromWorkDetail(work)`:从视觉小说 work detail 恢复 `VisualNovelAgentSessionSnapshot`,供草稿作品架回到结果页继续编辑。
|
||||
- `buildJumpHopPendingSession(item)`:从跳一跳作品架 summary 构造生成中 pending session。
|
||||
- `buildWoodenFishSessionFromWorkDetail(work, fallbackItem?)`:从敲木鱼 work detail 恢复 session,并按 summary / fallback / profileId 决定 sessionId。
|
||||
- `buildWoodenFishGeneratingWorkSummary(session, payload?)`:从敲木鱼生成 session 和可选表单 payload 构造作品架生成中摘要。
|
||||
- `buildWoodenFishPendingSession(item)`:从敲木鱼作品架 summary 构造生成中 pending session。
|
||||
|
||||
`PlatformEntryFlowShellImpl.tsx` 仍作为 **Adapter**:调用这些映射后继续负责 `set*Session`、`set*Work`、`set*Run`、`createMiniGameDraftGenerationState(...)`、`writeCreationUrlState(...)`、`enterCreateTab()` 和 `setSelectionStage(...)`。
|
||||
|
||||
## Interface 约束
|
||||
|
||||
- 拼图 runtime work 必须保留 `draft.coverImageSrc` 非空门槛,避免启动缺封面的草稿运行态。
|
||||
- 拼图 profileId 优先 `publishedProfileId`,否则用 `buildPuzzleResultProfileId(sessionId)`;workId 使用 `buildPuzzleResultWorkId(sessionId)`,缺失时回退 profileId。
|
||||
- 拼图 owner 缺省为 `current-user` / `玩家`;`publishReady` 来自 `session.resultPreview?.publishReady`。
|
||||
- 方洞 profile 的 `workId` 与 `profileId` 都来自 draft `profileId`;owner 固定为 `current-user`,`sourceSessionId` 来自 sessionId。
|
||||
- 方洞 profile 的 `updatedAt` 优先 session `updatedAt`,缺失时使用当前时间;`publicationStatus='draft'`、`playCount=0`、`publishedAt=null`,`publishReady` 来自 draft。
|
||||
- 视觉小说恢复 session 的 `sessionId` 优先归一化后的 `sourceSessionId`,为空时回退 `workId`;`status='ready'`,`messages=[]`,`pendingAction=null`,`sourceMode` 来自 draft,`updatedAt` 来自 summary。
|
||||
- 跳一跳 pending sessionId 优先 `sourceSessionId`,缺失时用 `profileId`;素材、路径和 prompt 维持空值兜底。
|
||||
- 敲木鱼 detail sessionId 优先级固定为 `work.summary.sourceSessionId > fallbackItem.sourceSessionId > profileId`。
|
||||
- 敲木鱼生成中摘要的 `workId/profileId/sourceSessionId` 都来自 sessionId;标题、描述和标签优先表单 payload,其次 session draft,最后回退 `敲木鱼` / 空描述 / `['敲木鱼']`。
|
||||
- 敲木鱼 pending session 保持 `floatingWords=['功德 +1']`、素材 / 音效 / 背景为空的旧默认。
|
||||
|
||||
## Depth / Leverage / Locality
|
||||
|
||||
- **Depth**:壳层以少量函数取得恢复用 DTO;ID 优先级、方洞 profile 默认值、视觉小说 session fallback、敲木鱼生成中摘要和 pending draft 字段藏入 Module Implementation。
|
||||
- **Leverage**:后续新增生成中作品恢复或修改 sessionId 规则时,先改 Module 与单测,再保持壳层 Adapter 副作用不变。
|
||||
- **Locality**:拼图、方洞、视觉小说、跳一跳和敲木鱼的恢复 / 生成中映射集中在一个纯测试面,避免在大型壳层顶部继续堆积 DTO 构造。
|
||||
|
||||
## 验收
|
||||
|
||||
- `npm run test -- src/components/platform-entry/platformMiniGameSessionMappingModel.test.ts`
|
||||
- `npx eslint src/components/platform-entry/platformMiniGameSessionMappingModel.ts src/components/platform-entry/platformMiniGameSessionMappingModel.test.ts src/components/platform-entry/PlatformEntryFlowShellImpl.tsx --quiet`
|
||||
- `npm run typecheck`
|
||||
- `npm run check:encoding`
|
||||
@@ -0,0 +1,39 @@
|
||||
# 【前端架构】Platform Played Work Open Model 收口计划
|
||||
|
||||
## 背景
|
||||
|
||||
`PlatformEntryFlowShellImpl.tsx` 的个人“玩过作品”点击回调曾在壳层内直接判断 `worldType`、`worldKey` 前缀、玩法别名、目标 ID 兜底、RPG 公开详情 payload 和大鱼吃小鱼 gallery miss fallback。壳层因此同时承载纯打开意图与异步副作用,后续新增玩法或修正玩过作品身份时缺少稳定测试面。
|
||||
|
||||
个人“玩过作品”的点击规则属于打开意图。壳层应只关闭面板、读取 gallery、打开详情和写错误;玩法别名、目标 ID、fallback payload 应收口到纯 **Module**。
|
||||
|
||||
## 决策
|
||||
|
||||
新增 `src/components/platform-entry/platformPlayedWorkOpenModel.ts` 作为 Platform Played Work Open **Module**。其公开 **Interface** 为:
|
||||
|
||||
- `resolvePlatformPlayedWorkOpenIntent(work)`:输入 `ProfilePlayedWorkSummary`,输出 `noop`、各玩法公开详情打开意图、`open-big-fish` 或 `open-rpg`。
|
||||
- `PlatformPlayedWorkOpenIntent`:描述壳层可执行的打开动作;大鱼吃小鱼意图包含 `sessionId` 和 gallery miss 时使用的 `fallbackWork`,RPG 意图包含 `CustomWorldGalleryCard` 详情 payload。
|
||||
|
||||
`PlatformEntryFlowShellImpl.tsx` 仍作为 **Adapter**:它保留 `setIsProfilePlayStatsOpen(false)`、各玩法 `open*PublicWorkDetail`、`refreshBigFishGallery()`、大鱼 gallery 命中优先逻辑、`mapBigFishWorkToPublicWorkDetail(...)` 与错误 setter。
|
||||
|
||||
## Interface 约束
|
||||
|
||||
- `worldType` 只做小写归一,不 trim;`worldKey` 前缀匹配保持大小写敏感,延续旧行为。
|
||||
- `profileId` 使用 nullish 优先级:只在 `profileId` 为 `null` / `undefined` 时从 `worldKey` 前缀兜底;空字符串仍视为缺目标并返回 `noop`。
|
||||
- `puzzle` 打开时固定携带 `{ tab: 'profile' }`。
|
||||
- `match3d` / `match_3d`、`square-hole` / `square_hole`、`jump-hop` / `jump_hop`、`wooden-fish` / `wooden_fish`、`big-fish` / `big_fish` 均保持既有别名。
|
||||
- `big-fish` 缺 gallery 命中时使用 Module 生成的 `fallbackWork`,默认 `ownerUserId` 为空串、`authorDisplayName` 为 `worldSubtitle || '玩家'`、关卡和素材 ready 计数为 `0` / `false`。
|
||||
- 未识别的 `worldType` 仍按 RPG 公开详情打开;缺 `ownerUserId` 或缺 profile 目标时返回 `noop`。
|
||||
|
||||
## Depth / Leverage / Locality
|
||||
|
||||
- **Depth**:调用方只消费一个打开 intent;玩法别名、目标 ID 兜底和 fallback payload 藏入 Module Implementation。
|
||||
- **Leverage**:新增“玩过作品”玩法时,先在 intent union、resolver 与单测中定义,再让壳层 Adapter 绑定对应打开副作用。
|
||||
- **Locality**:RPG fallback payload 与大鱼 fallback work 不再散落在大型壳层里,维护者可在纯测试中锁定字段契约。
|
||||
|
||||
## 验收
|
||||
|
||||
- `npm run test -- src/components/platform-entry/platformPlayedWorkOpenModel.test.ts`
|
||||
- `npx eslint src/components/platform-entry/platformPlayedWorkOpenModel.ts src/components/platform-entry/platformPlayedWorkOpenModel.test.ts src/components/platform-entry/PlatformEntryFlowShellImpl.tsx --quiet`
|
||||
- `npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "authenticated users can open save archives from the profile played panel"`
|
||||
- `npm run typecheck`
|
||||
- `npm run check:encoding`
|
||||
@@ -0,0 +1,32 @@
|
||||
# 【前端架构】Platform Profile Wallet Delta Model 收口计划
|
||||
|
||||
## 背景
|
||||
|
||||
`PlatformEntryFlowShellImpl.tsx` 仍内联维护个人钱包余额的本地 delta 规则:余额归一化、本地扣点 / 返还后的 dashboard 乐观更新,以及刷新服务端 dashboard 时如何抵消已经被服务端反映的本地 delta。
|
||||
|
||||
这些规则是纯展示状态计算,但留在平台壳层会让壳层同时理解钱包余额边界、整数截断、负数保护和服务端快照对账。
|
||||
|
||||
## 决策
|
||||
|
||||
新增 `platformProfileWalletDeltaModel.ts`,收口钱包余额本地 delta 的纯规则:
|
||||
|
||||
- `resolveProfileWalletBalance(...)` 负责把 dashboard 余额归一为非负整数。
|
||||
- `adjustProfileDashboardWalletBalance(...)` 负责把本地 delta 应用到 dashboard,并刷新 `updatedAt`。
|
||||
- `reconcileProfileWalletLocalDeltaWithServerDashboard(...)` 负责在拿到新服务端 dashboard 后扣除已被服务端反映的本地借贷变化。
|
||||
|
||||
`PlatformEntryFlowShellImpl.tsx` 继续保留 API 请求、React ref、state 写入和刷新触发副作用。
|
||||
|
||||
## 接口约束
|
||||
|
||||
- 非数字、无穷值或空 dashboard 的余额按 `0` 处理。
|
||||
- 本地 delta 必须先 `Math.trunc`,余额不得低于 `0`。
|
||||
- 当服务端最新余额已经反映本地扣点时,剩余负 delta 应减少;已经全部反映时归零。
|
||||
- 当服务端最新余额已经反映本地返还 / 奖励时,剩余正 delta 应减少;已经全部反映时归零。
|
||||
- 服务端余额变化方向与本地 delta 相反时,不得错误抵消。
|
||||
|
||||
## 验收
|
||||
|
||||
- `npm run test -- src/components/platform-entry/platformProfileWalletDeltaModel.test.ts`
|
||||
- 针对新 Module 与 `PlatformEntryFlowShellImpl.tsx` 执行 ESLint。
|
||||
- `npm run typecheck`
|
||||
- `npm run check:encoding`
|
||||
@@ -0,0 +1,40 @@
|
||||
# 【前端架构】Platform Public Code Search Model 收口计划
|
||||
|
||||
## 背景
|
||||
|
||||
`PlatformEntryFlowShellImpl.tsx` 的公开搜索回调曾直接在壳层内判断 `user_` / `user-`、`PZ`、`BF`、`JH`、`WF`、`BO`、`M3`、`SH`、`VN`、`BB`、`CW`、纯数字和普通关键词的优先级。壳层因此既要持有搜索输入到查找顺序的纯规则,又要执行各玩法公开详情读取、用户读取、运行态启动和错误归航副作用。
|
||||
|
||||
公开搜索的“先查什么、失败后回退什么”是稳定的分流规则,应有独立测试面;壳层只应作为副作用 Adapter,按计划执行网络读取与打开动作。
|
||||
|
||||
## 决策
|
||||
|
||||
新增 `src/components/platform-entry/platformPublicCodeSearchModel.ts` 作为 Platform Public Code Search **Module**。其公开 **Interface** 为:
|
||||
|
||||
- `resolvePlatformPublicCodeSearchPlan(keyword)`:输入用户搜索词,输出 `{ normalizedKeyword, steps }`;空输入返回 `null`。
|
||||
- `PlatformPublicCodeSearchStep`:枚举壳层可执行的查找步骤,包括 `user-id`、`public-user-code`、`rpg-work`、各玩法公开作品步骤与 `bark-battle-work`。
|
||||
- `mapRpgPublicCodeSearchDetailToGalleryCard(entry)`:把 RPG by-code 详情响应映射为公开作品卡,收口 `playCount` / `remixCount` / `likeCount` 的 `0` 兜底。
|
||||
- `resolve*PublicCodeSearchMatch(entries, keyword)`:统一各玩法公开作品列表的公开码匹配、公开可见性过滤和详情卡 DTO 映射;拼图、大鱼吃小鱼、跳一跳、敲木鱼、宝贝识物、抓大鹅、方洞挑战、视觉小说和汪汪声浪都走此接口。
|
||||
|
||||
`PlatformEntryFlowShellImpl.tsx` 仍作为 **Adapter**:它保留 `getPublicAuthUserByCode`、各玩法 gallery 刷新 / 详情打开、Bark Battle runtime 特例和 missing work 归航副作用,只按 `steps` 顺序执行,前一步失败才尝试下一步;壳层不再重复维护 per-play `isSame*PublicWorkCode` 匹配和 DTO 映射。
|
||||
|
||||
## Interface 约束
|
||||
|
||||
- 空白搜索词返回 `null`,壳层不得进入搜索 loading。
|
||||
- `user_` / `user-` 开头的内部用户 ID 只执行 `user-id`,不回退作品号。
|
||||
- `PZ`、`BF`、`JH`、`WF`、`BO`、`M3`、`SH`、`VN`、`BB` 前缀只进入对应玩法公开作品查找;`M3D-*` 继续归入并匹配 `M3` / 抓大鹅。
|
||||
- `CW` 与 `1-8` 位纯数字先查 RPG 公开作品,再回退陶泥号。
|
||||
- 普通关键词与 `SY` 陶泥号保持既有顺序:先查陶泥号,再查 RPG 公开作品,再查汪汪声浪作品,最后再以陶泥号兜底。
|
||||
|
||||
## Depth / Leverage / Locality
|
||||
|
||||
- **Depth**:壳层只消费短小的 `steps` 与 match result Interface,搜索前缀、优先级、回退顺序、per-play 匹配和 DTO 映射藏入 Module Implementation。
|
||||
- **Leverage**:新增公开作品前缀时,先扩展 Module 的 step union、前缀表、matcher 和单测,再在壳层 Adapter 绑定对应网络读取与打开动作。
|
||||
- **Locality**:搜索计划与作品命中规则集中在一个纯 Module;UI、网络、详情打开与 runtime 启动副作用继续留在壳层,避免把副作用 setter 变成浅 Interface。
|
||||
|
||||
## 验收
|
||||
|
||||
- `npm run test -- src/components/platform-entry/platformPublicCodeSearchModel.test.ts`
|
||||
- `npm run test -- src/services/publicWorkCode.test.ts`
|
||||
- `npx eslint src/components/platform-entry/platformPublicCodeSearchModel.ts src/components/platform-entry/platformPublicCodeSearchModel.test.ts src/components/platform-entry/PlatformEntryFlowShellImpl.tsx --quiet`
|
||||
- `npm run typecheck`
|
||||
- `npm run check:encoding`
|
||||
@@ -0,0 +1,68 @@
|
||||
# PlatformPublicWorkDetailFlow 收口计划
|
||||
|
||||
## 背景
|
||||
|
||||
`PlatformEntryFlowShellImpl.tsx` 已把公开作品身份、去重和推荐 runtime kind 收口到 `platformPublicGalleryFlow.ts`,但统一作品详情入口仍在壳层 Implementation 内直接判断 RPG、拼图、跳一跳、敲木鱼、视觉小说和其它玩法。壳层既要知道哪些公开详情可直接使用当前 entry,又要知道哪些玩法必须先补读完整详情,还要按当前用户判断详情按钮是“编辑”还是“改造”。这些是纯决策规则,继续留在巨型壳层会削弱 Locality。
|
||||
|
||||
## 决策
|
||||
|
||||
- 新增 `src/components/platform-entry/platformPublicWorkDetailFlow.ts` 作为 Platform Public Work Detail Flow Module。
|
||||
- Module Interface 收口:
|
||||
- `getPlatformPublicWorkDetailKind(entry)`
|
||||
- `resolvePlatformPublicWorkDetailOpenStrategy(entry)`
|
||||
- `resolvePlatformPublicWorkActionMode(entry, viewerUserId)`
|
||||
- `resolvePlatformPublicWorkEditIntent(entry, deps)`
|
||||
- `resolvePlatformPublicWorkLikeIntent(entry)`
|
||||
- `resolvePlatformPublicWorkRemixIntent(entry)`
|
||||
- `resolvePlatformPublicWorkStartIntent(entry, deps)`
|
||||
- `resolvePlatformPublicWorkDetailOpenDecision(entry, deps)`
|
||||
- `resolveActivePlatformPublicWorkAuthorEntry(args)`
|
||||
- `map*WorkToPublicWorkDetail(...)`
|
||||
- `mapPublicWorkDetailToPuzzleWork(entry)`
|
||||
- `mapPublicWorkDetailToBigFishWork(entry)`
|
||||
- `mapPublicWorkDetailToSquareHoleWork(entry)`
|
||||
- `mapBarkBattlePublicDetailToWorkSummary(entry)`
|
||||
- `resolveVisiblePuzzleDetailCoverCount(entry, run)`
|
||||
- `PlatformEntryFlowShellImpl.tsx` 继续作为 Adapter:根据 open strategy 调用 `openPublicWorkDetail`、`openPuzzlePublicWorkDetail`、`openJumpHopPublicWorkDetail`、`openWoodenFishPublicWorkDetail`、`openVisualNovelPublicWorkDetail` 或 `openRpgPublicWorkDetail`。
|
||||
- 公开详情 entry 映射与公开详情反推玩法 work 摘要也收口到 Module。壳层只在运行态启动、编辑、改造、推荐缓存和详情展示时调用映射 Interface,不再在壳层顶部持有每个玩法的 DTO 拼装 Implementation。
|
||||
- `mapMatch3DWorkToPublicWorkDetail` 归入 `platformMatch3DRuntimeProfile.ts`,继续委托 `normalizeMatch3DWorkForRuntimeUi` 处理素材归一和背景资产提升;`platformPublicWorkDetailFlow.ts` 不复制 Match3D 运行态素材规则。
|
||||
- 公开详情启动、编辑、点赞和改造只抽“意图” Interface,不把整个 callback 搬进 Module。壳层继续作为 Adapter 执行鉴权、API 调用、运行态启动、草稿恢复、busy 状态、缓存同步、stage 切换和错误 setter,避免形成伪 Seam。
|
||||
|
||||
## Interface 约束
|
||||
|
||||
- `getPlatformPublicWorkDetailKind` 只根据 `PlatformPublicGalleryCard` 的玩法判定 helper 归一 kind;没有 `sourceType` 的公开 RPG 作品回退为 `rpg`。
|
||||
- `resolvePlatformPublicWorkDetailOpenStrategy` 只表达“如何打开详情”,不执行网络请求或 state setter。
|
||||
- 拼图、跳一跳、敲木鱼、视觉小说需要按 `profileId` 补读完整详情;返回对应 `load-*` strategy。
|
||||
- 大鱼吃小鱼、抓大鹅、方洞挑战、汪汪声浪、宝贝识物和其它可直接展示的公开 entry 返回 `use-entry` strategy。
|
||||
- RPG 返回 `load-rpg-detail` strategy,由壳层 Adapter 继续调用 RPG 详情读取流程。
|
||||
- `resolvePlatformPublicWorkActionMode` 只比较 `entry.ownerUserId` 与当前 viewer user id;当前用户拥有该公开作品时返回 `edit`,否则返回 `remix`。
|
||||
- `resolvePlatformPublicWorkEditIntent` 只表达自有公开作品编辑意图:大鱼吃小鱼、拼图、抓大鹅、方洞挑战、视觉小说和汪汪声浪在能定位原草稿时返回对应 draft open 目标;跳一跳、敲木鱼和缺草稿作品返回原阻断文案;宝贝识物只返回需解析本地草稿的 intent;旧 RPG gallery fallback 只在完整 RPG 详情已补读且 profile 匹配时返回编辑 intent。壳层仍执行草稿恢复、宝贝识物异步草稿解析、RPG 编辑导航和错误展示。
|
||||
- `resolvePlatformPublicWorkEditIntent` 的 `deps` 只接编辑决策所需的当前拼图详情、当前 RPG 详情、视觉小说作品缓存、汪汪声浪作品缓存,以及抓大鹅 public detail -> work 的 Adapter。抓大鹅 Adapter 必须来自 Match3D Runtime Profile Module,以保留 `generatedItemAssets` 归一化与背景资产提升的 Locality。
|
||||
- `resolvePlatformPublicWorkLikeIntent` 只表达公开作品点赞意图:大鱼吃小鱼、拼图和旧 RPG gallery fallback 返回可执行 intent;宝贝识物、汪汪声浪、方洞挑战和视觉小说返回不可用文案。壳层只按 intent 调用 API、写缓存和展示错误,不再持有这组能力矩阵。
|
||||
- `resolvePlatformPublicWorkRemixIntent` 只表达公开作品改造意图:大鱼吃小鱼和拼图返回可执行 intent 与成功后目标 stage,旧 RPG gallery fallback 返回可执行 intent,其它玩法返回原未开放文案。壳层只按 intent 调用 remix API、写 session / 缓存、切 stage 和展示错误。
|
||||
- `resolvePlatformPublicWorkStartIntent` 只表达公开作品“开始游玩”意图:大鱼吃小鱼、拼图、跳一跳、敲木鱼、抓大鹅、方洞挑战、视觉小说、汪汪声浪和宝贝识物返回对应启动目标;旧 RPG gallery fallback 只在完整 RPG 详情已补读且 profile 匹配时返回记录游玩 intent,否则返回原阻断文案。壳层仍执行登录保护、运行态启动、RPG 游玩记录、详情更新、busy 状态和错误展示。
|
||||
- `resolvePlatformPublicWorkStartIntent` 的 `deps` 只接启动决策所需的当前拼图详情、当前 RPG 详情、汪汪声浪作品缓存,以及抓大鹅 public detail -> work 的 Adapter。抓大鹅 Adapter 必须来自 Match3D Runtime Profile Module,以保留 `generatedItemAssets` 归一化与背景资产提升的 Locality。
|
||||
- `resolvePlatformPublicWorkDetailOpenDecision` 只表达直接展示公开详情的打开 / 阻断结果、错误文案、目标 stage 与可写入历史的路径;真正执行 setter、push history 的副作用仍由壳层 Adapter 执行。
|
||||
- `resolveActivePlatformPublicWorkAuthorEntry` 只在 `work-detail` 阶段选择统一公开详情 entry,在 RPG `detail` 阶段只选择非 draft 的 RPG 详情 entry;作者请求、竞态 request key 和缓存仍留壳层。
|
||||
- `map*WorkToPublicWorkDetail` 只把各玩法已存在的 work / gallery summary 映射为统一详情 entry;公开码、封面、统计与标题字段继续复用 `rpgEntryWorldPresentation.ts` 的平台公开卡片映射。
|
||||
- `mapPublicWorkDetailToPuzzleWork`、`mapPublicWorkDetailToBigFishWork`、`mapPublicWorkDetailToSquareHoleWork` 和 `mapBarkBattlePublicDetailToWorkSummary` 只用于公开详情 CTA、推荐缓存或运行态启动前的兼容 work 摘要拼装;缺省值必须留在 Module 测试中固定,壳层不得重复推导。
|
||||
- `resolveVisiblePuzzleDetailCoverCount` 只表达拼图公开详情封面解锁规则:非拼图、无当前 run 或 run 不属于当前公开详情时只展示首图;当前 run 属于该公开详情时按 `clearedLevelCount + 1` 解锁,但至少为 1。`PlatformWorkDetailView` 只接收 `visibleCoverCount` 展示,不读取 run。
|
||||
- Match3D 的公开详情与 work 摘要互转仍属于 Match3D Runtime Profile Module,因为它依赖 `generatedItemAssets` 归一化与背景资产提升。公开详情 Flow 只接统一详情策略,不复制该运行态规则。
|
||||
|
||||
## Depth / Leverage / Locality
|
||||
|
||||
- **Depth**:壳层传入公开作品 entry、玩法 work summary、当前用户 id、当前拼图 run 或少量启动 / 编辑 deps,即可得到详情打开策略、动作模式、编辑 / 点赞 / 改造 / 启动意图、统一详情映射和封面可见数;玩法判定、能力矩阵与 DTO 默认值藏在 Module Implementation 内。
|
||||
- **Leverage**:新增玩法公开详情时先补 Strategy / Mapping 单测,再接壳层 Adapter,不必在多个 JSX / callback 位置重复 sourceType 判断或 DTO 回填。
|
||||
- **Locality**:公开作品详情入口的纯策略与通用映射集中到一个小 Module;Match3D 素材归一仍在 Match3D Module;启动运行态、点赞、改造、编辑等副作用仍留在壳层,避免伪 Seam。
|
||||
|
||||
## 验收
|
||||
|
||||
- `npm run test -- src/components/platform-entry/platformPublicWorkDetailFlow.test.ts`
|
||||
- `npm run test -- src/components/platform-entry/platformMatch3DRuntimeProfile.test.ts`
|
||||
- `npm run test -- src/components/platform-entry/platformPublicGalleryFlow.test.ts`
|
||||
- `npm run test -- src/components/platform-entry/PlatformWorkDetailView.test.tsx`
|
||||
- `npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "public detail|owned public puzzle detail|direct missing public work detail"`
|
||||
- `npx eslint src/components/platform-entry/platformPublicWorkDetailFlow.ts src/components/platform-entry/platformPublicWorkDetailFlow.test.ts --max-warnings 0`
|
||||
- `npx eslint src/components/platform-entry/PlatformEntryFlowShellImpl.tsx --quiet`
|
||||
- `npm run typecheck`
|
||||
- `npm run check:encoding`
|
||||
@@ -0,0 +1,44 @@
|
||||
# 【前端架构】Platform Puzzle Draft Recovery Model 收口计划
|
||||
|
||||
## 背景
|
||||
|
||||
`PlatformEntryFlowShellImpl.tsx` 曾内联维护拼图生成完成后刷新恢复的两个纯函数:`normalizeRecoveredPuzzleDraftSession` 与 `hasRecoverableGeneratedPuzzleDraft`。旧逻辑只要草稿有 `coverImageSrc`、首关 cover 或候选图,就会把恢复会话的 draft 和首关 `generationStatus` 抬成 `ready`,再进入结果页。
|
||||
|
||||
`.hermes/shared-memory/pitfalls.md` 已记录:拼图待发布判定偏弱时,只有首图但缺关卡画面、UI spritesheet 或关卡背景的半成品会被误当完成,用户进入结果页后仍可能空图或无法发布。
|
||||
|
||||
本切片先修前端恢复链路:只有完整首关资产包存在时,恢复流程才视为可完成。后端 `build_result_preview` / `validate_publish_requirements` / `is_puzzle_session_snapshot_publish_ready` 的发布门槛收紧另作后续切片,不混入本次前端模型收口。
|
||||
|
||||
## 决策
|
||||
|
||||
新增 `src/components/platform-entry/platformPuzzleDraftRecoveryModel.ts` 作为 Platform Puzzle Draft Recovery **Module**。其公开 **Interface** 为:
|
||||
|
||||
- `normalizeRecoveredPuzzleDraftSession(session)`:从恢复会话里补齐首图 cover、assetId 和 selectedCandidateId;只有完整资产包满足时,才把 draft 与首关 `generationStatus` 改为 `ready`。
|
||||
- `hasRecoverableGeneratedPuzzleDraft(session)`:判断恢复会话是否拥有完整首关资产包。
|
||||
|
||||
`PlatformEntryFlowShellImpl.tsx` 仍作为 **Adapter**:它继续负责拉取 session、写 background task、写 React state、打开结果页和切换 stage。
|
||||
|
||||
## Interface 约束
|
||||
|
||||
- 无 draft 时保持原 session,并判定不可恢复完成态。
|
||||
- 首图可来自 `draft.coverImageSrc`、首关 `coverImageSrc` 或选中 / 首个候选图。
|
||||
- 完整首关资产包必须同时具备:
|
||||
- 首图 cover;
|
||||
- `levelSceneImageSrc` 或 `levelSceneImageObjectKey`;
|
||||
- `uiSpritesheetImageSrc` 或 `uiSpritesheetImageObjectKey`;
|
||||
- `levelBackgroundImageSrc` 或 `levelBackgroundImageObjectKey`。
|
||||
- cover / assetId / selectedCandidateId 可按旧优先级从 draft、首关、候选图回填;但若完整资产包不满足,不得把 `generationStatus` 抬为 `ready`。
|
||||
- 只修复前端恢复判定,不改变拼图发布接口、后端 session stage 或后端 preview compiler。
|
||||
|
||||
## Depth / Leverage / Locality
|
||||
|
||||
- **Depth**:壳层以两个函数表达“恢复会话归一化”和“是否可作为生成完成态恢复”;完整资产门槛和候选图 fallback 藏入 Module Implementation。
|
||||
- **Leverage**:后续后端补齐发布门槛时,可用同一资产语言对齐前端恢复模型,避免壳层再散落条件判断。
|
||||
- **Locality**:拼图恢复判定集中到纯测试面,避免在异步恢复 callback 中把半成品 ready 规则继续隐身。
|
||||
|
||||
## 验收
|
||||
|
||||
- `npm run test -- src/components/platform-entry/platformPuzzleDraftRecoveryModel.test.ts`
|
||||
- `npx eslint src/components/platform-entry/platformPuzzleDraftRecoveryModel.ts src/components/platform-entry/platformPuzzleDraftRecoveryModel.test.ts src/components/platform-entry/PlatformEntryFlowShellImpl.tsx --quiet`
|
||||
- `npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "persisted generating puzzle draft"`
|
||||
- `npm run typecheck`
|
||||
- `npm run check:encoding`
|
||||
@@ -0,0 +1,36 @@
|
||||
# 【前端架构】Platform Puzzle Runtime State Model 收口计划
|
||||
|
||||
## 背景
|
||||
|
||||
`PlatformEntryFlowShellImpl.tsx` 曾内联 `mergePuzzleServiceRuntimeState(...)`,在拼图排行榜提交回包后,把服务端 run 快照合并回当前前端 run。此逻辑没有 React state、网络、URL 或弹窗副作用,却需要理解 `PuzzleRunSnapshot` 的局部真相分工:拼块布局、当前关卡状态和计时结果由前端即时裁决;服务端回包只补排行榜、run 身份、通关数上限和下一关 handoff。
|
||||
|
||||
若该合并规则继续留在平台壳,后续调整排行榜来源、相似作品下一关或本地 / 服务端 run 混合策略时,维护者必须翻大型壳层并同时避开大量副作用代码。
|
||||
|
||||
## 决策
|
||||
|
||||
新增 `src/components/platform-entry/platformPuzzleRuntimeStateModel.ts` 作为 Platform Puzzle Runtime State **Module**。公开 **Interface**:
|
||||
|
||||
- `mergePuzzleServiceRuntimeState(currentRun, serviceRun)`:当双方都有 `currentLevel` 时,保留当前前端关卡状态与棋盘,只合并服务端 run 身份、`clearedLevelCount` 上限、排行榜与下一关 handoff;任一方缺 `currentLevel` 时返回当前 run。
|
||||
|
||||
`PlatformEntryFlowShellImpl.tsx` 继续作为 **Adapter**:它负责提交排行榜、读取回包、写 React state、刷新 archive 和错误提示,不再持有拼图 run 快照合并字段清单。
|
||||
|
||||
## Interface 约束
|
||||
|
||||
- 缺少 `currentRun.currentLevel` 或 `serviceRun.currentLevel` 时不得合并,直接返回当前 run。
|
||||
- `clearedLevelCount` 取当前 run 与服务端 run 的最大值,避免服务端较旧回包降低本地通关数。
|
||||
- 排行榜优先取 `serviceRun.currentLevel.leaderboardEntries`;为空时取 `serviceRun.leaderboardEntries`;两者皆空时保留当前关卡榜单。
|
||||
- `currentLevel` 的棋盘、状态、计时和关卡字段来自当前 run,不被服务端回包覆盖。
|
||||
- `runId`、`entryProfileId`、`recommendedNextProfileId`、`nextLevelMode`、`nextLevelProfileId`、`nextLevelId`、`recommendedNextWorks` 来自服务端 run。
|
||||
|
||||
## Depth / Leverage / Locality
|
||||
|
||||
- **Depth**:壳层传入当前 run 与服务端 run,即取得合并后的稳定快照;排行榜来源、下一关 handoff 和前端局部真相保留规则藏入 Module Implementation。
|
||||
- **Leverage**:排行榜提交、后续相似作品推荐或服务端 run 字段变化时,先改纯 Module 与单测,壳层提交副作用不变。
|
||||
- **Locality**:拼图 runtime 快照合并规则集中到一个纯测试面,避免在平台壳中继续散落 `PuzzleRunSnapshot` 字段判断。
|
||||
|
||||
## 验收
|
||||
|
||||
- `npm run test -- src/components/platform-entry/platformPuzzleRuntimeStateModel.test.ts`
|
||||
- `npx eslint src/components/platform-entry/platformPuzzleRuntimeStateModel.ts src/components/platform-entry/platformPuzzleRuntimeStateModel.test.ts src/components/platform-entry/PlatformEntryFlowShellImpl.tsx --quiet`
|
||||
- `npm run typecheck`
|
||||
- `npm run check:encoding`
|
||||
@@ -0,0 +1,36 @@
|
||||
# 【前端架构】Platform Recommend Runtime Auth Model 收口计划
|
||||
|
||||
## 背景
|
||||
|
||||
平台首页推荐流会以 embedded runtime 方式启动跳一跳、抓大鹅、方洞挑战、拼图、敲木鱼、视觉小说、大鱼吃小鱼和汪汪声浪等玩法。旧规则散在 `PlatformEntryFlowShellImpl.tsx` 顶层 helper 与多个启动 callback:匿名访客应申请 Runtime Guest Token,已登录或已有 access token 时应走 background auth,非 embedded 正常启动则不改普通鉴权。拼图还额外维护 `isolated` / `default` runtime auth mode,容易与通用推荐流口径漂移。
|
||||
|
||||
## 决策
|
||||
|
||||
新增 `src/components/platform-entry/platformRecommendRuntimeAuthModel.ts`,以纯 **Module** 收口推荐 runtime 鉴权计划:
|
||||
|
||||
- `resolvePlatformRecommendRuntimeAuthPlan(input)`:返回 `requestKind` 为 `none`、`background` 或 `runtime-guest`,并给出拼图 runtime 应落到 `default` 还是 `isolated`。
|
||||
- `shouldUsePlatformRecommendRuntimeGuestAuth(input)`:只判断当前用户状态和是否允许 guest auth,不读取本地 token。
|
||||
|
||||
`PlatformEntryFlowShellImpl.tsx` 继续作为 **Adapter**:它读取 `getStoredAccessToken()`、调用 `ensureRuntimeGuestToken()`、拼装具体 request options,并在启动拼图时写入 `setPuzzleRuntimeAuthMode(...)`。
|
||||
|
||||
## Interface 约束
|
||||
|
||||
- 非 embedded 且未显式允许 runtime guest auth 时,计划为 `none`。
|
||||
- embedded 推荐 runtime 若无登录用户且无本地 access token,计划为 `runtime-guest`。
|
||||
- embedded 推荐 runtime 若已有登录用户或本地 access token,计划为 `background`。
|
||||
- 拼图公开详情要求 `authMode='isolated'` 时,匿名状态应返回 `runtime-guest` 且 `puzzleRuntimeAuthMode='isolated'`。
|
||||
- 拼图公开详情要求 `authMode='isolated'` 但已登录或已有 access token 时,应回到 `default`,避免把账号态伪装成匿名 isolated guest。
|
||||
|
||||
## Depth / Leverage / Locality
|
||||
|
||||
- **Depth**:壳层传入 embedded、是否允许 guest、用户 ID 与本地 token 布尔值,即得 request 计划和拼图 runtime auth mode。
|
||||
- **Leverage**:所有推荐 runtime 启动复用同一鉴权矩阵;新增玩法只需消费计划,不再重写匿名 / 已登录分支。
|
||||
- **Locality**:guest token 选择规则集中在纯测试面,具体 token 获取和 request options 仍留在壳层副作用 Adapter。
|
||||
|
||||
## 验收
|
||||
|
||||
- `npm run test -- src/components/platform-entry/platformRecommendRuntimeAuthModel.test.ts`
|
||||
- `npx eslint --max-warnings 0 src/components/platform-entry/platformRecommendRuntimeAuthModel.ts src/components/platform-entry/platformRecommendRuntimeAuthModel.test.ts`
|
||||
- `npx eslint src/components/platform-entry/PlatformEntryFlowShellImpl.tsx --quiet`
|
||||
- `npm run typecheck`
|
||||
- `npm run check:encoding`
|
||||
@@ -0,0 +1,40 @@
|
||||
# 【前端架构】Platform Recommend Runtime Auto Start 收口计划
|
||||
|
||||
## 背景
|
||||
|
||||
平台推荐页的 embedded runtime 会在移动端首页自动选择当前推荐作品并启动对应玩法。旧 `useEffect` 同时判断桌面断点、当前 stage、当前 Tab、平台 loading、推荐列表是否为空、active entry 是否仍存在、对应 runtime 是否 ready、是否已有启动请求,以及下一条 entry 应选谁。
|
||||
|
||||
这组判断是纯推荐流自动启动决策,但留在 `PlatformEntryFlowShellImpl.tsx` 会让 effect 依赖很长,也让后续新增玩法时容易把 ready 判定和启动时机混在副作用里。
|
||||
|
||||
## 决策
|
||||
|
||||
扩展 `src/components/platform-entry/platformPublicGalleryFlow.ts`,新增 `resolvePlatformRecommendRuntimeAutoStartDecision(input)`:
|
||||
|
||||
- `noop`:当前不需要改变推荐 runtime。
|
||||
- `clear`:推荐列表为空,壳层应清空 active entry、runtime kind 和错误。
|
||||
- `start`:壳层应调用既有 `selectRecommendRuntimeEntry(entry)` 启动指定作品。
|
||||
|
||||
`PlatformEntryFlowShellImpl.tsx` 继续作为 **Adapter**:它负责收集 React state、清空 state、调用 `selectRecommendRuntimeEntry(...)` 和执行各玩法 runtime 副作用。
|
||||
|
||||
## Interface 约束
|
||||
|
||||
- 桌面端、非 `platform` stage、非 `home` Tab 或平台仍在 loading 时返回 `noop`。
|
||||
- 推荐列表为空时返回 `clear`。
|
||||
- active entry 存在且对应 runtime 已 ready 时返回 `noop`。
|
||||
- 当前已有启动请求时返回 `noop`。
|
||||
- active entry 存在但未 ready 时返回 `start(activeEntry)`。
|
||||
- active key 缺失或已不在列表中时返回 `start(firstEntry)`。
|
||||
|
||||
## Depth / Leverage / Locality
|
||||
|
||||
- **Depth**:壳层只消费三态决策;列表查找、ready 判定和自动启动门禁藏入 Flow Module Implementation。
|
||||
- **Leverage**:后续推荐流新增玩法或改 ready 判定,只需补 `platformPublicGalleryFlow.ts` 的模型测试。
|
||||
- **Locality**:effect 只保留副作用动作,不再承载推荐流状态机知识。
|
||||
|
||||
## 验收
|
||||
|
||||
- `npm run test -- src/components/platform-entry/platformPublicGalleryFlow.test.ts`
|
||||
- `npx eslint --max-warnings 0 src/components/platform-entry/platformPublicGalleryFlow.ts src/components/platform-entry/platformPublicGalleryFlow.test.ts`
|
||||
- `npx eslint src/components/platform-entry/PlatformEntryFlowShellImpl.tsx --quiet`
|
||||
- `npm run typecheck`
|
||||
- `npm run check:encoding`
|
||||
@@ -0,0 +1,38 @@
|
||||
# 【前端架构】Platform RPG Agent Result Preview Model 收口计划
|
||||
|
||||
## 背景
|
||||
|
||||
`PlatformEntryFlowShellImpl.tsx` 曾内联维护 RPG Agent 结果页的发布门禁展示规则:从 `CustomWorldProfile` 顶层字段、`creatorIntent`、`anchorContent`、章节蓝图和场景章节中反证服务端返回的 legacy blocker 是否已经被当前结果页 profile 补齐,并同时在壳层内把 result preview source 映射成展示标签。
|
||||
|
||||
这些逻辑不读取 React state,不请求网络,不写 URL,也不操作弹窗;它们属于 RPG Agent 结果预览展示的纯判定。壳层继续负责 session、profile、发布动作和结果页 props 编排。
|
||||
|
||||
## 决策
|
||||
|
||||
新增 `src/components/platform-entry/platformRpgAgentResultPreviewModel.ts` 作为 Platform RPG Agent Result Preview **Module**。其公开 **Interface** 为:
|
||||
|
||||
- `buildPlatformRpgAgentResultPublishGateView(profile, fallbackBlockers, fallbackPublishReady)`:无 profile 时沿用服务端 fallback;有 profile 时过滤已经被当前 profile 结构字段满足的发布 blocker,并按剩余 blocker 重算展示态 `publishReady`。
|
||||
- `resolvePlatformRpgAgentResultPreviewSourceLabel(source)`:把 `published_profile`、`session_preview` 和未知 future source 映射成结果页预览来源标签。
|
||||
|
||||
`PlatformEntryFlowShellImpl.tsx` 仍作为 **Adapter**:它只把 `agentResultPreview` 与 `generatedCustomWorldProfile` 交给 Module,并将返回的 blocker / label 传入结果页组件。
|
||||
|
||||
## Interface 约束
|
||||
|
||||
- 无 profile 时不得自行修正 blocker,必须保留 fallback blocker message 与 fallback `publishReady`。
|
||||
- 有 profile 时只过滤已知结构 blocker:`publish_missing_world_hook`、`publish_missing_player_premise`、`publish_missing_core_conflict`、`publish_missing_main_chapter`、`publish_missing_first_act`。
|
||||
- 世界钩子兼容读取 `worldHook`、`creatorIntent.worldHook`、`anchorContent.worldPromise`、`anchorContent.worldPromise.hook` 和 `settingText`。
|
||||
- 玩家前提兼容读取 `playerPremise`、`creatorIntent.playerPremise`、`anchorContent.playerEntryPoint.openingIdentity`、`openingProblem`、`entryMotivation`。
|
||||
- 主章节兼容读取 `chapters`、`sceneChapterBlueprints`、`sceneChapters`;首幕读取 `sceneChapterBlueprints` / `sceneChapters` 下的 `acts`。
|
||||
- 未知 blocker code 不得被前端过滤;未知 source 保留“服务端预览”兜底,不做穷尽删除。
|
||||
|
||||
## Depth / Leverage / Locality
|
||||
|
||||
- **Depth**:壳层以两个函数取得发布门禁展示和 source label;profile 兼容字段路径、legacy blocker code 与兜底规则藏入 Module Implementation。
|
||||
- **Leverage**:后续后端调整 RPG result preview blocker 或新增 source 时,先改 Module 与单测,再让壳层 Adapter 保持结果页 props 编排不变。
|
||||
- **Locality**:RPG Agent 结果预览展示规则集中到一个纯测试面,避免在大型平台壳中继续混杂 profile 结构探测。
|
||||
|
||||
## 验收
|
||||
|
||||
- `npm run test -- src/components/platform-entry/platformRpgAgentResultPreviewModel.test.ts`
|
||||
- `npx eslint src/components/platform-entry/platformRpgAgentResultPreviewModel.ts src/components/platform-entry/platformRpgAgentResultPreviewModel.test.ts src/components/platform-entry/PlatformEntryFlowShellImpl.tsx --quiet`
|
||||
- `npm run typecheck`
|
||||
- `npm run check:encoding`
|
||||
@@ -0,0 +1,32 @@
|
||||
# 【前端架构】Platform Selection Stage Model 收口计划
|
||||
|
||||
## 背景
|
||||
|
||||
`PlatformEntryFlowShellImpl.tsx` 在受保护数据失效后会清空当前用户的私有作品、运行态、草稿 notice 和生成状态。清理完成后,壳层还要判断当前 `SelectionStage` 是否还能继续展示:公开首页、公开详情、工作台入口等阶段可保留;结果页、生成页、运行态、个人反馈等依赖私有数据或运行态快照的阶段必须回到首页。
|
||||
|
||||
此外,平台壳还曾在多个 `useEffect` 中分别判断 big-fish、match3d、square-hole、visual-novel、baby-object-match 缺少草稿、作品或 run 时应回工作台、结果页还是首页。这类“当前 stage 已不能被现有状态支撑”的规则同样属于 stage 纯判定,不应散在壳层。
|
||||
|
||||
此前这些规则以内联长否定串或多段相似 effect 维护在壳层 **Implementation** 内。新增玩法 stage 或调整登录态行为时,维护者必须在巨型壳层中查找白名单和状态缺失回退,缺少独立测试面。
|
||||
|
||||
## 决策
|
||||
|
||||
新增 `src/components/platform-entry/platformSelectionStageModel.ts` 作为 Platform Selection Stage **Module**。其公开 **Interface** 为:
|
||||
|
||||
- `resolveSelectionStageAfterProtectedDataLoss(stage)`:输入当前 `SelectionStage`,输出受保护数据失效后应停留的 stage;可保留则原样返回,否则返回 `platform`。
|
||||
- `resolveSelectionStageAfterMissingCreationState(params)`:输入当前 `SelectionStage` 与各玩法“是否有 session / draft / run / work / formPayload”等可渲染事实,输出状态缺失后应停留的 stage;仍可展示则原样返回。
|
||||
|
||||
`PlatformEntryFlowShellImpl.tsx` 仍作为副作用 **Adapter**:负责检测受保护数据从可读变为不可读、清空各玩法缓存、重置生成和错误状态,或把当前 React state 汇总为布尔事实,并只在模型输出与当前 stage 不一致时调用 `setSelectionStage(nextStage)`。
|
||||
|
||||
## 约定
|
||||
|
||||
- 新增 `SelectionStage` 时,必须判断它在退出登录或鉴权上下文收回后是否仍可展示,并在本 **Module** 的全量 `Record<SelectionStage, boolean>` 与测试中列明。
|
||||
- 公开列表、公开详情和创作工作台入口可保留;依赖当前用户私有数据、生成 session、运行态 run 或个人资料的 stage 默认回 `platform`。
|
||||
- 缺失状态回退只读取壳层传入的布尔事实,不直接读取玩法 session / work / run 对象。big-fish、match3d、square-hole 的草稿事实必须来自 `Boolean(session?.draft)`;visual-novel 的 session draft 与 work draft 可独立支撑结果页;baby-object-match runtime 缺 draft 时不看 formPayload,直接回 `platform`。
|
||||
- 此 **Module** 不清理 state、不调用路由、不触发登录弹窗,只表达纯 stage 决策。
|
||||
|
||||
## 验收
|
||||
|
||||
- `npm run test -- src/components/platform-entry/platformSelectionStageModel.test.ts`
|
||||
- `npx eslint src/components/platform-entry/platformSelectionStageModel.ts src/components/platform-entry/platformSelectionStageModel.test.ts src/components/platform-entry/PlatformEntryFlowShellImpl.tsx --quiet`
|
||||
- `npm run typecheck`
|
||||
- `npm run check:encoding`
|
||||
@@ -0,0 +1,30 @@
|
||||
# 【前端架构】Profile Dashboard Presentation 收口计划
|
||||
|
||||
## 背景
|
||||
|
||||
`RpgEntryHomeView.tsx` 的“我的数据”、钱包 chip 和“玩过”弹窗共用一批展示规则:泥点数量压缩、累计时长固定小时展示、单作品游玩时长压缩、作品类型标签和作品 ID 兜底。原先这些规则散在页面 **Implementation** 内,导致格式口径只能靠 UI 集成测试间接保护。
|
||||
|
||||
## 决策
|
||||
|
||||
新增 `src/components/rpg-entry/rpgEntryProfileDashboardPresentation.ts`,作为个人数据展示 **Module**。该 **Module** 的 **Interface** 收口为:
|
||||
|
||||
- `buildProfileDashboardPresentation(dashboard)`:统一生成钱包余额、钱包文案、累计时长文案和已玩数量文案。
|
||||
- `formatDashboardCount(value)`:统一泥点和计数压缩规则。
|
||||
- `formatTotalPlayTimeHours(playTimeMs)`:统一“累计游戏时长”固定小时口径。
|
||||
- `formatCompactPlayTime(playTimeMs)`:统一“玩过”单作品紧凑时长。
|
||||
- `formatPlayedWorkType(value)` 与 `formatPlayedWorkId(work)`:统一“玩过”列表里的玩法标签和作品号兜底。
|
||||
|
||||
`RpgEntryHomeView.tsx` 只消费这些 presentation 函数,保留卡片、弹窗和点击处理。个人数据展示规则的 **Locality** 转移到该 **Module** 与纯测试,后续修改计数、时长或作品类型标签不再穿透页面 JSX。
|
||||
|
||||
## 约定
|
||||
|
||||
- `formatDashboardCount` 与公开作品卡片的 `formatCompactCount` 不合并,二者展示口径不同。
|
||||
- “累计游戏时长”固定以小时展示,避免个人数据卡在分钟 / 天之间跳动。
|
||||
- “玩过”列表当前仍按历史契约用 `profileId || worldKey` 展示作品号;若后端未来下发 `publicWorkCode`,应在此 **Module** 改口径。
|
||||
|
||||
## 验证
|
||||
|
||||
- `npm run test -- src/components/rpg-entry/rpgEntryProfileDashboardPresentation.test.ts`
|
||||
- `npm run typecheck`
|
||||
- `npm run check:encoding`
|
||||
- 针对变更文件执行 ESLint
|
||||
31
docs/technical/【前端架构】ProfileFundsViewModel收口计划-2026-06-03.md
Normal file
31
docs/technical/【前端架构】ProfileFundsViewModel收口计划-2026-06-03.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# 【前端架构】Profile Funds ViewModel 收口计划
|
||||
|
||||
## 背景
|
||||
|
||||
`RpgEntryHomeView.tsx` 原先直接维护钱包账单来源文案、金额正负号、账单余额兜底、充值价格、充值商品主值和会员摘要文案。这些规则散在页面 **Implementation** 内,且已与 `ProfileWalletLedgerEntry.sourceType` 契约产生漂移:后端可返回 `puzzle_author_incentive_claim`,页面没有对应中文 label,会把原始枚举值外显给用户。
|
||||
|
||||
## 决策
|
||||
|
||||
新增 `src/components/rpg-entry/rpgEntryProfileFundsViewModel.ts` 作为个人资金展示 **Module**。该 **Module** 的 **Interface** 收口为:
|
||||
|
||||
- `getWalletLedgerSourceLabel(sourceType)`:统一账单来源中文文案,补齐 `puzzle_author_incentive_claim`。
|
||||
- `formatWalletLedgerAmount(amountDelta)`:统一账单金额正负号。
|
||||
- `buildWalletLedgerPresentation(ledger, fallbackBalance)`:统一余额兜底与账单行 presentation。
|
||||
- `formatRechargePrice(priceCents)` 与 `buildRechargeProductValueLabel(product)`:统一充值商品价格与主值文案。
|
||||
- `buildMembershipLabel(membership, formatTime)`:统一会员摘要文案,并保留页面现有时间格式 Adapter。
|
||||
|
||||
`RpgEntryHomeView.tsx` 只消费该 **Module** 输出,保留弹窗布局、支付流程、微信渠道和轮询副作用。资金展示规则的 **Locality** 收口到纯函数测试,后续新增账单来源或调整价格 / 会员文案时不再穿透页面 JSX。
|
||||
|
||||
## 约定
|
||||
|
||||
- 未知账单来源仍保留原始 sourceType 兜底,避免新后端枚举被空白吞掉。
|
||||
- 账单余额继续沿用既有口径:有账单时取第一条 `balanceAfter`,无账单时使用外部 fallback balance。
|
||||
- 本次只收展示 **Interface**,不迁移支付确认、微信跳转、订单轮询或弹窗状态。
|
||||
|
||||
## 验证
|
||||
|
||||
- `npm run test -- src/components/rpg-entry/rpgEntryProfileFundsViewModel.test.ts`
|
||||
- `npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx -t "wallet ledger|profile recharge modal shows native qr code"`
|
||||
- 针对变更文件执行 ESLint
|
||||
- `npm run typecheck`
|
||||
- `npm run check:encoding`
|
||||
29
docs/technical/【前端架构】ProfileTaskViewModel收口计划-2026-06-03.md
Normal file
29
docs/technical/【前端架构】ProfileTaskViewModel收口计划-2026-06-03.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# 【前端架构】Profile Task ViewModel 收口计划
|
||||
|
||||
## 背景
|
||||
|
||||
`RpgEntryHomeView.tsx` 的“每日任务”卡片与任务弹窗共用同一批展示规则:任务优先级、可领取 / 未完成选择、进度 clamp、奖励兜底、状态标签和按钮文案。原先这些规则散在巨型页面 **Implementation** 中,UI JSX 既要渲染,又要知道任务状态排序和兜底口径。
|
||||
|
||||
## 决策
|
||||
|
||||
新增 `src/components/rpg-entry/rpgEntryProfileTaskViewModel.ts`,作为每日任务展示模型 **Module**。该 **Module** 的 **Interface** 收口为:
|
||||
|
||||
- `selectProfileTaskCenterTasks(tasks)`:统一任务中心只展示一条可操作任务,按 claimable / incomplete 优先级并保持原始顺序。
|
||||
- `selectProfileTaskCardTask(tasks)`:统一任务卡兜底顺序,先可操作,再 claimed,再非 disabled。
|
||||
- `buildProfileTaskCardSummary(center)`:统一任务卡的奖励、阈值、进度百分比与动作文案。
|
||||
- `buildProfileTaskProgressLabel(task)`、`getProfileTaskStatusLabel(status)`、`getProfileTaskClaimButtonLabel(task, isClaiming)`:统一任务弹窗中的进度、状态和按钮文案。
|
||||
|
||||
`RpgEntryHomeView.tsx` 只消费这些 ViewModel 函数,保留弹窗、按钮和点击处理。每日任务展示规则的 **Locality** 转移到 ViewModel **Module** 与纯测试,后续新增任务状态或修改展示优先级不再穿透 UI。
|
||||
|
||||
## 约定
|
||||
|
||||
- 任务中心只露出当前最需要用户处理的一条任务。
|
||||
- 任务进度必须按 `0..threshold` clamp,避免异常后端进度撑破卡片进度条。
|
||||
- `pause` / `claim` 等副作用仍留在页面和后端 client;ViewModel 只做展示派生。
|
||||
|
||||
## 验证
|
||||
|
||||
- `npm run test -- src/components/rpg-entry/rpgEntryProfileTaskViewModel.test.ts`
|
||||
- `npm run typecheck`
|
||||
- `npm run check:encoding`
|
||||
- 针对变更文件执行 ESLint
|
||||
@@ -0,0 +1,40 @@
|
||||
# 【前端架构】Public Gallery ViewModel 收口计划
|
||||
|
||||
## 背景
|
||||
|
||||
`RpgEntryHomeView.tsx` 同时承担首页、发现、分类、排行、搜索和公开作品卡片渲染。公开作品的 category 分组、跨来源去重、搜索归一化、作品号匹配、时间戳解析和列表排序原本都放在页面巨型 **Implementation** 中,导致公开作品规则与 JSX 交错,新增玩法时难以判断该改页面、卡片还是平台入口规则。
|
||||
|
||||
## 决策
|
||||
|
||||
新增 `src/components/rpg-entry/rpgEntryPublicGalleryViewModel.ts`,作为公开作品 ViewModel **Module**。该 **Module** 的 **Interface** 收口为:
|
||||
|
||||
- `buildPublicGalleryCardKey(entry)`:复用平台公开作品身份规则,补齐 jump-hop / wooden-fish 等玩法 key。
|
||||
- `buildPublicCategoryGroups(featuredEntries, latestEntries)`:统一去重、标签兜底和分类排序。
|
||||
- `getPlatformPublicEntries(featuredEntries, latestEntries)` / `getAllPlatformPublicEntries(featuredEntries, latestEntries)`:统一公开作品合并规则。
|
||||
- `getPlatformSearchableWorkIds(entry)`、`filterPlatformWorkSearchResults(entries, keyword)` 与 `isExactPublicWorkCodeSearch(entries, keyword)`:统一搜索归一化、compact code 匹配和排序。
|
||||
- `parsePlatformEntryTimestamp(value)` / `getPlatformWorldTimestamp(entry)`:统一兼容 ISO 与后端 seconds.microsZ 时间戳。
|
||||
- `filterTodayPublishedEntries(entries)`:统一“今日游戏”本地自然日筛选。
|
||||
- `getPlatformWorldLikeCount(entry)` / `getPlatformWorldPlayCount(entry)` / `getPlatformWorldRemixCount(entry)`、`buildPlatformRankingEntries(entries, tab)` 与 `getPlatformRankingMetricValue(entry, tab)`:统一公开卡片指标读取、排行 Tab 排序与取值。
|
||||
- `DEFAULT_PLATFORM_CATEGORY_KIND_FILTER`、`DEFAULT_PLATFORM_CATEGORY_SORT_MODE`、`PLATFORM_CATEGORY_KIND_FILTERS`、`PLATFORM_CATEGORY_SORT_OPTIONS`、`getPlatformCategoryKindFilterOption(kindFilter)`、`getPlatformCategorySortOption(sortMode)` 与 `getNextPlatformCategorySortMode(sortMode)`:统一分类频道的筛选 / 排序选项、默认值、label 兜底和排序循环。
|
||||
- `getPlatformCategoryKindFilter(entry)`、`matchesPlatformCategoryKindFilter(entry, kindFilter)`、`sortPlatformCategoryEntries(entries, sortMode)` 与 `getPlatformCategoryPrimaryMetric(entry)`:统一分类频道的玩法过滤、排序和主指标展示。
|
||||
|
||||
`RpgEntryHomeView.tsx` 只消费这些 ViewModel 函数,保留渲染、事件处理和账号状态。公开作品规则的 **Locality** 转移到 ViewModel **Module** 与其测试,页面不再持有这批纯规则。
|
||||
|
||||
## 约定
|
||||
|
||||
- 公开作品身份 key 与平台入口推荐流保持一致,优先复用 `platformPublicGalleryFlow`。
|
||||
- 搜索应同时匹配作品号、`profileId`、`workId`、标题、作者、摘要和副标题。
|
||||
- 搜索排序先看标题前缀,再看作品号 compact 前缀,最后按发布时间 / 更新时间倒序。
|
||||
- 时间解析必须保留后端 `seconds.microsZ` 兼容。
|
||||
- 分类筛选与排序的选项顺序、默认值、中文 label 和“综合 -> 最新 -> 游玩 -> 点赞 -> 综合”循环属于 ViewModel **Interface**;页面只能消费该 **Interface**,不得在 `RpgEntryHomeView.tsx` 复写数组或 fallback 文案。
|
||||
|
||||
## 后续深化
|
||||
|
||||
下一步可把移动 / 桌面 discover feed 的数据准备继续迁入 ViewModel,但卡片 JSX 与交互状态仍留页面内。
|
||||
|
||||
## 验证
|
||||
|
||||
- `npm run test -- src/components/rpg-entry/rpgEntryPublicGalleryViewModel.test.ts`
|
||||
- `npm run typecheck`
|
||||
- `npm run check:encoding`
|
||||
- 针对变更文件执行 ESLint
|
||||
@@ -0,0 +1,31 @@
|
||||
# 【前端架构】Public Work Presentation 收口计划
|
||||
|
||||
## 背景
|
||||
|
||||
`RpgEntryHomeView.tsx` 的作品卡、推荐 runtime meta、排行项、分类项、搜索结果和桌面 hero 共用公开作品玩法类型 label 与紧凑计数格式。原先 `describePublicGalleryCardKind` 与 `formatCompactCount` 放在页面 **Implementation** 内,导致新增玩法或调整数字展示时需要穿过多段 JSX。公开作者 lookup key 与头像首字也曾由页面手写,页面既要知道公开作品作者来源优先级,又要知道 `code:` / `id:` 前缀约定。
|
||||
|
||||
## 决策
|
||||
|
||||
在 `src/components/rpg-entry/rpgEntryWorldPresentation.ts` 追加单作品展示 **Interface**:
|
||||
|
||||
- `describePlatformPublicWorkKind(entry)`:统一公开作品玩法类型 label,并继续复用 `formatPlatformWorkDisplayTag` 的 4 字截断口径。
|
||||
- `formatPlatformCompactCount(value)`:统一游玩、改造、点赞、排行和分类指标的紧凑数字展示。
|
||||
- `resolvePlatformPublicWorkAuthorLookup(entry)`:统一公开作者查询 lookup,优先使用 `authorPublicUserCode`,否则回退 `ownerUserId`,并用结构化 `{ key, source, value }` 避免页面复写前缀规则。
|
||||
- `formatPlatformPublicAuthorAvatarLabel(authorDisplayName)`:统一公开作者头像无图时的首字兜底。
|
||||
|
||||
`RpgEntryHomeView.tsx` 删除本地类型 label、紧凑计数、公开作者 lookup 与头像首字 **Implementation**,仅消费 `rpgEntryWorldPresentation.ts`。认证请求、缓存和失败兜底仍留页面侧 Adapter;集合筛选、排序和指标选择仍留在 `rpgEntryPublicGalleryViewModel.ts`,避免单作品展示 **Module** 与集合 **Module** 混杂。
|
||||
|
||||
## 约定
|
||||
|
||||
- 紧凑计数保留既有口径:`10000` 显示 `1.0万`,`100000000` 显示 `1.0亿`,一万以下不加千分位。
|
||||
- 玩法类型 label 继续遵循 4 字展示限制,例如“大鱼吃小鱼”外显为“大鱼吃小”。
|
||||
- 公开作者 lookup 的 `key` 只用于缓存索引;真正调用公开用户 Adapter 时以 `source` 和 `value` 分发,页面不得解析 `code:` / `id:` 前缀。
|
||||
- 本次不迁移排行 metric label / value 配对;该规则属于集合排序 **Module** 的后续切片。
|
||||
|
||||
## 验证
|
||||
|
||||
- `npm run test -- src/components/rpg-entry/rpgEntryWorldPresentation.test.ts`
|
||||
- `npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx -t "recommend|ranking|category"`
|
||||
- 针对变更文件执行 ESLint
|
||||
- `npm run typecheck`
|
||||
- `npm run check:encoding`
|
||||
32
docs/technical/【前端架构】RankingViewModel收口计划-2026-06-03.md
Normal file
32
docs/technical/【前端架构】RankingViewModel收口计划-2026-06-03.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# 【前端架构】Ranking ViewModel 收口计划
|
||||
|
||||
## 背景
|
||||
|
||||
平台发现页排行频道以 `PlatformRankingTab` 决定 tab 文案、空态文案、排序字段和指标展示。原先排序与指标取值在 `rpgEntryPublicGalleryViewModel.ts`,而 tab label、metric label 与 empty text 留在 `RpgEntryHomeView.tsx`,页面还用类型断言寻找 active config,导致同一个排行语义的 **Interface** 分散。
|
||||
|
||||
## 决策
|
||||
|
||||
在 `src/components/rpg-entry/rpgEntryPublicGalleryViewModel.ts` 收口排行 **Interface**:
|
||||
|
||||
- `DEFAULT_PLATFORM_RANKING_TAB` 与 `PLATFORM_RANKING_TABS`:统一 tab 顺序、tab label、metric label 与空态文案。
|
||||
- `getPlatformRankingTabConfig(tab)`:统一 active tab 配置兜底。
|
||||
- `getPlatformRankingMetric(entry, tab)`:统一 metric label 与 value,避免 label/value 漂移。
|
||||
- `buildPlatformRankingEntries(entries, tab)` 继续承载排序规则。
|
||||
|
||||
`RpgEntryHomeView.tsx` 只保留 active tab 状态、点击与渲染,不再理解“热门榜=游玩值”“新品榜=近 7 日值”等映射。排行规则的 **Locality** 收口到 PublicGallery ViewModel。
|
||||
|
||||
## 约定
|
||||
|
||||
- 默认排行 tab 保持 `hot`。
|
||||
- tab 顺序保持“热门榜 / 改造榜 / 新品榜 / 点赞榜”。
|
||||
- 排序口径保持:`hot=playCount`、`remix=remixCount`、`new=recentPlayCount7d`、`like=likeCount`。
|
||||
- “新品榜”仍按近 7 日游玩数排序,不改为发布时间排序。
|
||||
- 页面层继续保留最多显示 30 条的展示限制。
|
||||
|
||||
## 验证
|
||||
|
||||
- `npm run test -- src/components/rpg-entry/rpgEntryPublicGalleryViewModel.test.ts`
|
||||
- `npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx -t "bottom category tab becomes ranking and switches ranking metrics|ranking"`
|
||||
- 针对变更文件执行 ESLint
|
||||
- `npm run typecheck`
|
||||
- `npm run check:encoding`
|
||||
@@ -0,0 +1,31 @@
|
||||
# 【前端架构】Recommend Feed ViewModel 收口计划
|
||||
|
||||
## 背景
|
||||
|
||||
平台首页推荐 feed、发现页推荐频道、桌面推荐格和正式 runtime 的上一条 / 下一条选择共用一批展示规则:公开作品跨来源去重、过滤寓教于乐隐藏内容、按精选优先再最新兜底、active key 失效时回到首项、前后相邻条目回环且单条目不自循环。原先这些规则分别散在 `RpgEntryHomeView.tsx` 与 `PlatformEntryFlowShellImpl.tsx` 的 **Implementation** 内,导致推荐预览与正式 runtime 之间存在口径漂移风险。
|
||||
|
||||
## 决策
|
||||
|
||||
在 `src/components/rpg-entry/rpgEntryPublicGalleryViewModel.ts` 追加推荐 feed **Interface**:
|
||||
|
||||
- `dedupePlatformPublicGalleryEntries(entries)`:统一公开作品按 `buildPublicGalleryCardKey` 去重,后出现来源覆盖旧值。
|
||||
- `buildPlatformRecommendFeedEntries(featuredEntries, latestEntries)`:统一推荐 feed 的精选 + 最新合并、隐藏寓教于乐内容与去重顺序。
|
||||
- `selectPlatformRecommendFeedWindow(entries, activeEntryKey)`:统一推荐页当前项、上一项、下一项和 active key 失效兜底。
|
||||
- `selectAdjacentPlatformRecommendEntry(entries, direction, baseEntryKey)`:统一正式 runtime 上一条 / 下一条回环选择,并避免单作品自循环。
|
||||
|
||||
`RpgEntryHomeView.tsx` 不再自建 `Map` 或手写取模;`PlatformEntryFlowShellImpl.tsx` 的 runtime 推荐条目也改用同一 **Module**。推荐 feed 的 **Locality** 回到 PublicGallery ViewModel,页面与 runtime 只保留 UI、动画和启动副作用。
|
||||
|
||||
## 约定
|
||||
|
||||
- 推荐 feed 仍只展示普通公开作品;寓教于乐内容由独立频道控制,不进入推荐 runtime 队列。
|
||||
- 去重保留既有“后出现来源覆盖旧值、插入位置不变”的行为。
|
||||
- active key 缺失或失效时,展示窗口回到首个推荐作品;单个作品没有上一条 / 下一条预览。
|
||||
|
||||
## 验证
|
||||
|
||||
- `npm run test -- src/components/rpg-entry/rpgEntryPublicGalleryViewModel.test.ts`
|
||||
- `npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx -t "recommend|edutainment"`
|
||||
- `npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "logged out home recommendation next starts the next puzzle work"`
|
||||
- 针对变更文件执行 ESLint
|
||||
- `npm run typecheck`
|
||||
- `npm run check:encoding`
|
||||
@@ -0,0 +1,42 @@
|
||||
# RecommendSwipeDeckModel 收口计划
|
||||
|
||||
## 背景
|
||||
|
||||
移动端推荐首页的纵向 swipe deck 曾把拖拽阈值、offset clamp、commit 方向、rail class 和分享文案直接放在 `RpgEntryHomeView.tsx` Implementation 内。页面因此同时理解 DOM pointer 副作用、动画副作用与推荐卡纯规则,后续调整手势阈值或分享文案时缺少稳定测试面。
|
||||
|
||||
## 决策
|
||||
|
||||
- 新增 `src/components/rpg-entry/rpgEntryRecommendSwipeDeckModel.ts` 作为 Recommend Swipe Deck Module。
|
||||
- Module Interface 收口:
|
||||
- `hasRecommendDragStarted`
|
||||
- `clampRecommendDragOffset`
|
||||
- `resolveRecommendDragCommitDirection`
|
||||
- `resolveRecommendCommitOffset`
|
||||
- `buildRecommendSwipeRailClassName`
|
||||
- `shouldAnimateRecommendSwipe`
|
||||
- `buildRecommendShareText`
|
||||
- `RpgEntryHomeView.tsx` 保留 pointer capture、DOM 高度读取、`setTimeout`、clipboard、like/remix/open 等副作用 Adapter;推荐卡纯规则不再散落在页面 Implementation 内。
|
||||
|
||||
## Interface 约束
|
||||
|
||||
- swipe 阈值、commit 动画时长和 drag fallback limit 只从 Module 导出,不在页面重复定义。
|
||||
- `deltaY < 0` 表示上滑进入下一条,返回方向 `1`;`deltaY > 0` 表示下滑进入上一条,返回方向 `-1`。
|
||||
- 未达到 commit 阈值时必须返回 `null`,页面 Adapter 只负责把 offset 归零。
|
||||
- rail class 仅由 `offsetY` 与 `commitDirection` 决定,CSS class 名保持现有命名。
|
||||
- 分享文案只使用公开作品名、作品号和详情 URL;公开作品码解析与复制副作用仍在页面 Adapter。
|
||||
|
||||
## Depth / Leverage / Locality
|
||||
|
||||
- **Depth**:页面传入少量数值或公开作品身份,即可得到拖拽状态、提交方向、动画 class 和分享文案。
|
||||
- **Leverage**:调整推荐 swipe 体验时只需改 Module 与单测,交互测试仍护页面 Adapter。
|
||||
- **Locality**:pointer 事件生命周期与纯规则分离,推荐卡手势和分享规则集中到一个小 Module。
|
||||
|
||||
## 验收
|
||||
|
||||
- `npm run test -- src/components/rpg-entry/rpgEntryRecommendSwipeDeckModel.test.ts`
|
||||
- `npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx -t "recommend|edutainment"`
|
||||
- `npm run test -- src/components/rpg-entry/rpgEntryPublicGalleryViewModel.test.ts -t "recommend"`
|
||||
- `npx eslint src/components/rpg-entry/rpgEntryRecommendSwipeDeckModel.ts src/components/rpg-entry/rpgEntryRecommendSwipeDeckModel.test.ts --max-warnings 0`
|
||||
- `npx eslint src/components/rpg-entry/RpgEntryHomeView.tsx --quiet`
|
||||
- `npm run typecheck`
|
||||
- `npm run check:encoding`
|
||||
32
docs/technical/【前端架构】RuntimeClientFamily收口计划-2026-06-03.md
Normal file
32
docs/technical/【前端架构】RuntimeClientFamily收口计划-2026-06-03.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# 【前端架构】Runtime Client Family 收口计划
|
||||
|
||||
## 背景
|
||||
|
||||
多个小游戏 runtime client 都重复实现路径编码、JSON header / body、runtime guest token、认证影响策略和重试参数。重复逻辑分散在各玩法文件后,新增玩法容易遗漏 guest auth 或 retry 语义,也让测试必须逐玩法检查同一请求骨架。
|
||||
|
||||
## 决策
|
||||
|
||||
新增 `src/services/runtimeRequest.ts`,作为 Runtime Client Family 的请求 **Module**。其 **Interface** 包含:
|
||||
|
||||
- `buildRuntimeApiPath(basePath, ...segments)`:统一对 runtime path segment 执行 `encodeURIComponent`。
|
||||
- `requestRuntimeJson(params)`:统一设置 method、JSON body、`Content-Type`、runtime guest `Authorization`、auth options 和 retry options。
|
||||
|
||||
`match3dRuntimeClient.ts`、`squareHoleRuntimeClient.ts`、`bigFishRuntimeClient.ts`、`barkBattleRuntimeClient.ts`、`puzzleRuntimeClient.ts` 的公开 / 推荐运行态请求、`jumpHopClient.ts` 与 `woodenFishClient.ts` 的正式 run 请求,以及 `visualNovelRuntimeClient.ts` 的公开列表、run 读取、history 读取和 regenerate JSON 请求已迁入此 **Module**,并保留原有导出函数名、错误文案、返回契约和重试常量。点击 / 投入 / 成绩提交等玩法专属 payload 归一化仍留在各自 client 内,避免把领域规则塞进通用请求 **Implementation**。
|
||||
|
||||
## 约定
|
||||
|
||||
- Runtime client 不再手写 `encodeURIComponent` 拼 path;应优先使用 `buildRuntimeApiPath`。
|
||||
- Runtime JSON 请求不再手写 `Content-Type`、guest `Authorization` 和 `buildRuntimeGuestAuthOptions` 合并;应优先使用 `requestRuntimeJson`。
|
||||
- 玩法专属 payload 归一化、返回值适配和中文错误文案仍属于各玩法 client。
|
||||
- 每迁移一个 client,必须保留原导出函数名与原调用方契约。
|
||||
|
||||
## 后续深化
|
||||
|
||||
下一批可评估是否扩展 `requestRuntimeJson` 支持 `timeoutMs` / `signal`,再迁移 Visual Novel start 请求;Visual Novel SSE、平台存档、平台 checkpoint,以及 Puzzle `pause` / `props` 继续保留各自现有 auth / stream 语义,暂不纳入通用 JSON helper。
|
||||
|
||||
## 验证
|
||||
|
||||
- `npm run test -- src/services/runtimeRequest.test.ts src/services/recommendedRuntimeGuestLaunch.test.ts src/services/match3d-runtime/match3dRuntimeAdapter.test.ts`
|
||||
- `npm run typecheck`
|
||||
- `npm run check:encoding`
|
||||
- 针对变更文件执行 ESLint
|
||||
36
docs/technical/【前端架构】SSE客户端传输层收口约定-2026-06-03.md
Normal file
36
docs/technical/【前端架构】SSE客户端传输层收口约定-2026-06-03.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# SSE 客户端传输层收口约定
|
||||
|
||||
更新时间:`2026-06-03`
|
||||
|
||||
## 背景
|
||||
|
||||
前端多个服务 client 需要读取 Server-Sent Events,包括创作 Agent、创意互动 Agent、视觉小说运行态和微信充值订单状态。旧实现分别在各自文件里手写事件边界查找、`TextDecoder` 解码、JSON 解析和流结束 flush,容易出现 CRLF / LF 边界不一致、UTF-8 多字节字符尾部丢失、错误事件处理漂移,以及长连接达到最终状态后没有及时释放的问题。
|
||||
|
||||
## 决策
|
||||
|
||||
前端 SSE 的传输层统一收口到 `src/services/sseStream.ts`:
|
||||
|
||||
- `readSseStream` 负责读取 `Response.body`、识别 `\n\n` 与 `\r\n\r\n` 事件边界、合并多行 `data:`、flush `TextDecoder` 尾部缓冲,并支持事件处理函数返回 `false` 后取消 reader。
|
||||
- `readSseJsonStream` 只在传输事件基础上解析 JSON object,空 data 与异常 JSON 继续按旧口径静默跳过。
|
||||
- 各业务 client 只保留领域事件归一化、最终结果聚合和中文错误文案,不再重复实现 SSE 边界扫描、reader 循环或 UTF-8 flush。
|
||||
- OpenAI 兼容流、`[DONE]` 哨兵或其它非 JSON SSE 可直接使用 `readSseStream`;业务 JSON 事件优先使用 `readSseJsonStream`。
|
||||
|
||||
## 落地范围
|
||||
|
||||
本次先收口以下客户端:
|
||||
|
||||
- `src/services/aiService.ts`
|
||||
- `src/services/creation-agent/creationAgentSse.ts`
|
||||
- `src/services/creative-agent/creativeAgentSse.ts`
|
||||
- `src/services/visual-novel-runtime/visualNovelRuntimeSse.ts`
|
||||
- `src/services/rpg-entry/rpgProfileClient.ts`
|
||||
- `src/services/llmClient.ts`
|
||||
|
||||
后续新增 SSE client 时不得复制 `findSseEventBoundary`、`parseSseEventBlock` 或手写 reader 循环;若确实需要特殊 framing,应先扩展 `sseStream.ts` 的传输能力,再在业务 client 中处理领域语义。
|
||||
|
||||
## 验收
|
||||
|
||||
- `src/services/sseStream.test.ts` 覆盖 CRLF / LF 边界、UTF-8 尾部 flush、异常 JSON 跳过和提前停止取消 reader。
|
||||
- `src/services/llmClient.test.ts` 覆盖 OpenAI 兼容文本流、异常 JSON 跳过和 `[DONE]` 后提前停止。
|
||||
- 已有 OpenAI 兼容文本流、NPC 聊天流、创作 Agent、创意互动 Agent、视觉小说运行态和充值订单状态测试继续通过。
|
||||
- `npm run typecheck` 不产生新的类型错误。
|
||||
34
docs/technical/【前端架构】WorkShelfModule收口计划-2026-06-03.md
Normal file
34
docs/technical/【前端架构】WorkShelfModule收口计划-2026-06-03.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# 【前端架构】Work Shelf Module 收口计划
|
||||
|
||||
## 背景
|
||||
|
||||
创作中心作品架需要同时展示 RPG、拼图、抓大鹅、方洞、跳一跳、敲木鱼、视觉小说、Bark Battle 和宝贝识物等作品。`creationWorkShelf.ts` 已经统一了卡片标题、摘要、封面、发布码、分享路径、指标、生成态和动作 Adapter。后续深化前,`CustomWorldCreationHub.tsx` 虽已不再按玩法 `kind` 分发点击,但生产调用仍向 Hub 传入多玩法 raw items 与 open/delete/claim 回调列阵,Hub Interface 仍偏 shallow。
|
||||
|
||||
## 决策
|
||||
|
||||
`CreationWorkShelfItem.actions.open` 是打开作品的正式 **Interface**。`CustomWorldCreationHub.tsx` 只负责卡片点击与 `onOpenShelfItem` 通知,然后调用 `item.actions.open()`,不再根据 `item.source.kind` 分发玩法。
|
||||
|
||||
`buildCreationWorkShelfItemsFromSources` 是作品架 source registry 的正式 **Interface**。每个玩法提供一个 `CreationWorkShelfSourceAdapter`,Adapter 负责把玩法数据、删除权限、打开动作和特殊动作映射为 `CreationWorkShelfItem[]`。registry 统一执行 flatten、运行态覆盖、持久化生成态兜底和更新时间排序。
|
||||
|
||||
`CustomWorldCreationHub.tsx` 的生产 **Interface** 收敛为 `shelfItems: CreationWorkShelfItem[]` 加 `loading/error/onRetry/mode/recentWorkItems/onOpenShelfItem/deletingWorkId/claimingPuzzleProfileId` 等 UI 状态。平台壳 `PlatformEntryFlowShellImpl.tsx` 在外层作为 Adapter 调用 `buildCreationWorkShelfItems` 注入完整 open/delete/claim actions 后再传给 Hub;Hub 不再接触各玩法 raw items、删除权限布尔值或玩法专属打开回调。
|
||||
|
||||
测试文件通过 `CustomWorldCreationHub.testAdapter.tsx` 把旧 fixture 转成 `shelfItems`,避免测试继续强化生产 Hub 的旧浅 Interface。
|
||||
|
||||
此决策让 `creationWorkShelf.ts` 的 **Module** 更 deep:
|
||||
|
||||
- **Implementation**:玩法差异、草稿 / 已发布分支、profileId 进入方式和回调绑定都留在 Work Shelf Adapter 内。
|
||||
- **Interface**:Hub 只需要 `CreationWorkShelfItem[]`;后续调用方也可只传 `CreationWorkShelfSourceAdapter[]`,不需要知道每种玩法的打开规则、状态覆盖和排序规则。
|
||||
- **Leverage**:新增玩法时只补 shelf item 映射与 Adapter,Hub 不再新增 switch 分支。
|
||||
- **Locality**:作品架点击行为、source flatten、运行态覆盖和排序错误集中在 `creationWorkShelf.ts` 与其测试里定位。
|
||||
|
||||
## 后续深化
|
||||
|
||||
`buildCreationWorkShelfItems` 仍保留旧长参数兼容入口,但其 **Implementation** 已改为组装 `CreationWorkShelfSourceAdapter[]` 后复用 `buildCreationWorkShelfItemsFromSources`。下一步可让平台壳直接传入 source adapters,从而继续减少按玩法平铺的参数数量。`deletingWorkId` 与 `claimingPuzzleProfileId` 仍是 Hub UI 状态,可后续下沉到 shelf item/action busy state。
|
||||
|
||||
## 验证
|
||||
|
||||
- `npm run test -- src/components/custom-world-home/creationWorkShelf.test.ts src/components/custom-world-home/CustomWorldCreationHub.test.tsx src/components/custom-world-home/CustomWorldCreationHub.interaction.test.tsx`
|
||||
- `npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "creation hub published work can open detail view before deleting from detail page|creation hub published work enters existing detail view|creation hub published work card reveals delete action after card action reveal"`
|
||||
- `npm run typecheck`
|
||||
- `npm run check:encoding`
|
||||
- 针对变更文件执行 ESLint
|
||||
@@ -0,0 +1,56 @@
|
||||
# 【前端架构】平台入口 Public Gallery Flow Module 收口计划
|
||||
|
||||
## 背景
|
||||
|
||||
`PlatformEntryFlowShellImpl.tsx` 同时承载平台入口、推荐流、公开作品详情、运行态启动和作品架刷新。公开作品列表中的身份识别、跨玩法去重、时间排序和推荐运行态类型判定原本散落在入口巨型实现中,后续每新增一种玩法都需要在巨型文件内追加判断,影响前端架构的复用、统一和扩展。
|
||||
|
||||
## 决策
|
||||
|
||||
新增 `src/components/platform-entry/platformPublicGalleryFlow.ts`,作为平台入口公开作品流的 **Module**。该 Module 的 **Interface** 固定收口为:
|
||||
|
||||
- `getPlatformPublicGalleryEntryKey(entry)`:按玩法类型、作者和 `profileId` 生成公开作品身份。
|
||||
- `getPlatformRecommendRuntimeKind(entry)`:把公开作品卡映射为推荐运行态 kind。
|
||||
- `resolvePlatformRecommendRuntimeStartIntent(entry, deps)`:把公开作品卡映射为推荐 runtime 启动意图、错误落点和 embedded / returnStage 参数。
|
||||
- `isPlatformRecommendRuntimeReadyForEntry(entry, state)`:用标量 ready state 判定当前推荐 runtime 是否已能承接该公开作品。
|
||||
- `isSamePlatformPublicGalleryEntry(left, right)`:按公开作品身份比较。
|
||||
- `mergePlatformPublicGalleryEntries(rpgEntries, puzzleEntries)`:统一完成 RPG 与各玩法公开作品去重、覆盖和倒序排序。
|
||||
- `buildPlatformPublicGalleryFeeds(input)`:统一构造 `featuredEntries` 与 `latestEntries`,收口各玩法可见性 gate、mapper 矩阵、汪汪声浪 works fallback 和推荐首屏 `slice(0, 6)`。
|
||||
|
||||
入口壳层只调用这些函数,不再在 `PlatformEntryFlowShellImpl.tsx` 内手写公开作品身份、排序规则、公开作品流聚合矩阵、推荐 runtime 启动能力矩阵和 ready 判定。ready 判定只接布尔值与拼图 profile id,不把各玩法 run snapshot 类型拖入 Module。
|
||||
|
||||
## 玩法身份规则
|
||||
|
||||
- `big-fish`、`puzzle`、`jump-hop`、`wooden-fish`、`match3d`、`square-hole`、`visual-novel`、`bark-battle` 使用自身 `sourceType` 作为 key kind。
|
||||
- `edutainment` 使用 `edutainment:${templateId}` 作为 key kind,避免后续幼教类模板共用 `sourceType` 时互相覆盖。
|
||||
- 没有 `sourceType` 的 RPG 公开作品回退为 `rpg`。
|
||||
- 最终 key 格式为 `${kind}:${ownerUserId}:${profileId}`。
|
||||
- 合并时后进入的相同 key 会覆盖先进入的卡片,然后按 `publishedAt ?? updatedAt` 新到旧排序;非法时间按 `0` 处理。
|
||||
- 公开作品流聚合时,大鱼吃小鱼、宝贝识物和视觉小说必须受各自可见性 gate 控制;汪汪声浪优先用 gallery entries,gallery 为空时才从 works 中筛 `status === 'published'` 作为 fallback。
|
||||
|
||||
## 推荐 runtime 启动意图
|
||||
|
||||
- `resolvePlatformRecommendRuntimeStartIntent` 只表达推荐 runtime 的启动目标,不执行鉴权、运行态 API、错误 setter、缓存、request key 或 UI 状态更新。
|
||||
- 大鱼吃小鱼、拼图、跳一跳、敲木鱼、抓大鹅、方洞挑战、视觉小说、汪汪声浪和宝贝识物返回对应启动 intent;RPG 维持当前无嵌入 runtime 的 `mark-ready` 行为。
|
||||
- 大鱼吃小鱼、拼图、抓大鹅、方洞挑战和汪汪声浪在公开卡无法拼出启动 work 时返回 `blocked`,同时给出 `errorTarget`,由壳层 Adapter 分发到对应玩法错误 setter。
|
||||
- 拼图优先使用同 `profileId` 的 `selectedPuzzleDetail`,否则从公开卡映射兼容 work 摘要。
|
||||
- 抓大鹅 public detail -> work mapper 必须作为 Adapter 注入,继续由 Match3D Runtime Profile Module 维护 `generatedItemAssets` 归一化与背景资产提升。推荐 runtime 固定沿用旧参数 `returnStage = 'work-detail'` 与 `embedded = true`。
|
||||
- 汪汪声浪优先使用推荐流已持有的 `barkBattleGalleryEntries`,再回退公开卡映射;不额外读取作品架列表。
|
||||
|
||||
## 推荐 runtime ready 判定
|
||||
|
||||
- `isPlatformRecommendRuntimeReadyForEntry` 先要求 `state.activeKind` 与当前公开作品的 `getPlatformRecommendRuntimeKind(entry)` 相同,否则返回 `false`。
|
||||
- 大鱼吃小鱼、跳一跳、敲木鱼、抓大鹅、方洞挑战和视觉小说只看对应 `has*Run` 布尔值,保持旧行为,不在本 Module 内解析 run snapshot。
|
||||
- 拼图只看 `puzzleRunEntryProfileId` 或 `puzzleRunCurrentLevelProfileId` 是否等于当前公开作品 `profileId`。
|
||||
- 汪汪声浪和 RPG 在 kind 匹配时沿用旧 `ready = true` 行为;宝贝识物只看 `hasBabyObjectMatchDraft`。
|
||||
- 若未来要修正同玩法旧 run 误判或 RPG 无嵌入 runtime 的旧行为,应另立行为变更任务;本 Module 先只收口现有规则。
|
||||
|
||||
## 后续深化
|
||||
|
||||
下一步可继续把平台入口的作品架刷新、删除确认和直达恢复逻辑收口成更深的 Work Shelf **Module**。当前 `platformPublicGalleryFlow` 先提供一个稳定 seam,使公开作品 identity、runtime kind、推荐 runtime 启动意图与 ready 判定的修改集中在一处。
|
||||
|
||||
## 验证
|
||||
|
||||
- `npm run test -- src/components/platform-entry/platformPublicGalleryFlow.test.ts`
|
||||
- `npm run typecheck`
|
||||
- `npm run check:encoding`
|
||||
- 针对变更文件执行 ESLint
|
||||
@@ -0,0 +1,38 @@
|
||||
# 【后端架构】Puzzle Publish Asset Gate 收紧计划
|
||||
|
||||
## 背景
|
||||
|
||||
拼图前端恢复链路已由 `platformPuzzleDraftRecoveryModel.ts` 收紧:只有首图、关卡画面、UI spritesheet 与关卡背景资产包完整时,才把恢复草稿抬为完成态。但后端仍有两处待发布门槛偏弱:
|
||||
|
||||
- `module-puzzle::validate_publish_requirements(...)` 只校验作品名、描述、标签、关卡名与 cover。
|
||||
- `api-server::puzzle::tags::is_puzzle_session_snapshot_publish_ready(...)` 也只校验同一组轻字段,并据此把 session stage 置为 `ready_to_publish`。
|
||||
|
||||
这会让只有首图但缺关卡正式画面、UI spritesheet 或关卡背景的半成品显示为可发布或进入待发布 stage。
|
||||
|
||||
## 决策
|
||||
|
||||
后端拼图待发布门槛统一收紧到完整首关资产包:
|
||||
|
||||
- `module-puzzle` 在 `validate_publish_requirements` 中新增资产 blocker,业务规则继续留在领域模块。
|
||||
- `api-server` 的 session snapshot ready 判定复用同一资产语言,避免标签生成后把半成品 session stage 改成 `ready_to_publish`。
|
||||
- 本切片不改 SpacetimeDB schema、不改 DTO 字段、不改路由、不改计费和发布动作副作用。
|
||||
|
||||
## 接口约束
|
||||
|
||||
- 仍保留既有作品名、描述、标签数量、关卡名、cover 校验。
|
||||
- 每个关卡必须具备:
|
||||
- `cover_image_src`;
|
||||
- `level_scene_image_src` 或 `level_scene_image_object_key`;
|
||||
- `ui_spritesheet_image_src` 或 `ui_spritesheet_image_object_key`;
|
||||
- `level_background_image_src` 或 `level_background_image_object_key`。
|
||||
- 缺正式关卡画面、UI spritesheet、关卡背景时,各自输出明确 blocker。
|
||||
- `build_result_preview(...).publish_ready` 与 `is_puzzle_session_snapshot_publish_ready(...)` 必须在同一类缺资产草稿上返回 false。
|
||||
- `api-server` 从 action payload 构造 fallback session 时,缺资产 levels snapshot 应停留 `image_refining`,不得进入 `ready_to_publish`。
|
||||
|
||||
## 验收
|
||||
|
||||
- `cargo test -p module-puzzle --manifest-path server-rs/Cargo.toml validate_publish_requirements`
|
||||
- `cargo test -p api-server --manifest-path server-rs/Cargo.toml puzzle_image_generation`
|
||||
- `npm run check:encoding`
|
||||
- `git diff --check`
|
||||
- 修改后按仓规尝试 `npm run api-server` 拉起后端并确认 `/healthz`。
|
||||
@@ -194,7 +194,7 @@ cargo test -p api-server app --manifest-path server-rs/Cargo.toml
|
||||
|
||||
后续建议继续拆分:
|
||||
|
||||
- `match3d`: `draft.rs`、`background_and_cover.rs`、`material_sheet.rs`、`apimart_image.rs`。
|
||||
- `match3d`: `draft.rs`、`background_and_cover.rs`、`material_sheet.rs`。
|
||||
- `puzzle`: `session_form.rs`、`draft_compile.rs`、`image_provider.rs`、`errors.rs`。
|
||||
- `custom_world`: `publish_gate.rs`、`foundation_job.rs`、`foundation_assets.rs`、`errors.rs`。
|
||||
- `square_hole`: `config.rs`、`errors.rs`。
|
||||
|
||||
@@ -215,7 +215,7 @@ Handler 主要在 `story.rs`、`combat.rs`、`runtime_inventory.rs`:
|
||||
| Square Hole 图片重生成 | OpenAI/VectorEngine GPT image helper | URL 下载或 base64/data URL 解码 | `LegacyAssetPrefix::SquareHoleAssets` | 方洞作品图片槽位相关 kind | profile/work + image slot | 调用方包裹 | 生成成功但入库失败保留 Data URL 回包 |
|
||||
| Custom World 场景/封面 | VectorEngine GPT image 2 / OpenAI helper | URL 下载或 base64 解码 | `LegacyAssetPrefix::CustomWorldScenes` 等 | scene/cover/opening storyboard | `custom_world_profile` 或 profile/landmark/scene slot | `custom_world_ai.rs` 调用方包裹 | entity/scene 生成存在 LLM fallback;资产持久化失败按当前错误口径返回 |
|
||||
| Puzzle 图片 | GPT image 2 generations/edits | 无参考图 JSON 创建;有参考图 multipart 编辑;base64/URL 结果归一 | `LegacyAssetPrefix::PuzzleAssets` | puzzle level/background/generated image,另有 `puzzle_background_music` | puzzle profile/run/level slot | `puzzle.rs` 调用方包裹 | connectivity 可按既有规则跳过部分计费;运行态 fallback 保持原逻辑 |
|
||||
| Match3D 图片 | APIMart/VectorEngine/OpenAI image helper | 下载、切图、透明化、校准后入库 | `LegacyAssetPrefix::Match3DAssets` | cover/background/item material sheet,音频 kind 另列 | match3d profile/session slot | `match3d.rs` 调用方包裹 | 新草稿不回退 Rodin/GLB;部分连接错误按现有计费跳过规则处理 |
|
||||
| Match3D 图片 | VectorEngine/OpenAI image helper | 下载、切图、透明化、校准后入库 | `LegacyAssetPrefix::Match3DAssets` | cover/background/item material sheet,音频 kind 另列 | match3d profile/session slot | `match3d.rs` 调用方包裹 | 新草稿不回退 Rodin/GLB;部分连接错误按现有计费跳过规则处理 |
|
||||
| Visual Novel 音频 | VectorEngine Suno/Vidu | 任务提交后按 task publish 下载音频 | 视觉小说/creation audio scope | `visual_novel_music`、`visual_novel_ambient_sound` | `visual_novel_scene` + scene id + `music`/`ambient_sound` | `vector_engine_audio_generation.rs` 调用方包裹 | 上游/下载失败显式错误,不混入图片 Adapter |
|
||||
| 通用音频 | VectorEngine Suno/Vidu | 同上 | creation audio scope | background_music/sound_effect 由调用方目标指定 | creation target entity/slot | 调用方包裹 | 不与 VN 场景语义混用 |
|
||||
| 视频 Opening CG | Ark/火山视频 + storyboard | 先生 storyboard,再图生视频,下载 remote video | Custom World 相关 prefix | `custom_world_opening_cg_storyboard`、`custom_world_opening_cg_video` | `custom_world_profile` + opening cg slots | `execute_billable_asset_operation_with_cost` 固定点数 | 配置缺失/超时显式错误,不应静默降级 |
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
- `GET /admin/api/works/visibility`
|
||||
- `POST /admin/api/works/visibility`
|
||||
|
||||
后台操作 key 使用统一的 `sourceType + profileId` 组合。`profileId` 在大多数玩法中对应作品 profile;特殊玩法维持既有源表身份:`big-fish` 对应 `session_id`,`bark-battle` 对应 `work_id`。`custom-world` 更新源表时必须同步 `custom_world_gallery_entry.visible`,避免兼容 gallery 缓存与统一公开 read model 出现可见性漂移。
|
||||
后台操作 key 使用统一的 `sourceType + profileId` 组合。当前后端统一可见性管理覆盖 `puzzle`、`puzzle-clear`、`custom-world`、`jump-hop`、`wooden-fish`、`match3d`、`square-hole`、`visual-novel`、`big-fish` 和 `bark-battle`;`edutainment` 当前没有后端统一作品源表,暂不接入该后台能力。`profileId` 在大多数玩法中对应作品 profile;特殊玩法维持既有源表身份:`big-fish` 对应 `session_id`,`bark-battle` 对应 `work_id`。`custom-world` 更新源表时必须同步 `custom_world_gallery_entry.visible`,避免兼容 gallery 缓存与统一公开 read model 出现可见性漂移。
|
||||
|
||||
该后台能力只修改源表 / source view 过滤事实,不把 `visible` 暴露到公开列表或公开详情契约。隐藏作品后,统一 `public_work_gallery_entry` 与 `public_work_detail_entry` 不再返回该作品;恢复显示后重新进入公开 read model。
|
||||
|
||||
@@ -77,6 +77,7 @@
|
||||
- 旧 view 退到底层 source / 兼容职责。
|
||||
- 新 `public_work_*` view 是 `api-server` 公开列表 / 详情的统一主读模型。
|
||||
- 各玩法 source view 只暴露 `visible=true` 的已发布作品;旧数据迁移默认补 `visible=true`,避免历史作品被误隐藏。
|
||||
- RPG / 自定义世界旧数据可能缺少 `published_at`。统一公开详情可以用 `updated_at` 作为展示和排序兜底;点赞、游玩、Remix 等写入路径也必须按 `publication_status=Published + visible=true + 未删除` 判断作品存在,不能额外要求 `published_at` 非空。
|
||||
- 临时运行约束:SpacetimeDB 2.2 下抓大鹅 `match_3_d_gallery_view` 的 `publication_status` 索引过滤在源表更新触发统一 view 刷新时可能初始化 panic;为避免后台隐藏作品打爆 module instance,统一 `public_work_*` view 暂不级联抓大鹅 source view,抓大鹅公开入口先保留玩法专用路径。后续应以 source projection 表替代索引 view 后再重新并入统一 read model。
|
||||
- 旧 `/api/runtime/<play>/gallery` 响应 shape 保持兼容,由 BFF mapper 把统一 cache 再映射回当前 DTO。
|
||||
- 旧详情 / runtime / 点赞 / 游玩 / Remix 仍走玩法专用路径。
|
||||
|
||||
81
docs/test-cases/【测试用例】敲木鱼音频延迟上传与本地标准化-2026-06-06.md
Normal file
81
docs/test-cases/【测试用例】敲木鱼音频延迟上传与本地标准化-2026-06-06.md
Normal file
@@ -0,0 +1,81 @@
|
||||
# 敲木鱼音频延迟上传与本地标准化测试用例
|
||||
|
||||
## 覆盖目标
|
||||
|
||||
- 选择上传或录音结束后只在浏览器本地处理,不请求 OSS 上传凭证。
|
||||
- 用户点击 `生成` 时才上传处理后的音频 Blob/File,并把确认后的 `WoodenFishAudioAsset` 放入创建 session payload。
|
||||
- 上传和录音统一执行前后声音过小片段裁切、最长 1 秒限制、近似 `-15 LKFS` 响度平衡和峰值保护。
|
||||
- 音频面板明确显示 `最长 1 秒`,并正确处理上传、录音、重置、禁用和错误状态。
|
||||
- OSS 上传 client 只接收 Blob/File,不接受 Data URL,并覆盖上传凭证、OSS POST、资产确认和错误分支。
|
||||
|
||||
## 音频处理 helper
|
||||
|
||||
- 空文件:`size=0`,报 `音频文件为空,请重新选择。`
|
||||
- 非音频 MIME:`text/plain`,报 `请选择音频文件。`
|
||||
- 浏览器没有 `AudioContext`:报 `当前浏览器不支持音频处理。`
|
||||
- `decodeAudioData` 失败:报 `音频解码失败,请重新选择。`
|
||||
- 全静音或声音全低于阈值:报 `音频声音过小,请重新录制或上传。`
|
||||
- 前后静音裁切:低于阈值的头尾帧被裁掉,`startFrame` 和 `frameCount` 正确。
|
||||
- 裁切后刚好 `1000ms`:允许通过。
|
||||
- 裁切后超过 `1000ms`:报 `音频最长 1 秒。`
|
||||
- 上传来源 `uploaded` 与录音来源 `recorded`:返回 pending asset 保留对应 source。
|
||||
- 原文件名有扩展名:输出 `.wav` 文件名;无扩展名补 `.wav`;空白文件名输出 `creative-audio.wav`。
|
||||
- `URL.createObjectURL` 存在:`audioSrc` / `previewUrl` 为 blob URL;不存在时返回空字符串且不阻断处理。
|
||||
- 近似响度平衡:低 RMS 样本被拉向 `-15 LKFS` 目标。
|
||||
- 峰值保护:高峰值样本增益后不超过 `peakCeiling`。
|
||||
- 零能量 section:归一化阶段报声音过小。
|
||||
- WAV 编码:写入 RIFF/WAVE/data header、PCM16 数据长度和采样值。
|
||||
|
||||
## 音频输入面板
|
||||
|
||||
- 传入 `limitLabel` 时显示 `最长 1 秒`;未传入时不显示限制标签。
|
||||
- 无资产时显示默认音效文案。
|
||||
- 有资产且 `audioSrc` 存在时渲染 `<audio controls>`。
|
||||
- 有资产但无 `audioSrc` 时显示 `音效已选择`。
|
||||
- 点击重置调用 `onAssetChange(null)`。
|
||||
- 上传取消选择时不读取音频、不写入资产。
|
||||
- 上传成功后调用 `readFileAsAsset(file, 'uploaded')`,清空错误并写入资产。
|
||||
- 上传失败时展示错误,不写入资产。
|
||||
- 浏览器不支持录音时提示 `当前浏览器不支持录音。`
|
||||
- 麦克风启动失败时透传启动错误。
|
||||
- 录音停止后把 Blob 包成 File,并以 `recorded` 来源读取。
|
||||
- 录音保存失败时展示错误。
|
||||
- disabled 状态不启动录音,文件输入禁用。
|
||||
|
||||
## 木鱼工作台链路
|
||||
|
||||
- 音频面板显示 `最长 1 秒`,并只保留上传和录音入口。
|
||||
- 选择上传音频后只调用本地处理 helper,不调用 `uploadWoodenFishHitSoundAsset`。
|
||||
- 点击 `生成` 且有 pending 音频时,先上传处理后的 WAV,再调用 `woodenFishClient.createSession`。
|
||||
- 上传给 OSS 的文件是处理后的 WAV,文件名和 MIME 为 `.wav` / `audio/wav`。
|
||||
- 提交 payload 使用 OSS confirmed asset,不包含 `data:audio`。
|
||||
- 未选择音频时不上传 OSS,payload 使用默认木鱼音。
|
||||
- 处理阶段报超过 1 秒时展示错误,不写入用户音频;继续生成时走默认木鱼音。
|
||||
- OSS 上传失败时停留工作台,展示错误,不创建 session。
|
||||
- `createSession` 失败时停留工作台,展示错误。
|
||||
- 提交中重复点击 `生成` 不重复上传、不重复创建 session。
|
||||
- 替换本地音频时回收旧 `previewUrl`。
|
||||
|
||||
## 木鱼音频上传 client
|
||||
|
||||
- 空 Blob/File、超过 20MB、非音频 MIME 均在本地拒绝,不创建上传凭证。
|
||||
- File 上传默认使用 `file.name` 和 `file.type`。
|
||||
- Blob 上传支持通过显式文件名扩展推断 `audio/wav` 等音频 MIME。
|
||||
- Blob 缺少 MIME 且扩展未知时拒绝上传。
|
||||
- direct upload ticket 请求包含 `legacyPrefix`、path segments、fileName、contentType、access、maxSizeBytes 和木鱼音频 metadata。
|
||||
- OSS POST 成功后调用 `/api/assets/objects/confirm`。
|
||||
- OSS POST 非 2xx 时提示 `上传敲击音效失败。`,不确认资产对象。
|
||||
- confirm 失败时透传确认错误。
|
||||
- confirm 请求包含 bucket、objectKey、contentType、contentLength、assetKind、accessPolicy 和 entityId。
|
||||
- 成功返回的 `WoodenFishAudioAsset` 包含 assetObjectId、audioObjectKey、audioSrc、source 和 prompt。
|
||||
|
||||
## 验证命令
|
||||
|
||||
```bash
|
||||
npm run test -- src/components/common/creativeAudioProcessing.test.ts
|
||||
npm run test -- src/components/common/CreativeAudioInputPanel.test.tsx
|
||||
npm run test -- src/components/unified-creation/workspaces/WoodenFishCreationWorkspace.test.tsx
|
||||
npm run test -- src/services/wooden-fish/woodenFishAssetClient.test.ts
|
||||
npm run typecheck
|
||||
npm run check:encoding
|
||||
```
|
||||
@@ -1,6 +1,6 @@
|
||||
# server-rs 与 SpacetimeDB 数据契约
|
||||
|
||||
更新时间:`2026-05-15`
|
||||
更新时间:`2026-06-10`
|
||||
|
||||
## 后端主线
|
||||
|
||||
@@ -16,13 +16,13 @@ server-rs + Axum + SpacetimeDB
|
||||
|
||||
`server-rs/Cargo.toml` 是 workspace 事实源。默认构建成员为 `crates/api-server`;第三方依赖版本和 workspace 内 crate path 统一放在 `[workspace.dependencies]`。
|
||||
|
||||
SpacetimeDB 版本口径:当前 Rust crate `spacetimedb`、`spacetimedb-sdk`、`spacetimedb-lib` 统一锁定 `2.3.0`;本地 `spacetime` CLI / standalone、生成的 `spacetime-client` bindings 和容器压测镜像也必须与 `2.3.0` 对齐,避免 BSATN / procedure result 反序列化错配。
|
||||
SpacetimeDB 版本口径:当前 Rust crate `spacetimedb`、`spacetimedb-sdk`、`spacetimedb-lib` 统一锁定 `2.4.1`;本地 `spacetime` CLI / standalone、生成的 `spacetime-client` bindings 和容器压测镜像也必须与 `2.4.1` 对齐,避免 BSATN / procedure result 反序列化错配。
|
||||
|
||||
当前主要 crate:
|
||||
|
||||
- HTTP 服务:`api-server`。
|
||||
- 领域模块:`module-ai`、`module-assets`、`module-auth`、`module-bark-battle`、`module-big-fish`、`module-combat`、`module-creative-agent`、`module-custom-world`、`module-inventory`、`module-match3d`、`module-npc`、`module-progression`、`module-puzzle`、`module-quest`、`module-runtime`、`module-runtime-item`、`module-runtime-story`、`module-square-hole`、`module-story`、`module-visual-novel`。
|
||||
- 平台副作用:`platform-agent`、`platform-auth`、`platform-image`、`platform-llm`、`platform-oss`、`platform-speech`。
|
||||
- 平台副作用:`platform-agent`、`platform-auth`、`platform-image`、`platform-llm`、`platform-oss`、`platform-wechat`、`platform-speech`。
|
||||
- 共享层:`shared-contracts`、`shared-kernel`、`shared-logging`。
|
||||
- SpacetimeDB:`spacetime-client`、`spacetime-module`。
|
||||
- 测试支撑:`tests-support`。
|
||||
@@ -35,6 +35,7 @@ SpacetimeDB 版本口径:当前 Rust crate `spacetimedb`、`spacetimedb-sdk`
|
||||
4. 后端访问 SpacetimeDB 必须经 `spacetime-client` facade。
|
||||
5. HTTP 鉴权、BFF 聚合、SSE、外部模型编排、OSS 上传和第三方回调在 `api-server`。
|
||||
6. 前端共享 DTO 通过 `shared-contracts` 和 `packages/shared` 对齐,不在页面内重新发明旧接口。
|
||||
7. 微信能力按两层收口:`server-rs/crates/platform-wechat` 承载微信协议 client、订阅消息 `stable_token` / `subscribeMessage.send`、微信支付 V3 / 虚拟支付消息推送的 HTTP header、签名、验签、解密、mock 响应和协议 payload 解析;`server-rs/crates/api-server/src/wechat.rs` 与 `wechat/*` 承载 Axum handler、AppConfig 到平台配置的映射、Genarrative 用户 / 订单 / 钱包 / SSE / 错误 envelope 编排。`platform-auth` 当前仍承载微信 OAuth / 小程序登录 provider 协议,`api-server::wechat::provider` 只作为组合根 adapter,不在业务 handler 内散落 provider 构造。
|
||||
|
||||
验证:
|
||||
|
||||
@@ -53,12 +54,13 @@ npm run check:server-rs-ddd
|
||||
路由树由 `server-rs/crates/api-server/src/app.rs` 统一构造。当前主要分组:
|
||||
|
||||
- 健康检查:`GET /healthz`。
|
||||
- 后台管理:`/admin/api/*`,包括登录、概览、HTTP debug、埋点、表查询、创作入口开关、作品可见性、兑换码、邀请码、任务配置和充值商品配置。
|
||||
- 后台管理:`/admin/api/*`,包括登录、概览、HTTP debug、埋点、表查询、创作入口开关、作品互动配置、作品可见性、兑换码、邀请码、任务配置和充值商品配置。
|
||||
- 认证与账号:`/api/auth/*`、`/api/profile/me`,包括短信、密码、微信、refresh session、多端会话和登出。
|
||||
- 个人中心:`/api/profile/*`,包括钱包流水、任务、领奖、充值、反馈、邀请、兑换、存档、历史浏览和游玩统计。
|
||||
- LLM 与语音:`/api/llm/*`、`/api/speech/volcengine/*`。
|
||||
- 资产:`/api/assets/*`,包括直传票据、STS、对象确认、实体绑定、读签名、读 bytes、历史资产、角色图像/动画和 Hyper3D 代理。
|
||||
- 创作入口配置:`/api/creation-entry/config`,后台 `/admin/api/creation-entry/config` 和 `/admin/api/creation-entry/config/banners`。
|
||||
- 个人中心:`/api/profile/*`,包括钱包流水、任务、领奖、充值、反馈、邀请和兑换等账号侧能力。
|
||||
- 平台基础能力:`/api/llm/*`、`/api/speech/volcengine/*`,只保留通用 LLM 和语音代理。
|
||||
- 资产基础能力:`/api/assets/direct-upload-tickets`、`/api/assets/sts-upload-credentials`、`/api/assets/objects/*`、`/api/assets/read-*`,负责直传、确认、绑定和读取。
|
||||
- 创作 / 游玩支撑能力:`/api/creation-entry/config`、`/api/ai/tasks*`、`/api/runtime/chat/*`、`/api/runtime/settings`、`/api/runtime/save/snapshot`、`/api/profile/browse-history`、`/api/profile/save-archives*`、`/api/profile/play-stats`、`/api/assets/history`、`/api/assets/character-visual/*`、`/api/assets/character-animation/*`、`/api/assets/character-workflow-cache*`、`/api/assets/hyper3d/*`、`/api/runtime/custom-world/asset-studio/*`。
|
||||
- 后台入口配置:`/admin/api/creation-entry/config`、`/admin/api/creation-entry/config/banners` 和 `/admin/api/creation-entry/config/interactions`。
|
||||
- 自定义世界 / RPG:`/api/runtime/custom-world*`、`/api/story/*`、`/api/runtime/chat/*`。
|
||||
- 拼图:`/api/runtime/puzzle/*`。
|
||||
- 抓大鹅 Match3D:`/api/creation/match3d/*`、`/api/runtime/match3d/*`。
|
||||
@@ -66,17 +68,30 @@ npm run check:server-rs-ddd
|
||||
- 方洞挑战:`/api/creation/square-hole/*`、`/api/runtime/square-hole/*`。
|
||||
- 视觉小说:`/api/creation/visual-novel/*`、`/api/runtime/visual-novel/*`。
|
||||
- 大鱼吃小鱼:`/api/runtime/big-fish/*`。
|
||||
- 跳一跳:`/api/creation/jump-hop/*`、`/api/runtime/jump-hop/*`。
|
||||
- 汪汪声浪:`/api/runtime/bark-battle/*`。
|
||||
- 儿童向创作:`/api/creation/edutainment/*`。
|
||||
- AI task:`/api/ai/tasks*`。
|
||||
|
||||
需要新增路由时,先确认玩法入口配置和 tracking 分类,不要绕过 `app.rs` 的统一中间件、鉴权和入口开关。
|
||||
需要新增路由时,先确认玩法入口配置和 tracking 分类,不要绕过 `app.rs` 的统一中间件、鉴权和入口开关。涉及创作、生成、作品、公开详情、试玩、正式运行态、运行态库存、运行态设置 / 存档、游玩历史、存档归档、游玩统计、AI task、角色资产工坊或玩法生成支撑资产的路由,不再直接在 `app.rs` 逐玩法 `.merge(...)`,也不挂到 `modules/platform.rs`;必须先进入 `server-rs/crates/api-server/src/modules/play_flow.rs` 的统一玩法流程主干,再由主干注册表分发到各领域 HTTP Adapter 或支撑能力 handler。
|
||||
|
||||
### 创作 / 游玩统一流程主干
|
||||
|
||||
`modules/play_flow.rs` 是后端创作与游玩流程的统一入口。现有外部 URL、DTO、错误 envelope、鉴权方式、入口开关语义和 SpacetimeDB schema 默认不变,但路由组织必须遵循:
|
||||
|
||||
1. `app.rs` 只合并 `modules::play_flow::router(state)`,不直接合并 RPG、拼图、抓大鹅、跳一跳、敲木鱼、拼消消、汪汪声浪、视觉小说或儿童向创作等逐玩法模块。
|
||||
2. `play_flow` 统一注册每个玩法的 `playId`、领域模块 key、创作路由前缀和运行态路由前缀;后续新增玩法或迁移旧玩法时,先补这个注册表,再挂具体领域模块路由。
|
||||
3. 新建创作、首次生成和 Remix 成草稿等会产生新创作的入口开关匹配规则同样归 `play_flow` 管理;`creation_entry_config.rs` 只复用该规则执行 `open=false` 熔断,不再维护第二份路径判断。
|
||||
4. `play_flow` 在进入领域 handler 前先解析并挂载 `PlayFlowRequestContext`,统一标记请求处于 `Creation`、`Runtime`、`CreationEntryConfig`、`CreationSupport`、`RuntimeSupport`、`AiTask`、`PublicReadModel` 或 `RuntimeInventory` 阶段,并记录目标 `playId` / 领域模块 key;领域 handler 可以读取该上下文做后续收口,但不能绕过主干自建平行流程。
|
||||
5. `play_flow` 只做平台共性编排和领域 Adapter 组合,不下沉玩法规则;最后一步的草稿编译、资产生成、发布、运行态 start/action/finish、计分和排行榜仍交给对应 `module-*`、`spacetime-module` procedure 和玩法 HTTP handler 处理。
|
||||
6. 公开作品聚合、作品详情、运行态库存、运行态设置 / 存档、游玩历史、存档归档、游玩统计、历史素材、AI task、runtime chat、文档解析、角色资产工坊、角色图像 / 动画生成和 Hyper3D 代理属于跨玩法或玩法支撑流程,也从 `play_flow` 主干挂入;`modules/platform.rs` 只保留通用 LLM / 语音代理,不再承接创作 / 游玩支撑路由。
|
||||
7. 如果某个旧玩法仍使用历史 `/api/runtime/<play>/agent/*` 作为创作命名空间,只保留外部兼容路径;新增实现和文档仍按“统一主干 -> 领域 Adapter”的语义描述,不把历史路径当新架构模板。
|
||||
|
||||
### 认证态用户与会话摘要下发口径
|
||||
|
||||
- `AuthUserPayload` / `AuthUser` 只保留前端当前会用到的身份与绑定展示字段:`id`、`publicUserCode`、`displayName`、`avatarUrl`、`phoneNumberMasked`、`loginMethod`、`bindingStatus`、`wechatBound`。
|
||||
- `AuthUserPayload` / `AuthUser` 只保留前端当前会用到的身份与绑定展示字段:`id`、`publicUserCode`、`displayName`、`avatarUrl`、`phoneNumber`、`phoneNumberMasked`、`loginMethod`、`bindingStatus`、`wechatBound`、`wechatDisplayName`、`wechatAccount`。账号信息面板展示微信绑定时优先使用 `wechatDisplayName`;该字段只能来自微信平台 profile、历史已保存的微信身份资料,或小程序原生 `input type="nickname"` 提交的 `displayName`,不得用系统账号显示名或“微信旅人”这类假昵称兜底。小程序 `/api/auth/wechat/miniprogram-login` 与 `/api/auth/wechat/bind-phone` 可接收 `displayName`;`/api/auth/wechat/miniprogram-login` 额外返回 `created`,供小程序壳在快捷登录后判断是否需要补采集微信昵称。`jscode2session` 无法直接返回微信昵称或个人微信号,只能稳定拿到小程序维度 `openid`,后端以 `wechatAccount` 下发可区分的绑定账号标识,前端在缺少真实昵称时展示账号尾号。
|
||||
- `AuthSessionSummaryPayload` / `AuthSessionSummary` 只保留设备卡片与撤销需要的摘要字段:`sessionId`、`sessionIds`、`sessionCount`、`clientLabel`、`ipMasked`、`isCurrent`、`createdAt`、`lastSeenAt`、`expiresAt`。
|
||||
- 设备诊断信息(例如原始 `clientType` / `clientRuntime` / `clientPlatform` / `userAgent` / `miniProgramAppId` / `miniProgramEnv` / `deviceDisplayName`)不再默认下发到前端;若未来确需展示,优先单独加窄 DTO,而不是把账号 / 会话快照恢复为全量对象。
|
||||
- 多端登录语义以 `refresh_session` 为粒度:同一账号可保留多个 active session,普通登录不会撤销旧设备;`POST /api/auth/logout` 只撤销当前 refresh session,不提升 `token_version`;`POST /api/auth/logout-all`、改密、重置密码才吊销全端 session 并提升 `token_version`。鉴权中间件仍校验 Bearer `sid` 对应的 refresh session 是否 active,单独踢下线或当前设备退出可以让目标设备立即失效而不误伤其它设备。
|
||||
|
||||
## api-server 模块化演进规则
|
||||
|
||||
@@ -84,8 +99,8 @@ npm run check:server-rs-ddd
|
||||
|
||||
路由模块化规则:
|
||||
|
||||
1. 每个能力 Module 只暴露 `router(state) -> Router<AppState>`,由 `app.rs` 统一 `.merge(...)`。
|
||||
2. `app.rs` 只保留全局 middleware、TraceLayer、request context、tracking middleware、入口开关和少量顶层 glue。
|
||||
1. 每个能力 Module 只暴露 `router(state) -> Router<AppState>`;平台创作 / 游玩相关 Module 和支撑能力由 `modules/play_flow.rs` 统一 `.merge(...)` 或在支撑 router 内挂载,其它账号、资产基础、后台和平台基础能力再由 `app.rs` 直接合并。
|
||||
2. `app.rs` 只保留全局 middleware、TraceLayer、request context、tracking middleware、入口开关和少量顶层 glue;不得重新恢复逐玩法 creation/runtime merge 列表。
|
||||
3. 能力 Module 可在路由内部用 `FromRef<AppState>` 派生自己的 Feature State,例如 `PuzzleApiState`。全局 `AppState` 仍作为进程组合根、鉴权层和全局中间件状态,但业务 handler 优先只提取对应 Feature State,不直接暴露完整 `AppState`。
|
||||
4. Feature State 只暴露该能力实际需要的 facade / adapter / 配置快照;若必须复用仍要求 `AppState` 的横切 helper(例如计费、外部失败审计或通用 tracking),应通过 Feature State 的窄方法或显式 `root_state()` 过渡,并在后续继续收窄。
|
||||
5. 路由迁移和业务重构分阶段处理;先移动路由装配,再拆 handler 内部实现,再收窄 handler 可见状态。
|
||||
@@ -104,15 +119,20 @@ npm run check:server-rs-ddd
|
||||
- `server-rs/crates/api-server/src/puzzle/mappers.rs` 承接 SpacetimeDB record 到 shared-contracts DTO 的映射。
|
||||
- `server-rs/crates/api-server/src/puzzle/tags.rs` 保留拼图标签生成、拼图通用错误映射和 SSE helper。
|
||||
|
||||
拼图发布 / 待发布门槛必须同时要求首图、关卡画面、UI spritesheet 与关卡背景资产包完整;`module-puzzle::validate_publish_requirements` 与 `api-server::puzzle::tags::is_puzzle_session_snapshot_publish_ready` 使用同一资产语言,不得只凭 cover、标题、描述和标签把半成品标为 `publishReady` 或 `ready_to_publish`。
|
||||
|
||||
该拆分只改变 `api-server` 文件组织,不改变 `/api/runtime/puzzle/*` route、DTO、error envelope、SpacetimeDB schema、公开 gallery cache 语义或计费语义;后续继续细分时也必须先保持行为不变,再单独讨论领域规则下沉。
|
||||
|
||||
`/api/runtime/puzzle/runs*` 当前接受 `RuntimePrincipal`,可同时识别登录用户 Bearer 和 runtime guest token。推荐页嵌入运行态的正式开局、交换、拖拽、下一关、暂停、道具与排行榜请求,应由前端在登录态下继续携带账号 access token;匿名游客仅在确认为未登录时走 runtime guest token。不要再把拼图 runtime 当成只认普通 Bearer 的纯账号接口。
|
||||
|
||||
公开正式 runtime 的启动与局内同步动作统一接受 `RuntimePrincipal`,包括拼图、拼消消、跳一跳、敲木鱼、抓大鹅 Match3D、方洞挑战、视觉小说、大鱼吃小鱼和汪汪声浪。登录用户仍使用账号 Bearer;未登录推荐页或公开运行态使用 Runtime Guest Token,后端以 `principal.subject()` 作为本局 owner / player subject,并用 `WorkPlayTrackingDraft::runtime_principal(...)` 记录游玩。创作、个人作品、删除、发布、Remix、点赞等账号或所有权动作不得改成 runtime guest 鉴权。
|
||||
|
||||
抓大鹅 Match3D `api-server` 内部拆分:
|
||||
|
||||
- `server-rs/crates/api-server/src/modules/match3d.rs` 继续负责路由装配和 body limit;对外 handler 名称保持不变。
|
||||
- `server-rs/crates/api-server/src/match3d.rs` 只作为聚合入口,保留共享 import / 常量 / 内部类型、模块声明和 handler re-export。
|
||||
- `server-rs/crates/api-server/src/match3d/handlers.rs` 承接 Axum handler,负责 extract、鉴权上下文、调用 SpacetimeDB facade / 编排 helper,并返回 HTTP 响应。
|
||||
- `/api/runtime/match3d/works/{profile_id}/runs`、`/api/runtime/match3d/runs/{run_id}`、`/click`、`/stop`、`/restart` 与 `/time-up` 属于正式运行态局部请求,必须接受 `RuntimePrincipal`;登录用户使用账号 Bearer,推荐页匿名游客使用 runtime guest token,后端以 principal subject 作为本局 owner,不得退回只认普通 Bearer 的路由。
|
||||
- `server-rs/crates/api-server/src/match3d/draft.rs` 承接 Agent session、草稿编译、题材 / 难度 / 物品计划和草稿持久化编排。
|
||||
- `server-rs/crates/api-server/src/match3d/works.rs` 承接作品 CRUD、封面 / 背景 / 容器资产生成入口、发布 / Remix / 点赞 / 游玩记录和作品级 helper。
|
||||
- `server-rs/crates/api-server/src/match3d/item_assets.rs` 承接物品生成批次编排、append / replace / delete / sort / merge、计费外层和草稿素材映射;sheet prompt、绿幕 / 近白底透明化、切图和切片持久化复用 `generated_asset_sheets` 通用模块。
|
||||
@@ -128,9 +148,10 @@ npm run check:server-rs-ddd
|
||||
3. Adapter 输出应保留 legacy public path、object key、asset object id、MIME、extension、task id 和实际 prompt。
|
||||
4. Adapter 不负责扣费、退款或钱包读取;计费仍由调用方显式包裹。
|
||||
5. 图片 provider 协议不再放在玩法模块里实现。VectorEngine `gpt-image-2` 创建 / 编辑协议、URL / base64 图片解析、远端图片下载、请求超时 / 上游状态 / 响应解析 / 缺图 / 下载失败的结构化日志统一在 `server-rs/crates/platform-image/src/vector_engine/`;其中 `client.rs` 只保留 provider 调用编排,`transport.rs` 负责 HTTP client 与 reqwest 错误归一,`request.rs` 负责请求体和路径,`payload.rs` 负责响应 JSON 字段提取,`response.rs` 负责响应状态分流和图片结果归一。`api-server` 只负责配置校验、玩法 prompt 编排、OSS / asset object / binding 持久化、计费和外部 API 失败审计落库。
|
||||
6. Puzzle、Match3D、音频、GLB、视频等复杂媒体可以复用 OSS + asset object + binding 的底层持久化能力,但玩法专属处理规则留在各自编排层,不塞进公共接口。
|
||||
7. 拼图入口页与结果页新增关卡的本地参考图不走浏览器直传 OSS,前端读取为 Data URL 后随创作 action 提交,并在读取前限制 6MB、显示“图片≤6MB”。`api-server` 必须对 Data URL 实际字节数再次校验;历史图片才提交 `referenceImageAssetObjectId(s)`,后端校验 `asset_object` 的 bucket、kind、图片 MIME、大小和 owner 后签发只读 URL 给 VectorEngine 读取。
|
||||
8. 系列素材图集实现真相源在 `server-rs/crates/platform-image/src/generated_asset_sheets/`:调用方必须传入 `grid_size` 作为 `n*n` 的 `n`,可选传入物品名称 prompt 模板和特殊设定 prompt;模块负责 sheet prompt 组装、按 `n*n` 切片、透明化、PNG 输出、OSS private upload 请求构造和 sheet / item / special prompt 元数据持久化。`server-rs/crates/api-server/src/generated_asset_sheets.rs` 只保留 `AppState` / `AppError` 适配和兼容导出。玩法只负责规划 slot、调用具体生图 provider、计费、失败回写,以及把通用切片结果映射回自己的 DTO / 草稿 / runtime 字段。
|
||||
6. OSS 平台适配日志统一在 `server-rs/crates/platform-oss` 输出,覆盖 `sign_post_object`、`sign_get_object_url`、`head_object` 和 `put_object`。日志字段固定使用 `provider`、`operation`、`bucket`、`endpoint`、`object_key` / `key_prefix`、`access`、`content_type`、`content_length`、`status`、`status_class`、`error_kind` 和 `elapsed_ms`,只记录对象定位和排障信息;不得输出 AccessKey、policy、signature、Authorization header 或完整 signed URL。generated 私有对象上传时必须由 OSS 对象头承载浏览器 / CDN 缓存策略,默认写入 `Cache-Control: public, max-age=31536000, immutable`,不得改成 api-server 本地磁盘静态资源兜底。
|
||||
7. Puzzle、Match3D、音频、GLB、视频等复杂媒体可以复用 OSS + asset object + binding 的底层持久化能力,但玩法专属处理规则留在各自编排层,不塞进公共接口。
|
||||
8. 拼图入口页与结果页新增关卡的本地参考图不走浏览器直传 OSS,前端读取为 Data URL 后随创作 action 提交,并在读取前限制 6MB、显示“图片≤6MB”。`api-server` 必须对 Data URL 实际字节数再次校验;历史图片才提交 `referenceImageAssetObjectId(s)`,后端校验 `asset_object` 的 bucket、kind、图片 MIME、大小和 owner 后签发只读 URL 给 VectorEngine 读取。
|
||||
9. 系列素材图集实现真相源在 `server-rs/crates/platform-image/src/generated_asset_sheets/`:调用方必须传入 `grid_size` 作为 `n*n` 的 `n`,可选传入物品名称 prompt 模板和特殊设定 prompt;模块负责 sheet prompt 组装、按 `n*n` 切片、透明化、PNG 输出、OSS private upload 请求构造和 sheet / item / special prompt 元数据持久化。`server-rs/crates/api-server/src/generated_asset_sheets.rs` 只保留 `AppState` / `AppError` 适配和兼容导出。玩法只负责规划 slot、调用具体生图 provider、计费、失败回写,以及把通用切片结果映射回自己的 DTO / 草稿 / runtime 字段。
|
||||
|
||||
## SpacetimeDB schema 变更规则
|
||||
|
||||
@@ -164,17 +185,23 @@ npm run check:server-rs-ddd
|
||||
7. access JWT 只携带最小设备快照 `device.client_type`、`device.client_runtime`、`device.client_platform`。充值下单按该快照拦截渠道:小程序只允许 `wechat_mp`,手机微信内网页只允许 `wechat_h5`,桌面微信内网页只允许 `wechat_native`。
|
||||
8. 所有微信真实渠道都以微信支付通知或服务端查单确认 `SUCCESS` 为到账事实;小程序、H5 跳转和 Native 二维码返回都不能直接发放泥点或会员。
|
||||
|
||||
## 创作入口泥点扣费契约
|
||||
|
||||
1. `creation_entry_type_config.unified_creation_spec_json` 内的 `mudPointCost` 是玩法新建草稿初始生成的泥点成本真相源,同时供入口卡展示和前端余额前置校验使用;旧契约缺失时允许按代码默认成本兜底。
|
||||
2. `api-server` 执行拼图首图生成、抓大鹅完整草稿生成和汪汪声浪初始三图生成时,必须通过 `GET /api/creation-entry/config` 同源配置解析对应玩法成本后再调用钱包扣费 procedure,不得继续使用前端或后端硬编码常量作为实际扣费真相。
|
||||
3. 结果页单图重生成、发布、道具使用和其它独立资产操作仍按各自业务操作成本执行;不要把初始草稿成本误套到这些单次操作上。
|
||||
|
||||
## 外部服务与资产
|
||||
|
||||
- LLM:`GENARRATIVE_LLM_*`,创意 Agent 另用 `APIMART_BASE_URL` / `APIMART_API_KEY`。
|
||||
- 图片生成:VectorEngine `gpt-image-2` 图片 provider 归属 `platform-image`,密钥只在后端环境变量中;`api-server` 内的 `openai_image_generation.rs` 只是兼容调用面和外部失败审计桥接,不再承载 provider 协议实现。实际外部生成运行记录统一落 `tracking_event`,`event_key = external_generation_run`,metadata 记录开始 / 结束时间、耗时、状态、成功标记、失败原因、provider task id 和结果摘要,不再写回过时的 `ai_task`。APIMart 只保留给创意 Agent `gpt-5` Responses 文本 / 多模态链路;DashScope 只按仍在使用的历史能力单独处理,不作为 GPT-image-2 兜底。
|
||||
- LLM:通用 LLM 门面继续使用 `GENARRATIVE_LLM_*`;创意 Agent `gpt-5` Responses / Chat Completions 文本链路已于 2026-06 从 APIMart 迁移到 VectorEngine,使用 `VECTOR_ENGINE_BASE_URL` / `VECTOR_ENGINE_API_KEY` 构造 OpenAI-compatible client,`api-server` 会把未带 `/v1` 的 VectorEngine base URL 规范化到 `/v1` 后请求 `/responses`。`APIMART_BASE_URL` / `APIMART_API_KEY` 只作为历史残留,不再作为创意 Agent gpt-5 客户端来源;后续排障时优先确认 VectorEngine `/v1/models`、`/v1/chat/completions` 和 `/v1/responses` 可用性。
|
||||
- 图片生成:VectorEngine `gpt-image-2` 图片 provider 归属 `platform-image`,密钥只在后端环境变量中;`api-server` 内的 `openai_image_generation.rs` 只是兼容调用面和外部失败审计桥接,不再承载 provider 协议实现。实际外部生成运行记录统一落 `tracking_event`,`event_key = external_generation_run`,metadata 记录开始 / 结束时间、耗时、状态、成功标记、失败原因、provider task id 和结果摘要,不再写回过时的 `ai_task`。DashScope 只按仍在使用的历史能力单独处理,不作为 GPT-image-2 兜底。VectorEngine `/v1/images/generations` 和 `/v1/images/edits` 上游 POST 使用 `libcurl` 发送;`reqwest` 只保留给参考图 URL 下载和响应中图片 URL 下载。`/v1/images/edits` 的 multipart 参考图必须作为 libcurl 文件上传 part 发送,字段名为 `image`,实现上使用 `Form::buffer(file_name, bytes)` 并设置 `Content-Type`;不能只用 `contents(...).filename(...)`,否则上游会把请求转码为缺少图片并返回 `image is required`。`request_send` 阶段的 curl timeout / connect error 按可重试传输错误处理,最多尝试 5 次,并使用指数退避加短抖动;排障时优先看 `attempt`、`max_attempts`、`retry_delay_ms`、`reference_image_bytes_total` 和 `request_params`,不要把 `SendRequest` 当成上游业务错误。
|
||||
- Match3D 物品 sheet:关卡整图完成后走 VectorEngine `/v1/images/edits` multipart `image`,模型为 `gpt-image-2`,`2K 1:1` 输出 `10*10` spritesheet;物品 sheet prompt 固定要求纯绿色绿幕背景,后端上传 OSS 前必须把绿幕扣成透明 PNG,并把透明整图写入 `itemSpritesheetImageSrc/itemSpritesheetImageObjectKey`。后端优先按透明 alpha 连通域从该 sheet 识别真实素材矩形并持久化 20 个物品、每个 5 个形态;识别数量不足时才回退 `10*10` 固定网格。通用系列素材图集的行列索引按每行 2 个物品计算,必须落在 `1..=10`,难度只决定运行态加载 3 / 9 / 15 / 20 种。
|
||||
- Match3D UI spritesheet 和背景派生图:关卡整图作为参考图并发生成 `1K 1:1` UI spritesheet 与 `1K 9:16` 背景图,模型均为 `gpt-image-2`。UI spritesheet prompt 固定要求纯绿色绿幕背景,后端上传 OSS 前必须把绿幕扣成透明 PNG;背景图必须合成为全画幅不透明 PNG。
|
||||
- Match3D 1:1 容器 UI:VectorEngine `/v1/images/edits` multipart 参考图。该容器参考图是后端生图协议输入,必须通过 `include_bytes!` 随 `api-server` 编译进二进制,避免 API 单独发布或运行目录缺少 `public/` 时生成失败。
|
||||
- 敲木鱼敲击物和背景环境图:VectorEngine `/v1/images/edits`,模型固定 `gpt-image-2`。敲击物支持 multipart 多参考图,第一张固定为后端内嵌默认木鱼图,用户上传图只作为新主题参考;prompt 必须要求 `1:1` 真实透明 alpha PNG 并禁止黑底、白底、棋盘格和任何实底背景。当前敲击物上传 OSS 前不做服务端抠图后处理,避免误伤玉米等主体像素。背景环境图只使用第一步抠图完成后的透明敲击物图作为参考,prompt 必须要求中央主体预留区保持干净,中央 40% 区域禁止出现主题主体、主体局部特写、轮廓影子或重复元素,主题元素只能作为外围氛围,且必须显式声明不继承任何绿色底色、绿幕底色或纯绿色画布。
|
||||
- Hyper3D / Rodin:只保留后端安全代理和旧数据兼容;Rodin 提交、状态、下载和响应解析归属 `platform-hyper3d`,`api-server/src/hyper3d_generation.rs` 只做路由、配置和错误 envelope 映射;新 Match3D 草稿和批量新增不再生成 GLB。
|
||||
- 音频:视觉小说专用音频路由保留;VectorEngine Suno/Vidu provider 协议、任务提交/查询、音频 URL 提取、下载、MIME/extension 归一和 OSS put 请求准备归属 `platform-audio`。`api-server/src/vector_engine_audio_generation.rs` 只做路由、配置、计费、asset object confirm、entity binding 和错误 envelope 映射;拼图、抓大鹅和敲木鱼提示词生成音效入口暂时关闭,通用 `/api/creation/audio/*` 对这些目标返回 `410 Gone`。敲木鱼创作只接收上传 / 录音音频资产;未提供时由 `api-server` 写回内置默认木鱼音 `/wooden-fish/default-hit-sound.mp3`。
|
||||
- OSS:私有 generated legacy path 进入浏览器前必须通过 `/api/assets/read-url` 换签;不要裸请求 `/generated-*`。
|
||||
- 音频:视觉小说专用音频路由保留;VectorEngine Suno/Vidu provider 协议、任务提交/查询、音频 URL 提取、下载、MIME/extension 归一和 OSS put 请求准备归属 `platform-audio`。`api-server/src/vector_engine_audio_generation.rs` 只做路由、配置、计费、asset object confirm、entity binding 和错误 envelope 映射;拼图、抓大鹅和敲木鱼提示词生成音效入口暂时关闭,通用 `/api/creation/audio/*` 对这些目标返回 `410 Gone`。敲木鱼创作只接收上传 / 录音音频资产;前端选择或录音阶段只在浏览器本地处理待提交音频,统一限制裁切后最长 1 秒、裁掉前后声音过小片段,并用浏览器端近似响度算法平衡到 `-15 LKFS` 后做峰值保护。点击生成时才直传 OSS 并确认 `asset_object`,创作 JSON 只提交轻量 `WoodenFishAudioAsset`,不得继续上传 Data URL 音频;未提供时由 `api-server` 写回内置默认木鱼音 `/wooden-fish/default-hit-sound.mp3`。
|
||||
- OSS:私有 generated legacy path 进入浏览器前必须通过 `/api/assets/read-url` 换签;不要裸请求 `/generated-*`。前端如果收到同一 OSS bucket 的完整 `https://*.oss-*.aliyuncs.com/generated-*` 地址,也必须先归一为 legacy path 后走同一换签链路,避免裸连私有 bucket 403 或绕过签名缓存。OSS 签名、读签名、HEAD 和 PUT 的结构化日志由 `platform-oss` 输出,排查资产写入 / 确认失败时优先按 `operation`、`object_key` / `key_prefix`、`status_class`、`error_kind` 和 `elapsed_ms` 下钻。新上传 generated 私有对象默认写入 `Cache-Control: public, max-age=31536000, immutable`;旧对象若缺该头,只能依赖 `ETag` / `Last-Modified` 协商缓存,应通过 OSS 元数据刷新或 CDN 配置补齐,不要恢复 api-server 静态代理。
|
||||
- 外部 API 失败审计:外部供应商调用未成功时,`api-server` 必须发送 OTLP 失败事件并写入 `tracking_event`。VectorEngine 图片 provider 在 `platform-image` 内输出结构化日志和 `PlatformImageFailureAudit`,覆盖 `request_send`、`response_body`、`upstream_status`、`response_parse`、`missing_image` 和 `image_download` 阶段;`api-server` 只把该 audit 映射成 `external_api_call_failure`,`scope_kind = module`、`scope_id = provider`、`module_key = external-api`。metadata 固定包含 provider、endpoint、operation、failureStage、statusCode、statusClass、timeout、retryable、errorMessage、latencyMs、promptChars、referenceImageCount、imageModel、rawExcerpt,以及在调用方可获得上下文时补充的 `userId`(触发者)和 `profileId`(草稿 / 作品 / 场景作用域)。图片生成入口应优先把 owner user id 和 profile id 透传到失败审计,不要只保留 provider 级聚合,否则很难按“谁触发、哪个作品触发”定位问题。入库优先复用 tracking outbox,outbox 不可写或保护阈值拒绝时回退同步写 SpacetimeDB;不得新增前端兜底或在 SpacetimeDB reducer 内做外部 I/O。
|
||||
- 外部生成运行记录:所有外部生成编排的完成态统一写入 `tracking_event`,`event_key = external_generation_run`,`scope_kind = module`,`scope_id = provider`,`module_key = external-generation`。metadata 固定包含 `runId`、`provider`、`operation`、`requestLabel`、`requestPayload`、`status`、`success`、`failureReason`、`providerRequestId`、`resultPayload`、`startedAtMicros`、`completedAtMicros` 和 `durationMs`。这类记录只用于运行审计和排障,不再走 `ai_task` 旧表。
|
||||
|
||||
@@ -242,7 +269,7 @@ npm run check:server-rs-ddd
|
||||
- Rust 结构体:`AuthStoreSnapshot`
|
||||
- 源码:`server-rs/crates/spacetime-module/src/auth/tables.rs`
|
||||
|
||||
认证恢复策略:`api-server` 启动时只从 SpacetimeDB 正式认证表(`user_account` / `auth_identity` / `refresh_session`)投影恢复进程内认证工作集;`auth_store_snapshot` 只保留行级快照备查,不再作为启动兜底来源。`module-auth` 只保留内存工作集和 JSON 导入 / 导出能力,不再写本地持久化文件;`auth-store.json` / `GENARRATIVE_AUTH_STORE_PATH` 不再是兼容恢复源,也不得在启动时回写覆盖 `auth_identity` / `user_account`。认证创建、登录会话、刷新、退出、改密、重置密码、绑定和资料变更等写操作必须在返回客户端前成功同步 SpacetimeDB 正式认证表;同步失败时接口返回错误,不允许把只存在于当前进程内存的账号或会话当成成功结果。新用户注册奖励、邀请码绑定和登录埋点必须排在认证同步成功之后,避免认证没落库时先写出钱包或邀请关系。若启动恢复阶段 SpacetimeDB 不可连接或超时,`api-server` 进入依赖不可用模式并对请求返回 `503 SERVICE_UNAVAILABLE`,直到运维恢复 SpacetimeDB 并重启服务。
|
||||
认证恢复策略:`api-server` 启动时只从 SpacetimeDB 正式认证表(`user_account` / `auth_identity` / `refresh_session`)投影恢复进程内认证工作集;运行中若 Bearer `sid` 或 refresh cookie 在本进程工作集内未命中,会先从 SpacetimeDB 正式认证表按需刷新一次认证工作集再复查,避免多实例或滚动重启时新登录设备只被签发它的进程认识。`auth_store_snapshot` 只保留行级快照备查,不再作为启动兜底来源。`module-auth` 只保留内存工作集和 JSON 导入 / 导出能力,不再写本地持久化文件;`auth-store.json` / `GENARRATIVE_AUTH_STORE_PATH` 不再是兼容恢复源,也不得在启动时回写覆盖 `auth_identity` / `user_account`。认证创建、登录会话、刷新、退出、改密、重置密码、绑定和资料变更等写操作必须在返回客户端前成功同步 SpacetimeDB 正式认证表;同步失败时接口返回错误,不允许把只存在于当前进程内存的账号或会话当成成功结果。新用户注册奖励、邀请码绑定和登录埋点必须排在认证同步成功之后,避免认证没落库时先写出钱包或邀请关系。若启动恢复阶段 SpacetimeDB 不可连接或超时,`api-server` 会按固定间隔持续重试认证工作集恢复,恢复成功后才开始监听 HTTP,避免一次短超时让进程永久停留在依赖不可用状态。
|
||||
|
||||
`auth_store_snapshot` 禁止再写单行 `snapshot_id = "default"` 聚合 JSON。认证同步入口收到 `module-auth` 整份快照后必须拆成行级记录写入同一张表,当前行键前缀包括:`meta/next_user_id`、`user/<user_id>`、`phone/<phone+user>`、`session/<session_id>`、`session_hash/<hash+session>`、`wechat/<provider_uid+user>`、`union/<union+user>`。SpacetimeDB 模块只保留 `import_auth_store_snapshot_json` 与 `export_auth_store_snapshot_from_tables` 两个认证快照过程;旧 `get_auth_store_snapshot`、`upsert_auth_store_snapshot`、`import_auth_store_snapshot` 兼容入口已删除。导入正式表时只按主键 upsert 本次快照包含的用户、身份和会话,避免过期快照把其他用户整表删除。
|
||||
|
||||
@@ -332,15 +359,15 @@ npm run check:server-rs-ddd
|
||||
|
||||
- Rust 结构体:`CreationEntryConfig`
|
||||
- 源码:`server-rs/crates/spacetime-module/src/runtime/creation_entry_config.rs`
|
||||
- 字段:`config_id`、`start_title`、`start_description`、`start_idle_badge`、`start_busy_badge`、`modal_title`、`modal_description`、`updated_at`、`event_title`、`event_description`、`event_cover_image_src`、`event_prize_pool_mud_points`、`event_starts_at_text`、`event_ends_at_text`、`event_banners_json`。
|
||||
- 迁移兼容:旧迁移包缺少活动横幅字段时,由 `migration.rs` 写入 `None` / `58000` 默认值;旧库缺少 `event_banners_json` 时写入 `None`,运行态读取层再按 `module-runtime` 默认公告数组归一,不覆盖后台已保存配置。HTTP 响应同时返回 `eventBanners` 数组和旧 `eventBanner` 单条兼容字段,前端优先消费数组;后台新配置主格式为 HTML 公告字符串数组或 `{title, htmlCode}` 对象数组,旧结构化 banner 字段仅保留兼容。
|
||||
- 字段:`config_id`、`start_title`、`start_description`、`start_idle_badge`、`start_busy_badge`、`modal_title`、`modal_description`、`updated_at`、`event_title`、`event_description`、`event_cover_image_src`、`event_prize_pool_mud_points`、`event_starts_at_text`、`event_ends_at_text`、`event_banners_json`、`public_work_interactions_json`。
|
||||
- 迁移兼容:旧迁移包缺少活动横幅字段时,由 `migration.rs` 写入 `None` / `58000` 默认值;旧库缺少 `event_banners_json` 时写入 `None`,运行态读取层再按 `module-runtime` 默认公告数组归一,不覆盖后台已保存配置,也不把旧结构化 `eventBanner` 升格为前端优先数组。旧库缺少 `public_work_interactions_json` 时写入 `None`,读取层按 `module-runtime` 默认作品互动矩阵补齐 `publicWorkInteractions`,不覆盖后台已保存开关。HTTP 响应同时返回 `eventBanners` 数组、旧 `eventBanner` 单条兼容字段和 `publicWorkInteractions` 互动矩阵;前端优先消费数组与矩阵。后台新公告配置主格式为 HTML 公告字符串数组或 `{title, htmlCode}` 对象数组,旧结构化 banner 字段仅保留兼容。默认公告背景和旧结构化默认 `coverImageSrc` 必须引用 `public/` 下真实存在的静态资源,当前为 `/creation-type-references/puzzle.webp`。
|
||||
|
||||
### `creation_entry_type_config`
|
||||
|
||||
- Rust 结构体:`CreationEntryTypeConfig`
|
||||
- 源码:`server-rs/crates/spacetime-module/src/runtime/creation_entry_config.rs`
|
||||
- 字段:`id`、`title`、`subtitle`、`badge`、`image_src`、`visible`、`open`、`sort_order`、`updated_at`、`category_id`、`category_label`、`category_sort_order`、`unified_creation_spec_json`。
|
||||
- 迁移兼容:旧迁移包缺少入口分类字段或统一创作契约字段时,由 `migration.rs` 写入 `None` / `0` / `None` 默认值;入口分组展示由 `module-runtime` 和前端展示派生消费,统一创作契约由 `module-runtime` 解析为 `creationTypes[].unifiedCreationSpec`,为空时只回退首批 `puzzle`、`match3d`、`wooden-fish` 默认 spec。
|
||||
- 迁移兼容:旧迁移包缺少入口分类字段或统一创作契约字段时,由 `migration.rs` 写入 `None` / `0` / `None` 默认值;入口分组展示由 `module-runtime` 和前端展示派生消费,统一创作契约由 `module-runtime` 解析为 `creationTypes[].unifiedCreationSpec`,为空时按 `shared-contracts` 中当前支持的统一创作默认 spec 回退。`unifiedCreationSpec.title` 是统一创作页表头契约内容,读取和保存时不按入口 `title` 自动覆盖。
|
||||
|
||||
### `custom_world_agent_message`
|
||||
|
||||
@@ -375,6 +402,7 @@ npm run check:server-rs-ddd
|
||||
- Rust 结构体:`CustomWorldProfile`
|
||||
- 源码:`server-rs/crates/spacetime-module/src/custom_world.rs`
|
||||
- 字段变更:`visible` 控制是否进入公开列表 / 详情,默认 `true`;旧迁移数据由 `migration.rs` 补默认值。
|
||||
- 兼容约束:历史公开 RPG / 自定义世界 profile 可能存在 `publication_status=Published` 但 `published_at=None`。公开详情、点赞、游玩、Remix 和 `custom_world_gallery_entry` 同步都以 `Published + deleted_at=None + visible=true` 判断作品可公开互动;展示和 gallery 同步时间在 `published_at` 缺失时回退 `updated_at`,不得仅因 `published_at` 为空返回“已发布作品不存在”。
|
||||
|
||||
### `custom_world_session`
|
||||
|
||||
@@ -406,15 +434,24 @@ npm run check:server-rs-ddd
|
||||
- Rust 结构体:`JumpHopEventRow`
|
||||
- 源码:`server-rs/crates/spacetime-module/src/jump_hop/tables.rs`
|
||||
|
||||
### `jump_hop_leaderboard_entry`
|
||||
|
||||
- Rust 结构体:`JumpHopLeaderboardEntryRow`
|
||||
- 源码:`server-rs/crates/spacetime-module/src/jump_hop/tables.rs`
|
||||
- 说明:跳一跳作品维度排行榜 read model,每个 `profile_id + player_id` 只保留 1 条最佳记录;排序口径为成功跳跃次数降序、游戏时长升序、更新时间升序,草稿试玩不作为公开排行榜语义。
|
||||
- 展示契约:`player_id` 只作为后端去重和 `viewerBest` 匹配身份键,不得直接进入 HTTP/UI 展示字段;`/api/runtime/jump-hop/works/{profile_id}/leaderboard` 必须补齐 `displayName`,已登录玩家读取账号显示名,匿名游客展示“游客玩家”,失效账号展示“失效玩家”。
|
||||
|
||||
### `jump_hop_runtime_run`
|
||||
|
||||
- Rust 结构体:`JumpHopRuntimeRunRow`
|
||||
- 源码:`server-rs/crates/spacetime-module/src/jump_hop/tables.rs`
|
||||
- 说明:运行记录持久化 `runtime_mode`,取值为 `draft` / `published`;草稿试玩只允许作品所有者启动,不累计公开游玩次数,也不写入公开排行榜。
|
||||
|
||||
### `jump_hop_work_profile`
|
||||
|
||||
- Rust 结构体:`JumpHopWorkProfileRow`
|
||||
- 源码:`server-rs/crates/spacetime-module/src/jump_hop/tables.rs`
|
||||
- 说明:作品投影持久化独立 `theme_text`,用于生成主题和公开卡片主题展示;历史行为空时按 `work_title` 兜底。`back_button_asset_json` 保存 image2 单独生成并去绿后的 1:1 左上角返回按钮资产快照;旧迁移数据按 `None` 兼容,运行态缺失该字段时使用同尺寸 CSS 主题按钮兜底。
|
||||
|
||||
### SpacetimeDB view:`jump_hop_gallery_card_view`
|
||||
|
||||
@@ -715,8 +752,8 @@ npm run check:server-rs-ddd
|
||||
|
||||
跨玩法公开作品列表 / 详情主读模型是 `public_work_gallery_entry` 与 `public_work_detail_entry`。拼图、自定义世界等旧玩法公开列表 HTTP 路由保留原响应 shape,由 BFF mapper 从统一 public cache 映射回当前 DTO;旧 `*_gallery_card_view` / `*_gallery_view` / `custom_world_gallery_entry` 继续作为 source view 和兼容缓存。各玩法的个人作品列表、详情、发布、点赞、游玩记录、Remix 和其它需要鉴权或写入副作用的路径继续走 procedure / reducer;不要为了公开列表性能把这些 owner-specific 或 mutation 语义混进 public view。
|
||||
|
||||
`GET /api/creation-entry/config` 和入口熔断优先从订阅 cache 读取创作入口配置;cache 缺失时使用最近一次成功读取的内存快照,再兜底调用 `get_creation_entry_config` procedure 完成空库种子或旧库兼容。
|
||||
入口配置快照包含 start card、类型弹窗、公告位兼容字段和入口类型列表;入口类型列表新增 `category_id`、`category_label`、`category_sort_order` 后,后台 upsert、`shared-contracts`、`module-runtime` 和 `spacetime-client` binding 必须同步,旧迁移 JSON 通过 `migration.rs` 默认值兼容。
|
||||
`GET /api/creation-entry/config`、入口熔断和公开作品互动熔断优先从订阅 cache 读取创作入口配置;cache 缺失时使用最近一次成功读取的内存快照,再兜底调用 `get_creation_entry_config` procedure 完成空库种子或旧库兼容。
|
||||
入口配置快照包含 start card、类型弹窗、公告位兼容字段、入口类型列表和 `publicWorkInteractions` 作品互动矩阵;入口类型列表新增 `category_id`、`category_label`、`category_sort_order` 后,后台 upsert、`shared-contracts`、`module-runtime` 和 `spacetime-client` binding 必须同步,旧迁移 JSON 通过 `migration.rs` 默认值兼容。作品互动矩阵是全局公开作品详情能力配置,不属于单个 `creation_entry_type_config`;后台通过 `/admin/api/creation-entry/config/interactions` 保存,前端据此隐藏或拦截已接入的点赞 / Remix 入口,api-server 同时对已接入后端动作执行 `public_work_interaction_disabled` 熔断。
|
||||
|
||||
RPG 创作入口的配置 ID 是 `rpg`,当前 `visible=true`、`open=true`;历史 `custom-world` 路由仍是 RPG 的工程域与运行态源类型。入口熔断把 `/api/runtime/custom-world*`、`/api/story/*` 和 `/api/runtime/chat/*` 统一映射到 `rpg`,不要新增平行 `airp` 路由或用 `airp` 接管当前文字冒险链路。
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 本地开发验证与生产运维
|
||||
# 本地开发验证与生产运维
|
||||
|
||||
更新时间:`2026-05-15`
|
||||
更新时间:`2026-06-09`
|
||||
|
||||
## 标准开发流程
|
||||
|
||||
@@ -47,7 +47,7 @@ npm run dev:api-server
|
||||
|
||||
Linux 本机多用户并发开发时,`npm run dev` 和 `npm run dev:*` 单模块命令会先在系统级端口段注册表里给当前用户分配一个端口段,再把该段映射为 `web = start`、`api = start + 1`、`spacetime = start + 2`、`admin-web = start + 3`。默认注册表目录是 `/var/tmp/genarrative-dev-port-ranges/`,其中 `registry.json` 记录各用户的活跃段,`registry.lock` 负责串行化分配;可以用 `GENARRATIVE_DEV_PORT_RANGE_REGISTRY_DIR` 覆盖目录。系统自动分配时从 `10000-10099` 开始,每次占用 100 个端口块,后续块按 `10100-10199`、`10200-10299` 递增;`GENARRATIVE_DEV_PORT_RANGE` 或 `--port-range` 只在 Linux 上生效,Windows 仍按原来的 3000 / 8082 / 3101 / 3102 端口探测与漂移逻辑运行,不读这个系统级注册表。
|
||||
|
||||
后端日志默认写入 `logs/api-server/`。后端 API smoke 使用 `npm run dev:api-server` 并检查 `/healthz`;不要使用旧 `api-server:maincloud` 或任何 `GENARRATIVE_SPACETIME_MAINCLOUD_*` 口径。
|
||||
后端日志默认写入 `logs/api-server/`。后端 API smoke 使用 `npm run dev:api-server` 并检查 `/healthz`;需要确认实例可接生产流量时检查 `/readyz`。不要使用旧 `api-server:maincloud` 或任何 `GENARRATIVE_SPACETIME_MAINCLOUD_*` 口径。
|
||||
|
||||
开发态 `npm run dev` 与 `npm run dev:api-server` 会默认注入 `GENARRATIVE_DEV_PASSWORD_ENTRY_AUTO_REGISTER_ENABLED=true`,因此密码登录在本地开发环境可直接注册未知手机号账号;生产环境仍按 `api-server` 配置默认关闭该开关。
|
||||
|
||||
@@ -55,6 +55,8 @@ Linux 本机多用户并发开发时,`npm run dev` 和 `npm run dev:*` 单模
|
||||
|
||||
微信小程序虚拟支付使用 `WECHAT_MINI_PROGRAM_VIRTUAL_PAYMENT_OFFER_ID`、`WECHAT_MINI_PROGRAM_VIRTUAL_PAYMENT_APP_KEY`、`WECHAT_MINI_PROGRAM_VIRTUAL_PAYMENT_SANDBOX_APP_KEY` 和 `WECHAT_MINI_PROGRAM_VIRTUAL_PAYMENT_ENV` 配置。小程序充值统一走 `wechat_mp_virtual` / `wx.requestVirtualPayment`:泥点属于代币(`coin`),`buyQuantity` 按当前充值商品快照里的 `points_amount` 传;会员和后台新增道具类商品走 `short_series_goods`,`productId` 对应微信后台道具 ID。旧登录快照若缺 `session_key`,需要用户在小程序内重新登录后再支付;客户端成功回调不是最终到账,仍以后端通知或查询确认订单为准。详细口径见 `docs/【技术方案】微信虚拟支付接入-2026-05-26.md`。
|
||||
|
||||
微信小程序订阅消息生成结果通知使用 `WECHAT_MINIPROGRAM_SUBSCRIBE_MESSAGE_ENABLED`、`WECHAT_MINIPROGRAM_GENERATION_RESULT_TEMPLATE_ID` 和 `WECHAT_MINIPROGRAM_SUBSCRIBE_MESSAGE_STATE` 配置。当前模板为 `AI创作生成结果通知`;H5 在生成动作发起前先进入生成进度态并立即继续生成动作,同时非阻塞跳转到小程序原生订阅授权页尝试请求授权,用户接受、拒绝或返回都不能阻塞生成,且原生页不改写上一页 `webViewUrl`,避免返回后丢失 H5 当前进度页状态。后端只在玩法草稿生成成功或失败终态后用微信登录保存的 openid 调用 `subscribeMessage.send`,发送失败只打 warning,不影响生成主链路。模板 `thing1` 字段发送玩法模板名,例如 `拼图`、`敲木鱼`、`抓大鹅`;`number6` 字段发送本次生成结算后的实际泥点扣除,失败退款后固定为 `0`。模板 `time4` 字段固定发送北京时间 `YYYY-MM-DD HH:mm`,不要使用内部微秒时间戳、秒级时间戳或带时区后缀的 RFC3339 字符串,否则微信会返回 `argument invalid! data.time4.value invalid`。当前已接入拼图、敲木鱼、抓大鹅、跳一跳、方洞、视觉小说的草稿生成终态;分槽素材生成或发布动作不得直接复用生成结果通知,避免一次作品生成产生多条订阅消息。
|
||||
|
||||
如果本地 `GET /api/creation-entry/config` 返回 `No such procedure`,或 `api-server` 日志出现 `no such table: puzzle_gallery_card_view` / `no such table: wooden_fish_gallery_card_view` 这类公开 view 缺失,通常是 `.env.local` 指向的 SpacetimeDB 库还没有发布当前 `spacetime-module`,或当前 CLI 身份无权发布该库。debug 构建的 `api-server` 会临时使用后端默认入口配置兜底,避免创作作品架整块消失;正式修复仍应切换到拥有目标库权限的 SpacetimeDB 身份后重新运行 `npm run dev` 完成发布,或用 gitignored 的 `spacetime.local.json` 指向可发布的本地库。
|
||||
|
||||
本地排查 schema 漂移时,先用当前 dev server 显式查询目标库,例如:
|
||||
@@ -67,10 +69,14 @@ spacetime sql <database> "SELECT * FROM puzzle_gallery_card_view LIMIT 1" --serv
|
||||
|
||||
本地 `npm run dev:spacetime` 发布模块时必须显式忽略仓库根目录的 `spacetime.json`,由脚本固定追加 `--no-config` 并使用命令参数里传入的数据库名和 `--server http://127.0.0.1:3101`。否则 CLI 可能把发布目标改写到配置文件里的其他数据库,导致 `dev:spacetime` 启动后又因发布失败自动退出,浏览器随后会在 `ws://127.0.0.1:3101/v1/database/.../subscribe` 看到连接拒绝。
|
||||
|
||||
本地 `spacetime` CLI / standalone 版本必须和 `server-rs/Cargo.toml` 里锁定的 `spacetimedb` 版本一致;当前统一版本为 `2.3.0`。若版本错配,procedure 返回值可能在宿主侧触发 `Failed to BSATN deserialize procedure return value`,api-server 最终表现为敲木鱼等创作动作的 `SpacetimeDB procedure 调用超时`。排障时先运行 `spacetime --version`,再对照 `server-rs/Cargo.toml` 的 `spacetimedb = "..."`;需要切版本时执行 `spacetime version install <version> && spacetime version use <version>`,然后重新启动 `npm run dev:spacetime`。当前 `scripts/dev.mjs` 会在启动和复用本地 SpacetimeDB 前写入并校验 `dev-spacetime-tool-version`,避免把旧 standalone 继续带进新一轮创作。
|
||||
本地 `spacetime` CLI / standalone 版本必须和 `server-rs/Cargo.toml` 里锁定的 `spacetimedb` 版本一致;当前统一版本为 `2.4.1`。若版本错配,procedure 返回值可能在宿主侧触发 `Failed to BSATN deserialize procedure return value`,api-server 最终表现为敲木鱼等创作动作的 `SpacetimeDB procedure 调用超时`。排障时先运行 `spacetime --version`,再对照 `server-rs/Cargo.toml` 的 `spacetimedb = "..."`;需要切版本时执行 `spacetime version install <version> && spacetime version use <version>`,然后重新启动 `npm run dev:spacetime`。当前 `scripts/dev.mjs` 会在启动和复用本地 SpacetimeDB 前写入并校验 `dev-spacetime-tool-version`,避免把旧 standalone 继续带进新一轮创作。
|
||||
|
||||
本地 `.env`、`.env.local` 或 `.env.secrets.local` 修改后必须重启 `api-server` 才会生效;若已经通过 `npm run dev` 启动完整联调,可在该终端输入 `rs api-server`。排查 RPG / 拼图 / 抓大鹅等 VectorEngine 生图链路时,确认 `VECTOR_ENGINE_BASE_URL`、`VECTOR_ENGINE_API_KEY` 和 `VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS` 只在本地或服务器密钥文件中配置,不能写入 Git。VectorEngine `gpt-image-2` 图片协议、URL / base64 响应解析、远端图片下载和 provider 侧结构化日志在 `server-rs/crates/platform-image`;`api-server` 只做配置、玩法编排、OSS / asset 持久化、计费和失败审计落库。开局 CG 故事板、首图、背景和图集都属于长耗时图片请求;后端默认会把 `VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS` 下限收口到 `1000000`,旧进程仍可能沿用重启前的短超时。若 VectorEngine 在 `send()` 阶段失败且日志显示 `SendRequest`,先看同一 `request_id` 的 provider 日志字段 `source`、`source_chain`、`source_chain_depth`,再查 `external_api_call_failure.metadata_json.errorSource`;当前 multipart `/v1/images/edits` 单独强制 HTTP/1.1。拼图关卡资产按 `level_scene -> ui_spritesheet -> level_background` 顺序生成,日志会带 `slot`、`asset_kind` 和 `elapsed_ms`。
|
||||
|
||||
VectorEngine 图片生成 / 编辑在 `request_send` 阶段出现 `timeout`、`connect`、libcurl 35 SSL connect reset、libcurl 56 receive error / `unexpected eof while reading`、recv failure 等临时传输错误,或在 `upstream_status` 阶段收到 408 / 429 / 5xx(例如 Nginx HTML `502 Bad Gateway`)时,`platform-image` 会对同一请求最多发送 5 次;multipart 图片编辑每次重试都会重新构造 form,避免复用已消费的 body。日志中 `VectorEngine 图片请求发送失败,准备重试` 或 `VectorEngine 图片上游状态可重试,准备重试` 表示本次失败已进入下一次尝试;最终仍失败时才会写入 `external_api_call_failure` 并返回 504 / 502。排查生产失败时应同时统计 retry 前的尝试日志和最终 audit,避免把一次用户请求内的多次发送误判成多个用户请求。
|
||||
|
||||
拼图入口直创的 `compile_puzzle_draft` 是长耗时链路:后端会先快速编译草稿并返回 `image_refining` / `generating` 快照,然后在 api-server 后台任务中完成首图、UI 资产、OSS 持久化、作品投影、计费退款和失败态回写。生产排查小程序 `Failed to fetch` 时,若 Nginx access log 里 action POST 是 `499`、`upstream_status=-`,说明客户端或 WebView 先断开;此时不应再把长 POST 是否返回作为生成成败依据,而应继续按实际 `session_id` 查后台任务日志、VectorEngine provider 日志、`external_api_call_failure` 和后续 GET 轮询结果。同一用户可能先轮询旧的 `puzzle-session-*`,随后 POST 新建实际生成 session;必须用 action POST 的 `request_id` 和 `/api/runtime/puzzle/agent/sessions/<session_id>/actions` 路径对齐真实失败请求,避免被前端显示的“来源草稿”误导。
|
||||
|
||||
查看本地 Rust / SpacetimeDB 日志:
|
||||
|
||||
```bash
|
||||
@@ -90,6 +96,7 @@ npm run admin-web:typecheck
|
||||
```bash
|
||||
npm run check:encoding
|
||||
npm run check:spacetime-schema
|
||||
npm run check:production-ops
|
||||
npm run check:server-rs-ddd
|
||||
npm run lint:eslint
|
||||
npm run typecheck
|
||||
@@ -208,10 +215,10 @@ UI 相关修改要重点验证:
|
||||
数据库备份不放进 `spacetime-module` reducer / procedure:备份属于文件系统与 OSS 外部副作用,必须由运维脚本在 SpacetimeDB 宿主外执行。当前统一脚本为;生产 provision 还会安装 `genarrative-database-backup.timer`,每天 `03:20` 左右自动执行一次 OSS 冷备份:
|
||||
|
||||
```bash
|
||||
npm run database:backup:oss -- --data-dir /stdb --stop-service spacetimedb.service
|
||||
npm run database:backup:oss -- --data-dir /stdb --stop-service spacetimedb.service --restart-service-after genarrative-api.service
|
||||
```
|
||||
|
||||
脚本会将数据目录打包成 `tar.gz`,上传到 `oss://<bucket>/<prefix>/<database>/<database>-<UTC时间>.tar.gz`。生产建议做冷备份:传入 `--stop-service spacetimedb.service`,脚本会在打包前停止服务、打包后恢复服务,再上传 OSS。由于 OSS 上传可能受服务器带宽限制,`Genarrative-Stdb-Module-Publish` 默认使用 `DATABASE_BACKUP_MODE=async`:先在 publish 前用 `--defer-upload` 生成本地冷备份和 `.manifest.json`,随后继续执行 publish;发布脚本退出前会用后台 `node ... --upload-archive <tar.gz>` 上传同一份发布前备份,不等待上传完成。发布脚本在校验 wasm 后、执行 `spacetime publish` 前会等待显式 `SPACETIME_SERVER_URL` 的 `/v1/ping` 就绪,默认最多等待 `60` 秒;如生产机器冷备份恢复 `spacetimedb.service` 较慢,可临时设置 `GENARRATIVE_STDB_PUBLISH_READY_TIMEOUT_SECONDS` 调整等待时间。需要强一致发布闸门时改用 `DATABASE_BACKUP_MODE=sync`(等价脚本参数 `--backup-mode sync`),备份会在 publish 前同步打包并上传,失败会阻断 publish;确认已有其他备份窗口时才使用 `DATABASE_BACKUP_MODE=skip`(兼容脚本参数 `--skip-backup`)。若业务不能接受停机窗口,应先规划 SpacetimeDB 原生快照或主备策略,不要直接在写入中的数据目录上做热拷贝并当作强一致备份。
|
||||
脚本会将数据目录打包成 `tar.gz`,上传到 `oss://<bucket>/<prefix>/<database>/<database>-<UTC时间>.tar.gz`。生产建议做冷备份:传入 `--stop-service spacetimedb.service`,脚本会在打包前停止服务、打包后恢复服务,再上传 OSS;因 `genarrative-api.service` 依赖 `spacetimedb.service`,生产定时冷备份还必须传入 `--restart-service-after genarrative-api.service`,确保备份后 API 随数据库一起恢复。`2026-06-10` release 故障就是现场 unit 漏掉该参数,`03:20` 冷备份停止 SpacetimeDB 后 API 被依赖关系一并停止,备份脚本只恢复了 SpacetimeDB,API 直到人工重启前都不可用;后续现场变更、provision 模板和 Jenkins 归档都必须通过 `npm run check:production-ops` 防止回退。由于 OSS 上传可能受服务器带宽限制,`Genarrative-Stdb-Module-Publish` 默认使用 `DATABASE_BACKUP_MODE=async`:先在 publish 前用 `--defer-upload` 生成本地冷备份和 `.manifest.json`,随后继续执行 publish;发布脚本退出前会用后台 `node ... --upload-archive <tar.gz>` 上传同一份发布前备份,不等待上传完成。发布脚本在校验 wasm 后、执行 `spacetime publish` 前会等待显式 `SPACETIME_SERVER_URL` 的 `/v1/ping` 就绪,默认最多等待 `60` 秒;如生产机器冷备份恢复 `spacetimedb.service` 较慢,可临时设置 `GENARRATIVE_STDB_PUBLISH_READY_TIMEOUT_SECONDS` 调整等待时间。需要强一致发布闸门时改用 `DATABASE_BACKUP_MODE=sync`(等价脚本参数 `--backup-mode sync`),备份会在 publish 前同步打包并上传,失败会阻断 publish;确认已有其他备份窗口时才使用 `DATABASE_BACKUP_MODE=skip`(兼容脚本参数 `--skip-backup`)。若业务不能接受停机窗口,应先规划 SpacetimeDB 原生快照或主备策略,不要直接在写入中的数据目录上做热拷贝并当作强一致备份。
|
||||
|
||||
生产环境变量模板在 `deploy/env/api-server.env.example`:
|
||||
|
||||
@@ -228,6 +235,17 @@ GENARRATIVE_DATABASE_BACKUP_OSS_ACCESS_KEY_SECRET=
|
||||
|
||||
`GENARRATIVE_DATABASE_BACKUP_OSS_BUCKET` 为空时会回退 `ALIYUN_OSS_BUCKET`;AccessKey 默认复用 `ALIYUN_OSS_ACCESS_KEY_ID` / `ALIYUN_OSS_ACCESS_KEY_SECRET`,也可用 `GENARRATIVE_DATABASE_BACKUP_OSS_ACCESS_KEY_ID` / `GENARRATIVE_DATABASE_BACKUP_OSS_ACCESS_KEY_SECRET` 为备份 bucket 单独配置最小权限账号。`Genarrative-Server-Provision` 会创建 `/var/lib/genarrative/database-backups` 并归属 `genarrative:genarrative`,同时安装并启用 `genarrative-database-backup.timer`。手动检查定时器:`systemctl list-timers genarrative-database-backup.timer`;手动触发一次:`systemctl start genarrative-database-backup.service`。
|
||||
|
||||
冷备份后必须做一次只读验收,不要只看 `genarrative-database-backup.service` 是否成功退出:
|
||||
|
||||
```bash
|
||||
systemctl is-active spacetimedb.service genarrative-api.service nginx.service
|
||||
curl -fsS --max-time 5 http://127.0.0.1:3101/v1/ping
|
||||
curl -fsS --max-time 5 http://127.0.0.1:8082/healthz
|
||||
curl -fsS --max-time 5 http://127.0.0.1:8082/readyz
|
||||
curl -fsS --max-time 5 http://127.0.0.1/api/creation-entry/config >/dev/null
|
||||
curl -fsS --max-time 5 http://127.0.0.1/api/runtime/puzzle/gallery >/dev/null
|
||||
```
|
||||
|
||||
## 生产运维
|
||||
|
||||
生产部署当前口径:
|
||||
@@ -238,32 +256,63 @@ Nginx 负责站点和反向代理
|
||||
Jenkins 按 web / api / Spacetime module / build / deploy / publish 拆分
|
||||
```
|
||||
|
||||
### 生产健康巡检
|
||||
|
||||
`Genarrative-Server-Provision` 会安装并启用 `genarrative-health-patrol.timer`,默认每 5 分钟运行一次 `genarrative-health-patrol.service`。巡检脚本随 API release 归档到 `/opt/genarrative/current/scripts/ops/production-health-patrol.mjs`,只读检查:
|
||||
|
||||
- `genarrative-api.service`、`spacetimedb.service`、`nginx.service` 是否 active。
|
||||
- API 直连 `/healthz`、`/readyz`。
|
||||
- SpacetimeDB 直连 `/v1/ping`。
|
||||
- 默认直连 API 端口检查 `/api/creation-entry/config`、`/api/runtime/puzzle/gallery`、`/api/runtime/custom-world-gallery`;如需走 Nginx / 公网域名,在 `/etc/genarrative/health-patrol.env` 配置 `GENARRATIVE_HEALTH_PATROL_PUBLIC_BASE_URL=https://<域名>`。
|
||||
- 最近 15 分钟 `genarrative-api.service`、`spacetimedb.service`、`nginx.service` 的 `err..alert` 日志。
|
||||
|
||||
巡检输出总状态 `OK / WARNING / CRITICAL`;只有 `CRITICAL` 默认让 systemd service 失败,`WARNING` 只写日志和状态文件,避免历史日志噪声把 timer 长期打成失败。最近一次结果写入 `/var/lib/genarrative/health-patrol/status.json`。手动执行:
|
||||
|
||||
```bash
|
||||
systemctl start genarrative-health-patrol.service
|
||||
systemctl status genarrative-health-patrol.service --no-pager
|
||||
journalctl -u genarrative-health-patrol.service -n 80 --no-pager
|
||||
cat /var/lib/genarrative/health-patrol/status.json
|
||||
```
|
||||
|
||||
如需接外部告警,可在 `/etc/genarrative/health-patrol.env` 配置 `GENARRATIVE_HEALTH_PATROL_WEBHOOK_URL`;脚本只会在 `WARNING` 或 `CRITICAL` 时向该 webhook 发送 JSON。未配置 webhook 时,告警来源是 systemd 失败状态、journal 和状态文件。
|
||||
|
||||
`Genarrative-Web-Build` 的主站构建失败若出现 Rollup 报错 `"xxx" is not exported by "src/services/publicWorkCode.ts"`,优先按前端公开作品号工具缺失处理,而不是排查 Jenkins 节点环境。修复时要让 `publicWorkCode.ts` 的 `build<Play>PublicWorkCode` 与 `isSame<Play>PublicWorkCode` 成对导出,并补 `src/services/publicWorkCode.test.ts` 覆盖对应玩法前缀;随后用 `npm run build:production-release -- --component web --name <临时名>` 复现 Jenkins web 构建路径。
|
||||
|
||||
`Genarrative-Web-Build` 会把 `build/<version>/web.tar.gz`、`web.tar.gz.sha256` 和 `release-manifest.json` 直接归档为 Jenkins 构建产物;`Genarrative-Web-Deploy` 只通过 `copyArtifacts` 从指定上游构建复制这些产物,再执行 `scripts/deploy/production-web-deploy.sh`。Web 发布不再读取构建机本地缓存目录,也不再通过 release agent `rsync` 回构建机拉取大包;如果 deploy 找不到 `web.tar.gz`,应先检查上游 Web Build 是否按同一 `BUILD_VERSION` 成功归档产物。
|
||||
`Genarrative-Web-Build` 会把 `build/<version>/web.tar.gz`、`web.tar.gz.sha256`、`release-manifest.json` 和 `scripts/deploy/production-web-deploy.sh` 直接归档为 Jenkins 构建产物;`Genarrative-Web-Deploy` 只通过 `copyArtifacts` 从指定上游构建复制这些产物和部署脚本,不再在目标机器 checkout Git,再执行随构建归档的 `scripts/deploy/production-web-deploy.sh`。Web 发布不再读取构建机本地缓存目录,也不再通过 release agent `rsync` 回构建机拉取大包;如果 deploy 找不到 `web.tar.gz`,应先检查上游 Web Build 是否按同一 `BUILD_VERSION` 成功归档产物。
|
||||
|
||||
`Genarrative-Api-Build` 的 Jenkins 归档产物必须包含 `build/<version>/api-server`、`api-server.sha256`、`release-manifest.json`、`scripts/database-backup-to-oss.mjs`、`scripts/ops/production-health-patrol.mjs`、`scripts/deploy/production-api-deploy.sh`、`scripts/deploy/maintenance-on.sh` 和 `scripts/deploy/maintenance-off.sh`。`deploy/systemd/genarrative-database-backup.service` 从 `/opt/genarrative/current/scripts/database-backup-to-oss.mjs` 执行冷备份,`deploy/systemd/genarrative-health-patrol.service` 从 `/opt/genarrative/current/scripts/ops/production-health-patrol.mjs` 执行巡检;`Genarrative-Api-Deploy` 会从上游 API 构建产物复制部署脚本、备份脚本和巡检脚本,不再在目标机器 checkout Git。如果 API 发布后 current release 中缺少这些脚本,应先检查 `Genarrative-Api-Build` 的 `archiveArtifacts` 和 `Genarrative-Api-Deploy` 的 `copyArtifacts` 过滤器是否仍包含 `build/<version>/scripts/database-backup-to-oss.mjs` 与 `build/<version>/scripts/ops/production-health-patrol.mjs`,不要只在部署机工作区手工补文件。
|
||||
|
||||
`Genarrative-Stdb-Module-Build` 的 Jenkins 归档产物必须包含 `build/<version>/spacetime_module.wasm`、`spacetime_module.wasm.sha256`、`release-manifest.json`、`scripts/deploy/production-stdb-publish.sh`、`scripts/deploy/maintenance-on.sh`、`scripts/deploy/maintenance-off.sh` 和 `scripts/database-backup-to-oss.mjs`。`Genarrative-Stdb-Module-Publish` 只通过 `copyArtifacts` 复制这些产物和发布脚本,不再在目标机器 checkout Git;如果 publish 前备份脚本缺失,应先检查 Stdb Build 的归档列表和 Stdb Publish 的复制过滤器。
|
||||
|
||||
`Genarrative-Web-Build` 打包 `web.tar.gz` 前、`Genarrative-Web-Deploy` 解包后都会把 Web 静态目录规范为目录 `755`、文件 `644`。如果前端页面能打开但 public 图片、字体或音频返回 `403 Forbidden`,优先检查当前 `/srv/genarrative/web` 指向的 release 中对应文件权限是否被异常归档为 `600`,临时恢复可对该 release 的 `web` 目录执行目录 `755`、文件 `644` 的权限修正。
|
||||
|
||||
生产 Jenkins 的 `Pipeline script from SCM` 仍由 Jenkins controller 读取 Jenkinsfile,SCM URL 继续使用 `https://git.genarrative.world/GenarrativeAI/Genarrative.git`。现在所有生产流水线 job 的首次 checkout 都先走 `http://127.0.0.1:3000/GenarrativeAI/Genarrative.git`,失败后回退到 `https://git.genarrative.world/GenarrativeAI/Genarrative.git`;两层 checkout 都必须保留单分支 refspec、`shallow=true`、`depth=1`、`noTags=true` 与 `honorRefspec=true`,后续二次源码确认继续走 `scripts/jenkins-checkout-source.sh`。
|
||||
生产 Jenkins 的 `Pipeline script from SCM` 由 Jenkins controller 读取 Jenkinsfile。`Genarrative-Server-Provision` 是服务器初始化流水线,Job 配置里的 SCM URL 必须使用 controller 本机可访问的仓库路径或内网 Gitea 地址,不能使用 `https://git.genarrative.world/...`;否则日志一开始的 `Checking out git ... to read jenkins/Jenkinsfile.production-server-provision` 就会先从公网拉 Jenkinsfile。构建类流水线仍按各自 Jenkinsfile 的 checkout 口径执行;所有 `GitSCM checkout` 都必须保留单分支 refspec、`shallow=true`、`depth=1`、`noTags=true` 与 `honorRefspec=true`。API / Web / Stdb 发布类流水线不在目标机器 checkout Git,统一执行上游构建归档里的部署脚本,避免产物 commit 与部署脚本 commit 漂移。
|
||||
|
||||
dev 服务器上的 Gitea 内网入口固定为 `http://10.2.0.10/GenarrativeAI/Genarrative.git`,用于 release / dev 等内网 agent 直接拉取仓库,避免绕公网 `git.genarrative.world`。该入口由 dev Nginx `/etc/nginx/conf.d/gitea-internal.conf` 暴露,只允许 `10.2.0.0/16` 和本机访问;Gitea 进程自身仍只监听 `127.0.0.1:3000`,公网域名 `https://git.genarrative.world/` 继续走原有 TLS 反代。验证时从 release 执行 `git ls-remote http://10.2.0.10/GenarrativeAI/Genarrative.git HEAD`,应能直接返回 HEAD。
|
||||
|
||||
`scripts/jenkins-checkout-source.sh` 是生产 Jenkinsfile 内部二次确认源码的统一入口。构建流水线和服务器初始化流水线传入 `COMMIT_HASH` 时,脚本必须先保持 `depth=1` 浅拉,若上游 commit 已在浅历史内则直接校验并 checkout;只有浅历史无法证明 commit 属于目标分支时,才按 `GENARRATIVE_JENKINS_CHECKOUT_DEEPEN_STEPS`(默认 `50 200 1000 5000`)逐步加深,最后才尝试展开完整历史。`Genarrative-Api-Deploy`、`Genarrative-Web-Deploy` 和 `Genarrative-Stdb-Module-Publish` 仍保留上游构建传入的 `COMMIT_HASH` 作为通知和追溯字段,但不再用它在目标机器重新 checkout 部署脚本。
|
||||
|
||||
|
||||
`Genarrative-Stdb-Module-Publish` 在 `Pipeline script from SCM` 阶段如果一开始就报 `No such DSL method 'pipeline'`,优先检查 `jenkins/Jenkinsfile.production-stdb-module-publish` 是否带 UTF-8 BOM。Jenkins Declarative Pipeline 的首个 token 必须是纯 `pipeline`;仓库中的 Jenkinsfile 应保存为 UTF-8 without BOM,只有临时写给 Windows PowerShell 5.1 `-File` 执行的 `.ps1` 才需要按对应 helper 转成带 BOM。验证时可检查文件前三字节不再是 `EF BB BF`,并运行 `validateDeclarativePipeline` 或重放该流水线。
|
||||
|
||||
`Genarrative-Stdb-Module-Build` 或 SpacetimeDB module 构建失败若出现 Rust `E0425 cannot find function migrate_*`,优先排查 `server-rs/crates/spacetime-module/src/runtime/creation_entry_config.rs` 等同文件内默认种子迁移 helper 是否在分支合并时只保留了调用、漏掉了函数定义。`Genarrative-Stdb-Module-Build` 现在运行在 `linux && genarrative-build` 节点上,Checkout 与 Build 都走 bash + cargo + sccache,不再依赖 Windows PowerShell 或 Git Bash。修复时不要直接删除迁移调用;应恢复只纠偏历史默认种子且不覆盖后台手动配置的 helper,并用 `cargo check -p spacetime-module --manifest-path server-rs/Cargo.toml` 复现 Jenkins module 编译路径。
|
||||
`Genarrative-Stdb-Module-Build` 或 SpacetimeDB module 构建失败若出现 Rust `E0425 cannot find function migrate_*`,优先排查 `server-rs/crates/spacetime-module/src/runtime/creation_entry_config.rs` 等同文件内默认种子迁移 helper 是否在分支合并时只保留了调用、漏掉了函数定义。`Genarrative-Stdb-Module-Build` 现在运行在 `linux && genarrative-build` 节点上,Checkout 与 Build 都走 bash + cargo + sccache,不再依赖 Windows PowerShell 或 Git Bash;Stdb module 的 `CARGO_HOME`、`CARGO_TARGET_DIR` 和 `SCCACHE_DIR` 默认落在稳定缓存根 `~/caches/genarrative-jenkins/stdb-module` 下,可用 `GENARRATIVE_STDB_CACHE_ROOT` 覆盖,避免 `WORKSPACE@tmp` 被清理后无改动也触发近似冷构建。修复时不要直接删除迁移调用;应恢复只纠偏历史默认种子且不覆盖后台手动配置的 helper,并用 `cargo check -p spacetime-module --manifest-path server-rs/Cargo.toml` 复现 Jenkins module 编译路径。
|
||||
|
||||
`Genarrative-Server-Provision` 现在也运行在 `linux && genarrative-build` / `linux && genarrative-release-deploy` 节点上,`Prepare Provision Tools` 会在 Linux build 节点直接准备 SpacetimeDB 与 `otelcol-contrib` 交付件,再 stash 给后续发布阶段;旧 Windows 下载 helper 已退役。`Genarrative-Stdb-Module-Build`、`Genarrative-Server-Provision` 和 `Genarrative-Notify-Email` 都不再需要单独的 Windows 节点口径。
|
||||
`Genarrative-Server-Provision` 只做服务器初始化,不再承担构建职责。流水线全程运行在目标服务器 agent:`DEPLOY_TARGET=development` 使用 `linux && genarrative-dev-deploy`,`DEPLOY_TARGET=release` 使用 `linux && genarrative-release-deploy`;`Prepare Provision Tools` 也在同一个目标 agent 工作区内准备 SpacetimeDB 与 `otelcol-contrib` 交付件,不再切到 `linux && genarrative-build`,也不再 stash 给后续阶段。`SOURCE_GIT_REMOTE_URL` 必须显式填写为目标 agent 可访问的本机路径、`file:///` 地址、localhost / 127.0.0.1、RFC1918 内网 HTTP Git 地址、单标签内网主机名或 `.local` / `.lan` / `.internal` 地址;这条流水线不配置公网 Git 备用地址,目标 agent 拉不到内网源就应直接失败。真实初始化会写入 `/etc` / systemd / Nginx、创建系统用户并修改服务,目标 dev / release agent 非 dry-run 时都必须具备 root 权限。
|
||||
|
||||
生产环境变量模板:`deploy/env/api-server.env.example`。真实密钥只放服务器,不提交 Git,不写入文档示例。
|
||||
|
||||
`Genarrative-Server-Provision` 会安装基础构建依赖、systemd 模板和 Nginx 站点模板。Ubuntu / apt 目标机会额外安装 `libnginx-mod-http-brotli-filter` 与 `libnginx-mod-http-brotli-static`,随后由 `scripts/jenkins-server-provision.sh` 通过临时 `nginx -t` 配置探测 Brotli 指令是否可用;该临时配置必须先 `include /etc/nginx/modules-enabled/*.conf`,因为 apt 安装的 Brotli 是动态模块,不会出现在普通 `nginx -V` 编译参数里。探测成功才在渲染后的 `deploy/nginx/genarrative.conf` / `genarrative-dev-http.conf` 中启用 Brotli,避免未安装模块的机器直接写入无效配置。Provision 写入 Genarrative Nginx 站点时会把 `/etc/nginx/sites-enabled/default*` 移到 `/etc/nginx/sites-disabled/`,避免 Debian / Certbot 默认站点继续占用 `genarrative.world` / `www.genarrative.world` 并在 `nginx -T` 中出现 `conflicting server name ... ignored`。如果 `nginx -t` 失败,脚本会恢复写入前的 Genarrative 配置和被移动的默认站点。
|
||||
`Genarrative-Server-Provision` 会安装 systemd 模板和 Nginx 站点模板,不再安装 clang / lld / pkg-config / OpenSSL headers / sccache 等通用构建链依赖。因 VectorEngine 图片上游 POST 已改用 `libcurl`,当前 Linux release 构建出的 `api-server` 运行时需要 `OPENSSL_3.2.0` 符号;Ubuntu 24.04 apt 默认只提供 OpenSSL 3.0.x,不能直接满足该符号版本。Provision 会把 OpenSSL `3.2.0` 独立安装到 `/opt/genarrative/openssl-3.2.0`,校验官方 tarball SHA256,并只通过 `genarrative-api.service` 的 `LD_LIBRARY_PATH=/opt/genarrative/openssl-3.2.0/lib64:/opt/genarrative/openssl-3.2.0/lib` 让 api-server 使用,避免替换系统 OpenSSL 或影响 ssh / nginx / apt。Ubuntu / apt 目标机会额外安装 `libnginx-mod-http-brotli-filter` 与 `libnginx-mod-http-brotli-static`,随后由 `scripts/jenkins-server-provision.sh` 通过临时 `nginx -t` 配置探测 Brotli 指令是否可用;该临时配置必须先 `include /etc/nginx/modules-enabled/*.conf`,因为 apt 安装的 Brotli 是动态模块,不会出现在普通 `nginx -V` 编译参数里。探测成功才在渲染后的 `deploy/nginx/genarrative.conf` / `genarrative-dev-http.conf` 中启用 Brotli,避免未安装模块的机器直接写入无效配置。Provision 写入 Genarrative Nginx 站点时会把 `/etc/nginx/sites-enabled/default*` 移到 `/etc/nginx/sites-disabled/`,避免 Debian / Certbot 默认站点继续占用 `genarrative.world` / `www.genarrative.world` 并在 `nginx -T` 中出现 `conflicting server name ... ignored`。如果 `nginx -t` 失败,脚本会恢复写入前的 Genarrative 配置和被移动的默认站点。
|
||||
|
||||
50 HTTP req/s 首版压测优化口径:
|
||||
|
||||
- `api-server` 生产模板默认 `GENARRATIVE_API_LISTEN_BACKLOG=1024`、`GENARRATIVE_API_WORKER_THREADS=4`;本地未设置 worker threads 时继续使用 Tokio 默认值。
|
||||
- `GENARRATIVE_API_MAX_CONCURRENT_REQUESTS=512` 开启应用内 HTTP 并发背压;`GENARRATIVE_API_GALLERY_MAX_CONCURRENT_REQUESTS=320`、`GENARRATIVE_API_DETAIL_MAX_CONCURRENT_REQUESTS=64`、`GENARRATIVE_API_ADMIN_MAX_CONCURRENT_REQUESTS=16` 分别限制公开列表、公开详情和后台 API 热路径。超过许可时直接返回 `429 Too Many Requests` 和 `Retry-After: 1`,`/healthz` 不受该限制。这些值不是 RPS 限速;如果压测中 429 上升但内存和 p95 收敛,说明背压正在保护进程。直连 `api-server` 的极高 RPS 压测若出现 `connection refused`,通常已经打到 TCP 监听 / accept 层,应同时检查 backlog、Nginx upstream keepalive 和前置限流。
|
||||
- `genarrative-api.service` 设置 `LimitNOFILE=65535`、`TasksMax=2048`;上线后用 `systemctl show genarrative-api.service -p LimitNOFILE -p TasksMax` 和 `cat /proc/$(pidof api-server)/limits` 核对。
|
||||
- Server provision 不再通过 Windows helper 下载。`Genarrative-Server-Provision` 的 `Prepare Provision Tools` 在 Linux build 节点直接准备 `spacetime-x86_64-unknown-linux-gnu.tar.gz` 和 `otelcol-contrib_0.151.0_linux_amd64.tar.gz`,再 stash `provision-tools/` 给后续发布阶段;如果 build 节点需要代理,在 `PROVISION_DOWNLOAD_PROXY` 配置 Linux 侧可访问的 HTTP 代理。后续 Linux 目标节点只消费 `provision-tools/`,不再回退到外网下载。
|
||||
- `Genarrative-Stdb-Module-Build`、`Genarrative-Web-Build`、`Genarrative-Api-Build`、`Genarrative-*Deploy`、`Genarrative-Database-Import/Export`、`Genarrative-Full-Build-And-Deploy` 和 `Genarrative-Notify-Email` 的生产流水线现都以 Linux agent 为主,统一走 `http://127.0.0.1:3000/GenarrativeAI/Genarrative.git` 优先、`https://git.genarrative.world/GenarrativeAI/Genarrative.git` 备用的 checkout 口径。
|
||||
- `GENARRATIVE_API_MAX_CONCURRENT_REQUESTS=512` 开启应用内 HTTP 并发背压;`GENARRATIVE_API_GALLERY_MAX_CONCURRENT_REQUESTS=320`、`GENARRATIVE_API_DETAIL_MAX_CONCURRENT_REQUESTS=64`、`GENARRATIVE_API_ADMIN_MAX_CONCURRENT_REQUESTS=16` 分别限制公开列表、公开详情和后台 API 热路径。超过许可时直接返回 `429 Too Many Requests` 和 `Retry-After: 1`,`/healthz` 与 `/readyz` 不受该限制。这些值不是 RPS 限速;如果压测中 429 上升但内存和 p95 收敛,说明背压正在保护进程。直连 `api-server` 的极高 RPS 压测若出现 `connection refused`,通常已经打到 TCP 监听 / accept 层,应同时检查 backlog、Nginx upstream keepalive 和前置限流。
|
||||
- `api-server` 正常运行时 `/healthz` 只返回进程存活状态,`/readyz` 会同时检查进程是否仍接收新流量和 SpacetimeDB 连接租约是否健康;收到 `SIGINT` / `SIGTERM` 后会先把 readiness 标记为不可用,再让 Axum 停止接新连接并等待已有 HTTP 请求排空。systemd 仍以 `KillSignal=SIGINT` 停服务,`TimeoutStopSec=90` 作为长请求排空上限。
|
||||
- SpacetimeDB 健康检查默认使用 `GENARRATIVE_SPACETIME_HEALTH_CHECK_TIMEOUT_SECONDS=2` 的短等待窗口,和业务 procedure 的 `GENARRATIVE_SPACETIME_PROCEDURE_TIMEOUT_SECONDS` 分开。`/readyz` 失败时 `details.spacetime.stage` 会标出当前卡住阶段:`pool_acquire`、`connect_build`、`connect_handshake`、`read_model_subscribe`、`procedure_result`、`reducer_result` 或 `read_cache`;`elapsedMs` / `timeoutMs` 用于确认是否命中健康检查窗口。业务请求日志也会写入 `operation_kind`、`operation_name`、`spacetime_stage` 和 `elapsed_ms`,后续 45 秒超时不再只靠 Nginx `request_time=45s` 推断。
|
||||
- `genarrative-api.service` 设置 `LimitNOFILE=65535`、`TasksMax=2048`;上线后用 `systemctl show genarrative-api.service -p LimitNOFILE -p TasksMax -p TimeoutStopUSec` 和 `cat /proc/$(pidof api-server)/limits` 核对。
|
||||
- Server provision 不再通过 Windows helper 下载,也不再通过 Linux build 节点中转工具包。`Prepare Provision Tools` 在目标 dev / release agent 工作区内先检查 `/usr/local/bin/otelcol-contrib` 与 `${SPACETIME_ROOT}/bin/current`:版本已满足时直接复用目标机现有文件生成 `provision-tools/`,只有缺失或版本不匹配时才使用 `PROVISION_DOWNLOADS_DIR` 里的本地包或从配置的下载源准备 SpacetimeDB `2.4.1` / `otelcol-contrib 0.151.0`;如果目标服务器下载需要代理,在 `PROVISION_DOWNLOAD_PROXY` 配置目标机可访问的 HTTP 代理。
|
||||
- 除 `Genarrative-Server-Provision` 外,`Genarrative-Stdb-Module-Build`、`Genarrative-Web-Build`、`Genarrative-Api-Build`、`Genarrative-*Deploy`、`Genarrative-Database-Import/Export`、`Genarrative-Full-Build-And-Deploy` 和 `Genarrative-Notify-Email` 的生产流水线现都以 Linux agent 为主,仍按各自 Jenkinsfile 的 checkout 口径执行。Server provision 不使用公网备用 Git 源。
|
||||
- `otelcol-contrib.service` 作为可选系统服务加入 provision,默认监听 `127.0.0.1:4317/4318` 并使用 `deploy/otelcol/genarrative-debug.yaml`。api-server 是否发送 OTLP 仍由 `GENARRATIVE_OTEL_ENABLED` 控制,服务 unit 见 `deploy/systemd/otelcol-contrib.service`。
|
||||
- Nginx `/api/` 与 `/admin/api/` 通过 `genarrative_api` upstream 代理到 `127.0.0.1:8082`,upstream keepalive 为 64;`limit_conn` 负责连接 / 并发保护,`limit_req` 负责入口 RPS 快拒绝。当前模板把公开 gallery list 单独放到 `genarrative_gallery_rps`,默认 `rate=5000r/s`、`burst=4096`、`limit_conn=320`;公开详情和普通 API 放到 `genarrative_api_rps`,后台 API 放到 `genarrative_admin_rps`。通用 `/api` location 设置 `client_max_body_size 64m` 是反代兜底,防止拼图入口页 / 新增关卡本地参考图 Data URL 或旧兼容请求在到达 `api-server` 前被默认 1 MiB 上限拦截;拼图本地参考图前后端统一限制 6MB,历史图片仍提交 `referenceImageAssetObjectId(s)`。若线上出现 `413 Request Entity Too Large` 且 access log 中 `request_time=0.000`、`upstream_status=-`,说明请求在 Nginx 层被拦截,先用 `nginx -T | grep client_max_body_size` 检查 release 模板是否已渲染并 reload,同时检查前端是否超出 6MB 或错误提交了未压缩大图。`limit_conn_status 429` 和 `limit_req_status 429` 必须在 HTTP 与 HTTPS server 中同时生效;若线上压测看到 `limiting connections by zone "genarrative_api_conn"` 却返回 503,优先检查 `nginx -T` 里 HTTPS server 是否缺少这些状态码,以及 `/api/runtime/puzzle/gallery` 是否误落到通用 `location ~ ^/api` 的 `limit_conn=64`。压测时看 `/var/log/nginx/genarrative.access.log` 中的 `request_time`、`upstream_connect_time`、`upstream_header_time`、`upstream_response_time`、`upstream_status`、`request_id`。
|
||||
- 作品列表 K6 脚本一次 iteration 默认请求两个公开接口,因此约 50 HTTP req/s 的目标命令使用 `SCENARIO=spike START_RPS=5 PEAK_RPS=25 HOLD=60s END_RPS=5 DETAIL_RATIO=0 npm run loadtest:k6:works`。
|
||||
@@ -281,7 +330,7 @@ npm run container:k6
|
||||
npm run container:down
|
||||
```
|
||||
|
||||
容器方案默认暴露 `http://127.0.0.1:18080`,`api-server` 在容器内监听 `0.0.0.0:8082`,Nginx 通过 `api-server:8082` upstream 反代 `/api/` 和 `/admin/api/`。SpacetimeDB 也纳入 compose,容器内由 `spacetimedb:3101` 提供服务,宿主机通过 `http://127.0.0.1:13101` 进行模块发布;Collector 镜像使用 `otel/opentelemetry-collector-contrib:0.151.0`。生产 provision 侧现在由 Linux build 节点直接准备 `provision-tools/otelcol-contrib`,再交给后续 Linux 发布阶段安装本机 `otelcol-contrib.service`,真实库名、token 和外部服务密钥只写本地 `deploy/container/api-server.env`,不提交 Git。完整拓扑、端口、k6 参数和 OTLP debug exporter 使用方法见 `deploy/container/README.md`。
|
||||
容器方案默认暴露 `http://127.0.0.1:18080`,`api-server` 在容器内监听 `0.0.0.0:8082`,Nginx 通过 `api-server:8082` upstream 反代 `/api/` 和 `/admin/api/`。SpacetimeDB 也纳入 compose,容器内由 `spacetimedb:3101` 提供服务,宿主机通过 `http://127.0.0.1:13101` 进行模块发布;Collector 镜像使用 `otel/opentelemetry-collector-contrib:0.151.0`。生产 provision 侧现在由目标 dev / release agent 自己准备 `provision-tools/otelcol-contrib`,并安装本机 `otelcol-contrib.service`,真实库名、token 和外部服务密钥只写本地 `deploy/container/api-server.env`,不提交 Git。完整拓扑、端口、k6 参数和 OTLP debug exporter 使用方法见 `deploy/container/README.md`。
|
||||
`npm run container:config` 默认只做 quiet 校验,避免把本地 env 中的 token 展开到终端;确需排查完整 compose 时再传 `-- --print`。
|
||||
|
||||
OpenTelemetry 现阶段默认开启 OTLP traces / metrics / logs,但本地日志与 Nginx 文件日志仍保留:
|
||||
@@ -294,7 +343,8 @@ OpenTelemetry 现阶段默认开启 OTLP traces / metrics / logs,但本地日
|
||||
- debug exporter / Rider 转发都会同时接收 traces、metrics 和 logs。
|
||||
- api-server 会随 metrics 发送进程级指标:`process.memory.usage`、`process.memory.virtual`、`process.cpu.time`、`genarrative.process.cpu.usage_percent`、`process.thread.count`、`genarrative.process.memory.private`;Windows 额外发送 `process.windows.handle.count`,Linux 额外发送 `process.unix.file_descriptor.count`。这些指标只描述当前进程,不携带请求、用户或作品 label。
|
||||
- HTTP 运行态补充发送 `genarrative.http.server.response_bodies.in_flight` 与 `genarrative.http.server.request_permits.available`,后者带低基数 `pool=default|gallery|detail|admin` label,用于区分业务 handler / 背压 permit 是否仍被占用;拼图广场热点缓存补充发送 `genarrative.puzzle_gallery.cache.*` 指标,记录 fresh hit、stale hit、未命中、后台刷新开始 / 失败、重建耗时和预序列化 data JSON 字节数。
|
||||
- 外部 API 失败统一发送 OTLP 并落库。当前 VectorEngine `gpt-image-2` 图片生成 / 编辑失败由 `platform-image` provider 输出低基数字段结构化日志,字段包括 provider、endpoint、failure_stage、status、source、source_chain、source_chain_depth、timeout、retryable、latency_ms、prompt_chars、reference_image_count、image_model 和 raw_excerpt;`api-server` 再记录指标 `genarrative.external_api.failures{provider,failure_stage,status_class,retryable}`,并写入 `tracking_event`,`event_key = external_api_call_failure`、`module_key = external-api`、`scope_kind = module`、`scope_id = provider`。调用方能拿到身份上下文时,失败事件还会在行级 `user_id` / `owner_user_id` / `profile_id` 和 `metadata_json.userId` / `metadata_json.profileId` / `metadata_json.requestId` / `metadata_json.errorSource` 中记录触发者、草稿 / 作品作用域、请求标识和传输错误链。排障时先按 provider / failureStage 聚合,再下钻 userId / profileId,最后结合 request 日志、errorSource 和上游响应 excerpt 判断是限流、超时、解析失败还是未返回图片。
|
||||
- 外部 API 失败统一发送 OTLP 并落库。当前 VectorEngine `gpt-image-2` 图片生成 / 编辑失败由 `platform-image` provider 输出结构化日志字段,字段包括 provider、endpoint、failure_stage、status、source、source_chain、source_chain_depth、timeout、retryable、latency_ms、prompt_chars、reference_image_count、image_model、request_params 和 raw_excerpt;图片编辑请求参数日志还会带 reference_image_bytes_total,并在 request_params.referenceImages 中记录每个 multipart `image` part 的 fileName、mimeType 和 bytes,不记录 API key 或原始图片 bytes;`api-server` 再记录指标 `genarrative.external_api.failures{provider,failure_stage,status_class,retryable}`,并写入 `tracking_event`,`event_key = external_api_call_failure`、`module_key = external-api`、`scope_kind = module`、`scope_id = provider`。调用方能拿到身份上下文时,失败事件还会在行级 `user_id` / `owner_user_id` / `profile_id` 和 `metadata_json.userId` / `metadata_json.profileId` / `metadata_json.requestId` / `metadata_json.errorSource` 中记录触发者、草稿 / 作品作用域、请求标识和传输错误链。排障时先按 provider / failureStage 聚合,再下钻 userId / profileId,最后结合 request 日志、errorSource 和上游响应 excerpt 判断是限流、超时、解析失败还是未返回图片。
|
||||
- OSS 平台适配器也输出结构化日志,覆盖 `sign_post_object`、`sign_get_object_url`、`head_object` 和 `put_object`。排查资产签名、上传或确认失败时,先按 `provider=aliyun-oss` 与 `operation` 过滤,再看 `object_key` / `key_prefix`、`status`、`status_class`、`error_kind`、`content_length`、`content_type` 和 `elapsed_ms`;日志不得包含 AccessKey、policy、signature、Authorization header 或完整 signed URL。排查 generated 图片重复下载时,先确认前端输入是否为 `/generated-*` legacy path 或可归一化的 `https://*.oss-*.aliyuncs.com/generated-*`;正确链路应先调 `/api/assets/read-url`,再由浏览器请求 signed URL,且同一路径、同一 `refreshKey` 版本和未临近过期的 signed URL 应复用。新上传 generated 私有对象应带 `Cache-Control: public, max-age=31536000, immutable`;旧对象若只有 `ETag` / `Last-Modified`,浏览器会走 304 协商缓存而不是长期强缓存,可通过刷新 OSS 元数据或 CDN 配置补齐。
|
||||
- SpacetimeDB 观测分为两类:procedure / reducer 调用继续用 `genarrative.spacetime.procedure.*`,订阅本地 cache 读使用 `genarrative.spacetime.read.*`。`read=list_puzzle_gallery` 表示拼图广场当前从 `puzzle_gallery_card_view` 本地 cache 读取,不再每个 HTTP 请求调用 `list_puzzle_gallery` procedure。
|
||||
- 本地 Windows 直连压测的内存高水位要结合 K6 VU / 连接数解释。250 RPS 下过高 `PREALLOCATED_VUS` 可能让 300 个本地 Established 连接把 `api-server` private memory 瞬时推到 GB 级,且 `/healthz` 小响应也能复现;若压测结束后回落、`response_bodies.in_flight` 和背压 permit 未显示业务积压,应优先按连接 / 发送链路高水位处理,而不是判断为 SpacetimeDB 或 JSON 缓存泄漏。
|
||||
- Rider 的 Logs 面板只展示 log event 自身字段,不会自动展开父 span 的全部 attributes;请求完成日志会直接带 `request_id`、`http.request.method`、`http.route`、`url.scheme`、`url.path`、`http.response.status_code`、`status_class`、`latency_ms` 和 `slow_request`,完整链路继续到 Traces 面板按 trace/span 查看。
|
||||
@@ -307,8 +357,9 @@ OpenTelemetry 现阶段默认开启 OTLP traces / metrics / logs,但本地日
|
||||
- `GENARRATIVE_SPACETIME_TOKEN`
|
||||
- `GENARRATIVE_DATABASE_BACKUP_*`
|
||||
- `GENARRATIVE_LLM_*`
|
||||
- `APIMART_*`
|
||||
- `VECTOR_ENGINE_*`
|
||||
- ~~`APIMART_*`~~(已弃用,LLM 文本调用统一迁移到 VectorEngine)
|
||||
- `APIMART_*`(历史残留,创意 Agent LLM 已迁移到 VectorEngine)
|
||||
- `HYPER3D_*`
|
||||
- `VOLCENGINE_SPEECH_*`
|
||||
- `DASHSCOPE_*`
|
||||
@@ -318,6 +369,14 @@ OpenTelemetry 现阶段默认开启 OTLP traces / metrics / logs,但本地日
|
||||
|
||||
结构化创作 / RPG 的 Responses JSON 链路默认不打开 `web_search`;本地和生产如需联网增强,必须显式配置 `GENARRATIVE_RPG_LLM_WEB_SEARCH_ENABLED=true` 或 `GENARRATIVE_CREATION_AGENT_LLM_WEB_SEARCH_ENABLED=true`。如果上游未开通工具,Responses 可能先吐自然语言再返回 `ToolNotOpen`,这类报错应按工具不可用排查,不要先当成 JSON 解析 bug。
|
||||
|
||||
创意 Agent `gpt-5` 文本链路已从 APIMart 切到 VectorEngine:`api-server` 读取 `VECTOR_ENGINE_BASE_URL` / `VECTOR_ENGINE_API_KEY` 构造 OpenAI-compatible LLM client,并自动补齐 `/v1` 前缀用于 Responses 协议。排查或切换密钥后,可在本地运行:
|
||||
|
||||
```bash
|
||||
node scripts/test-ve-llm.mjs
|
||||
```
|
||||
|
||||
该脚本读取仓库根目录 `.env.secrets.local` 中的 `VECTOR_ENGINE_BASE_URL` 和 `VECTOR_ENGINE_API_KEY`,依次探测 `/v1/models`、`/v1/chat/completions`、`/v1/responses`、`gpt-5` Chat Completions 和基础 JSON 输出能力;脚本只输出 HTTP 状态、耗时、模型和截断摘要,不应打印密钥。若 `.env.secrets.local` 不存在,先补本地 secrets 文件再运行,不要把 secrets 提交进仓库。
|
||||
|
||||
### 手机验证码短信
|
||||
|
||||
手机验证码发送走阿里云普通短信 `SendSms`,验证码由 `module-auth` 在当前 `api-server` 进程内生成、哈希存储和校验,不再调用阿里云托管验证码的 `SendSmsVerifyCode` / `CheckSmsVerifyCode`。因此 `api-server` 重启后,已发送但未校验的验证码会失效。
|
||||
@@ -351,7 +410,7 @@ cargo test -p platform-auth --manifest-path server-rs/Cargo.toml aliyun_send_sms
|
||||
- `profile_task_reward_claim`
|
||||
- `profile_wallet_ledger`
|
||||
|
||||
个人任务首版 scope 仅支持 `user`。后台、RPG、大鱼吃小鱼、Visual Novel、Story、Combat 等特定链路按 tracking 中间件排除规则处理;作品游玩统一使用 `work_play_start`。
|
||||
个人任务首版 scope 仅支持 `user`。每日登录任务按北京时间自然日 0 点重置;用户已登录并停留在“我的”页跨日时,前端需要先非阻断调用 refresh session 以写入新业务日 `daily_login`,再请求 `/api/profile/tasks` 刷新任务中心。后台、RPG、大鱼吃小鱼、Visual Novel、Story、Combat 等特定链路按 tracking 中间件排除规则处理;作品游玩统一使用 `work_play_start`。
|
||||
|
||||
外部 API 失败审计复用 `tracking_event`,不新增表。失败事件优先写入本机 tracking outbox,再由后台 worker 批量落库;如果 outbox 因权限、磁盘或保护阈值不可写,会回退同步直写 SpacetimeDB。`metadata_json` 包含 endpoint、operation、failureStage、statusCode、statusClass、timeout、retryable、errorMessage、errorSource、latencyMs、promptChars、referenceImageCount、imageModel、rawExcerpt、userId、profileId 和 requestId;其中 `userId` 是触发生成的用户,`profileId` 是调用方传入的草稿 / 作品 / 场景作用域,`requestId` 用于回查同一次 HTTP 请求日志,入口拿不到上下文时允许为空。常用查询:
|
||||
|
||||
@@ -380,7 +439,7 @@ ORDER BY failures DESC, last_seen DESC
|
||||
LIMIT 100;
|
||||
```
|
||||
|
||||
VectorEngine `request_send` 且 `timeout = true` 的记录表示 `reqwest::Error::is_timeout()` 判定为超时,常见于连接、发送请求体、等待上游首包或上游长时间无响应;`errorSource` 会保存 reqwest 底层错误链,若只看到 `client error (SendRequest)`,表示 Hyper 只暴露到发送请求阶段,仍不等于最终根因。若 `statusCode` 为空,应优先查同一 `requestId` 的 `api-server` request 日志、provider 日志 `source_chain`、Nginx / 出口网络、VectorEngine 可用性和请求体大小;若已有 `502`、`429 moderation_blocked` 等状态码,则按上游网关或内容审核失败单独处理,不要和传输超时混为一类。
|
||||
VectorEngine `request_send` 且 `timeout = true` 的记录表示 `reqwest::Error::is_timeout()` 判定为超时,常见于连接、发送请求体、等待上游首包或上游长时间无响应;`errorSource` 会保存 reqwest 底层错误链,若只看到 `client error (SendRequest)`,表示 Hyper 只暴露到发送请求阶段,仍不等于最终根因。若 `statusCode` 为空,应优先查同一 `requestId` 的 `api-server` request 日志、provider 日志 `source_chain`、request_params、reference_image_bytes_total、Nginx / 出口网络、VectorEngine 可用性和请求体大小;若已有 `502`、`429 moderation_blocked` 等状态码,则按上游网关或内容审核失败单独处理,不要和传输超时混为一类。
|
||||
|
||||
tracking outbox 默认配置:
|
||||
|
||||
@@ -390,9 +449,10 @@ GENARRATIVE_TRACKING_OUTBOX_DIR=/var/lib/genarrative/tracking-outbox
|
||||
GENARRATIVE_TRACKING_OUTBOX_BATCH_SIZE=500
|
||||
GENARRATIVE_TRACKING_OUTBOX_FLUSH_INTERVAL_MS=1000
|
||||
GENARRATIVE_TRACKING_OUTBOX_MAX_BYTES=268435456
|
||||
GENARRATIVE_API_SHUTDOWN_OUTBOX_FLUSH_TIMEOUT_MS=5000
|
||||
```
|
||||
|
||||
outbox 采用 NDJSON 文件保存原始事件。达到 `BATCH_SIZE` 时会立刻把当前 active 文件原子封存为 sealed 文件,并马上切到新的 active 继续写入;后台 worker 异步 flush sealed 文件,HTTP 请求线程不等待 SpacetimeDB。`FLUSH_INTERVAL_MS` 只负责兜底封存长时间未满批的 active 文件。SpacetimeDB 批量 procedure 返回成功后删除 sealed 文件,失败则保留文件并重试。`MAX_BYTES` 是磁盘保护阈值,不是 flush 阈值;超过后低价值 route tracking 可以被丢弃并记录日志 / 指标,关键同步事件不进入该丢弃路径。sealed 文件若出现无法解析的坏行,会重命名为 `corrupt-*` 隔离并记录 `genarrative.tracking_outbox.files.corrupt` 指标,避免一个坏文件阻塞后续批量入库。该机制提供至少一次投递语义,依赖 `tracking_event.event_id` 幂等跳过重复事件。
|
||||
outbox 采用 NDJSON 文件保存原始事件。达到 `BATCH_SIZE` 时会立刻把当前 active 文件原子封存为 sealed 文件,并马上切到新的 active 继续写入;后台 worker 异步 flush sealed 文件,HTTP 请求线程不等待 SpacetimeDB。`FLUSH_INTERVAL_MS` 只负责兜底封存长时间未满批的 active 文件。SpacetimeDB 批量 procedure 返回成功后删除 sealed 文件,失败则保留文件并重试。`MAX_BYTES` 是磁盘保护阈值,不是 flush 阈值;超过后低价值 route tracking 可以被丢弃并记录日志 / 指标,关键同步事件不进入该丢弃路径。sealed 文件若出现无法解析的坏行,会重命名为 `corrupt-*` 隔离并记录 `genarrative.tracking_outbox.files.corrupt` 指标,避免一个坏文件阻塞后续批量入库。api-server 收到退出信号后会在 `GENARRATIVE_API_SHUTDOWN_OUTBOX_FLUSH_TIMEOUT_MS` 窗口内封存 active 文件并尽力 flush sealed 文件,超时或 SpacetimeDB 暂不可用时保留本地文件给下次启动继续投递。该机制提供至少一次投递语义,依赖 `tracking_event.event_id` 幂等跳过重复事件。
|
||||
|
||||
release 机器如果日志每秒刷 `tracking outbox ... Permission denied (os error 13)`,先检查 `/etc/genarrative/api-server.env` 是否缺少 `GENARRATIVE_TRACKING_OUTBOX_DIR`。缺少时 `api-server` 会回退到本地开发默认相对路径 `server-rs/.data/tracking-outbox`,而 systemd 的工作目录是只读发布目录 `/opt/genarrative/releases/<version>`,`genarrative` 用户无法在其中创建 `server-rs`。修复顺序:
|
||||
|
||||
@@ -403,7 +463,9 @@ systemctl restart genarrative-api.service
|
||||
journalctl -u genarrative-api.service --since '30 seconds ago' --no-pager | grep -E 'tracking outbox|Permission denied|os error 13'
|
||||
```
|
||||
|
||||
`Genarrative-Server-Provision` 和 `Genarrative-Api-Deploy` 会在保留旧 `/etc/genarrative/api-server.env` 的前提下补齐缺失的 tracking outbox 运行态路径,并确保 `/var/lib/genarrative/tracking-outbox` 归属 `genarrative:genarrative`。用户认证真相源只允许在 SpacetimeDB 正式认证表(`user_account` / `auth_identity` / `refresh_session`)恢复;不要再配置或依赖 `GENARRATIVE_AUTH_STORE_PATH` / `auth-store.json`,`module-auth` 也不再维护本地文件持久化;`auth_store_snapshot` 只保留行级记录,不再保存为单行 `default` 聚合快照,且旧 `get_auth_store_snapshot` / `upsert_auth_store_snapshot` / `import_auth_store_snapshot` 入口已经删除。如果 `api-server` 启动时连不上 SpacetimeDB,会等待启动恢复,超时后继续监听但进入依赖不可用模式,所有请求统一返回 `503 SERVICE_UNAVAILABLE`,错误详情包含 `reason=spacetime_startup_unavailable`,以避免用空本地状态或旧快照覆盖认证表。
|
||||
`Genarrative-Server-Provision` 和 `Genarrative-Api-Deploy` 会在保留旧 `/etc/genarrative/api-server.env` 的前提下补齐缺失的 tracking outbox 运行态路径,并确保 `/var/lib/genarrative/tracking-outbox` 归属 `genarrative:genarrative`。用户认证真相源只允许在 SpacetimeDB 正式认证表(`user_account` / `auth_identity` / `refresh_session`)恢复;不要再配置或依赖 `GENARRATIVE_AUTH_STORE_PATH` / `auth-store.json`,`module-auth` 也不再维护本地文件持久化;`auth_store_snapshot` 只保留行级记录,不再保存为单行 `default` 聚合快照,且旧 `get_auth_store_snapshot` / `upsert_auth_store_snapshot` / `import_auth_store_snapshot` 入口已经删除。如果 `api-server` 启动时连不上 SpacetimeDB,会持续重试启动恢复,直到认证工作集从 SpacetimeDB 正式表恢复成功后才开始监听 HTTP,以避免用空本地状态或旧快照覆盖认证表。
|
||||
|
||||
前端登录态恢复只把 `/api/auth/refresh` 的 `401` / `403` 当成权威失效信号;服务器重启窗口里的 `502` / `503` / `504`、浏览器 `Failed to fetch` 或 refresh 响应契约异常都必须保留已有本地 access token,不触发全局 auth 变化。refresh 成功响应以共享契约 `RefreshSessionResponse { token }` 为准,前端不要额外要求业务 `ok` 字段。排查“重启后用户都掉线”时,先区分前端是否被暂时不可用清掉本地 token,再检查 SpacetimeDB 正式认证表是否缺 `user_account` / `refresh_session` 数据。
|
||||
|
||||
常用检查思路:
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
- H5 与桌面微信环境仍分别走 `wechat_h5` / `wechat_native`,不进入虚拟支付链路。
|
||||
- `session_key` 只保存在后端认证仓储内,用于计算虚拟支付用户态签名,不下发给前端。
|
||||
- 客户端支付成功回调只代表已拉起支付并返回成功;最终到账仍以后端虚拟支付消息推送写入订单为准,普通微信支付订单则继续走微信支付 V3 notify / query。虚拟支付订单的确认接口只读取本地订单真相,不再用普通微信支付 V3 查单。
|
||||
- 小程序 WebView 默认进入时会静默调用 `wx.login` 刷新后端微信登录态,避免历史登录用户只有前端 JWT、后端缺少 `session_key` 时无法生成虚拟支付签名。
|
||||
- 小程序 WebView 普通进入不预登录;H5 触发受保护入口或支付前必须保留 `clientRuntime=wechat_mini_program` 等宿主上下文,并用 `MicroMessenger + miniProgram` User-Agent 兜底识别首点 bridge 未就绪场景,再跳转小程序原生授权态,确保后端拿到带 `session_key` 的微信登录态。
|
||||
|
||||
## 关键文件
|
||||
|
||||
@@ -17,7 +17,9 @@
|
||||
- 充值入口:`src/components/rpg-entry/RpgEntryHomeView.tsx`
|
||||
- 小程序支付承接页:`miniprogram/pages/wechat-pay/index.shared.js`
|
||||
- API 契约:`packages/shared/src/contracts/runtime.ts`、`server-rs/crates/shared-contracts/src/runtime.rs`
|
||||
- 后端下单与签名:`server-rs/crates/api-server/src/runtime_profile.rs`
|
||||
- 后端下单与订单编排:`server-rs/crates/api-server/src/runtime_profile.rs`、`server-rs/crates/api-server/src/wechat/pay.rs`
|
||||
- 微信支付 / 虚拟支付协议适配:`server-rs/crates/platform-wechat/src/pay.rs`
|
||||
- 微信订阅消息协议适配:`server-rs/crates/platform-wechat/src/subscribe_message.rs`
|
||||
- WebView 回流确认:`GET /api/profile/recharge/orders/{orderId}/wechat/events`、`POST /api/profile/recharge/orders/{orderId}/wechat/confirm`
|
||||
- 微信登录态保存:`server-rs/crates/platform-auth/src/lib.rs`、`server-rs/crates/module-auth/src/lib.rs`
|
||||
|
||||
@@ -33,6 +35,9 @@ WECHAT_MINI_PROGRAM_VIRTUAL_PAYMENT_APP_KEY=<现网 AppKey>
|
||||
WECHAT_MINI_PROGRAM_VIRTUAL_PAYMENT_SANDBOX_APP_KEY=<沙箱 AppKey,可选>
|
||||
WECHAT_MINIPROGRAM_MESSAGE_TOKEN=<微信消息推送 Token>
|
||||
WECHAT_MINIPROGRAM_MESSAGE_ENCODING_AES_KEY=<微信消息推送 EncodingAESKey>
|
||||
WECHAT_MINIPROGRAM_SUBSCRIBE_MESSAGE_ENABLED=true
|
||||
WECHAT_MINIPROGRAM_GENERATION_RESULT_TEMPLATE_ID=m5z7BkkBhJGbcH0cdDeHaeRU2tViDEguP38XdrRRCdU
|
||||
WECHAT_MINIPROGRAM_SUBSCRIBE_MESSAGE_STATE=formal
|
||||
WECHAT_MINI_PROGRAM_VIRTUAL_PAYMENT_ENV=0
|
||||
```
|
||||
|
||||
@@ -59,7 +64,7 @@ npm run check:encoding
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 旧微信登录快照可能没有 `session_key`;小程序 WebView 会在普通进入时静默刷新一次微信登录态,刷新失败时仍允许匿名打开 WebView,但虚拟支付会继续由后端拦截并提示重新登录。
|
||||
- 旧微信登录快照可能没有 `session_key`;普通进入小程序 WebView 仍允许匿名打开,虚拟支付会由后端拦截并提示用户在小程序内重新登录。H5 内部导航不得清理 `clientType`、`clientRuntime`、`miniProgramEnv`,且首点登录要用小程序 User-Agent 兜底识别,否则登录和支付会误判为普通网页环境。
|
||||
- 小程序充值商品全部映射到虚拟支付;泥点使用 `short_series_coin`,会员使用 `short_series_goods`。
|
||||
- `short_series_coin` 只用于代币购买,后端从本次下单返回的充值中心商品快照读取 `points_amount` 并写入 `buyQuantity`;不要把 coin 商品当成道具,也不要把 `buyQuantity` 固定为 1。
|
||||
- 后台新增的会员类充值商品会直接把商品 `productId` 作为微信 `short_series_goods` 的道具 ID;例如微信后台道具 ID 为 `item01` 时,后台会员商品 `productId` 也应配置为 `item01`,且商品价格需要与微信后台道具价格一致。
|
||||
@@ -69,4 +74,5 @@ npm run check:encoding
|
||||
- 沙箱或基础库失败会把微信返回的 `errCode` / `errMsg` 透传到前端失败弹窗,便于区分微信后台道具、沙箱 AppKey、签名和基础库能力问题。
|
||||
- Web 侧在拉起虚拟支付后会短时轮询 `wx_pay_result`,即使小程序 `web-view` 回写 hash 没触发浏览器 `hashchange`,也必须展示回写的微信错误内容。
|
||||
- WebView 返回但没有拿到 `wx_pay_result` 时,前端必须主动调用订单确认接口,并接入 `/api/profile/recharge/orders/{orderId}/wechat/events` 的 SSE 事件流作为服务端推送兜底;后端收到虚拟支付消息推送并入账后会发布订单更新,SSE 先推当前订单快照,再在订单结束时推 `done`。
|
||||
- 小程序订阅消息用于 AI 创作生成结果通知:H5 在生成动作发起前先把页面切到生成进度态并立即调用生成 action,同时非阻塞跳转到小程序原生订阅授权页尝试请求授权;授权接受、拒绝或页面返回都不得阻塞或取消生成。原生页不得改写上一页 `webViewUrl`,避免返回后丢失 H5 当前进度页状态。通知发送只允许发生在玩法草稿生成成功或失败终态之后,api-server 使用当前用户微信登录保存的 openid 调用微信 `subscribeMessage.send`。发送失败只记录 warning,不阻断作品生成。模板 `thing1` 发送玩法模板名,`number6` 发送本次生成结算后的实际泥点扣除,失败退款后固定为 `0`;模板 `time4` 字段必须是北京时间 `YYYY-MM-DD HH:mm`。`WECHAT_MINIPROGRAM_SUBSCRIBE_MESSAGE_STATE` 支持 `formal` / `trial` / `developer`,应与当前发布环境一致。
|
||||
- WebView 返回后,在订单状态拉取或 SSE 等待期间展示不可关闭遮罩“正在确认支付”,阻止用户离开或继续操作;只有确认到最终订单状态后才展示一次最终结果弹窗,不能先弹“正在支付/支付已提交”再二次弹成功。
|
||||
|
||||
@@ -1,20 +1,38 @@
|
||||
# 平台入口与玩法链路
|
||||
|
||||
更新时间:`2026-06-03`
|
||||
更新时间:`2026-06-10`
|
||||
|
||||
## 平台创作入口
|
||||
|
||||
创作入口配置事实源在 SpacetimeDB,通过 `GET /api/creation-entry/config` 下发;后台通过 `/admin/api/creation-entry/config` 管理。前端只在展示层派生可见卡片和入口状态,`api-server` 路由熔断也使用同一份配置。不要恢复前端硬编码入口配置文件。
|
||||
创作入口配置事实源在 SpacetimeDB,通过 `GET /api/creation-entry/config` 下发;后台通过 `/admin/api/creation-entry/config` 管理入口开关,通过 `/admin/api/creation-entry/config/interactions` 管理公开作品点赞 / 改造能力矩阵。前端只在展示层派生可见卡片、入口状态和作品详情互动状态,`api-server` 路由熔断也使用同一份配置。不要恢复前端硬编码入口配置文件。
|
||||
|
||||
当前点击底部加号进入的创作入口页承载后台公告位、创作入口页签和两列模板卡;页签中只有真实后端作品架摘要存在时才展示“最近创作”,其余为玩法模板分类。点击模板卡后直接进入对应玩法已有的入口创作表单 stage,不再经过空白占位页,也不把旧表单嵌进创作入口页。移动端创作入口页顶栏在 `陶泥儿` 品牌同一行显示真实账户泥点数,数据来自 `profileDashboard.walletBalance`,不得再把公告内容或活动奖池当作账号余额展示。创作入口页公告位数据优先读取 `GET /api/creation-entry/config` 的 `eventBanners` 数组,多条配置时前端自动轮播,旧 `eventBanner` 仅作为单条兼容兜底。后台公告配置面向表单:每条公告包含标题和 HTML 内容,后台保存时序列化为后端 `eventBannersJson` 传输字段,由前端空权限沙箱 iframe 展示;旧结构化 banner 字段仅保留回显兼容,不再作为后台公告配置主格式;不得执行 JSX 或把后台代码直接注入 DOM。玩法列表不再套外部边框卡片,移动端需要压缩横向边距和两列间距;玩法卡统一按“上图、左上状态标签(仅非开放态显示)、封面右下 `10-20泥点数`、下方白底标题/描述”结构展示,卡片高度保持紧凑但标题、描述和预估消耗点数都必须可见。创作入口页根容器不再使用 `platform-page-stage` 这类全局内容卡片壳,但继续保留 `platform-remap-surface` 作为主题和输入框样式命中钩子。创作入口页字号需要对齐平台普通 UI 档位:顶栏泥点组件、公告正文、分类 Tab 和玩法卡标题 / 副标题 / 消耗说明优先使用 `11px` 到 `14px`,不使用 `text-lg`、`text-xl` 或更大的展示级字号。草稿 Tab 继续承接作品架;底部加号入口页的“最近创作”只用 7 天内的真实后端作品架摘要判断是否展示,并从摘要里推导最近使用过的模板 ID,页面必须展示“仅显示最近7天内使用过的模板”提示,列表内容必须复用其它页签里的模板卡样式、文案和点击行为,不展示具体作品名称、摘要或生成状态,也不新增独立最近创作卡组件。RPG、RPG 之外的各玩法入口分别落到既有的 `agent-workspace`、`big-fish-agent-workspace`、`match3d-agent-workspace`、`square-hole-agent-workspace`、`jump-hop-workspace`、`wooden-fish-workspace`、`puzzle-agent-workspace`、`bark-battle-workspace`、`visual-novel-agent-workspace`、`baby-object-match-workspace`,这些入口继续承接各玩法自己的表单、草稿恢复和后续编排,不作为创作入口页内容。
|
||||
当前点击底部加号进入的创作入口页承载后台公告位、创作入口页签和两列模板卡;页签中只有真实后端作品架摘要存在时才展示“最近创作”,其余为玩法模板分类。点击模板卡后直接进入对应玩法已有的入口创作表单 stage,不再经过空白占位页,也不把旧表单嵌进创作入口页;模板点击的占位 no-op、隐藏模板拦截、未知入口 no-op 和工作台启动目标统一由 `platformCreationLaunchModel.ts` 判定,壳层只执行启动前准备、错误提示和受保护动作。移动端创作入口页顶栏在 `陶泥儿` 品牌同一行显示真实账户泥点数,数据来自 `profileDashboard.walletBalance`,不得再把公告内容或活动奖池当作账号余额展示。创作入口页公告位数据优先读取 `GET /api/creation-entry/config` 的 `eventBanners` 数组,多条配置时前端自动轮播;旧 `eventBanner` 只保留字段回显与旧客户端兼容,不再作为前端公告数组的兜底来源。后台公告配置面向表单:每条公告包含标题和 HTML 内容,后台保存时序列化为后端 `eventBannersJson` 传输字段,由前端空权限沙箱 iframe 展示;旧结构化 banner 字段仅保留回显兼容,不再作为后台公告配置主格式;不得执行 JSX 或把后台代码直接注入 DOM。玩法列表不再套外部边框卡片,移动端需要压缩横向边距和两列间距;玩法卡统一按“上图、左上状态标签(仅非开放态显示)、封面右下显示 `creationTypes[].unifiedCreationSpec.mudPointCost` 经前端格式化后的泥点消耗、下方白底标题/描述”结构展示,旧契约缺少该字段时兜底 `10` 并由前端显示为 `10泥点数`,卡片高度保持紧凑但标题、描述和预估消耗点数都必须可见。创作入口页根容器不再使用 `platform-page-stage` 这类全局内容卡片壳,但继续保留 `platform-remap-surface` 作为主题和输入框样式命中钩子。创作入口页字号需要对齐平台普通 UI 档位:顶栏泥点组件、公告正文、分类 Tab 和玩法卡标题 / 副标题 / 消耗说明优先使用 `11px` 到 `14px`,不使用 `text-lg`、`text-xl` 或更大的展示级字号。草稿 Tab 继续承接作品架;底部加号入口页的“最近创作”只用 7 天内的真实后端作品架摘要判断是否展示,并从摘要里推导最近使用过的模板 ID,页面必须展示“仅显示最近7天内使用过的模板”提示,列表内容必须复用其它页签里的模板卡样式、文案和点击行为,不展示具体作品名称、摘要或生成状态,也不新增独立最近创作卡组件。RPG、RPG 之外的各玩法入口分别落到既有的 `agent-workspace`、`big-fish-agent-workspace`、`match3d-agent-workspace`、`square-hole-agent-workspace`、`jump-hop-workspace`、`wooden-fish-workspace`、`puzzle-agent-workspace`、`bark-battle-workspace`、`visual-novel-agent-workspace`、`baby-object-match-workspace`,这些入口继续承接各玩法自己的表单、草稿恢复和后续编排,不作为创作入口页内容。
|
||||
|
||||
旧库或旧迁移包没有 `event_banners_json` 时,后端读取层必须把 `eventBanners` 归一到 `module-runtime` 默认公告数组,不能把旧结构化 `eventBanner` 当成前端优先数组下发。默认公告引用的背景图必须指向 `public/` 下真实存在的站内静态资源,当前默认使用 `/creation-type-references/puzzle.webp`,避免创作入口顶部 banner 出现失效图片。
|
||||
|
||||
创作页和草稿页顶栏右上角的泥点余额胶囊是补足泥点入口:如果当前运行环境开启充值入口,点击后直接打开账户充值弹窗;否则直接打开运营兑换码弹窗。该入口不再跳到账户面板或泥点账单,头像 / 设置等账号入口继续保留各自语义。
|
||||
|
||||
创作恢复参数只保留 `sessionId`、`profileId`、`draftId`、`workId` 这四个私有 query。它们只允许在同一条创作链路的结果页、生成页、工作台之间保留;切到首页、公开作品详情、runtime 或另一条玩法链路时必须清掉。生成页等待时间统一以生成状态里的 `startedAtMs` 为准;创建该状态时优先使用后端 session 下发的时间戳,作品摘要里的 `updatedAt` 仍只用于排序与摘要展示,不作为前端自行推导业务状态的真相。
|
||||
创作恢复参数只保留 `sessionId`、`profileId`、`draftId`、`workId` 这四个私有 query。它们只允许在同一条创作链路的结果页、生成页、工作台之间保留;切到首页、公开作品详情、runtime 或另一条玩法链路时必须清掉。平台入口刷新直达时,路径到玩法恢复目标、四个 query 归一化、生成页标记、大鱼吃小鱼 workId 兜底、作品 / 草稿身份匹配和跳一跳 / 敲木鱼恢复阶段落点统一由 `platformCreationUrlStateModel.ts` 解析,壳层只执行读取作品、恢复草稿和切换阶段等副作用。生成页等待时间统一以生成状态里的 `startedAtMs` 为准;创建该状态时优先使用后端 session 下发的时间戳,作品摘要里的 `updatedAt` 仍只用于排序与摘要展示,不作为前端自行推导业务状态的真相。
|
||||
|
||||
统一创作入口覆盖当前可进入创作链路的已有模板:`rpg`、`big-fish`、`puzzle`、`match3d`、`jump-hop`、`wooden-fish`、`square-hole`、`bark-battle`、`visual-novel`、`baby-object-match` 和 `creative-agent`;`airp` 仍是未开放占位,不作为当前统一创作链路目标。拼图、抓大鹅、跳一跳和敲木鱼在前端继续经过 `UnifiedCreationWorkspace` 和 `UnifiedGenerationPage`:`UnifiedCreationWorkspace` 作为平台壳依赖的统一创作编排层,再内部调用 `src/components/unified-creation/workspaces/` 下的 `PuzzleCreationWorkspace`、`Match3DCreationWorkspace`、`JumpHopCreationWorkspace` 和 `WoodenFishCreationWorkspace`。其它已有模板由平台壳用 `UnifiedCreationPage` 包住既有工作台,复用统一标题栏、返回入口、页面级纵向滚动和隐藏字段契约,同时保留各玩法自己的表单、草稿恢复和后续编排。创作页字段清单由后端在 `GET /api/creation-entry/config` 的 `creationTypes[].unifiedCreationSpec` 下发,前端仅在该扩展位缺失时回退到本地默认 spec;字段类型只保留 `text`、`select`、`image`、`audio`。`UnifiedCreationPage` 不在 UI 中额外展示字段说明 chip,也不在右上角显示内部 `playId`、模板 ID 或工作台阶段名;竖屏移动端必须能从标题、表单一路滑到提交按钮。各玩法工作台负责渲染真实输入控件、上传、历史素材、校验和提交,但返回按钮只保留在统一页头,工作台内部不再重复渲染。暗色创作进度卡片位于 `platform-remap-surface` 内时,必须用组件专属 class 覆盖浅色主题 remap,确保白字、浅色边框和进度条底色不会被全局规则改成深色;不要只依赖通用 `text-white*` 类。敲木鱼的音效和功德词条面板不得放进独立内部滚动容器,移动端应跟随页面自然滚动展开。生成页统一展示阶段、当前步骤、总进度、错误和重试动作。
|
||||
生成页进度 tick 是否启动统一由 `platformGenerationProgressTickModel.ts` 判定:各小游戏生成页只在当前 stage 与对应生成状态匹配、状态存在且 phase 非 `ready` / `failed` 时 tick;视觉小说继续使用 `startedAtMs` 与轻量 phase 判定,不强行转成小游戏生成状态。平台壳只保留 `Date.now()`、`setInterval` 和 cleanup 副作用,不在壳层重复维护 stage 到 state 的三元链。
|
||||
|
||||
创作表单提交前的泥点余额前置校验只允许用独立弹窗提示失败原因,不得把用户退回创作入口或玩法模板列表,也不得清空当前表单状态。当前适用拼图、抓大鹅和汪汪声浪等会在前端提交前校验泥点的生成入口;余额不足、余额读取失败都应停留在当前工作台,由用户关闭提示后继续编辑或自行补足泥点。
|
||||
拼图 runtime 刷新恢复、跳一跳生成中草稿打开和敲木鱼生成中 / detail 草稿恢复所需的 session / work DTO 映射统一由 `platformMiniGameSessionMappingModel.ts` 构造。平台壳只负责读取后端、写入本地 state、写 URL 和切换 stage;不得在壳层重新手写 sessionId 优先级、pending draft 空素材默认值或拼图稳定 ID 映射。
|
||||
|
||||
平台小游戏生成状态的恢复、失败 / 完成收尾、展示 rebase、拼图后端进度合并和 ready / generating 判定统一由 `platformMiniGameDraftGenerationStateModel.ts` 处理。平台壳只决定何时调用并写入对应 React state,不得在壳层重新维护 `MiniGameDraftGenerationState` 的 phase 阈值、`finishedAtMs` 清理或拼图进度 metadata 合并规则。
|
||||
|
||||
拼图 / 抓大鹅草稿恢复和提交所需的表单 payload、拼图编译 action、pending metadata 与拼图 form-only 草稿判定统一由 `platformMiniGameDraftPayloadModel.ts` 构造。平台壳不得重新手写拼图描述字段优先级、formDraft 回退、form-only 空草稿判定、Match3D config / draft / anchorPack 优先级、数字解析或 pending 标题摘要派生规则。
|
||||
|
||||
拼图生成完成后刷新恢复的草稿归一化与可恢复完成态判定统一由 `platformPuzzleDraftRecoveryModel.ts` 处理。恢复链路只有在首图、关卡画面、UI spritesheet 与关卡背景资产包完整时才可把 draft 和首关状态抬为 `ready`;只有 cover 或候选图的半成品不得直接进入结果页完成态。
|
||||
|
||||
后端拼图发布 / 待发布门槛同样必须要求首图、关卡画面、UI spritesheet 与关卡背景资产包完整:`module-puzzle` preview blockers 与 `api-server` session stage 判定不得只凭 cover、标题、描述和标签把半成品标为 `publishReady` 或 `ready_to_publish`。
|
||||
|
||||
平台入口个人钱包本地 delta 由 `platformProfileWalletDeltaModel.ts` 判定:余额归一、本地扣点 / 返还后的 dashboard 乐观更新,以及服务端 dashboard 刷新后的 delta 对账不得散落在平台壳层;壳层只负责 API、React ref 和 state 写入。
|
||||
|
||||
RPG Agent 结果页发布门禁展示由 `platformRpgAgentResultPreviewModel.ts` 判定:平台壳不得重新手写 `CustomWorldProfile` 顶层、`creatorIntent`、`anchorContent`、章节蓝图与首幕 acts 的结构探测,也不得在壳层内联 result preview source label 映射;壳层只负责 session/profile 编排和结果页 props 传递。
|
||||
|
||||
统一创作入口覆盖当前可进入创作链路的已有模板:`rpg`、`big-fish`、`puzzle`、`match3d`、`jump-hop`、`wooden-fish`、`square-hole`、`bark-battle`、`visual-novel`、`baby-object-match` 和 `creative-agent`;`airp` 仍是未开放占位,不作为当前统一创作链路目标。拼图、抓大鹅、跳一跳和敲木鱼在前端继续经过 `UnifiedCreationWorkspace` 和 `UnifiedGenerationPage`:`UnifiedCreationWorkspace` 作为平台壳依赖的统一创作编排层,再内部调用 `src/components/unified-creation/workspaces/` 下的 `PuzzleCreationWorkspace`、`Match3DCreationWorkspace`、`JumpHopCreationWorkspace` 和 `WoodenFishCreationWorkspace`。其它已有模板由平台壳用 `UnifiedCreationPage` 包住既有工作台,复用统一标题栏、返回入口、页面级纵向滚动和隐藏字段契约,同时保留各玩法自己的表单、草稿恢复和后续编排。创作页字段清单、表头和入口卡泥点消耗数量由后端在 `GET /api/creation-entry/config` 的 `creationTypes[].unifiedCreationSpec` 下发,前端仅在该扩展位缺失时回退到本地默认 spec;字段类型只保留 `text`、`select`、`image`、`audio`。统一创作页表头按 `unifiedCreationSpec.title` 契约内容原样显示,入口卡泥点消耗按 `unifiedCreationSpec.mudPointCost` 由前端格式化为 `X泥点数`,读取和保存时不再用入口名称或前端固定文案自动覆盖;需要改表头或入口卡消耗数量时应在后台契约结构卡片点击修改,并通过弹窗表单编辑 `title` 或 `mudPointCost` 字段,不再要求直接编辑 JSON。`workspaceStage`、`generationStage` 和 `resultStage` 属于内部阶段标识,后台弹窗不展示也不允许编辑;保存时沿用已有契约值,新增契约时按 `playId` 的前端固定阶段映射自动带出。`UnifiedCreationPage` 不在 UI 中额外展示字段说明 chip,也不在右上角显示内部 `playId`、模板 ID 或工作台阶段名;竖屏移动端必须能从标题、表单一路滑到提交按钮。统一创作页根容器必须保留平台浅色背景并让内容区占满剩余高度,移动端软键盘打开或视口被小程序宿主压缩时,短表单也不得露出浏览器 / 宿主黑底;H5 根节点在 `data-mobile-keyboard-open=true` 时必须把 `html` / `body` / `#root` 背景切到当前平台浅色底,但不得再用 `.platform-viewport-shell` 全局 `transform` 二次上推页面;小程序 `web-view` 页面原生宿主也必须使用浅色背景,不能沿用全局黑色 page 背景。各玩法工作台负责渲染真实输入控件、上传、历史素材、校验和提交,但返回按钮只保留在统一页头,工作台内部不再重复渲染。暗色创作进度卡片位于 `platform-remap-surface` 内时,必须用组件专属 class 覆盖浅色主题 remap,确保白字、浅色边框和进度条底色不会被全局规则改成深色;不要只依赖通用 `text-white*` 类。敲木鱼的音效和功德词条面板不得放进独立内部滚动容器,移动端应跟随页面自然滚动展开。生成页统一展示阶段、当前步骤、总进度、错误和重试动作。
|
||||
|
||||
创作表单提交前的泥点余额前置校验只允许用独立弹窗提示失败原因,不得把用户退回创作入口或玩法模板列表,也不得清空当前表单状态。当前适用拼图、抓大鹅和汪汪声浪等会在前端提交前校验泥点的生成入口;校验成本必须读取同一份 `creationTypes[].unifiedCreationSpec.mudPointCost`,不能回到前端常量。余额不足、余额读取失败都应停留在当前工作台,由用户关闭提示后继续编辑或自行补足泥点。
|
||||
|
||||
平台入口、生成页、结果页、作品详情、作品架和运行态的跨流程错误统一收口到 `PlatformErrorDialog`。弹窗必须带明确错误来源,例如某个草稿、某次生成、作品详情或某个游玩实例,并提供复制按钮复制“错误来源 + 错误内容”。页面内不再重复渲染裸错误 banner;表单校验、发布确认弹窗里的局部业务错误可以保留在原弹窗内。生成任务在用户离开生成页后异步失败时,也必须通过同一弹窗通知用户,并把失败消息写入该 session 的草稿 notice,供草稿页和失败重试页恢复使用。
|
||||
|
||||
@@ -22,7 +40,9 @@
|
||||
|
||||
入口配置中的 `open=false` 表示关闭新建创作入口,不表示下架已有草稿、私有作品或公开作品。api-server 的入口熔断只允许拦截新建创作、新建草稿、首次生成入口和 Remix 成草稿等会产生新创作的请求;公开广场列表、公开详情、点赞、已发布作品启动、运行态过程请求、存档 / 浏览记录和已有作品回读不能因为创作入口关闭而返回 `creation_entry_disabled`。平台首页如果遇到旧服务端返回的 `creation_entry_disabled`,只能降级为空列表或隐藏入口,不弹平台级错误弹窗。
|
||||
|
||||
创作入口页的关闭态卡片必须有明显差异:卡片禁用点击,展示后台配置的关闭态 badge 或 `暂未开放`,不再显示 `10-20泥点数` 这类可创建成本提示;开放态卡片仍不显示普通 `可创建 / 可创作` badge。
|
||||
公开作品点赞 / 改造是否开放不跟随入口 `open` 字段,而是读取 `GET /api/creation-entry/config` 的 `publicWorkInteractions`。后台可以按 `sourceType` 分别关闭点赞或改造并维护关闭提示;前端只据此关闭已接入的作品详情动作,尚未接入后端动作的玩法仍按实际能力矩阵返回不可用提示。api-server 对已接入的 RPG、自定义世界兼容路径、大鱼吃小鱼和拼图点赞 / 改造接口做同源熔断,关闭时返回 `public_work_interaction_disabled`,但公开列表、公开详情、已发布作品启动和运行态过程请求不受影响。
|
||||
|
||||
创作入口页的关闭态卡片必须有明显差异:卡片禁用点击,展示后台配置的关闭态 badge 或 `暂未开放`,不再显示泥点消耗这类可创建成本提示;开放态卡片仍不显示普通 `可创建 / 可创作` badge。
|
||||
|
||||
`PlatformEntryFlowShellImpl.tsx` 仍是平台入口编排壳,后续维护时应优先把独立 UI 片段、公开作品映射、草稿生成 notice 和运行态状态 helper 拆到 `src/components/platform-entry/PlatformEntryFlowShellImpl/` 或同目录紧邻 helper 文件。拆分只允许改变文件组织,不改变入口配置事实源、默认导出、props、页面阶段、UI 文案或现有交互;其中拼图首访 onboarding 已拆为 `PlatformEntryFlowShellImpl/PuzzleOnboardingView.tsx`。
|
||||
|
||||
@@ -38,29 +58,36 @@
|
||||
创作入口 -> 工作台 -> 生成页 -> 结果页 -> 试玩 -> 发布 -> 运行态
|
||||
```
|
||||
|
||||
后端链路也按同一条平台主干组织:所有创作、生成、作品回读、发布、试玩、正式 runtime、公开详情、作品架、运行态设置 / 存档、游玩历史、存档归档、游玩统计、历史素材、AI task、runtime chat、文档解析、角色资产工坊和玩法生成支撑资产相关 HTTP 路由,先注册到 `server-rs/crates/api-server/src/modules/play_flow.rs`,由主干在进入领域 handler 前统一解析 `PlayFlowRequestContext`,再在最后一步分发给对应领域模块或支撑能力 handler 处理。`app.rs` 不再逐玩法挂载创作 / 运行态路由,`modules/platform.rs` 只保留通用 LLM / 语音代理;新增玩法、补齐旧玩法或迁移旧路径时,必须先补 `play_flow` 的 `playId`、领域模块 key、创作路由前缀、运行态路由前缀和入口开关匹配规则,再补具体 handler。领域规则、胜负裁决、计分、发布状态、资产完整性和排行榜仍留在各自 `module-*` 与 SpacetimeDB procedure 中,不把平台主干写成某个玩法的新业务真相。
|
||||
|
||||
默认工作台只提交结构化表单、图片槽位和配置 payload,不默认增加聊天输入区、流式消息区或轻输入 Agent。确需偏离该模式时,必须先在 PRD 和本文档写明例外原因、影响范围和回退方式,再进入编码。
|
||||
|
||||
单图资产编辑统一通过 `CreativeImageInputPanel` 承载上传、AI 重绘、参考图、历史图和删除确认;新玩法页面不得重复手写这些交互。系列素材图集生成统一走“批量规划 -> sheet 生图 -> 后端切图 -> 透明化 -> OSS 持久化 -> 状态回写 -> 局部重生成”流程,玩法只提供 `sheetSpec`、`slotSpecs`、提示词和字段映射,不把任一玩法专属素材 DTO 当作平台通用模型。
|
||||
单图资产编辑统一通过 `CreativeImageInputPanel` 承载上传、AI 重绘、参考图、历史图、主图预览和删除确认;新玩法页面不得重复手写这些交互。主图已有图片时,默认点击图片打开全屏预览,上传 / 更换收口到右下角 `ImagePlus` 图标按钮;无图时仍允许点击空图卡上传。调用方只能通过 `canUploadMainImage`、`canUseImageHistory` 等受控参数开关上传和历史入口,不得用复制组件或样式遮挡改行为。系列素材图集生成统一走“批量规划 -> sheet 生图 -> 后端切图 -> 透明化 -> OSS 持久化 -> 状态回写 -> 局部重生成”流程,玩法只提供 `sheetSpec`、`slotSpecs`、提示词和字段映射,不把任一玩法专属素材 DTO 当作平台通用模型。
|
||||
|
||||
通用系列素材图集能力的实现真相源在 `platform-image::generated_asset_sheets`:`n` 是必选参数,模块负责组装 `n*n` sheet prompt、按 `n*n` 切片、绿幕 / 近白底透明化、导出 PNG 和 OSS 持久化请求。`api-server::generated_asset_sheets` 只保留 `AppError` / `AppState` 适配,不再承载图像处理和 OSS 请求构造细节。物品名称 prompt 和特殊设定 prompt 是可选输入;调用方可传入类似“每个物品生成五个不同视图”的视角约束,通用模块会把 sheet prompt、物品行 prompt、特殊设定 prompt 编码写入 OSS 元数据。玩法仍负责计费、物品规划、slot 映射、失败回写和把通用切片结果映射回自己的草稿 / profile / runtime 字段。
|
||||
通用系列素材图集能力的实现真相源在 `platform-image::generated_asset_sheets`:`n` 是必选参数,模块负责组装 `n*n` sheet prompt、按 `n*n` 切片、默认绿幕 / 近白底透明化、导出 PNG 和 OSS 持久化请求;高风险撞色玩法可显式使用专用 key 色、关闭近白扣除并限制为边缘连通背景扣除。`api-server::generated_asset_sheets` 只保留 `AppError` / `AppState` 适配,不再承载图像处理和 OSS 请求构造细节。物品名称 prompt 和特殊设定 prompt 是可选输入;调用方可传入类似“每个物品生成五个不同视图”的视角约束,通用模块会把 sheet prompt、物品行 prompt、特殊设定 prompt 编码写入 OSS 元数据。玩法仍负责计费、物品规划、slot 映射、失败回写和把通用切片结果映射回自己的草稿 / profile / runtime 字段。
|
||||
|
||||
当前所有玩法生成页 UI 统一收敛为圆环主视觉:`media/create_bg_video.mp4` 作为生成页固定全屏背景层循环静音播放,主进度圆环居中覆盖在背景之上,围绕陶泥儿视觉展示;页面只保留当前步骤名称和当前步骤进度,不再渲染步骤列表块。视频层需要显式触发播放,不能只依赖 `autoPlay/loop/muted` 属性。圆环内部保持 `400x400` SVG 坐标系,外层显示宽度以 `400px` 为上限,窄屏按视口宽度收缩,预计等待 / 已耗时信息卡在窄屏下落到圆环下方,避免右侧裁切。共用生成页 `CustomWorldGenerationView` 和汪汪声浪生成页都必须遵循这一口径。
|
||||
当前所有玩法生成页 UI 统一收敛为圆环主视觉:`media/create_bg_video.mp4` 作为生成页固定全屏背景层循环静音播放,主进度圆环居中覆盖在背景之上,围绕陶泥儿视觉展示;页面只保留当前步骤名称和当前步骤进度,不再渲染步骤列表块,也不再展示“当前拼图信息”“当前敲木鱼信息”“当前世界信息”等玩法设定信息模块。视频层需要显式触发播放,不能只依赖 `autoPlay/loop/muted` 属性。圆环内部保持 `400x400` SVG 坐标系,外层显示宽度以 `400px` 为上限,窄屏按视口宽度收缩,预计等待 / 已耗时信息卡在窄屏下落到圆环下方,和当前步骤卡保持更大的垂直间距;预计等待左边缘、已耗时右边缘必须分别与当前步骤卡左右边缘对齐,避免右侧裁切或横向漂移。生成页顶部返回栏和状态标识不参与内容滚动,滚动只发生在进度内容区。共用生成页 `CustomWorldGenerationView` 和汪汪声浪生成页都必须遵循这一口径。
|
||||
|
||||
## 草稿与作品架
|
||||
|
||||
1. 草稿页作品卡对齐发现页列表卡风格:左侧信息,右侧封面图,移动端单列,桌面两到三列。
|
||||
2. 草稿页顶部 `全部 / 草稿 / 已发布` 筛选与发现页 `推荐 / 今日 / 分类 / 排行` 频道标签复用同一选中 / 未选中视觉,即 `platform-mobile-home-channel` 与 `platform-mobile-home-channel--active`,不再使用旧 `platform-tab` 胶囊样式。
|
||||
3. 草稿页与底部导航的未读提示点统一使用平台暖棕色点和暖棕光晕,不再使用红点或红色 glow;草稿 Tab 作品架卡片无论草稿 / 已发布都不外露作者信息;已发布作品卡右上角直接显示无边框分享 icon。删除等破坏性动作在作品卡上也要直接开放独立删除入口,左滑或长按仅作为辅助操作层。
|
||||
3. 草稿页与底部导航的未读提示点统一使用平台暖棕色点和暖棕光晕,不再使用红点或红色 glow;草稿 Tab 作品架卡片无论草稿 / 已发布都不外露作者信息;已发布作品卡右上角直接显示带底色的分享 icon,并统一唤起发布分享弹窗 `PublishShareModal`,不在卡片内部单独复制分享文案。删除等破坏性动作在作品卡上也要直接开放统一 `actions.delete` 入口,左滑、长按和键盘左箭头仅作为打开同一操作层的辅助交互;所有玩法草稿和已发布列表项都必须通过该统一接口接入删除确认、删除中状态和列表刷新,不允许只给拼图保留专属滑动删除分支。
|
||||
4. 生成中作品在整卡上加等待遮罩,但不移除作品基础信息。
|
||||
5. 生成中状态不能只存在前端内存 notice。后端作品摘要必须下发可恢复的 `generationStatus`;前端刷新或退出产品后,作品架优先用摘要状态恢复等待遮罩,本轮内存 notice 只作为即时反馈。
|
||||
6. 点击 `generationStatus=generating` 的草稿卡必须恢复对应玩法的生成进度页,不能进入空白结果页或普通工作区;恢复生成页的 `startedAtMs` 优先使用后端 session 的 `updatedAt`,没有 session 时再使用作品摘要 `updatedAt`,不得因重新进入页面从 0 秒重新计时。
|
||||
7. 生成失败必须按 session 独立记录,不能用一个失败打断或覆盖同玩法的其它生成任务。失败 notice 需要保存错误消息并覆盖作品架本地状态:即使后端摘要暂时仍是 `generationStatus=generating` 或只写出半成品投影,草稿卡也不得继续显示“生成中”,点击后必须进入失败 / 重试生成页,不能重新创建一轮生成;拼图这类失败半成品若没有有效 `workTitle`,作品架标题回退为“拼图草稿”,不暴露“第1关”空壳。
|
||||
7. 生成失败必须按 session 独立记录,不能用一个失败打断或覆盖同玩法的其它生成任务。失败 notice 需要保存错误消息并覆盖作品架本地状态:即使后端摘要暂时仍是 `generationStatus=generating` 或只写出半成品投影,草稿卡也不得继续显示“生成中”,点击后必须进入失败 / 重试生成页,不能重新创建一轮生成。失败页点击重新生成时必须优先复用当前可恢复 `sessionId` 执行编译 action;只有没有可恢复 session 时才允许回退到新建草稿。拼图这类失败半成品若没有有效 `workTitle`,作品架标题回退为“拼图草稿”,不暴露“第1关”空壳。
|
||||
8. 从草稿 Tab 作品架打开草稿工作区、生成页或结果页时,返回按钮必须回到草稿 Tab 的同一作品架语境;从创作 Tab 新建或直接进入创作链路时才回到创作 Tab。平台壳层需要显式记录本次创作流的返回来源,不能让结果页返回动作固定跳到创作入口。
|
||||
9. 私有 generated 图片必须通过 `ResolvedAssetImage` / `/api/assets/read-url` 换签读取。
|
||||
10. 敲木鱼作品架读取当前用户作品列表时走 `GET /api/creation/wooden-fish/works`;发布成功后平台壳必须同时刷新作品架与公开广场,避免作品刚发布时仍停留在旧列表。
|
||||
11. 移动端草稿页整体禁止长按选择文字,避免误触系统选区;输入框、文本域和可编辑区域仍必须保留文本选择能力。
|
||||
12. 作品架删除确认的纯规则统一由 `platformCreationWorkDeleteFlow.ts` 解析,输出确认框 `id/title/detail` 与删除成功后清理的草稿 notice keys;平台壳只接回该模型执行删除 API、刷新列表、清错误和跳转。Jump Hop、Wooden Fish、Bark Battle 虽在作品架 action 层有预留删除入口,但未补齐删除 API 前不得传入删除 handler 或开放按钮。
|
||||
|
||||
发现页 / 推荐页公开作品卡的作者行只显示可读公开昵称;不得把手机号掩码、`SY-*` 陶泥号或作品号拼接进卡片作者名。陶泥号搜索、作品号复制和完整作品身份只在搜索、详情页或明确的复制入口展示,避免卡片列表暴露账号标识。
|
||||
发现页 / 推荐页公开作品卡的作者行只显示可读公开昵称;不得把手机号掩码、账号生成的脱敏手机号、`SY-*` 陶泥号或作品号拼接进卡片作者名。陶泥号搜索、作品号复制和完整作品身份只在搜索、详情页或明确的复制入口展示,避免卡片列表暴露账号标识。推荐页运行态、标题和作者信息必须使用同一套公开作品 key 选中当前条目;新增或补齐公开玩法类型时复用 `buildPlatformPublicGalleryCardKey(...)`,避免运行内容已切换但标题 / 作者仍退回第一条作品。
|
||||
|
||||
平台公开搜索的分流顺序、per-play 公开码匹配、公开可见性过滤和详情卡 DTO 映射统一由 `platformPublicCodeSearchModel.ts` 判定:`user_` / `user-` 内部用户 ID 只查用户 ID;`PZ`、`BF`、`JH`、`WF`、`BO`、`M3`、`SH`、`VN`、`BB` 前缀分别直达对应玩法公开作品;`M3D-*` 作为抓大鹅旧前缀继续匹配;`CW` 与 1-8 位纯数字先查 RPG 公开作品再回退陶泥号;普通关键词和 `SY` 陶泥号保持先查陶泥号、再查 RPG 作品、再查汪汪声浪作品、最后陶泥号兜底的既有顺序。平台壳只按计划执行网络读取、详情打开、Bark Battle runtime 特例和缺失作品归航,不在壳层重复维护前缀布尔链、`isSame*PublicWorkCode` 或 DTO 映射。
|
||||
|
||||
个人“玩过作品”面板点击作品时,玩法别名、`worldKey` 前缀兜底、RPG 公开详情 payload 和大鱼吃小鱼缺 gallery 命中时的 fallback work 统一由 `platformPlayedWorkOpenModel.ts` 判定。平台壳只负责关闭面板、调用对应公开详情打开函数、刷新大鱼 gallery、优先使用真实 gallery 命中项和写入错误提示;不要在壳层重新维护 `worldType` / `worldKey` 分支链。
|
||||
|
||||
发现 Tab、创作 Tab 与草稿 Tab 的页面根内容区不再套 `platform-page-stage` 外层全局卡片壳,让列表、筛选和玩法卡获得更宽的横向空间;推荐页和我的页仍按各自页面设计保留原有全局卡片口径。移动端“我的”页仍按顶部头像 / 昵称 / 陶泥号、会员横幅、三张统计卡、每日任务、五项常用功能宫格、设置入口和法律信息组织,不保留旧的底部“填邀请码”次级入口;常用功能当前只展示四项常驻入口时必须按四列铺满整行,不保留五列网格导致左对齐空位;每日任务卡必须读取 `/api/profile/tasks` 的当前任务摘要并在领取后同步刷新卡片进度。字号必须维持平台普通 UI 档位,不能因为窄屏把卡片标题、功能 label 或法律信息撑成展示级字号;最后一屏内容必须能在底部 dock 上方完整滚动露出,不得被固定底部导航遮挡。
|
||||
|
||||
@@ -104,10 +131,11 @@ RPG / 拼图等运行态存档仍以 `/api/profile/save-archives` 的后端列
|
||||
|
||||
- 图像输入复用 `CreativeImageInputPanel`。
|
||||
- 结果页每关画面编辑复用 `CreativeImageInputPanel`;入口页和关卡画面只共享受控 UI 模块,不共享数据源、状态、action 或存储位置:入口页继续写 `formDraft` 与草稿编译 payload,关卡画面写 `levels[].pictureReference/pictureDescription` 并触发 `generate_puzzle_images`。结果页删除独立“素材配置”Tab,不再提供单独 UI 背景生成入口。通用图片面板的展示图和 AI 重绘参考图能力必须分开控制:结果页正式关卡图只作为预览图,不因存在正式图自动暴露 AI 重绘开关;只有本地上传、历史选择或已保存 `pictureReference` 可作为重绘参考图时,才显示 AI 重绘开关并把状态带入 `generate_puzzle_images`。用户在本次编辑中上传或选择历史图后,该图优先占据主图卡片,可删除、切换 AI 重绘,也可关闭 AI 重绘直用;仅有正式图预览时,画面描述框仍可上传多张参考图。关卡详情弹窗应使用加宽面板,关卡名称、画面图和画面描述合并在同一个纵向列表中,名称输入和画面编辑模块外层不再包独立 `platform-subpanel`;画面图卡仍必须保留稳定最小高度,避免弹窗内 `flex-1` 布局坍缩后只剩标题、描述输入和操作按钮。
|
||||
- 历史图片选择弹窗只展示缩略图与生成时间,不展示从对象路径或文件名解析出的图片名称;选中历史图后内部兜底文案统一使用“历史素材”。
|
||||
- 支持画面描述生图、多参考图生图、上传或历史生成主图后 AI 重绘、上传或历史生成主图后不重绘;主链要求浏览器先经 `/api/assets/direct-upload-tickets` 直传 OSS 并确认 `asset_object`,创作 action 只提交 `referenceImageAssetObjectId(s)`,由后端校验 owner / bucket / kind / MIME / size 后签发 OSS 只读 URL 并下载为 VectorEngine `/v1/images/edits` 的 multipart `image` part。本地上传 Data URL 与历史 `/generated-*` 图片路径仅保留为旧草稿、旧入口或未迁移客户端的兼容输入;关闭 AI 重绘时,后端统一解析为首关或当前关卡正式图后再持久化,不调用第一段拼图首图生成。
|
||||
- 草稿生成会先持久化 `generationStatus=generating` 的作品摘要,生成完成并回写关卡拼图画面、关卡画面参考图、UI spritesheet 和关卡背景图后再变为 `ready`;当前不自动生成背景音乐。生成页步骤推进必须跟随后端 session `progressPercent` 的真实里程碑:`88` 表示草稿编译完成并进入出图步骤,`94` 表示生成图已保存并进入 UI / 背景步骤,`96` 表示正式图与 UI 背景已确认并进入写入步骤,最终 action 成功或发布才进入完成态;每个步骤内部可以按实际等待时间使用假进度平滑推进。`88/94/96` 只负责切换当前步骤,不作为总进度地板;总进度按已完成步骤权重加当前步骤内假进度推导,非完成态最多停在 `98%`。任一同步 action 回包到达时立即以真实完成/失败结果冻结进度。
|
||||
- 作品架拼图草稿的“生成中”遮罩只表示初始草稿还没有可查看结果;只要作品摘要、首关封面或任一关卡候选图已经可用,后续 UI 背景重生成和追加关卡生图都必须作为结果页局部生成态处理,不能阻止打开草稿结果页。生成失败后,同一浏览器会话内的失败 notice 必须覆盖后端可能仍短暂返回的 `generationStatus=generating` 摘要,作品架保留对应草稿卡但不再显示“生成中”,点击后回到失败 / 重试状态。
|
||||
- 拼图草稿编译是长耗时 action,前端 action 请求默认等待 `1_800_000ms`(30 分钟)且不自动重试。每次图片生成调用的预期用时按 90 秒计算,但 `生成拼图首图` 单独按 4 分钟展示;完整 AI 重绘路径为 `编译首关草稿` 8 秒、`生成关卡名称` 10 秒、`生成拼图首图` 4 分钟、`生成关卡画面` 90 秒、`生成UI与背景` 90 秒、`写入正式草稿` 10 秒,合计约 448 秒。上传图且关闭 AI 重绘时必须跳过 `生成拼图首图`,直接进入 `生成关卡画面` 和 `生成UI与背景`,合计约 208 秒。生成页恢复时必须使用后端 session `updatedAt` 或作品摘要 `updatedAt` 作为原始 `startedAtMs`;失败/完成态用 `finishedAtMs` 冻结耗时。未收到对应后端里程碑前,后续步骤保持待处理;即使当前步骤预计时长耗尽,也只能让当前步骤内部进度停在 `98%` 内,不能自动完成当前步骤或跳到后续步骤。生成页每个步骤只展示标题和进度,不展示步骤详细描述。
|
||||
- 拼图草稿编译是长耗时 action,前端 action 请求默认等待 `1_800_000ms`(30 分钟)且不自动重试。每次图片生成调用的预期用时按 90 秒计算,但 `生成拼图首图` 单独按 4 分钟展示;完整 AI 重绘路径为 `编译首关草稿` 8 秒、`生成关卡名称` 10 秒、`生成拼图首图` 4 分钟、`生成关卡画面` 90 秒、`生成UI与背景` 90 秒、`写入正式草稿` 10 秒,合计约 448 秒。上传图且关闭 AI 重绘时必须跳过 `生成拼图首图`,直接进入 `生成关卡画面` 和 `生成UI与背景`,合计约 208 秒。生成页恢复时必须使用后端 session `updatedAt` 或作品摘要 `updatedAt` 作为原始 `startedAtMs`;失败/完成态用 `finishedAtMs` 冻结耗时。生成完成后若自动进入草稿试玩,进入 `/runtime/puzzle` 前必须先把 `/creation/puzzle/result` 和当前 `sessionId/profileId/workId` 写成浏览器历史前一站;运行态返回按钮和系统返回都应回到结果页,不得退回生成进度页或暴露重新生成入口。未收到对应后端里程碑前,后续步骤保持待处理;即使当前步骤预计时长耗尽,也只能让当前步骤内部进度停在 `98%` 内,不能自动完成当前步骤或跳到后续步骤。生成页每个步骤只展示标题和进度,不展示步骤详细描述。
|
||||
- 前端创作、结果页、生成页和错误提示不展示 GPT / Gemini 等具体模型名称;如需在内部保留模型路由,UI 只使用“标准模式”“创意模式”等产品化名称。
|
||||
- 若浏览器锁屏、息屏或网络切换导致 compile 请求失败,前端在标记失败前必须先复读 `getPuzzleAgentSession(sessionId)`;只有最新 session 仍缺 `draft.coverImageSrc`、首关 `coverImageSrc` 或候选图时才展示失败,复读到已生成草稿时按成功收尾、刷新作品架并继续自动试玩/结果页链路。
|
||||
- 拼图参考图 AI 重绘走 VectorEngine `/v1/images/edits`;无参考图时走 `/v1/images/generations`。两者模型都使用 `gpt-image-2`,参考图由后端作为 multipart `image` part 传入编辑接口。
|
||||
@@ -118,12 +146,13 @@ RPG / 拼图等运行态存档仍以 `/api/profile/save-archives` 的后端列
|
||||
- 拼图试玩和正式运行态刷新恢复不复用创作私有 query。进入 `/runtime/puzzle` 时必须写入 `runtimeProfileId`、草稿 `runtimeSessionId`、可选 `runtimeLevelId`、公开作品 `work` 和 `mode=draft|published`;进入运行态的导航顺序必须先切到 `/runtime/puzzle`,再写这些 runtime query,避免被阶段导航清掉后刷新停在“正在进入拼图关卡”。
|
||||
- 结果页生成关卡图时若关卡名为空,前端必须传 `shouldAutoNameLevel=true`,后端复用首关命名契约先按画面描述生成关卡名,再在图片生成后用视觉命名结果精修,并把生成名和 UI 背景提示词随本次关卡快照写回。
|
||||
- 拼图运行态背景优先读取当前关卡 `levelBackgroundImageSrc/levelBackgroundImageObjectKey`,旧数据才兼容 `uiBackgroundImageSrc/uiBackgroundImageObjectKey`;本地试玩、直达指定关卡和正式 `next-level` 推进时,目标关卡缺关卡背景时必须继承同作品首个可用关卡背景,仍缺失时才沿用当前运行态快照背景或默认 UI。运行态按钮视觉优先读取当前关卡 `uiSpritesheetImageSrc/uiSpritesheetImageObjectKey`,先按透明 alpha 自动边界检测识别 spritesheet 中的独立按钮展示矩形,再按原图位置从左到右、从上到下映射到返回、设置、下一关、提示、原图、冻结;同一组件还要按较高 alpha 阈值派生紧致点击热区,透明留白和柔边低 alpha 区域尽量不响应点击。检测失败时回退旧固定六格裁切,缺失时才用现有图标按钮兜底。有 spritesheet 时,返回、设置和下一关的点击容器只提供透明点击区,不再叠加默认白色圆形底、胶囊主按钮底或额外文字;下一关按钮在通关弹窗和底部入口中都直接使用 spritesheet 裁切出的 next 素材作为按钮本体。底部提示、原图、冻结三枚素材按检测矩形的原始宽高比显示,不能强行拉伸成正圆或铺满整列。底部道具区不再使用连片胶囊背景,提示、原图、冻结三个按钮均匀分布;运行态只展示按钮素材本身,不额外叠加“提示 / 原图 / 冻结”文字。
|
||||
- 推荐页本身不是登录门禁入口,未登录用户点击底部或侧边栏的推荐 Tab 应直接进入嵌入运行态,不主动打开登录弹窗。推荐页嵌入运行态必须按真实身份分流:已登录用户或本地已有 access token 时,启动拼图和后续排行榜 / 下一关等正式请求继续走账号 Bearer;只有确认为匿名访客时才申请并透传 runtime guest token。`/api/runtime/puzzle/runs*` 后端统一接受 `RuntimePrincipal`,可识别账号用户和匿名 runtime guest;推荐卡片的后台读写请求仍使用 local auth impact,避免单卡 401 清空整站登录态。创作、个人作品、删除、发布、Remix 等账号或所有权动作仍保持普通用户鉴权。
|
||||
- 推荐页本身不是登录门禁入口,未登录用户点击底部或侧边栏的推荐 Tab 应直接进入嵌入运行态,不主动打开登录弹窗。推荐页嵌入运行态必须按真实身份分流:已登录用户或本地已有 access token 时,正式 runtime 启动与后续局内动作继续走账号 Bearer;只有确认为匿名访客时才申请并透传 runtime guest token。平台壳统一通过 `buildRecommendRuntimeRequestOptions(...)` 为各玩法的 start / checkpoint / finish / input / drop / click / restart / time-up / leaderboard / next-level 等动作生成局部 request options,不允许每个玩法各写一套匿名分支。后端 `/api/runtime/*` 正式运行态写请求统一接受 `RuntimePrincipal`,可识别账号用户和匿名 runtime guest;推荐卡片的后台读写请求仍使用 local auth impact,避免单卡 401 清空整站登录态。创作、个人作品、删除、发布、Remix 等账号或所有权动作仍保持普通用户鉴权。
|
||||
- 推荐页作品队列只能通过 `buildPlatformRecommendFeedEntries(...)` 生成,首页卡片窗口、桌面推荐格、嵌入 runtime 自动启动和上一条 / 下一条切换都必须消费同一队列。不得在首页和 `PlatformEntryFlowShellImpl` 内分别按“最新列表顺序”和“评分推荐顺序”各算一套相邻作品,否则连续切换会出现视觉上跳过作品或回跳。
|
||||
- 拼图运行态棋盘不叠加分块蒙版、描边、阴影、选中底色或合并块 SVG 轮廓;拼图片本体需要裁切为圆角形状,单块使用独立圆角裁切,合并块使用 SVG 原生 `clipPath` 裁切整体外轮廓,外凸角和内凹角分别计算半径,内凹角半径要比外凸角更明显以避免手机 WebView 中看起来仍是直角。原图道具只在用户主动确认后打开独立原图查看层,不在当前拼图棋盘上叠加原图。
|
||||
- 拼图运行态拖拽必须完全跟随手指或鼠标位置,`pointermove` 期间即时写入可见拼块的 transform,不依赖等待后端回包、React 重渲染或下一帧动画队列;进入拖动后不展示拼块选中态或“已选择”提示,松手后再提交目标格同步规则真相。
|
||||
- 拼图运行态的提示、设置等点击弹层跟随当前运行态主色主题,使用普通圆角主题面板,不复用像素九宫格素材框。
|
||||
- 拼图运行态壳层自身要补齐 `platform-ui-shell` / `platform-theme` / `platform-theme--light|dark`,不能依赖外层平台壳来提供主题变量;`/puzzle` 直达页和平台内嵌页都必须渲染同一套主题语义类。
|
||||
- 拼图运行态顶部关卡信息采用游戏化铭牌样式:橘棕横向关卡名牌承载 `第 N 关` 和关卡名,左侧固定使用 `media/logo.png` 卡通形象;倒计时作为下挂米白小牌独立显示,紧贴铭牌但不遮挡棋盘。该样式只改变运行态 HUD 视觉,不改变计时、暂停、失败同步或关卡推进规则。
|
||||
- 拼图运行态顶部关卡信息采用游戏化铭牌样式:橘棕横向关卡名牌承载 `第 N 关` 和关卡名,左侧固定使用 `media/logo-runtime-hud.webp` 卡通形象小图;倒计时作为下挂米白小牌独立显示,紧贴铭牌但不遮挡棋盘。该样式只改变运行态 HUD 视觉,不改变计时、暂停、失败同步或关卡推进规则。
|
||||
- 拼图运行态进行中关卡的 `elapsedMs` 仍是结算字段,设置面板的“当前用时”必须按 `startedAtMs`、暂停累计和冻结累计实时派生;不要直接把进行中的 `currentLevel.elapsedMs` 当作展示值。
|
||||
- 推荐页嵌入拼图运行态时,通关结算弹层必须挂到页面级 fixed 浮层,不能留在推荐卡片视觉区内的 absolute 覆盖层;推荐页滑动卡片和运行态视口都使用 `overflow: hidden`,半屏内容区会裁剪排行榜、下一关按钮和相似作品卡。
|
||||
- 推荐页嵌入拼图运行态时,“下一关”应优先切到相似作品;如果当前推荐候选为空,才回退到同作品下一关,避免匿名推荐流在多关卡作品上持续停留在同一作品内。下一关请求 pending 期间必须保留当前 `PuzzleRuntimeShell` 和棋盘,不得把推荐卡整体切回 `加载中...` 占位态;局部同步状态由拼图运行态自己的 busy 表现承接。后端返回的新关卡属于其它作品时,前端必须同步 `selectedPuzzleDetail`、推荐页 `puzzleGalleryEntries` 缓存和 `activeRecommendEntryKey`,让底部作品信息、分享 / 点赞 / 改造和下一次“下一个”基准都指向新作品。
|
||||
@@ -134,29 +163,43 @@ RPG / 拼图等运行态存档仍以 `/api/profile/save-archives` 的后端列
|
||||
|
||||
对外名称:`跳一跳`。工程域:`jump-hop`。PRD 见 `docs/prd/【玩法创作】跳一跳俯视角玩法模板PRD-2026-05-19.md`。
|
||||
|
||||
首版定位为俯视角 / 等距视角 2D 休闲跳跃模板,链路对齐拼图的创作闭环:
|
||||
当前定位为竖屏俯视角 2D 平台跳跃模板,链路对齐平台创作闭环:
|
||||
|
||||
```text
|
||||
创作入口 -> 模板输入 -> 生成过程页 -> 结果页 -> 试玩 -> 发布 -> 运行态
|
||||
创作入口 -> 主题输入 -> 生成过程页 -> 结果页 -> 试玩 -> 发布 -> 运行态
|
||||
```
|
||||
|
||||
创作入口配置事实源仍是 SpacetimeDB `creation_entry_type_config`:默认 `visible=true`、`open=true`、`badge=可创建`、`subtitle=主题驱动平台跳跃`、`image_src=/creation-type-references/jump-hop.webp`。旧库中仍停留在 `subtitle=俯视角跳跃闯关` 且 `image_src=/creation-type-references/puzzle.webp` 的系统默认行会在入口配置播种流程中自动迁移;同时 `spacetime-client` 的入口配置读模型也会对同一条旧系统默认行做纠偏,避免订阅缓存长期回放老口径。后台手动改过的跳一跳入口配置不被覆盖。
|
||||
|
||||
素材生成规则固定为:
|
||||
|
||||
1. 初始草稿生成时,角色形象单独调用一次生图;
|
||||
2. 初始草稿生成时,地块单独调用一次生图,输出 3D 视图的 2D 图片图集;
|
||||
3. 跳一跳地块图集使用专用 `2行*3列` 六格布局,后端按 `start / normal / target / finish / bonus / accent` 顺序切分为透明 PNG;
|
||||
4. 封面和分享图由角色图与地块图轻量合成,不再额外调用第三次生图;
|
||||
5. 显式重生成角色或地块时,只重生成对应资产槽位。
|
||||
1. 创作端只保留主题输入,作品标题、简介、标签和地块提示词由系统派生;
|
||||
2. v1 不再单独生成角色图片,运行态固定使用抠除白底后的陶泥儿 logo 透明 PNG 作为玩家角色;
|
||||
3. 地板贴图只调用一次 image2,输出一张 `1024x1536` 竖版、`3列*6行`、单一纯洋红 `#FF00FF` key 安全缝 / 外圈背景的立方体主题物体 UV 展开图集;image2 要生成 18 个完整 `1x1x1` 立方体主题物体包装,每个大单元格内部固定为 `4列*3行` UV 网:第 1 行第 2 列为 `top`,第 2 行依次为 `left / front / right / back`,第 3 行第 2 列为 `bottom`,其它 UV 空位保持纯洋红。每个大单元格的六个面必须属于同一个方块化主题物体,top/front/right/back/left/bottom 之间的果皮、切面、籽点、条纹、果柄、叶片等身份特征要连续一致,不能把同一张纹理重复六次,也不能六面各画互不相关的小图标。水果主题应生成 18 种可一眼辨认的方块水果 UV,例如方块苹果、方块香蕉、方块橙子、方块西瓜、方块草莓、方块葡萄、方块奇异果、方块菠萝、方块柠檬、方块桃子、方块梨、方块蓝莓、方块芒果、方块椰子、方块火龙果、方块樱桃、方块哈密瓜、方块石榴;苹果需要果柄叶片跨 top/front,香蕉需要剥皮条带跨 front/right,橙子需要放射切面跨 top/front,西瓜需要红瓤黑籽和绿皮条纹在各面连续。禁止文字、UI、底座、托盘、圆台、地板垫层、落地投影、接触阴影、方形阴影、洋红描边、紫色底边、粉色脏边、彩色光晕、发光边、透明背景、留白、自然圆形水果、自然长条香蕉、孤立水果照片、小型贴纸、纯果皮材质、纯果肉纹理、纯叶脉纹理和无法分辨具体物体的抽象纹理;真实透视、极小倒角、侧壁厚度和阴影统一由运行态 Three.js 标准 `1x1x1` 等比立方体生成。后端只把洋红 key 作为图集安全边界处理,先按 3x6 大单元格切出 18 个方块,再按每格 4x3 UV 网切出 108 张 `256x256` 不透明面贴图,不再运行透明化抠图、最大 alpha 连通主体保留或透明安全边补白;若裁切后仍残留极少洋红 key 色,会转成不透明材质底色。前端和后端默认 `tilePrompt` 都必须使用“立方体主题物体 UV 展开包装图集 / cube object UV unwrap atlas”的口径,不再提交“正面30度主题物体 / 平台素材 / 跳台 / 地块成品 / 地砖 / 材质贴片 / 平铺纹理”等会把模型拉回 2D 地块、平台或单纯材质的词,后端生成前也会清洗旧草稿遗留的这些词;当主题或地块提示词命中宝可梦 / 神奇宝贝 / 口袋妖怪 / Pokemon / Pikachu / 精灵球等宝可梦相关词时,仅生图请求侧改写为“原创幻想萌宠冒险道具 / 彩色冒险能量球 / 黄色闪电萌宠符号”,用户草稿标题和主题展示不改;
|
||||
4. 背景底图同样由 image2 生成,复用现有 `coverComposite` / `coverImageSrc` 作为运行态背景读写字段,OSS 槽位固定为 `background/image.png`;提示词必须严格以用户主题关键词为背景主题,结构以左右两侧氛围为主,中央纵轴 1/2 区域保持少元素、简洁、可读且有纵深感,两侧允许更强立体层次和行进感;背景只作为底图,禁止生成跳板、地块、落脚物、角色、UI、返回按钮、文字、路径箭头或海报排版;左上角返回按钮不允许画进背景,而是单独生成 `backButtonAsset` 透明 PNG,OSS 槽位固定为 `back-button/image.png`,提示词要求标准圆形、主题色材质包装、居中左箭头、纯绿色 key 背景,后端去绿后写入作品 profile;
|
||||
5. 后端按从上到下、从左到右均匀切分为 `tile-01` 到 `tile-18`,每个方块再持久化 `tile-XX-top/front/right/back/left/bottom` 六个独立 slot/path,不能按重复的 `tileType` 复用槽位;`tileAssets[].faceAssets` 保存六面贴图,历史兼容字段 `imageSrc/imageObjectKey/assetObjectId` 写 top 面作为旧单贴图 fallback,运行态对旧作品没有 `faceAssets` 时仍可把单张贴图应用到立方体所有面;
|
||||
6. 结果页只展示陶泥儿 logo 透明角色预览、地块池预览和首屏 3 地块预览;不再提供旧角色图生成槽;移动端结果页必须由结果页根容器承接纵向滚动并保留底部安全区,确保素材预览较长时仍能下滑到返回编辑、试玩和发布按钮;
|
||||
7. 前端跳一跳创作 client 的创建会话与执行生成动作请求都必须使用 20 分钟等待窗口,避免背景底图、返回按钮去绿、地板贴图图集切片和 OSS 写入仍在后端执行时被共创会话默认 15 秒超时中断。
|
||||
|
||||
运行态规则真相必须沉到 `module-jump-hop`,前端只做蓄力表现、角色位移、投影和落地反馈。通关、失败、分数、combo、运行态快照和发布作品状态以后端为准。公开列表应走 `jump_hop_gallery_card_view` 订阅缓存,不要每次 HTTP 请求调用 procedure 组装全量列表。
|
||||
待解决问题(风险程度:高):跳一跳创作链路目前仍是一次 HTTP 请求内串行生成背景底图、返回按钮、地板贴图图集、切片和 OSS 写入;VectorEngine image2 单步 timeout/connect 失败会在后端最多重试 5 次,而前端只有 20 分钟总等待窗口。若某次背景底图生成接近或超过 18 分钟,前端会先报“请求超时,请稍后重试”,但后端可能继续跑完并在数分钟后写入草稿;同时因为背景、返回按钮和图集等中间资产未按阶段落库,同一 session 超时后重试会重新从背景图开始生成,存在重复生图、重复计费、用户误以为失败、作品架状态短时间不一致的风险。后续应将跳一跳生成改为后端任务化 / 可轮询真实阶段进度,并在每个素材阶段成功后写入可恢复状态;同时收口后端全局生成 deadline、前端等待策略和失败态回写,确保超时、重试和最终成功不会互相打架。
|
||||
|
||||
平台首页推荐、精选、最新、公开详情、搜索、已玩作品和公开试玩统一按 `sourceType='jump-hop'` 与 `JH-*` 公开作品号识别跳一跳作品;从公开详情或推荐流启动运行态时,若卡片摘要不足以携带角色图、地块图集和路径配置,必须先补读完整 work profile 再传入运行态。平台壳层必须同步注册 `jump-hop-workspace`、`jump-hop-generating`、`jump-hop-result`、`jump-hop-runtime`、`jump-hop-gallery-detail` 阶段,并在 `appPageRoutes.ts` 映射 `/creation/jump-hop/workspace`、`/creation/jump-hop/generating`、`/creation/jump-hop/result`、`/gallery/jump-hop/detail`、`/runtime/jump-hop`,同时持有 session、work、run、gallery、busy/error 与生成进度状态,避免只合入渲染分支但遗漏状态源或分享路径导致 typecheck 失败、刷新回首页。
|
||||
生成页“当前跳一跳信息”只展示实际参与创作提示词的主题、地块提示词等用户可理解信息;`stylePreset` 等未参与当前 image2 提示词组装的内部风格枚举不得作为兜底内容展示,避免把 `minimal-blocks`、`paper-toy` 等工程值暴露给创作者。
|
||||
|
||||
运行态规则真相必须沉到 `module-jump-hop`,前端只做长按蓄力、角色位移、投影和落地反馈。失败、成功跳跃次数、游戏时长冻结、运行态快照和发布作品状态以后端为准。v1 不保留公开 combo / perfect / 通关语义,旧 `score` 兼容映射为成功跳跃次数。公开列表应走 `jump_hop_gallery_card_view` 订阅缓存,不要每次 HTTP 请求调用 procedure 组装全量列表。
|
||||
|
||||
每屏只展示 3 个地块:当前地块、目标地块和下一预览地块。平台流按同一 seed 无限生成,前端不得自行生成正式路径。运行态 HUD 顶部只保留返回按钮和成功跳跃次数,不展示计时器或右上角重开按钮;生成背景和游戏舞台必须覆盖整个运行态视口,HUD 直接绝对定位压在背景上,不再用外层白底、居中窄栏、卡片边框或游戏区域圆角裁切背景。返回按钮固定在左上角安全区,交互热区固定为移动端 `56px`、桌面约 `62px`,不显示“返回”文字,并通过顶部锚点微调与得分标题牌保持协调;运行态优先使用独立 `backButtonAsset` 透明 PNG 作为真实可点击按钮图,旧作品缺失该字段时才使用同尺寸 CSS 主题色圆形按钮兜底。上方成功跳跃次数 UI 复用拼图模板顶部 HUD 结构:`puzzle-runtime-header-card` 内包含陶泥儿 IP logo、居中的“得分”标题牌,以及下挂 `puzzle-runtime-timer-card / puzzle-runtime-timer` 居中数字卡;数字卡展示成功跳跃次数而不是倒计时。游玩中不显示左下角“进行中”状态,也不在屏幕底部常驻排行榜。排行榜按作品维度展示玩家 ID、成功跳跃次数和游戏时长;每位玩家只保留 1 条最佳记录,排序固定为 `成功跳跃次数 desc -> 游戏时长 asc -> 更新时间 asc`,并只在失败结算弹窗内展示,弹窗保留重开和返回动作。
|
||||
|
||||
运行态渲染分层固定为:舞台底层 `.jump-hop-runtime__scene-backdrop` 优先使用 `coverComposite` / `coverImageSrc` 中的 image2 背景底图,图片读取继续走平台资产换签,没有背景时才回退到内置渐变;Three.js 平台层复用同一份标准 `1x1x1` 等比极小倒角立方体几何体,只按单一 side 等比缩放当前 / 目标 / 预览地块,并把 `tileAssets[]` 的生成切片作为主题身份方块包装贴图加载到立方体表面;单块地板保持正轴向摆放,不做 Y 轴偏航或 Z 轴歪斜旋转;`tileAssets[].faceAssets` 存在时,Three.js 材质刷新签名必须纳入 top/front/right/back/left/bottom 六面 texture URL,任一面异步换签或 blob URL 变化都要重建平台材质,不能只监听旧单图 `imageSrc` 或基础 render key;运行态采用约 `1.3x` 近距相机、45° 下压视角和更紧凑的可见地板间距,当前脚下地块基准位于屏幕中线略下方,目标和预览地块向上展开,侧壁、倒角、透视和软椭圆阴影均由 Three.js 统一表现;Three.js 相机和 DOM 角色层必须保持屏幕 X 轴同向,不得通过反向 `camera.up` 或镜像 wrapper 把平台层左右翻转,否则会出现地块显示在右侧但蓄力与飞行动画朝左侧的反向错觉;DOM 地块图片层只作为资产换签、预加载、WebGL 不可用和测试环境 fallback,Three.js 平台层 ready 后必须隐藏 DOM 地块图片和 DOM 阴影,避免露出旧原型方块或双层闪现;推进期存在旧地块退出保留时,Three 平台层必须继续承接 3D 地块渲染,旧地块只跟随后续相机推进逐步离屏,不播放独立飞走动画,超过屏幕后自然销毁;图片读取继续走平台资产换签,并以 `assetObjectId` 作为刷新键避免重生成后沿用旧签名或旧图片缓存。DOM 角色层固定使用 `public/branding/jump-hop-taonier-character.png` 陶泥儿 logo 透明 PNG 并保持在 Three.js 平台层之上。长按蓄力、计时刷新和角色位置变化只能更新 refs 或 DOM 状态,不得销毁重建透明画布、背景、平台贴图预加载层或 DOM 角色层,否则会造成背景、地块和角色层频闪。
|
||||
|
||||
跳一跳当前长按蓄力手感统一采用 `chargeToDistanceRatio=0.004`,用于把长按时长换算成世界跳跃距离;如果历史路径仍保存其它系数,`start_run` 会在开局归一化到新系数。用户按住画面开始蓄力,松手立即起跳;前端必须提交 `dragDistance` 以及换算到后端世界坐标的 `dragVectorX/dragVectorY`,后端正式裁决用该方向向量计算真实落点,只有旧客户端缺失方向、方向非有限数或向量长度过小时,才 fallback 到当前地块中心指向下一块地块中心。成功判定使用下一块地块可见顶面 footprint:后端以该地块 `width/height` 的收缩矩形模拟 45° 视角下的可见顶面,当前命中区约为宽度 72% 和高度 52%,落点进入该视觉顶面则成功,未进入则失败;旧 `landingRadius/perfectRadius` 只保留兼容读写,不再作为当前命中真相。蓄力中角色只做垂直压缩,不沿目标方向拉伸;蓄力反馈可显示朝向当前目标方向的轻量引导,但不显示落点辅助点、投影圈或其它命中提示。松手后运行态必须立即生成 `visualJump`,用当前角色位置作为起点、前端预测真实落点作为终点,播放约 `560ms` 的角色飞行动画:视觉预测必须使用当前显示窗口的 current/next 地块作为方向来源,即使后端最新 run 已提前返回,也不能拿新 run 目标配旧窗口角色导致下一跳反向;角色沿本次提交方向弹向预测真实落点,成功也不得强制吸附回目标地块中心。若后端新 run 晚于飞行动画返回,角色必须停在预测真实落点等待;新 run 到达后应优先用 `lastJump.landedX/landedY` 映射出的真实落点显示角色,再把显示态切到后端最新 run,并用约 `1440ms` 的相机层推进过渡承接新窗口,避免先飞过很远再瞬间拉回地块造成闪现。推进时地块 DOM 层和 DOM 角色层统一包在同一个 camera layer 下移动,旧当前地块只随相机推进保留在屏幕后方,不单独执行向上 / 向下飞走动画;玩家继续向前跳时,旧地块继续被新的相机推进带离视口,超过离屏阈值后自然销毁,新预览地块从上方露出,禁止用 p1/p2 各自 `top/left` 过渡造成角色和地块不同步。相机层推进必须同时使用 X/Y 偏移,从旧真实落点位置斜向滑到新当前地块聚焦位置,不得先横向瞬切到居中再纵向滑动。地块允许保留当前 / 目标 / 预览的深度尺寸差异,但该差异必须通过固定基准宽高上的 `transform: scale(...)` 缓动呈现,并与相机推进使用同一 `1440ms` 节奏;不要直接修改宽高造成瞬切,也不要再给当前态额外叠 CSS scale。相机推进期间角色自身必须禁用 `left/top` transition,只允许父级 camera layer 负责位移,否则角色局部坐标切换和相机推进会叠加,表现为落地后又从屏幕外闪回。
|
||||
|
||||
平台首页推荐、精选、最新、公开详情、搜索、已玩作品和公开试玩统一按 `sourceType='jump-hop'` 与 `JH-*` 公开作品号识别跳一跳作品;从公开详情或推荐流启动运行态时,若卡片摘要不足以携带地板贴图图集和路径配置,必须先补读完整 work profile 再传入运行态。`/runtime/jump-hop?work=JH-*` 这类正式深链必须先通过公开作品号回读 gallery detail,再以 profileId 启动 published run;直接打开没有 `work` 参数的 `/runtime/jump-hop` 时不能停留在空运行态或“正在加载内容”,应回到平台首页。平台壳层必须同步注册 `jump-hop-workspace`、`jump-hop-generating`、`jump-hop-result`、`jump-hop-runtime`、`jump-hop-gallery-detail` 阶段,并在 `appPageRoutes.ts` 映射 `/creation/jump-hop`、`/creation/jump-hop/generating`、`/creation/jump-hop/result`、`/gallery/jump-hop/detail`、`/runtime/jump-hop`,同时持有 session、work、run、gallery、busy/error 与生成进度状态,避免只合入渲染分支但遗漏状态源或分享路径导致 typecheck 失败、刷新回首页。
|
||||
|
||||
跳一跳作品架走创作中心的统一作品列表:前端通过 `/api/creation/jump-hop/works` 拉取作品摘要,草稿态会与 pending notice 合并后显示在作品架里,已发布作品点击后会先按 profileId 读取完整详情再进入详情或运行态。生成中作品仍以后端摘要里的 `generationStatus` 为准,刷新后应能恢复等待遮罩,不能只依赖内存 notice。
|
||||
|
||||
删除等破坏性动作当前未接入 jump-hop 删除 API;如果后续要在作品架提供删除入口,必须先补齐后端/SpacetimeDB/前端整条删除链路,再开放按钮。
|
||||
跳一跳作品架删除入口必须走 `/api/creation/jump-hop/works/{profile_id}`,并通过 SpacetimeDB 同步删除 work profile、源 session、运行态 run 与事件,再刷新作品架和公开广场;不得只做前端本地隐藏。
|
||||
|
||||
推荐页匿名游玩不再限定为跳一跳。移动端一级 `推荐` Tab 是内嵌运行态刷卡流,会自动选择推荐作品并启动对应玩法;桌面端首页不启动这套移动推荐运行态,而是渲染桌面发现壳,展示 `今日游戏`、`推荐`、`作品分类` 等桌面内容。断点事实统一走 `platformEntryResponsive.ts` 的 `usePlatformDesktopLayout()`,平台壳和首页视图必须共用同一个判断,避免桌面发现页与移动推荐页同时挂载、重复触发请求或启动运行态。推荐页嵌入运行态启动时按真实身份分流:已登录用户或本地已有 access token 时继续使用账号 Bearer,但请求选项必须是 local auth impact,避免单卡 401 清空整站登录态;只有确认为匿名访客时才申请短期 Runtime Guest Token,并只把它作为局部请求头传给运行态客户端,不写入全局登录态、不触发 refresh,也不把匿名流量伪装成普通用户。当前覆盖矩阵为:跳一跳、视觉小说、抓大鹅 Match3D、方洞挑战、拼图、敲木鱼、大鱼吃小鱼、汪汪声浪。每个模板的启动请求、推荐页内后续运行态动作以及需要上报的 play/finish/leaderboard/next-level 类请求,都必须继续按该身份分流;公开读取入口仍可匿名读取,创作、个人作品、删除、发布、Remix 等账号/所有权动作仍保持普通用户鉴权。
|
||||
推荐页匿名游玩不再限定为跳一跳。移动端一级 `推荐` Tab 是内嵌运行态刷卡流,会自动选择推荐作品并启动对应玩法;桌面端首页不启动这套移动推荐运行态,而是渲染桌面发现壳,展示 `今日游戏`、`推荐`、`作品分类` 等桌面内容。推荐页候选顺序由前端轻量推荐算法 `platformRecommendation.ts` 统一生成:先按公开作品 key 去重,再使用公开读模型已有的精选来源、近 7 日游玩、点赞、改造、总游玩、发布时间新鲜度、封面和标签完整度做确定性评分,最后优先交错不同玩法类型;只要还有其它玩法候选,就不要连续推荐同一玩法,只有候选池已没有其它玩法时才允许同玩法相邻。该算法不得新增前端业务真相或绕过公开作品 read model。断点事实统一走 `platformEntryResponsive.ts` 的 `usePlatformDesktopLayout()`,平台壳和首页视图必须共用同一个判断,避免桌面发现页与移动推荐页同时挂载、重复触发请求或启动运行态。移动端推荐页拿到推荐作品列表后必须预加载每个作品的卡片封面、主封面和玩法兜底封面;启动或切换作品时先展示当前带玩法标签和标题的作品卡面遮罩,嵌入 runtime 在卡面下层加载,不得再从卡面闪切到另一层单独纯封面图。作品切换提交后,当前 runtime 遮罩接手已在屏幕上的卡面时必须瞬时贴合,不允许再执行“卡面到同一卡面”的淡入或重绘过渡;推荐页 runtime 必须通过统一 `ready` 门控等待对应运行态 run / profile、lazy runtime 组件和 runtime DOM 内图片资源都准备好,且必须持续观察后续新增图片、内联 `background-image` 和换签中的资源标记,不能只在首次挂载时扫描主图或封面;`ready` 返回 `true` 后才由外层放开游戏画面并只让卡面遮罩渐隐。遮罩层级必须高于并隔离下层 runtime,防止运行态 HUD、canvas 或高 `z-index` 子层穿透到封面上;ready 前不展示“加载中”文案,但封面内必须保留无文案加载动效或进度条,避免用户误以为卡片损坏,也不得把未准备好的运行态直接暴露给用户。切换推荐作品时,如果上一条作品的启动请求、退出收口或目标玩法 busy 状态尚未结束,应继续显示当前作品卡面遮罩并等待下一轮自动启动;只有目标作品启动明确失败时,才显示“作品暂时无法进入,请稍后再试。”这类失败态。推荐页内拼图通关后的同 run 相似作品推进不视为推荐作品切换,不能重新显示启动封面;如果需要跨公开作品进入下一关,则必须走推荐页统一切卡入口,不能复用拼图 runtime 的跨作品 handoff,也不能直接把当前 run 改写到另一个作品,`activeRecommendEntryKey` 只能由推荐页统一选择下一作品后更新。推荐页嵌入运行态启动时按真实身份分流:已登录用户或本地已有 access token 时继续使用账号 Bearer,但请求选项必须是 local auth impact,避免单卡 401 清空整站登录态;只有确认为匿名访客时才申请短期 Runtime Guest Token,并只把它作为局部请求头传给运行态客户端,不写入全局登录态、不触发 refresh,也不把匿名流量伪装成普通用户。当前覆盖矩阵为:跳一跳、视觉小说、抓大鹅 Match3D、方洞挑战、拼图、敲木鱼、大鱼吃小鱼、汪汪声浪。每个模板的启动请求、推荐页内后续运行态动作以及需要上报的 play/finish/leaderboard/next-level 类请求,都必须继续按该身份分流;公开读取入口仍可匿名读取,创作、个人作品、删除、发布、Remix 等账号/所有权动作仍保持普通用户鉴权。推荐 runtime 的 `none` / `background` / `runtime-guest` 请求计划和拼图 `default` / `isolated` runtime auth mode 由 `platformRecommendRuntimeAuthModel.ts` 统一判定,平台壳只负责读取 token、申请 Runtime Guest Token 和传递 request options。推荐 runtime 自动启动只由 `platformPublicGalleryFlow.ts` 输出 `noop` / `clear` / `start(entry)` 决策,平台壳只执行清空 state 或启动指定作品。
|
||||
|
||||
## 敲木鱼
|
||||
|
||||
@@ -171,7 +214,7 @@ RPG / 拼图等运行态存档仍以 `/api/profile/save-archives` 的后端列
|
||||
创作输入固定为:
|
||||
|
||||
1. `敲什么`:敲击物单图资产槽位。默认模板使用内置透明 PNG `/wooden-fish/default-hit-object.png` 作为 `bundled-default` 敲击物资产,避免默认关键词被重新语义化改形;用户输入自定义关键词或上传参考图时,后端必须以默认木鱼图作为基础结构和画风参考,使用 image2 生成最终敲击物图案,上传图只作为新主题参考,不直接进入运行态。自定义 `compile-draft` / `regenerate-hit-object` 必须完成 image2 -> OSS 私有对象 -> asset object 登记和绑定后,再由 `api-server` 注入真实 `hitObjectAsset.imageSrc`,不能只写 `/generated-wooden-fish-assets/...` 占位路径,也不能接受前端请求自带的 `hitObjectAsset` 短路生成。
|
||||
2. `敲击音效`:音频资产槽位,当前创作阶段只支持用户上传或麦克风录制;未提供音频时统一写回内置默认木鱼音 `/wooden-fish/default-hit-sound.mp3`。提示词生成音效入口临时关闭,通用 `/api/creation/audio/sound-effect` 对木鱼 `hit_sound` 目标也返回 `410 Gone`;`hitSoundPrompt` 只作为历史兼容字段保留,不参与当前创作流程,也不得由 `spacetime-client` 合成假音频路径。
|
||||
2. `敲击音效`:音频资产槽位,当前创作阶段只支持用户上传或麦克风录制;音频面板必须在前端明确显示 `最长 1 秒`。选择文件或录音结束后,前端只在浏览器本地解码并生成待提交音频对象,不在选择阶段请求 `/api/assets/direct-upload-tickets`。上传和录音统一裁掉前后声音过小片段,裁切后仍超过 1 秒时提示错误且不写入表单状态;有效音频按浏览器端近似算法做响度平衡,目标为 GY/T 377-2023 口径下的 `-15 LKFS`,并做峰值保护后重新编码为可上传 Blob。用户点击 `生成` 时才把处理后的音频直传 OSS、确认 `asset_object`,创作 session/action 只提交 `hitSoundAsset.assetObjectId`、`audioSrc` 和对象 key 等轻量字段;未提供音频时统一写回内置默认木鱼音 `/wooden-fish/default-hit-sound.mp3`。提示词生成音效入口临时关闭,通用 `/api/creation/audio/sound-effect` 对木鱼 `hit_sound` 目标也返回 `410 Gone`;`hitSoundPrompt` 只作为历史兼容字段保留,不参与当前创作流程,也不得由 `spacetime-client` 合成假音频路径。后端对敲木鱼创作 JSON 的放宽 body limit 仅用于兼容旧小程序 Data URL 请求,不作为新链路输入方式。
|
||||
3. `功德有什么`:最多 8 条飘字,创作态首屏只保留一个默认词条 `幸运`,其下提供加号格继续追加词条;创作态只保存词条名,运行态飘字展示时再追加 `+1`。运行态顶部总数卡采用品牌化徽标样式,子项计数器预置展示在可展开面板中,未出现词条初始值为 0。
|
||||
4. `作品标题 / 作品简介 / 主题标签`:不再放在创作工作台首屏,改为生成草稿后的结果页补录区,提交试玩或发布前必须先写回当前作品信息。主题标签编辑样式对齐拼图结果页的胶囊标签编辑器。
|
||||
|
||||
@@ -233,7 +276,7 @@ RPG / 拼图等运行态存档仍以 `/api/profile/save-archives` 的后端列
|
||||
|
||||
当前素材生成流水线:
|
||||
|
||||
1. 点击生成前弹出泥点确认,草稿生成固定消耗 `10` 泥点。
|
||||
1. 点击生成前弹出泥点确认,草稿初始生成成本来自后台入口契约 `creationTypes[].unifiedCreationSpec.mudPointCost`;抓大鹅完整草稿生成按该值一次性预扣,汪汪声浪初始三张图按该值分摊到三次素材请求,结果页单图重新生成仍按单图资产操作计费。
|
||||
2. 先写入可恢复草稿 profile,再执行文本计划、关卡整图生成、三张派生图生成、OSS 上传和素材解析;作品摘要在背景、UI spritesheet 或物品 spritesheet 未完整时下发 `generationStatus=generating`,完整后下发 `ready`,草稿完成条件不包含 `backgroundMusic`。
|
||||
3. 首次调用 VectorEngine `gpt-image-2`,无参考图,竖屏 `9:16`,生成完整抓大鹅关卡画面并持久化到 `generatedBackgroundAsset.levelSceneImageSrc/levelSceneImageObjectKey`。提示词必须包含用户主题描述、顶部返回 / 标题倒计时 / 设置按钮、中间与主题匹配且贴横向边缘的容器,以及底部“移出 / 凑齐 / 打乱”三个道具按钮。
|
||||
4. 关卡整图完成后并发发起三次 `gpt-image-2` 编辑请求,三者都以关卡整图作为参考图:`1K`、`1:1` 的 UI spritesheet 写入 `uiSpritesheetImageSrc/uiSpritesheetImageObjectKey`;`1K`、`9:16` 的背景图写入 `imageSrc/imageObjectKey`;`2K`、`1:1` 的物品 spritesheet 写入 `itemSpritesheetImageSrc/itemSpritesheetImageObjectKey`。
|
||||
@@ -262,7 +305,7 @@ RPG / 拼图等运行态存档仍以 `/api/profile/save-archives` 的后端列
|
||||
- 难度只决定本局加载的物品种类数量:轻松 3、标准 9、进阶 15、硬核 20。硬核仍保留 21 次消除和 63 件总物品,运行态按 20 种素材循环复用,不要求生成第 21 种素材。
|
||||
- 运行态启动前要预加载 `generatedItemAssets[].imageViews[]`、顶层 `generatedBackgroundAsset`、物品挂载 `backgroundAsset` 中的背景、UI spritesheet 和物品 spritesheet;首次生成自动试玩、结果页手动试玩、推荐流和公开详情启动都必须传入提升后的 profile。卡片摘要缺图集字段时,进入运行态前必须补读 work detail。补读后的 profile 也要再次提升 `generatedItemAssets[].backgroundAsset`,确保背景和图集字段传给 `Match3DRuntimeShell`。
|
||||
- 背景图作为运行态全屏背景,图内已经保留容器;旧 `containerImage*` 只作为历史透明容器兼容字段。若 `containerImage*` 与 `uiSpritesheetImage*` 同源,运行态不得把 UI spritesheet 当中心容器图叠到棋盘上。
|
||||
- 抓大鹅运行态 HUD 需贴近拼图顶部信息条的视觉口径:左上只保留透明返回按钮;右上不再暴露设置入口;顶部关卡名和倒计时直接复用拼图同款的铭牌 + 下挂计时牌结构、同色板和同造型,并在牌面左侧挂上 `media/logo.png` 产品 logo;下方备选栏和道具图标只保留内容与交互边界,不再显示灰白半透底板;中央容器图层视觉可隐藏,但棋盘命中边界仍保留。
|
||||
- 抓大鹅运行态 HUD 需贴近拼图顶部信息条的视觉口径:左上只保留透明返回按钮;右上不再暴露设置入口;顶部关卡名和倒计时直接复用拼图同款的铭牌 + 下挂计时牌结构、同色板和同造型,并在牌面左侧挂上 `media/logo-runtime-hud.webp` 产品 logo 小图;下方备选栏和道具图标只保留内容与交互边界,不再显示灰白半透底板;中央容器图层视觉可隐藏,但棋盘命中边界仍保留。
|
||||
- generated 私有图换签未完成时,局内物品先隐藏等待,不得短暂显示默认积木;同一批资源在重启 run 时保留已解析签名 URL,只有资源源列表变化或换签失败后才允许进入兜底视觉。
|
||||
- `itemSize` 只缩放生成 2D 图片本体:`大`、`中`、`小` 均按相对尺寸缩放,其中 `大` 也比原始图片略小,`中` 和 `小` 进一步缩小;不改变后端下发的布局半径、点击半径或三消规则。
|
||||
- 物品进入底部物品栏时按同类型插入:如果物品栏已有同类物品,新物品插到该类型最后一个物品后面,后续物品整体后移;没有同类时追加到当前末尾。达到三件同类时,在飞入物品栏动画结束后,左侧和右侧同类物品向中间合成,三件一起消失,播放合成音效,不展示星星图标,后面的物品再向前补位。该动效只是前端表现层,后端和本地试玩仍负责权威插入、指定点击类型清除与补位后的槽位快照。
|
||||
@@ -319,8 +362,8 @@ RPG / 拼图等运行态存档仍以 `/api/profile/save-archives` 的后端列
|
||||
- 结果页:围绕三图槽位展示错误态与已生成结果,只保留单槽重试、重新生成和上传,不再提供一次生成按钮、音频配置入口或排名配置;生成回写 `partial_failed` 时作品架不再显示整卡“生成中”遮罩,由结果页槽位错误承接失败。
|
||||
- 手动上传:结果页通过平台资产直传 `/api/assets/direct-upload-tickets` 与 `/api/assets/objects/confirm` 写入私有资产,再把返回的历史 generated 路径写回草稿配置。
|
||||
- 发布:结果页确认后必须携带草稿返回的同一个 `workId` 和结果页最终 `publishedSnapshot` 调用 `POST /api/creation/bark-battle/works/publish`;SpacetimeDB 发布态的 `config_json` 必须使用该最终快照,works summary 若拿到 `publishedSnapshotJson` 也优先使用最终快照映射封面三图。发布成功后先进入统一作品详情页,再由详情页进入正式 runtime;缺少 `workId` 的旧草稿状态需要重新生成草稿。
|
||||
- 作品架:Bark Battle 草稿 / 已发布列表优先读取后端 `/works`,但创建、生成完成、保存或发布后的本地摘要必须在后端 read model 尚未回读到同 `workId` 前继续保留;创作中心作品架同时接入 pending shelf 兜底,避免 ready 且三图齐全的草稿在刷新窗口期从“我的草稿 / 已发布”中消失。
|
||||
- 试玩与正式 runtime:草稿试玩使用 `runtimeMode=draft` 和 mock 输入,不写正式 run;正式 runtime 使用 `runtimeMode=published`,进入运行态后直接申请真实麦克风权限,授权成功后立刻进入倒计时,启动对局时调用 `POST /api/runtime/bark-battle/works/{workId}/runs` 登记 start run,并以返回的 `runtimeConfig` 作为本局前端规则参数;结算时调用 `POST /api/runtime/bark-battle/runs/{runId}/finish` 写入基础统计派生指标;对局会在能量条推到任一侧边界时提前结算并弹出独立结算弹窗,运行态内固定提供返回按钮。
|
||||
- 作品架:Bark Battle 草稿 / 已发布列表优先读取后端 `/works`,但创建、生成完成、保存或发布后的本地摘要必须在后端 read model 尚未回读到同 `workId` 前继续保留;创作中心作品架同时接入 pending shelf 兜底,避免 ready 且三图齐全的草稿在刷新窗口期从“我的草稿 / 已发布”中消失。草稿三图完整性、`pending_assets` / `partial_failed` / `ready` 生成状态归一和作品摘要合并规则统一由 `barkBattleWorkCache.ts` 承接,平台壳只执行读取、刷新与 React state 副作用。
|
||||
- 试玩与正式 runtime:草稿试玩使用 `runtimeMode=draft` 和 mock 输入,不写正式 run;正式 runtime 使用 `runtimeMode=published`,进入运行态后直接申请真实麦克风权限,授权成功后立刻进入倒计时,启动对局时调用 `POST /api/runtime/bark-battle/works/{workId}/runs` 登记 start run,并以返回的 `runtimeConfig` 作为本局前端规则参数;结算时调用 `POST /api/runtime/bark-battle/runs/{runId}/finish` 写入基础统计派生指标;对局会在能量条推到任一侧边界时提前结算并弹出独立结算弹窗,运行态内固定提供返回按钮。发布快照拼装、发布回包缺图时沿用草稿图,以及草稿 / 已发布作品进入前端 runtime 前的 `BarkBattlePublishedConfig` 映射也统一由 `barkBattleWorkCache.ts` 提供,缺失 `publishedAt` 时仍按 `updatedAt` 兜底。
|
||||
|
||||
支持的创作者可替换内容:
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 当前产品与工程约束
|
||||
|
||||
更新时间:`2026-05-15`
|
||||
更新时间:`2026-06-05`
|
||||
|
||||
## 项目定位
|
||||
|
||||
@@ -45,7 +45,13 @@ Genarrative / 陶泥儿是一个 AI 原生互动内容与小游戏平台。当
|
||||
2. `login-options` 为空、失败、只返回 `phone` 或只返回 `password` 时,前端仍要同时展示验证码登录页签和密码登录页签;短信能力真实可用性由发送验证码接口返回结果表达。
|
||||
3. 登录弹窗继续复用现有独立 modal 和页签结构,不在页面中新增功能说明类文案,也不把邀请码输入放回登录面板。
|
||||
4. 微信小程序 `web-view` 外壳默认不预登录,首次进入直接打开 H5,并保持与 Web 端一致的未登录状态;只有 H5 触发 `openLoginModal` / `requireAuth` 等受保护入口时,才跳转小程序原生授权态。
|
||||
5. 小程序内需要登录时不展示 H5 登录弹窗,也不走手输手机号 / 短信验证码流程;统一通过原生 `button open-type="getPhoneNumber"` 获取微信手机号授权,再调用 `/api/auth/wechat/miniprogram-login` 与 `/api/auth/wechat/bind-phone` 换取系统登录态。
|
||||
5. 小程序内需要登录时不展示 H5 登录弹窗,也不走手输手机号 / 短信验证码流程;统一先通过 `wx.login` 获取微信登录 code 并调用 `/api/auth/wechat/miniprogram-login` 完成快捷登录。若该接口返回 `created=true`,或返回用户昵称仍是手机号、公开陶泥号、“微信旅人”等默认展示值,才展示原生 `input type="nickname"` 补充微信昵称并再次调用 `/api/auth/wechat/miniprogram-login` 写入 `displayName`。若后端返回 `pending_bind_phone`,再通过原生 `button open-type="getPhoneNumber"` 获取微信手机号授权并调用 `/api/auth/wechat/bind-phone` 换取系统登录态。
|
||||
6. 小程序外壳注入到 H5 URL 的 `clientType`、`clientRuntime`、`miniProgramEnv` 是宿主上下文,H5 内部 `pushState` / 阶段导航必须跨页面保留,避免登录和充值误判为普通浏览器;首点时微信 JS bridge 可能尚未就绪,前端还需用 `MicroMessenger + miniProgram` User-Agent 作为小程序识别兜底。
|
||||
7. 小程序 `web-view` 页必须启用好友分享与朋友圈分享,分享目标固定回到 `pages/web-view/index`,不把 H5 当前 URL 作为不受控启动参数传回小程序页。
|
||||
8. 小程序 `web-view` 外壳运行时通过 `wx.getAccountInfoSync().miniProgram.envVersion` 自动识别版本:线上版 `release` 使用 `www.genarrative.world`,体验版 `trial` 与开发版 `develop` 使用 `dev.genarrative.world`;传给后端的 `x-mini-program-env` 分别为 `release`、`trial`、`dev`。
|
||||
9. 账号信息面板只展示 `账号信息` 标题;绑定手机号和绑定微信以紧凑模块展示当前绑定状态,已绑定手机号展示完整手机号,已绑定微信优先展示微信平台实际返回并由后端保存的 `wechatDisplayName`。小程序 `jscode2session` 不能直接返回微信昵称或个人微信号,只能稳定拿到当前小程序维度的 `openid`,并在满足微信开放平台条件时拿到 `unionid`;小程序昵称来自快捷登录后按需展示的原生 `input type="nickname"` 提交的 `displayName`。后端下发 `wechatAccount` 作为绑定账号标识,前端在没有真实昵称时展示微信账号尾号,不展示裸“已绑定”。换绑入口放在对应模块右上角,退出登录和退出全部设备固定放在面板内容最底部。
|
||||
10. H5 登录态从未登录变为已登录,或从已登录变为未登录后,必须刷新当前页面一次,确保推荐运行态、作品架、个人缓存和私有 query 都按新身份重新初始化;普通 access token 续期、账号资料更新和同一登录态内的设置变化不得触发整页刷新。
|
||||
11. 同一账号允许多端同时在线。新增登录和单设备退出只影响对应 refresh session,不得提升账号级 `tokenVersion` 让其它设备的 access token 失效;只有“退出全部设备”、修改密码、重置密码等明确安全动作才吊销全端 refresh session 并提升 `tokenVersion`。
|
||||
|
||||
## 账户与充值
|
||||
|
||||
@@ -91,13 +97,13 @@ server-rs + Axum + SpacetimeDB
|
||||
3. 点击按钮弹出独立面板时,必须弹出 dialog / drawer / modal,不要在当前面板下方展开内容。
|
||||
4. 优先复用现有系统、页面、组件和弹层,不因一次需求新建平行系统。
|
||||
5. 游戏式页面要防止文字、按钮、HUD、底部 dock、输入法和画布互相遮挡。
|
||||
6. 平台根壳已处理移动端输入法聚焦:输入法弹出时保持画布稳定高度,用偏移聚焦输入框,业务组件不要重复注册全局键盘适配。
|
||||
6. 平台根壳已处理移动端输入法聚焦:输入法弹出时保持画布稳定高度,只记录键盘状态、隐藏底部 dock 并补齐浅色暴露背景,不再全局上移平台壳;业务组件不要重复注册全局键盘适配。
|
||||
7. 主站入口已锁定移动端页面级缩放;单个游戏页面不要再重复实现整页缩放锁定。
|
||||
8. 图像输入通用 UI 统一走 `src/components/common/CreativeImageInputPanel.tsx`。外层页面持有业务状态,组件只承担上传卡、预览、参考图缩略图、AI 重绘开关、错误展示和提交按钮。
|
||||
9. 发现页 `分类` 子频道的筛选必须打开独立 dialog / drawer / modal,至少支持玩法类型过滤与排序切换;筛选结果为空时显示空状态,不把筛选内容展开在当前列表下方。
|
||||
10. 移动端“我的”页顶部品牌行承载扫码和设置入口,正文按参考图顺序组织为头像 / 昵称 / 陶泥号、会员横幅、三张统计卡、每日任务、五项常用功能宫格、设置入口和法律信息;`media/profile/` 中的陶泥素材作为该页图形资产。常用功能宫格固定承载泥点充值、邀请好友、兑换码、玩家社区、反馈与建议;当前只展示四项常驻入口时必须按四列铺满整行,不保留五列网格导致左对齐空位。页面不再提供独立存档按钮入口,也不在底部保留旧的填邀请码次级入口。填邀请码只由邀请链接 query 或其它明确引导打开独立弹窗,不作为“我的”页常驻按钮。
|
||||
11. “我的”页每日任务卡必须展示后端 `/api/profile/tasks` 返回的当前任务摘要,包括奖励泥点数、进度和领取 / 去完成 / 已完成状态;任务领取成功后,卡片摘要必须跟随返回的任务中心数据同步刷新,不能继续硬编码 `0 / 1` 或只更新弹窗内任务列表。
|
||||
12. “我的”页泥点、游戏时长、已玩游戏数量三张统计卡只展示各自标签和值,三个统计 icon 使用小尺寸普通 UI 档位,内容不换行,不在统计区底部展示“更新于”时间;移动端昵称、会员卡、每日任务、常用功能和法律信息也应保持 `10px` 到 `14px` 的普通 UI 字号区间,避免展示级字号挤压内容。
|
||||
10. 移动端“我的”页顶部品牌行承载扫码和设置入口,正文按参考图顺序组织为头像 / 昵称 / 陶泥号、会员横幅、三张统计卡、每日任务、五项常用功能宫格、通用设置入口和法律信息;`media/profile/` 中的陶泥素材作为该页图形资产。常用功能宫格固定承载泥点充值、邀请好友、兑换码、玩家社区、反馈与建议;当前只展示四项常驻入口时必须按四列铺满整行,不保留五列网格导致左对齐空位。页面不再提供独立存档按钮入口,也不在底部保留旧的填邀请码次级入口;主题设置、账号与安全只作为通用设置弹窗下一级入口,不在“我的”页外层单独占行。填邀请码只由邀请链接 query 或其它明确引导打开独立弹窗,不作为“我的”页常驻按钮。
|
||||
11. “我的”页每日任务卡必须展示后端 `/api/profile/tasks` 返回的当前任务摘要,包括奖励泥点数和进度;外层任务卡不展示“去完成”等左右侧行动按钮,领取 / 去完成 / 已完成状态只在任务中心弹窗内表达。任务领取成功后,卡片摘要必须跟随返回的任务中心数据同步刷新,不能继续硬编码 `0 / 1` 或只更新弹窗内任务列表。用户停留在“我的”页跨过北京时间 0 点时,前端必须非阻断刷新登录态以补齐 `daily_login` 埋点,再重拉任务中心,避免继续展示上一自然日已领取状态。
|
||||
12. “我的”页泥点余额、累计游玩、已玩游戏三张统计卡只展示各自标签和值,三个统计 icon 使用小尺寸普通 UI 档位,内容不换行,不在统计区底部展示“更新于”时间;移动端昵称、会员卡、每日任务、常用功能和法律信息也应保持 `10px` 到 `14px` 的普通 UI 字号区间,避免展示级字号挤压内容。
|
||||
13. 移动端“我的”页需要兼容窄屏:头像 / 昵称 / 陶泥号、三张统计卡、每日任务、五项常用功能和法律信息都必须能在底部固定 TabBar 上方完整滚动露出,不得与底部 dock、刘海 safe-area 或相邻 UI 元素遮挡重叠。
|
||||
14. RPG 等运行态的战斗飘字、血量变化和即时反馈必须在暗色、噪声高的场景背景上保持可读:使用高亮文字、深色描边、强阴影或小面积半透明底,不只依赖红/绿文字本身表达伤害或治疗。
|
||||
15. 平台亮色 UI 配色以陶泥儿主视觉为准:暖白 / 米杏底、陶土橙主按钮、深棕正文与浅杏边框;新增界面优先复用 `src/index.css` 的 `--platform-*` 主题变量和 `apps/admin-web/src/styles/admin.css` 的同系色值,不再引入粉红、蓝绿等独立主色方案。
|
||||
|
||||
Reference in New Issue
Block a user