Merge branch 'master' of http://82.157.175.59:3000/GenarrativeAI/Genarrative
Some checks failed
CI / verify (push) Has been cancelled

This commit is contained in:
2026-05-01 01:55:57 +08:00
168 changed files with 14616 additions and 2420 deletions

View File

@@ -47,7 +47,7 @@ server-rs/crates/api-server/src/prompt/big_fish.rs
同时把 `prompt/mod.rs` 补齐为正式导出入口,和现有:
1. `puzzle_image.rs`
1. `puzzle/image.rs`
2. `character_visual.rs`
3. `character_animation.rs`
4. `scene_background.rs`

View File

@@ -0,0 +1,79 @@
# RPG 剧情与模板创作模型路由调整2026-04-30
## 1. 背景
当前 `server-rs` 的文本模型主链统一通过 `platform-llm` 走 Ark OpenAI 兼容 `/chat/completions`。本轮模型切换有两个不同边界:
1. RPG 运行时剧情推理继续使用 Ark `/chat/completions`,但模型固定为 `doubao-seed-character-251128`
2. 模板创作流程的大模型推理统一使用 Ark `/responses`,模型固定为 `deepseek-v3-2-251201`,并按 Responses API 的 `tools: [{ type: "web_search", max_keyword: 3 }]` 方式启用联网搜索。
因此本次不能只替换 `GENARRATIVE_LLM_MODEL` 默认值。默认值仍可能被通用代理或其他兼容调用使用RPG 剧情与模板创作需要在业务请求上显式覆盖模型和协议,避免两条主链互相污染。
## 2. 落地范围
### 2.1 RPG 剧情推理
以下运行时 RPG 推理请求必须显式使用:
- model: `doubao-seed-character-251128`
- protocol: `/chat/completions`
覆盖入口:
1. `runtime_story/compat/ai.rs`
- 首段剧情与继续剧情。
- NPC 对话剧情文本。
- 预留的动作结果叙事生成。
2. `runtime_chat.rs`
- NPC 单轮聊天回复。
- NPC 单轮后续建议。
3. `runtime_chat_plain.rs`
- 角色私聊回复、建议、摘要。
- NPC 对话、招募对话等纯文本流。
### 2.2 模板创作流程
以下创作链路必须显式使用:
- model: `deepseek-v3-2-251201`
- protocol: `/responses`
- web_search: 开启时映射为 `tools: [{ "type": "web_search", "max_keyword": 3 }]`
覆盖入口:
1. `creation_agent_llm_turn.rs`
- RPG/自定义世界 Agent 单轮 JSON turn。
- 大鱼吃小鱼 Agent 单轮 JSON turn。
- 拼图 Agent 单轮 JSON turn。
- 动态状态判断等非流式 JSON turn。
2. `custom_world_foundation_draft.rs`
- 世界框架、角色、场景、角色详情等分阶段底稿生成。
- JSON 修复阶段。
3. `custom_world_agent_entities.rs`
- 结果页新增角色/地点生成。
4. `custom_world_ai.rs`
- 结果页兜底补齐实体生成。
5. `big_fish_draft_compiler.rs`
- 大鱼吃小鱼草稿结构化编译与 JSON 修复。
图片、视频、OSS、SpacetimeDB reducer 不属于本次模型切换范围。
## 3. 平台层改造
`platform-llm` 保留原 `/chat/completions` 能力,并新增 Responses 协议:
1. `LlmTextProtocol::ChatCompletions`
2. `LlmTextProtocol::Responses`
3. `LlmTextRequest::with_responses_api()`
4. `LlmConfig::responses_url()`
Responses 非流式解析优先读取 `output_text`,再兼容 `output[].content[].text`。Responses 流式解析只把 `response.output_text.delta``delta` 推给上层,避免把 reasoning summary、工具事件或完成事件误拼进玩家可见文本。
## 4. 验收标准
1. RPG 运行时 LLM 请求在代码层显式带 `doubao-seed-character-251128`
2. 创作模板 LLM 请求在代码层显式带 `deepseek-v3-2-251201` 与 Responses 协议。
3. `platform-llm` 单测覆盖 Responses 非流式、Responses SSE、Responses web_search tools 请求体。
4. `cargo test -p platform-llm --manifest-path server-rs/Cargo.toml` 通过。
5. `cargo test -p api-server creation_agent_llm_turn --manifest-path server-rs/Cargo.toml` 通过。
6. 修改后按项目约束使用 `npm run api-server:maincloud` 重新启动后端,并执行相应自动测试。

View File

@@ -23,11 +23,12 @@
1. 点击昵称右侧编辑按钮打开独立弹窗。
2. 弹窗内只提供昵称输入、取消、保存。
3. 前端先做长度与字符校验:
3. 弹窗面板使用平台标准不透明面板底,不复用透明轻量面板。
4. 前端先做长度与字符校验:
- `2-20` 个字符。
- 允许中文、英文、数字、下划线。
- 不允许纯空白。
4. 保存调用 `PATCH /api/profile/me`,成功后即时回写 `AuthUiContext.user`
5. 保存调用 `PATCH /api/profile/me`,成功后即时回写 `AuthUiContext.user`
### 2.3 头像上传与裁剪
@@ -36,9 +37,10 @@
- MIME 类型仅允许 `image/jpeg``image/png``image/webp`
- 单文件不超过 `5MB`
3. 校验通过后读取为图片,打开裁剪弹窗。
4. 裁剪工具使用正方形裁剪框,支持拖动裁剪区域与缩放图片
5. 保存时前端输出 `256x256` 的 PNG data URL调用 `PATCH /api/profile/me` 保存为账号头像
6. 成功后资料卡头像立即展示新图
4. 裁剪弹窗面板使用平台标准不透明面板底,避免底层资料卡内容透出
5. 裁剪工具使用正方形裁剪框,支持拖动裁剪区域与缩放图片
6. 保存时前端输出 `256x256` 的 PNG data URL调用 `PATCH /api/profile/me` 保存为账号头像
7. 成功后资料卡头像立即展示新图。
## 3. 后端契约
@@ -85,6 +87,7 @@ SpacetimeDB 正式表 `user_account` 需要增加 `avatar_url: Option<String>`
2. “我的”页陶泥号复制按钮点击后显示 `已复制`
3. “我的”页不展示 `手机号``正常` 标签。
4. 昵称编辑成功后,资料卡与顶部账号入口同步新昵称。
5. 非法头像文件不会进入裁剪流程
6. 裁剪保存成功后,资料卡头像展示裁剪后的图片
7. 桌面右上角账号入口与“我的”资料卡共用 `avatarUrl`,有已保存头像展示头像图片,缺失时才回退到首字头像。
5. 昵称与头像裁剪弹窗面板不透明,不能露出底层页面内容
6. 非法头像文件不会进入裁剪流程
7. 裁剪保存成功后,资料卡头像展示裁剪后的图片。
8. 桌面右上角账号入口与“我的”资料卡共用 `avatarUrl`,有已保存头像时展示头像图片,缺失时才回退到首字头像。

View File

@@ -0,0 +1,22 @@
# 网页端首页模块内容同步移动端首页 2026-04-30
## 背景
平台首页移动端已经收口为 `推荐 / 今日游戏 / 游戏分类` 三个频道。网页端首页保留宽屏布局,但模块文案和数据语义仍残留 `趋势关注``最新发布``作品广场` 等旧入口口径,导致双端首页内容不一致。
## 落地规则
1. 网页端首页只调整模块内容和文案,不改变现有宽屏栅格、面板数量与卡片布局。
2. `推荐` 使用移动端推荐频道同源数据:精选作品优先,并与最新公开作品去重合并。
3. `趋势关注` 改为 `今日游戏`,数据只取今天首次发布的公开作品,不把今天更新的旧作品计入今日游戏。
4. `最新发布` 改为 `作品分类`,数据使用当前分类组内按综合指标排序后的作品。
5. 首页首屏和快捷区域不再展示 `作品广场` 文案。
6. 删除首页中的 `公开作品` 兜底模块;快捷区域只在存在最近作品或最近浏览时显示,不再用空模块占位。
## 验收标准
1. 网页端首页仍保持原有 hero、右侧列表、中部双栏与底部网格布局。
2. 网页端可见模块包含推荐、今日游戏、作品分类。
3. 网页端首页不再出现 `趋势关注``最新发布``作品广场`
4. 无最近作品和最近浏览时,网页端首页不再展示 `公开作品` 快捷模块。
5. 今日游戏与移动端 `今日游戏` 频道使用同一发布时间过滤规则。

View File

