feat: checkpoint m5 and bootstrap m6 asset flow

This commit is contained in:
2026-04-22 14:46:43 +08:00
parent 0773a0d0ca
commit 91fb8edee7
22 changed files with 5096 additions and 445 deletions

View File

@@ -4,16 +4,16 @@
## 0. 文档目标
本文件冻结 `M4` 当前下一条最小可落地兼容桥
本文件冻结 `M4` 当前 runtime story compat bridge 的实际已落地边界
**先把 Rust `api-server` 旧 `runtime story state` 兼容返回所需的 DTO 与状态桥边界冻结清楚,再进入 Axum handler 与状态编译迁移**
**Rust `api-server` 已承接旧 `runtime/story/*` 兼容接口,但当前仍属于“快照桥 + 确定性兼容动作”阶段,不等价于最终 SpacetimeDB 真相 story reducer**
当前仓库已经有两条并行现实:
1. `server-node` 侧旧兼容接口 `POST /api/runtime/story/state/resolve` 仍然在真实服务前端。
2. `server-rs` 侧已经有 `story_session / battle_state / npc battle / inventory state` 等真相态接口,但还没有编译成旧前端消费的 `RuntimeStoryActionResponse`
因此,本轮不直接宣称“runtime story 已迁完”,而是先把兼容桥 contract 冻结为下一段可编码的工程基线
因此,本文档既记录当前兼容桥为什么存在,也明确它的已完成能力和仍未替换掉的真相态缺口
---
@@ -74,19 +74,19 @@
## 2. 本轮冻结范围
本轮冻结以下兼容桥边界:
本轮实际已落地并冻结以下兼容桥边界:
1. Rust `shared-contracts` 新增旧 `runtime story` 兼容响应 DTO
2. Rust `shared-contracts` 新增 `POST /api/runtime/story/state/resolve` 的最小请求 DTO
3. 明确 Rust 侧第一段只先承接“状态查询兼容桥”
4. 明确 `actions/resolve``initial``continue` 继续后置
2. Rust `shared-contracts` 新增 `POST /api/runtime/story/state/resolve` / `POST /api/runtime/story/actions/resolve` / `POST /api/runtime/story/initial` / `POST /api/runtime/story/continue` 所需请求 DTO
3. Rust `api-server` 已挂出全部旧 runtime story 兼容接口
4. 明确当前实现仍以 `runtime_snapshot` 为状态真相来源,而不是新的 `resolve_story_action` reducer
本轮明确做:
本轮明确仍未做:
1. 不在 `server-rs` 里直接落完整 `resolve_story_action`
2. 不迁移 Node 侧全部 story 行为决策
3. 不把 `runtime snapshot` 正式持久化真相一次性迁到 Rust
4. 不在本轮让前端切到 Rust `api-server`
3. 不把 `runtime snapshot projection` 一次性改成全量新真相模型
4. 不在本文里宣称前端默认流量已经切到 Rust `api-server`
---
@@ -183,6 +183,81 @@
这与当前 Node `getRuntimeStoryState(...)` 的行为一致,不需要在状态查询时伪造 patch。
### 4.2.2 `actions/resolve` 首版策略
当前 Rust compat handler 已按“确定性兼容动作 + snapshot 回写”落地,目标是先覆盖前端实际点击主链,而不是一步到位复刻 Node 全部 story domain。
当前已覆盖动作:
1. `story_continue_adventure`
2. `story_opening_camp_dialogue`
3. `camp_travel_home_scene`
4. `idle_call_out`
5. `idle_explore_forward`
6. `idle_observe_signs`
7. `idle_rest_focus`
8. `idle_travel_next_scene`
9. `npc_preview_talk`
10. `npc_chat`
11. `npc_help`
12. `npc_leave`
13. `npc_fight`
14. `npc_spar`
15. `npc_recruit`
16. `battle_attack_basic`
17. `battle_use_skill`
18. `battle_all_in_crush`
19. `battle_escape_breakout`
20. `battle_feint_step`
21. `battle_finisher_window`
22. `battle_guard_break`
23. `battle_probe_pressure`
24. `battle_recover_breath`
25. `treasure_secure`
26. `treasure_inspect`
27. `treasure_leave`
统一规则:
1. 请求带 `snapshot` 时先写入 `runtime_snapshot`
2. 请求不带 `snapshot` 时回退读取持久化 `runtime_snapshot`
3. `clientVersion``gameState.runtimeActionVersion` 不一致时返回 `409`
4. 动作成功后递增 `runtimeActionVersion`
5. 追加 `storyHistory`,并把新的 `currentStory` / `viewModel` / `presentation` / `patches` 回写到 snapshot
当前已额外对齐的 Node 旧主链细节:
1. `npc_chat`
- 已从最初的固定 `+1 affinity` 修正为 Node 旧规则 `max(2, 6 - chattedCount)`
- 例如 `chattedCount = 0` 时首聊会从 `46 -> 52`
2. `npc_help`
- 已改为一次性援手
- 成功时恢复 `10 HP / 8 Mana`
- 同时关系 `+4`
- 二次调用返回错误
3. `npc_recruit`
- 已要求 `affinity >= 60`
- 当前队伍满员时必须提交 `releaseNpcId`
- 当前 compat bridge 也会把换队结果写回 `companions`
4. quest compat 主循环
- 已补 `npc_chat_quest_offer_view / replace / abandon`
- 已补 `npc_quest_accept / npc_quest_turn_in`
- `pendingQuestOffer.quest` 会继续写回 `currentStory.npcChatState`
- quest offer 选项会继续携带前端面板依赖的 `runtimePayload.npcChatQuestOfferAction`
5. `npc_quest_turn_in`
- quest 不再被直接从快照中移除,而是保留为 `status = turned_in`
- 当前最小奖励闭环已写回 `playerCurrency / playerInventory / playerProgression / npc affinity`
- `playerProgression` 当前仍走 compat 侧确定性经验累计,不等价于最终 SpacetimeDB 真相成长链
### 4.2.3 `initial` / `continue` 首版策略
当前 Rust compat handler 已提供稳定 `RuntimeStoryAiResponse`
1. 优先复用 `requestOptions.availableOptions / optionCatalog`
2. 未配置 `platform-llm` 时返回确定性 fallback `storyText`
3. 已配置 `platform-llm` 时,允许基于同一请求载荷生成增强版文本
4. 当前 `encounter` 仍返回 `null`
---
## 5. DTO 分层
@@ -223,15 +298,29 @@
---
## 6. 第一段工程落地顺序
## 6. 当前已落地工程顺序
建议直接按下面顺序编码
本轮实际完成顺序
1. `shared-contracts` 新增 `runtime_story.rs`
2.`RuntimeStoryStateResolveRequest / RuntimeStoryActionResponse` 补 camelCase 序列化测试
3. `docs/technical/README.md``shared-contracts/README.md` 更新索引
4. `backend-rewrite-tasklist/03_M4_STORY_AND_GAMEPLAY.md` 追加当前冻结进展
5. 下一轮再进入 `api-server``state/resolve` handler 与兼容 compiler
3. 恢复并重建 `api-server/src/runtime_story.rs`
4. 接入 `state/resolve``GET state``actions/resolve``initial``continue`
5. 复用 `runtime_save`SpacetimeDB snapshot 持久化链
6. 执行 `cargo test -p shared-contracts`
7. 执行 `cargo check -p api-server`
8. 执行 `cargo test -p api-server runtime_story`
9. 继续把 Node 旧 route boundary 回归平移到 Rust
- `runtime_story_routes_resolve_through_rust_route_boundary`
- `runtime_story_action_resolve_rejects_client_version_conflict`
10. 继续补关键 NPC compat 行为回归:
- `runtime_story_npc_help_is_one_shot_and_restores_resources`
- `runtime_story_npc_recruit_requires_threshold_and_release_target_when_party_full`
11. 继续补 quest compat 回归:
- `runtime_story_quest_offer_replace_updates_pending_offer_and_payload`
- `runtime_story_quest_offer_abandon_clears_pending_offer_and_restores_chat_options`
- `runtime_story_quest_accept_writes_quest_runtime_stats_and_followup_story`
- `runtime_story_quest_turn_in_marks_quest_rewards_and_affinity`
---
@@ -239,13 +328,14 @@
以下内容继续明确后置:
1. `POST /api/runtime/story/actions/resolve` 的请求 DTO 是否直接复用旧 TS contract 全量字段
2. `resolve_story_action` 是否拆成:
1. `resolve_story_action` 是否拆成:
- `resolve_story_action`
- `resolve_story_combat_action`
- `resolve_story_interaction_action`
3. `snapshot` 缺失时是否允许直接从 Rust 真相表完整恢复旧 `currentStory`
4. `LLM` 文本续写是在 Rust bridge 内继续调用,还是继续通过 Node 兼容层兜底
2. `snapshot` 缺失时是否允许直接从 Rust 真相表完整恢复旧 `currentStory`
3. 当前确定性 compat action 何时被真正的 SpacetimeDB story reducer 替换
4. `battle / npc / quest / inventory` patch 是否继续细化成与 Node 完全逐字段一致
5. `npc_quest_turn_in` 的经验、物品、情报、章节推进何时切换到真正的 SpacetimeDB progression / inventory / quest 真相链,而不是 compat 侧快照写回
这些边界在状态桥稳定前都不应提前拍死。
@@ -258,7 +348,20 @@
1. 已有独立技术文档冻结 `state/resolve` 兼容桥边界
2. `shared-contracts` 已拥有旧 `runtime story` 兼容 DTO
3. DTO 字段名与当前前端消费口径保持一致
4. `cargo test -p shared-contracts` 通过
5. `npm run check:encoding` 通过,确保新增中文文档与 Rust 源文件编码未损坏
4. `api-server` 已挂出:
- `POST /api/runtime/story/state/resolve`
- `GET /api/runtime/story/state/:sessionId`
- `POST /api/runtime/story/actions/resolve`
- `POST /api/runtime/story/initial`
- `POST /api/runtime/story/continue`
5. `cargo test -p shared-contracts` 通过
6. `cargo check -p api-server` 通过
7. `cargo test -p api-server runtime_story` 通过
8. `node scripts/check-encoding.mjs` 通过
达到以上条件后,下一轮即可直接进入 Rust `state bridge compiler` 与 Axum handler 落地。
补充边界:
1. 当前测试里为 `runtime_snapshot` 加了 `#[cfg(test)]` 下的内存兜底,只用于在未启动本地 SpacetimeDB 时稳定验证 Rust route boundary。
2. 该测试兜底不进入生产链路,不改变真实 `runtime_save -> spacetime-client -> SpacetimeDB procedure` 的运行时实现。
达到以上条件后,兼容桥这一段已不再停留在 DTO / 空壳状态下一轮重点转向“compat bridge 替换成真相态 reducer / projection”。

