This commit is contained in:
2026-04-28 19:36:39 +08:00
parent a9febe7678
commit f0471a4f8d
206 changed files with 18456 additions and 10133 deletions

View File

@@ -0,0 +1,164 @@
# RPG 运行时 Story Engine 后端迁移落地方案2026-04-28
## 0. 本轮目标
本轮只收口 `RPG_FRONTEND_SCRIPT_BACKEND_MIGRATION_AUDIT_2026-04-28.md``4.4 P0 story engine / chapter / world mutation` 这条。
目标不是新增一套前端规则,而是让运行时动作完成后由 `server-rs` 统一写回:
1. `storyHistory`
2. `storyEngineMemory`
3. `chapterState`
4. `currentScenePreset.mutationStateText / currentPressureLevel / description`
前端只能展示这些后端字段,不能继续在 hook 中运行 `chapterDirector / threadSignalRouter / worldMutationRouter` 等正式状态机。
## 1. 后端落点
### 1.1 `module-runtime-story-compat`
新增 `story_engine.rs`,作为无 HTTP、无 `AppState` 的纯 JSON projector。
职责:
1. 确保 `storyEngineMemory` 最小结构存在。
2. 按上一帧与下一帧快照生成 story signals。
3. 基于信号推进 active thread、recent signal。
4. 基于当前场景和任务生成 `ChapterState`
5. 基于章节和信号生成 `WorldMutation`
6. 把 mutation 投影到当前场景展示字段。
7. 追加最小 chronicle、journey beat、continue digest。
### 1.2 `api-server runtime_story compat`
`resolve_runtime_story_action` 在动作确定性结算和 `storyHistory` 写入后,统一调用 projector再持久化快照。
这样即使前端只提交 `functionId/payload`,正式叙事记忆也由后端结果生成。
## 2. 前端收口
### 2.1 `progressionActions.ts`
保留:
1. 展示层 loading/error。
2. encounter 入场动画。
3. 调用后端生成 story 或 fallback story。
移除:
1. `applyStoryEngineEchoes`
2. 本地章节任务补发。
3. 本地 thread signal、companion reaction、chapter、journey beat、world mutation、QA、release gate 等编排。
### 2.2 `storyContextBuilder.ts`
保留 prompt context 适配职责,但只能读取后端已存在的字段:
1. `state.chapterState`
2. `state.storyEngineMemory.currentChapter`
3. `state.storyEngineMemory.currentJourneyBeat`
4. `state.storyEngineMemory.worldMutations`
5. `state.currentScenePreset`
禁止继续导入并运行 story engine director。
## 3. 验收标准
1. `src/hooks/rpg-runtime-story/progressionActions.ts` 不再导入 `services/storyEngine/*`
2. `src/hooks/rpg-runtime-story/storyContextBuilder.ts` 不再导入 `services/storyEngine/*`
3. `resolve_runtime_story_action` 返回的 snapshot 中包含后端写入的 `storyEngineMemory.currentChapter`
4. 场景动作后 `currentScenePreset.mutationStateText` 由后端 projector 写入。
5. `cargo test -p module-runtime-story-compat story_engine --manifest-path server-rs/Cargo.toml` 通过。
6. `cargo test -p api-server runtime_story --manifest-path server-rs/Cargo.toml` 通过。
7. 前端相关 vitest 与编码检查通过。
## 4. 本轮落地记录
### 4.1 后端已落地
1. `server-rs/crates/module-runtime-story-compat/src/story_engine.rs` 新增确定性 projector。
2. `server-rs/crates/api-server/src/runtime_story/compat.rs` 在 action resolve 写入 `storyHistory` 后调用 projector再保存 snapshot。
3. `server-rs/crates/api-server/src/runtime_story/compat/tests.rs` 新增 route 边界测试,覆盖响应 snapshot 中的:
- `chapterState.id`
- `storyEngineMemory.currentChapter.id`
- `quests[].chapterId`
- `currentScenePreset.mutationStateText`
- `storyEngineMemory.worldMutations`
### 4.2 前端已收口
1. `src/hooks/rpg-runtime-story/progressionActions.ts` 不再执行本地 story engine echo、chapter、journey beat、world mutation 编排。
2. `src/hooks/rpg-runtime-story/storyContextBuilder.ts` 不再导入 `services/storyEngine/*`只读取后端快照中已有的章节、旅程、mutation、chronicle、companion reaction 等字段。
3. prompt context 中 `visibilitySlice / sceneNarrativeDirective / goalStack / activeScenarioPack / activeCampaignPack` 暂不在前端重建,等待后端后续模块正式写入后直接透传。
### 4.3 验证结果
已通过:
1. `cargo test -p module-runtime-story-compat story_engine --manifest-path server-rs\Cargo.toml`
2. `cargo test -p api-server runtime_story --manifest-path server-rs\Cargo.toml`
3. `cargo test -p api-server runtime_story_route_boundary_projects_story_engine_state --manifest-path server-rs\Cargo.toml`
4. `npm run test -- src/hooks/rpg-runtime-story/storyRequestCoordinator.test.ts src/hooks/rpg-runtime-story/storyRequestRuntime.test.ts src/hooks/rpg-runtime-story/useRpgRuntimeStoryController.test.tsx src/hooks/rpg-runtime-story/choiceActions.test.ts src/hooks/rpg-runtime-story/storyInteractionCoordinator.test.ts`
5. `npx eslint src/hooks/rpg-runtime-story/storyContextBuilder.ts src/hooks/rpg-runtime-story/progressionActions.ts --max-warnings 0`
6. `cargo fmt --manifest-path server-rs\Cargo.toml --all --check`
已发现的非本轮阻塞:
1. `npm run typecheck` 当前被既有 NPC 交易、背包/锻造 UI、测试 fixture、`src/services/ai.ts` 缺 import 等错误拦截。
2. `npm run test -- src/hooks/rpg-runtime-story` 当前有 1 个 `storyChoiceRuntime.test.ts` 战斗死亡/复活断言失败,属于审计后续 `4.5` post-battle 迁移范围。
### 4.4 NPC 聊天半量快照容错补丁
用户复测角色聊天时,点击 NPC 聊天选项后触发:
`Cannot read properties of undefined (reading 'length')`
复查调用链确认,后端 story engine projector 已经成为 `storyEngineMemory` 的主写入方,但部分快照或旧存档可能只携带 `currentChapter / worldMutations` 等增量字段,没有补齐 `activeThreadIds / recentCarrierIds / discoveredFactIds` 等数组字段。前端在 `syncNpcNarrativeState()` 中把半量对象当完整 `StoryEngineMemoryState` 消费,直接读取 `activeThreadIds.length`,导致 NPC 选项点击后的好感与叙事记忆同步中断。
本轮只做消费边界容错,不恢复前端 story engine 状态机:
1. `visibilityEngine.ts` 增加 `normalizeStoryEngineMemoryState()`,以 `createEmptyStoryEngineMemoryState()` 为基底补齐数组字段,同时保留后端快照已有字段。
2. `syncNpcNarrativeState()``appendStoryEngineCarrierMemory()` 在读写叙事记忆前统一归一化,避免半量快照在 NPC 聊天、物品回声等路径里崩溃。
3. `buildEncounterVisibilitySlice()``buildQuestVisibilitySlice()` 直接消费外部 memory 时也先归一化,保证 visibility 层独立调用时口径一致。
4. 新增 `echoMemory.test.ts` 回归用例,覆盖只有 `currentChapter`、缺少 `activeThreadIds` 的后端投影快照。
验证:
1. `npm run test -- src/services/storyEngine/echoMemory.test.ts src/services/storyEngine/visibilityEngine.test.ts`
2. `npm run check:encoding -- src/services/storyEngine/echoMemory.ts src/services/storyEngine/visibilityEngine.ts src/services/storyEngine/echoMemory.test.ts docs/technical/RPG_RUNTIME_STORY_ENGINE_BACKEND_MIGRATION_2026-04-28.md`
### 4.5 story prompt context 后端 projector 收口
本轮继续收口 `RPG_FRONTEND_SCRIPT_BACKEND_MIGRATION_COMPLETION_CHECK_2026-04-28.md` 中仍未完成的 `story engine / prompt context / AI story 请求编排`
1. `server-rs/crates/module-runtime-story-compat/src/prompt_context.rs` 新增 `build_runtime_story_prompt_context(...)`,基于后端持久化 `gameState` 投影:
- 场景描述、mutation、压力等级。
- encounter / NPC 好感、披露阶段、可谈话题、首次接触姿态。
- conversationSituation / conversationPressure / talkPriority。
- chapter、journey beat、worldMutations、chronicle、party relationship notes。
2. `POST /api/runtime/story/initial``POST /api/runtime/story/continue` 支持新主链 payload
- `sessionId`
- `clientVersion`
- `choice`
- `lastFunctionId`
- `observeSignsRequested`
- `recentActionResult`
- `requestOptions`
3. 后端收到 `sessionId` 后只从服务端 runtime snapshot 读取 `worldType / playerCharacter / sceneHostileNpcs / storyHistory / prompt context`;旧 `worldType / character / history / context` 字段仅保留兼容,不作为正式主链来源。
4. `runtime_chat_plain.rs``runtime_chat.rs` 同步支持 `sessionId`角色私聊、NPC 对话、NPC 单轮聊天、招募对话的 prompt context 也由后端快照投影;前端只继续提交对话草稿、目标角色、玩家发言和必要 UI 临时项。
5. `src/hooks/rpg-runtime-story/storyContextBuilder.ts` 缩减为 session 元信息适配器,不再推导 `conversationSituation / conversationPressure / NPC disclosure / partyRelationshipNotes / scene pressure` 等正式上下文。
6. `src/services/aiService.ts` 在存在 `runtimeSessionId`story initial/continue 只提交 session 轻量 payload聊天接口只附带 `sessionId` 与对话输入,不再上传完整 `StoryGenerationContext`
7. `src/hooks/rpg-runtime-story/sessionActions.ts` 领取任务奖励时不再运行前端 `chapterDirector / echoMemory`,只保留旧 UI 层奖励展示所需的本地字段;章节和 `storyEngineMemory.currentChapter` 等正式叙事字段等待后端 action snapshot 刷新。
8. `src/hooks/rpg-runtime-story/useRpgRuntimeNpcInteraction.ts` NPC 聊天闭合后不再调用前端 scene act runtime 推进 `storyEngineMemory.currentSceneActState`,也不再把 `deferredRuntimeState.storyEngineMemory` 写回正式 `GameState`
9. `src/hooks/rpg-runtime-story/choiceActions.ts` 兼容旧 `deferredRuntimeState` 时只允许采用场景字段,不再从 story moment 写入 `storyEngineMemory`
新增验证:
1. `cargo test -p module-runtime-story-compat prompt_context --manifest-path server-rs\Cargo.toml`
2. `cargo test -p shared-contracts runtime_story_ai_request --manifest-path server-rs\Cargo.toml`
3. `cargo test -p api-server runtime_story_initial_uses_server_snapshot_prompt_context_when_session_id_present --manifest-path server-rs\Cargo.toml`
4. `cargo check -p api-server --manifest-path server-rs\Cargo.toml --message-format short`
5. `npm run test -- src/services/ai.test.ts src/hooks/rpg-runtime-story/storyRequestCoordinator.test.ts src/hooks/rpg-runtime-story/useRpgRuntimeStoryController.test.tsx`
6. `npm run test -- src/hooks/rpg-runtime-story/sessionActions.test.ts src/hooks/rpg-runtime-story/choiceActions.test.ts src/hooks/rpg-runtime-story/npcEncounterActions.test.ts`
7. `npx eslint src/hooks/rpg-runtime-story/sessionActions.ts src/hooks/rpg-runtime-story/sessionActions.test.ts src/hooks/rpg-runtime-story/useRpgRuntimeNpcInteraction.ts src/hooks/rpg-runtime-story/choiceActions.ts src/hooks/rpg-runtime-story/choiceActions.test.ts --max-warnings 0`