This commit is contained in:
2026-04-30 17:49:07 +08:00
parent 805d6f8cae
commit 9d684cb7b3
615 changed files with 15368 additions and 6172 deletions

View File

@@ -0,0 +1,66 @@
# RPG 幕预览启动卡载入修复2026-04-30
## 背景
编辑器内点击“幕预览”后,独立预览层会一直停在“正在载入这一幕的游戏流程...”,无法进入真实 RPG 运行壳。
## 根因
`SceneActPreviewRuntime` 先调用 `handleCustomWorldSelect(profile)`,紧接着调用 `handleCharacterSelect(previewCharacter)`
`handleCharacterSelect()` 读取的是当前 render 闭包中的旧 `gameState`。此时 `handleCustomWorldSelect()` 写入的 `worldType` 还没有完成 React 状态提交,所以选角入口看到 `worldType` 为空后直接返回。随后幕预览虽然又手动写入了 `currentScene / currentScenePreset / currentEncounter`,却没有写入 `playerCharacter`,导致 `isPreviewReady` 永远不成立。
另一个隐患是:有 `currentEncounter` 时 story controller 不会主动生成普通首段剧情,而是交给 NPC 交互流接管;若预览没有显式注入一个可展示的 `currentStory`,运行面板也可能无法稳定挂载。
## 本轮继续修复
继续复测时发现,`SceneActPreviewRuntime` 虽然已经不再调用 `handleCharacterSelect()`,但仍会调用 `handleCustomWorldSelect(profile)` 来同步 runtime 静态资料。
这个入口是正式运行态的“选择世界”入口,会排队写入“已选择世界、尚未选角”的中间 `GameState`。在幕预览本地 `setGameState()` 写入玩家、场景与故事后,这个中间态仍可能覆盖回来,导致 `currentScenePreset``playerCharacter` 被清掉,预览层重新停在“正在载入这一幕的游戏流程...”。
本轮调整后:
1. 幕预览不再调用 `handleCustomWorldSelect(profile)`
2. 幕预览直接调用 `setRuntimeCustomWorldProfile(profile)``setRuntimeCharacterOverrides(buildCustomWorldRuntimeCharacters(profile))` 同步静态资料层。
3. `isPreviewReady` 同时校验:
- `currentScene === "Story"`
- `runtimeSessionId === "runtime-scene-act-preview"`
- 当前玩家就是本次预览角色
- 当前场景就是本次预览场景
- 当前 story 已经完成注入
4. 这样 preview ready 只依赖本次预览自己的完整启动结果,不再被正式选世界中间态影响。
## 修复口径
1. 幕预览不再调用 `handleCharacterSelect()` 触发后端开局副作用。
2. 幕预览不调用正式 `handleCustomWorldSelect(profile)`,而是直接同步 runtime 静态资料层。
3. 随后在同一个 `setGameState` 中一次性写入:
- `playerCharacter`
- `runtimeMode: "play"`
- `runtimePersistenceDisabled: true`
- `currentScene / currentScenePreset / currentEncounter`
- 玩家血蓝、技能冷却、装备、统计、进度、队伍、任务等运行态基础字段
- 当前幕 `currentSceneActState`
4. 幕预览使用固定临时 `runtimeSessionId: "runtime-scene-act-preview"`,并通过禁持久化快照保持不写正式存档。
5. 启动时同步 `hydrateStoryState()`,注入当前幕 NPC 的本地开场 story`RpgRuntimeShell` 立即满足挂载条件。
## 约束
1. 幕预览继续复用正式 `play` 运行链,不恢复旧 `preview/test` 行为分支。
2. 幕预览只允许前端做临时运行态装配;正式游戏开局仍由 `server-rs` 裁决。
3. 后续如把幕预览也迁到后端 bootstrap应新增专门的禁持久化 bootstrap 入口,而不是再次依赖 `handleCharacterSelect()` 的异步状态顺序。
## 验证
新增回归覆盖:
```bash
npm test -- --run src/components/CustomWorldEntityEditorModal.test.tsx
```
断言幕预览打开后:
1. 不再显示“正在载入这一幕的游戏流程...”。
2. `RpgRuntimeShell` 已收到预览玩家角色。
3. 运行态为 `play` 且禁用持久化。
4. 当前 story 已注入为当前幕 NPC 开场内容。