View File

@@ -0,0 +1,158 @@
# M6 custom world 资产接入 OSS 第一批设计
日期:`2026-04-22`
## 1. 文档目的
这份文档用于冻结 `M6` 第一批 custom world 资产链的真实落地口径。
本批只解决一个明确问题:
1. `POST /api/custom-world/scene-image`
2. `POST /api/custom-world/cover-image`
3. `POST /api/custom-world/cover-upload`
不再把图片产物写入仓库 `public/` 本地文件,而是统一接到:
1. `platform-oss::put_object`
2. `asset_object`
3. `asset_entity_binding`
形成正式 `OSS + SpacetimeDB 元数据` 真相链。
## 2. 当前前提
当前仓库已经具备以下基础能力:
1. `POST /api/assets/direct-upload-tickets`
2. `GET /api/assets/read-url`
3. `POST /api/assets/objects/confirm`
4. `POST /api/assets/objects/bind`
5. `platform-oss::OssClient::put_object`
6. `spacetime-module` 中的 `asset_object / asset_entity_binding`
因此本批不重新设计新资产系统,只复用既有 `assets` 主链。
## 3. 本批范围
### 3.1 要完成的内容
1. custom world 场景图生成结果写入 OSS
2. custom world 封面图生成结果写入 OSS
3. custom world 封面上传结果写入 OSS
4. 每个写入对象都执行一次正式对象确认
5. 每个正式对象都绑定到 custom world 业务实体槽位
6. 路由响应继续返回旧前端可消费的 `imageSrc`
### 3.2 本批不解决的内容
1. 不补 DashScope 图片模型的完整 Rust 编排
2. 不补 `cover-upload` 的裁剪、压缩、16:9 强校验全量能力
3. 不新增 `scene_image_asset / character_visual_asset` 强业务表
4. 不在本批落 `custom_world_asset_link`
5. 不把旧前端响应 contract 改成直接返回 OSS URL
## 4. 业务实体与槽位约定
本批统一复用通用 `asset_entity_binding`
### 4.1 场景图
| 字段 | 取值 |
| --- | --- |
| `entity_kind` | `custom_world_landmark` |
| `entity_id` | 优先 `landmarkId`,否则回退 `landmarkName` |
| `slot` | `scene_image` |
| `asset_kind` | `scene_image` |
### 4.2 封面图
| 字段 | 取值 |
| --- | --- |
| `entity_kind` | `custom_world_profile` |
| `entity_id` | 优先 `profileId`,否则回退世界 `id/name` |
| `slot` | `cover` |
| `asset_kind` | `custom_world_cover` |
补充口径:
1. 绑定幂等键仍是 `entity_kind + entity_id + slot`
2. 同一 profile 重复生成/上传封面时,允许覆盖到最新对象
3. 同一 landmark 重复生成场景图时,允许覆盖到最新对象
## 5. OSS 对象键与返回 contract
### 5.1 对象键
场景图固定写入:
`generated-custom-world-scenes/{profileSegment}/{landmarkSegment}/{assetId}/scene.{ext}`
封面图固定写入:
`generated-custom-world-covers/{profileSegment}/{assetId}/cover.{ext}`
### 5.2 返回 contract
路由响应继续沿用旧前端使用的字段:
1. `imageSrc`
2. `assetId`
3. `sourceType`
4. `model`
5. `size`
6. `taskId`
7. `prompt`
8. `actualPrompt`
其中:
1. `imageSrc` 固定返回 `legacyPublicPath`,也就是旧 `/generated-*` 路径
2. 前端若要真正读取私有 OSS 对象,仍必须通过 `GET /api/assets/read-url` 换签名读 URL
3. 不直接把 `signedUrl` 塞进 custom world 业务返回,避免把短期读签名误存成长期业务字段
## 6. 服务端执行顺序
每次 custom world 图片产出固定执行以下顺序:
1. 生成或接收图片字节
2.`platform-oss::put_object`
3. 通过 `HEAD Object` 真值确认对象
4. 写入 `asset_object`
5. 写入 `asset_entity_binding`
6. 返回 `legacyPublicPath`
注意:
1. `put_object` 成功不代表已完成正式落库
2. `asset_object` 仍必须经过确认链路写入
3. 业务引用真相以 `asset_entity_binding` 为准,不以 OSS 上是否存在 key 为准
## 7. 与 M5 的衔接
`M5` 为保证前端不断链,曾允许 `scene-image / cover-image / cover-upload` 先写本地 `public/`
从本批开始,这个临时口径失效,统一改为:
1. 二进制对象只进 OSS
2. 元数据只进 `asset_object`
3. 业务槽位只进 `asset_entity_binding`
这样 `Stage9` 的兼容路由就不会继续偏离 `M6` 正式资产主链。
## 8. 完成定义
当以下条件满足时,本批视为完成:
1. custom world 三条图片兼容路由不再写本地 `public/`
2. 路由成功返回的 `imageSrc` 全部来自 `OSS legacyPublicPath`
3. 每次成功写图后都能在 SpacetimeDB 中形成 `asset_object`
4. 每次成功写图后都能形成对应 `asset_entity_binding`
5. 旧前端仍可继续使用返回的 `/generated-*` 路径配合读签名服务显示图片
## 9. 关联文档
1. [M6_OSS_SERVER_UPLOAD_AND_STS_POLICY_2026-04-21.md](./M6_OSS_SERVER_UPLOAD_AND_STS_POLICY_2026-04-21.md)
2. [ASSET_OBJECT_CONFIRM_FLOW_DESIGN_2026-04-21.md](./ASSET_OBJECT_CONFIRM_FLOW_DESIGN_2026-04-21.md)
3. [ASSET_ENTITY_BINDING_REDUCER_DESIGN_2026-04-21.md](./ASSET_ENTITY_BINDING_REDUCER_DESIGN_2026-04-21.md)
4. [SPACETIMEDB_CUSTOM_WORLD_WORKS_AND_AGENT_EXTENSION_STAGE9_DESIGN_2026-04-22.md](./SPACETIMEDB_CUSTOM_WORLD_WORKS_AND_AGENT_EXTENSION_STAGE9_DESIGN_2026-04-22.md)