@@ -0,0 +1,98 @@
# 拼图失败续时与存档投影设计 2026-05-01
## 背景
拼图运行时已经有倒计时失败态、道具确认扣费、下一关推荐和个人存档页,但失败后的玩家选择与拼图作品存档投影还没有闭环:
1. 倒计时结束后只能返回,不能重新开始或付费继续。
2. 进入拼图作品后,存档页没有稳定出现一条可恢复的拼图游戏存档。
3. 每通过一关后,存档应该更新到下一关入口,而不是停留在旧关卡。
本轮只补齐拼图运行态与存档投影,不迁移旧 `server-node`,不新增平行存档页。
## 目标
1. 限定时间内未完成时弹出失败面板。
2. 失败面板提供两个选择:
- `重新开始`:重新开启当前拼图关卡,不扣陶泥币。
- `继续1分钟`:先弹出确认窗口,确认后消耗 `1` 陶泥币,并把当前失败关卡恢复为 `playing`,剩余时间固定为 `60000ms`
3. 进入拼图作品后立即写入 `profile_save_archive`,存档页显示拼图存档。
4. 每次进入下一关后更新同一条拼图存档,使存档恢复时指向最新可继续的关卡。
## 运行态规则
### 失败续时
`PuzzleRuntimePropKind` 增加 `extendTime`,沿用现有道具确认与扣费接口:
1. 前端只在 `runtimeStatus = failed` 时开放 `继续1分钟`
2. 点击后打开独立确认弹窗,文案只显示短标题和 `消耗 1 陶泥币`
3. 正式 run 继续走 `POST /api/runtime/puzzle/runs/:runId/props`
4. `api-server``extendTime` 映射为账单 `asset_kind = puzzle_prop_extend_time`
5. SpacetimeDB 侧只允许失败关卡续时;续时成功后:
- `status = playing`
- `remaining_ms = 60000`
- `elapsed_ms = None`
- `cleared_at_ms = None`
- 清空暂停与冻结生效点
- 调整 `paused_accumulated_ms`,保证从确认成功那一刻开始完整倒计时 `60`
本地调试 run 没有真实钱包,沿用本地道具兜底:仍弹确认窗,但不扣真实陶泥币。
### 重新开始
重新开始不复用旧失败棋盘,而是重新创建当前关卡的 run
1. 前端从当前 `currentLevel.profileId``currentLevel.levelId` 调用 `startPuzzleRun`
2. 新 run 的棋盘重新打乱、倒计时重置。
3. 如果当前关卡来自作品内部第 N 关,必须携带 `levelId`,避免重开误回作品第 1 关。
4. 旧失败 run 保留为历史运行记录,不在前端继续使用。
为支持第 3 点,`PuzzleRuntimeLevelSnapshot` 增加 `levelId: string | null`
## 存档投影规则
复用现有 `profile_save_archive` 表,不新增拼图专属存档表。拼图存档固定规则:
1. `world_key = puzzle:{entry_profile_id}`
2. `world_type = PUZZLE`
3. `profile_id = entry_profile_id`,保证同一个作品链只覆盖一条存档。
4. `world_name` 使用当前可恢复关卡名。
5. `subtitle` 使用 `第 N 关`
6. `summary_text` 使用可恢复关卡状态:
- playing`拼图进行中`
- failed`关卡失败`
- cleared`关卡已完成`
7. `cover_image_src` 使用可恢复关卡正式图。
8. `game_state_json` 保存最小拼图恢复载荷:
- `runtimeKind = "puzzle"`
- `runId`
- `entryProfileId`
- `currentProfileId`
- `currentLevelIndex`
- `currentLevelId`
- `status`
通关存档投影有一个额外规则:如果当前关卡已通关,并且 `refresh_next_level_handoff` 已经确认同作品存在下一关,则存档立即投影到同作品下一关入口,`status` 写为 `playing``subtitle / world_name / cover_image_src / currentLevelId` 都使用下一关。若当前作品没有下一关、只存在相似作品候选,存档保持当前已通关关卡,等待玩家在结算弹窗里选择相似作品,不能提前替玩家切换到某个候选作品。
## 写入时机
SpacetimeDB 拼图运行态每次持久化 run 时同步刷新存档:
1. `start_puzzle_run`:创建 run 后立即写入拼图存档。
2. `advance_puzzle_next_level`:进入下一关后更新同一条存档。
3. `use_puzzle_runtime_prop(extendTime)`:续时成功后更新状态。
4. `get_puzzle_run` 导致失败态落库时,也同步更新为失败存档。
5. `submit_puzzle_leaderboard_entry`:正式 run 提交成绩并把当前关标记为已通关时,先刷新下一关 handoff再按上面的通关投影规则同步存档。
前端在 `startPuzzleRun / usePuzzleProp / submitPuzzleLeaderboard / advancePuzzleLevel / getPuzzleRun` 成功后主动刷新存档列表,避免存档页停留在进入作品前或上一关的旧投影。
## 验收
1. 倒计时归零后失败弹窗有 `重新开始``继续1分钟`
2. 点击 `继续1分钟` 后先出现扣费确认,确认成功后失败弹窗关闭并恢复 `60` 秒倒计时。
3. 陶泥币余额不足时确认弹窗保留,并展示错误。
4. 点击 `重新开始` 后当前关卡重新打乱并重置倒计时。
5. 进入拼图作品后,存档页出现 `worldType = PUZZLE` 的拼图存档。
6. 通过一关后,只要后端确认同作品下一关存在,同一条存档立即更新到新关卡;没有同作品下一关时保留已完成关卡,等待玩家选择相似作品。
7. 定向前端测试、Rust 拼图模块测试与编码检查通过。

View File

