This commit is contained in:
2026-04-28 02:05:12 +08:00
parent 271db02e4a
commit 1eb090e4a5
39 changed files with 2671 additions and 165 deletions

View File

@@ -0,0 +1,127 @@
# 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 战斗胜利”进入战后收束
- 玩家死亡后必须直接走死亡复活链,且复活时重置到开局场景第一幕,不能先推进下一幕
## 继续收口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. 这样可以保证作品测试、幕预览与正式游戏在“死亡后回开局第一幕”这一口径上继续对齐
## 结论
本次修复后RPG 战斗 compat 主链的胜负判定口径变为:
1. 玩家死亡优先于敌方倒地胜利
2. 胜利与败北都只走确定性固定流程
3. 不再出现“玩家已死却结算成战斗胜利”的串线结果