# RPG 战斗败北结果修复(2026-04-27) 更新时间:`2026-04-27` ## 背景 线上出现了一个确定性战斗兼容链 bug: 1. 玩家在 RPG 战斗中已经被打死。 2. 画面却突然跳到“对方已经败下阵来”的胜利收束。 3. 最终走了战斗胜利固定流程,而不是死亡动画与三秒后复活。 这与既有战斗结束规则冲突: 1. 玩家血量小于等于 `0` 时必须优先进入败北/死亡流程。 2. 战斗胜利只允许在玩家仍然存活、且敌方被击败时成立。 ## 根因 问题出在 `server-rs/crates/module-runtime-story-compat/src/battle.rs` 的 compat 战斗结算: 1. `apply_player_damage(...)` 旧实现把玩家生命最低钳在 `1`,不会真正写出 `0` 血。 2. `resolve_battle_action(...)` 旧实现只有 `ongoing / victory / spar_complete` 三种结果,没有正式 `defeat` 分支。 3. 当同一回合里敌方也被打到 `0` 附近时,结算会只看敌方血量并直接归类到 `victory`。 这会把“玩家应败北”的回合错误地写成“敌方已败下阵来”。 继续复测后又确认,**作品测试** 里还有第二层根因: 1. 作品测试并不总是走服务端 compat 战斗结算; 2. 一部分战斗点击仍会走前端本地 `src/hooks/combat/battlePlan.ts`; 3. 这条本地链原先会在玩家或同伴刚打掉最后一只敌人时,立刻: - 把敌方从 `sceneHostileNpcs` 移除; - 把 `currentNpcBattleOutcome` 直接写成 `fight_victory`; 4. 这样会导致同一轮中原本还应该出手的敌方单位失去反击机会; 5. 体感上就会变成“我明明该死了,但作品测试先把对方判成败下阵来”。 ## 本次修复 ### 1. 后端 compat 战斗结算补齐败北态 在 `module-runtime-story-compat::battle` 中补齐: 1. 显式 `BattleResolutionOutcome`:`ongoing / victory / spar_complete / defeat` 2. `apply_player_damage(...)` 允许把 `playerHp` 写到 `0` 3. 新增统一胜负决议: - 玩家 `hp <= 0` 时优先判定 `defeat` - 只有玩家仍存活时,敌方 `hp <= 0` 才能进入 `victory / spar_complete` ### 2. 服务端状态写回补齐 `fight_defeat` 当 compat 战斗判定为失败时: 1. `presentation.battle.outcome = "defeat"` 2. `currentNpcBattleOutcome = "fight_defeat"` 3. 关闭战斗态,清理当前遭遇与敌对列表 4. 不发放胜利经验、不累计 `hostileNpcsDefeated` ### 3. 前端服务端战斗回包分支补齐失败态 前端 `storyChoiceRuntime` 同步修正: 1. 服务端回包若 `playerHp <= 0`,无论 battle outcome 是否同时包含敌方掉血,都优先走死亡动画与复活链 2. 敌方是否播放死亡态时,`defeat` 不再被当成“目标已被击败” ### 4. 本地 battlePlan 改为整轮后统一收束 在 `src/hooks/combat/battlePlan.ts` 中同步修正: 1. 普通战斗不再在玩家/同伴动作阶段即时写入 `fight_victory` 2. 敌方被打到 `0` 血时,先只保留为“本轮已死亡但尚未清场”的状态 3. 整轮 turn order 跑完后,再统一按以下优先级判定: - 玩家 `playerHp <= 0`:`fight_defeat` - 否则若敌方全部 `hp <= 0`:`fight_victory` - 否则继续战斗 4. `spar_complete` 仍维持切磋的即时收束规则,不和正式战斗混用 ## 类型同步 本轮同步扩展: 1. `NpcBattleOutcome`:新增 `fight_defeat` 2. `StoryNpcChatState.combatContext.battleOutcome`:新增 `defeat` 3. `aiService` 的战斗上下文类型:新增 `defeat` ## 回归测试 新增以下回归: 1. `module-runtime-story-compat`: - 同回合双方都归零时,必须优先判定为 `defeat` 2. 前端 `storyChoiceRuntime`: - 服务端返回 `defeat` 且 `playerHp = 0` 时,必须进入死亡复活流程,不能进入胜利收束 3. 前端 `battlePlan`: - 作品测试 / 本地战斗链里,同一轮玩家先手打空敌方但随后自己被打死时,最终必须判定为 `fight_defeat` 4. 前端 `storyChoiceContinuation` / `useRpgRuntimeNpcInteraction`: - `fight_defeat` 不能再被当成“本地 NPC 战斗胜利”进入战后收束 - 玩家死亡后必须直接走死亡复活链,且复活时重置到开局场景第一幕,不能先推进下一幕 5. 前端 `postBattleFlow`: - 复活回到开局场景时,必须重新走首幕 encounter preview 恢复链 - 第一幕主交互 NPC 与同幕陪衬 NPC 要继续沿用既有场景槽位,不能退化成全部站成一排 ## 继续收口(2026-04-28) 继续复测后又确认,作品测试里还残留一层“败北被当成胜利”的本地剧情续写问题: 1. `storyChoiceContinuation` 旧判断把 `currentNpcBattleOutcome` 只要是 truthy 就当成本地 NPC 战斗可收束; 2. 这会把 `fight_defeat` 也误送进 `finalizeNpcBattleResult(...)`; 3. 后续又因为 `!nextState.inBattle` 条件过宽,继续走到 `buildPostBattleVictoryStory(...)`,表现为: - 死亡后仍显示“对方已经败下阵来” - 并且还会把场景幕推进到下一幕 本轮补充修正如下: 1. 本地 NPC 战斗收束只接受 `fight_victory / spar_complete` 2. `fight_defeat` 一律交回死亡复活主链 3. NPC 战后结算 helper 显式拒绝 `fight_defeat` 4. 本地 `battle` 的战后推进只允许: - 正式 NPC 战斗明确 `fight_victory` - 切磋明确 `spar_complete` - 非 NPC 通用敌对战斗 `!inBattle` 5. 这样可以保证作品测试、幕预览与正式游戏在“死亡后回开局第一幕”这一口径上继续对齐 ## 继续收口:复活后首幕 NPC 与站位恢复(2026-04-28) 在继续复测后,又确认死亡复活链还有一层表现问题: 1. 角色虽然已经回到开局场景第一幕; 2. 但复活态旧实现只是重置 `currentSceneActState`,没有重新恢复第一幕 encounter preview; 3. 于是画布只能把第一幕 NPC 都按普通 ambient 角色绘制; 4. 视觉上就会表现为: - 主交互 NPC 没有按首幕重新成为前景目标 - 同幕 NPC 失去原本的前后排关系 - 最终看起来像“所有人站成一排” 本轮补充修正如下: 1. `buildRevivedFirstSceneState(...)` 在重置到首幕之后,立即复用 `ensureSceneEncounterPreview(...)` 2. 这样复活链与“开局进入世界 / 场景正常进场”继续共用同一套首幕恢复逻辑 3. 第一幕主交互 NPC、同幕陪衬 NPC 与既有槽位会一起恢复,不再额外发明一套复活专用站位规则 ## 结论 本次修复后,RPG 战斗 compat 主链的胜负判定口径变为: 1. 玩家死亡优先于敌方倒地胜利 2. 胜利与败北都只走确定性固定流程 3. 不再出现“玩家已死却结算成战斗胜利”的串线结果