View File

@@ -42,8 +42,10 @@
- [SPACETIMEDB_CUSTOM_WORLD_AGENT_SESSION_STAGE6_DESIGN_2026-04-22.md](./SPACETIMEDB_CUSTOM_WORLD_AGENT_SESSION_STAGE6_DESIGN_2026-04-22.md):冻结 `M5` Agent session create / snapshot 的最小 SpacetimeDB 与 Axum facade 闭环,明确本轮不迁移 LLM、SSE、卡片更新和完整 action registry。
- [SPACETIMEDB_CUSTOM_WORLD_AGENT_MESSAGE_STAGE7_DESIGN_2026-04-22.md](./SPACETIMEDB_CUSTOM_WORLD_AGENT_MESSAGE_STAGE7_DESIGN_2026-04-22.md):冻结 `M5` Agent `message submit / operation query` 的 deterministic 最小闭环,明确同步写入 user/assistant 消息、`process_message` operation 与 session 进度推进规则。
- [SPACETIMEDB_CUSTOM_WORLD_AGENT_MESSAGE_STREAM_STAGE8_DESIGN_2026-04-22.md](./SPACETIMEDB_CUSTOM_WORLD_AGENT_MESSAGE_STREAM_STAGE8_DESIGN_2026-04-22.md):冻结 `M5` Agent `/messages/stream` 的最小兼容 SSE facade明确复用 Stage 7 的同步写表逻辑,只输出当前前端真实消费的 `reply_delta / session / done / error` 事件。
- [RUST_API_SERVER_SSE_INFRASTRUCTURE_DESIGN_2026-04-22.md](./RUST_API_SERVER_SSE_INFRASTRUCTURE_DESIGN_2026-04-22.md):冻结 `server-rs/crates/api-server` 内部 SSE 基础设施抽取口径,统一 Rust 侧 `text/event-stream` 响应头、事件编码与缓冲式 SSE 输出 helper。
- [SPACETIMEDB_CUSTOM_WORLD_LIBRARY_DETAIL_STAGE5_EXTENSION_DESIGN_2026-04-22.md](./SPACETIMEDB_CUSTOM_WORLD_LIBRARY_DETAIL_STAGE5_EXTENSION_DESIGN_2026-04-22.md):补齐 `M5` Stage 5 遗漏的 owner-only `GET /api/runtime/custom-world-library/:profileId` 设计,冻结单条 profile detail 的 SpacetimeDB procedure、client facade、404 语义与 Axum 路由扩展方式。
- [SPACETIMEDB_CUSTOM_WORLD_WORKS_AND_AGENT_EXTENSION_STAGE9_DESIGN_2026-04-22.md](./SPACETIMEDB_CUSTOM_WORLD_WORKS_AND_AGENT_EXTENSION_STAGE9_DESIGN_2026-04-22.md):冻结 `M5` 剩余主链的 works、card detail、publish gate、supportedActions、action registry 与 AI/OSS 兼容路由边界,作为 Stage 9 到收口阶段的统一落地依据。
- [M6_CUSTOM_WORLD_ASSET_OSS_INTEGRATION_STAGE1_2026-04-22.md](./M6_CUSTOM_WORLD_ASSET_OSS_INTEGRATION_STAGE1_2026-04-22.md):冻结 `M6` 第一批 custom world 场景图、封面图、封面上传从本地 `public/` 临时落地切到 `OSS + asset_object + asset_entity_binding` 正式真相链的边界与槽位约定。
- [M3_BROWSE_HISTORY_AXUM_SPACETIMEDB_DESIGN_2026-04-21.md](./M3_BROWSE_HISTORY_AXUM_SPACETIMEDB_DESIGN_2026-04-21.md):冻结 `M3` 第二批 `browse history` 纵向切片的 `user_browse_history` 表、双路径 facade、宽松归一化、去重排序规则与测试策略。
- [ASSET_ENTITY_BINDING_REDUCER_DESIGN_2026-04-21.md](./ASSET_ENTITY_BINDING_REDUCER_DESIGN_2026-04-21.md):冻结已确认 `asset_object` 绑定到业务实体槽位的首版 reducer/procedure、通用 `asset_entity_binding` 表与 Axum facade。
- [FRONTEND_TO_BACKEND_MIGRATION_EXECUTION_PLAN_2026-04-21.md](./FRONTEND_TO_BACKEND_MIGRATION_EXECUTION_PLAN_2026-04-21.md)把鉴权、浏览历史、runtime story 快照、NPC 待接委托与正式生成编排继续后移到 Express 后端的实施方案与验收口径。