@@ -2,25 +2,41 @@
## 背景
拼图创作入口不再使用 Agent 对话收集题材锚点。新流程让玩家填写两个字段:拼图标题、画面描述。画面描述支持上传参考图。玩家确认后直接进入草稿生成进度页,后续草稿生成、首图生成、正式图选择、结果页编辑和发布沿用现有后端编排。
拼图创作入口不再使用 Agent 对话收集题材锚点。新流程让玩家填写作品名称、作品描述、画面描述三类信息,其中画面描述只服务首关画面生成与关卡画面语义,不再作为作品详情页的作品描述。画面描述支持上传参考图。玩家确认后直接进入草稿生成进度页,后续草稿生成、首图生成、正式图选择、结果页编辑和发布沿用现有后端编排。
## 入口表单
1. 拼图标题为必填字段,保存到 `seedText`,同时作为 `levelName` 的优先来源。
2. 画面描述为必填字段,保存到 `pictureDescription`,同时作为 `summary` 和首图生成 prompt 的优先来源;支持多行文本,后端解析不得截断首行之后的内容。
3. 参考图为可选字段,保存到 `referenceImageSrc`。表单支持本地图片上传为 Data URL草稿首图生成时直接传入现有拼图图生图接口
4. 表单确认后前端先创建拼图 session再立即执行 `compile_puzzle_draft`,并传入 `promptText = pictureDescription``referenceImageSrc`
5. 表单提交 payload 需要在前端创作流程中暂存,生成进度页失败重试时必须继续携带同一份画面描述与参考图
6. 入口不再展示拼图 Agent 聊天气泡、快捷补齐或多锚点卡片;新建拼图时必须清空旧 session只有从当前生成进度页返回表单时保留本轮内容
### 2026-04-30 初始表单草稿保存补充
1. 玩家在创作页点击“拼图”入口时,前端必须立即创建一个新的拼图 Agent session并同步生成一条 `publicationStatus = draft` 的拼图作品卡;此时不触发 `compile_puzzle_draft`,不生成图片,不进入生成进度页
2. 新 session 的 `seedText` 允许为空SpacetimeDB 侧用空锚点和空表单草稿初始化,不得把默认题材文案写入玩家草稿字段
3. 初始表单输入自动保存到 session 的 `draft_json``puzzle_work_profile` 投影。保存字段只包含 `workTitle``workDescription``pictureDescription`、可推断标签和一个 `generationStatus = idle` 的默认关卡草稿设置阶段默认关卡名称必须为空不得写入“第一关”“第1关”或作品名称作为默认值。参考图只保存在当前前端会话内不落入 SpacetimeDB
4. 玩家在生成草稿前退出,再次从创作中心点击这条拼图草稿时,必须恢复到填表页,并回填之前自动保存的作品名称、作品描述和画面描述;只有执行 `compile_puzzle_draft` 且生成结果页草稿后,草稿入口才进入结果页
5. 表单自动保存走 `save_puzzle_form_draft` action不消耗陶泥币不生成图片不改变 `stage = collecting_anchors`;生成草稿按钮仍单独触发 `compile_puzzle_draft` 并进入进度页。
6. 点击拼图入口始终创建新草稿,不复用上一次未完成 session恢复旧草稿只通过“我的创作”中的草稿卡进入。
7. 若 Maincloud 仍运行旧 wasm缺少 `save_puzzle_form_draft` procedure前端提交生成或生成失败页重试时不得继续复用空 `seedText` 的表单 session必须用当前表单 payload 新建带真实 seed 的 session 再执行 `compile_puzzle_draft`
8. api-server 也要兼容旧 wasm`save_puzzle_form_draft` 缺失时,自动保存 action 降级返回当前 session`compile_puzzle_draft` 前置保存缺失且当前 session 为空 seed 时,创建一条带表单 seed 的替代 session 后继续编译,避免再次暴露 `No such procedure`
9. 正式修复仍是发布最新 SpacetimeDB wasm。当前 Maincloud `xushi-p4wfr` 的迁移操作员表为空,但旧库引导密钥来自旧 wasm本次临时生成的新引导密钥无法授权导出迁移需使用已有迁移操作员 token 或数据库 owner 重新授权后发布;禁止为绕过冲突直接清库,除非明确接受数据丢失。
1. 作品名称为必填字段,保存到 `workTitle`,兼容写入旧 `seedText`,同时作为作品级 `workTitle` 的真相源。
2. 作品描述为必填字段,保存到 `workDescription`,作为作品详情页、作品列表和发布资料中的 `summary` 真相源。
3. 画面描述为必填字段,保存到 `pictureDescription`,只作为首关画面生成 prompt、首关 `pictureDescription` 和关卡命名输入,不再覆盖作品描述。
4. 参考图为可选字段,保存到 `referenceImageSrc`。表单支持本地图片上传为 Data URL草稿首图生成时直接传入现有拼图图生图接口。
5. 表单确认后前端先创建拼图 session再立即执行 `compile_puzzle_draft`,并传入 `promptText = pictureDescription``referenceImageSrc`
6. 表单提交 payload 需要在前端创作流程中暂存,生成进度页失败重试时必须继续携带同一份作品名称、作品描述、画面描述与参考图。
7. 生成进度页的“当前拼图信息”必须优先读取这份表单 payload而不是读取 session 中旧 Agent 锚点或编译后的关卡名,避免用户确认后看到的标题、作品描述、画面描述发生漂移。
8. `compile_puzzle_draft` action 必须显式携带 `workTitle``workDescription``pictureDescription``promptText``referenceImageSrc``seedText` 只作为 SpacetimeDB 旧表结构兼容载体,不能成为前端生成页展示和失败重试的唯一来源。
9. 入口不再展示拼图 Agent 聊天气泡、快捷补齐或多锚点卡片;新建拼图时必须清空旧 session只有从当前生成进度页返回表单时保留本轮内容。
## 锚点映射
拼图模式锚点收口为个玩家输入源:
拼图模式锚点收口为个玩家输入源:
| 新字段 | 落地字段 | 说明 |
| --- | --- | --- |
| 拼图标题 | `themePromise.value``levelName``creatorIntent.themePromise` | 作为题材承诺与关卡名称的真相源 |
| 画面描述 | `visualSubject.value``summary`、首图 `promptText` | 作为画面主体与生图 prompt 的真相源 |
| 作品名称 | `themePromise.value``workTitle`、旧 `levelName` 兼容字段`creatorIntent.themePromise` | 作为作品名称与题材承诺真相源 |
| 作品描述 | `workDescription``summary` 兼容字段 | 作为作品详情页描述、列表描述和发布描述真相源 |
| 画面描述 | `visualSubject.value``levels[0].pictureDescription`、首图 `promptText` | 作为首关画面主体、首图生成 prompt 和首关关卡命名输入 |
兼容旧结构时仍保留 `visualMood``compositionHooks``tagsAndForbidden` 字段,但它们不再由 Agent 问答收集:
@@ -28,31 +44,66 @@
2. `compositionHooks` 固定标记为系统推断,值为“主体轮廓、色块分区、局部细节”。
3. `tagsAndForbidden` 根据拼图标题和画面描述生成 3 到 6 个题材标签;禁忌只保留通用图像约束,不写入 UI。
生成进度页的“当前拼图信息”只展示玩家输入锚点:拼图标题、画面描述。题材标签仅作为草稿结果页内容展示,不在进度页混入旧五锚点结构。
生成进度页的“当前拼图信息”只展示玩家输入锚点:作品名称、作品描述、画面描述。题材标签仅作为草稿结果页内容展示,不在进度页混入旧五锚点结构。
## 草稿数据结构
拼图草稿从单关卡字段升级为作品级信息与关卡列表并存:
1. `PuzzleResultDraft.workTitle`:作品名称,旧 `levelName` 只作为兼容字段同步为当前主关卡名称或作品名称。
2. `PuzzleResultDraft.workDescription`:作品描述,旧 `summary` 只作为兼容字段同步为作品描述。
3. `PuzzleResultDraft.themeTags`:作品标签,仍限制 3 到 6 个。
4. `PuzzleResultDraft.levels[]`:关卡列表。每个关卡包含 `levelId``levelName``pictureDescription``candidates``selectedCandidateId``coverImageSrc``coverAssetId``generationStatus`
5. 首次草稿生成时必须创建一个默认关卡,`levelId = puzzle-level-1``pictureDescription = 表单画面描述`,草稿设置阶段 `levelName` 为空;首图生成后可由后端根据画面描述和图片语义生成关卡名称并写入该关卡。
6. 关卡名称由后端基于画面描述和图片语义输入生成;无可用语义时按题材标签与序号兜底,禁止继续直接使用作品名称作为关卡名称。
7. 旧草稿或旧作品缺少 `levels` 时,读取层必须由旧 `levelName``summary``coverImageSrc``candidates` 补出一个兼容关卡,避免历史草稿无法打开。
## 后端编译
1. `CreatePuzzleAgentSessionRequest` 新增 `pictureDescription``referenceImageSrc`,但不改 SpacetimeDB 表结构。
2. api-server 创建 session 时把标题和画面描述合成 `seedText` 传入 SpacetimeDBSpacetimeDB reducer 只做确定性锚点生成,不接触图片或外部服务。
3. `compile_puzzle_draft_with_initial_cover` 新增首图 prompt 和参考图参数。若前端传入画面描述,则首图生成直接使用这段文本;若传入参考图,则走现有 DashScope 图生图链路。
4. 图片生成仍在 api-server 内完成,遵守 SpacetimeDB reducer 不做网络 I/O 的约束
1. `CreatePuzzleAgentSessionRequest` 新增 `workTitle``workDescription``pictureDescription``referenceImageSrc`,但不改 SpacetimeDB 表结构。
2. api-server 创建 session 时把作品名称、作品描述和画面描述合成 `seedText` 传入 SpacetimeDBSpacetimeDB reducer 只做确定性锚点生成,不接触图片或外部服务。
3. `compile_puzzle_draft_with_initial_cover` 新增首图 prompt 和参考图参数。若前端传入画面描述,则首图生成直接使用这段文本;若传入参考图,则走现有 DashScope 图生图链路;生成结果写入默认第一关
4. 首图文生图 prompt 由 api-server 拼接固定拼图约束后统一压缩到 `500` 字符以内,避免玩家长画面描述触发 DashScope 参数非法;进度页和结果页仍展示玩家原始画面描述,不展示压缩后的内部 prompt
5. 图片生成仍在 api-server 内完成,遵守 SpacetimeDB reducer 不做网络 I/O 的约束。
6. 参考图以 Data URL 进入 `POST /api/runtime/puzzle/agent/sessions``POST /api/runtime/puzzle/agent/sessions/{sessionId}/actions`,这两条路由必须单独放宽 JSON body 上限;不要放大全局默认 body limit。
7. 前端仍应优先压缩参考图;后端 body 上限只用于容纳合理尺寸的单张参考图,超大原图不应直接落入 SpacetimeDB 或作为作品字段持久化。
8. 作品更新接口 `PUT /api/runtime/puzzle/works/{profileId}` 必须支持作品信息和关卡列表一起写入,前端自动保存不得只写旧单关字段。
9. `StartPuzzleRunRequest` 新增可选 `levelId`。详情页或草稿结果页单独体验某关时传入目标关卡,后端从作品/草稿的 `levels` 中选取该关卡生成运行态。
10. `ExecutePuzzleAgentActionRequest` 必须保留 `pictureDescription` 字段。表单直达生成时,`compile_puzzle_draft` 优先用 `pictureDescription` 作为首图 prompt再回退到旧 `promptText`;避免生成页展示的是玩家画面描述,但后端实际用作品名称或旧摘要出图。
11. `compile_puzzle_draft` 中的图片上游失败不得映射成 `400 BAD_REQUEST`。DashScope 返回 `InvalidParameter` 或任务失败时api-server 统一按 `502 UPSTREAM_ERROR` 暴露,并在 `details.message` 中保留“拼图图片生成失败:...”的业务原因,避免生成页只显示“请求参数不合法”。
12. `compile_puzzle_draft` 前置陶泥币预扣失败不得映射成 `400 BAD_REQUEST`。余额不足返回 `409 CONFLICT`SpacetimeDB procedure 不可用、绑定不匹配、钱包服务异常等统一按 `502 UPSTREAM_ERROR` 暴露,并在 `details.message` 中保留真实钱包错误。
13. 生成拼图作品草稿动作涉及的表单 seed prompt 与首图 prompt 来源选择统一收口在 `server-rs/crates/api-server/src/prompt/puzzle/draft.rs``puzzle.rs` 只负责调用 SpacetimeDB、计费、图片服务和持久化不再直接拼草稿 prompt 文本。
## 结果页
拼图草稿结果页不再区分 Tab合并为一个可滚动列表页内容顺序固定为
拼图草稿结果页分为两个 Tab
1. 关卡名称
2. 画面预览
3. 画面描述。
4. 重新生成画面按钮。
5. 题材标签。
1. 拼图关卡列表:默认展示草稿生成出的第一关。列表项参考 RPG 草稿卡片样式,显示画面图、关卡名称和轻量状态。支持新增关卡、删除关卡。点击列表项进入独立关卡详情页,不在列表项下方展开。关卡详情页可编辑关卡名称、画面描述、生成或重新生成画面,并在已有正式图后支持关卡测试
2. 作品信息:展示并编辑作品名称、作品描述、作品标签
画面描述区域不再展示候选图实际 prompt 或“请生成一张适合……”之类内部提示词模块。参考图入口保留在画面描述编辑区域内,便于重新生成时继续带入。结果页编辑画面描述时必须同步更新 `summary`,确保自动保存、作品测试、发布和重新生成画面使用同一份描述。
### 2026-04-30 关卡列表卡片交互补充
1. 关卡列表卡片的删除按钮与关卡名称放在同一信息行,按钮固定在卡片右下角;不得再单独占用一整条底部分隔栏。
2. 关卡图片、序号与名称区域仍作为打开关卡详情的主点击区;删除按钮只触发删除,不进入详情。
### 2026-04-30 关卡详情面板交互补充
1. 关卡详情面板内容区按移动端优先的单列顺序展示:`关卡名称 -> 画面图 -> 画面描述`。其中画面图只在该关卡已有正式图时出现;新建关卡或画面为空的关卡不展示空图占位模块。
2. 画面生成主按钮固定吸底,始终位于关卡详情面板底部操作区。若当前关卡还没有正式图,按钮文案为“生成画面”;已有正式图后,按钮文案为“重新生成画面”。
3. 关卡已有正式图后,底部操作区在生成按钮上方新增单独的关卡测试入口,原“体验该关”文案收口为“关卡测试”。无正式图时不展示该入口。
4. 底部吸底操作区只承载动作按钮,不默认写玩法说明或规则解释,避免压缩移动端编辑空间。
5. 关卡详情面板内触发生成画面时,前端必须把当前编辑态完整 `levelsJson``generate_puzzle_images` action 一起提交。这样新建关卡在自动保存完成前立即生成,也能由后端写回目标关卡。
6. api-server 处理 `generate_puzzle_images` 时,若 action 带有 `levelsJson`,必须用这份关卡快照覆盖本次生成的草稿关卡视图后再定位 `levelId`。若请求明确传入 `levelId` 但关卡列表中不存在该关卡,必须返回错误,不得静默回退第一关。
7. 历史拼图素材入口只在已有正式图的 `画面图` 区域右下角展示,不再放在 `画面描述` 输入区;本地上传参考图入口仍保留在画面描述输入区右下角。
8. 历史拼图素材列表必须由服务端按当前登录账号过滤,只返回 `asset_kind = puzzle_cover_image``owner_user_id = 当前账号` 的资产;不得依赖前端过滤,也不得展示其他账号素材。
画面描述区域不再展示候选图实际 prompt 或“请生成一张适合……”之类内部提示词模块。参考图入口保留在画面描述编辑区域内,便于重新生成时继续带入。结果页编辑关卡画面描述时只同步该关卡 `pictureDescription`;作品描述只在作品信息 Tab 编辑,作品详情页不得再回退使用画面描述。
## 验收
1. 从拼图创作入口只能看到标题、画面描述和参考图上传,不出现 Agent 聊天输入、补齐设定、锚点问答。
1. 从拼图创作入口只能看到作品名称、作品描述、画面描述和参考图上传,不出现 Agent 聊天输入、补齐设定、锚点问答。
2. 点击确认后进入拼图草稿生成进度页,并自动完成草稿编译、首图生成、正式图选择。
3. 首图生成请求使用玩家画面描述作为 prompt上传参考图时走图生图。
4. 结果页为单列表,顺序符合上文要求,不展示 Tab 和内部实际 prompt
5. 发布、作品测试、自动保存标题、画面描述和标签仍可用。
3. 首图生成请求使用玩家画面描述作为 prompt上传参考图时走图生图;作品详情页展示玩家作品描述
4. 结果页包含“拼图关卡”和“作品信息”两个 Tab关卡列表默认至少一关支持新增、删除和进入关卡详情
5. 关卡详情页支持生成或重新生成画面;已有正式图后显示吸底“关卡测试”入口。
6. 发布、作品测试、自动保存作品名称、作品描述、作品标签和关卡列表仍可用。

