Merge remote-tracking branch 'origin/master' into hermes/wechat

# Conflicts:
#	.hermes/shared-memory/pitfalls.md
#	.hermes/todos/【后端架构】api-server能力模块化与图片资产Adapter收口计划-2026-05-14.md
This commit is contained in:
2026-05-15 01:28:04 +08:00
266 changed files with 23417 additions and 4373 deletions

View File

@@ -0,0 +1,549 @@
# Bark Battle Phase 2 Platform Work Loop Implementation Plan
> **For Hermes:** Use subagent-driven-development skill to implement this plan task-by-task.
**Goal:**`bark-battle` 从内部试玩 demo 升级为 Genarrative 正式 play type打通轻创作配置、发布态作品、正式 runtime、run start / finish、后端裁决、个人历史、作品统计和最小排行榜闭环。
**Architecture:** 先冻结 shared contracts 与 `module-bark-battle` 纯领域规则,再落 SpacetimeDB 表/reducer、`spacetime-client` facade 和 `api-server` BFF随后接前端最小纵切最后补排行榜/个人历史/作品统计投影体验。前端只承接表现、交互和临时 UI 状态,正式业务真相由后端裁决。
**Tech Stack:** React + TypeScript + Vite, server-rs + Axum, SpacetimeDB Rust module, shared-contracts, Vitest, Cargo tests, npm scripts.
---
## 0. 已确认决策
1. “有效叫声”统一为 **有效声浪触发**:当前采样响度达到有效阈值且满足 `minBarkGapMs` 冷却即触发;不再要求 `minBarkDurationMs` / `maxBarkDurationMs`,也不等待响度回落。
2. Phase 2 范围是 **Bark Battle 平台作品闭环**,不是单纯玩法表现深化。
3. 作品形态是 **轻创作配置作品**:标题、描述、主题/背景预设、狗狗皮肤预设、难度预设、排行榜开关。
4. 难度预设只影响 AI 对手行为;不影响有效阈值、冷却、时长、分数公式或反作弊阈值。
5. 排行榜按 `workId + difficultyPreset + rulesetVersion` 分榜。
6. 后端裁决正式单局结果;前端只提交派生指标,`clientResult` 只用于 debug/对账。
7. 排行榜只收录 `serverResult = player_win` 且未被反作弊拒绝的单局结果,排序以 `finalEnergy` 优先。
8. 作品统计使用最小后端投影start、finish、win/draw/loss、flagged、leaderboard、best/avg energy。
9. 个人历史成绩 = 最近记录列表 + 个人最佳摘要;仅本人可见。
10. 正式入口闭环覆盖创作入口、作品详情 CTA、广场/作品卡片、我的作品、稳定作品 ID runtime 路由和 `work_play_start`
11. 创作编辑形态是单页轻配置表单 + 预览卡片。
12. 实施顺序固定为:契约与领域规则 → SpacetimeDB 表/reducer 与 api-server BFF → 最小前端纵切 → 投影与列表体验 → 收口验证。
---
## 1. 必读文档与约束
实施前先读:
- `AGENTS.md`
- `CONTEXT.md`
- `docs/prd/BARK_BATTLE_BDD_2026-05-11.md`
- `docs/technical/BARK_BATTLE_BACKEND_DDD_TECHNICAL_PLAN_2026-05-11.md`
- `docs/technical/BARK_BATTLE_2D_RUNTIME_TECHNICAL_PLAN_2026-05-11.md`
- `docs/technical/SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md`
- `docs/technical/SERVER_RS_DDD_FULL_REFACTOR_2026-04-28.md`
- `.codex/skills/spacetimedb-cli/SKILL.md`
- `.codex/skills/spacetimedb-rust/SKILL.md`
- `.codex/skills/spacetimedb-concepts/SKILL.md`
- `.codex/skills/spacetimedb-typescript/SKILL.md`
关键约束:
- 后端路线固定 `server-rs + Axum + SpacetimeDB`
- 领域规则进 `module-bark-battle`SpacetimeDB 表和事务编排进 `spacetime-module`
- HTTP/SSE/BFF 留在 `api-server`
- 前后端 DTO 留在 `shared-contracts`
- 数据库表结构更改必须同步 `migration.rs` 和生成绑定。
- 人工命令/文档示例禁止继续使用 `spacetime --root-dir`
- 修改中文文件后必须跑 `npm run check:encoding`
---
## 2. 阶段一:契约与领域规则
### Task 1.1: 新增 Rust shared-contracts 模块
**Objective:** 定义 Bark Battle Phase 2 的 Rust DTO 边界。
**Files:**
- Create: `server-rs/crates/shared-contracts/src/bark_battle.rs`
- Modify: `server-rs/crates/shared-contracts/src/lib.rs`
- Test: `server-rs/crates/shared-contracts/src/bark_battle.rs`
**Steps:**
1. 新增枚举:`BarkBattleDifficultyPreset { Easy, Normal, Hard }``BarkBattleServerResult { PlayerWin, OpponentWin, Draw }``BarkBattleFinishStatus { Accepted, AcceptedWithFlags, Rejected }`
2. 新增配置 DTO`BarkBattleDraftConfig``BarkBattlePublishedConfig``BarkBattleRuntimeConfig`
3. 新增 run DTO`BarkBattleRunStartRequest/Response``BarkBattleRunFinishRequest/Response`
4. 新增派生指标 DTO`BarkBattleDerivedMetrics`,字段包含 `trigger_count``max_volume``average_volume``final_energy``combo_max`
5. 新增排行榜/历史/统计 DTO`BarkBattleLeaderboardEntry``BarkBattlePersonalHistoryItem``BarkBattlePersonalBestSummary``BarkBattleWorkStats`
6.`lib.rs` 导出 `pub mod bark_battle;`
**Verification:**
```bash
cargo test -p shared-contracts bark_battle
```
Expected: contracts tests pass.
### Task 1.2: 新增 TypeScript shared contracts mirror
**Objective:** 让前端获得与 Rust DTO 对齐的类型。
**Files:**
- Create: `packages/shared/src/contracts/barkBattle.ts`
- Modify: `packages/shared/src/contracts/index.ts`
- Test: `packages/shared/src/contracts/barkBattle.test.ts`
**Steps:**
1. 定义 `BarkBattleDifficultyPreset = 'easy' | 'normal' | 'hard'`
2. 定义 `BarkBattleServerResult = 'player_win' | 'opponent_win' | 'draw'`
3. 定义 draft / published / runtime config 类型。
4. 定义 start / finish request response 类型。
5. 定义 leaderboard / personal history / work stats 类型。
6. 写最小序列化/fixture 测试,确保字段命名采用前端约定 camelCase并在 API client 层做必要映射。
**Verification:**
```bash
npm test -- --run packages/shared/src/contracts/barkBattle.test.ts
npx tsc -p tsconfig.typecheck-guardrails.json --noEmit --pretty false
```
### Task 1.3: 新建 module-bark-battle crate
**Objective:** 将正式裁决规则放入纯领域 crate。
**Files:**
- Create: `server-rs/crates/module-bark-battle/Cargo.toml`
- Create: `server-rs/crates/module-bark-battle/src/lib.rs`
- Create: `server-rs/crates/module-bark-battle/src/domain.rs`
- Create: `server-rs/crates/module-bark-battle/src/scoring.rs`
- Modify: `server-rs/Cargo.toml`
**Steps:**
1. 在 workspace 中注册 `module-bark-battle`
2. 定义 `RulesetVersion`,首版固定如 `bark-battle-ruleset-v1`
3. 定义 `BarkBattleRuleset`,包含标准局时长 30s、`min_bark_gap_ms`、合法音量/能量/连击范围、duration tolerance。
4. 实现 `validate_finish_metrics()`
5. 实现 `adjudicate_result()`:以后端 `final_energy` 和 draw threshold 生成 `serverResult`
6. 实现 `compute_leaderboard_score()`:只允许胜利局入榜,排序因子为 `finalEnergy``triggerCount``maxVolume`、duration 接近度、`finishedAt`
**Verification:**
```bash
cargo test -p module-bark-battle
```
### Task 1.4: 领域规则单测覆盖作弊边界
**Objective:** 防止前端伪造 finish 直接刷榜。
**Files:**
- Modify: `server-rs/crates/module-bark-battle/src/scoring.rs`
**Test cases:**
- 28s-35s 合法窗口内可接受。
- 1s / 300s 应 rejected 或 flagged。
- `triggerCount > durationMs / minBarkGapMs + tolerance` 应 flagged。
- `finalEnergy` 越界应 rejected。
- 平/负不生成 leaderboard entry。
- easy/normal/hard 不改变阈值、冷却、分数公式,只改变 AI preset key。
**Verification:**
```bash
cargo test -p module-bark-battle -- --nocapture
```
---
## 3. 阶段二SpacetimeDB 表/reducer 与 api-server BFF
### Task 2.1: 设计 SpacetimeDB 表目录
**Objective:** 新增 Bark Battle 表并与 migration 对齐。
**Files:**
- Create: `server-rs/crates/spacetime-module/src/bark_battle/mod.rs`
- Create: `server-rs/crates/spacetime-module/src/bark_battle/types.rs`
- Create: `server-rs/crates/spacetime-module/src/bark_battle/tables.rs`
- Modify: `server-rs/crates/spacetime-module/src/lib.rs`
- Modify: `server-rs/crates/spacetime-module/src/migration.rs`
**Tables:**
- `bark_battle_draft_config`
- `bark_battle_published_config`
- `bark_battle_runtime_run`
- `bark_battle_score_record`
- `bark_battle_leaderboard_entry`
- `bark_battle_work_stats_projection`
- `bark_battle_personal_best_projection`
**Pitfalls:**
- 表结构不要 derive `SpacetimeType`
- reducer 使用 `&ReducerContext`
- 授权身份来自 `ctx.sender()`
- 需要公开订阅的表才加 `public`
**Verification:**
```bash
cargo test -p spacetime-module
```
### Task 2.2: 实现草稿/发布 reducer
**Objective:** 支持轻配置草稿保存和发布态 config 固化。
**Reducers:**
- `create_bark_battle_draft`
- `update_bark_battle_draft_config`
- `publish_bark_battle_work`
- `get_bark_battle_runtime_config` 如仓库约定使用 reducer/procedure 查询则按现有 pattern 实现。
**Rules:**
- 草稿配置只允许标题、描述、主题/背景预设、狗狗皮肤预设、难度预设、排行榜开关。
- 发布生成稳定作品 ID / config version。
- 发布态 config 包含 `rulesetVersion`
**Verification:**
```bash
cargo test -p spacetime-module bark_battle
```
### Task 2.3: 实现 run start / finish reducer
**Objective:** 打通正式运行态后端事务。
**Reducers:**
- `start_bark_battle_run`
- `finish_bark_battle_run`
- `get_bark_battle_run`
**Rules:**
- start 创建 `run_id` 和一次性 `run_token`
- start 记录 work/config/ruleset/difficulty 快照。
- finish 必须校验 run token、未 finish、work/config/ruleset/difficulty 一致。
- finish 调用 `module-bark-battle` 裁决结果。
- accepted 写 score record。
- `serverResult = player_win` 且排行榜开启且未 rejected 时写 leaderboard entry。
- accepted / accepted_with_flags 更新 work stats 和 personal best projection。
**Verification:**
```bash
cargo test -p spacetime-module bark_battle_run
```
### Task 2.4: 更新 migration 与生成绑定
**Objective:** 让 SpacetimeDB 表结构变更可发布。
**Files:**
- Modify: `server-rs/crates/spacetime-module/src/migration.rs`
- Generated: `server-rs/crates/spacetime-client/src/module_bindings/*bark*`
**Commands:**
按仓库现有脚本优先;不要手改 generated bindings。
```bash
npm run spacetime:build
npm run spacetime:generate
```
若脚本名不同,先查 `package.json``server-rs` README。
### Task 2.5: 实现 spacetime-client facade
**Objective:** api-server 不直接操作 generated bindings。
**Files:**
- Create: `server-rs/crates/spacetime-client/src/bark_battle.rs`
- Modify: `server-rs/crates/spacetime-client/src/lib.rs`
**Methods:**
- `create_bark_battle_draft`
- `save_bark_battle_draft_config`
- `publish_bark_battle_work`
- `get_bark_battle_runtime_config`
- `start_bark_battle_run`
- `finish_bark_battle_run`
- `list_bark_battle_leaderboard`
- `list_my_bark_battle_history`
- `get_my_bark_battle_best_summary`
- `get_bark_battle_work_stats`
**Verification:**
```bash
cargo test -p spacetime-client bark_battle
```
### Task 2.6: 实现 api-server BFF 路由
**Objective:** 暴露前端需要的 HTTP API。
**Files:**
- Create: `server-rs/crates/api-server/src/bark_battle.rs`
- Modify: `server-rs/crates/api-server/src/app.rs`
**Routes:**
- `POST /api/bark-battle/drafts`
- `PATCH /api/bark-battle/drafts/:draftId`
- `POST /api/bark-battle/drafts/:draftId/publish`
- `GET /api/bark-battle/works/:workId/runtime-config`
- `POST /api/bark-battle/runs/start`
- `POST /api/bark-battle/runs/:runId/finish`
- `GET /api/bark-battle/works/:workId/leaderboard`
- `GET /api/bark-battle/me/history`
- `GET /api/bark-battle/me/best-summary`
- `GET /api/bark-battle/works/:workId/stats`
**Verification:**
```bash
cargo test -p api-server bark_battle
npm run api-server
curl -f http://127.0.0.1:<api-port>/healthz
```
---
## 4. 阶段三:最小前端纵切
### Task 3.1: 新增前端 service client
**Files:**
- Create: `src/services/bark-battle/barkBattleClient.ts`
- Test: `src/services/bark-battle/barkBattleClient.test.ts`
**Methods:** 与 BFF routes 一一对应。
**Verification:**
```bash
npm test -- --run src/services/bark-battle/barkBattleClient.test.ts
```
### Task 3.2: 接入创作入口与 SelectionStage
**Files:**
- Modify: `src/config/newWorkEntryConfig.ts`
- Modify: `src/components/platform-entry/platformEntryCreationTypes.ts`
- Modify: `src/components/platform-entry/platformEntryTypes.ts`
- Modify: `src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`
**Rules:**
- 新增 `bark-battle` play type。
- 入口打开单页轻配置表单,不走复杂 agent workspace。
- 移动端入口布局不能溢出。
### Task 3.3: 实现单页轻配置表单 + 预览卡片
**Files:**
- Create: `src/components/bark-battle-creation/BarkBattleConfigEditor.tsx`
- Create: `src/components/bark-battle-creation/BarkBattlePreviewCard.tsx`
- Test: `src/components/bark-battle-creation/BarkBattleConfigEditor.test.tsx`
**UI fields:**
- 标题必填
- 简介选填
- 主题/背景预设
- 狗狗皮肤预设
- 难度预设,默认 `normal`
- 排行榜开关,默认开启
**UI constraints:**
- 不堆大段玩法说明。
- 按现有游戏 UI 风格设计。
- 移动端优先。
### Task 3.4: 发布后进入作品详情
**Files:**
- Modify: `src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`
- Modify: `src/components/platform-entry/PlatformWorkDetailView.tsx`
- Modify: `src/components/custom-world-home/CustomWorldCreationHub.tsx`
- Modify: `src/components/custom-world-home/creationWorkShelf.ts`
**Rules:**
- 发布成功刷新 works/gallery/shelf。
- 跳作品详情。
- 详情 CTA 可以进入正式 runtime。
### Task 3.5: runtime 拉发布态 config 并 start / finish
**Files:**
- Modify: `src/games/bark-battle/*`
- Modify: `src/games/bark-battle/ui/BarkBattleRuntimeShell.tsx`
- Create/Modify: `src/components/bark-battle-runtime/BarkBattleRuntimeRoute.tsx` 如需要
**Rules:**
- runtime 通过稳定 `workId``BarkBattleRuntimeConfig`
- 开始正式局时调用 start run。
- 结束时提交 finish 派生指标。
- 结算展示 `serverResult``scoreSummary``antiCheatFlags`、leaderboard entry。
- 麦克风原始音频不上传。
**Verification:**
```bash
npm test -- --run src/games/bark-battle/domain/__tests__/BarkDetector.test.ts src/games/bark-battle/application/__tests__/BarkBattleController.test.ts src/games/bark-battle/ui/__tests__/BarkBattleRuntimeShell.test.tsx
```
---
## 5. 阶段四:投影与列表体验
### Task 4.1: 排行榜 UI
**Files:**
- Create: `src/components/bark-battle-leaderboard/BarkBattleLeaderboardPanel.tsx`
- Test: `src/components/bark-battle-leaderboard/BarkBattleLeaderboardPanel.test.tsx`
- Modify: `src/components/platform-entry/PlatformWorkDetailView.tsx`
**Rules:**
- 查询维度 `workId + difficultyPreset + rulesetVersion`
- 只展示胜利入榜成绩。
- 不展示平/负/flagged 历史。
### Task 4.2: 个人历史最近记录 + 最佳摘要 UI
**Files:**
- Create: `src/components/bark-battle-history/BarkBattlePersonalHistoryPanel.tsx`
- Test: `src/components/bark-battle-history/BarkBattlePersonalHistoryPanel.test.tsx`
**Rules:**
- 默认最近 20 条。
- 仅本人可见。
- 可按 workId / difficultyPreset 过滤。
- flagged 只做轻提示,不展示详细反作弊原因。
### Task 4.3: 作品统计展示
**Files:**
- Create: `src/components/bark-battle-stats/BarkBattleWorkStatsPanel.tsx`
- Test: `src/components/bark-battle-stats/BarkBattleWorkStatsPanel.test.tsx`
**Fields:**
- `playStartCount`
- `finishCount`
- `winCount`
- `drawCount`
- `lossCount`
- `flaggedCount`
- `leaderboardEntryCount`
- `bestLeaderboardScore`
- `bestFinalEnergy`
- `averageFinalEnergy`
- `updatedAt`
### Task 4.4: 广场卡片/我的作品适配
**Files:**
- Modify: `src/components/custom-world-home/creationWorkShelf.ts`
- Modify: `src/components/custom-world-home/CustomWorldCreationHub.tsx`
- Modify: `src/components/rpg-entry/rpgEntryWorldPresentation.ts`
- Modify: `src/services/publicWorkCode.ts` 如分享码需要支持
**Rules:**
- Bark Battle 作品能展示、打开详情、开始游玩。
- 不新增独立 Bark Battle 专区。
---
## 6. 阶段五:收口验证
### Task 5.1: 自动测试清单
```bash
cargo test -p shared-contracts bark_battle
cargo test -p module-bark-battle
cargo test -p spacetime-module bark_battle
cargo test -p spacetime-client bark_battle
cargo test -p api-server bark_battle
npm test -- --run packages/shared/src/contracts/barkBattle.test.ts
npm test -- --run src/services/bark-battle/barkBattleClient.test.ts
npm test -- --run src/components/bark-battle-creation/BarkBattleConfigEditor.test.tsx
npm test -- --run src/games/bark-battle/domain/__tests__/BarkDetector.test.ts src/games/bark-battle/application/__tests__/BarkBattleController.test.ts src/games/bark-battle/ui/__tests__/BarkBattleRuntimeShell.test.tsx
npx tsc -p tsconfig.typecheck-guardrails.json --noEmit --pretty false
npm run check:encoding
git diff --check
```
### Task 5.2: 后端 smoke
1. 按项目脚本启动 SpacetimeDB + api-server优先使用 `npm run api-server`,不要使用旧命令。
2. 确认 `/healthz`
3. smoke 流程:创建草稿 → 保存配置 → 发布 → 拉 runtime config → start run → finish run → 查询 leaderboard/history/stats。
### Task 5.3: 人工验收路径
1. 进入创作入口/玩法选择,选择 Bark Battle。
2. 在单页轻配置表单中填写标题,选择主题、狗狗皮肤、难度,保持排行榜开启。
3. 保存草稿。
4. 发布作品。
5. 发布后自动进入作品详情。
6. 点击开始游玩进入正式 runtime。
7. 授权麦克风,完成 30 秒单局。
8. 结算页显示后端 `serverResult` 和 score summary。
9. 若胜利,排行榜出现本局成绩。
10. 我的记录显示最近记录和个人最佳摘要。
11. 作品详情/作者视角能看到作品统计。
12. 广场/作品卡片和我的作品入口都能再次进入详情和 runtime。
---
## 7. 不做范围
- 不做实时多人。
- 不做 ghost replay。
- 不做 AI 狗叫识别。
- 不保存原始音频、PCM、waveform 或可还原语音内容。
- 不做独立 Bark Battle 专区/活动页。
- 不做挑战分享、好友邀请、多人数房间。
- 不做复杂编辑器、多步骤向导、规则参数编辑、AI 生成配置。
- 不做 DAU/留存、按小时统计曲线、好友对比。
---
## 8. 三人并行建议
### 开发者 A后端契约与领域规则
负责 Task 1.1、1.3、1.4。先提交 contracts 与 `module-bark-battle`,为后续后端/前端提供稳定类型和裁决规则。
### 开发者 BSpacetimeDB + api-server
负责 Task 2.1 到 2.6。必须等开发者 A 的 DTO/领域规则基本稳定后开始,或先基于计划字段开分支实现表结构。
### 开发者 C前端纵切与 UI
负责 Task 3.x 与 4.x。开始时可先做组件空态和 service client 类型,真正联调等 B 的 BFF ready。
---
## 9. 推荐提交节奏
1. `feat: add bark battle contracts and domain rules`
2. `feat: add bark battle spacetime tables and reducers`
3. `feat: add bark battle api server routes`
4. `feat: add bark battle creation editor`
5. `feat: connect bark battle runtime to server results`
6. `feat: add bark battle leaderboard history stats`
7. `docs: finalize bark battle phase2 verification guide`
---
## 10. 完成定义
Phase 2 完成必须同时满足:
- Bark Battle 可以从正式创作入口创建轻配置作品。
- 作品可以发布为稳定 workId。
- 作品详情/广场/我的作品可以发现并进入正式 runtime。
- runtime 从后端发布态 config 拉配置。
- start run 写 `work_play_start`
- finish 只上传派生指标。
- 后端裁决 `serverResult` / `scoreSummary` / `leaderboardScore` / `antiCheatFlags`
- 胜利局进入按 `workId + difficultyPreset + rulesetVersion` 分榜的排行榜。
- 个人历史和作品统计可查询。
- 自动测试、encoding、typecheck、diff check 和人工验收路径通过。