View File

@@ -0,0 +1,118 @@
# Rust `api-server` SSE 基础设施设计2026-04-22
日期:`2026-04-22`
## 1. 文档目的
这份文档用于冻结 `server-rs/crates/api-server` 内部的 SSE 基础设施抽取口径。
本轮目标只有一个:
1. 把当前散落在业务 handler 中的 `text/event-stream` 响应头与事件文本编码逻辑,收口为 `api-server` 可复用的 Rust 基础设施。
本轮不做:
1. 不改前端消费协议
2. 不把 custom world message stream 改成真实逐段 token streaming
3. 不引入跨 crate 的共享 `shared-contracts` SSE runtime helper
4. 不同时重构 story / runtime / txt mode 的未来流式接口
## 2. 当前问题
当前 Rust 侧 SSE 能力只在一个地方手写:
1. `server-rs/crates/api-server/src/custom_world.rs`
当前实现存在以下问题:
1. `append_sse_event(...)``build_event_stream_response(...)` 直接写在业务文件里
2. SSE 响应头、事件编码规则没有统一入口
3. 后续如果再新增第二条 Rust SSE 路由,极容易复制一份近似实现
4. 业务层和传输层耦合在一起,不利于测试
## 3. 抽取边界
本轮只抽以下基础能力:
1. 标准 SSE 响应头构造
2. 单条事件编码
3. 缓冲式 SSE body builder
4. 一次性返回完整 SSE 文本的响应构造
本轮明确不抽:
1. `reply_delta / session / done / error` 这些业务事件名
2. 事件发送顺序
3. custom world session 的查询与回复文本推导
4. 业务错误到 SSE `error` 事件的映射策略
原因固定如下:
1. 这些内容属于业务协议,而不是通用传输设施
2. 当前不同链路未来很可能有不同事件集合
3. 先把传输层抽干净,后续真实流式能力才能稳定复用
## 4. 基础设施 API 口径
本轮在 `server-rs/crates/api-server/src/sse.rs` 提供:
1. `SseEventBuffer`
- 面向当前最小兼容场景
- 内部持有 `String`
- 提供 `push_json(event, payload)``into_response()`
2. `build_sse_response(body)`
- 统一写入标准 SSE 响应头
3. `encode_sse_event(body, event, payload)`
- 只负责把事件编码为:
```text
event: xxx
data: {...}
```
## 5. 标准响应头
所有通过本基础设施输出的 SSE 响应,统一包含:
1. `Content-Type: text/event-stream; charset=utf-8`
2. `Cache-Control: no-cache`
3. `X-Accel-Buffering: no`
当前不默认加入:
1. `Connection: keep-alive`
原因:
1. 当前 Rust `axum` 一次性 body 返回场景不依赖显式设置该头
2. 保持最小必要头集合,避免提前固化未来长连接策略
## 6. 与 custom world message stream 的关系
`POST /api/runtime/custom-world/agent/sessions/:sessionId/messages/stream` 仍然保持 Stage 8 文档冻结的最小语义:
1. 业务层先完成 deterministic 写表
2. 读取最新 session snapshot
3. 组装 `reply_delta`
4. 组装 `session`
5. 组装 `done`
6. 一次性返回完整 SSE 文本
本轮变化只在于:
1. 事件编码和响应头不再手写在 `custom_world.rs`
2. 改由 `sse.rs` 基础设施承接
## 7. 验收标准
当以下条件满足时,本轮视为完成:
1. `api-server/src/sse.rs` 已提供可复用 SSE helper
2. `custom_world.rs` 不再内联维护 SSE 编码与响应头细节
3. `cargo fmt -p api-server` 通过
4. `cargo check -p api-server` 通过
5. `npm run check:encoding` 通过
## 8. 一句话结论
本轮把 Rust `api-server` 里的 SSE 能力收口为“最小传输层基础设施”,统一事件编码与响应头,但不改业务事件协议和当前 custom world 的同步伪流式语义。