View File

@@ -16,8 +16,12 @@
1. 拼图生成图固定使用 `1024*1024`
2. 文生图和参考图生图共用同一个尺寸常量,禁止一条链路仍生成竖屏或横版图。
3. 拼图图片提示词明确写入 `1:1 正方形画布`,继续保留 `3x3 4x4 拼图切块`、主体清晰、层次明确、无文字水印等约束。
4. 图片生成仍由 `api-server` 执行。SpacetimeDB reducer 不做网络 I/O
3. 拼图图片提示词明确写入 `1:1 正方形画布`,继续保留适配 `3x3 / 4x4 / 5x5 / 6x6 / 7x7` 拼图切块、主体清晰、层次明确、无文字水印等约束。
4. 文生图正向 prompt 必须由后端压缩到 `500` 字符以内,优先保留玩家画面描述开头与固定拼图约束,避免 DashScope 旧 text2image 协议把超长 prompt 判为“请求参数不合法”
5. DashScope 上游失败时api-server 必须在错误 details 中保留业务 message、`upstreamStatus` 和截断后的 `rawExcerpt`,日志也要记录同样的摘要,避免生成进度页只能看到通用 HTTP 文案。
6. 图片生成仍由 `api-server` 执行。SpacetimeDB reducer 不做网络 I/O。
7. 拼图文生图请求体按 DashScope Wan text2image 协议收口:`input``prompt` 与非空 `negative_prompt``parameters``n``size``prompt_extend``watermark`。不要在 `input``parameters` 里重复写入反向提示词,否则上游容易返回参数非法。
8. 陶泥币预扣失败属于钱包或 SpacetimeDB 服务链路错误,不得映射成 `400 BAD_REQUEST`。除余额不足返回 `409 CONFLICT` 外,其余预扣异常统一按上游/服务错误暴露,避免生成页误提示“请求参数不合法”。
### 2. 前端规则裁决
@@ -33,10 +37,19 @@
3. 单格不设置固定最小高度,避免移动端被单格撑破。
4. 顶部 HUD 与底部道具仍保留安全区,不能遮挡棋盘可操作区域。
### 4. 拼块视觉圆角
1. 基础单块和合并块都必须使用圆角,不能只让合并后的外轮廓有圆角。
2. 基础单块的图片层必须跟随单块容器裁剪,避免图片直角从圆角边框里露出。
3. 合并块继续按实际拼块外轮廓描边,内部相邻边不额外显示边框。
## 验收
1. 点击拼图草稿生成或重新生成画面时,后端请求 DashScope 的 `size``1024*1024`
2. 图片提示词包含 `1:1 正方形拼图关卡`
3. 正式拼图 run 中拖动拼块后,前端立即更新棋盘、合并块和通关状态,不再等待 `/drag`
4. 移动端运行时棋盘为正方形,并尽量贴近屏幕两侧边缘
5. 下一关、道具、排行榜仍走现有后端链路,不把外部 I/O 或扣费逻辑塞回前端。
3. 图片提示词长度不超过 `500` 字符,超长画面描述会被截断,但适配 `3x3 / 4x4 / 5x5 / 6x6 / 7x7` 拼图切块、`避免文字、水印、边框和 UI 元素` 等玩法约束不能丢
4. DashScope 返回参数错误、任务失败或非 2xx 时,前端错误优先展示后端 details.message后端日志能看到 `upstreamStatus``rawExcerpt`
5. 正式拼图 run 中拖动拼块后,前端立即更新棋盘、合并块和通关状态,不再等待 `/drag`
6. 移动端运行时棋盘为正方形,并尽量贴近屏幕两侧边缘。
7. 基础单块和合并块都能看到圆角,且基础单块图片不会溢出圆角裁剪。
8. 下一关、道具、排行榜仍走现有后端链路,不把外部 I/O 或扣费逻辑塞回前端。

View File

@@ -6,7 +6,7 @@
## 本轮落地边界
1. 拼图图片提示词统一放到 `server-rs/crates/api-server/src/prompt/puzzle_image.rs`
1. 拼图图片提示词统一放到 `server-rs/crates/api-server/src/prompt/puzzle/image.rs`
2. `puzzle.rs` 只负责读取提示词构建结果,并继续处理 DashScope、OSS、SpacetimeDB 写回。
3. 提示词模块只暴露:
- `build_puzzle_image_prompt(level_name, prompt)`
@@ -18,7 +18,7 @@
1. 不把图片生成逻辑下沉到 SpacetimeDB reducer外部 I/O 必须留在 `api-server`
2. 不改候选图 JSON 持久化结构,仍使用 `module-puzzle::PuzzleGeneratedImageCandidate` 对应的 snake_case 字段。
3. 不改前端 UI 文案和交互;本轮只拆后端提示词脚本。
4. 后续若调整拼图图片风格、尺寸、禁止元素或切块可读性要求,优先修改 `prompt/puzzle_image.rs`,再按需补测试。
4. 后续若调整拼图图片风格、尺寸、禁止元素或切块可读性要求,优先修改 `prompt/puzzle/image.rs`,再按需补测试。
## 验收

View File

@@ -0,0 +1,37 @@
# 拼图排行榜前端关卡提交与 RPG 敬请期待 2026-04-30
## 背景
拼图运行态的交换、拖动、合并、拆分与本关通关判定已经交给前端即时计算。第二关通过后端 `local-next-level` 兼容接口生成下一关时,前端当前关卡已经推进到新作品,但 SpacetimeDB 中的旧 run 快照可能仍停留在上一关。
因此第二关通关后提交排行榜,如果后端继续要求 `run.currentLevel.profileId == payload.profileId`,会误报:
```text
提交成绩的拼图作品与当前关卡不匹配
```
本轮同时把 RPG 创作入口设置为“敬请期待”,只调整平台入口展示与分流防线,不删除 RPG 既有代码、历史作品详情或运行时兼容能力。
## 落地口径
### 1. 拼图排行榜
1. 排行榜提交仍必须校验 run 归属,避免跨用户提交。
2. 排行榜写入以本次提交的 `profileId``gridSize``elapsedMs` 为准。
3. 当 SpacetimeDB 旧 run 的当前关卡与提交关卡一致时,后端可以把真实榜单合并回服务端关卡快照。
4. 当旧 run 当前关卡与提交关卡不一致时,不再报错;后端只写入真实榜单,并把榜单放到 run 顶层 `leaderboardEntries` 返回给前端。
5. 前端继续用当前本地 run 合并后端返回的 `leaderboardEntries`,不能用服务端旧棋盘覆盖本地第二关棋盘。
### 2. RPG 敬请期待
1. 平台创作类型元数据中,`rpg` 改为 `locked: true`
2. RPG 创作卡片 badge 与副标题统一显示 `敬请期待`
3. 创作类型弹窗与创作首页卡带复用同一份元数据,因此入口自动禁用。
4. 分流函数中继续防御 `rpg` 类型直达触发,避免旧测试或隐藏入口绕过禁用态。
## 验收
1. 第二关拼图通关后提交排行榜,不再出现“提交成绩的拼图作品与当前关卡不匹配”。
2. 排行榜返回后,前端仍保留当前第二关棋盘与通关状态。
3. 创作页 RPG 卡片显示 `敬请期待` 且不可点击。
4. 拼图、大鱼和其它已锁定玩法的显示状态不被本轮改动影响。

View File

