Merge remote-tracking branch 'origin/master' into codex/wooden-fish-template

This commit is contained in:
2026-05-22 04:00:52 +08:00
121 changed files with 10876 additions and 3477 deletions

View File

@@ -14,6 +14,32 @@
- 关联:相关文件、文档、提交或 Issue
```
## 抓大鹅新 UI spritesheet 不要回退成中心容器图
- 现象:新素材流程生成后,运行态棋盘中心可能叠出一整张 UI spritesheet导致按钮素材、方格和空白图集覆盖容器区域。
- 原因:为了兼容旧 DTO后端可能把 `uiSpritesheetImage*` 同步写入历史 `containerImage*` 字段;旧前端只看 `containerImage*`,会误把 UI 图集当透明中心容器。
- 处理:读取中心容器图时先比较归一化后的 `containerImage*``uiSpritesheetImage*`。两者同源时忽略 `containerImage*`,只把它作为旧数据兼容字段;新流程背景图本身已经保留容器,运行态只需加载背景和解析 UI / 物品 spritesheet。
- 验证:`npm run test -- src/components/match3d-runtime/Match3DRuntimeShell.test.tsx` 应覆盖“运行态不把兼容写入的UI spritesheet当中心容器图”。
- 关联:`src/components/match3d-runtime/Match3DRuntimeShell.tsx``server-rs/crates/api-server/src/match3d/mappers.rs``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
## UI spritesheet 不要依赖模型直接生成透明背景
- 现象:拼图或抓大鹅运行态解析 UI spritesheet 时,把整张背景图、棋盘格、叶子或装饰图也当作 UI 素材区域,按钮映射错乱;截图里常表现为底部按钮区只剩透明棋盘格或素材碎片。
- 原因:前端解析依赖 alpha 连通域检测,透明背景是前提;但生图模型收到“透明背景 spritesheet”提示后仍可能输出带实景背景或伪透明棋盘格的普通不透明 PNGOSS 中保存的图没有真实 alpha。
- 处理UI spritesheet 提示词应要求统一纯绿色绿幕背景,而不是让模型直接产透明背景;后端在上传 OSS 前复用 `generated_asset_sheets::apply_generated_asset_sheet_green_screen_alpha(...)` 把绿幕扣成真实透明 PNG再把透明图写入 `uiSpritesheetImageSrc/uiSpritesheetImageObjectKey`
- 验证:`cargo test -p api-server puzzle_ui_spritesheet_postprocess_turns_green_screen_transparent --manifest-path server-rs\Cargo.toml``cargo test -p api-server puzzle_level_scene_spritesheet_and_background_requests_use_references --manifest-path server-rs\Cargo.toml``cargo test -p api-server match3d_derived_asset_prompts_match_three_sheet_pipeline --manifest-path server-rs\Cargo.toml`
- 关联:`server-rs/crates/api-server/src/puzzle/generation.rs``server-rs/crates/api-server/src/match3d/works.rs``server-rs/crates/api-server/src/generated_asset_sheets.rs``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
## 拼图 UI spritesheet 运行态不要二次包圆底或拉伸比例
- 现象:拼图运行态左上返回和右上设置按钮外面出现白色圆圈;底部“提示 / 原图 / 冻结”三枚素材被压扁、拉宽或拉成正圆,和图集原始按钮比例不一致。
- 原因UI spritesheet 已经包含按钮视觉本体,但运行态仍给顶部按钮套默认圆形 icon 容器;底部三枚素材用 `h-full w-full rounded-full` 铺满按钮格,覆盖了自动检测矩形的真实宽高比。
- 处理:有 `uiSpritesheetImage*` 时,顶部返回 / 设置按钮容器只保留透明点击区和 focus 状态,不再叠加默认圆形底;`buildPuzzleUiSpriteBackgroundStyle(...)` 对检测到的矩形写入 `aspectRatio`,底部三枚素材按原始宽高比和最大尺寸渲染,不强制 `w-full`
- 验证:`npm run test -- src/components/puzzle-runtime/PuzzleRuntimeShell.test.tsx``npm run test -- src/services/puzzle-runtime/puzzleUiSpritesheetParser.test.ts`
- 关联:`src/components/puzzle-runtime/PuzzleRuntimeShell.tsx``src/services/puzzle-runtime/puzzleUiSpritesheetParser.ts``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
2026-05-22 补充:展示矩形和点击热区要分开处理。`puzzleUiSpritesheetParser``regions` 保留完整视觉裁切矩形,`hitRegions` 用较高 alpha 阈值只包住实心按钮主体;运行态底部 spritesheet 道具按钮启用 `puzzle-runtime-sprite-tool-button--precise-hit`,父按钮不吃整块透明留白,内部 `puzzle-runtime-ui-sprite-hit-zone` 才接收指针事件,避免透明区域成为点击热区。
## 图像输入组件不要把业务状态藏在页面内联实现里
- 现象:拼图页把参考图上传、缩略图、主图删除确认和 AI 重绘开关内联实现后,后续想复用到其它创作页时,页面级状态和通用 UI 状态混在一起,容易出现多套上传卡和参考图展示口径。
@@ -22,6 +48,54 @@
- 验证:拼图入口测试仍可通过,且新组件可通过不同页面复用而不需要复制上传卡实现。
- 关联:`src/components/common/CreativeImageInputPanel.tsx``src/components/puzzle-agent/PuzzleAgentWorkspace.tsx`
## RPG 发布不能只依赖 agent session seed_text
- 现象RPG 结果页 `publish_world` 返回 `UPSTREAM_ERROR`details 为 `custom_world.setting_text 不能为空`;同一 session 的 `result-view` 日志显示 `publish_ready=true`
- 原因:前端发布动作只提交 `{ action: 'publish_world' }`,旧 agent 会话的 `seed_text` 可能为空;如果后端只从 action payload 或 `seed_text``setting_text`,就会在最终 compile / publish 校验阶段失败。
- 处理:`module-custom-world::resolve_custom_world_publish_setting_text(...)` 以当前 `draft_profile_json` 为草稿真相,优先读取 `settingText``creatorIntent.rawSettingText``creatorIntent.worldHook``worldHook``anchorContent.worldPromise(.hook)``summary``name/title`,最后才回退 `seed_text`
- 验证:`cargo test -p module-custom-world publish_setting_text --manifest-path server-rs\Cargo.toml``cargo check -p spacetime-module --manifest-path server-rs\Cargo.toml`
- 关联:`server-rs/crates/module-custom-world/src/application.rs``server-rs/crates/spacetime-module/src/custom_world.rs``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
## RPG 已发布结果页进入世界不能重复 publish_world
- 现象RPG 草稿发布成功后,按钮文案已变为“进入世界”,但点击仍请求 `POST /api/runtime/custom-world/agent/sessions/{sessionId}/actions` 且 payload 为 `{"action":"publish_world"}`,后端返回 `publish_world is only available during object_refining, visual_refining, long_tail_review or ready_to_publish`
- 原因:按钮文案依据 agent session `stage === 'published'` 切换,但点击处理仍走发布协调路径;如果前端只依赖草稿同步回包判断是否已发布,回包为空或缺少可进入状态时就会继续重复发送 `publish_world`
- 处理:进入世界协调器接收当前 agent session stage当 stage 已为 `published` 时,只调用 `result-view` 回读已发布 profile 并启动运行态,不再调用 `sync_result_profile``publish_world`
- 验证:`npm run test -- src/components/rpg-entry/useRpgCreationEnterWorld.test.tsx`;确认已发布场景下 `syncAgentDraftResultProfile``executePublishWorld` 均未被调用。
- 关联:`src/components/rpg-entry/useRpgCreationEnterWorld.ts``src/components/platform-entry/PlatformEntryFlowShellImpl.tsx``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
## RPG 点击启动黑屏 / 默认 profile 先查 profile 归一化和摘要覆盖
- 现象:作品详情点击“启动”后页面切到 RPG runtime但用户只看到黑屏、空白或进入默认角色 / 默认 profile从作品详情点“作品编辑”后开局 CG、封面、角色图、技能动作预览、初始物品图标或场景背景图丢失DevTools 里可能同时看到旧自动存档 `/api/runtime/save/snapshot` 被主动 cancel。
- 原因:`/custom-world-library` / `/custom-world-gallery` 详情接口可能返回历史或摘要式 `profile`,缺少 `playableNpcs``storyNpcs``landmarks``attributeSchema` 等运行态字段;前端 client 若直接把该对象传给 runtime角色选择首屏会在 `buildCustomWorldPlayableCharacters(profile)` 或后续属性解析处抛错。另一类常见原因是详情接口已回读完整 profile 后,`savedCustomWorldEntries` 里的列表摘要又把 `selectedDetailEntry` 覆盖回空 profile导致启动或编辑时只剩卡片摘要。发布 / 回读 result-view 若返回字段更少的旧视图,也可能把当前结果页已编辑资产降级掉。`save/snapshot (canceled)` 通常是切 runtime 或卸载时 `AbortController` 取消旧自动存档,不是黑屏根因。
- 处理RPG 入口作品库 client 在所有返回 `CustomWorldLibraryEntry<CustomWorldProfile>` 的接口边界统一调用 `normalizeCustomWorldProfileRecord`,并用 `profileId/worldName/subtitle/summaryText` 补齐旧数据缺字段;详情页已拿到运行态字段或资产槽位更多的完整 profile 时,不允许列表摘要覆盖当前详情;同一 `profile.id` 下,正式进入世界发布 / 回读不得用字段更少的后端旧视图降级当前结果页 profile。`normalizeCustomWorldProfileRecord` 必须近似无损保留 `cover``openingCg``camp.narrativeResidues``landmark.visualDescription/narrativeResidues``skills[].actionPreviewConfig``initialItems[].iconSrc``attributeSchema`、角色 `attributeProfile``sceneChapterBlueprints[].acts[]` 的背景与结构字段;只有背景资产的 act 也不能被过滤。角色选择页对角色生成异常或空数组回退默认角色,并保留返回按钮/轻量空态;顶层 runtime 懒加载 fallback 不使用纯 `null`
- 验证:`npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "creation hub published work start uses loaded detail profile instead of library summary|creation hub published work edit keeps loaded detail profile assets instead of library summary"``npm run test -- src/data/customWorldLibrary.test.ts -t "保留结果页封面和关键图片资产槽位|近似无损保留编辑态和运行态结构字段|保留只有背景资产的场景幕"``npm run test -- src/components/rpg-entry/useRpgEntryAgentDraftRestore.test.tsx -t "默认封面和角色编辑结构差异也不能被列表摘要覆盖"``npm run test -- src/components/rpg-entry/useRpgCreationEnterWorld.test.tsx -t "正式进入世界回读结果页字段更少时不降级当前完整 profile"``npm run typecheck`
- 关联:`src/components/rpg-entry/useRpgEntryLibraryDetail.ts``src/components/rpg-entry/useRpgCreationEnterWorld.ts``src/data/customWorldLibrary.ts``src/services/rpg-entry/rpgEntryLibraryClient.ts``src/components/rpg-entry/RpgEntryCharacterSelectView.tsx``src/App.tsx``src/components/rpg-runtime-shell/RpgRuntimeShell.tsx``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
## RPG 战后一轮战斗后卡在观察/试探/调息先查 post-battle finalization
- 现象RPG 一轮战斗胜利后,运行态只显示默认 `观察周围迹象 / 主动出声试探 / 原地调息`,这些按钮只有文字反馈;点“继续冒险”后又回到同样选项,点探索只播退场/进场动画,场景和剧情不推进。
- 原因:终局战斗 action 如果只走通用 `resolve_story_runtime_action` fallback而没有在后端调用 `finalize_post_battle_resolution(...)`,就不会持久写入 `story_continue_adventure``deferredOptions` 和下一幕 `currentSceneActState`。另外旧 bootstrap 快照可能只有 `connectedSceneIds` / `forwardSceneId`、没有 `connections`,战后选项生成若只读 `connections` 也会退回 `idle_explore_forward` 循环。
- 处理:`module-runtime-story` 在 story action 投影后统一调用 post-battle finalization`idle_explore_forward` 清理战斗态并生成下一段遭遇预览;`idle_travel_next_scene` / `camp_travel_home_scene` 由后端写入新 `currentScenePreset`、场景 act 状态、遭遇预览和 `runtimeStats.scenesTraveled`。前端只负责播放继续、探索和切场景动画,不承接正式剧情推进真相。
- 验证:`cargo test -p module-runtime-story --manifest-path server-rs\Cargo.toml battle_tests -- --nocapture` 应覆盖战斗终局持久化 `story_continue_adventure``deferredOptions`、下一幕 act以及 `idle_travel_next_scene` 真正切换场景。
- 关联:`server-rs/crates/module-runtime-story/src/session_action.rs``server-rs/crates/module-runtime-story/src/post_battle.rs``server-rs/crates/module-runtime-story/src/battle_tests.rs``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
## RPG 战斗飘字不要只靠低对比红绿文字
- 现象:暗色或棕黑噪声背景下,战斗伤害飘字看起来像背景纹理,尤其是远端敌人头顶的小号红字几乎不可读。
- 原因:旧 `CombatFloatingNumber` 主要依赖 `text-rose-200` / `text-emerald-200` 和 8px 同色 glow在暗红、棕黑、像素噪声背景上颜色与背景混在一起1px 深色描边也不足以形成轮廓。
- 处理:飘字本体使用高亮近白文字、小面积半透明深色底、明显深色描边和多层黑色阴影;只增强瞬时反馈,不新增说明面板,不遮挡主要战斗画面。
- 验证:`npm run test -- src/components/game-canvas/GameCanvasEntityLayer.test.tsx` 覆盖伤害/治疗飘字样式策略;运行态截图中敌方头顶伤害数字应能在暗场景上辨认。
- 关联:`src/components/game-canvas/GameCanvasEntityLayer.tsx``docs/【项目基线】当前产品与工程约束-2026-05-15.md`
## 弹窗里复用 CreativeImageInputPanel 要保留画面卡高度
- 现象:拼图草稿结果页的关卡详情弹窗中仍能看到“画面图”标题、画面描述和生成按钮,但实际画面图卡片视觉上消失。
- 原因:`CreativeImageInputPanel` 内部依赖 `flex-1``h-full``max-h-full` 撑开正方形画面卡;放进弹窗里的普通 `section` 后,父级没有可计算高度,卡片会被压到不可见。
- 处理:通用画面卡 `puzzle-image-upload-card` 保持 `aspect-square` 的同时设置稳定 `min-height`,让入口页和关卡详情弹窗都能显示主图/上传区。
- 验证:`npm run test -- src/components/puzzle-result/PuzzleResultView.test.tsx -t "opens an independent level detail dialog"` 应断言关卡详情中的 `.puzzle-image-upload-card` 具备最小高度类;`npm run test -- src/components/common/CreativeImageInputPanel.test.tsx` 应继续通过。
- 关联:`src/components/common/CreativeImageInputPanel.tsx``src/components/puzzle-result/PuzzleResultView.tsx``src/components/puzzle-result/PuzzleResultView.test.tsx`
## Windows provision 下载截断要断点续传而不是回退目标机下载
- 现象:`Genarrative-Server-Provision``Download Provision Tool Archives` 阶段出现 `curl: (18) end of response ... bytes missing`,常见于 `otelcol-contrib_0.151.0_linux_amd64.tar.gz` 等 GitHub release 大文件。
@@ -189,7 +263,7 @@
## 陶泥儿 logo 生图慢请求先缩短 prompt 并单张串行
- 现象:使用 VectorEngine `gpt-image-2-all` 生成陶泥儿 logo 概念图时,部分 prompt 会超过 10 分钟仍无响应,或返回 `429` / `当前分组上游负载已饱和`;同一批次里后续图片会被前面的慢请求拖住。
- 现象:使用 VectorEngine `gpt-image-2` 生成陶泥儿 logo 概念图时,部分 prompt 会超过 10 分钟仍无响应,或返回 `429` / `当前分组上游负载已饱和`;同一批次里后续图片会被前面的慢请求拖住。
- 原因:复杂抽象 logo prompt 同时包含品牌解释、禁用元素、中文结构和多重隐喻时,上游排队与生成时长不稳定;并发或批量运行会放大单条慢请求的影响。
- 处理:先 `--dry-run` 看请求体;真实生成时优先短 prompt、单一造型、单张串行或小批量。失败后不要反复重试同一长 prompt先压缩到“一个主体 + 一个负形 + 颜色 + 禁用文字/播放键/聊天气泡”再跑。联系表中的中文标签不要通过 PowerShell 管道内联 Python 写入,容易因编码链路显示为问号,可改用英文标签或脚本文件方式。
- 验证:生成文件落在 `public/branding/taonier-logo-*/`,用 Pillow 检查图片尺寸和非空;执行 `node --check scripts/generate-taonier-logo-concepts.mjs``npm run check:encoding``git diff --check`
@@ -223,11 +297,11 @@
- 现象:点击生成抓大鹅草稿后,页面只提示“服务暂不可用”,或者本地 `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` 单格宽度空白间距,避免切割后相邻格内容污染
- 处理:前端 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_*` 四件套。抓大鹅 UI spritesheet 和物品 spritesheet 的提示词必须要求纯绿色绿幕背景,后端上传 OSS 前统一扣成透明 PNG避免运行态 alpha 连通域解析失败
- 验证:`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}` 排查
2026-05-22 补充:抓大鹅“物品 spritesheet”不再按旧 Gemini `generateContent` / `5*5` sheet 路径排查;当前链路先用 `gpt-image-2` 无参考图生成 `9:16` 关卡整图,再以该关卡整图作为 multipart `image` 参考并发编辑生成 `1K 1:1` UI spritesheet、`1K 9:16` 背景图和 `2K 1:1` 物品 spritesheet。UI 与物品 spritesheet 都要求纯绿色绿幕背景,上传 OSS 前通过后端透明化处理写入真实 alpha PNG
## 抓大鹅发布按钮要先开发布面板,封面编辑收口到发布面板内
@@ -321,7 +395,7 @@
## 儿童动作 Demo 绘本风资源未生成先查 VectorEngine 配置
- 现象:`/child-motion-demo` 已经呈现绘本草地风格,但 `public/child-motion-demo/picture-book-grass-stage.png``picture-book-grass-floor.png``picture-book-ground-ring.png``picture-book-character-outline.png``picture-book-ui-panel.png``picture-book-ui-button.png` 不存在Network 里对应图片返回 404或运行 `npm run assets:child-motion-demo -- --live` 返回缺少 VectorEngine 配置。
- 原因:儿童动作 Demo 的真实背景、地面、UI、地面指示环和角色轮廓资源都使用 VectorEngine `gpt-image-2-all` 生成,脚本只读取 `VECTOR_ENGINE_BASE_URL``VECTOR_ENGINE_API_KEY` 和可选 `VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS`;仓库内不能提交真实 key缺配置时页面只能使用 CSS 草地绘本兜底。
- 原因:儿童动作 Demo 的真实背景、地面、UI、地面指示环和角色轮廓资源都使用 VectorEngine `gpt-image-2` 生成,脚本只读取 `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` 仍通过。
- 关联:`scripts/generate-child-motion-demo-assets.mjs``src/index.css``docs/technical/CHILD_MOTION_DEMO_WARMUP_IMPLEMENTATION_SPEC_2026-05-09.md`
@@ -345,8 +419,8 @@
## GPT-image-2 不再读 APIMart 图片配置
- 现象:配置了 `APIMART_BASE_URL` / `APIMART_API_KEY`RPG、拼图或方洞的 GPT-image-2 生图仍返回缺配置,或请求体里还出现 `official_fallback` / `image_urls`
- 原因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`
- 原因2026-05-21 后 GPT-image-2 图片生成 VectorEngine 创建/编辑接口分流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`有参考图路径为 `/v1/images/edits`模型为 `gpt-image-2`
- 验证:运行 `cargo test -p api-server openai_image --manifest-path server-rs/Cargo.toml` 和相关玩法图片生成测试;真实联调只在本地私密环境放置 VectorEngine key。
- 关联:`docs/technical/VECTOR_ENGINE_GPT_IMAGE_2_GENERATION_2026-05-09.md``server-rs/crates/api-server/src/openai_image_generation.rs`
@@ -366,19 +440,19 @@
- 验证:后端单测覆盖 `build_puzzle_levels_with_primary_update``apply_generated_puzzle_candidates_to_session_snapshot`;结果页重新生成应在未重新上传时继续带入 `level.pictureReference`
- 关联:`server-rs/crates/api-server/src/puzzle.rs``src/components/puzzle-result/PuzzleResultView.tsx`
## 拼图图生图仍不像参考图时先看是否走了 edits
## 拼图参考图不像时先看 edits multipart image
- 现象Network payload 已带 `referenceImageSrc`,但 VectorEngine 生成结果仍明显不像上传图。
- 原因:`gpt-image-2-all` `/v1/images/generations` 更适合纯文生图;有参考图且需要重绘时应切到 `/v1/images/edits` 的 multipart 图生图接口
- 处理:`referenceImageSrc` 存在且 `aiRedraw = true`直接走 editsprompt 保留参考图强约束;入口页关闭 AI 重绘时直接应用上传图,不调用图片生成;前端把参考图压到单边 1024 内,后端解析后拒绝超过 8MB 的参考图字节。
- 验证:后端单测应覆盖 `images/edits` 路由、`b64_json` 响应解码和参考图强提示;真实联调看日志里是否命中 `拼图 VectorEngine 图片编辑 HTTP 返回`
- 原因:参考图只在 `aiRedraw = true` 时由后端解析并传给 `gpt-image-2` `/v1/images/edits` 的 multipart `image` part若前端没传 `referenceImageSrc`、后端解析失败或 prompt 缺少参考图强约束,生成会退化为纯文生图
- 处理:`referenceImageSrc` 存在且 `aiRedraw = true` 时走 edits multipartprompt 保留参考图强约束;入口页关闭 AI 重绘时直接应用上传图,不调用图片生成;前端把参考图压到单边 1024 内,后端解析后拒绝超过 8MB 的参考图字节。
- 验证:后端单测应覆盖 `/v1/images/edits` 路由、`b64_json` 响应解码和参考图强提示;真实联调看日志里是否命中 `拼图 VectorEngine 图片编辑 HTTP 返回`
- 关联:`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 先看网络分类
- 现象:拼图有参考图时返回 `拼图图片生成失败:创建拼图 VectorEngine 图片编辑任务失败error sending request for url (https://api.vectorengine.ai/v1/images/edits)`,后端没有 `拼图 VectorEngine 图片编辑 HTTP 返回` 日志。
- 原因:这是 `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 发送
- 处理:查看错误响应`拼图 VectorEngine 图片编辑` 相关日志若请求发送阶段失败先查网络出口、DNS、防火墙、代理、参考图大小和 `VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS`
- 验证:`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`
- 关联:`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`
@@ -409,17 +483,41 @@
## 拼图草稿生成 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 自动重试一次,导致同一次点击重复触发生图与扣退费。
- 原因:首图生成走 VectorEngine `gpt-image-2`,默认 `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 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`
## 开局 CG 故事板生图失败先查 VectorEngine 请求预算和旧进程
- 现象RPG 结果页点击开局 CG 后,`POST /api/runtime/custom-world/opening-cg` 在较长等待后返回“开局 CG 故事板生成失败创建图片生成任务失败error sending request for url (https://api.vectorengine.ai/v1/images/generations)”。
- 原因:该故事板会把角色图和首幕背景图作为参考图一起传给 VectorEngine `gpt-image-2-all`,请求体和上游生成耗时都比普通单图更大;若运行中的 `api-server` 仍沿用旧 `VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS`,或者参考图过大,会在请求发送/等待阶段被 reqwest 截断。日志里 `timeout=false connect=false request=true body=false source=client error (SendRequest)` 表示还没拿到上游 HTTP 响应,通常优先怀疑大 JSON 请求体、上游网关中断或 HTTP 协议兼容,而不是业务响应解析失败。直接请求 VectorEngine 若无效 token 可快速返回 401不能据此判断真实生图不会超时。
- 处理:开局 CG 参考图入参先压到单边 768 的 JPEG`/v1/images/generations` 保持 reqwest 默认 HTTP 协商,只有 multipart `/v1/images/edits` 单独强制 HTTP/1.1。后端图片 helper 将 `request_body_bytes`、每张参考图 Data URL 长度、`timeout/connect/body/source/rootSource/sourceChain/endpoint` 分类写入日志和 `error.details`,前端优先展示 `details.reason`。修改 `.env.secrets.local` 后必须重启 `api-server``npm run dev` 终端用 `rs api-server`,否则旧进程仍按旧超时运行。
- 验证:分别运行 `cargo test -p api-server custom_world_ai --manifest-path server-rs/Cargo.toml``cargo test -p api-server openai_image_generation --manifest-path server-rs/Cargo.toml`;真实联调重启后再触发开局 CG若仍失败看返回的 `details.reason/source/rootSource/sourceChain/timeout/connect/body/endpoint``logs/api-server/` 同一 request_id。
- 关联:`server-rs/crates/api-server/src/custom_world_ai.rs``server-rs/crates/api-server/src/custom_world_ai/opening_cg.rs``server-rs/crates/api-server/src/openai_image_generation.rs``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
## 开局 CG 成功后又变空白要保留 profile.openingCg
- 现象RPG 结果页里的开局 CG 成功显示一瞬后,窗口又退回空白占位。
- 原因:`openingCg` 只存在于结果页 profile 槽位,如果父层在 `onProfileChange` 后重新同步了 profile却经过 `normalizeCustomWorldProfileRecord` 或作品库写回时丢掉 `openingCg`,预览就会从视频 / 故事板回退为空白。
- 处理:`src/data/customWorldLibrary.ts` 的 profile 归一化必须透传 `openingCg`;结果页和父层后续同步都应把它当作受控资产槽位,而不是临时 UI 状态。
- 验证:`npm run test -- src/data/customWorldLibrary.test.ts src/components/CustomWorldResultView.test.tsx`,确认生成后即使父层做一次归一化回写,开局 CG 仍继续显示。
- 关联:`src/data/customWorldLibrary.ts``src/components/rpg-creation-result/RpgCreationResultViewImpl.tsx``src/components/CustomWorldEntityCatalog.tsx`
## RPG 发布报 legacy_result_profile_json 非法先查 null 兼容
- 现象RPG 结果页发布动作返回 `UPSTREAM_ERROR`SpacetimeDB details 里是 `custom_world.compile.legacy_result_profile_json 不是合法 JSON object`
- 原因:`publish_world` 前端契约只要求 `{ action: 'publish_world' }``ExecuteCustomWorldAgentActionRequest.legacy_result_profile` 是可选字段,经 HTTP / serde / SpacetimeDB payload 传递时可能显式成为 JSON `null`。旧的编译器只接受 object 或缺省,把 `Some("null")` 当成非法 legacy JSON。
- 处理:`module-custom-world` 的 optional JSON object 解析要把 `null` 视为未提供,仍拒绝数组、字符串、数字和坏 JSON正式发布继续以 session `draft_profile_json` 为草稿真相。
- 验证:`cargo test -p module-custom-world published_profile_compile --manifest-path server-rs/Cargo.toml`
- 关联:`server-rs/crates/module-custom-world/src/application.rs``server-rs/crates/spacetime-module/src/custom_world.rs``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`
## 本地脚本调 VectorEngine 生图卡住先区分 fetch 首部超时
- 现象:用 Node `fetch` 直接请求 `POST /v1/images/generations`,已经设置较长的 AbortController 超时,但仍在约 180 到 300 秒后抛 `AbortError``TypeError: fetch failed``UND_ERR_HEADERS_TIMEOUT`;同一 prompt 改用原生 `https.request` 可以在较短时间内成功返回图片。
- 原因Node/Undici 的默认 headers timeout 可能早于业务脚本期望的长生图等待窗口触发,表现上容易被误判成 VectorEngine 上游本身超时。
- 处理:长期脚本优先复用后端 reqwest 或项目已有生成脚本;临时本地工具若必须用 Node可改用原生 `http`/`https.request` 并显式设置 socket timeout或为 Undici 单独配置 headers timeout。仍需隐藏 `VECTOR_ENGINE_API_KEY`,只报告配置是否存在。
- 验证:同一 `gpt-image-2-all` 请求体、同一环境变量下,原生 HTTP 请求能返回 `url` / `b64_json` 并落盘;失败时错误里能区分请求发送、首部等待、下载和解码阶段。
- 验证:同一 `gpt-image-2` 请求体、同一环境变量下,原生 HTTP 请求能返回 `url` / `b64_json` 并落盘;失败时错误里能区分请求发送、首部等待、下载和解码阶段。
- 关联:`.codex/skills/gpt-image-2-apimart/SKILL.md``server-rs/crates/api-server/src/openai_image_generation.rs`
## 旧后端路线文档造成判断漂移
@@ -852,7 +950,7 @@
- 现象:修改抓大鹅素材时容易沿用旧 Rodin/GLB 方案,导致新草稿生成耗时变长、进度停在模型阶段,或运行态等待不存在的 GLB。
- 原因:仓库里保留了 Hyper3D 通用代理和历史模型字段,旧文档也曾要求草稿阶段同步生成 GLB。当前产品口径已经改为 2D 多视角素材。
- 处理:新 `match3d_compile_draft` 与批量新增只生成 2D 图片:每个物品 5 个视角,单张 1K 素材图固定 5x5最多承载 5 个物品,一行对应一个物品,不足 5 个物品也补齐到完整 5 行;超过 5 个物品自动分批并行生图。素材图 prompt 固定要求纯绿色绿幕背景,切割前先把绿幕处理为透明 alpha再做格内内容前景边界校准并带留白避免固定内缩切掉贴近格线的主体。`generatedItemAssets[].status` 使用 `image_ready`,发布校验看 `imageViews[]`首图引用。`generated-models` 仅用于历史外部模型链接转存,不能作为新生产链路。
- 处理:新 `match3d_compile_draft` 与批量新增只生成 2D 图片:每个物品 5 个形态,单张 `2K 1:1` 物品 spritesheet 固定 `10*10`,每行承载两种物品、每种五个形态,单张最多承载 20 种物品。素材图 prompt 固定要求纯绿色绿幕背景,上传 OSS 前先把整张 spritesheet 绿幕处理为透明 alpha再由运行态和编辑器按 alpha 连通域解析;`generatedItemAssets[].status` 使用 `image_ready`,发布校验看 `imageViews[]`首图引用或可解析的物品 spritesheet`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`
- 关联:`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`
@@ -888,11 +986,11 @@
- 验证:执行 `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` 对应图片。
- 关联:`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 并接管棋盘外观
## 抓大鹅容器参考图必须进入 edits multipart image 并接管棋盘外观
- 现象:抓大鹅结果页看似有容器生成入口,但真实生成出的局内容器不像 `pot-fused-reference.png`,或进入试玩后仍被默认圆形锅壳、金色边框和径向底色覆盖/裁切。
- 原因:`/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该参考图属于后端生图协议输入需通过 `include_bytes!` 编译进 `api-server`,不能在运行时按当前工作目录读取 `public/`共享 GPT-image-2 HTTP client 承载 multipart 时强制 HTTP/1.1。`Match3DRuntimeShell` 在容器图换签并成功加载后,把棋盘外壳切为透明和 `overflow-visible`,只在容器缺失或加载失败时使用默认圆形容器。
- 原因:容器参考图必须进入 `gpt-image-2` `/v1/images/edits` multipart `image` part并配合强 prompt 锁定大尺寸轻俯视容器构图;即使生成了容器图,如果运行态继续保留默认 `rounded-full` 锅壳和 `overflow-hidden`,生成图也会被默认视觉覆盖或裁掉。
- 处理:抓大鹅 `1:1` 容器 UI 图统一调用 VectorEngine `POST /v1/images/edits`,参考 `public/match3d-background-references/pot-fused-reference.png` 的透明容器图由后端作为 `image` part 上传;该参考图属于后端生图协议输入,需通过 `include_bytes!` 编译进 `api-server`,不能在运行时按当前工作目录读取 `public/``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`
- 关联:`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`
@@ -970,9 +1068,9 @@
## 抓大鹅难度配置的物品种类和消除次数必须分离
- 现象:历史草稿选择标准 / 硬核难度后,系统可能把 `clearCount` 当成局内物品种类数量,导致标准需要 12 种、硬核需要 20/21 种;素材不足时发布或试玩行为不一致
- 现象:历史草稿选择标准 / 硬核难度后,系统可能把 `clearCount` 当成局内物品种类数量,导致标准需要 12 种、硬核需要 20/21 种;或者把第 11 到 20 个物品持久化为第 11 到 20 行,触发“系列素材图集持久化的行列索引必须落在 n*n 范围内”
- 原因:旧运行态把消除次数和类型数量绑在一起,结果页文案又同时展示“素材图片 / 局内类型”,导致前端、发布校验和 run start 口径不一致。
- 处理:统一使用 `物品种类` 口径轻松 3、标准 9、进阶 15、硬核 21历史 `clearCount=20` 且难度为硬核的运行态按新硬核升为 21 组三消,避免 20 组却要求 21 种素材。发布前按 `image_ready` 且有 `imageViews[]``imageSrc/imageObjectKey` 的生成素材数量阻断不足难度;试玩不阻断,但通过 `itemTypeCountOverride` 自动降到已生成 2D 素材数量。重启从已有 run 快照反推实际物品种类,保持同一局重开不变。
- 处理:生成和持久化固定使用 20 个物品素材;运行态物品种类口径轻松 3、标准 9、进阶 15、硬核 20历史 `clearCount=20` 且难度为硬核的运行态仍可升为 21 组三消,但类型池不超过 20。10*10 sheet 每行两种物品、每种五个形态,持久化行列为 `row = itemIndex / 2 + 1``col = itemIndex % 2 * 5 + viewIndex + 1`。发布前按 `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`
- 关联:`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.rs``docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`
@@ -989,7 +1087,7 @@
- 现象:抓大鹅生成的物品视角图裁剪后仍带白边,或者整块纯绿色绿幕背景没有被透明化,运行态看到绿色方块。
- 原因:素材 sheet 可能是“每格内部绿幕、整张图外圈近白底”,内部绿幕不一定连通到 sheet 外边缘;旧 flood fill 只从外边缘找背景会漏掉这种绿幕块。白底抗锯齿如果不纳入抠像和边缘去污染,也会随裁剪输出成一圈白边。即使顺序已是先整张 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 切图
- 验证:`cargo test -p api-server match3d_material_sheet_slicing --manifest-path server-rs\Cargo.toml` 覆盖非连通绿幕、白边、贴边主体保留和固定 `10*10` 切图;`cargo test -p api-server match3d_spritesheet_green_screen_postprocess_turns_background_transparent --manifest-path server-rs\Cargo.toml` 覆盖完整 spritesheet 上传前绿幕透明化
- 关联:`server-rs/crates/api-server/src/match3d.rs``docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`
## 抓大鹅物品详情大方格只做单张大图查看
@@ -1056,6 +1154,14 @@
- 验证:运行 `npm run test -- src/components/puzzle-runtime/PuzzleRuntimeShell.test.tsx -t "拖拽合并大块时底层单格不显示选中色块"`,并确认合并块拖拽时底层 `[data-piece-id]` 仍为 `puzzle-runtime-piece--merged`
- 关联:`src/components/puzzle-runtime/PuzzleRuntimeShell.tsx``src/components/puzzle-runtime/PuzzleRuntimeShell.test.tsx``docs/technical/PUZZLE_FORM_CREATION_FLOW_2026-04-29.md`
## 推荐页嵌入拼图通关结算不要放在运行态内部 absolute 层
- 现象:推荐页里玩拼图通关后,结算面板只显示上半部分,排行榜、下一关按钮或相似作品卡被截断。
- 原因:推荐页把运行态放在滑动作品卡的视觉区内,`platform-recommend-swipe-page``platform-recommend-swipe-card__visual``platform-recommend-runtime-viewport` 都是 `overflow: hidden`;拼图通关结算如果仍是运行态内部 `absolute inset-0` 弹层,就只能在半屏卡片区域里显示。
- 处理:`PuzzleRuntimeShell``embedded` 模式下把通关结算层通过 portal 挂到 `document.body`,使用 `puzzle-runtime-modal-overlay--fixed` 页面级 fixed 浮层;非嵌入态继续使用运行态内部覆盖层。
- 验证:运行 `npm run test -- src/components/puzzle-runtime/PuzzleRuntimeShell.test.tsx -t "推荐页嵌入拼图通关结算使用页面级浮层避免卡片裁剪"`,确认弹层不再位于 `.platform-recommend-runtime-viewport` 内。
- 关联:`src/components/puzzle-runtime/PuzzleRuntimeShell.tsx``src/index.css``src/components/rpg-entry/RpgEntryHomeView.tsx`
## 拼图历史图片列表不要把账号归属当图片名
- 现象:拼图创作页或结果页打开“选择历史图片”后,历史列表显示 `账号 user-1` 之类归属文案而不是图片名;`1713686400.000000Z` 这类时间显示为未知;选中后预览或生成参考图可能被怀疑不可用。
@@ -1074,12 +1180,22 @@
## 拼图结果页局部生图不要污染草稿生成态
- 现象:拼图草稿已经生成完成后,在结果页重新生成 UI 背景或追加关卡生成图片,草稿页仍显示整卡“生成中”,点击草稿会回到生成过程页,无法查看已有结果;UI 背景生成中还会禁用“新增关卡”和关卡图生成
- 现象:拼图草稿已经生成完成后,在结果页重新生成关卡图片或追加关卡生成图片,草稿页仍显示整卡“生成中”,点击草稿会回到生成过程页,无法查看已有结果;关卡图片生成中还会禁用“新增关卡”和其它关卡详情编辑
- 原因:结果页局部 action 复用了全局 `isPuzzleBusy` / 持久化 `generationStatus=generating` 语义,作品架没有区分“初始草稿不可查看”和“已有结果上的局部关卡生成”。
- 处理:作品架只在拼图没有可用封面、首关候选图或任一可查看关卡时才把 `generationStatus=generating` 解释为初始草稿生成;结果页 UI 背景和关卡图走 background action不设置全局 busyUI 背景只禁用自己的按钮SpacetimeDB/API mapper 读写时把已有图片但状态仍是 `generating` 的历史关卡归一为 `ready`
- 处理:作品架只在拼图没有可用封面、首关候选图或任一可查看关卡时才把 `generationStatus=generating` 解释为初始草稿生成;结果页关卡图走 background action不设置全局 busy只标记对应关卡局部生成进度SpacetimeDB/API mapper 读写时把已有图片但状态仍是 `generating` 的历史关卡归一为 `ready`
- 验证:`npm run test -- src/components/custom-world-home/CustomWorldCreationHub.test.tsx src/components/puzzle-result/PuzzleResultView.test.tsx``cargo test -p api-server puzzle --manifest-path server-rs\Cargo.toml`
- 关联:`src/components/custom-world-home/creationWorkShelf.ts``src/components/platform-entry/PlatformEntryFlowShellImpl.tsx``src/components/puzzle-result/PuzzleResultView.tsx``server-rs/crates/api-server/src/puzzle/mappers.rs``server-rs/crates/spacetime-module/src/puzzle.rs`
2026-05-22 补充:结果页关卡详情的“关卡测试”不能把单关 `draft` 传给父级再调用 `updatePuzzleWork``updatePuzzleWork` 会同步 `puzzle_work_profile.levels_json` 和 source session 草稿,单关快照会把整份多关卡草稿覆盖成一个关卡,退出重进后只剩最后测试的关卡且序号表现为第一关。修复口径是 `PuzzleResultView` 始终传完整 `syncedDraft`,额外用 `{ levelId }` 指定起始关卡;父级持久化完整 levels 后调用 `startLocalPuzzleRun(item, levelId)`
## 拼图上传图关闭 AI 重绘不要走首图生图
- 现象:用户在拼图入口页或结果页关卡详情上传图片并关闭 AI 重绘后,生成页仍显示“生成拼图首图”,或者后端仍调用 `generate_puzzle_image_candidates` 生成第一张 1:1 候选图。
- 原因:上传图直用路径应把 Data URL 或 `/generated-*` 历史图解析后持久化为 `sourceType=uploaded` 的正式候选,再继续生成 9:16 关卡画面、UI spritesheet 和纯背景;如果只把 `aiRedraw=false` 当作“不参考图片生成”,就会误走首图生成。
- 处理:入口页用 payload 的 `aiRedraw` 写入生成页 metadata`puzzleAiRedraw=false` 时进度跳过 `生成拼图首图`;后端 `compile_puzzle_draft` 和结果页 `generate_puzzle_images` 都在 `aiRedraw=false && referenceImageSrc 非空` 时走上传图直用候选。结果页关卡详情必须复用 `CreativeImageInputPanel`,不要把正式图当成可重绘参考图;本次上传或历史选择的图才显示 AI 重绘开关并可删除。
- 验证:`npm run test -- src/services/miniGameDraftGenerationProgress.test.ts src/components/puzzle-result/PuzzleResultView.test.tsx``cargo test -p api-server puzzle_result_level_direct_upload_skips_cover_image_generation --manifest-path server-rs\Cargo.toml`
- 关联:`src/services/miniGameDraftGenerationProgress.ts``src/components/puzzle-agent/PuzzleAgentWorkspace.tsx``src/components/puzzle-result/PuzzleResultView.tsx``server-rs/crates/api-server/src/puzzle/draft.rs``server-rs/crates/api-server/src/puzzle/generation.rs`
## Jenkins 数据库导入导出脚本先补 Node 工具链 PATH
- 现象:`Genarrative-Database-Import``Genarrative-Database-Export` 运行到迁移脚本时,`bash``node: command not found`,常见在日志里表现为某个 `sh` 块内第 61 行直接调用 `node` 失败。
@@ -1135,3 +1251,11 @@
- 处理:优先切换到拥有目标库权限的 SpacetimeDB 身份后重新运行 `npm run dev` 完成发布;若只是本地验证,可用 gitignored 的 `spacetime.local.json` 指向可发布的本地库。debug 构建的 `api-server` 对入口配置缺 procedure 会使用后端默认入口配置兜底,避免作品架因本地库漂移整块空白。
- 验证:`curl.exe -i http://127.0.0.1:8082/api/creation-entry/config` 返回 `200` 且包含 `baby-object-match`;前端草稿页作品架重新渲染。
- 关联:`server-rs/crates/api-server/src/state.rs``server-rs/crates/api-server/src/creation_entry_config.rs``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
## 存档选择入口不要只藏在“玩过”弹窗里
- 现象:用户有 RPG / 拼图运行态存档,但平台底部 `草稿` Tab 只展示作品架,个人中心只有点击 `玩过` 后才可能看到“可继续”,导致看起来没有存档选择入口。
- 原因:`/api/profile/save-archives` 已在入口 bootstrap 加载,但前端只把 `saveEntries` 注入 `ProfilePlayedWorksModal`;没有独立的存档入口。
- 处理:个人中心 `常用功能` 必须保留 `存档` 快捷入口,点击后打开独立存档选择弹窗并复用 `SaveArchiveCard`;恢复仍走 `/api/profile/save-archives/{worldKey}`,拼图存档继续走拼图 resume 分支RPG 走 `handleContinueGame(snapshot)`
- 验证:`npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "profile page exposes save archive picker"`
- 关联:`src/components/rpg-entry/RpgEntryHomeView.tsx``src/components/platform-entry/PlatformEntryFlowShellImpl.tsx``src/components/rpg-entry/useRpgEntryBootstrap.ts``docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`