View File

@@ -16,14 +16,38 @@
---
## 2026-05-14 抓大鹅物品素材 sheet 改用 APIMart nanobanana
## 2026-05-14 抓大鹅物品素材批量重新生成复用 item-assets 替换模式
- 背景:抓大鹅 2D 五视角物品素材仍沿用 5x5 sheet、绿幕去背、切图、OSS 转存和 `generatedItemAssets` 持久化,但用户要求物品素材图片生成步骤改用 APIMart 已接好的 nanobanana / Gemini 图片模型
- 决策:抓大鹅物品素材 sheet 生图固定走 APIMart `POST {APIMART_BASE_URL}/images/generations`,模型为 `gemini-3.1-flash-image-preview``size = 1:1``resolution = 1K``official_fallback = true`;响应优先读图片 URL 或 base64缺图片时按 `task_id` 轮询 `/tasks/{task_id}`。封面、9:16 纯背景图、1:1 容器 UI 图、音频、切图、OSS、扣费和运行态消费链路保持不变
- 影响范围:`server-rs/crates/api-server/src/match3d.rs``server-rs/crates/api-server/src/config.rs``deploy/env/api-server.env.example`、抓大鹅素材生成技术文档。
- 验证方式:执行 `cargo test -p api-server match3d_material_sheet --manifest-path server-rs\Cargo.toml``cargo test -p api-server from_env_reads_non_public_models_and_urls --manifest-path server-rs\Cargo.toml``cargo check -p api-server --manifest-path server-rs\Cargo.toml``npm run check:encoding`
- 背景:抓大鹅结果页 `素材配置 > 物品` 需要在不改变玩法物品映射的前提下,批量重新生成已存在物品的 2D 五视角图片
- 决策:继续复用 `POST /api/creation/match3d/works/{profileId}/item-assets`,请求体通过 `mode = "replace"` 表达替换模式;前端面板预填当前素材名称,只提交仍能匹配到已有素材的名称。后端只替换匹配素材的 `imageSrc/imageObjectKey/imageViews/status/error`,保留原 `itemId`、列表顺序、模型兼容字段、UI 背景、历史背景音乐和点击音效字段;未匹配名称不计费、不新增、不持久化
- 影响范围:Match3D 结果页素材配置、前端/后端 shared contracts、`api-server` Match3D item-assets 编排、运行态物品类型映射和素材生成技术文档。
- 验证方式:执行 `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``npm run check:encoding`
- 关联文档:`docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`
## 2026-05-14 拼图与抓大鹅音频生成入口临时关闭
- 背景:当前需要暂时关闭抓大鹅、拼图中生成背景音乐和音效的能力,并隐藏草稿中的相关入口。
- 决策:拼图 `compile_puzzle_draft` 不再自动生成背景音乐,结果页素材配置只保留 `UI`;抓大鹅 `match3d_compile_draft` 和批量新增只生成 2D 图片、背景和容器 UI不再调用 Suno/Vidu结果页隐藏 `背景音乐` 子 Tab 与点击音效生成控件;通用 `/api/creation/audio/*` 当前整体返回 `410 Gone`。历史已写入的 `backgroundMusic` / `clickSound` 字段保留,运行态继续兼容播放旧音频。
- 影响范围:`api-server` 拼图/抓大鹅草稿编排、通用创作音频路由、拼图/抓大鹅结果页、生成进度模型、相关技术文档。
- 验证方式:执行拼图/抓大鹅结果页定向测试、生成进度单测、`cargo check -p api-server --manifest-path server-rs/Cargo.toml``npm run check:encoding`
- 关联文档:`docs/technical/PUZZLE_MATCH3D_RESULT_AUDIO_TAB_2026-05-11.md``docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`
## 2026-05-14 抓大鹅物品素材 sheet 改用 VectorEngine Gemini
- 背景:抓大鹅 2D 五视角物品素材仍沿用 5x5 sheet、绿幕去背、切图、OSS 转存和 `generatedItemAssets` 持久化,但用户要求物品素材图片生成步骤改用 VectorEngine Apifox `api-381740608` 对应的 Gemini 原生图片接口。
- 决策:抓大鹅物品素材 sheet 生图固定走 VectorEngine `POST {VECTOR_ENGINE_BASE_URL}/v1beta/models/gemini-3-pro-image-preview:generateContent?key={VECTOR_ENGINE_API_KEY}`,请求体使用 `contents[].parts[].text``generationConfig.responseModalities = ["TEXT", "IMAGE"]``imageConfig.aspectRatio = "1:1"`;响应从 `candidates[].content.parts[].inlineData.data` / `inline_data.data` 读取 base64 图片。封面、9:16 纯背景图、1:1 容器 UI 图、切图、OSS、扣费和运行态消费链路保持不变音频以后续“拼图与抓大鹅音频生成入口临时关闭”决策为准。
- 影响范围:`server-rs/crates/api-server/src/match3d.rs``server-rs/crates/api-server/src/config.rs``deploy/env/api-server.env.example`、抓大鹅素材生成技术文档。
- 验证方式:执行 `cargo test -p api-server match3d_material_sheet --manifest-path server-rs\Cargo.toml``cargo test -p api-server match3d_vector_engine_gemini --manifest-path server-rs\Cargo.toml``cargo check -p api-server --manifest-path server-rs\Cargo.toml``npm run check:encoding`
- 关联文档:`docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`
## 2026-05-14 草稿页作品卡对齐分类页列表
- 背景:草稿页作品架原本偏封面大卡片,和发现页分类列表的横向卡片样式不一致;生成中状态也缺少整卡级的统一遮罩。
- 决策:草稿页作品卡统一收口为与分类页一致的横向列表卡结构,左侧承载标题/状态/类型/摘要与必要数据,右侧显示带透明度的封面图;移动端保持单列列表,网页端使用两到三列卡片式网格,避免宽屏长条列表。不再常驻“继续创作”“查看详情”“查看进度”等右侧动作按钮。原有删除、分享、积分激励、公开统计、未读红点全部保留,其中删除与分享进入左滑操作层,常态不显示删除按钮,也不得透出删除底层。生成中的作品在整卡上加半透明蒙版、旋转等待符号和“生成中...”标识,但不移除任何原有信息。
- 影响范围:`src/components/custom-world-home/CustomWorldCreationHub.tsx``src/components/custom-world-home/CustomWorldWorkCard.tsx`、相关样式与测试、草稿页 UI 文档。
- 验证方式:草稿页作品卡与分类页列表视觉口径保持一致;`npm run test -- src/components/custom-world-home/CustomWorldCreationHub.test.tsx src/components/custom-world-home/CustomWorldCreationHub.interaction.test.tsx``npm run typecheck``npm run check:encoding`
- 关联文档:`docs/design/MOBILE_CREATION_WORK_LIST_TWO_COLUMN_LAYOUT_2026-04-29.md``docs/experience/MOBILE_UI_DEV_EXPERIENCE.md`
## 2026-05-13 认证运行期同步直接导入正式认证表
- 背景:`auth_store_snapshot` 是 Stage 1 整包快照过渡表,主键固定 `default`,会让所有用户状态集中在一条 `snapshot_json`Stage 2/3 已有 `user_account/auth_identity/refresh_session` 正式认证表,继续刷新 `default` 容易让运行时真相和表拆分目标混在一起。
@@ -82,8 +106,8 @@
## 2026-05-12 抓大鹅结果页素材编辑统一走作品级资产面板
- 背景:抓大鹅结果页需要支持面图上传 / AI 重绘、物品素材独立预览、单项删除和批量新增,且不能把素材编辑继续做成列表内联展开或前端临时状态。
- 决策:结果页 `作品信息`面图点击打开独立面板,参考图可来自本地上传、物品素材和 UI 素材AI 重绘统一调用 `POST /api/creation/match3d/works/{profileId}/cover-image` 并转存到 `generated-match3d-assets``素材配置 > 物品` 列表项点击打开独立预览面板,不再提供单项重新生成按钮;单项删除和批量新增都写回同一份 `generated_item_assets_json`。批量新增调用 `POST /api/creation/match3d/works/{profileId}/item-assets`,复用草稿生成的 2D 素材图、5x5 切图、OSS 上传和可选点击音效链路,仅作用于新增物品,不新增 SpacetimeDB 表。
- 背景:抓大鹅结果页需要支持面图上传 / AI 重绘、物品素材独立预览、单项删除和批量新增,且不能把素材编辑继续做成列表内联展开或前端临时状态。
- 决策:结果页 `作品信息`面图点击打开独立面板,封面图面板对齐拼图入口上传卡。已有上传主图时,请求体传 `uploadedImageSrc`AI 重绘走 VectorEngine `/v1/images/edits`;关闭 AI 重绘时只写回上传图,不调用生图。没有上传主图时,请求体传 `referenceImageSrcs`,可混合本地上传、物品素材和 UI 素材,多参考图作为 `gpt-image-2-all` generations 的 `image` 数组传入。生成结果统一调用 `POST /api/creation/match3d/works/{profileId}/cover-image` 并转存到 `generated-match3d-assets``素材配置 > 物品` 列表项点击打开独立预览面板,不再提供单项重新生成按钮;单项删除和批量新增都写回同一份 `generated_item_assets_json`。批量新增调用 `POST /api/creation/match3d/works/{profileId}/item-assets`,复用草稿生成的 2D 素材图、5x5 切图、OSS 上传和可选点击音效链路,仅作用于新增物品,不新增 SpacetimeDB 表。
- 影响范围Match3D 结果页、Match3D works shared contracts、`api-server` Match3D 作品路由、生成资产历史类型和草稿恢复路径。
- 验证方式:执行 `npm run test -- src/components/match3d-result/Match3DResultView.test.tsx``npm run typecheck``cargo test -p api-server match3d --manifest-path server-rs/Cargo.toml``cargo check -p api-server --manifest-path server-rs/Cargo.toml``npm run check:encoding`
- 关联文档:`docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`
@@ -103,6 +127,22 @@
- 验证方式:执行 `npm run check:encoding``node scripts/check-wechat-miniprogram-auth-smoke.mjs``cargo test -p shared-contracts wechat_bind_phone_request_accepts_mini_program_phone_code --manifest-path server-rs/Cargo.toml``cargo test -p api-server wechat_miniprogram_bind_phone_code_activates_pending_user --manifest-path server-rs/Cargo.toml -- --nocapture`
- 关联文档:`docs/technical/WECHAT_MINIPROGRAM_WEB_VIEW_SHELL_2026-05-03.md`
## 2026-05-13 宝贝爱画先作为寓教于乐独立本地 Demo 落地
- 背景:第三关 `宝贝爱画` 需要默认出现在“发现 / 寓教于乐”板块下方,但本阶段只验证画板、手部绘制、绘画魔法和本地保存闭环,不进入创作模板、公开作品或正式持久化。
- 决策:`baby-love-drawing / 宝贝爱画` 先作为独立运行态接入,入口由发现页寓教于乐默认卡片打开,并支持 `/runtime/baby-love-drawing` 直达;关闭 `VITE_ENABLE_EDUTAINMENT_ENTRY` 时前端不展示频道/卡片且直达路由回落主应用。绘画魔法统一走 `POST /api/creation/edutainment/baby-love-drawing/magic` 后端安全代理,使用 VectorEngine `gpt-image-2-all` 与原始画布 Data URL 参考图生成绘本风图片;保存只写 localStorage正式持久化后续再设计。
- 影响范围:`packages/shared/src/contracts/edutainmentBabyDrawing.ts``src/components/edutainment-runtime/BabyLoveDrawingRuntimeShell.tsx``src/services/edutainment-baby-drawing/``src/routing/appRoutes.tsx``src/components/rpg-entry/RpgEntryHomeView.tsx``server-rs/crates/api-server/src/edutainment_baby_drawing.rs``src/index.css`、宝贝爱画 PRD 与技术方案。
- 验证方式:执行宝贝爱画 model/runtime/service/route 定向测试、`npm run typecheck`、定向 ESLint、`cargo test -p api-server edutainment_baby_drawing --manifest-path server-rs/Cargo.toml``cargo test -p api-server resolves_runtime_paths_to_creation_type_ids --manifest-path server-rs/Cargo.toml` 和编码检查;真实魔法生成需配置 `VECTOR_ENGINE_BASE_URL``VECTOR_ENGINE_API_KEY`
- 关联文档:`docs/prd/BABY_LOVE_DRAWING_EDUTAINMENT_LEVEL_PRD_2026-05-13.md``docs/technical/BABY_LOVE_DRAWING_RUNTIME_DEMO_IMPLEMENTATION_2026-05-13.md`
## 2026-05-12 宝贝识物创作同时生成玩法视觉主题包
- 背景:`宝贝识物` 创作原本只根据两个关键词生成物品透明图运行态背景、UI、礼物盒和篮子仍使用固定 CSS 绘本风,无法根据“小猪佩琪 / 奥特曼”或“苹果 / 橘子”等创作者提示词做主题化包装。
- 决策:`POST /api/creation/edutainment/baby-object-match/assets` 同一次 image-2 / VectorEngine 调用链返回两个物品图和 `visualPackage`。视觉包包含 `background``ui-frame``gift-box``basket``smoke-puff` 五类资源;总风格保持寓教于乐明亮卡通绘本插画风,主题按两个物品关键词匹配,水果偏果园自然,动漫角色 / 玩具偏动漫玩具。物品图和礼物盒 / 篮子 / UI / 烟雾特效资源走透明 PNG 后处理,背景为清爽不遮挡玩法区的环境图;运行态中礼物盒按约 2 倍视觉尺寸展示、篮子按约 1.5 倍展示,礼物盒打开时使用 `smoke-puff` 弹出中央物品并移除礼盒。前端草稿保存该包,运行态消费该包;旧草稿以 `visualPackage = null` 继续使用 CSS 兜底。
- 影响范围:`packages/shared/src/contracts/edutainmentBabyObject.ts``server-rs/crates/api-server/src/edutainment_baby_object.rs``src/services/edutainment-baby-object/babyObjectMatchClient.ts``src/components/edutainment-runtime/BabyObjectMatchRuntimeShell.tsx``src/index.css`、宝贝识物 PRD 与技术方案。
- 验证方式:执行宝贝识物 service / runtime 定向测试、`cargo test -p api-server edutainment_baby_object --manifest-path server-rs/Cargo.toml`、相关 ESLint 与编码检查;真实生图需配置 `VECTOR_ENGINE_BASE_URL``VECTOR_ENGINE_API_KEY`
- 关联文档:`docs/prd/BABY_OBJECT_MATCH_EDUTAINMENT_TEMPLATE_PRD_2026-05-11.md``docs/technical/BABY_OBJECT_MATCH_CREATION_PUBLISH_IMPLEMENTATION_2026-05-11.md`
## 2026-05-11 拼图与抓大鹅结果页音频资产复用通用创作音频链路
- 背景:拼图和抓大鹅结果页需要接入 Suno 背景音乐,抓大鹅还需要物体点击音效,但当前两类作品没有独立的作品级音频表或 metadata 字段。
@@ -136,6 +176,14 @@
- 验证方式:执行入口配置、创作 Hub 和平台入口交互定向测试,确认看不到“方洞挑战” Tab、按钮和作品架条目。
- 关联文档:`docs/technical/NEW_WORK_ENTRY_CONFIG_2026-05-01.md``docs/design/PLATFORM_CREATE_TAB_CREATIVE_AGENT_HOME_2026-05-05.md`
## 2026-05-14 视觉小说从创作页入口隐藏
- 背景:当前创作页需要关闭视觉小说模板入口,不能继续在模板 Tab、旧选择弹层或创作 Hub 卡片中展示。
- 决策SpacetimeDB `creation_entry_type_config` 默认种子中 `visual-novel.visible=false``open=false`;旧默认可见配置会被迁移为隐藏和关闭。前端继续只消费 `GET /api/creation-entry/config`,不得用硬编码恢复视觉小说模板入口。
- 影响范围SpacetimeDB 入口配置默认种子、api-server 测试配置、创作页模板 Tab、创作 Hub 测试和创作入口文档。
- 验证方式:执行入口配置、创作 Hub、平台入口交互和 api-server 路由熔断定向测试,确认“视觉小说”不出现在创作页且 `/api/creation/visual-novel/*` 默认被熔断。
- 关联文档:`docs/design/PLATFORM_CREATE_TAB_CREATIVE_AGENT_HOME_2026-05-05.md``docs/technical/ADMIN_CREATION_ENTRY_SWITCH_CONFIG_2026-05-11.md`
## 2026-05-10 运行态输入设备抽象层全项目通用化
- 背景:拼图运行态接入 mocap 后,鼠标/触控和 mocap 各自维护输入逻辑会导致合并大块、拖拽语义和取消会话行为不一致;后续其他玩法也需要复用体感、摇杆、键盘等设备输入。
@@ -147,7 +195,7 @@
## 2026-05-11 前端调试模式统一判断
- 背景:拼图 mocap 调试面板此前在运行态常驻展示,生产构建和正式体验里容易遮挡棋盘内容;后续其它局部诊断 UI 也需要统一的调试模式入口。
- 决策:前端新增 `src/config/debugMode.ts` 作为全局调试模式判断,默认跟随 Vite 开发态,允许 `VITE_DEBUG_MODE=true/false` 显式覆盖。拼图运行态 mocap 调试面板只在调试模式下渲染,并默认折叠,只保留连接状态行
- 决策:前端新增 `src/config/debugMode.ts` 作为全局调试模式判断,默认跟随 Vite 开发态,允许 `VITE_DEBUG_MODE=true/false` 显式覆盖。2026-05-14 起,拼图运行态已临时移除 mocap 调用、体感光标和 mocap 调试面板调试模式仍供其它局部诊断 UI 使用
- 影响范围:前端局部调试 UI、拼图运行态 mocap 诊断面板、`.env.example` 和运行态输入技术文档。
- 验证方式:执行 `npm run test -- src\components\puzzle-runtime\PuzzleRuntimeShell.test.tsx``npm run typecheck` 和编码检查。
- 关联文档:`docs/technical/RUNTIME_INPUT_DEVICE_ABSTRACTION_2026-05-10.md`