@@ -0,0 +1,75 @@
# 拼图下一关与相似作品接续设计 2026-04-30
## 背景
拼图通关结算弹窗已有“下一关”按钮,但当前按钮依赖 `recommendedNextProfileId`。这会带来两个问题:
1. 当前作品还有未玩的内部关卡时,按钮可能因为没有跨作品推荐而被禁用。
2. 当前作品全部关卡玩完后,只返回单个推荐作品,无法满足“三个相似作品由用户选择”的交互。
本轮只修复拼图运行态接续链路,不迁移旧 `server-node`,不在前端计算正式相似度。
## 目标
1. 通关后默认点击“下一关”,优先加载当前拼图作品的下一关。
2. 当前作品没有下一关时,后端按标签语义相似度选出相似度最高的三个已发布作品。
3. 用户在通关弹窗里点击候选作品后,进入该作品并从第 `1` 关重新开始。
4. 移动端优先,候选卡片要紧凑,不写玩法说明类文案。
## 数据契约
`PuzzleRunSnapshot` 增加:
1. `nextLevelMode: "sameWork" | "similarWorks" | "none"`
2. `nextLevelProfileId: string | null`:同作品下一关或跨作品推荐的默认目标。
3. `nextLevelId: string | null`:同作品下一关的 `levelId`;跨作品时为 `null`
4. `recommendedNextWorks: PuzzleRecommendedNextWork[]`:跨作品候选,最多 3 个。
`PuzzleRecommendedNextWork` 字段:
1. `profileId`
2. `levelName`
3. `authorDisplayName`
4. `themeTags`
5. `coverImageSrc`
6. `similarityScore`
保留 `recommendedNextProfileId` 作为旧字段兼容,但前端新逻辑不再只依赖它。
## 后端规则
1. SpacetimeDB 侧在 `start / get / swap / drag / leaderboard / advance` 后刷新下一关状态。
2. 当前作品存在未玩的下一张关卡图时:
- `nextLevelMode = "sameWork"`
- `nextLevelProfileId = 当前作品 profileId`
- `nextLevelId = 下一关 levelId`
- `recommendedNextWorks = []`
3. 当前作品没有内部下一关时:
- 使用拼图现有 `recommendation_score = tagSimilarity * 0.7 + sameAuthor * 0.3`
- `tagSimilarity` 优先复用 RPG/build 标签语义亲和度模型;两侧标签未命中该语义模型时,回退到规范化标签 Jaccard
- 排除当前 run 已玩过的作品;若池子为空,允许回收但不连续重复上一关作品
- 返回最高的 3 个候选
4. `advance_puzzle_next_level`
- `nextLevelMode = sameWork` 时加载当前作品的下一关,并继续当前 run。
- `nextLevelMode = similarWorks` 时默认加载候选第一项,并把 `entryProfileId / clearedLevelCount / currentLevelIndex` 重置到目标作品第 `1` 关。
5. `local-next-level` 兼容接口同样优先找同作品下一关;没有时返回 `similarWorks` 候选并保持当前通关 run只有候选池为空时才进入旧草稿兜底。
## 前端规则
1. 结算弹窗:
- `sameWork`:主按钮显示“下一关”,直接触发默认推进。
- `similarWorks`:展示最多 3 个作品候选卡;用户点击卡片进入候选作品。
- `none`:禁用下一关入口。
2. 底部通关后入口:
- `sameWork` 保留“下一关”。
- `similarWorks` 显示“换个作品”,点击后打开结算弹窗供选择。
3. 所有正式相似度计算只信任后端返回,不在 UI 里重新算。
4. 本地/草稿 run 通关提交本地排行榜后,会异步调用 `local-next-level` 刷新 handoff若拿到 `similarWorks`,只合并候选字段,不把已通关弹窗改成新的 playing 关卡。
## 验收
1. 当前作品有下一关时,点击“下一关”进入当前作品下一关。
2. 当前作品没有下一关时,通关弹窗显示最多 3 个相似作品。
3. 点击相似作品后进入该作品第 `1`HUD 关卡序号、切割规格和倒计时都按第 `1` 关显示。
4.`recommendedNextProfileId` 为空时,只要 `nextLevelMode = sameWork`,按钮仍可用。
5. 拼图 runtime 单测、Rust 拼图模块测试和编码检查通过。

View File

@@ -41,6 +41,15 @@
2. 单块交换、拖到合并块后拆分、合并块整体重排,继续沿用当前本地运行态规则。
3. 不新增前端本地裁决,不把玩法真相从既有运行态实现中分叉出去。
### 3.4 点击触觉反馈
移动端用户每次按下可交互拼图片时,需要触发一次短促手机震动:
1. 震动触发点放在 `pointerdown`,让点击选中、按住准备拖动与拖起都有一致手感。
2. 同一次按下会话只触发一次震动,后续连续移动不重复震动。
3. 使用浏览器标准 `navigator.vibrate([12])`,不支持震动能力的设备静默跳过。
4. 该反馈只属于前端表现层,不影响拖拽落点、交换、合并、拆分与通关判定。
## 4. 验收标准
1. 单块拖动时拼块视觉位置应紧跟手指或鼠标,不再出现明显缓动拖尾。
@@ -48,3 +57,4 @@
3. 点击选中与拖动阈值判定仍保持原语义,不因为优化误触发交换。
4. 运行时现有结算弹窗、排行榜和下一关入口不受影响。
5. 定向测试覆盖拖动提交坐标的行为,并运行编码检查确保中文文档未被写坏。
6. 移动端点击拼图片时立即触发一次短震,同一次按下后的连续移动不重复触发。

View File

@@ -24,12 +24,28 @@
## 难度限时
第一版按网格规模定义限时
拼图关卡切割规格和倒计时由统一关卡配置函数解析,不再按网格规模单独推导时间
1. `3x3``180000ms`
2. `4x4``300000ms`
| 关卡 | 切割规格 | 限时 |
| -------- | -------- | ---------- |
| 第 1 关 | `3x3` | `300000ms` |
| 第 2 关 | `4x4` | `300000ms` |
| 第 3 关 | `5x5` | `300000ms` |
| 第 4 关 | `5x5` | `210000ms` |
| 第 5 关 | `5x5` | `210000ms` |
| 第 6 关 | `6x6` | `240000ms` |
| 第 7 关 | `5x5` | `210000ms` |
| 第 8 关 | `7x7` | `270000ms` |
| 第 9 关 | `5x5` | `240000ms` |
| 第 10 关 | `7x7` | `270000ms` |
后续若扩展更多难度,只能通过同一个难度解析函数扩展,不允许在 UI 里写死另一套时间
第 11 关开始,每 6 关循环复用第 5 关到第 10 关的配置,即 `5x5/210000ms``6x6/240000ms``5x5/210000ms``7x7/270000ms``5x5/240000ms``7x7/270000ms`
同作品下一关必须使用同一个运行时关卡序号继续推进。跨作品相似推荐代表进入新作品,必须从目标作品第 `1` 关重新开始。
失败状态点击“重新开始”时,不进入作品第 `1` 关,而是重开当前失败关卡:前端需要传当前关 `levelId`,服务端按该 `levelId` 在作品内的位置恢复 `currentLevelIndex`、切割规格和倒计时。
后续若扩展更多难度,只能通过同一个关卡配置解析函数扩展,不允许在 UI 里写死另一套时间。
## 计时规则

View File

@@ -0,0 +1,60 @@
# 拼图作品积分激励链路设计
更新时间:`2026-05-01`
## 1. 目标
1. 拼图草稿页“新增关卡”按钮下方显示一行小字:“获得更多积分激励”。
2. 创作页的已发布拼图作品卡展示当前作品的积分激励总数、待领取积分数和领取按钮。
3. 用户在他人已发布拼图作品中消耗陶泥币时,作品作者获得消耗陶泥币数量的一半作为积分激励。
4. 作者领取时只能领取整数个陶泥币,待领取值向下取整;未满 1 个陶泥币的半数余额继续保留。
## 2. 数据模型
拼图作品激励归属到 `puzzle_work_profile`
1. `point_incentive_total_half_points: u64`
- 记录该作品累计获得的激励,单位为“半个陶泥币”。
- 每消耗 `N` 个陶泥币,增加 `N` 个 half points当前拼图道具每次消耗 1 个陶泥币,因此每次为作者增加 0.5。
2. `point_incentive_claimed_points: u64`
- 记录作者已领取的整数陶泥币数量。
3. 前端展示:
- 激励总数 = `pointIncentiveTotalHalfPoints / 2`,允许展示一位小数。
- 待领取积分 = `floor(pointIncentiveTotalHalfPoints / 2) - pointIncentiveClaimedPoints`
- 领取按钮仅在待领取积分大于 0 时可用。
## 3. 后端事务
1. 拼图运行道具扣费成功、道具效果成功落库后,后端根据 run 的当前作品 `profile_id` 查找作者。
2. 若使用者不是作品作者,则给该作品累积 `consumed_points` 个 half points。
3. 若使用者是作者本人,视为作者自测,不产生积分激励。
4. 若后续业务操作失败并触发扣费退款,不写入激励。
5. 领取接口:
- 只允许作品作者领取。
- 计算可领取整数 `claimable = total_half_points / 2 - claimed_points`
- `claimable <= 0` 时拒绝领取。
- 同一事务内更新作品 `claimed_points += claimable`,并向作者钱包增加 `claimable` 陶泥币,钱包流水来源使用 `puzzle_author_incentive_claim`
## 4. API 与前端
1. `PuzzleWorkSummary` / `PuzzleWorkProfile` 增加:
- `pointIncentiveTotalHalfPoints`
- `pointIncentiveClaimedPoints`
- `pointIncentiveTotalPoints`
- `pointIncentiveClaimablePoints`
2. 新增领取接口:
- `POST /api/runtime/puzzle/works/{profile_id}/point-incentive/claim`
- 返回更新后的 `PuzzleWorkProfile`
3. 创作页仅对已发布拼图作品显示积分激励块RPG、大鱼和草稿卡不显示。
4. 领取成功后刷新对应拼图作品列表状态,按钮立即禁用或显示新的待领取数,并同步刷新个人钱包看板。
5. `spacetime-client` 映射层继续兼容历史拼图运行快照:旧 `run_json` 若缺少 `started_at_ms`API 记录回填为非 0 值,避免前端计时器拿到无效开始时间。
## 5. 验收点
1. 拼图草稿页新增关卡按钮下方显示“获得更多积分激励”。
2. 已发布拼图作品卡展示“积分激励总数”和“待领取”两个数值。
3. 待领取积分为 0 时领取按钮禁用。
4. 非作者游玩他人拼图并使用付费道具后,该作品累计 half points 增加。
5. 作者领取后钱包增加向下取整后的整数陶泥币,作品待领取数归零或保留不足 1 的小数余额。
6. 领取成功后顶部/我的页钱包余额随个人看板刷新。
7. 修改后运行编码检查、SpacetimeDB 绑定生成、Rust 检查和必要前端测试。

View File

