# 拼图玩法创作与运行态最小落地技术方案 日期:`2026-04-22` ## 1. 文档目的 本文件承接 PRD《AI 原生拼图玩法创作工具与玩法系统》,冻结本轮拼图玩法在当前平台内的最小完整闭环。 本轮目标不是抽象一个通用拼图编辑器,也不是额外搭建一个脱离平台的新小游戏站点,而是在现有平台壳层内新增独立 `puzzle` 玩法域,跑通下面这条主链: 1. 平台创作入口选择拼图玩法 2. Agent-first 对话收束 5 个高杠杆视觉锚点 3. 编译结果页草稿 4. 在结果页编辑关卡名、标签并生成候选拼图图片 5. 发布作品进入拼图广场 6. 玩家从广场进入第 1 关 7. 后端初始化 `3x3 / 4x4` 棋盘 8. 后端裁决交换、合并、拖动、拆分与通关 9. 通关后根据“标签相似度 `70%` + 同作者 `30%`”推荐下一关 ## 2. 本轮明确不做 1. 不做异形拼块、旋转拼块、道具、体力、倒计时。 2. 不做新的平台站点或新的全局导航系统。 3. 不做前端本地推荐、前端本地裁决、前端本地持久化真相。 4. 不做复杂图片模型编排;首版图片生成沿用 `api-server` 的占位资产生成方式,保证完整链路可跑通。 5. 不把拼图玩法继续挂在 `customWorld`、`rpgWorld` 或 RPG runtime 旧语义下。 6. 不扩到拼图排行榜、社交评论、收藏、复盘系统。 ## 3. 分层边界 ### 3.1 前端 前端继续使用当前 `React + TypeScript + Vite` 平台壳层,只负责: 1. 展示拼图创作中心、Agent 工作区、结果页、广场、运行时画布。 2. 发起聊天、结果页编辑、发布、开始游戏、交换与拖动请求。 3. 基于后端快照渲染棋盘、HUD、选中态与合并反馈。 前端禁止: 1. 自行判断下一关推荐。 2. 自行判断拼块是否应当合并。 3. 自行判断合并块是否应当拆分。 4. 自行判断通关。 ### 3.2 Axum `server-rs/crates/api-server` 负责: 1. 对外暴露 `/api/runtime/puzzle-*` HTTP 接口。 2. 鉴权、请求上下文、错误 envelope。 3. 结果页占位图片生成与静态资产落盘。 4. 调用 `spacetime-client` 读写拼图玩法真相态。 ### 3.3 SpacetimeDB `server-rs/crates/spacetime-module` 负责: 1. 存储拼图 Agent session / message。 2. 存储已发布拼图作品 profile。 3. 存储拼图运行态 run snapshot。 4. 通过 procedure 同步返回 session / works / gallery / runtime 快照。 ### 3.4 纯领域 crate 新增 `server-rs/crates/module-puzzle`,承载: 1. 5 个锚点与会话阶段的纯领域模型。 2. 草稿编译、标签规范化、发布校验。 3. `3x3 / 4x4` 棋盘初始化。 4. 交换、合并、拖动、拆分、通关与下一关推荐算法。 ## 4. 共享契约 ### 4.1 TypeScript shared contracts 在 `packages/shared/src/contracts/` 新增: 1. `puzzleAgentSession.ts` 2. `puzzleAgentDraft.ts` 3. `puzzleAgentActions.ts` 4. `puzzleResultPreview.ts` 5. `puzzleWorkSummary.ts` 6. `puzzleRuntimeSession.ts` 这些文件分别承载: 1. Agent session / message / anchor pack 2. 结果页草稿与候选图片 3. Agent actions 与 works/gallery mutation request 4. 结果页 publish gate / preview 5. owner-only works 与 gallery card 6. runtime run / board / swap / drag / next-level contract ### 4.2 Rust shared contracts 在 `server-rs/crates/shared-contracts/src/` 新增: 1. `puzzle_agent.rs` 2. `puzzle_works.rs` 3. `puzzle_gallery.rs` 4. `puzzle_runtime.rs` Rust DTO 只承载对前端公开的 HTTP contract,不直接泄露 `module-puzzle` 内部实现细节。 ## 5. Spacetime 表与 procedure 本轮保持“最小闭环优先”,作品与运行时仍以结构化字段 + `snapshot_json` 组合持久化,不额外拆出更多高耦合表。 ### 5.1 `puzzle_agent_session` 字段: 1. `session_id` 2. `owner_user_id` 3. `seed_text` 4. `current_turn` 5. `progress_percent` 6. `stage` 7. `anchor_pack_json` 8. `draft_json` 9. `last_assistant_reply` 10. `published_profile_id` 11. `created_at` 12. `updated_at` ### 5.2 `puzzle_agent_message` 字段: 1. `message_id` 2. `session_id` 3. `role` 4. `kind` 5. `text` 6. `created_at` ### 5.3 `puzzle_work_profile` 字段: 1. `profile_id` 2. `owner_user_id` 3. `source_session_id` 4. `author_display_name` 5. `level_name` 6. `summary_text` 7. `theme_tags_json` 8. `cover_image_src` 9. `cover_asset_id` 10. `anchor_pack_json` 11. `publication_status` 12. `play_count` 13. `updated_at` 14. `published_at` ### 5.4 `puzzle_runtime_run` 字段: 1. `run_id` 2. `owner_user_id` 3. `entry_profile_id` 4. `current_profile_id` 5. `cleared_level_count` 6. `current_level_index` 7. `current_grid_size` 8. `played_profile_ids_json` 9. `previous_level_tags_json` 10. `snapshot_json` 11. `updated_at` 12. `created_at` ### 5.5 Procedure 本轮全部使用 procedure 同步返回快照,避免 Axum 再次读 private table: 1. `create_puzzle_agent_session` 2. `get_puzzle_agent_session` 3. `submit_puzzle_agent_message` 4. `compile_puzzle_agent_draft` 5. `save_puzzle_generated_images` 6. `select_puzzle_cover_image` 7. `publish_puzzle_work` 8. `list_puzzle_works` 9. `get_puzzle_work_detail` 10. `update_puzzle_work` 11. `list_puzzle_gallery` 12. `get_puzzle_gallery_detail` 13. `start_puzzle_run` 14. `get_puzzle_run` 15. `swap_puzzle_pieces` 16. `drag_puzzle_piece_or_group` 17. `advance_puzzle_next_level` ## 6. 结果页图片生成策略 本轮后续已经接入 `api-server` 统一资产链路:拼图候选图由 `api-server` 调用图像服务生成,再以 OSS 对象作为持久化真值,SpacetimeDB 只保存候选图 URL、assetId 与 prompt snapshot。 1. 每次生成 2 张候选图。 2. 候选图通过 `api-server` 写入 OSS,兼容展示路径统一为 `/generated-puzzle-assets/...`,禁止再落到仓库 `public/` 目录。 3. Axum 把候选图 URL、assetId、prompt snapshot 回写到 Spacetime session draft。 4. 百梦主在结果页选择其中 1 张作为正式图。 这样可以保证: 1. 结果页图片生成、重生、应用正式图完整可用。 2. 发布链有正式图片可校验。 3. 不再依赖本地 `public/` 占位目录,避免开发工作区混入运行时生成文件。 ### 6.1 发布前编辑真相补充 结果页允许百梦主在发布前直接编辑: 1. `关卡名` 2. `摘要` 3. `题材标签` 这 3 个字段不能只停留在前端临时态。 本轮冻结为: 1. `publish_puzzle_work` 允许直接携带 `levelName / summary / themeTags` 2. `spacetime-module` 在发布事务内先把这些字段覆盖回 session draft 真相 3. 覆盖后的 draft 再参与发布校验与 profile 持久化 这样可以避免额外新增一条“草稿轻量编辑 procedure”,同时确保结果页编辑内容会真实进入广场作品与后续运行时 HUD。 ## 7. 运行态规则冻结 ### 7.1 难度推进 ```ts function resolvePuzzleGridSize(clearedLevelCount: number): 3 | 4 { return clearedLevelCount >= 3 ? 4 : 3; } ``` ### 7.2 棋盘初始化 1. 根据正式图片与网格规格生成 `pieceId -> correctRow/correctCol`。 2. 随机打乱到非完成态。 3. 生成初始 `mergedGroups = []`,再执行一次正确连接检查。 ### 7.3 正确连接 若两个拼块在当前棋盘中四向相邻,且它们在原图上的正确位置也以同方向相邻,则视为正确连接。 所有正确连接链通过并查集合并为 `mergedGroup`。 ### 7.4 拖动与拆分 1. 单块拖到单块位置:执行交换。 2. 合并块拖到任意目标锚点:保持内部相对布局整体重排。 3. 若合并块整体平移后覆盖到多个单块,被覆盖单块必须与合并块腾出的原格子做一对一交换,禁止把多个单块回填到同一个源格。 4. 一对一交换必须满足: - 每个被覆盖单块只移动一次。 - 每个被腾出的源格只接收一个被覆盖单块。 - 若腾出的源格数量与被覆盖单块数量不一致,本次拖动视为非法,不更新棋盘。 3. 单块拖到合并块占据位置:先拆分目标合并块,再执行交换,最后重算合并。 ### 7.5 通关 当所有拼块回到正确位置,或全盘只剩一个覆盖全部拼块的合并组时,标记当前关卡 `cleared`。 ### 7.6 下一关推荐 固定公式: ```ts finalScore = tagSimilarityScore * 0.7 + sameAuthorScore * 0.3; ``` 标签相似度首版使用规范化标签集合的 Jaccard。 同分裁决顺序: 1. `tagSimilarityScore` 更高 2. 当前 run 未出现过 3. `play_count` 更低 4. `updated_at` 更近 ## 8. 前端接入 ### 8.1 平台入口 只改现有平台壳层: 1. 在创作类型弹层新增“拼图玩法”。 2. 新增拼图专属 stage,不改 RPG runtime 主链。 ### 8.2 组件目录 新增: 1. `src/components/puzzle-agent/` 2. `src/components/puzzle-result/` 3. `src/components/puzzle-gallery/` 4. `src/components/puzzle-runtime/` ### 8.3 服务目录 新增: 1. `src/services/puzzle-agent/` 2. `src/services/puzzle-works/` 3. `src/services/puzzle-gallery/` 4. `src/services/puzzle-runtime/` 本轮全部走 HTTP facade,不引入新的前端 Spacetime 直连。 ### 8.4 当前前端最小落地补充 当前实现固定走下面这条最小链路: 1. `PlatformEntryCreationTypeModal` 选择 `puzzle` 2. `PuzzleAgentWorkspace` 收束锚点并触发 `compile_puzzle_draft` 3. `PuzzleResultView` 编辑 `levelName / summary / themeTags` 4. 图片生成通过独立 `PuzzleImageStudioModal` 触发,不在结果页内联堆叠 5. 发布后跳转 `PuzzleGalleryDetailView` 6. 从详情进入 `PuzzleRuntimeShell` 创作中心作品展示冻结为: 1. 拼图作品也是平台作品,和其他创作作品共用同一套列表项样式。 2. 创作中心不再保留独立“拼图玩法作品模块”。 3. 拼图作品仅通过 `拼图` 标签与题材标签区分,不额外拆出第二块作品区。 4. 创作中心仍保留统一“新建作品”入口,由创建类型弹层继续分流到 RPG / 拼图玩法。 运行时前端表现冻结为: 1. 使用正式封面图按 `correctRow / correctCol` 做真实网格切片渲染 2. 点击两块时仅前端维护轻量选中态,真正交换以后端返回快照为准 3. 拖动统一采用 pointer 事件,兼顾网页端与移动端 4. 不在前端计算合并、拆分、通关与下一关推荐 ## 9. 验收与检查 完成后至少执行: 1. `npm run check:encoding` 2. `npm run typecheck` 3. `npm run test` 4. `cargo check -p module-puzzle` 5. `cargo check -p shared-contracts` 6. `cargo check -p spacetime-module` 7. `npm run spacetime:generate` 8. `cargo check -p spacetime-client` 9. `cargo check -p api-server` 如果检查中发现拼图主链缺口,继续补齐;如果已经满足 PRD 主链和上述检查,不再追加额外玩法能力。 ## 10. 2026-04-22 最终验收记录 本轮已按“最小完整闭环、禁止超出需求过度实现”完成拼图玩法主链落地,并补齐收尾检查。 ### 10.1 已落地主链 1. 平台创作中心可选择 `puzzle` 玩法入口。 2. `PuzzleAgentWorkspace` 已接入 Agent-first 锚点收束与草稿编译。 3. `PuzzleResultView` 已支持最小结果页编辑与独立图片生成弹层。 4. 发布后作品可进入拼图广场与详情页。 5. `PuzzleRuntimeShell` 已按正式封面图真实切片渲染 `3x3 / 4x4` 关卡。 6. 交换、拖动、拆分、合并、通关、下一关推荐真相全部以后端快照为准。 ### 10.2 本轮额外修复的验收阻塞 在最终验收阶段,补齐了与拼图主链无直接业务耦合、但会阻塞仓库整体检查的基线问题: 1. `typecheck` 基线类型不兼容。 2. `AccountModal` 测试 mock 字段落后于最新鉴权契约。 3. `customWorld` 存档归一化中场景连接方向未收敛到强类型。 4. 结果页生成资源在签名 URL 尚未返回时会短暂空白,已调整为先展示原路径占位,再异步替换签名读地址。 ### 10.3 实际通过的检查 1. `npm run check:encoding` 2. `npm run typecheck` 3. `npm run test` 4. `cargo check -p module-puzzle` 5. `cargo check -p shared-contracts` 6. `cargo check -p spacetime-module` 7. `cargo check -p spacetime-client` 8. `cargo check -p api-server` ### 10.4 冻结说明 截至本次验收,拼图玩法已满足 PRD 要求的最小产品闭环;未继续扩展排行榜、提示、体力、异形拼块、倒计时、前端本地裁决等超出本轮需求的能力。 ## 11. 2026-04-26 运行态机制补齐记录 本次按 PRD 第 9 章补齐拼图运行态的未完成机制,落点保持在 `server-rs/crates/module-puzzle` 领域层;前端本地兜底只同步表现和离线闭环,不改变后端真相源。 ### 11.1 棋盘初始化 1. `build_initial_board_with_seed` 使用种子化洗牌生成初始棋盘,不再固定左移一格。 2. 正式 run 的种子由 `runId + profileId + levelIndex + gridSize` 派生;由于每次进入都会创建新的 `runId`,同一作品多次进入也会得到不同打乱样式。 3. 洗牌后若极端情况下仍为完成态,强制旋转一次,保证新关卡不是已完成局面。 4. 初始棋盘不得存在任何原图相邻块互相贴边;初始化会多次洗牌筛选,若极端情况下未命中,则使用确定性约束搜索兜底,避免开局出现局部连续结构。 5. `module-puzzle` 与本地 fallback 的测试都必须直接断言初始棋盘不存在原图相邻贴边对,不能只检查 `mergedGroups = []`。 ### 11.2 局部重算与合并 1. 交换后只把源格、目标格和四向邻格纳入重算范围。 2. 拖动后把源格、目标格、被移动合并块边界格、被拆分目标合并块格子纳入重算范围。 3. 对受影响范围内的旧合并组先拆回单块,再按正确四向相邻关系重新生成合并组;未受影响的旧合并组保留。 4. 每次生成快照时统一重编号合并组,避免保留组与新组出现重复 `groupId`。 ### 11.3 拖动与拆分 1. 单块拖到单块位置时执行交换。 2. 合并块拖动时保持内部相对布局,以被拖动块作为锚点整体平移。 3. 单块拖入合并块占据位置时,先拆分目标合并块,再完成本次交换,最后按受影响范围重新合并。 ### 11.4 本次新增验证 1. `cargo test -p module-puzzle` 覆盖:每次 run 不同打乱、正确相邻自动合并、单块拖入合并块拆分目标组。 2. `npm run test -- src/services/puzzle-runtime/puzzleLocalRuntime.test.ts` 覆盖:本地兜底每次启动不同打乱、交换后正确相邻自动合并、通关后推进下一关。 3. `npm run check:encoding` 已通过,确认中文文档未被编码写坏。 ## 12. 2026-04-26 二次运行态缺口补齐 本次继续按 PRD 第 9.12 与第 14.4 节收敛两个剩余缺口: 1. 通关判定必须同时支持“所有拼块回到正确位置”和“所有拼块汇成一个覆盖全盘的大合并块”。领域层以 `all_tiles_resolved` 作为唯一对外真相,但其计算来源必须包含这两个条件。 2. 运行态底部不再常驻玩法说明文字,只保留短状态反馈、错误反馈和下一关动作;点击/拖动规则不写成长期 UI 文案。 ### 12.1 合并块可见性修正 用户反馈“正确连接的块自动合并没有看到”后,确认原实现只把已合并格子染成绿色,仍按单块逐格渲染,视觉上无法形成“合并块”。本次运行态画布改为: 1. 根据 `mergedGroups` 计算合并块外接矩形。 2. 原单格位置让位为透明占位。 3. 在棋盘上叠加一个跨格整体层,内部仍按原图切片拼接,但外边框、阴影和拖动事件都属于同一个合并块。 4. 合并块整体层以组内第一块作为拖动锚点,继续沿用后端/本地运行态的合并块拖动裁决。 ### 12.2 拖动可用性修正 用户反馈“没有办法拖动拼图块”后,确认原交互只在 pointer move 超过阈值后记录 `dragging = true`,没有持续记录当前指针位置,也没有把拖动中的块做视觉平移;移动端还可能被浏览器默认触控手势抢占。修正如下: 1. `dragState` 持续记录 `currentX/currentY`,拖动中按指针偏移对单块或合并块做 `translate3d` 跟手反馈。 2. 棋盘与合并块交互层增加 `touch-none select-none`,避免移动端滚动、选中文本等默认行为打断拖动。 3. 松手后仍只提交 `pieceId + targetRow + targetCol`,最终交换、合并、拆分和通关继续以后端/本地运行态快照为准。 ### 12.3 合并块外轮廓描边修正 用户反馈“合并的块的边界显示要描边自己的块的边界,不要搞一个正方形或者矩形的边界”后,移除合并块外接矩形 `ring` 层。运行态现在按合并组真实占据格逐格判断四向邻居:某一边没有同组合并格时才画该边描边,同组内部相邻边不画线。这样 L 形、长条或其他非矩形合并块只显示自身外轮廓,拖动热区仍只覆盖真实拼块格。 后续反馈要求合并块边界也要圆角后,外轮廓描边补充按四个角判断:只有相邻两条外露边同时存在的真实外轮廓角才应用圆角,内部拼接角保持直角且不显示分界线。 2026-05-01 追加修正:合并块的圆角不能继续依赖逐格 `border-radius` 叠加。运行时应根据合并组真实占据格提取一条整体 SVG 轮廓路径,外凸角和内凹角都通过同一条路径生成二次贝塞尔圆角;合并块图片层也必须用这条整体路径裁剪,避免 L 形、阶梯形凹口处仍露出直角图片像素。 ### 12.4 第二关后打乱规则旁路修正 用户反馈“从第二关开始打乱规则像是完全相同”后,检查发现 `api-server` 的本地下一关 fallback 仍使用旧版 `build_local_puzzle_board` 固定左移一格,没有复用 `module-puzzle` 的种子化初始化规则。该路径会在图库/正式推荐不可用、由 API 临时构造下一关时触发。 修正后 `api-server` 本地下一关构造改为调用 `module_puzzle::build_initial_board_with_seed`,种子由 `runId + profileId + levelIndex + gridSize` 派生;因此第二关、第三关以及后续 fallback 关卡也会得到不同布局,并继续满足“开局没有原图相邻块贴边”的约束。