Refactor local dev stack scheduler

This commit is contained in:
kdletters
2026-05-15 09:37:08 +08:00
parent 0152f9bd67
commit 7a3b137565
20 changed files with 2393 additions and 2088 deletions

View File

@@ -22,12 +22,12 @@
- 验证:拼图入口测试仍可通过,且新组件可通过不同页面复用而不需要复制上传卡实现。
- 关联:`src/components/common/CreativeImageInputPanel.tsx``src/components/puzzle-agent/PuzzleAgentWorkspace.tsx`
## 汪汪声浪重新开放时不要再回到独立配置阶段
## 汪汪声浪入口不要再回到独立配置阶段
- 现象:汪汪声浪入口如果继续切换到独立配置阶段,会和拼图、抓大鹅的创作页内嵌结构不一致,用户会感觉入口跳页。
- 原因:旧实现把 `bark-battle` 单独挂到 `bark-battle-config` selectionStage而不是复用创作 Tab 里的模板区。
- 处理:当前 `bark-battle` 入口为 `visible=true``open=false`展示为“敬请期待”api-server 会熔断 `/api/creation/bark-battle/*``/api/runtime/bark-battle/*`。后续重新开放时,入口点击只设置 `activeCreationFormType = 'bark-battle'` 并回到创作 Tab`BarkBattleConfigEditor` 作为内嵌表单使用默认隐藏返回按钮和页面标题runtime `onExit` 重新回到创作 Tab 的汪汪声浪模板。
- 验证:当前点击汪汪声浪不进入创作表单,直连创作 / 运行态 API 返回 `creation_entry_disabled`;重新开放时再覆盖内嵌表单与 runtime 返回路径。
- 处理:入口点击只设置 `activeCreationFormType = 'bark-battle'` 并回到创作 Tab`BarkBattleConfigEditor` 作为内嵌表单使用默认隐藏返回按钮和页面标题runtime `onExit` 重新回到创作 Tab 的汪汪声浪模板。
- 验证:点击汪汪声浪后直接看到创作页内嵌表单,不再出现独立配置页;测试应覆盖内嵌表单与 runtime 返回路径。
- 关联:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx``src/components/bark-battle-creation/BarkBattleConfigEditor.tsx``src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx`
## 抓大鹅批量重新生成物品不要新增 itemId
@@ -36,7 +36,7 @@
- 原因:重新生成和批量新增共用 `item-assets` 接口,如果前端不传 `mode = "replace"`,或后端替换时重新分配 `itemId` / 追加未匹配名称,就会破坏 `generatedItemAssets` 顺序和运行态类型映射。
- 处理:批量重新生成只提交当前素材列表中能匹配到的名称,并传 `mode = "replace"`;后端只对同名已有素材生成新图片,合并时保留原 `itemId``itemName`、模型兼容字段、UI 背景和历史音频字段,未匹配名称直接忽略且不计费。
- 验证:`npm run test -- src\components\match3d-result\Match3DResultView.test.tsx` 覆盖前端提交口径,`cargo test -p api-server match3d_item_asset --manifest-path server-rs\Cargo.toml``cargo test -p api-server match3d_regenerated_asset --manifest-path server-rs\Cargo.toml` 覆盖后端替换计划与身份保留。
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
- 关联:`src/components/match3d-result/Match3DResultView.tsx``server-rs/crates/api-server/src/match3d.rs``packages/shared/src/contracts/match3dWorks.ts``server-rs/crates/shared-contracts/src/match3d_works.rs``docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`
## 抓大鹅生成封面图不要覆盖物品素材或配置
@@ -44,7 +44,7 @@
- 原因:封面生成属于定向图片槽位更新;若后端复用草稿编译写回,可能按 session config 重算作品行。即使后端已修正,前端若直接把封面接口返回的整份 `item` 当成最新 profile也可能用旧回包里的空 `generatedItemAssets` 覆盖当前页面素材。
- 处理:`POST /api/creation/match3d/works/{profileId}/cover-image` 只保存 `coverImageSrc` / `coverAssetId` 等封面字段,保留当前 `generated_item_assets_json`、难度、消除次数、题材和描述;前端收到回包后只合并 `coverImageSrc`,继续保留当前可见 `generatedItemAssets``clearCount``difficulty`
- 验证:`npm run test -- src\components\match3d-result\Match3DResultView.test.tsx` 覆盖旧回包不覆盖物品素材和配置;`cargo test -p api-server match3d_cover --manifest-path server-rs\Cargo.toml` 覆盖封面提示词与参考图链路。
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
- 关联:`src/components/match3d-result/Match3DResultView.tsx``server-rs/crates/api-server/src/match3d.rs``server-rs/crates/spacetime-module/src/match3d/mod.rs``docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`
## OSS V4 签名时间和 bucket/object_key 兼容
@@ -60,7 +60,7 @@
- 原因:生成音乐转存到 OSS 私有对象后,`audioSrc` 是 generated legacy path浏览器 `<audio>` 不能像公开静态资源一样直接请求裸路径。另一个常见误判是浏览器拒绝自动播放,资源已经进入运行态但开局第一次 `audio.play()` 被拦截。
- 处理:结果页试听控件和运行态隐藏 `<audio>` 设置 `src` 前,都先通过 `useResolvedAssetReadUrl``resolveAssetReadUrl` 换签;签名未就绪时不要回退请求裸 generated 路径。运行态自动播放失败只静默兜底,但玩家首次按下拼图块或点击抓大鹅物品时要重试同一个背景音乐播放函数。拼图读取 `currentLevel.backgroundMusic.audioSrc`,抓大鹅读取 `generatedItemAssets[].backgroundMusic.audioSrc`
- 验证:结果页试听和运行态 `<audio loop>``src` 为签名 URL 或公开 URL拼图/抓大鹅运行态首次局内交互后会再次尝试播放背景音乐;`npm run typecheck` 不报契约字段缺失,后端 run response 带 `backgroundMusic`
- 关联:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
- 关联:`src/components/puzzle-runtime/PuzzleRuntimeShell.tsx``src/components/match3d-runtime/Match3DRuntimeShell.tsx``docs/technical/PUZZLE_MATCH3D_RESULT_AUDIO_TAB_2026-05-11.md`
## 抓大鹅背景音乐是作品级字段但暂存在首个物品素材
@@ -68,23 +68,7 @@
- 原因:当前表结构没有作品级音频字段,背景音乐暂存在 `generatedItemAssets[]`。如果 action response 的 draft assets 缺音乐,前端又优先用它覆盖 work detail或音乐落在非首个素材而结果页只读 `assetDrafts[0].backgroundMusic`,就会丢掉已生成音乐。
- 处理:前端统一使用 `normalizeMatch3DGeneratedItemAssetsForRuntime` / `mergeMatch3DGeneratedItemAssetsForRuntime`:把任意素材上的 `backgroundMusic` 与音乐元信息迁移到首个素材清空其它素材上的作品级音乐字段action draft assets 与 work detail assets 按 `itemId` 合并保留详情里的音乐、UI 背景和点击音效。
- 验证:`npm run test -- src\services\match3dGeneratedModelCache.test.ts src\components\match3d-result\Match3DResultView.test.tsx src\components\match3d-runtime\Match3DRuntimeShell.test.tsx`;平台推荐流定向跑 `RpgEntryFlowShell.agent.interaction.test.tsx` 中的 Match3D runtime assets 用例;`npm run typecheck`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## 抓大鹅草稿关闭背景音乐后仍触发 Suno 先查后端自动编排
- 现象:用户已关闭抓大鹅草稿里的 `生成音效` 或隐藏结果页音频入口,但点击生成仍报 `提交 Suno 背景音乐任务失败`
- 原因:入口开关只影响前端可见控件或点击音效,后端 `match3d_compile_draft` 之前还会把 `backgroundMusic` 当作自动完成条件,继续调用 `generate_background_music_asset_for_creation()`,从而直接发 `/suno/submit/music`
- 处理:抓大鹅草稿自动编排只保留 2D 物品图片、UI 背景和容器图;背景音乐字段只作为历史兼容数据,不再作为草稿完成条件,也不再由自动编排提交 Suno。
- 验证:`cargo test -p api-server match3d_generated_assets_require_only_images_when_click_sound_is_closed --manifest-path server-rs/Cargo.toml` 通过,`cargo check -p api-server --manifest-path server-rs/Cargo.toml` 无新的编译问题。
- 关联:`server-rs/crates/api-server/src/match3d.rs``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``.hermes/shared-memory/decision-log.md`
## 抓大鹅 UI 预览和实际游戏不一致先查运行态样式复用
- 现象:结果页 `素材配置 > UI` 打开的预览弹层和真实抓大鹅运行态不一致,例如顶部只显示倒计时、右上还是转圈占位、中心容器尺寸或定位和局内不同。
- 原因:预览层手写了简化 HUD、棋盘尺寸和容器图样式没有消费 `Match3DRuntimeShell` 运行态使用的共享样式常量。
- 处理:把运行态 HUD、棋盘、容器图和槽位的 Tailwind 类抽到 `match3dRuntimeUiStyles.ts`,结果页预览只复用这些常量;运行态之后改顶部设置入口、关卡卡片、容器图宽度或棋盘兜底样式时,不要在结果页再写一套。
- 验证:`npm run test -- src/components/match3d-result/Match3DResultView.test.tsx src/components/match3d-runtime/Match3DRuntimeShell.test.tsx`
- 关联:`src/components/match3d-result/Match3DResultView.tsx``src/components/match3d-runtime/match3dRuntimeUiStyles.ts``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
- 关联:`src/services/match3dGeneratedModelCache.ts``src/components/match3d-result/Match3DResultView.tsx``src/components/platform-entry/PlatformEntryFlowShellImpl.tsx``docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`
## 中文乱码与编码风险
@@ -105,15 +89,15 @@
- 原因:重置/修改密码会更新 `password_hash``password_login_enabled``token_version`,如果 API 层只更新本地 `InMemoryAuthStore`,没有调用 `sync_auth_store_snapshot_to_spacetime()``api-server` 重启时可能从旧的 SpacetimeDB 表或旧快照恢复账号状态。
- 处理:`POST /api/auth/password/change``POST /api/auth/password/reset` 成功后必须同步认证快照;启动恢复时从 SpacetimeDB 表、SpacetimeDB 快照记录和本地 `GENARRATIVE_AUTH_STORE_PATH` 文件中选择可判断的最新快照,本地文件更新时尝试回写 SpacetimeDB。
- 验证:执行 `cargo test -p module-auth password --manifest-path server-rs/Cargo.toml``cargo test -p api-server password --manifest-path server-rs/Cargo.toml`;手测时重设密码后旧密码应失败,新密码应成功,重启后仍应保持。
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
- 关联:`server-rs/crates/api-server/src/password_management.rs``server-rs/crates/api-server/src/state.rs``docs/technical/PASSWORD_LOGIN_CHANGE_RESET_DESIGN_2026-04-24.md`
## 抓大鹅生成页只显示服务暂不可用先查 reason 和外部服务配置
- 现象:点击生成抓大鹅草稿后,页面只提示“服务暂不可用”,或者本地 `npm run api-server` 看似启动但生成接口不可用。
- 现象:点击生成抓大鹅草稿后,页面只提示“服务暂不可用”,或者本地 `npm run dev:api-server` 看似启动但生成接口不可用。
- 原因:配置缺失类错误通常在后端 `error.details.reason` 中给出具体缺项,前端如果只读 `details.message` 会吞掉原因;本地只配置 `ALIYUN_OSS_BUCKET` / `ALIYUN_OSS_ENDPOINT` 时,旧逻辑还会在启动期构造空 AccessKey 的 OSS 客户端并失败。抓大鹅新链路仍是 2D 生图切割,不需要也不应回退 Rodin/GLB。
- 处理:前端 API 错误展示优先读取 `details.reason`,再读取 `details.message`,避免底层 `error sending request` 覆盖真正可操作的配置或网络原因;`api-server` 只有在 OSS 四件套齐全时初始化 OSS 客户端,部分缺失只记 warning 并让具体 generated 上传/换签接口返回 `OSS 未完成环境变量配置`。抓大鹅素材、封面和背景生成在调用 VectorEngine 前先预检 OSS并通过 `details.missingEnv` 列出缺项;真实生成需补齐 `VECTOR_ENGINE_BASE_URL``VECTOR_ENGINE_API_KEY` 和完整 `ALIYUN_OSS_*` 四件套。抓大鹅 `5*5` 素材图提示词还必须要求相邻物体主体至少保留 `1/4` 单格宽度空白间距,避免切割后相邻格内容污染。
- 验证:`npm run test -- src/services/apiClient.test.ts` 覆盖 `details.reason``cargo test -p api-server state --manifest-path server-rs/Cargo.toml` 覆盖半配置 OSS 不阻断启动;`npm run api-server` 后按实际 `GENARRATIVE_API_PORT` 请求 `/healthz`,不要默认打 `3100`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
- 验证:`npm run test -- src/services/apiClient.test.ts` 覆盖 `details.reason``cargo test -p api-server state --manifest-path server-rs/Cargo.toml` 覆盖半配置 OSS 不阻断启动;`npm run dev:api-server` 后按实际 `GENARRATIVE_API_PORT` 请求 `/healthz`,不要默认打 `3100`
- 关联:`packages/shared/src/http.ts``server-rs/crates/api-server/src/state.rs``docs/technical/API_SERVER_EXTERNAL_SERVICE_ENV_CONFIG_2026-05-07.md``docs/technical/AUTH_SNAPSHOT_AND_MATCH3D_LOCAL_DEV_FIX_2026-05-01.md`
2026-05-14 补充:抓大鹅“物品素材 sheet”已改用 VectorEngine Gemini `gemini-3-pro-image-preview` 原生 `generateContent`,真实生成读取 `VECTOR_ENGINE_BASE_URL``VECTOR_ENGINE_API_KEY``VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS`;封面和 `9:16` 背景图走 VectorEngine `/v1/images/generations``1:1` 容器 UI 走 VectorEngine `/v1/images/edits` multipart 参考图链路。排查素材 sheet 时看请求路径是否为 `/v1beta/models/gemini-3-pro-image-preview:generateContent?key=...`,响应图片在 `candidates[].content.parts[].inlineData.data` / `inline_data.data`,不要再按 APIMart `/images/generations``/tasks/{task_id}` 排查。
@@ -123,7 +107,7 @@
- 原因:发布按钮被 `publishReady` 直接禁用,导致未满足门槛时无法进入发布检查面板;封面编辑仍挂在作品信息 Tab不能和发布检查一起收口。
- 处理:发布按钮只受忙碌态控制,点击后始终打开独立发布面板;发布面板内先展示阻断项,再承载封面图上传 / AI 重绘 / 参考图编辑,满足条件后再点击 `发布到广场`
- 验证:`npm run test -- src/components/match3d-result/Match3DResultView.test.tsx``npm run typecheck`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
- 关联:`src/components/match3d-result/Match3DResultView.tsx``src/components/match3d-result/Match3DResultView.test.tsx``docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`
## `.hermes` 只放共享内容,不放个人 Hermes 配置
@@ -139,7 +123,7 @@
- 原因:浏览器摄像头视频流只是舞台背景;如果热身关把 `getUserMedia` 状态当成主动作数据源,或只在 gesture 阶段消费 `useMocapInput`,就会错过 mocap 的身体中心、动作名和手部坐标。
- 处理:确认 `src/components/child-motion-demo/ChildMotionWarmupDemo.tsx` 全热身流程启用 `useMocapInput`,页面主提示展示 mocap 动作数据源状态而不是浏览器摄像头状态;确认 `src/services/useMocapInput.ts` 能解析 `/stream` 包里的 `general.body.center_norm``actions/action/gesture/gestures/event/name/type``hands[]``leftHand/rightHand``left_hand/right_hand`、左右手标记和 `open_palm/grab` 状态。`/stream` 是 WebSocket普通 HTTP 访问返回 404 不能当成服务不可用。
- 验证:运行 `npx vitest run src\services\useMocapInput.test.ts src\components\child-motion-demo\ChildMotionWarmupDemo.test.tsx`,并在本地硬件服务启动后进入 `/child-motion-demo` 实测站位、招手、左右手挥动和跳跃阶段。
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
- 关联:`src/services/useMocapInput.ts``src/components/child-motion-demo/ChildMotionWarmupDemo.tsx``docs/technical/CHILD_MOTION_DEMO_WARMUP_IMPLEMENTATION_SPEC_2026-05-09.md`
## 儿童动作 Demo 左右手阶段误通过先查身体侧映射和手臂展开阈值
@@ -147,7 +131,7 @@
- 原因:本地 mocap 的 handedness 当前按摄像头视角输出,不能直接当作用户身体左/右;同时左右手阶段的目标是确认现实空间安全,需要验证手臂向外打开和上下摆动角度,不能只看手部 `x` 轨迹范围。
- 处理:热身关中用户左手应消费 camera-right用户右手应消费 camera-left左右手阶段只在同侧肩肘腕外展、手腕非自然下垂、连续有效帧、横向范围、上下摆动范围、肩腕角度范围和上下方向变化全部达标时完成并记录轨迹空间包络、角度范围和最大外展距离。
- 验证:运行 `npx vitest run src\components\child-motion-demo\ChildMotionWarmupDemo.test.tsx src\components\child-motion-demo\childMotionWarmupModel.test.ts`,确认相反侧手、自然下垂、单纯横向轨迹不会完成,真实展开上下摆动可以完成。
- 关联:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
- 关联:`src/components/child-motion-demo/ChildMotionWarmupDemo.tsx``src/components/child-motion-demo/childMotionWarmupModel.ts``docs/technical/CHILD_MOTION_DEMO_WARMUP_IMPLEMENTATION_SPEC_2026-05-09.md`
## 儿童动作 Demo 角色轮廓抽搐先查 mocap 坐标防抖和渲染分层
@@ -155,7 +139,7 @@
- 原因:`general.body.center_norm.x` 原始值逐包直接写入 `avatarX` 时,硬件坐标小噪声会直接驱动位置保持判定和 CSS 动画;如果角色外层同时承担横向定位和跳跃 `transform`,半透明 PNG 在移动时也更容易出现重采样抖动观感。
- 处理mocap 身体中心进入角色位置前必须先 clamp再经过小幅死区、低通阻尼和单包最大步长限制键盘 A/D 调试输入仍保持即时。角色 DOM 外层只负责横向定位,内层 sprite 负责轮廓图和跳跃位移,避免同一层 `transform` 同时表达多种运动。
- 验证:运行 `npx vitest run src\components\child-motion-demo\ChildMotionWarmupDemo.test.tsx src\components\child-motion-demo\childMotionWarmupModel.test.ts src\services\useMocapInput.test.ts src\services\child-motion-demo\childMotionDebugInput.test.ts`,并用真实硬件进入站位阶段观察小幅身体晃动不会导致角色频繁左右跳动。
- 关联:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
- 关联:`src/components/child-motion-demo/ChildMotionWarmupDemo.tsx``src/index.css``docs/technical/CHILD_MOTION_DEMO_WARMUP_IMPLEMENTATION_SPEC_2026-05-09.md`
## 宝贝识物选篮误触发先查多套判定和残余轨迹
@@ -164,7 +148,7 @@
- 处理:宝贝识物选篮只使用明确 `leftHand` / `rightHand` 的连续横向轨迹阈值;侧别为 `unknown` 的手部轨迹不参与选篮;反馈阶段清空轨迹,不在非 `active` 阶段累计路径。进入关卡和每次正确反馈结束后自动弹出物品,不再用 `open_palm -> grab` 抓握序列激活礼物盒。
- 补充:当前本地 mocap 的 handedness 是摄像头视角,宝贝识物选篮前需要换算为用户身体视角;`rightHand` 轨迹代表玩家左手并进入左篮,`leftHand` 轨迹代表玩家右手并进入右篮。键鼠调试不走该换算,仍保持鼠标左键=左篮、右键=右篮。
- 验证:运行 `npm run test -- src/components/edutainment-runtime/BabyObjectMatchRuntimeShell.test.tsx src/services/useMocapInput.test.ts`,确认动作名负向测试、未知侧别负向测试和左右手横向轨迹测试通过。
- 关联:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
- 关联:`src/components/edutainment-runtime/BabyObjectMatchRuntimeShell.tsx``docs/technical/BABY_OBJECT_MATCH_CREATION_PUBLISH_IMPLEMENTATION_2026-05-11.md`
## 宝贝爱画左右手反了先查 mocap 摄像头视角换算
@@ -172,7 +156,7 @@
- 原因:本地 mocap 的 handedness 当前按摄像头视角输出,不能直接当成用户身体左 / 右;宝贝爱画初版直接消费 `latestCommand.leftHand/rightHand`,漏做摄像头视角到用户身体视角的换算。
- 处理:宝贝爱画运行态消费 mocap 前先换算:`rightHand` 作为用户左手,用于颜色悬停和左手指示器;`leftHand` 作为用户右手,用于画笔 / 橡皮光标、绘制、擦除和工具切换。键鼠调试输入不做该换算,继续保持鼠标左键为左手、右键为右手。
- 验证:运行 `npm run test -- src/components/edutainment-runtime/BabyLoveDrawingRuntimeShell.test.tsx src/components/edutainment-runtime/babyLoveDrawingModel.test.ts`,确认 camera-left 驱动用户右手画笔、camera-right 渲染用户左手选色指示器。
- 关联:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
- 关联:`src/components/edutainment-runtime/BabyLoveDrawingRuntimeShell.tsx``docs/technical/BABY_LOVE_DRAWING_RUNTIME_DEMO_IMPLEMENTATION_2026-05-13.md`
## 宝贝识物创作卡在准备结果页先查长耗时 image-2 请求
@@ -180,15 +164,15 @@
- 原因:宝贝识物一次创作会生成 2 张物品图和 `background``ui-frame``gift-box``basket``smoke-puff` 5 张视觉包装图。旧前端只等待 180 秒并对长耗时 POST 自动重试,容易在 VectorEngine 仍在生成时先 abort再重复发起第二次生成上游某张图超过后端 `VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS` 或返回 5xx 时会表现为 502。
- 处理:`babyObjectMatchClient``/api/creation/edutainment/baby-object-match/assets` 使用 10 分钟超时并取消自动重试;后端并发启动物品图和视觉主题包生成,并把该路由的 VectorEngine 单图请求等待预算提升到至少 8 分钟,按资源类别输出开始、完成和耗时日志。
- 验证:运行 `npm run test -- src/services/edutainment-baby-object/babyObjectMatchClient.test.ts src/services/miniGameDraftGenerationProgress.test.ts``cargo test -p api-server edutainment_baby_object --manifest-path server-rs/Cargo.toml` 和编码检查;真实联调时查看 `宝贝识物 image-2 资源生成完成` 耗时是否小于前端超时,若仍 502 再看 `VectorEngine 图片生成上游错误``upstreamStatus/raw_excerpt`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
- 关联:`src/services/edutainment-baby-object/babyObjectMatchClient.ts``src/services/miniGameDraftGenerationProgress.ts``server-rs/crates/api-server/src/edutainment_baby_object.rs``docs/technical/BABY_OBJECT_MATCH_CREATION_PUBLISH_IMPLEMENTATION_2026-05-11.md`
## 寓教于乐作品和宝贝识物模板同时消失先查入口种子
- 现象:发现页“寓教于乐”分类下已发布的宝贝识物作品突然消失,同时创作界面模板选项中也看不到或无法正常展示 `宝贝识物`
- 原因:创作入口配置事实源已迁到 SpacetimeDB `creation_entry_type_config`;前端用 `baby-object-match` 入口可见性同时控制创作模板展示和发现页宝贝识物公开作品合入。若默认种子或后台配置缺少 `baby-object-match` 行,两条链路会一起被判定为不可见。
- 处理:确认 `server-rs/crates/spacetime-module/src/runtime/creation_entry_config.rs` 默认种子包含 `id=baby-object-match``title=宝贝识物``visible=true``open=false``badge=敬请期待``sort_order=90`api-server 测试降级配置也要同步包含该类型。入口图片路径需指向真实存在资源,避免卡片图片 404。若只是“敬请期待”,不要把 `visible` 关掉,否则发现页寓教于乐公开作品可见性也会受影响。
- 验证:运行 `cargo test -p module-runtime default_creation_entry_types_include_baby_object_match --manifest-path server-rs/Cargo.toml``cargo test -p api-server creation_entry_config --manifest-path server-rs/Cargo.toml``cargo check -p spacetime-module --manifest-path server-rs/Cargo.toml``npm run test -- src/components/platform-entry/platformEntryCreationTypes.test.ts`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
- 处理:确认 `server-rs/crates/spacetime-module/src/runtime/creation_entry_config.rs` 默认种子包含 `id=baby-object-match``title=宝贝识物``visible=true``open=true``sort_order=90`api-server 测试降级配置也要同步包含该类型。入口图片路径需指向真实存在资源,避免卡片图片 404。
- 验证:运行 `cargo test -p module-runtime default_creation_entry_types_include_baby_object_match --manifest-path server-rs/Cargo.toml``cargo test -p api-server test_creation_entry_config_response_keeps_baby_object_match_visible --manifest-path server-rs/Cargo.toml``cargo check -p spacetime-module --manifest-path server-rs/Cargo.toml``npm run test -- src/components/platform-entry/platformEntryCreationTypes.test.ts`
- 关联:`server-rs/crates/spacetime-module/src/runtime/creation_entry_config.rs``server-rs/crates/api-server/src/creation_entry_config.rs``docs/technical/NEW_WORK_ENTRY_CONFIG_2026-05-01.md`
## 儿童动作 Demo 绘本风资源未生成先查 VectorEngine 配置
@@ -196,7 +180,7 @@
- 原因:儿童动作 Demo 的真实背景、地面、UI、地面指示环和角色轮廓资源都使用 VectorEngine `gpt-image-2-all` 生成,脚本只读取 `VECTOR_ENGINE_BASE_URL``VECTOR_ENGINE_API_KEY` 和可选 `VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS`;仓库内不能提交真实 key缺配置时页面只能使用 CSS 草地绘本兜底。
- 处理:在本地私密环境补齐 `VECTOR_ENGINE_BASE_URL=https://api.vectorengine.ai``VECTOR_ENGINE_API_KEY`,不要把 key 写入 Git先运行 `npm run assets:child-motion-demo -- --dry-run` 核对 prompt再运行 `npm run assets:child-motion-demo -- --live``npm run assets:child-motion-demo -- --live --only ui-panel` 等小批量命令生成资源。透明资源的品红底源图写入 `tmp/child-motion-demo-assets/`,不要把源图或预览图放入 `public/child-motion-demo/` 作为正式资产。
- 验证:生成后确认 `public/child-motion-demo/` 只保留页面引用的最终 PNG重新打开 `/child-motion-demo` 可看到真实绘本草地背景、地面、圆环、角色轮廓和 UI 资源;`npm run check:encoding` 仍通过。
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
- 关联:`scripts/generate-child-motion-demo-assets.mjs``src/index.css``docs/technical/CHILD_MOTION_DEMO_WARMUP_IMPLEMENTATION_SPEC_2026-05-09.md`
## 儿童动作 Demo 绘本资源变形先查用途拆分和透明后处理
@@ -204,7 +188,7 @@
- 原因:早期资源中 `picture-book-ui-panel.png` 是接近方形画布,`picture-book-grass-floor.png` 也含大量透明边界;若 CSS 用 `background-size: 100% 100%` 把同一资源强行铺成 HUD、状态条、开始面板或底部地板就会出现变形和层叠观感。
- 处理:使用 v2 用途专属资源:`picture-book-foreground-grass-v2.png``picture-book-ground-ring-v2.png``picture-book-character-outline-v2.png``picture-book-hud-strip-v2.png``picture-book-calibration-strip-v2.png``picture-book-start-panel-v2.png``picture-book-ui-button-v2.png`CSS 按资源比例等比缩放底部草坪只覆盖下沿HUD / 状态条 / 开始托盘分别引用各自资源。若只需修透明裁切或品红边,运行 `npm run assets:child-motion-demo -- --live --postprocess-only --force --only <asset-id>`,不重新请求 image-2。
- 验证:用横屏截图检查没有新旧资源叠加、没有方形面板拉成长条、角色和地面指示环不被前景草坪埋住;同时运行 `npm run check:encoding`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
- 关联:`scripts/generate-child-motion-demo-assets.mjs``src/index.css``public/child-motion-demo/``docs/technical/CHILD_MOTION_DEMO_WARMUP_IMPLEMENTATION_SPEC_2026-05-09.md`
## GPT-image-2 不再读 APIMart 图片配置
@@ -212,7 +196,7 @@
- 原因2026-05-09 后 GPT-image-2 图片生成已切到 VectorEngine `gpt-image-2-all`APIMart 只保留给创意 Agent 的 `gpt-5` Responses 文本/多模态链路。
- 处理:为图片生成配置 `VECTOR_ENGINE_BASE_URL=https://api.vectorengine.ai``VECTOR_ENGINE_API_KEY``VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS`;排查请求体时确认路径为 `/v1/images/generations`、模型为 `gpt-image-2-all`、参考图字段为 `image`
- 验证:运行 `cargo test -p api-server openai_image --manifest-path server-rs/Cargo.toml` 和相关玩法图片生成测试;真实联调只在本地私密环境放置 VectorEngine key。
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
- 关联:`docs/technical/VECTOR_ENGINE_GPT_IMAGE_2_GENERATION_2026-05-09.md``server-rs/crates/api-server/src/openai_image_generation.rs`
## 拼图参考图没有影响生成时先查 action payload 和阶段日志
@@ -236,7 +220,7 @@
- 原因:`gpt-image-2-all``/v1/images/generations` 更适合纯文生图;有参考图且需要重绘时应切到 `/v1/images/edits` 的 multipart 图生图接口。
- 处理:`referenceImageSrc` 存在且 `aiRedraw = true` 时直接走 editsprompt 仍保留参考图强约束;入口页关闭 AI 重绘时直接应用上传图,不调用图片生成;前端把参考图压到单边 1024 内,后端解析后拒绝超过 8MB 的参考图字节。
- 验证:后端单测应覆盖 `images/edits` 路由、`b64_json` 响应解码和参考图强提示;真实联调先看日志里是否命中 `拼图 VectorEngine 图片编辑 HTTP 返回`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
- 关联:`server-rs/crates/api-server/src/puzzle.rs``src/services/puzzleReferenceImage.ts``docs/technical/VECTOR_ENGINE_GPT_IMAGE_2_GENERATION_2026-05-09.md`
## 拼图 edits 报 error sending request 先看网络分类
@@ -244,7 +228,7 @@
- 原因:这是 `reqwest``send()` 阶段失败,尚未收到 VectorEngine HTTP 响应;常见原因是服务器网络 / DNS / 防火墙 / 代理问题,或上游网关中断 multipart 连接。
- 处理:查看错误响应 `details.reason/source/connect/body/timeout/endpoint``拼图 VectorEngine 请求发送失败` 日志。拼图图片客户端已强制 HTTP/1.1,降低 multipart HTTP/2 兼容风险;若 `connect=true` 先查网络出口,若 `body=true` 先查参考图大小和 multipart 发送。
- 验证:`curl --http1.1 -i -X POST https://api.vectorengine.ai/v1/images/edits -H "Authorization: Bearer invalid" -F "model=gpt-image-2" -F "prompt=test" -F "n=1" -F "size=1024x1024" -F "image=@public/match3d-background-references/pot-fused-reference.png;type=image/png"` 至少应返回 HTTP `401`说明域名、TLS、路径和 multipart 上传可达;执行 `cargo test -p api-server puzzle_vector_engine --manifest-path server-rs/Cargo.toml`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
- 关联:`server-rs/crates/api-server/src/puzzle.rs``docs/technical/VECTOR_ENGINE_GPT_IMAGE_2_GENERATION_2026-05-09.md``docs/technical/API_SERVER_EXTERNAL_SERVICE_ENV_CONFIG_2026-05-07.md`
## 拼图 UI 背景缺失先区分生成失败和消费链路丢字段
@@ -252,7 +236,7 @@
- 原因:`compile_puzzle_draft` 设计上会在首图后生成 UI 背景,且缺 `uiBackgroundImageSrc/uiBackgroundImageObjectKey` 会让自动草稿失败;若草稿已成功,通常不是“没生成”,而是前端消费链路漏了 `levels[].uiBackgroundImageObjectKey` 回退,或本地 `startLocalPuzzleRun(...)` 只把 `coverImageSrc` 带入 `currentLevel`
- 处理:结果页预览、运行态和本地运行态统一用 `resolvePuzzleUiBackgroundSource`,优先 `uiBackgroundImageSrc`,为空时把 `uiBackgroundImageObjectKey` 规范成 `/generated-...` 路径并交给 `/api/assets/read-url` 换签;`startLocalPuzzleRun` 与本地下一关 handoff 都要从 `PuzzleWorkSummary.levels[]` 复制 `uiBackgroundImageSrc/uiBackgroundImageObjectKey/backgroundMusic``currentLevel`。结果页 `UI背景提示词` 输入框不得把本地兜底 prompt 直接显示成已保存提示词,避免误判为后端已生成。
- 验证:`npm run test -- src/components/puzzle-result/PuzzleResultView.test.tsx src/services/puzzle-runtime/puzzleLocalRuntime.test.ts src/components/puzzle-runtime/PuzzleRuntimeShell.test.tsx`,以及 `npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "puzzle draft generation auto starts trial"`;后端用 `cargo test -p api-server puzzle_ui_background --manifest-path server-rs\Cargo.toml` 确认生成 / 序列化链路。
- 关联:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
- 关联:`src/services/puzzle-runtime/puzzleUiBackgroundSource.ts``src/services/puzzle-runtime/puzzleLocalRuntime.ts``src/components/puzzle-runtime/PuzzleRuntimeShell.tsx``src/components/puzzle-result/PuzzleResultView.tsx``docs/technical/PUZZLE_FORM_CREATION_FLOW_2026-04-29.md`
## 拼图草稿生成后音乐/UI 又变空先查结果页回包合并
@@ -260,23 +244,23 @@
- 原因:结果页若已有本地 `generationStatus = generating` 编辑态,后端生成完成回包会走 `mergeDraftEditStateWithIncomingState(...)` 合并。该合并必须把生成候选图、正式图、`uiBackground*``backgroundMusic` 作为同一批生成资产处理;漏掉 `backgroundMusic` 时,随后自动保存会把空音乐写回 `levels_json`
- 处理:`PuzzleResultView` 合并生成完成回包时同步保留 `backgroundMusic`,并用回归测试覆盖 UI 预览、音乐试听和试玩 payload 都读取最新 `levels[]` 资产。
- 验证:`npm run test -- src/components/puzzle-result/PuzzleResultView.test.tsx`,以及自动试玩入口测试 `npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "puzzle draft generation auto starts trial"`
- 关联:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
- 关联:`src/components/puzzle-result/PuzzleResultView.tsx``src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx``docs/technical/PUZZLE_MATCH3D_RESULT_AUDIO_TAB_2026-05-11.md`
## 自动草稿成功但缺音乐或 UI 先查后端吞错
- 现象:拼图或抓大鹅生成页提示完成,但草稿页仍显示“暂无音乐”,拼图 UI 仍是默认预览,试玩局内也没有生成音乐或 UI 背景。
- 原因:自动草稿阶段如果把 VectorEngine / Suno / OSS / 资产绑定错误记录为 warning 后继续返回成功,前端只能拿到缺关键资产的成功 draft随后保存和试玩都会消费这份空资产状态。
- 处理:自动草稿必须把必需生成资产当作后端完成条件:拼图首关需同时具备 `levels[0].backgroundMusic.audioSrc``levels[0].uiBackgroundImageSrc/uiBackgroundImageObjectKey`;抓大鹅需在 `generatedItemAssets[]` 中具备非空 `backgroundMusic.audioSrc`。缺失或上游失败时返回错误并停留在生成页,结果页手动重新生成只作为已有草稿补救入口。
- 验证:`cargo test -p api-server puzzle_initial_draft_assets_must_include_music_and_ui_background match3d_background_music_ready_requires_audio_src match3d_background_music_title_is_required_for_auto_draft --manifest-path server-rs\Cargo.toml`,并重启 `npm run api-server` 后检查 `/healthz`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
- 验证:`cargo test -p api-server puzzle_initial_draft_assets_must_include_music_and_ui_background match3d_background_music_ready_requires_audio_src match3d_background_music_title_is_required_for_auto_draft --manifest-path server-rs\Cargo.toml`,并重启 `npm run dev:api-server` 后检查 `/healthz`
- 关联:`server-rs/crates/api-server/src/puzzle.rs``server-rs/crates/api-server/src/match3d.rs``docs/technical/PUZZLE_FORM_CREATION_FLOW_2026-04-29.md``docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`
## 拼图草稿生成 180 秒后 502/504 先查 VectorEngine 超时与前端重试
- 现象:点击“生成拼图游戏草稿”后,`POST /api/runtime/puzzle/agent/sessions/{sessionId}/actions` 等待约 180 秒返回 `502 Bad Gateway``504 Gateway Timeout`;钱包流水里同一 session 可能出现连续两组 `puzzle_initial_image` 扣费后退款。
- 原因:首图生成走 VectorEngine `gpt-image-2-all`,默认 `VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS=1000000`;若上游在该窗口内未返回,后端退款并返回超时错误。旧前端 action 写请求会对 502/503/504 自动重试一次,导致同一次点击重复触发生图与扣退费。
- 处理:拼图/创作 Agent 的 `executeAction` 默认不做前端自动重试;后端将 VectorEngine / 图片请求超时映射为 `504 Gateway Timeout``error.details.provider=vector-engine``timeout=true`。真实排障按日志同一 `session_id``拼图 VectorEngine 图片生成 HTTP 返回` 是否缺失,以及钱包流水扣费到退款的时间差是否接近 `VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS`
- 验证:运行 `npm run test -- src/services/creation-agent/creationAgentClientFactory.test.ts src/services/apiClient.test.ts``cargo test -p api-server puzzle_vector_engine --manifest-path server-rs/Cargo.toml`,真实联调重启 `npm run api-server` 后检查 `/healthz`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
- 验证:运行 `npm run test -- src/services/creation-agent/creationAgentClientFactory.test.ts src/services/apiClient.test.ts``cargo test -p api-server puzzle_vector_engine --manifest-path server-rs/Cargo.toml`,真实联调重启 `npm run dev:api-server` 后检查 `/healthz`
- 关联:`src/services/creation-agent/creationAgentClientFactory.ts``server-rs/crates/api-server/src/puzzle.rs``docs/technical/API_SERVER_EXTERNAL_SERVICE_ENV_CONFIG_2026-05-07.md`
## 本地脚本调 VectorEngine 生图卡住先区分 fetch 首部超时
@@ -290,25 +274,17 @@
- 现象:开发时参考到 Express、Node、PostgreSQL 或 Go 方向旧文档,导致接口、数据真相或部署路径与当前主线不一致。
- 原因:项目历史文档较多,部分旧方案仍保留作迁移参考。
- 处理涉及服务端、数据真相、SpacetimeDB、运行时状态时先看当前后端架构文档,再看相关 crate README 和源码
- 处理涉及服务端、数据真相、SpacetimeDB、运行时状态时先看 `CURRENT_BACKEND_IMPLEMENTATION_BASELINE_2026-04-25.md`,再看 DDD 总纲和具体技术方案
- 验证:代码改动应落在 `server-rs + Axum + SpacetimeDB` 主线;旧路线只作为迁移参考,不作为兼容目标。
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`
- 关联:`docs/technical/CURRENT_BACKEND_IMPLEMENTATION_BASELINE_2026-04-25.md``AGENTS.md`
## SpacetimeDB 表结构变更不能按 PostgreSQL 迁移直觉处理
- 现象:发布时 schema 冲突、自动迁移拒绝、旧客户端调用 reducer 失败、private 表数据迁移遗漏。
- 原因SpacetimeDB 对字段删除、类型变化、索引/主键/RLS/reducer 变化有不同自动迁移边界。
- 处理:变更前阅读 `docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`;已有表新增字段必须放在 Rust 表结构体最后并设置明确默认值;需要修改字段名时,先询问用户并确认迁移计划;涉及表变化时同步 `migration.rs``docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md` 和 bindings必要时走 JSON 导入导出与分片导入迁移流程。
- 处理:变更前阅读 `SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md`;已有表新增字段必须放在 Rust 表结构体最后并设置明确默认值;需要修改字段名时,先询问用户并确认迁移计划;涉及表变化时同步 `migration.rs``SPACETIMEDB_TABLE_CATALOG.md` 和 bindings必要时走 JSON 导入导出与分片导入迁移流程。
- 验证:发布前运行 `npm run check:spacetime-schema`,完成 schema 检查、bindings 生成、表目录更新和相关 smoke。
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`
## SpacetimeDB schema guard 新增表应校验 sidecar不应把新增本身当失败
- 现象:新增 SpacetimeDB 表后schema guard 只要看到新 table accessor 就直接报错,哪怕 `migration.rs`、表目录和生成绑定都已同步。
- 原因:守卫脚本把“发现新增表”本身当成失败,而不是把它当成需要校验 sidecar 的信号。
- 处理:新增表只应触发 `migration.rs``SPACETIMEDB_TABLE_CATALOG.md` 和 bindings 的一致性检查;当 sidecar 都已同步时,新增表应允许通过。不要把“合法新增表”本身判成失败。
- 验证:`npm run check:spacetime-schema` 在新增表、迁移、目录和绑定都同步后应通过。
- 关联:`scripts/check-spacetime-schema-guard.mjs``server-rs/crates/spacetime-module/src/migration.rs``docs/technical/SPACETIMEDB_TABLE_CATALOG.md`
- 关联:`docs/technical/SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md``docs/technical/SPACETIMEDB_TABLE_CATALOG.md`
## SpacetimeDB publish 报 wasm-bindgen 时先查 shared-contracts feature
@@ -316,7 +292,7 @@
- 原因SpacetimeDB module 的 wasm32 构建树被间接带入原生/网页依赖;已验证链路是 `reqwest -> platform-oss -> shared-contracts -> module-runtime -> spacetime-module`,由共享契约默认启用资产 OSS 契约触发。
- 处理:让 `shared-contracts` 的 OSS 资产契约走 `oss-contracts` featureworkspace 根依赖保持 `default-features = false``api-server` 这类原生后端需要资产 DTO 时在自身 `Cargo.toml` 显式启用 `features = ["oss-contracts"]`
- 验证:执行 `cargo tree -i wasm-bindgen --manifest-path server-rs\crates\spacetime-module\Cargo.toml --target wasm32-unknown-unknown` 应显示 nothing to print再执行 `cargo check -p spacetime-module --manifest-path server-rs\Cargo.toml --target wasm32-unknown-unknown`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
- 关联:`server-rs/crates/shared-contracts/Cargo.toml``server-rs/crates/api-server/Cargo.toml``docs/technical/RUST_WORKSPACE_DEPENDENCY_CONSOLIDATION_2026-05-07.md`
## 本地 SpacetimeDB replica identity 不匹配
@@ -324,15 +300,15 @@
- 原因:本地 SpacetimeDB 数据目录中的 replica 数据残留与当前数据库身份不一致。
- 处理:按本地 replica identity mismatch 文档进行备份、重建和脚本诊断。
- 验证:本地 SpacetimeDB 可正常启动并 publish / 访问。
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`
- 关联:`docs/technical/SPACETIMEDB_LOCAL_REPLICA_IDENTITY_MISMATCH_FIX_2026-04-30.md`
## 本地 SpacetimeDB publish 403 优先查 CLI 身份和目标库
- 现象:`spacetime publish``Pre-publish check` 阶段返回 `403 Forbidden`,提示当前 identity 无权对目标 database identity 执行 `update database`
- 原因:当前 CLI 登录态不是目标数据库的创建者或授权身份,或 `.env.local` / publish 命令指向了另一个数据库或 SpacetimeDB 服务。
- 处理:除 CI/CD 脚本内部受控用法外,不再使用 `spacetime --root-dir` 排障或发布。先执行 `spacetime login show``spacetime server list`,再用 `spacetime list --server http://127.0.0.1:3101` 或实际 `--server-url` 确认当前身份是否能看到目标库;本地开发发布优先使用 `npm run dev:rust` 或从 `server-rs` 目录执行显式 `--server``spacetime publish`。如果身份不对,重新登录正确身份、使用项目脚本重新生成本地库,或在 SpacetimeDB 侧补授权。
- 处理:除 CI/CD 脚本内部受控用法外,不再使用 `spacetime --root-dir` 排障或发布。先执行 `spacetime login show``spacetime server list`,再用 `spacetime list --server http://127.0.0.1:3101` 或实际 `--server-url` 确认当前身份是否能看到目标库;本地开发发布优先使用 `npm run dev:spacetime` 或从 `server-rs` 目录执行显式 `--server``spacetime publish`。如果身份不对,重新登录正确身份、使用项目脚本重新生成本地库,或在 SpacetimeDB 侧补授权。
- 验证:`spacetime list --server http://127.0.0.1:3101` 能看到目标库;重新发布不再使用无权限 identity。
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
- 关联:`scripts/dev.mjs``docs/technical/SPACETIMEDB_START_SH_PUBLISH_403_IDENTITY_FIX_2026-04-26.md`
## `npm run dev` 本地 SpacetimeDB 401 / 403 可重置默认 local 身份
@@ -340,15 +316,23 @@
- 原因:本机 `spacetime` CLI 保存的旧 token、默认 server、正在运行的 standalone 进程或默认 local 数据库与当前发布身份不一致。
- 处理:确认只是本地测试库且数据可丢弃后,先查看并停止本地 `spacetimedb-standalone`,执行 `spacetime logout`,确认并设置 `spacetime server set-default local`,停 server 后用 `spacetime server clear -y` 清空默认本地库,再 `spacetime start`,另开终端执行 `spacetime login --server-issued-login local`,最后用 `spacetime publish --server local A` 或项目脚本重新发布。
- 验证:`spacetime server list` 默认目标为 local重新登录后发布不再返回 `401` / `403``npm run dev` 可以完成 SpacetimeDB publish 并继续启动 `api-server`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
- 关联:`docs/technical/SPACETIMEDB_START_SH_PUBLISH_403_IDENTITY_FIX_2026-04-26.md``scripts/dev.mjs`
## 本地 SpacetimeDB 联调可按阶段跳过宿主或发布
- 现象:本地 `npm run dev``3101` 已占用、重复发布 SpacetimeDB wasm 编译太慢,或只想检查 `spacetime-module` 语法而被完整联调链路拖慢。
- 原因:`npm run dev` 默认同时启动 SpacetimeDB standalone、发布 `server-rs/crates/spacetime-module`、启动 Rust `api-server`、主站 Vite 与后台 Vite并非每个阶段都需要完整重启和重新发布。
- 处理:`npm run dev` 启动后会把实际 SpacetimeDB URL 记录到 `server-rs/.spacetimedb/local/data/dev-rust-spacetime-url`。下次启动即使没有传 `--skip-spacetime`脚本也会先检查 `spacetime.pid` 对应进程和该 URL 是否在线;在线则直接复用现有宿主。确认需要新启动 SpacetimeDB 时,脚本先检测 `3101`,被占用则输出占用进程并选择最近可用端口,保证 publish 与 `api-server` 都连接同一个实际 SpacetimeDB URL。显式传 `--skip-spacetime` 时表示复用既有宿主,脚本不再对 SpacetimeDB 端口做可用性漂移;`--spacetime-port 3101` 就是后端要连接的实际端口,避免被误改到空闲但未启动的 `3102``api-server` 启动前也会检测 `8082` 并选择最近可用端口。Windows / Git Bash 下不要用 `tr/head/xargs` 管道读取 `spacetime.pid` 或 URL 记录,脚本应使用 Node 读取并短重试,避免 `tr: read error: Device or resource busy`;未修改 `spacetime-module` 时使用 `npm run dev -- --skip-publish`;只查模块语法时执行 `cargo check -p spacetime-module --manifest-path server-rs/Cargo.toml``npm run dev` 会在启动前检查 SpacetimeDB、api-server、主站 Vite、后台 Vite 端口,不可用时自动寻找后续可用端口,并把实际端口传给 publish、后端环境变量和前端代理目标。
- 验证:`--skip-spacetime` 后脚本复用现有 `http://127.0.0.1:3101`;日志中的 `[dev:rust] spacetime:` 不应漂移到没有服务的 `3102``GET /api/creation-entry/config` 不应返回连接空端口导致的 `502``3101``8082` 被其他进程占用时,脚本输出占用进程并使用最近可用端口;`--skip-publish` 后不再进入 publish 阶段;`cargo check -p spacetime-module --manifest-path server-rs/Cargo.toml` 能完成 Rust 语法和类型检查。端口漂移时控制台会打印 `[dev:ports] ... 不可用,改用 ...`,后续 `[dev:rust] web/admin web/rust api/spacetime` 地址应与实际端口一致。
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
- 处理:`npm run dev` 启动后会把实际 SpacetimeDB URL 记录到 `server-rs/.spacetimedb/local/data/dev-spacetime-url`,并同步写旧兼容文件 `dev-rust-spacetime-url`。下次启动即使没有传 `--skip-spacetime`调度器也会先检查该 URL 旁边的 `spacetime.pid` `/v1/ping` 是否可复用;在线则直接复用现有宿主。确认需要新启动 SpacetimeDB 时,脚本先检测 `3101`,被占用则选择最近可用端口,保证 publish 与 `api-server` 都连接同一个实际 SpacetimeDB URL。显式传 `--skip-spacetime` 时表示复用既有宿主,脚本不再对 SpacetimeDB 端口做可用性漂移;`--spacetime-port 3101` 就是后端要连接的实际端口,避免被误改到空闲但未启动的 `3102``api-server`、主站 Vite 和后台 Vite 启动前也会解析可用端口。`spacetime-module` 改动后只重新 publish不重启 standalone 宿主;未修改 `spacetime-module` 时使用 `npm run dev -- --skip-publish`;只查模块语法时执行 `cargo check -p spacetime-module --manifest-path server-rs/Cargo.toml``npm run dev` 会在启动前检查 SpacetimeDB、api-server、主站 Vite、后台 Vite 端口,不可用时自动寻找后续可用端口,并把实际端口传给 publish、后端环境变量和前端代理目标。
- 验证:`--skip-spacetime` 后脚本复用现有 `http://127.0.0.1:3101`;日志中的 `[dev] spacetime:` 不应漂移到没有服务的 `3102``GET /api/creation-entry/config` 不应返回连接空端口导致的 `502``3101``8082` 被其他进程占用时,脚本使用最近可用端口;`--skip-publish` 后不再进入 publish 阶段;`cargo check -p spacetime-module --manifest-path server-rs/Cargo.toml` 能完成 Rust 语法和类型检查。端口漂移时控制台会打印 `[dev:ports] ... 不可用,改用 ...`,后续 `[dev] web/admin web/api-server/spacetime` 地址应与实际端口一致。`spacetime-module` 变更后只应看到重新发布日志,不应看到 standalone 重启日志。
- 关联:`docs/technical/RUST_LOCAL_AND_REMOTE_DEPLOYMENT_SCRIPTS_2026-04-22.md``scripts/dev.mjs`
## `npm run dev -- --watch` 前端无限重启先查外层 watcher
- 现象:开启 `npm run dev -- --watch` 后,后台 Vite 或主站 Vite 反复退出重启,即使没有手动修改源码。
- 原因Vite 本身会监听源码并写入 `node_modules/.vite` 等缓存;外层调度器如果再递归监听前端目录并重启 dev server就可能把 Vite 自己的缓存写入当成源码变化,形成循环重启。
- 处理:外层 watcher 只负责后端侧:`spacetime-module` 改动后重新 publish`api-server` 改动后重启 Rust 进程。主站 Vite 和后台 Vite 的源码变化交给 Vite HMR需要进程级重启时在 `npm run dev` 终端手动输入 `rs web``rs admin-web`
- 验证:`npm run dev -- --watch` 下修改 `apps/admin-web/src/**` 应由 Vite HMR 处理,不应出现连续 `[dev] 重启 admin-web``scripts/dev.test.ts` 覆盖 web/admin-web 不注册外层 watch。
- 关联:`scripts/dev.mjs``docs/technical/RUST_LOCAL_AND_REMOTE_DEPLOYMENT_SCRIPTS_2026-04-22.md`
## 本地 SpacetimeDB publish 401 可清本地库重发
@@ -356,7 +340,7 @@
- 原因:本地开发数据目录中保留的数据库、控制库身份或发布身份与当前目标不一致。
- 处理:确认本地开发数据可以丢弃后,停止本地 SpacetimeDB备份或删除 `server-rs/.spacetimedb/local/data`,再重新运行 `npm run dev` 或本地 publish不要用 `--root-dir` 手工清库。
- 验证:重新发布日志应显示创建新的数据库,而不是更新旧数据库;若仍显示更新或继续 `401`,继续检查数据目录、库名和 CLI 身份。
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
- 关联:`docs/technical/RUST_LOCAL_AND_REMOTE_DEPLOYMENT_SCRIPTS_2026-04-22.md``docs/technical/SPACETIMEDB_START_SH_PUBLISH_403_IDENTITY_FIX_2026-04-26.md`
## SpacetimeDB 模块 publish 报 `wasm-bindgen detected`
@@ -364,7 +348,7 @@
- 原因SpacetimeDB 模块是数据库内 WASM不允许拉入 Web/HTTP client 链路;常见误因是 `spacetime-module -> module-* -> shared-contracts -> platform-* -> reqwest -> wasm-bindgen` 这类反向依赖。
- 处理:执行 `cargo tree -i wasm-bindgen --manifest-path server-rs/Cargo.toml -p spacetime-module --target wasm32-unknown-unknown` 找到链路;把平台实现类型从 `shared-contracts``module-*` 中移除,只保留公开 DTO平台响应到 DTO 的转换放回 `api-server` 等 adapter 层。
- 验证:上述 `cargo tree` 输出 `warning: nothing to print``cargo check -p shared-contracts``cargo check -p api-server` 通过;重新 `spacetime publish ... --module-path server-rs/crates/spacetime-module` 不再报 wasm-bindgen。
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
- 关联:`docs/technical/RUST_WORKSPACE_DEPENDENCY_CONSOLIDATION_2026-05-07.md``server-rs/crates/shared-contracts/src/assets.rs``server-rs/crates/api-server/src/assets.rs`
## Vite SPA fallback 吞掉 API 请求
@@ -372,7 +356,7 @@
- 原因Vite 代理缺少对应 `/api/*` 前缀API 请求落到 SPA fallback。
- 处理:补齐 Vite 代理,让 API 请求转发到 Rust `api-server`
- 验证:请求返回 JSON相关页面不再出现 HTML parse 错误。
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`
- 关联:`docs/technical/PROFILE_MAIN_ROUTE_VITE_PROXY_FIX_2026-05-02.md`
## 反馈页清空 file input 前必须先拷贝 FileList
@@ -380,7 +364,7 @@
- 原因:浏览器传入的 `FileList` 可能跟 `<input type="file">` 保持 live 绑定;如果先执行 `input.value = ''`,再从参数里的 `FileList` 读取文件,列表可能已经为空。
- 处理:在清空 file input 前先执行 `const selectedFiles = files ? Array.from(files) : []`后续图片类型、大小、Data URL 读取和预览都基于这个普通数组。
- 验证:`PlatformFeedbackView.test.tsx` 用 mock `FileReader` 断言选择图片后出现 `反馈凭证预览`,且提交 payload 带 `evidenceItems[].dataUrl`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
- 关联:`src/components/platform-entry/PlatformFeedbackView.tsx``docs/technical/PROFILE_FEEDBACK_BACKEND_INTEGRATION_2026-05-08.md`
## 拼图 VectorEngine 图片生成密钥不能复用 DashScope / ARK key
@@ -388,30 +372,30 @@
- 原因:拼图 `gpt-image-2` / 历史 `nanobanana2` 图片生成已统一走 VectorEngine后端只读取 `VECTOR_ENGINE_BASE_URL``VECTOR_ENGINE_API_KEY``VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS`,不会用 `DASHSCOPE_API_KEY``LLM_API_KEY``ARK_API_KEY``APIMART_API_KEY` 兜底。
- 处理:在本机私密配置 `.env.secrets.local` 或进程环境中配置真实 `VECTOR_ENGINE_API_KEY`,不要提交到 Git填入后必须重启 `api-server` / `npm run dev`,运行中的进程不会自动加载新 env。
- 验证:不打印密钥内容,只检查 `VECTOR_ENGINE_API_KEY` 非空;重启后触发拼图生成不再返回本地配置缺失的 503。
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
- 关联:`docs/technical/VECTOR_ENGINE_GPT_IMAGE_2_GENERATION_2026-05-09.md``.codex/skills/gpt-image-2-apimart/SKILL.md`
## `npm run api-server` 读取 env 的顺序必须让 `.env.secrets.local` 最后覆盖
## `npm run dev:api-server` 读取 env 的顺序必须让 `.env.secrets.local` 最后覆盖
- 现象:`POST /api/assets/hyper3d/text-to-model` 在本地返回 503详情里提示 `HYPER3D_API_KEY 未配置`,但开发者明明已经在本地私密文件里写了 key。
- 原因:`scripts/api-server-dev.mjs` 之前按 `.env.secrets.local → .env.local → .env` 合并,结果仓库里的 `.env` 空示例值会把前面已经设置好的私密 key 覆盖掉。
- 处理:`npm run api-server` / `npm run dev:rust` / `npm run dev` 统一按“外层 shell 变量优先,其后 `.env``.env.local``.env.secrets.local` 逐层覆盖”的顺序加载;真实密钥优先放 `.env.secrets.local`
- 原因:`scripts/dev-utils.mjs` 之前按 `.env.secrets.local → .env.local → .env` 合并,结果仓库里的 `.env` 空示例值会把前面已经设置好的私密 key 覆盖掉。
- 处理:`npm run dev:api-server` / `npm run dev:spacetime` / `npm run dev` 统一按“外层 shell 变量优先,其后 `.env``.env.local``.env.secrets.local` 逐层覆盖”的顺序加载;真实密钥优先放 `.env.secrets.local`
- 验证:本地加入临时测试后,`HYPER3D_API_KEY` 应能被 `.env.secrets.local` 覆盖,且 shell 变量仍然最高优先级。
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
- 关联:`scripts/dev-utils.mjs``server-rs/crates/api-server/src/hyper3d_generation.rs``docs/technical/HYPER3D_RODIN_GEN2_MODEL_GENERATION_2026-05-08.md`
## OSS 密钥键名不要把字母 O 写成数字 0
- 现象:`.env.secrets.local` 看起来已经配置 OSS AccessKey Secret但拼图或抓大鹅生成仍返回 `OSS 未完成环境变量配置`
- 原因:后端只读取 `ALIYUN_OSS_ACCESS_KEY_SECRET`。如果写成 `ALIYUN_0SS_ACCESS_KEY_SECRET`,中间是数字 `0`,配置合并检查会显示正确键缺失,`api-server` 不会初始化 OSS 客户端。另一个常见原因是外层 shell / IDE 预置了空的 `ALIYUN_OSS_*`,旧启动脚本会把空值当作最高优先级,导致 `.env.local``.env.secrets.local` 的真实值被跳过。
- 处理:只改键名为 `ALIYUN_OSS_ACCESS_KEY_SECRET`,保留原值;不要在日志、文档或对话里输出密钥内容。本地启动脚本应只保护非空外层环境变量,空字符串或全空白值不得遮蔽本地 env 文件。
- 验证:运行 `npm run check:api-server-env`,确认 `VECTOR_ENGINE_BASE_URL``VECTOR_ENGINE_API_KEY``ALIYUN_OSS_BUCKET``ALIYUN_OSS_ENDPOINT``ALIYUN_OSS_ACCESS_KEY_ID``ALIYUN_OSS_ACCESS_KEY_SECRET` 都是 `present`,再重启 `npm run api-server``npm run dev`
- 验证:运行 `npm run check:api-server-env`,确认 `VECTOR_ENGINE_BASE_URL``VECTOR_ENGINE_API_KEY``ALIYUN_OSS_BUCKET``ALIYUN_OSS_ENDPOINT``ALIYUN_OSS_ACCESS_KEY_ID``ALIYUN_OSS_ACCESS_KEY_SECRET` 都是 `present`,再重启 `npm run dev:api-server``npm run dev`
## 拼图图片生成 98% 后报 OSS V4 签名时间格式化失败
- 现象:拼图创作表单生成进度卡在 98%`POST /api/runtime/puzzle/agent/sessions/{sessionId}/actions` 返回 `502 Bad Gateway`,前端提示 `拼图图片生成失败OSS V4 签名时间格式化失败`
- 原因:`platform-oss` 曾用 `OffsetDateTime::time().to_string()` 拼接 `x-oss-date`UTC 小时、分钟或秒为个位数时可能缺少前导零,导致 V4 签名时间不是固定 `YYYYMMDDTHHMMSSZ`
- 处理OSS V4 签名日期统一显式补零格式化;签名 scope 用 `YYYYMMDD`,完整签名时间用 `YYYYMMDDTHHMMSSZ`,不要再依赖 `time().to_string()`
- 验证:运行 `cargo test -p platform-oss``cargo check -p api-server`;重启 `npm run api-server` 后检查 `/healthz`,再重新触发拼图生成。
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`
- 验证:运行 `cargo test -p platform-oss``cargo check -p api-server`;重启 `npm run dev:api-server` 后检查 `/healthz`,再重新触发拼图生成。
- 关联:`server-rs/crates/platform-oss/src/lib.rs``server-rs/crates/api-server/src/assets.rs``docs/technical/M6_OSS_SERVER_UPLOAD_AND_STS_POLICY_2026-04-21.md`
## 拼图生成完成后图片只显示破图或 alt 文案
@@ -419,7 +403,7 @@
- 原因:拼图正式图保存为 `/generated-puzzle-assets/*` 兼容标识,旧 `/generated-*` 直读代理已删除;如果前端没有通过 `ResolvedAssetImage` / `/api/assets/read-url` 换签,或收到无前导斜杠的 `generated-puzzle-assets/*` object key 后未识别为 generated 私有资源,浏览器会直接请求裸路径并失败。生成完成后的结果图还会传入 `refreshKey`,它只能用于重新请求 `/api/assets/read-url`,不能给 OSS V4 签名 URL 追加 `_v`OSS 会把 query 纳入签名,额外参数会让签名失效,历史素材常因未传 `refreshKey` 而表现正常。
- 处理:拼图结果页、发布预览、运行态和历史素材预览都走 `ResolvedAssetImage``useResolvedAssetReadUrl``isGeneratedLegacyPath(...)` 必须同时识别 `/generated-*``generated-*``refreshKey` 只绕过前端签名缓存并重新换签,不修改已返回的 OSS 签名 URL禁止恢复 `/generated-puzzle-assets` 直读代理。
- 验证:运行 `npm run test -- src\services\assetReadUrlService.test.ts src\hooks\useResolvedAssetReadUrl.test.tsx src\components\puzzle-result\PuzzleResultView.test.tsx`,再触发一次真实生成确认 Network 中先请求 `/api/assets/read-url`,图片 `src` 为未追加 `_v` 的签名 URL。
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
- 关联:`src/services/assetReadUrlService.ts``src/components/ResolvedAssetImage.tsx``docs/technical/PUZZLE_IMAGE_ASSET_PROXY_FIX_2026-04-27.md`
## 本地短信登录页签突然消失
@@ -430,25 +414,25 @@
- 3000 端口被旧 `dev:web` 占用后,新的完整栈 Vite 自动漂移到 3001/3002浏览器仍打开旧 3000 页面,旧页面继续代理到已经下线的端口。
- 生成页 UI 改动看起来“完全没变化”时,也要先确认当前浏览器打开的 Vite 进程正在返回最新源码;例如直接请求 `http://127.0.0.1:3000/src/components/CustomWorldGenerationView.tsx` 检查是否包含本次新增类名或关键字。
- 单独 `npm run dev:web` 启动瞬间另一个临时 API 端口可用,脚本若自动切过去,之后临时 API 停掉也会让 3000 继续代理到空端口。
- 处理:优先用 `npm run api-server``npm run dev:rust``npm run dev` 启动,这些入口应保持 shell 环境变量最高优先级,并允许 `.env.local` 覆盖 `.env`;完整栈启动时还要确保脚本计算出的 `RUST_SERVER_TARGET` 不被 `.env.local` 里的旧值覆盖。排查时先请求 3000 域名下的 `/api/auth/login-options`,再直连 Rust API 目标,并核对 `.env.local``SMS_AUTH_ENABLED` 与代理端口;若 3001/3002 才返回正确结果,说明当前 3000 是旧前端进程,应清理旧进程后重启。
- 处理:优先用 `npm run dev:api-server``npm run dev:spacetime``npm run dev` 启动,这些入口应保持 shell 环境变量最高优先级,并允许 `.env.local` 覆盖 `.env`;完整栈启动时还要确保脚本计算出的 `RUST_SERVER_TARGET` 不被 `.env.local` 里的旧值覆盖。排查时先请求 3000 域名下的 `/api/auth/login-options`,再直连 Rust API 目标,并核对 `.env.local``SMS_AUTH_ENABLED` 与代理端口;若 3001/3002 才返回正确结果,说明当前 3000 是旧前端进程,应清理旧进程后重启。
- 验证:`http://127.0.0.1:3000/api/auth/login-options` 返回至少 `{"availableLoginMethods":["phone","password"]}` 后,登录弹窗会恢复短信登录页签和“获取验证码”按钮。
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
- 关联:`scripts/dev-utils.mjs``scripts/dev.mjs``docs/technical/AUTH_LOGIN_OPTIONS_DESIGN_2026-04-21.md`
## 本地短信收不到验证码先查 provider
- 现象:登录弹窗可以进入短信页签,但点击“获取验证码”后,手机没有收到短信。
- 原因:本地 `.env.local` 里如果是 `SMS_AUTH_PROVIDER="mock"`,后端不会发真实短信,只会返回固定 mock 验证码;另外 `npm run api-server` 过去曾让 `.env` 覆盖 `.env.local`,导致本地真实短信配置被错误压回默认值。
- 原因:本地 `.env.local` 里如果是 `SMS_AUTH_PROVIDER="mock"`,后端不会发真实短信,只会返回固定 mock 验证码;另外 `npm run dev:api-server` 过去曾让 `.env` 覆盖 `.env.local`,导致本地真实短信配置被错误压回默认值。
- 处理:真实短信联调时把 `.env.local``SMS_AUTH_PROVIDER` 显式设为 `aliyun`,然后重启 `api-server`;如果只想验证 UI 和账号链路,则保留 `mock` 并使用 `SMS_AUTH_MOCK_VERIFY_CODE`
- 验证:`GET /api/auth/login-options` 返回 `["phone","password"]``api-server` 日志里 `provider=aliyun` 才说明真实短信链路已生效。
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
- 关联:`scripts/dev-utils.mjs``docs/technical/AUTH_LOGIN_OPTIONS_DESIGN_2026-04-21.md``docs/technical/PHONE_SMS_REAL_PROVIDER_MANUAL_VERIFICATION_RUNBOOK_2026-04-23.md`
## 手机验证码登录 500 先查短信 provider 语义
- 现象:登录弹窗手机号验证码登录失败,浏览器看到 `POST /api/auth/phone/login 500`,后端日志里同时出现阿里云短信 `UNKNOWN``biz.FREQUENCY``check frequency failed`
- 原因:真实短信 provider 的配置错误或上游失败曾被 `module-auth` 折叠成 `PhoneAuthError::Store`HTTP 层只能按内部错误返回 `500`,掩盖了 provider 失败。
- 处理:保留 provider 错误语义,配置错误映射 `503 Service Unavailable`,上游短信失败映射 `502 Bad Gateway`;本地只验证 UI/账号链路时可用 shell 临时覆盖 `SMS_AUTH_PROVIDER=mock` 后启动 `npm run api-server`
- 处理:保留 provider 错误语义,配置错误映射 `503 Service Unavailable`,上游短信失败映射 `502 Bad Gateway`;本地只验证 UI/账号链路时可用 shell 临时覆盖 `SMS_AUTH_PROVIDER=mock` 后启动 `npm run dev:api-server`
- 验证:`cargo test -p api-server phone_auth_sms_provider_errors_keep_upstream_http_semantics --manifest-path server-rs/Cargo.toml`,真实 provider 频控时接口不再返回 `500`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`
- 关联:`server-rs/crates/module-auth/src/errors.rs``server-rs/crates/api-server/src/phone_auth.rs``docs/technical/PHONE_SMS_PROVIDER_ERROR_HTTP_MAPPING_FIX_2026-05-08.md`
## 手机验证码登录成功后又瞬间回到未登录
@@ -456,7 +440,7 @@
- 原因:`AuthGate` 首次 hydrate 会异步轮换 refresh cookie 并请求 `/api/auth/me`。如果用户在 hydrate 完成前已经登录,晚到的旧 hydrate 仍可能把刚写入的 `user` 覆盖成 `null`
- 处理:给 `AuthGate` 的 hydrate 增加版本号保护;登录成功、退出登录和全局 auth 事件都会推进版本号,旧 hydrate 结果到达后直接丢弃。
- 验证:`npm run test -- src/components/auth/AuthGate.test.tsx`,新增用例应覆盖“旧 guest hydrate 不覆盖新登录态”。
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
- 关联:`src/components/auth/AuthGate.tsx``src/components/auth/AuthGate.test.tsx``docs/technical/AUTH_GATE_LOGIN_RACE_GUARD_FIX_2026-05-09.md`
## 刷新网页后登录态失效
@@ -464,7 +448,7 @@
- 原因:`AuthGate` hydrate 曾先强制调用 `refreshStoredAccessToken()`;当 refresh cookie 临时失效、代理错配或后端返回 `401` 时,该方法会先清空本地 access token随后 `/api/auth/me` 只能恢复成未登录。
- 处理:`refreshStoredAccessToken()` 增加 `clearOnFailure` 选项;`AuthGate` 在已有本地 access token 时先用 `/api/auth/me` 确认用户,确认成功后再后台 refresh 续期与写每日登录埋点,后台 refresh 失败不清 token。
- 验证:`npm run test -- src/services/apiClient.test.ts src/components/auth/AuthGate.test.tsx -t "explicit refresh opts out|auth gate keeps a valid local token login"`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
- 关联:`src/services/apiClient.ts``src/components/auth/AuthGate.tsx``docs/technical/AUTH_RESTORE_AND_RECOMMEND_LOADING_FIX_2026-05-09.md`
## 登录后推荐页加载出作品又回到未登录
@@ -475,7 +459,7 @@
- 追加处理:从推荐页点进公开拼图作品并启动完整运行态后,`startPuzzleRun`、通关自动 `submitPuzzleLeaderboard`、下一关 `advancePuzzleNextLevel` 和重开同样属于当前玩法局部同步;这些请求失败时只应留在拼图错误态,不应清 token 或广播 auth 事件。
- 追加处理:通关后 `refreshSaveArchives()`、首屏 bootstrap 的个人看板/作品架/浏览历史读写也只是平台投影刷新,失败应显示局部错误,不能充当全局登录态判定。
- 验证:`npm run test -- src/services/apiClient.test.ts src/services/assetReadUrlService.test.ts``npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "home recommendation starts embedded puzzle"``npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "formal puzzle runtime uses frontend move merge logic and backend leaderboard"``npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "formal puzzle similar work keeps current run level progression"`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
- 关联:`src/services/apiClient.ts``src/services/assetReadUrlService.ts``src/services/puzzle-runtime/puzzleRuntimeClient.ts``src/components/platform-entry/PlatformEntryFlowShellImpl.tsx``docs/technical/RECOMMEND_RUNTIME_AUTH_FAILURE_ISOLATION_FIX_2026-05-09.md`
## 推荐页作品卡一直显示加载中
@@ -483,15 +467,7 @@
- 原因:推荐页自动启动嵌入运行态时先设置 `activeRecommendEntryKey` / `activeRecommendRuntimeKind` / `isStartingRecommendEntry`,但失败或并发切换时外层缺少稳定错误态和请求版本保护,旧启动请求可能晚到覆盖新状态。
- 处理:`selectRecommendRuntimeEntry` 使用启动请求版本号丢弃旧请求;启动失败统一设置 `activeRecommendRuntimeError = "作品暂时无法进入,请稍后再试。"` 并关闭 `isStartingRecommendEntry`
- 验证:`npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "home recommendation surfaces start failure"`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## 推荐页拼图改造返回后又卡在进入中
- 现象:推荐页进入拼图作品后点击改造,返回推荐页时卡在“正在进入拼图关卡”或空白加载态,像是作品还在启动。
- 原因:改造或其他页面流程会清掉当前 `puzzleRun`,但推荐页自动启动逻辑曾只检查 `activeRecommendEntryKey` 是否仍在推荐列表中;旧 key 还在、玩法 run 已丢失时,会继续渲染失效的 `PuzzleRuntimeShell` 占位。
- 处理:从推荐页拼图进入改造结果页时,先收口推荐嵌入态;推荐页自动启动逻辑还必须校验当前推荐作品对应的 runtime 数据是否存在,例如拼图要有 `puzzleRun`,抓大鹅要有 `match3dRun`。key 还在但 runtime 不完整时,优先重新启动当前作品,不要误判为已激活。
- 验证:`npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "home recommendation starts embedded puzzle|home recommendation surfaces start failure|first puzzle runtime back click can open remix result page"`
- 关联:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx``src/components/puzzle-runtime/PuzzleRuntimeShell.tsx`
- 关联:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx``src/components/rpg-entry/RpgEntryHomeView.tsx``docs/technical/AUTH_RESTORE_AND_RECOMMEND_LOADING_FIX_2026-05-09.md`
## 推荐页未登录入口误打开公开详情
@@ -499,46 +475,46 @@
- 原因:`RpgEntryHomeView` 曾只有 `onOpenGalleryDetail` 一个回调,同时服务发现页公开详情和推荐页作品入口;一旦为发现页保留公开浏览能力,推荐页也会跟着打开详情。
- 处理:公开详情与推荐页入口分离为 `onOpenGalleryDetail``onOpenRecommendGalleryDetail`。发现页、搜索和排行榜保留公开详情;推荐 Tab、推荐封面、推荐运行态错误重试和桌面推荐模块统一走登录门禁。未登录推荐页只显示封面点击封面只弹登录窗不携带登录后自动打开详情的回调。
- 验证:`npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx -t "logged out recommend"`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
- 关联:`src/components/rpg-entry/RpgEntryHomeView.tsx``src/components/platform-entry/PlatformEntryFlowShellImpl.tsx``docs/technical/AUTH_RESTORE_AND_RECOMMEND_LOADING_FIX_2026-05-09.md`
## Rust 冷编译导致 api-server 健康检查误超时
- 现象:`npm run dev:rust` 在 Windows 冷编译/链接阶段误判 `/healthz` 等待超时并杀掉 `cargo run`
- 现象:`npm run dev:rust` 在 Windows 冷编译/链接阶段误判 `/healthz` 等待超时并杀掉 `cargo run`;现入口为 `npm run dev``npm run dev:api-server`
- 原因:脚本把 SpacetimeDB 与 api-server 等待窗口混在一起,未考虑 Rust 冷编译耗时。
- 处理:按冷编译超时修复文档拆分等待窗口。
- 验证:冷启动时不再误杀仍在编译的 api-server。
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
- 关联:`docs/technical/API_SERVER_DEV_STACK_COLD_BUILD_TIMEOUT_FIX_2026-04-25.md`
## Windows debug api-server 主线程栈溢出
- 现象:`cargo check -p api-server``build_router` 测试通过,但 `npm run api-server` 在 Windows debug 启动时 `thread 'main' has overflowed its stack`
- 现象:`cargo check -p api-server``build_router` 测试通过,但 `npm run dev:api-server` 在 Windows debug 启动时 `thread 'main' has overflowed its stack`
- 原因:`api-server` Axum 路由树已经很深debug 主线程默认栈偏小,初始化状态和构造路由时容易触顶。
- 处理:入口 `main` 用显式 16MB 栈线程启动 Tokio runtime并把实际服务逻辑放入 `run_server()`;新增路由时优先用小 router `.merge()`,避免继续拉长主链。
- 验证:`npm run api-server``/healthz` 返回 200相关路由冒烟通过。
- 验证:`npm run dev:api-server``/healthz` 返回 200相关路由冒烟通过。
- 关联:`server-rs/crates/api-server/src/main.rs``server-rs/crates/api-server/src/app.rs`
## Windows debug api-server.exe 锁文件与强杀退出码容易混淆
- 现象:`cargo run -p api-server``npm run api-server``failed to remove file ... target\debug\api-server.exe`;清理旧进程后,旧终端可能继续打印 `process didn't exit successfully: server-rs\target\debug\api-server.exe (exit code: 0xffffffff)`
- 原因Windows 不能覆盖仍在运行的 exe通常是上一条 `npm run api-server` 链路仍在运行,进程树为 `npm run api-server -> node scripts/api-server-dev.mjs -> cargo run -> api-server.exe``0xffffffff` 常见于排障时用 `Stop-Process -Force` 强制结束旧 `api-server.exe` 后由 Cargo 回显,不一定代表新启动失败。
- 处理:先按目标路径确认并停止本仓库的旧 `api-server.exe` 及其父级 `cargo/node/cmd` 启动链路,再重新启动;不要同时开多个 `npm run api-server`
- 验证:确认没有匹配 `C:\Genarrative\server-rs\target\debug\api-server.exe` 的进程后,`Remove-Item` 能删除旧 exe随后 `npm run api-server` 启动并访问 `/healthz` 返回 200。
- 关联:`scripts/api-server-dev.mjs``server-rs/crates/api-server/src/main.rs`
- 现象:`cargo run -p api-server``npm run dev:api-server``failed to remove file ... target\debug\api-server.exe`;清理旧进程后,旧终端可能继续打印 `process didn't exit successfully: server-rs\target\debug\api-server.exe (exit code: 0xffffffff)`
- 原因Windows 不能覆盖仍在运行的 exe通常是上一条 `npm run dev:api-server` 链路仍在运行,进程树为 `npm run dev:api-server -> node scripts/dev.mjs api-server -> cargo run -> api-server.exe``0xffffffff` 常见于排障时用 `Stop-Process -Force` 强制结束旧 `api-server.exe` 后由 Cargo 回显,不一定代表新启动失败。
- 处理:先按目标路径确认并停止本仓库的旧 `api-server.exe` 及其父级 `cargo/node/cmd` 启动链路,再重新启动;不要同时开多个 `npm run dev:api-server`
- 验证:确认没有匹配 `C:\Genarrative\server-rs\target\debug\api-server.exe` 的进程后,`Remove-Item` 能删除旧 exe随后 `npm run dev:api-server` 启动并访问 `/healthz` 返回 200。
- 关联:`scripts/dev.mjs``server-rs/crates/api-server/src/main.rs`
## dev-rust-stack 端口被旧进程占用时会误判健康检查
## dev scheduler 端口被旧进程占用时会误判健康检查
- 现象:`node scripts/run-bash-script.mjs scripts/dev-rust-stack.sh --skip-spacetime` 输出 `Port 3000 is in use, trying another one...`,随后 `api-server.exe``AddrInUse` / `code: 10048`
- 现象:旧本地 dev 链路可能输出 `Port 3000 is in use, trying another one...`,随后 `api-server.exe``AddrInUse` / `code: 10048`
- 原因:旧 `api-server` 仍监听默认 `8082` 时,脚本的 `/healthz` 探测会命中旧进程并误判新服务已就绪;旧 Vite 占住 `3000`Vite 默认漂移到新端口,浏览器仍可能打开旧页面。
- 处理:`scripts/dev-rust-stack.sh` 已在 publish / 编译前预检 `api-server`、主站 Vite、后台 Vite 端口,并让 Vite 使用 `--strictPort`;遇到端口占用时按脚本打印的 PID 停止旧进程,或显式传入 `--api-port` / `--web-port` / `--admin-web-port`
- 验证:默认端口被占用时,完整栈应在发布模块前直接失败并打印监听进程;清理端口后重新启动不再漂移端口或命中旧 `/healthz`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
- 处理:`scripts/dev.mjs` 已在 publish / 编译前解析 SpacetimeDB、`api-server`、主站 Vite、后台 Vite 端口,并让 Vite 使用 `--strictPort`;遇到端口占用时会自动选择后续可用端口,也可显式传入 `--api-port` / `--web-port` / `--admin-web-port`
- 验证:默认端口被占用时,完整栈应打印 `[dev:ports] ... 不可用,改用 ...` 并把实际端口传给后续 publish、健康检查和 Vite 代理;清理端口后重新启动不再命中旧 `/healthz`
- 关联:`scripts/dev.mjs``docs/technical/DEV_RUST_STACK_PORT_CONFLICT_PRECHECK_2026-05-09.md`
## Windows debug 长 SSE Future 触发 api-server 断连
- 现象:前端 Vite 代理请求 `/api/runtime/creative-agent/sessions/{sessionId}/messages/stream``read ECONNRESET`,随后 `api-server.exe``0xffffffff` 退出,`dev:rust` 回收 SpacetimeDB、Vite 和后台 Vite。
- 现象:前端 Vite 代理请求 `/api/runtime/creative-agent/sessions/{sessionId}/messages/stream``read ECONNRESET`,随后 `api-server.exe``0xffffffff` 退出,`dev:spacetime` 回收 SpacetimeDB、Vite 和后台 Vite。
- 原因:单个 `async_stream::stream!` 中塞入 Agent 执行、外部模型请求、会话更新和大量 SSE 事件,会在 Windows debug 下生成很大的 Future真实消费 SSE body 时容易触发 worker 线程栈压力或进程级中断,单元测试若只测函数和路由状态会漏掉。
- 处理:长 SSE 路由优先使用 `tokio::spawn` 跑业务流程,通过 `mpsc` + `UnboundedReceiverStream` 向 Axum 返回轻量 stream失败时更新会话为 `failed` 并发送 SSE `error`,不要把大段执行逻辑内联到路由返回的 stream future 中。
- 验证:补充实际 `collect()` SSE body 的路由测试,确认首轮包含 `stage``puzzle_template_catalog``done`,且不会提前发送 `puzzle_template_selection` / `puzzle_cost_range`;再执行 `cargo check -p api-server``cargo test -p api-server creative_agent`,联调时用 `npm run api-server` 检查 `/healthz`
- 验证:补充实际 `collect()` SSE body 的路由测试,确认首轮包含 `stage``puzzle_template_catalog``done`,且不会提前发送 `puzzle_template_selection` / `puzzle_cost_range`;再执行 `cargo check -p api-server``cargo test -p api-server creative_agent`,联调时用 `npm run dev:api-server` 检查 `/healthz`
- 关联:`server-rs/crates/api-server/src/creative_agent.rs``server-rs/crates/api-server/src/app.rs`
## creative-agent 过程项不要把历史事件渲染成运行中
@@ -550,7 +526,7 @@
- 工具开始事件必须等同一 `toolCallId``tool_completed` 收口;兼容旧流时可按后续同名完成事件兜底。
- 思考摘要只展示用户可见摘要,且流结束或会话进入等待/完成/失败态后必须改成 done。
- 验证:前端测试断言完成后 `CreativeAgentProcessItem` 不再存在 `tone === 'active'`;后端测试确认工具开始/完成事件使用相同 `toolCallId`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
- 关联:`src/components/creative-agent/creativeAgentViewModel.ts``server-rs/crates/api-server/src/creative_agent.rs``docs/prd/CREATIVE_INTERACTIVE_AGENT_PHASE1_LANGCHAIN_RUST_PUZZLE_LOOP_PRD_2026-05-05.md`
## creative-agent 会话切换要清理本地待确认模板
@@ -566,15 +542,15 @@
- 原因前端上传与预览容易混在一起若不走平台资产对象SpacetimeDB 和长期草稿会被大文本或大二进制污染。
- 处理VN 资产统一用 `/api/assets/direct-upload-tickets`、OSS 直传、`/api/assets/objects/confirm`,长期状态只保存 `assetObjectId``/generated-*` 引用;运行时图片用 `ResolvedAssetImage` 换签。
- 验证:文档模式 `sourceAssetIds` 为平台资产 id草稿中不出现 `data:`;图片和音乐字段为平台 generated 引用或 null。
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
- 关联:`docs/prd/AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md``src/services/visual-novel-creation/visualNovelAssetClient.ts`
## 视觉小说 VN-13 交接时不要再回头找旧迁移方案
- 现象:接手视觉小说的人容易重新打开旧 TXT 迁移文档,把“外部平台工程迁入”误当成当前实现目标。
- 原因:视觉小说历史资料里保留了很多迁移阶段的讨论,而当前真正的实现口径已经收口到 PRD、表目录、Prompt 工具说明、实现收口文档和负向扫描报告。
- 处理:维护视觉小说时优先看当前玩法链路文档、当前后端架构文档,并运行 `npm run check:visual-novel-vn11` / `npm run check:visual-novel-vn12` 确认负向扫描和全链路验收
- 处理:维护视觉小说时优先看 `AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md``SPACETIMEDB_TABLE_CATALOG.md``VISUAL_NOVEL_PROMPT_AND_LLM_TOOLS_VN03_2026-05-05.md``VISUAL_NOVEL_IMPLEMENTATION_HANDOFF_2026-05-07.md``VISUAL_NOVEL_HANDOFF_AND_MAINTENANCE_2026-05-07.md``VN11_NEGATIVE_SCAN_REPORT_2026-05-07.md`
- 验证:新开发者只读这组文档即可继续维护,不需要把旧 TXT 迁移方案重新当作编码依据。
- 关联:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
- 关联:`docs/prd/AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md``docs/technical/VISUAL_NOVEL_IMPLEMENTATION_HANDOFF_2026-05-07.md``docs/experience/VISUAL_NOVEL_HANDOFF_AND_MAINTENANCE_2026-05-07.md`
## 视觉小说公开广场不要触发登录刷新
@@ -582,7 +558,7 @@
- 原因:公开只读接口如果复用默认 `requestJson` 选项,缺少 access token 时会先走静默 refresh。
- 处理:视觉小说公开广场列表使用 `skipAuth: true``skipRefresh: true`;鉴权 mutation 仍保持默认鉴权链路。
- 验证:执行 `src/services/visual-novel-runtime/visualNovelRuntimeClient.test.ts`,确认 `/api/runtime/visual-novel/gallery` 请求携带 `skipAuth` / `skipRefresh`,而 run、重生成和存档 mutation 仍走受保护路由。
- 关联:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
- 关联:`src/services/visual-novel-runtime/visualNovelRuntimeClient.ts``docs/prd/AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md`
## 创作 Tab 语义迁移后,旧“新建作品”测试要改看智能创作首页
@@ -598,7 +574,7 @@
- 原因workspace default-members 当前只包含 `crates/api-server`SpacetimeDB module 有独立构建/发布方式。
- 处理:默认 Rust 构建只覆盖原生 `api-server`;本地模块发布继续走 `spacetime publish --module-path ... --build-options="--debug"` / bindings 生成流程。
- 验证:查看 `server-rs/Cargo.toml` default-members并按相关 SpacetimeDB 文档执行模块构建。
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
- 关联:`server-rs/Cargo.toml``docs/technical/RUST_WORKSPACE_DEFAULT_BUILD_SCOPE_FIX_2026-04-25.md`
## Windows 原生 `spacetime-module` 单测会链接缺失 SpacetimeDB 宿主符号
@@ -606,7 +582,7 @@
- 原因:`spacetime-module` 依赖的 SpacetimeDB runtime API 面向 wasm 宿主环境,原生 test exe 链接不到这些宿主导出。
- 处理:日常语法和类型验证使用 `cargo check -p spacetime-module --manifest-path server-rs/Cargo.toml`;需要验证模块行为时走 SpacetimeDB publish/dev 或模块域纯 Rust crate 的单测,不把该原生链接错误当作业务测试失败。
- 验证:`cargo check -p spacetime-module --manifest-path server-rs/Cargo.toml` 能通过;原生 `cargo test` 若仍报上述宿主符号缺失,按当前限制记录为未执行。
- 关联:`server-rs/crates/spacetime-module``docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`
- 关联:`server-rs/crates/spacetime-module``docs/technical/SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md`
## Rust 构建不要让不可用的 sccache 阻断 rustc
@@ -614,15 +590,15 @@
- 原因环境、Jenkinsfile 或 `server-rs/.cargo/config.toml` 启用了 `sccache` wrapper但当前 agent 没有可执行的 `sccache`、PATH 中 shim 损坏,或本地 sccache server/client 通道状态损坏。Windows 本机若配置了 `SCCACHE_OSS_*`sccache daemon 冷启动会先经 OSS/本机代理完成缓存读写检查,再监听 `127.0.0.1:4226`;代理或 OSS 链路慢时Cargo 的 `sccache rustc -vV` 可能先超时。
- 处理:保留 `server-rs/.cargo/config.toml``rustc-wrapper = "sccache"`Windows 本机优先在 `%APPDATA%\Mozilla\sccache\config\config` 写入 `server_startup_timeout_ms = 60000`,拉长 client 等待 daemon 完成 OSS 初始化的时间,然后删除 `server-rs/target/.rustc_info.json` 里缓存的失败探测结果并重跑原始 Cargo 命令。冷启动验证优先用 `sccache --stop-server`,不要在另一个 `cargo` / `rustc` 仍在编译时 `taskkill /F /IM sccache.exe /T`,否则 proc-macro crate 可能被打断并表现为 `serde_derive` / `spacetimedb-bindings-macro``sccache ... exit code: 1`。若只做临时排障,可在 Git Bash 中执行 `RUSTC_WRAPPER= CARGO_BUILD_RUSTC_WRAPPER= cargo build ...`,或在 PowerShell 用 `cargo check -p api-server --config "build.rustc-wrapper=''"` 一次性绕过 wrapper生产流水线必须先实际执行 `sccache --version`,失败时移除 `RUSTC_WRAPPER` 并回退到直接 `rustc`
- 验证:`rustc -Vv` 能输出版本;冷启动后原始 `cargo check -p api-server``cargo check -p spacetime-module` 能通过;`sccache --show-stats` 显示 `Cache location oss, name: genarrative-sccache`,证明仍在使用 sccache/OSS 缓存Jenkins 日志出现“未找到可用 sccache改用 rustc 直接构建”后仍继续真实构建。
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
- 关联:`scripts/dev.mjs``jenkins/Jenkinsfile.production-stdb-module-build``docs/technical/SPACETIMEDB_PUBLISH_SCCACHE_FALLBACK_2026-05-09.md``docs/technical/PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md`
## 生产发布入口不要沿用旧 Jenkinsfile / 一体化脚本
- 现象:部署、回滚或 Jenkins Job 重建时参考旧发布文档,导致 systemd、Nginx、SpacetimeDB 自托管和生产包拆分不一致。
- 原因:旧 Jenkins / 旧本地远端部署脚本文档仍作为历史经验保留。
- 处理:生产相关操作先看 `docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`,再按需追溯旧文档。
- 处理:生产相关操作先看 `PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md`,再按需追溯旧文档。
- 验证:发布链路使用当前 `deploy/systemd``deploy/nginx``scripts/deploy``jenkins/Jenkinsfile.production-*`
- 关联:`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
- 关联:`docs/technical/PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md`
## Release Web 产物通过内网 rsync 拉取
@@ -630,7 +606,7 @@
- 原因Web 大包为了避免从 Linux 构建机拉回 Jenkins controller默认留在构建机稳定缓存目录development 目标与构建机同机可直接读取release 目标是独立机器,需要内网同步。
- 处理release 服务器的 Jenkins 运行用户配置 SSH Host `genarrative-build-internal` 指向构建机内网地址,`Genarrative-Web-Deploy``DEPLOY_TARGET=release` 且本地缺少大包时默认执行 `rsync` 拉取同一路径内容;真实内网 IP、用户和私钥路径只放在服务器本机 SSH config不写入 Jenkinsfile。
- 验证:在 release 服务器上先手工跑通 `rsync -av --progress "genarrative-build-internal:${SRC}/" "${DST}/"`,再运行 Web Deploy流水线会继续执行 `web.tar.gz.sha256` 校验。
- 关联:`jenkins/Jenkinsfile.production-web-deploy``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
- 关联:`jenkins/Jenkinsfile.production-web-deploy``docs/technical/PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md`
## Jenkins 生产流水线拉 Git 先本机再域名备用
@@ -638,7 +614,7 @@
- 原因:`127.0.0.1` 只代表当前执行阶段的 agent 自身;当 release agent 与 Git 服务不在同一台机器,或本机 Git/Web 服务临时不可用时,固定写死 localhost 会阻断 Jenkinsfile 内部源码/脚本 checkout。即使只使用域名 Git如果 `GitSCM` 没有显式 refspec 并开启 `CloneOption honorRefspec=true`Jenkins Git 插件也会拉取所有分支。
- 处理Jenkins Job 的 `Pipeline script from SCM` 由 Windows controller 执行SCM URL 使用公网域名 `https://git.genarrative.world/GenarrativeAI/Genarrative.git`。运行于 Linux agent 的 Jenkinsfile 首次 `checkout([$class: 'GitSCM', ...])` 层先尝试 `GIT_REMOTE_URL=http://127.0.0.1:3000/GenarrativeAI/Genarrative.git`,失败后直接尝试 `GIT_REMOTE_FALLBACK_URL=https://git.genarrative.world/GenarrativeAI/Genarrative.git`,不再配置内网 IP fallback所有生产 Jenkinsfile 的首次 checkout 都必须使用目标分支 refspec、`CloneOption shallow=true depth=1 noTags=true honorRefspec=true`。后续统一走 `scripts/jenkins-checkout-source.sh`,该脚本也按主地址、域名备用地址顺序重新 fetch 并把 `origin` 切到实际可用地址;`COMMIT_HASH` 为空时继续 `--depth=1 --no-tags`,只有指定 commit 时才允许加深历史做分支归属校验。
- 验证:扫描本地 Jenkins live job `config.xml`,确认 SCM `<url>` 都是 `https://git.genarrative.world/GenarrativeAI/Genarrative.git`;扫描所有生产 Jenkinsfile 的首次 `GitSCM checkout`,确认 `userRemoteConfigs``+refs/heads/${params.SOURCE_BRANCH}:refs/remotes/origin/${params.SOURCE_BRANCH}``CloneOption``honorRefspec: true`;运行 `bash -n scripts/jenkins-checkout-source.sh`
- 关联:`jenkins/Jenkinsfile.production-full-build-and-deploy``jenkins/Jenkinsfile.production-web-build``jenkins/Jenkinsfile.production-api-build``jenkins/Jenkinsfile.production-stdb-module-build``jenkins/Jenkinsfile.production-web-deploy``jenkins/Jenkinsfile.production-api-deploy``jenkins/Jenkinsfile.production-stdb-module-publish``jenkins/Jenkinsfile.production-server-provision``jenkins/Jenkinsfile.production-database-export``jenkins/Jenkinsfile.production-database-import``scripts/jenkins-checkout-source.sh``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
- 关联:`jenkins/Jenkinsfile.production-full-build-and-deploy``jenkins/Jenkinsfile.production-web-build``jenkins/Jenkinsfile.production-api-build``jenkins/Jenkinsfile.production-stdb-module-build``jenkins/Jenkinsfile.production-web-deploy``jenkins/Jenkinsfile.production-api-deploy``jenkins/Jenkinsfile.production-stdb-module-publish``jenkins/Jenkinsfile.production-server-provision``jenkins/Jenkinsfile.production-database-export``jenkins/Jenkinsfile.production-database-import``scripts/jenkins-checkout-source.sh``docs/technical/PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md`
## Jenkins 可选参数在 set -u 下不能裸读
@@ -646,7 +622,7 @@
- 原因Jenkins string/boolean 参数留空时不一定会导出同名环境变量,而生产数据库导入导出脚本块启用了 `set -u`
- 处理:进入 Bash 执行块后先使用 `${VAR:-}``${VAR:-默认值}` 收敛成本地变量;必填项使用 `${VAR:?中文错误}` 明确失败原因。
- 验证:扫描 `jenkins/Jenkinsfile.production-database-export``jenkins/Jenkinsfile.production-database-import`,确认 `INCLUDE_TABLES``CHUNK_SIZE``SERVER_BACKUP_DIRECTORY``SMOKE_HEALTH_URL` 等可选参数不再裸读。
- 关联:`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md``jenkins/Jenkinsfile.production-database-export``jenkins/Jenkinsfile.production-database-import`
- 关联:`docs/technical/PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md``jenkins/Jenkinsfile.production-database-export``jenkins/Jenkinsfile.production-database-import`
## 个人任务 scope 不得扩成 work/site/module
@@ -654,7 +630,7 @@
- 原因:首版个人任务只支持用户维度,非 user scope 会造成任务进度读取语义错误。
- 处理Admin 任务配置页不展示范围选择,保存时固定 `scopeKind: 'user'`API 和领域构造层拒绝非 `User`
- 验证:非 `user` scope 返回错误;相关测试覆盖 `Site` / `Module` / `Work` 被拒绝。
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
- 关联:`docs/technical/RUNTIME_PROFILE_TASK_SCOPE_2026-05-04.md``docs/technical/ANALYTICS_DATE_DIMENSION_IMPLEMENTATION_2026-05-04.md`
## 拼图发布 409 不一定是接口故障
@@ -662,7 +638,7 @@
- 原因:`publish_puzzle_work` 是资产操作发布入口,发布前会预扣 `1` 枚泥点;余额不足时后端按业务冲突返回 `409 CONFLICT``details.message``泥点余额不足`
- 处理:前端发布弹窗在用户点击发布后必须保留并展示后端业务错误,不能只把错误写到弹窗背后的页面 banner。
- 验证:`PuzzleResultView` 单测覆盖发布弹窗内展示 `泥点余额不足`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
- 关联:`src/components/puzzle-result/PuzzleResultView.tsx``docs/technical/PUZZLE_RESULT_AUTOSAVE_AND_TAG_GATE_FIX_2026-04-28.md``docs/technical/ASSET_GENERATION_POINTS_CONSUMPTION_2026-04-27.md`
## WebGL 画布在高 DPR 移动端放大溢出
@@ -670,7 +646,7 @@
- 原因:`WebGLRenderer.setPixelRatio(...)` 会把绘图缓冲区乘上设备 DPR如果没有给 `renderer.domElement` 单独设置 CSS `width/height: 100%` 和绝对铺满,浏览器可能把高 DPR 缓冲区尺寸当成页面显示尺寸。
- 处理:中心棋盘和托盘预览的 WebGL canvas 统一套用 `position:absolute; inset:0; width:100%; height:100%; display:block``renderer.setSize(..., false)` 只负责同步绘图缓冲区。
- 验证:强制移动端 `390x844`、DPR 2 截图确认棋盘左右边界在视口内canvas CSS 尺寸等于容器尺寸,内部 `width/height` 属性可大于 CSS 尺寸。
- 关联:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
- 关联:`src/components/match3d-runtime/Match3DPhysicsBoard.tsx``docs/technical/MATCH3D_RUNTIME_3D_GEOMETRY_EXPERIMENT_2026-05-02.md`
## Hyper3D subscriptionKey 不要按固定短文本限长
@@ -678,7 +654,7 @@
- 原因:`subscriptionKey` 是 Hyper3D 返回的 opaque token长度由上游决定后端状态查询曾复用普通文本校验把它限制在 256 字符。
- 处理:`query_task_status``subscriptionKey` 只做 trim 和非空校验,不做固定长度限制;前端临时任务和 Match3D 草稿响应可继续展示该 token但不要把它当作可编辑短文本。
- 验证:`cargo test -p api-server accepts_opaque_subscription_key_without_length_cap --manifest-path server-rs/Cargo.toml`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`
- 关联:`server-rs/crates/api-server/src/hyper3d_generation.rs``docs/technical/HYPER3D_RODIN_GEN2_MODEL_GENERATION_2026-05-08.md`
## 抓大鹅新草稿不要再接回 Rodin 或 GLB 生成
@@ -686,7 +662,7 @@
- 原因:仓库里保留了 Hyper3D 通用代理和历史模型字段,旧文档也曾要求草稿阶段同步生成 GLB。当前产品口径已经改为 2D 多视角素材。
- 处理:新 `match3d_compile_draft` 与批量新增只生成 2D 图片:每个物品 5 个视角,单张 1K 素材图固定 5x5最多承载 5 个物品,一行对应一个物品,不足 5 个物品也补齐到完整 5 行;超过 5 个物品自动分批并行生图。素材图 prompt 固定要求纯绿色绿幕背景,切割前先把绿幕处理为透明 alpha再做格内内容前景边界校准并带留白避免固定内缩切掉贴近格线的主体。`generatedItemAssets[].status` 使用 `image_ready`,发布校验看 `imageViews[]` 或首图引用。`generated-models` 仅用于历史外部模型链接转存,不能作为新生产链路。
- 验证:`cargo test -p api-server match3d --manifest-path server-rs/Cargo.toml``npm run test -- src\services\miniGameDraftGenerationProgress.test.ts src\components\match3d-result\Match3DResultView.test.tsx src\components\match3d-runtime\Match3DRuntimeShell.test.tsx`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
- 关联:`server-rs/crates/api-server/src/match3d.rs``src/components/match3d-runtime/Match3DRuntimeShell.tsx``docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`
## 抓大鹅切图路径不能只用中文物品名
@@ -694,7 +670,7 @@
- 原因:中文物品名经过 OSS 路径段清洗后都可能退化成 `item`,多张切割图片写到同一个 object key后写入覆盖先写入。
- 处理:切割图上传路径必须带稳定唯一 `itemId` 前缀,例如 `items/match3d-item-1-item/views/view-01.png`;运行态读取 generated 私有图片时通过同源 `/api/assets/read-url` 换签,不直接请求裸 OSS 路径。
- 验证:后端单测覆盖中文名路径唯一,前端运行态测试覆盖 generated 图片源解析。
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
- 关联:`server-rs/crates/api-server/src/match3d.rs``src/components/match3d-result/Match3DResultView.tsx``docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`
## 抓大鹅生成素材不能只挂在 compile response
@@ -702,7 +678,7 @@
- 原因:`generatedItemAssets` 如果只附加在 `match3d_compile_draft` 的 HTTP response draft 上,刷新或重进时 `getMatch3DWorkDetail` 只能读取 SpacetimeDB 中的 `match3d_work_profile`;旧 mapper 返回空数组,自然无法恢复素材。拼图链路已经通过 `save_puzzle_generated_images` 把候选图和 levels 写回 work profile抓大鹅也必须同样写持久字段。
- 处理compile 成功时把独立物品图片列表序列化写入 `match3d_work_profile.generated_item_assets_json``update_match3d_work` / `publish_match3d_work` 保留该字段API work summary/detail 映射反序列化为 `generatedItemAssets`。前端保持“本次 draft 优先,重进 profile 兜底”的读取顺序。
- 验证:`cargo test -p spacetime-client match3d --manifest-path server-rs/Cargo.toml``cargo test -p api-server match3d --manifest-path server-rs/Cargo.toml``npm run test -- src/components/match3d-result/Match3DResultView.test.tsx`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
- 关联:`server-rs/crates/spacetime-module/src/match3d/*``server-rs/crates/spacetime-client/src/mapper.rs``server-rs/crates/api-server/src/match3d.rs``src/components/match3d-result/Match3DResultView.tsx``docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`
## 抓大鹅试玩和正式运行态不要只读草稿页本地素材预览
@@ -710,7 +686,7 @@
- 原因:结果页本地 `assetDrafts` 和作品 profile 的 `generatedItemAssets` 可能不同步;推荐流内嵌运行态若只读卡片摘要,卡片缺素材时会把已持久化 profile 素材丢掉;点击试玩时 React state 异步更新也可能让运行态第一帧读取旧 `match3dProfile`
- 处理:删除、批量新增、音效生成或封面引用物品素材后,都把当前 `generatedItemAssets` 写回作品 profile`Match3DResultView` 合并同 `itemId` 的 draft/profile 素材,用 profile 已有 `imageViews[]`、首图引用、`backgroundMusic``backgroundAsset` 补齐旧 draft点击试玩前把试玩可用物品种类通过 `itemTypeCountOverride` 降到已生成 2D 素材数量;推荐流内嵌运行态启动前若卡片摘要没有物品图片素材,补读 `getMatch3DWorkDetail(profileId)` 并把详情资产传给 `Match3DRuntimeShell``PlatformEntryFlowShellImpl` 需要维护 `match3dRuntimeProfile`,在 `startMatch3DRunFromProfile` 创建 run 后立即锁定本次完整 profileruntime 渲染时优先按 `run.profileId` 使用这份 profile而不是等待普通 `match3dProfile` state 下一轮刷新。同 profile 下已有 `generatedItemAssets` 时不能因为图片完整性判断失败就覆盖为空数组。判断是否需要补读详情时只看 `imageViews[]``imageSrc/imageObjectKey`;背景、音乐、容器 UI 是附属运行态资产,不能单独证明物品素材已完整。
- 验证:执行 `npm run test -- src/components/match3d-result/Match3DResultView.test.tsx``npm run test -- src/components/match3d-runtime/Match3DRuntimeShell.test.tsx``npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx`,并检查历史草稿和公开 M3 作品的 Network 响应里 `generatedItemAssets[].imageViews/imageSrc/imageObjectKey`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
- 关联:`src/components/match3d-result/Match3DResultView.tsx``src/components/platform-entry/PlatformEntryFlowShellImpl.tsx``src/components/match3d-runtime/Match3DPhysicsBoard.tsx``docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`
## 抓大鹅 UI 背景和容器只在顶层字段时也要传进运行态
@@ -718,22 +694,7 @@
- 原因:部分链路把 UI 资产只放在作品顶层 `generatedBackgroundAsset` / `backgroundImageObjectKey`,没有同步放进首个 `generatedItemAssets[].backgroundAsset`;如果运行态入口只传 `generatedItemAssets``backgroundImageSrc``Match3DRuntimeShell` 就拿不到 `containerImageObjectKey`
- 处理:`PlatformMatch3DGalleryCard``mapPublicWorkDetailToMatch3DWork``resolveMatch3DRuntimeGeneratedBackgroundAsset``Match3DRuntimeShell` 都必须保留并传递顶层 `generatedBackgroundAsset`;运行态背景读取顺序为 `backgroundImageSrc` / 顶层 `generatedBackgroundAsset.image*` / `generatedItemAssets[].backgroundAsset.image*`,容器读取顺序为顶层 `generatedBackgroundAsset.containerImage*` / `generatedItemAssets[].backgroundAsset.containerImage*`
- 验证:执行 `npm run test -- src/components/match3d-runtime/Match3DRuntimeShell.test.tsx``npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "Match3D runtime"`;浏览器 Network 中背景和容器 generated path 应先请求 `/api/assets/read-url` 换签,局内出现 `match3d-background-image``match3d-container-image` 对应图片。
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## 抓大鹅 UI 背景和容器只在物品挂载字段时也要提升
- 现象草稿恢复、结果页素材配置、UI 预览或试玩时仍显示默认背景 / 默认容器,但 work profile 的首个 `generatedItemAssets[].backgroundAsset` 里已经有生成的背景和容器图。
- 原因UI 背景和容器资产没有独立表字段,后端持久化常落在 `generatedItemAssets[].backgroundAsset`;如果 session 映射、结果页 profile、推荐运行态详情补读后不提升到顶层 `generatedBackgroundAsset``backgroundImageSrc`,后续组件会误判“没有生成 UI 资产”。
- 处理Agent session 返回前要用持久化 work profile 资产回填 draft前端进入结果页、构建草稿 profile、推荐 / 公开作品启动运行态前,都要把 `generatedItemAssets[].backgroundAsset` 提升为顶层背景字段。容器图在运行态和 UI 预览复用同一套居中 `object-contain` 样式,移动端宽度接近屏宽,只有缺失或加载失败时才使用透明参考图兜底。
- 验证:`cargo test -p api-server match3d_agent_session_response_hydrates_persisted_ui_assets --manifest-path server-rs/Cargo.toml``npm run test -- src/components/match3d-result/Match3DResultView.test.tsx src/components/match3d-runtime/Match3DRuntimeShell.test.tsx`
## 抓大鹅重启时不要清空 generated 图片签名缓存
- 现象:抓大鹅进入局内时背景或生成物品首帧缺失;点击右上角重启后,局内短暂显示默认积木,过一段时间才换回实际生成素材。
- 原因:`Match3DRuntimeShell` 在 run / resources 变化时清空 `resolvedImageSources`,导致同一批 generated 私有图每次重启都重新换签;换签未完成期间渲染层把空图片当成“没有生成素材”,直接落到默认积木兜底。另一个常见遗漏是运行态预加载只覆盖物品图,没覆盖顶层或物品挂载的 UI 背景和容器图。
- 处理:运行态解析 generated 图片时按源列表保留已有签名 URL只移除不再使用的源generated 图仍在换签时先隐藏对应物品,不显示默认积木,只有没有生成源或换签失败后才使用兜底。`preloadMatch3DGeneratedRuntimeAssets` 必须同时预加载物品视角、纯背景和容器 UI推荐流卡片摘要缺物品图或 UI 背景 / 容器字段时,进入运行态前补读 `getMatch3DWorkDetail`
- 验证:执行 `npm run test -- src/components/match3d-runtime/Match3DRuntimeShell.test.tsx -t "generated 图片素材换签未完成前不显示默认积木|同一批 generated 图片素材在重启 run 时保留已解析地址|运行态会从顶层 UI 资产加载背景和容器图"``npm run test -- src/services/match3dGeneratedModelCache.test.ts``npm run typecheck``npm run check:encoding`
- 关联:`src/components/match3d-runtime/Match3DRuntimeShell.tsx``src/services/match3dGeneratedModelCache.ts``src/components/platform-entry/PlatformEntryFlowShellImpl.tsx``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
- 关联:`src/components/match3d-runtime/Match3DRuntimeShell.tsx``src/components/platform-entry/PlatformEntryFlowShellImpl.tsx``src/components/rpg-entry/rpgEntryWorldPresentation.ts``docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`
## 抓大鹅容器参考图必须走 edits 并接管棋盘外观
@@ -741,7 +702,7 @@
- 原因:`/v1/images/generations``image` 数组更适合弱参考文生图,难以稳定锁定大尺寸轻俯视容器构图;即使生成了容器图,如果运行态继续保留默认 `rounded-full` 锅壳和 `overflow-hidden`,生成图也会被默认视觉覆盖或裁掉。
- 处理:抓大鹅 `1:1` 容器 UI 图必须用 VectorEngine `POST /v1/images/edits` multipart`public/match3d-background-references/pot-fused-reference.png` 作为 `image` part 上传;共享 GPT-image-2 HTTP client 承载 multipart 时强制 HTTP/1.1。`Match3DRuntimeShell` 在容器图换签并成功加载后,把棋盘外壳切为透明和 `overflow-visible`,只在容器缺失或加载失败时使用默认圆形容器。
- 验证:执行 `cargo test -p api-server vector_engine --manifest-path server-rs/Cargo.toml``cargo test -p api-server match3d_background --manifest-path server-rs/Cargo.toml``npm run test -- src/components/match3d-runtime/Match3DRuntimeShell.test.tsx src/components/match3d-result/Match3DResultView.test.tsx`;真实联调看容器生成请求是否命中 `/v1/images/edits`,局内 `match3d-container-image` 是否渲染且 `match3d-board` 不再含默认 `rounded-full`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
- 关联:`server-rs/crates/api-server/src/openai_image_generation.rs``server-rs/crates/api-server/src/match3d.rs``src/components/match3d-runtime/Match3DRuntimeShell.tsx``docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`
## 抓大鹅结果页音频试听也要先换签
@@ -749,7 +710,7 @@
- 原因:结果页试听控件和运行态一样运行在浏览器里,不能直接读取 generated 私有对象;只在运行态换签会造成“运行态可能有声,结果页不能预览”的割裂。
- 处理:结果页音频控件统一通过 `useResolvedAssetReadUrl` / `/api/assets/read-url` 取得签名 URL 后再传给 `<audio>`;换签失败时只显示“音频已绑定”,不要回退请求裸 generated path。
- 验证:`npm run test -- src/components/match3d-result/Match3DResultView.test.tsx` 覆盖背景音乐和点击音效试听使用签名 URL。
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
- 关联:`src/components/match3d-result/Match3DResultView.tsx``src/services/assetReadUrlService.ts``docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`
## 法律文档弹窗通过 portal 挂载时要显式带平台主题
@@ -764,7 +725,7 @@
- 原因:完成回调用 `selectionStageRef.current` 判断用户是否仍在生成页;如果执行 compile 前只调用 `setSelectionStage('*-generating')`action 很快返回时 ref 仍可能是旧 stage。
- 处理:进入各玩法生成页时同步写 `selectionStageRef.current = '*-generating'`,再调用 `setSelectionStage('*-generating')`。这不是为渲染服务,而是给同一异步链路里的完成回调提供即时事实。
- 验证:`npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx` 覆盖抓大鹅和拼图生成后自动试玩 / 返回结果页。
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
- 关联:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx``docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`
## 微信支付回调验签不要用商户私钥
@@ -773,7 +734,7 @@
- 处理api-server 真实微信支付配置同时需要商户私钥与微信平台公钥:`WECHAT_PAY_PRIVATE_KEY_*` 用于签名,`WECHAT_PAY_PLATFORM_PUBLIC_KEY_*``WECHAT_PAY_PLATFORM_SERIAL_NO` 用于通知验签,`WECHAT_PAY_API_V3_KEY` 只用于解密通知 resource。支付成功后只通过通知里的 `out_trade_no` 确认本地 pending 订单,并保存 `transaction_id``profile_recharge_order.provider_transaction_id`
- APIv3 通知成功应答使用 HTTP `204 No Content`,不要沿用 V2 XML 成功报文;失败仍返回 4XX/5XX 让微信重试。
- 验证mock 通知测试只能覆盖本地回调推进;真实环境还需用微信支付平台公钥、真实通知头和 API v3 密钥验证签名与解密链路。
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
- 关联:`server-rs/crates/api-server/src/wechat_pay.rs``docs/technical/MY_TAB_ACCOUNT_RECHARGE_IMPLEMENTATION_2026-04-25.md`
## 微信支付 JSAPI 下单必须显式带 User-Agent
@@ -781,7 +742,7 @@
- 原因:`reqwest` 请求即使已设置 `Accept: application/json`,也不会默认附带业务侧 `User-Agent`;微信支付网关会校验这两个头。
- 处理:`api-server` 的 JSAPI 下单请求统一通过 `with_wechat_pay_jsapi_headers(...)` 设置 `Accept: application/json``Content-Type: application/json``User-Agent: Genarrative-WechatPay/1.0`
- 验证:执行 `cargo test -p api-server jsapi_order_request_sets_wechat_required_http_headers --manifest-path server-rs/Cargo.toml`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
- 关联:`server-rs/crates/api-server/src/wechat_pay.rs``docs/technical/MY_TAB_ACCOUNT_RECHARGE_IMPLEMENTATION_2026-04-25.md`
## 后台表查询展示 SpacetimeDB 枚举时不要套用 Option 解码
@@ -789,7 +750,7 @@
- 原因SpacetimeDB HTTP SQL 对无载荷枚举会返回 SATS 形态 `[variant_index, []]`;后台通用 normalizer 曾把任何 `[0, value]` 都当作 `Option::Some(value)` 展开,导致 `[0, []]` 最终只剩 `[]`
- 处理:通用表查询解析应先按表名和列名识别已知业务枚举,再落回 Option / Timestamp 通用展开;例如 `profile_recharge_order.kind` 映射为 `points` / `membership``profile_recharge_order.status` 映射为 `pending` / `paid` / `failed` / `closed` / `refunded`
- 验证:执行 `cargo test -p api-server admin_database -- --nocapture`,并确认后台详情弹层的 `raw` 与表格 `cells` 都显示业务字符串。
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
- 关联:`server-rs/crates/api-server/src/admin.rs``docs/technical/ADMIN_DATABASE_TABLE_QUERY_2026-05-08.md`
## 抓大鹅历史草稿外部 Rodin GLB 链接必须转存后再试玩或发布
@@ -797,7 +758,7 @@
- 原因:历史结果页手动 `重新生成` 会把 Hyper3D/Rodin 的外部 CDN 下载链接直接保存到 `generatedItemAssets[].modelSrc`,同时 `modelObjectKey` 为空。外部链接可能过期、跨域、返回 HTML 错误页或非 GLB 内容;前端预览和运行态不能把它当作稳定私有资产。
- 处理:该问题只适用于旧数据。结果页发现 `status = model_ready``modelSrc = https://...` 且无 `modelObjectKey` 时,可调用 `POST /api/creation/match3d/works/{profileId}/generated-models` 做一次性转存;新草稿和批量新增不得继续生成或依赖 GLB。若历史半修复数据同时保留外部 `modelSrc` 和平台 `modelObjectKey`,旧模型预览读取层优先用 `modelObjectKey`
- 验证:`npm run test -- src\components\match3d-result\Match3DResultView.test.tsx``npm run test -- src\components\match3d-runtime\Match3DRuntimeShell.test.tsx``npm run test -- src\components\rpg-entry\RpgEntryFlowShell.agent.interaction.test.tsx``cargo test -p api-server match3d_model_download --manifest-path server-rs\Cargo.toml`,并检查修复后响应中的 `generatedItemAssets[].modelObjectKey` 不为空。
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
- 关联:`server-rs/crates/api-server/src/match3d.rs``src/components/match3d-result/Match3DResultView.tsx``src/components/match3d-result/Match3DModelPreview.tsx``src/components/match3d-runtime/Match3DPhysicsBoard.tsx``docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`
## 抓大鹅难度配置的物品种类和消除次数必须分离
@@ -805,7 +766,7 @@
- 原因:旧运行态把消除次数和类型数量绑在一起,结果页文案又同时展示“素材图片 / 局内类型”,导致前端、发布校验和 run start 口径不一致。
- 处理:统一使用 `物品种类` 口径:轻松 3、标准 9、进阶 15、硬核 21历史 `clearCount=20` 且难度为硬核的运行态按新硬核升为 21 组三消,避免 20 组却要求 21 种素材。发布前按 `image_ready` 且有 `imageViews[]``imageSrc/imageObjectKey` 的生成素材数量阻断不足难度;试玩不阻断,但通过 `itemTypeCountOverride` 自动降到已生成 2D 素材数量。重启从已有 run 快照反推实际物品种类,保持同一局重开不变。
- 验证:`npm run test -- src\components\match3d-result\Match3DResultView.test.tsx``cargo test -p module-match3d --manifest-path server-rs\Cargo.toml`,涉及发布 reducer 时补跑 `cargo test -p spacetime-module match3d --manifest-path server-rs\Cargo.toml`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
- 关联:`src/components/match3d-result/Match3DResultView.tsx``src/services/match3d-runtime/match3dRuntimeClient.ts``server-rs/crates/module-match3d/src/application.rs``server-rs/crates/spacetime-module/src/match3d/mod.rs``docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`
## 抓大鹅标签清洗不要把 `3D素材` 当编号剥掉
@@ -819,17 +780,9 @@
- 现象:抓大鹅生成的物品视角图裁剪后仍带白边,或者整块纯绿色绿幕背景没有被透明化,运行态看到绿色方块。
- 原因:素材 sheet 可能是“每格内部绿幕、整张图外圈近白底”,内部绿幕不一定连通到 sheet 外边缘;旧 flood fill 只从外边缘找背景会漏掉这种绿幕块。白底抗锯齿如果不纳入抠像和边缘去污染,也会随裁剪输出成一圈白边。即使顺序已是先整张 sheet 去绿再裁剪,较厚的半透明或混色软绿边仍可能低于高置信绿幕阈值,被当作前景带进独立 PNG。
- 处理:`api-server``slice_match3d_material_sheet` 必须先在整张 sheet 上做透明背景后处理:外边缘连通绿幕/近白底清 alpha非连通但高置信纯绿块也清 alpha沿整张 sheet 透明背景继续吃掉软绿边;每个视角单图还要以扩大的 PNG 边界带为种子,把连通的浅绿 / 近白抗锯齿边直接改为透明,再按剩余可见主体收紧裁边,同时保护不够纯的绿色主体像素。不要改成先裁剪单格再去绿。
- 处理:`api-server``slice_match3d_material_sheet` 必须先在整张 sheet 上做透明背景后处理:外边缘连通绿幕/近白底清 alpha非连通但高置信纯绿块也清 alpha沿整张 sheet 透明背景继续吃掉软绿边,边缘近白和绿幕抗锯齿做透明或去污染;同时保护不够纯的绿色主体像素。不要改成先裁剪单格再去绿。
- 验证:`cargo test -p api-server match3d_material_sheet_slicing --manifest-path server-rs\Cargo.toml` 覆盖非连通绿幕、白边、贴边主体保留和固定 5x5 切图。
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
## 抓大鹅背景 UI 图出现透明区域先查背景不透明后处理
- 现象:抓大鹅生成的 `9:16` 局内背景 UI 图边缘、角落或局部出现透明 alpha运行态可能露出底色或透明棋盘底。
- 原因:背景图和中心容器图是两张资产;容器图需要透明 alpha但背景图是整屏底图。如果只靠提示词约束生图服务仍可能返回带透明通道的 PNG。
- 处理:`generate_match3d_background_image` 上传背景前必须调用 `make_match3d_background_image_opaque()`,把所有半透明 / 全透明像素合成到不透明底色;不要把这条逻辑套到容器图,容器图仍由 `make_match3d_container_image_transparent()` 保持透明。
- 验证:`cargo test -p api-server match3d_background_image_postprocess_removes_transparent_pixels --manifest-path server-rs\Cargo.toml`,并确认背景提示词包含“全画幅不透明”和“透明 alpha”禁用约束。
- 关联:`server-rs/crates/api-server/src/match3d.rs``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
- 关联:`server-rs/crates/api-server/src/match3d.rs``docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`
## 抓大鹅物品详情大方格只做单张大图查看
@@ -837,7 +790,7 @@
- 原因:旧预览把上方区域当作横向视角带,当前焦点只是带内缩略图的一张,视觉上不是“详细查看物品形象”的大图。
- 处理:上方方格只渲染当前选中的单张大图,使用 `object-contain` 和少量内边距放大查看;底部缩略图栏负责切换视角,缩略图可以保留选中态边框,但上方大图不渲染焦点内框或缩略图容器边框。
- 验证:`npm run test -- src/components/match3d-result/Match3DResultView.test.tsx` 覆盖上方大图、底部缩略图和视角切换。
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
- 关联:`src/components/match3d-result/Match3DResultView.tsx``docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`
## 草稿页卡片有真实素材但仍显示黑卡先查摘要字段
@@ -845,7 +798,7 @@
- 原因:拼图列表摘要若不下发 `levels`,前端拿不到关卡 `coverImageSrc` / 候选图;抓大鹅列表摘要若只提供公开 URL、不保留 `generatedBackgroundAsset``generatedItemAssets` 中的 object key前端无法换签读取私有生成图。卡片封面组件如果自带暗色默认背景也会让兜底失败时看起来仍是黑卡。
- 处理:拼图 `map_puzzle_work_summary_response` 必须保留 `levels`;草稿页优先用关卡 `coverImageSrc`,再用候选图。抓大鹅货架封面解析必须读取 `backgroundImageObjectKey``generatedBackgroundAsset.imageObjectKey/containerImageObjectKey``generatedItemAssets[].imageObjectKey``imageViews[].imageObjectKey`。图片渲染统一交给 `ResolvedAssetImage` 换签,并给卡片传入玩法参考图与暖色底兜底。
- 验证:执行 `npm run test -- src/components/custom-world-home/creationWorkShelf.test.ts src/components/custom-world-home/CustomWorldCreationHub.test.tsx src/hooks/useResolvedAssetReadUrl.test.tsx``cargo test -p api-server puzzle_work_summary_response_keeps_levels_for_shelf_cover --manifest-path server-rs\Cargo.toml``npm run typecheck`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
- 关联:`src/components/custom-world-home/creationWorkShelf.ts``src/components/CustomWorldCoverArtwork.tsx``server-rs/crates/api-server/src/puzzle.rs``docs/technical/CREATION_WORK_SHELF_UNIFICATION_2026-04-25.md`
## 用户标签不要直接外显SpacetimeDB Vec 字段不要写 default 宏
@@ -853,7 +806,7 @@
- 原因SpacetimeDB 的 table default 宏会走编译期常量求值,不能直接使用有析构逻辑的堆分配类型默认值。
- 处理:`user_account.user_tags` 使用 `Option<Vec<String>>` + `#[default(None::<Vec<String>>)]` 表达数据库默认空,业务层统一把 `None` 归一化为空数组;邀请码授予标签复用 `metadata_json.userTags` 存储和解析,不再新增独立 Vec 列。用户标签原始值不得进入登录态、个人资料等通用响应,只能在明确业务白名单里投影,例如拼图排行榜 `visibleTags` 首版仅允许 `北科`
- 验证:`npm run spacetime:generate -- --rust-only` 能通过;`user_account` 旧迁移 JSON 缺字段时能导入,`profile_invite_code``metadata_json` 时按 `{}` 兼容。
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
- 关联:`docs/technical/USER_TAG_INVITE_AND_PUZZLE_LEADERBOARD_2026-05-10.md``docs/technical/SPACETIMEDB_TABLE_CATALOG.md`
## 公开作品详情深链找不到作品不能停在空详情页
@@ -861,7 +814,7 @@
- 原因:旧恢复逻辑只覆盖 `/runtime/...`,没有覆盖 `/works/detail`。同时 `selectionStage === 'work-detail'``selectedPublicWorkDetail === null` 时没有兜底渲染,详情数据为空就只剩空页面。
- 处理:公开详情失效统一走 `resolveWorkNotFoundRecoveryAction(...)`,覆盖 `/works/detail``/gallery/puzzle/detail``/gallery/visual-novel/detail`;搜索失败和拼图详情 404 分支清理详情/运行态临时状态并回首页;`work-detail` 空数据阶段显示轻量读取态,避免异步间隙白屏。
- 验证:`npm run test -- src/routing/runtimeNotFoundRecovery.test.ts``npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "direct missing public work detail alert returns to platform home"`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
- 关联:`docs/technical/PUBLIC_WORK_DETAIL_NOT_FOUND_RECOVERY_2026-05-11.md``src/routing/runtimeNotFoundRecovery.ts``src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`
## 拼图 UI 背景只有 objectKey 时不要回退默认 UI
@@ -877,15 +830,15 @@
- 原因:首关命名 LLM 旧契约只返回 `levelName`,自动 UI 背景阶段只能用作品名、作品描述、关卡描述和标签拼接确定性兜底提示词;如果模型返回截断 JSON解析层还可能把 `levelNam` 这类字段名片段当作普通英文关卡名归一化通过。
- 处理:首关命名 LLM 契约必须同时返回 `{"levelName":"...","workDescription":"...","workTags":["..."],"uiBackgroundPrompt":"..."}`;解析层必须拒绝 `levelNam``levelName``workDescription``workTags``uiBackgroundPrompt` 等字段名片段作为关卡名。草稿自动 UI 背景生成优先使用该 AI 提示词,作品描述和 6 个作品标签默认填入草稿;视觉精修请求若返回新提示词或作品元信息则覆盖文本请求结果,否则保留文本请求结果。前端文本框只展示已保存的 `uiBackgroundPrompt` 或用户编辑值,字段为空时不展示本地兜底模板。
- 验证:执行 `cargo test -p api-server puzzle_level_naming_parser --manifest-path server-rs\Cargo.toml``cargo test -p api-server puzzle_first_level_name --manifest-path server-rs\Cargo.toml``cargo test -p api-server puzzle_initial --manifest-path server-rs\Cargo.toml``npm run test -- src/components/puzzle-result/PuzzleResultView.test.tsx`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
- 关联:`server-rs/crates/api-server/src/prompt/puzzle/level_name.rs``server-rs/crates/api-server/src/puzzle.rs``src/components/puzzle-result/PuzzleResultView.tsx``docs/technical/PUZZLE_FORM_CREATION_FLOW_2026-04-29.md`
## 拼图 / 抓大鹅 UI 背景重生成报 No such procedure 先查 SpacetimeDB 版本漂移
- 现象:拼图或抓大鹅结果页点击 `重新生成` UI 背景时报 `No such procedure`,常见位置是泥点预扣、`save_puzzle_ui_background` 或 Match3D 草稿写回。
- 原因:`api-server``spacetime-client` 已按新 bindings 调用 procedure但目标 SpacetimeDB 数据库仍运行旧 wasm尚未导出钱包扣退费、拼图 UI 背景保存或 Match3D 写回相关 procedure。
- 处理:临时容错是把这类 `No such procedure` 当作后端版本漂移:泥点预扣阶段跳过扣费,图片已经生成但保存失败时返回本次内存快照 / 内存 profile避免草稿页直接报错。长期修复仍是发布最新 `spacetime-module`、重新生成 bindings并用 `spacetime describe` 或定向 smoke 确认 procedure 已导出。
- 验证:`cargo test -p api-server asset_operation_billing_skips_spacetime_connectivity_errors --manifest-path server-rs\Cargo.toml``cargo test -p api-server match3d_fallback_work_profile_keeps_generated_background_asset --manifest-path server-rs\Cargo.toml``npm run api-server` 后检查 `/healthz`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
- 验证:`cargo test -p api-server asset_operation_billing_skips_spacetime_connectivity_errors --manifest-path server-rs\Cargo.toml``cargo test -p api-server match3d_fallback_work_profile_keeps_generated_background_asset --manifest-path server-rs\Cargo.toml``npm run dev:api-server` 后检查 `/healthz`
- 关联:`server-rs/crates/api-server/src/asset_billing.rs``server-rs/crates/api-server/src/match3d.rs``docs/technical/PUZZLE_FORM_CREATION_FLOW_2026-04-29.md``docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`
## 拼图合并块拖起后原位置出现红色块先查选中态泄漏
@@ -893,23 +846,7 @@
- 原因:合并块拖拽的可见层来自 `mergedGroups` 绝对定位整体层,但 `pointerdown` 会同步写入 `selectedPieceId`;若棋盘格里的底层单块 DOM 先匹配选中态,再匹配合并态,整体层移开后就会露出单块选中填充色。
- 处理:合并格底层 DOM 只作为透明定位占位,`isSelected` 必须排除 `isMerged`;合并格样式优先级高于单块选中态。
- 验证:运行 `npm run test -- src/components/puzzle-runtime/PuzzleRuntimeShell.test.tsx -t "拖拽合并大块时底层单格不显示选中色块"`,并确认合并块拖拽时底层 `[data-piece-id]` 仍为 `puzzle-runtime-piece--merged`
- 关联:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
## 拼图拖拽延迟和圆角异常先查即时 DOM 视觉层
- 现象:拼图块拖动时有明显追手延迟,或合并块的 L 形 / 凹形内角圆角和外凸角表现一致、出现方角。
- 原因:拖拽视觉如果依赖 `requestAnimationFrame` 排队、React 重渲染或 transform transition会在触控设备上产生一帧以上延迟合并块如果只靠单格 `border-radius`,无法正确处理整体外轮廓和内凹角。
- 处理:`PuzzleRuntimeShell``pointermove` 期间直接给当前单块或合并组 DOM 写入 `translate3d(...)`,拖拽阶段禁用 transform transition并隐藏拼块选中态和底部“已选择”提示单块使用 `buildRoundedGridCellClipPath()` 独立裁切,合并块视觉层使用 SVG 原生 `clipPath` 裁切整体轮廓,不只依赖 HTML `clip-path:url(#...)`,外凸角和内凹角半径分开计算,内凹角半径更大。
- 验证:执行 `npm run test -- src/components/puzzle-runtime/PuzzleRuntimeShell.test.tsx`,重点覆盖拖拽 move 后 DOM transform 立即变化且不调用 rAF、拖动中不显示已选择状态、基础单块有圆角裁切、合并块内凹角路径使用更大半径且视觉层为 SVG 裁切。
- 关联:`src/components/puzzle-runtime/PuzzleRuntimeShell.tsx``src/components/puzzle-runtime/puzzleRuntimeShape.ts``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
## 拼图设置面板当前用时为 0 先查结算字段误用
- 现象:拼图运行态还在游玩时,设置面板里的“当前用时”一直显示 `0:00.00`,但顶部倒计时正常变化。
- 原因:`currentLevel.elapsedMs` 是通关后的结算字段,进行中关卡按契约通常为 `null`;设置面板若直接格式化该字段,就会被归一化为 0。
- 处理:设置面板的当前用时按 `startedAtMs``pausedAccumulatedMs`、UI 暂停起点、`freezeAccumulatedMs` 和当前冻结区间实时派生,和剩余时间使用同一套暂停 / 冻结扣除口径。
- 验证:运行 `npm run test -- src/components/puzzle-runtime/PuzzleRuntimeShell.test.tsx -t "拼图设置面板展示进行中关卡的实时当前用时"`
- 关联:`src/components/puzzle-runtime/PuzzleRuntimeShell.tsx``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
- 关联:`src/components/puzzle-runtime/PuzzleRuntimeShell.tsx``src/components/puzzle-runtime/PuzzleRuntimeShell.test.tsx``docs/technical/PUZZLE_FORM_CREATION_FLOW_2026-04-29.md`
## 拼图历史图片列表不要把账号归属当图片名
@@ -917,4 +854,4 @@
- 原因:`/api/assets/history?kind=puzzle_cover_image` 返回的 `ownerLabel` 是资产归属账号,不是图片标题;`createdAt` 可能是 SpacetimeDB / shared-kernel 秒级时间字符串,不能只用浏览器 `new Date(value)` 解析。历史图的 `imageSrc``/generated-*` 私有兼容路径,浏览器预览必须换签。
- 处理:前端标题和选中标签从 `imageSrc` 路径末尾推导,例如 `image.png`;时间解析兼容 ISO 与 `1713686400.000000Z`;创作页主图、历史列表图和结果页参考图继续用 `ResolvedAssetImage`,提交给后端时仍保留原始 `imageSrc`
- 验证:`npm run test -- src/components/puzzle-agent/PuzzleAgentWorkspace.interaction.test.tsx src/components/puzzle-result/PuzzleResultView.test.tsx`,并执行 `npm run check:encoding`
- 关联:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md``docs/【项目基线】当前产品与工程约束-2026-05-15.md``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
- 关联:`src/services/puzzle-works/puzzleHistoryAsset.ts``src/components/puzzle-agent/PuzzleHistoryAssetPickerDialog.tsx``docs/technical/ASSET_HISTORY_PUZZLE_COVER_KIND_FIX_2026-04-27.md`