@@ -5,6 +5,8 @@
## 文档列表
- [SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md](./SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md):冻结 SpacetimeDB 表结构变更约束、自动迁移可接受范围、冲突后的系统行为,以及保留旧数据的增量迁移流程;凡涉及 `spacetime publish`、表字段调整或 `migration.rs` 对齐时优先参考。
- [SPACETIMEDB_LOCAL_REPLICA_IDENTITY_MISMATCH_FIX_2026-04-30.md](./SPACETIMEDB_LOCAL_REPLICA_IDENTITY_MISMATCH_FIX_2026-04-30.md):记录本地 standalone 启动时报 `mismatched database identity` 的 root-dir/replica 数据残留根因、备份重建步骤和脚本诊断口径。
- [LLM_MODEL_ROUTING_RPG_AND_CREATION_2026-04-30.md](./LLM_MODEL_ROUTING_RPG_AND_CREATION_2026-04-30.md):冻结 RPG 运行时剧情推理使用 `doubao-seed-character-251128``/chat/completions`,以及所有模板创作大模型推理使用 `deepseek-v3-2-251201``/responses`
- [PROFILE_INVITE_CODE_REGISTRATION_AND_ADMIN_2026-04-30.md](./PROFILE_INVITE_CODE_REGISTRATION_AND_ADMIN_2026-04-30.md):冻结邀请码从“我的 Tab 填写”迁到注册环节的前后端边界、`profile_invite_code.metadata_json` 表结构扩展、管理员邀请码虚拟主体和奖励规则。
- [MATCH3D_CREATION_AND_RUNTIME_MINIMAL_IMPLEMENTATION_2026-04-30.md](./MATCH3D_CREATION_AND_RUNTIME_MINIMAL_IMPLEMENTATION_2026-04-30.md):冻结抓大鹅 Match3D 首版 demo 的独立玩法域、表与 procedure、HTTP facade、前端即时反馈/后端权威确认协议,以及可并行开发包。
- [MATCH3D_DOMAIN_AND_CONTRACTS_STAGE1_2026-04-30.md](./MATCH3D_DOMAIN_AND_CONTRACTS_STAGE1_2026-04-30.md):冻结抓大鹅 Match3D B1+B2 的纯领域规则 crate、Rust/TypeScript shared contracts以及 Stage1 不触碰 SpacetimeDB 表和 api-server 的边界。
@@ -23,7 +25,14 @@
- [BIG_FISH_PROMPT_MODULE_EXTRACTION_2026-04-28.md](./BIG_FISH_PROMPT_MODULE_EXTRACTION_2026-04-28.md):记录大鱼吃小鱼草稿生成、生图、动作三类提示词从业务脚本中抽离到独立 `prompt/big_fish.rs` 模块的边界与职责划分。
- [BIG_FISH_MAIN_IMAGE_TRANSPARENT_BACKGROUND_ALIGNMENT_2026-04-28.md](./BIG_FISH_MAIN_IMAGE_TRANSPARENT_BACKGROUND_ALIGNMENT_2026-04-28.md):记录大鱼吃小鱼等级主图与动作关键帧正式图在 Rust 后端复用 RPG 角色主图透明背景 alpha 后处理的对齐口径,并明确场地背景不走该处理。
- [PUZZLE_IMAGE_AND_FRONTEND_RULES_ALIGNMENT_2026-04-29.md](./PUZZLE_IMAGE_AND_FRONTEND_RULES_ALIGNMENT_2026-04-29.md):记录拼图生成图片回到 1:1运行时拖动、交换、合并与拆分由前端即时裁决以及移动端棋盘贴近屏幕边缘的落地边界。
- [PUZZLE_FORM_CREATION_FLOW_2026-04-29.md](./PUZZLE_FORM_CREATION_FLOW_2026-04-29.md):冻结拼图填表式创作入口、初始表单自动保存草稿、生成前退出后的表单恢复,以及草稿编译/首图生成的前后端边界。
- [PUZZLE_LEADERBOARD_FRONTEND_LEVEL_AND_RPG_COMING_SOON_2026-04-30.md](./PUZZLE_LEADERBOARD_FRONTEND_LEVEL_AND_RPG_COMING_SOON_2026-04-30.md):记录拼图第二关排行榜提交以前端当前关卡为准、不被 SpacetimeDB 旧 run 快照误杀,以及 RPG 创作入口改为敬请期待的落地边界。
- [PUZZLE_NEXT_LEVEL_AND_SIMILAR_WORK_HANDOFF_2026-04-30.md](./PUZZLE_NEXT_LEVEL_AND_SIMILAR_WORK_HANDOFF_2026-04-30.md):记录拼图通关后优先同作品下一关、无下一关时按 RPG/build 标签语义相似度返回三个候选作品,并在跨作品时只切换到候选作品第 1 张图、运行时关卡序号继续累进的落地规则。
- [PUZZLE_FAILURE_EXTENSION_AND_SAVE_ARCHIVE_2026-05-01.md](./PUZZLE_FAILURE_EXTENSION_AND_SAVE_ARCHIVE_2026-05-01.md):记录拼图失败后重新开始/付费续时,以及进入作品与过关后同步存档页投影的落地规则。
- [PUZZLE_RUNTIME_TIMER_AND_PROPS_2026-04-29.md](./PUZZLE_RUNTIME_TIMER_AND_PROPS_2026-04-29.md)记录拼图关卡切割、倒计时、失败态和三个运行时道具的统一规则2026-05-01 起关卡切割与限时按第 1-10 关配置,并从第 11 关按第 5-10 关六关循环。
- [RPG_SCENE_ACT_PREVIEW_BOOTSTRAP_FIX_2026-04-30.md](./RPG_SCENE_ACT_PREVIEW_BOOTSTRAP_FIX_2026-04-30.md):记录编辑器幕预览卡在“正在载入这一幕”时的启动态根因,收口预览本地运行态装配与禁持久化首段 story 注入。
- [PUZZLE_RESULT_AUTOSAVE_AND_TAG_GATE_FIX_2026-04-28.md](./PUZZLE_RESULT_AUTOSAVE_AND_TAG_GATE_FIX_2026-04-28.md):记录拼图结果页名称与标签编辑自动保存、发布门槛统一到 `3~6` 标签,以及前端发布校验不再被旧 session blocker 卡死的修复口径。
- [WORK_AUTHOR_ID_RESOLUTION_2026-04-30.md](./WORK_AUTHOR_ID_RESOLUTION_2026-04-30.md):记录作品作者以 `owner_user_id` 为真相源API 按用户 ID 解析最新昵称与公开用户码,历史 `author_display_name` 仅作为兼容回退。
- [SPACETIMEDB_START_SH_EARLY_EXIT_DIAGNOSTICS_2026-04-27.md](./SPACETIMEDB_START_SH_EARLY_EXIT_DIAGNOSTICS_2026-04-27.md):记录发布包 `start.sh` 只输出“SpacetimeDB 进程在就绪前退出”时的诊断补强,启动失败或超时时自动回显 `logs/spacetimedb.log``server ping`、端口监听和 root-dir 相关进程。
- [RPG_AND_AGENT_CHAT_TRUE_SSE_STREAMING_2026-04-26.md](./RPG_AND_AGENT_CHAT_TRUE_SSE_STREAMING_2026-04-26.md):记录 RPG 运行时 NPC 聊天、RPG/自定义世界 Agent 与大鱼 Agent 从“拼完整 SSE 字符串后一次性返回”改为 `mpsc + Sse<Event>` 真流式输出的后端落地口径。
- [SPACETIMEDB_START_SH_ROOT_OWNER_FALSE_POSITIVE_FIX_2026-04-27.md](./SPACETIMEDB_START_SH_ROOT_OWNER_FALSE_POSITIVE_FIX_2026-04-27.md):记录发布包 `start.sh` root-dir 占用检测把 `grep -F .../.spacetimedb` 误判为 SpacetimeDB 实例的根因、脚本修复和现场处理方式。

View File

@@ -15,11 +15,13 @@
## 体验规则
- 等待态继续复用 `RouteLoadingScreen`,只显示简短加载文案,不在 UI 中追加规则说明。
- `RouteLoadingScreen` 必须读取 `tavernrealms.settings.v1` 中的 `platformTheme`,并复用 `platform-theme--light / platform-theme--dark``platform-body-fill / platform-text-*` token不能硬编码独立深色背景。
- 页面主体隐藏时使用 `visibility: hidden`,不能用 `display: none`,否则浏览器可能不触发布局与图片加载。
- 图片加载失败不直接改写业务 UI后续仍由原页面的兜底图、占位图或错误态处理。
## 涉及文件
- `src/routing/RouteImageReadyGate.tsx`
- `src/routing/RouteLoadingScreen.tsx`
- `src/routing/RouteImageReadyGate.test.ts`
- `src/main.tsx`

View File

@@ -51,7 +51,10 @@ server-rs/crates/api-server/src/prompt/
├─ big_fish.rs
├─ character_animation.rs
├─ character_visual.rs
├─ puzzle_image.rs
├─ puzzle/
│ ├─ agent_chat.rs
│ ├─ image.rs
│ └─ mod.rs
├─ scene_background.rs
├─ mod.rs
└─ rpg/

View File