View File

@@ -14,6 +14,14 @@
- 关联:相关文件、文档、提交或 Issue
```
## 抓大鹅批量重新生成物品不要新增 itemId
- 现象:结果页批量重新生成物品后,试玩或正式运行态的物品类型和图片对应关系漂移,或者用户输入一个不存在名称后被当作新物品追加。
- 原因:重新生成和批量新增共用 `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` 覆盖后端替换计划与身份保留。
- 关联:`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`
## OSS V4 签名时间和 bucket/object_key 兼容
- 现象OSS V4 私有读签名在部分时间点失败,可能出现 `OSS V4 签名时间格式化失败` 或服务端判定签名格式错误;排查用例中 bucket 为 `xushi-dev`object_key 为 `generated-square-hole-assets/.../image.png`
@@ -67,7 +75,7 @@
- 验证:`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`
- 关联:`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”已改用 APIMart `gemini-3.1-flash-image-preview`,真实生成还需要 `APIMART_BASE_URL``APIMART_API_KEY``APIMART_IMAGE_REQUEST_TIMEOUT_MS`;封面、背景图和容器 UI 仍继续使用 VectorEngine。排查时先按失败阶段区分缺 APIMart、VectorEngine 还是 OSS不能把物品素材缺 APIMart 误判成 VectorEngine 缺配置
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}` 排查
## `.hermes` 只放共享内容,不放个人 Hermes 配置
@@ -85,15 +93,55 @@
- 验证:运行 `npx vitest run src\services\useMocapInput.test.ts src\components\child-motion-demo\ChildMotionWarmupDemo.test.tsx`,并在本地硬件服务启动后进入 `/child-motion-demo` 实测站位、招手、左右手挥动和跳跃阶段。
- 关联:`src/services/useMocapInput.ts``src/components/child-motion-demo/ChildMotionWarmupDemo.tsx``docs/technical/CHILD_MOTION_DEMO_WARMUP_IMPLEMENTATION_SPEC_2026-05-09.md`
## 儿童动作 Demo 左右手阶段误通过先查身体侧映射和手臂展开阈值
- 现象:热身关“挥动左手 / 挥动右手”阶段,用户只是手自然下垂、横向小幅抖动,或挥了相反侧手,也可能被判定通过。
- 原因:本地 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`,确认相反侧手、自然下垂、单纯横向轨迹不会完成,真实展开上下摆动可以完成。
- 关联:`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 坐标防抖和渲染分层
- 现象:`/child-motion-demo` 中间半透明小人在真实硬件驱动下左右轻微来回摆,移动过程中看起来忽大忽小,用户很难稳定停在目标圆环内。
- 原因:`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`,并用真实硬件进入站位阶段观察小幅身体晃动不会导致角色频繁左右跳动。
- 关联:`src/components/child-motion-demo/ChildMotionWarmupDemo.tsx``src/index.css``docs/technical/CHILD_MOTION_DEMO_WARMUP_IMPLEMENTATION_SPEC_2026-05-09.md`
## 宝贝识物选篮误触发先查多套判定和残余轨迹
- 现象:`宝贝识物` 运行态打开礼物盒或反馈结束后,当前物品被连续送入左侧或右侧篮子,或硬件动作名偶发命中导致未做明确横移动作也触发选篮。
- 原因:选篮如果同时消费 `wave_left_hand` / `wave_right_hand` / `wave` 动作名和手部轨迹,或在 `correct` / `wrong` 反馈阶段继续累计手部路径,会把抓握、反馈期间残留移动或未知侧别手部误算成下一次选篮。
- 处理:宝贝识物选篮只使用明确 `leftHand` / `rightHand` 的连续横向轨迹阈值;侧别为 `unknown` 的手部轨迹不参与选篮;礼物盒打开和反馈阶段清空轨迹,不在非 `active` 阶段累计路径。礼物盒激活仍使`open_palm -> grab` 抓握序列。
- 处理:宝贝识物选篮只使用明确 `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`,确认动作名负向测试、未知侧别负向测试和左右手横向轨迹测试通过。
- 关联:`src/components/edutainment-runtime/BabyObjectMatchRuntimeShell.tsx``docs/technical/BABY_OBJECT_MATCH_CREATION_PUBLISH_IMPLEMENTATION_2026-05-11.md`
## 宝贝爱画左右手反了先查 mocap 摄像头视角换算
- 现象:`宝贝爱画` 中真实硬件下左手指示器和右手画笔表现反向,用户抬右手却出现左手选色指示器,或抬左手却驱动画笔 / 橡皮。
- 原因:本地 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 渲染用户左手选色指示器。
- 关联:`src/components/edutainment-runtime/BabyLoveDrawingRuntimeShell.tsx``docs/technical/BABY_LOVE_DRAWING_RUNTIME_DEMO_IMPLEMENTATION_2026-05-13.md`
## 宝贝识物创作卡在准备结果页先查长耗时 image-2 请求
- 现象:`/creation/baby-object-match` 创作生成停在“准备结果页”,约 3 分钟后显示“生成失败 / 请求超时”;后端日志可能出现同一路由 `status=502 latency_ms=231291`,或前端已失败但后端稍后返回 200。
- 原因:宝贝识物一次创作会生成 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`
- 关联:`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=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 配置
- 现象:`/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 配置。
@@ -150,13 +198,13 @@
- 验证:`curl -i -X POST https://api.vectorengine.ai/v1/images/edits -H "Authorization: Bearer invalid" -F "model=gpt-image-2-all" -F "prompt=test" -F "n=1" -F "size=1024x1024"` 至少应返回 HTTP `401`说明域名、TLS 和路径可达;执行 `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`
## 拼图自动试玩缺 UI 背景先查本地运行态字段继承
## 拼图 UI 背景缺失先区分生成失败和消费链路丢字段
- 现象:拼图草稿生成完成后,草稿数据里已有首关 `uiBackgroundImageSrc`,结果页素材配置也能看到背景图,但自动试玩结果页“试玩”进入局内仍只显示封面模糊背景,甚至看不到 UI 背景。
- 原因:生成完成后的自动试玩走前端 `startLocalPuzzleRun(...)` 本地运行态兜底,不经过后端 `start_puzzle_run`;如果本地 run 只把 `coverImageSrc` 带入 `currentLevel`,就会丢掉 `levels[].uiBackgroundImageSrc``levels[].backgroundMusic`
- 处理:`startLocalPuzzleRun` 与本地下一关 handoff 都要从关卡 `levels[]` 复制 `uiBackgroundImageSrc``backgroundMusic``currentLevel``PuzzleRuntimeShell` 继续读取 `currentLevel.uiBackgroundImageSrc` 渲染全屏背景
- 验证:`npm run test -- src/services/puzzle-runtime/puzzleLocalRuntime.test.ts src/components/puzzle-runtime/PuzzleRuntimeShell.test.tsx src/components/puzzle-result/PuzzleResultView.test.tsx`
- 关联:`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 背景,或结果页能看到背景但自动试玩 / 结果页“试玩”进入局内仍只显示封面模糊背景。
- 原因:`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` 确认生成 / 序列化链路
- 关联:`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 又变空先查结果页回包合并
@@ -182,6 +230,14 @@
- 验证:运行 `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`
- 关联:`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 首部超时
- 现象:用 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` 并落盘;失败时错误里能区分请求发送、首部等待、下载和解码阶段。
- 关联:`.codex/skills/gpt-image-2-apimart/SKILL.md``server-rs/crates/api-server/src/openai_image_generation.rs`
## 旧后端路线文档造成判断漂移
- 现象:开发时参考到 Express、Node、PostgreSQL 或 Go 方向旧文档,导致接口、数据真相或部署路径与当前主线不一致。
@@ -234,8 +290,8 @@
- 现象:本地 `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。`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``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` 地址应与实际端口一致。
- 处理:`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/technical/RUST_LOCAL_AND_REMOTE_DEPLOYMENT_SCRIPTS_2026-04-22.md``scripts/dev-rust-stack.sh`
## 本地 SpacetimeDB publish 401 可清本地库重发
@@ -591,6 +647,22 @@
- 验证:执行 `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`
- 关联:`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 背景和容器只在顶层字段时也要传进运行态
- 现象:抓大鹅草稿 / 推荐卡片响应里已有 `generatedBackgroundAsset`,结果页 UI 预览能看到纯背景图和容器图,但进入试玩或正式局内仍显示默认渐变背景和默认圆形容器。
- 原因:部分链路把 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` 对应图片。
- 关联:`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 并接管棋盘外观
- 现象:抓大鹅结果页看似有容器生成入口,但真实生成出的局内容器不像 `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 上传;共享 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`
- 关联:`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`
## 抓大鹅结果页音频试听也要先换签
- 现象:抓大鹅草稿生成完成后,背景音乐已写在 `generatedItemAssets[0].backgroundMusic.audioSrc`,但 `素材配置 > 背景音乐` 或物品详情音效 `<audio>` 不能播放Network 可能请求裸 `/generated-match3d-assets/...mp3` 并返回 403。
@@ -663,6 +735,22 @@
- 验证:`cargo test -p api-server match3d_tag_normalization --manifest-path server-rs/Cargo.toml`,并保留 `normalize_match3d_tag("3D素材") == "3D素材"` 的单测。
- 关联:`server-rs/crates/api-server/src/match3d.rs`
## 抓大鹅物品切图白边或绿幕残留先查后端透明化
- 现象:抓大鹅生成的物品视角图裁剪后仍带白边,或者整块纯绿色绿幕背景没有被透明化,运行态看到绿色方块。
- 原因:素材 sheet 可能是“每格内部绿幕、整张图外圈近白底”,内部绿幕不一定连通到 sheet 外边缘;旧 flood fill 只从外边缘找背景会漏掉这种绿幕块。白底抗锯齿如果不纳入抠像和边缘去污染,也会随裁剪输出成一圈白边。
- 处理:`api-server``slice_match3d_material_sheet` 必须先在整张 sheet 上做透明背景后处理:外边缘连通绿幕/近白底清 alpha非连通但高置信纯绿块也清 alpha边缘近白和绿幕抗锯齿做透明或去污染同时保护不够纯的绿色主体像素。
- 验证:`cargo test -p api-server match3d_material_sheet_slicing --manifest-path server-rs\Cargo.toml` 覆盖非连通绿幕、白边、贴边主体保留和固定 5x5 切图。
- 关联:`server-rs/crates/api-server/src/match3d.rs``docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`
## 草稿页卡片有真实素材但仍显示黑卡先查摘要字段
- 现象:草稿页拼图卡片没有关卡图背景,抓大鹅卡片没有背景图或物品图背景,甚至兜底视觉也退回黑色面板。
- 原因:拼图列表摘要若不下发 `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`
- 关联:`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 宏
- 现象:给 `user_account.user_tags` 或邀请码独立标签列写 `#[default(Vec::<String>::new())]`SpacetimeDB WASM 构建报 `destructor of Vec<String> cannot be evaluated at compile-time`
@@ -678,3 +766,27 @@
- 处理:公开详情失效统一走 `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/technical/PUBLIC_WORK_DETAIL_NOT_FOUND_RECOVERY_2026-05-11.md``src/routing/runtimeNotFoundRecovery.ts``src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`
## 拼图 UI 背景只有 objectKey 时不要回退默认 UI
- 现象:拼图草稿页、试玩和正式运行态都显示默认 UI或者只在结果页看到生成图进入试玩后又回到默认背景。
- 原因:`uiBackgroundImageSrc` 可能为空而真实生成结果只写了 `uiBackgroundImageObjectKey`;如果前端和运行态只读 `src`,或者本地试玩 / 正式 run 没把 `objectKey` 一起传递,就会丢掉已有背景。
- 处理:统一通过一个解析入口把 `uiBackgroundImageSrc || uiBackgroundImageObjectKey` 归一到可展示路径;本地试玩和正式运行态都要保留 `uiBackgroundImageObjectKey`,并在 `uiBackgroundImageSrc` 为空时换签读取。
- 验证:结果页 UI Tab、`startLocalPuzzleRun``PuzzleRuntimeShell` 都应在仅有 `objectKey` 时显示生成背景,不再回落默认 UI。
- 关联:`src/services/puzzle-runtime/puzzleUiBackgroundSource.ts``src/components/puzzle-result/PuzzleResultView.tsx``src/services/puzzle-runtime/puzzleLocalRuntime.ts``src/components/puzzle-runtime/PuzzleRuntimeShell.tsx``server-rs/crates/module-puzzle/src/application.rs`
## 拼图 UI 背景提示词不像 AI 生成先查首关命名契约
- 现象:拼图草稿生成完成后,`素材配置 > UI` 里显示的 `UI背景提示词` 像前端或后端模板拼接,而不是 AI 生成的视觉提示词。
- 原因:首关命名 LLM 旧契约只返回 `levelName`,自动 UI 背景阶段只能用作品名、作品描述、关卡描述和标签拼接确定性兜底提示词;前端旧实现又会在 `uiBackgroundPrompt` 为空时把本地默认模板直接填进文本框,造成“看起来已有 AI 提示词”的假象。
- 处理:首关命名 LLM 契约必须同时返回 `{"levelName":"...","uiBackgroundPrompt":"..."}`;草稿自动 UI 背景生成优先使用该 AI 提示词,视觉精修请求若返回新提示词则覆盖文本请求提示词,否则保留文本请求提示词。前端文本框只展示已保存的 `uiBackgroundPrompt` 或用户编辑值,字段为空时不展示本地兜底模板。
- 验证:执行 `cargo test -p api-server puzzle_level_naming --manifest-path server-rs\Cargo.toml``cargo test -p api-server puzzle_initial_ui_background_prompt --manifest-path server-rs\Cargo.toml``npm run test -- src/components/puzzle-result/PuzzleResultView.test.tsx`
- 关联:`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`
- 关联:`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`