View File

@@ -832,6 +832,21 @@ workflow-cache/{workflow_type}/{workflow_id}.json
1. `editor` 已于 `2026-04-21` 被确认为遗留无用模块,退出本轮 Rust 后端重写范围。
2. Phase 5 只覆盖资产与 OSS 主链,不再包含 editor 迁移。
## Phase 6联调、回归、部署与切流收口
交付:
1. 联调与回归测试体系
2. 灰度环境、切流开关、回退方案
3. tracing / request id / 关键链路观测
4. 拆分 `server-rs/crates/spacetime-module/src/lib.rs`,按业务模块与 SpacetimeDB 的 `table / reducer / procedure / view` 结构重组为 `runtime``gameplay::{story/combat/inventory/npc/quest/runtime_item/progression}``custom_world``asset_metadata``ai` 等聚合子模块,主工程 crate 根入口只保留模块声明、统一导出与最小发布入口
阶段执行补充:
1. 这是切流前的工程结构收口,不是新功能扩张;拆分过程中不得改变既有 table schema、reducer / procedure 名称、对外 contract 与 publish 行为。
2. 拆分后的目录与模块边界必须对齐 `M0` 已冻结的模块迁移归属,避免 `spacetime-module` 回退成“单大文件 + 单大包”结构。
3. 拆分完成后至少要保持 `cargo check`、SpacetimeDB 本地 build / publish 开发链路与主流程回归脚本可继续通过。
## 14. 验收标准
重写完成至少要满足:

View File

@@ -331,15 +331,24 @@ session snapshot 中的 `resultPreview` 固定输出:
#### `scene-image / cover-image`
1. 当前不直接生成真实图片
2. 返回明确 `NOT_IMPLEMENTED` 或最小占位错误会导致前端主链中断
3. 因此前端兼容需要的最小可用策略是:创建上传票据或返回可继续上传的对象位置信息
1. `M5` 验收时允许先用本地占位产物保证前端主链不断
2. `2026-04-22``M6` 第一批开始,正式口径改为:
- `platform-oss::put_object`
- `asset_object`
- `asset_entity_binding`
3. 兼容响应仍返回旧 `/generated-*` 路径,不直接返回裸 OSS URL
4. 详细边界见:
- [M6_CUSTOM_WORLD_ASSET_OSS_INTEGRATION_STAGE1_2026-04-22.md](./M6_CUSTOM_WORLD_ASSET_OSS_INTEGRATION_STAGE1_2026-04-22.md)
#### `cover-upload`
1. 复用 `/api/assets/direct-upload-tickets`
2. 生成 OSS 上传票据
3. 返回兼容旧前端所需的上传字段
1. `M5` 阶段允许先走最小本地上传兼容
2. `2026-04-22``M6` 第一批开始,正式口径与 `cover-image` 一致:
- 服务器接收 Data URL
- 服务器上传 OSS
- 确认 `asset_object`
- 绑定 `asset_entity_binding`
3. 返回值仍保持旧前端所需的 `imageSrc / assetId / sourceType`
## 8. crate 级改动范围