@@ -0,0 +1,67 @@
# RPG 开局首幕 NPC 流程收口方案2026-04-30
## 目标
本轮只收口“进入游戏开局场景后遇到第一幕第一批人”的运行时流程:
1. 对方主角色好感度 `>= 0` 时,聊天过程中允许出现 `npc_chat`、任务、送礼、交易、获得帮助等 NPC 功能选项;聊天结束后界面只保留一个 `story_continue_adventure`,点击后直接推进到下一幕。
2. 对方主角色好感度 `< 0` 时,聊天过程中只允许 `npc_chat`;聊天可以由模型中断,也可以由玩家主动中断。中断后只允许 `npc_fight``battle_escape_breakout`
3. 删除这条主流程里的干扰分支:正好感聊天结束后不再展开旧 NPC 目录或相邻场景旅行;负好感聊天中不再混入交易、送礼、求助、任务、招募、切磋、离开等 function。
## 工程落点
1. `src/hooks/rpg-runtime-story/useRpgRuntimeNpcInteraction.ts`
- `buildNpcChatFunctionOptionCatalog(...)` 按当前 NPC 好感过滤功能候选。
- 负好感聊天候选只保留 `npc_chat`
- 正好感聊天结束后的 `story_continue_adventure` 只揭开下一幕入口;若当前场景没有下一幕,才退回相邻场景入口。
2. `src/hooks/rpg-runtime-story/choiceActions.ts`
- 应用 `deferredRuntimeState.storyEngineMemory`,保证点击继续后真正切到下一幕的 `currentSceneActState`
3. `server-rs/crates/api-server/src/runtime_story/compat/presentation.rs`
- 服务端 active NPC option catalog 与前端同规则对齐。
- 负好感 active NPC 只返回 `npc_chat`
- 非负好感 active NPC 返回聊天、帮助、交易、送礼、任务、招募等功能,不再返回战斗、切磋、离开。
## 验收
1. 正好感 NPC 主动退出聊天后,只显示 `story_continue_adventure`
2. 点击 `story_continue_adventure` 后,`storyEngineMemory.currentSceneActState.currentActId` 推进到下一幕。
3. 负好感 NPC 聊天请求中的 `functionOptions` 为空,聊天 UI 不出现非聊天 function。
4. 负好感聊天中断后只出现“战斗”和逃跑选项。
5. 服务端 state catalog 对负好感 active NPC 不返回交易、送礼、帮助、任务、招募、切磋、离开等干扰入口。
## 2026-04-30 补充:负好感主动中止恢复
### 问题
敌对聊天的模型主动中止依赖后端建议 JSON 中的 `shouldEndChat` 字段,但部分入口没有把负好感 NPC 标记为 `terminationMode: hostile_model`,导致后端即使收到 `shouldEndChat: true` 也会按非敌对聊天忽略。另一个缺口是 NPC 主动开场第一轮只展示后续候选,没有处理 `chatDirective.forceExit`,因此第一轮开场也无法被模型主动中止。
### 落地
1. `src/hooks/rpg-runtime-story/useRpgRuntimeNpcInteraction.ts`
- 构造 `NpcChatDirective` 时直接读取当前 NPC 好感度。
- 只要 `affinity < 0`,统一写入 `limitReason: negative_affinity``terminationMode: hostile_model``isHostileChat: true`
- NPC 主动开场收到 `chatTurn.chatDirective.forceExit === true` 时,立即收起 `npcChatState`,展示战斗与逃跑选项。
### 补充验收
1. 任意负好感 NPC 聊天轮都必须向后端传 `terminationMode: hostile_model`,不能只依赖第一幕主 NPC 场景幕状态。
2. 负好感 NPC 主动开场第一轮若返回 `forceExit: true`,聊天输入立即关闭,只显示战斗与逃跑。
## 2026-04-30 补充:聊天首句统一由模型 NPC 发起
### 问题
NPC 主动开场链路本身已经存在,并会以空玩家消息调用模型,同时传入 `npcInitiatesConversation: true`。但运行时入口曾把这条链路限制在 `firstMeaningfulContactResolved !== true`,导致角色完成首次有效接触后,再次从 NPC 入口或交互选项进入聊天时,会退回旧的 `enterNpcChat(...)` 本地入口:界面先展示玩家可点的话题,没有模型生成的 NPC 首句。负好感且非限定场景幕时,还存在一条本地敌对宣言分支,会直接给战斗/逃跑,绕过“先聊天再中断”的主流程。
### 落地
1. `enterNpcInteraction(...)` 不再用 `firstMeaningfulContactResolved` 决定是否走 NPC 主动开场;只要是从 NPC 入口新开聊天,都调用 `startNpcInitiatedOpening(...)`
2. `handleNpcInteraction(...)``chat` 分支保留“当前已经在同一段 `npcChatState` 内时,点击 `npc_chat` 作为玩家回复”的行为;若不在已有聊天内,统一调用 `startNpcInitiatedOpening(...)`
3. 删除负好感入口的本地敌对宣言分支。负好感只通过 `NpcChatDirective` 影响模型语气、功能选项和 `forceExit` 后的战斗/逃跑收束,不再跳过模型首句。
4. `enterNpcChat(...)` 仅保留为缺少角色/世界类型或模型开场失败时的兜底入口,不作为正常聊天开场路径。
### 补充验收
1. 不论好感度正负,也不论 `firstMeaningfulContactResolved` 是否为 `true`,新开聊天首轮都必须调用 `streamNpcChatTurn(..., '', { npcInitiatesConversation: true })`
2. 新开聊天最终展示的第一条 `dialogue` 必须是模型返回的 NPC 文本,`npcChatState.openingSource` 必须是 `npc_initiated`
3. 已经处于同一段 `npcChatState` 中时,点击 `npc_chat` 仍作为玩家本轮回复进入 `handleNpcChatTurn(...)`,不能重新开一段 NPC 首句。
4. 负好感入口不能直接显示本地战斗/逃跑;只有模型或玩家中断聊天后,才显示 `npc_fight``battle_escape_breakout`

View File

@@ -0,0 +1,66 @@
# RPG 幕预览启动卡载入修复2026-04-30
## 背景
编辑器内点击“幕预览”后,独立预览层会一直停在“正在载入这一幕的游戏流程...”,无法进入真实 RPG 运行壳。
## 根因
`SceneActPreviewRuntime` 先调用 `handleCustomWorldSelect(profile)`,紧接着调用 `handleCharacterSelect(previewCharacter)`
`handleCharacterSelect()` 读取的是当前 render 闭包中的旧 `gameState`。此时 `handleCustomWorldSelect()` 写入的 `worldType` 还没有完成 React 状态提交,所以选角入口看到 `worldType` 为空后直接返回。随后幕预览虽然又手动写入了 `currentScene / currentScenePreset / currentEncounter`,却没有写入 `playerCharacter`,导致 `isPreviewReady` 永远不成立。
另一个隐患是:有 `currentEncounter` 时 story controller 不会主动生成普通首段剧情,而是交给 NPC 交互流接管;若预览没有显式注入一个可展示的 `currentStory`,运行面板也可能无法稳定挂载。
## 本轮继续修复
继续复测时发现,`SceneActPreviewRuntime` 虽然已经不再调用 `handleCharacterSelect()`,但仍会调用 `handleCustomWorldSelect(profile)` 来同步 runtime 静态资料。
这个入口是正式运行态的“选择世界”入口,会排队写入“已选择世界、尚未选角”的中间 `GameState`。在幕预览本地 `setGameState()` 写入玩家、场景与故事后,这个中间态仍可能覆盖回来,导致 `currentScenePreset``playerCharacter` 被清掉,预览层重新停在“正在载入这一幕的游戏流程...”。
本轮调整后:
1. 幕预览不再调用 `handleCustomWorldSelect(profile)`
2. 幕预览直接调用 `setRuntimeCustomWorldProfile(profile)``setRuntimeCharacterOverrides(buildCustomWorldRuntimeCharacters(profile))` 同步静态资料层。
3. `isPreviewReady` 同时校验:
- `currentScene === "Story"`
- `runtimeSessionId === "runtime-scene-act-preview"`
- 当前玩家就是本次预览角色
- 当前场景就是本次预览场景
- 当前 story 已经完成注入
4. 这样 preview ready 只依赖本次预览自己的完整启动结果,不再被正式选世界中间态影响。
## 修复口径
1. 幕预览不再调用 `handleCharacterSelect()` 触发后端开局副作用。
2. 幕预览不调用正式 `handleCustomWorldSelect(profile)`,而是直接同步 runtime 静态资料层。
3. 随后在同一个 `setGameState` 中一次性写入:
- `playerCharacter`
- `runtimeMode: "play"`
- `runtimePersistenceDisabled: true`
- `currentScene / currentScenePreset / currentEncounter`
- 玩家血蓝、技能冷却、装备、统计、进度、队伍、任务等运行态基础字段
- 当前幕 `currentSceneActState`
4. 幕预览使用固定临时 `runtimeSessionId: "runtime-scene-act-preview"`,并通过禁持久化快照保持不写正式存档。
5. 启动时同步 `hydrateStoryState()`,注入当前幕 NPC 的本地开场 story`RpgRuntimeShell` 立即满足挂载条件。
## 约束
1. 幕预览继续复用正式 `play` 运行链,不恢复旧 `preview/test` 行为分支。
2. 幕预览只允许前端做临时运行态装配;正式游戏开局仍由 `server-rs` 裁决。
3. 后续如把幕预览也迁到后端 bootstrap应新增专门的禁持久化 bootstrap 入口,而不是再次依赖 `handleCharacterSelect()` 的异步状态顺序。
## 验证
新增回归覆盖:
```bash
npm test -- --run src/components/CustomWorldEntityEditorModal.test.tsx
```
断言幕预览打开后:
1. 不再显示“正在载入这一幕的游戏流程...”。
2. `RpgRuntimeShell` 已收到预览玩家角色。
3. 运行态为 `play` 且禁用持久化。
4. 当前 story 已注入为当前幕 NPC 开场内容。

View File

@@ -57,3 +57,15 @@
- LLM 不可用时的聊天 reply、普通 choice、function choice 兜底生成。
3. `server-rs/crates/api-server/src/runtime_chat.rs` 只保留 Axum SSE、LLM 调用、解析、好感变化、结束聊天判断等流程编排,不再直接承载提示词正文或 choice 文案兜底。
4. 后续调整聊天 choice 语气、候选数量、`functionOptions` 描述方式、敌对聊天收束策略时,优先修改 `prompt/runtime_chat.rs`
## 7. 拼图 Prompt 独立目录收口
2026-04-30 追加收口:
1. 拼图提示词参考 RPG 的目录组织,统一迁入 `server-rs/crates/api-server/src/prompt/puzzle/`
2. `prompt/puzzle/agent_chat.rs` 承接拼图共创 Agent 的 system prompt、单轮 JSON 输出契约、用户提示词与 anchor pack / 聊天记录提示词组装。
3. `prompt/puzzle/draft.rs` 承接生成拼图作品草稿动作里的表单 seed prompt、草稿首图 prompt 来源选择、单关图片再生成 prompt 来源选择。
4. `prompt/puzzle/image.rs` 承接拼图图片生成正式提示词与默认反向提示词。
5. `puzzle_agent_turn.rs` 只保留 LLM 调用、结果解析、阶段判断和 SpacetimeDB 写回输入构造,不再内联拼图聊天提示词正文。
6. `puzzle.rs` 只保留拼图路由、计费、DashScope、OSS、候选图持久化和运行态编排不再内联拼图草稿或图片提示词正文。
7. 后续调整拼图共创问法、输出契约、生成草稿 prompt 来源、图片画面约束或反向提示词时,优先修改 `prompt/puzzle/`,不要在 `puzzle.rs``puzzle_agent_turn.rs` 中新增提示词正文。

View File

@@ -0,0 +1,88 @@
# SpacetimeDB 本地 replica identity 不一致处理方案
日期:`2026-04-30`
## 1. 问题
本地启动 SpacetimeDB standalone 时出现:
```text
error starting database: failed to init replica 1 for <new-database-identity>: mismatched database identity: <old-database-identity> != <new-database-identity>
```
本次现场日志中,`server-rs/.spacetimedb/local/data/logs/spacetime-standalone.log` 显示:
1. `2026-04-30T12:17:26Z` 开始按 `c2006f3d846a8259512006a556b1bc3f751a9aef6608fc0ee75788deea6d9331` 启动数据库。
2. `replica 1` 的持久化数据仍带有旧库 `c20037fcfaac4e5c4b1f492f026a4f6119a98f56319b77f21ef021ededf8b7ae`
3. SpacetimeDB 因同一个副本目录中 identity 不一致而拒绝继续启动。
这不是 Rust 编译错误,也不是 `api-server:maincloud` 的 token 错误。只要错误来自 `server-rs/.spacetimedb/local/.../spacetime-standalone.log`,优先按本地 root-dir 数据目录污染处理。
## 2. 根因
`spacetime start --edition standalone` 会在同一个 `--root-dir` 下保存控制库、程序字节、WAL 与 replica 数据。当前仓库默认本地 root-dir 是:
```text
server-rs/.spacetimedb/local
```
当这个目录曾经启动并发布过旧 database identity之后又用同一个 root-dir 初始化或发布到另一个 database identity 时,可能出现:
1. `control-db` 记录的是新库。
2. `data/replicas/1` 里仍残留旧库 WAL 或快照。
3. 启动时 SpacetimeDB 尝试把旧 replica 当作新库加载,触发 `mismatched database identity`
## 3. 处理原则
1. 不在脚本里默认删除 `.spacetimedb` 数据,避免误删本地开发数据。
2. 如果只是本地开发库且数据可丢弃,优先备份后重建 `data` 目录。
3. 如果数据必须保留,不要清理目录;应改回创建旧库时使用的 database/root-dir或先导出迁移数据。
4. Maincloud 发布与本地 standalone root-dir 是两条链路;不要通过切回 `server-node` 或 PostgreSQL 绕过。
## 4. 本地可丢弃数据时的修复
PowerShell
```powershell
$root = "C:\Genarrative\server-rs\.spacetimedb\local"
Get-CimInstance Win32_Process |
Where-Object { $_.Name -match "spacetime" -and $_.CommandLine -and $_.CommandLine.Replace("/", "\") -like "*$($root.Replace("/", "\"))*" } |
Select-Object ProcessId, Name, CommandLine
```
确认占用进程后停止:
```powershell
Stop-Process -Id <pid> -Force
```
备份运行态数据目录:
```powershell
$stamp = Get-Date -Format "yyyyMMdd-HHmmss"
Move-Item -LiteralPath "C:\Genarrative\server-rs\.spacetimedb\local\data" -Destination "C:\Genarrative\server-rs\.spacetimedb\local\data.identity-mismatch-backup.$stamp"
```
重新启动本地链路:
```powershell
npm run dev:rust
```
`npm run dev:rust` 会重新启动 standalone、发布 `spacetime-module`,并生成新的本地数据库运行态。
## 5. 需要保留数据时的处理
不要移动或删除 `server-rs/.spacetimedb/local/data`。先确认旧库 identity 对应的数据库名、root-dir 与发布命令,然后选择:
1. 用旧库对应的 database/root-dir 重新启动。
2. 使用迁移导出脚本导出旧数据,再清理本地 root-dir 并导入到新库。
3. 如目标其实是 Maincloud改用 `npm run api-server:maincloud` 连接云端,避免误启动本地 standalone。
## 6. 脚本诊断
`scripts/dev-rust-stack.sh` 已补充本地启动失败诊断:
1. SpacetimeDB 进程在就绪前退出时,会打印 `spacetime-standalone.log` 尾部。
2. 若日志包含 `mismatched database identity`,会提示本地 `data/replicas/1` 与当前 control-db identity 不一致。
3. 诊断只输出建议,不自动清理数据。

View File

@@ -407,6 +407,7 @@ SELECT * FROM custom_world_gallery_entry WHERE public_work_code = '<public_work_
- 作用:拼图创作 Agent 会话表,保存种子、阶段、锚点包、草稿和已发布 profile。
- 结构:`session_id PK: String`, `owner_user_id: String`, `seed_text: String`, `current_turn: u32`, `progress_percent: u32`, `stage: PuzzleAgentStage`, `anchor_pack_json: String`, `draft_json: Option<String>`, `last_assistant_reply: Option<String>`, `published_profile_id: Option<String>`, `created_at: Timestamp`, `updated_at: Timestamp`
- 说明:填表式拼图入口会在点击“拼图”时立即创建空 session生成草稿前的表单自动保存复用 `seed_text``draft_json`,不新增表字段,`stage` 保持 `CollectingAnchors`
- 索引:`owner_user_id`
```sql
@@ -426,8 +427,10 @@ SELECT * FROM puzzle_agent_message WHERE session_id = '<session_id>' ORDER BY cr
### `puzzle_work_profile`
- 作用:拼图作品主表,保存作品信息、封面、发布状态、游玩次数和锚点包。
- 结构:`profile_id PK: String`, `work_id: String`, `owner_user_id: String`, `source_session_id: Option<String>`, `author_display_name: String`, `level_name: String`, `summary: String`, `theme_tags_json: String`, `cover_image_src: Option<String>`, `cover_asset_id: Option<String>`, `publication_status: PuzzlePublicationStatus`, `play_count: u32`, `anchor_pack_json: String`, `publish_ready: bool`, `created_at: Timestamp`, `updated_at: Timestamp`, `published_at: Option<Timestamp>`
- 作用:拼图作品主表,保存作品信息、多关卡草稿、封面、发布状态、游玩次数和锚点包。
- 结构:`profile_id PK: String`, `work_id: String`, `owner_user_id: String`, `source_session_id: Option<String>`, `author_display_name: String`, `work_title: String`, `work_description: String`, `level_name: String`, `summary: String`, `theme_tags_json: String`, `cover_image_src: Option<String>`, `cover_asset_id: Option<String>`, `levels_json: String`, `publication_status: PuzzlePublicationStatus`, `play_count: u32`, `anchor_pack_json: String`, `publish_ready: bool`, `created_at: Timestamp`, `updated_at: Timestamp`, `published_at: Option<Timestamp>`
- 说明:`work_title`/`work_description` 是作品详情页展示来源;`levels_json` 保存拼图关卡列表,`level_name`/`summary` 继续作为首关兼容字段和旧数据回退来源。
- 说明:拼图初始表单草稿也写入本表作为创作中心卡片投影;未生成图片前 `cover_image_src = None``publish_ready = false`,再次打开草稿时通过 `source_session_id` 恢复表单。
- 索引:`owner_user_id`, `publication_status`
```sql

View File

@@ -0,0 +1,42 @@
# 作品作者按用户 ID 解析设计 2026-04-30
## 背景
作品列表、公开广场和详情页需要展示作者信息。旧链路里部分作品表会同时写入 `author_display_name`,如果用户后续修改昵称,旧作品仍会显示发布时的昵称快照,造成作者信息不一致。
## 目标
1. 作品作者的真相源统一使用 `owner_user_id`
2. API 返回作品读模型时,通过 `owner_user_id` 读取账号公开资料,并使用最新 `display_name` 作为 `authorDisplayName`
3. `author_display_name` 暂时保留为历史兼容字段,只在用户资料不存在或读取失败时作为回退值。
4. 前端详情页优先展示按 `ownerUserId` 读取到的公开用户资料;作品字段里的作者名只作为兜底展示。
## 落地规则
### SpacetimeDB 存储
1. `custom_world_profile.owner_user_id` / `custom_world_gallery_entry.owner_user_id` 是 RPG 作品作者 ID。
2. `puzzle_work_profile.owner_user_id` 是拼图作品作者 ID。
3. `big_fish_creation_session.owner_user_id` 是大鱼吃小鱼作品作者 ID。
4. 现有 `author_display_name` 不再作为作者真相源,不新增依赖它做权限、同作者推荐或作者资料展示的逻辑。
5. 本次不删除 `author_display_name`,避免破坏历史迁移包、生成绑定和旧客户端兼容;后续若要删除,必须单独做 schema 迁移和绑定刷新。
### API facade
1. 输出 `authorDisplayName` 时先用 `owner_user_id` 查询认证用户表。
2. 查询成功时使用用户最新 `display_name`,并同步补齐 `public_user_code`
3. 查询失败或用户缺失时才回退作品表旧 `author_display_name`
4. 大鱼吃小鱼公开作品不再由前端硬编码作者名API 根据 `owner_user_id` 输出作者显示名。
### 前端
1. 统一作品详情页已按 `ownerUserId` 读取公开用户摘要,用于头像和作者名。
2. 详情页展示作者名时优先使用公开用户摘要的 `displayName`,缺失时回退作品读模型的 `authorDisplayName`
3. 新增作品类型接入平台详情页时,不允许只在前端写固定作者昵称。
## 验收点
1. 用户修改昵称后RPG / 拼图 / 大鱼公开作品列表与详情页能展示新昵称。
2. 旧作品缺少可读取用户资料时,仍能用历史 `author_display_name` 或“玩家”兜底。
3. 作品权限和“同作者”判断继续使用 `owner_user_id`
4. 本次不改变 SpacetimeDB 表结构,因此不需要调整 `migration.rs` 白名单或导入补字段逻辑。