# 战斗直结算与表现修复记录(2026-04-27) 更新时间:`2026-04-27` ## 1. 问题背景 本次修复针对 RPG 运行态战斗里两类直接可感知的问题: 1. 战斗面板中的“普通攻击”“技能释放”等选项点击后,体验上像是又走了一次剧情/模型推演,而不是立刻播动作并结算伤害。 2. 伤害飘字与敌方头顶血条在战斗里没有稳定可见,尤其是最后一击和脱战前后的几帧容易直接丢失。 ## 2. 本次约束 为避免后续回归,本次明确补充以下工程约束: 1. 只要已经处于 `inBattle = true` 的战斗面板阶段,`battle_*` 与战斗内 `inventory_use` 都必须走前端现有的本地确定性战斗播放链。 2. 这类动作只负责“播放动作 -> 扣血/扣蓝/冷却 -> 刷新下一轮 options / 脱战收尾”,不能再回到服务端剧情推演分流。 3. 脱战前最后一拍的画布表现必须保留,不能因为 `inBattle` 提前变成 `false` 就把伤害飘字、敌方血条和受击反馈一起关掉。 ## 3. 落地改动 ### 3.1 战斗动作分流 文件:`src/hooks/rpg-runtime-story/choiceActions.ts` 调整: 1. 新增“战斗直结算动作”判定。 2. 当运行态已经处于战斗中时,`battle_*` 和 `inventory_use` 不再命中 `runServerRuntimeChoiceAction()`。 3. 这些动作统一回到 `runLocalStoryChoiceContinuation()`,沿用现有 `buildBattlePlan + playback` 本地确定性链路。 结果: 1. 普通攻击、技能释放、调息、逃跑、战斗内用物品都会直接播动作并结算。 2. UI 不再把这类按钮误导成“剧情推演中”的体验。 ### 3.2 战斗画布表现保留 文件:`src/components/game-canvas/GameCanvasEntityLayer.tsx` 调整: 1. 新增 `hasCombatAfterimage` 判定,用于识别“虽然 `inBattle` 已经开始收尾,但敌方仍处于受击/死亡帧或刚产生过伤害反馈”的阶段。 2. 伤害反馈样本采集、飘字事件保留、敌我血条展示统一改为依赖 `shouldRenderCombatPresentation`,而不是只看 `inBattle`。 结果: 1. 伤害数值飘字不会在最后一击前被提前清空。 2. 敌方头顶血条能跟着受击与死亡过渡完整显示出来。 ### 3.3 面板文案修正 文件:`src/components/rpg-runtime-panels/RpgAdventurePanel.tsx` 调整: 1. 运行态仍需锁输入的短暂阶段,如果当前处于战斗中,加载提示改为“战斗结算中...”。 2. 非战斗态保留“剧情推演中...”。 结果: 1. 战斗按钮点击后的反馈语义与真实执行链路一致。 2. 用户不会再误判为“点击普通攻击/技能后又触发了一次模型推理”。 ## 4. 回归关注点 后续若继续改 `server-rs` / Runtime Story 的战斗 option 下发,需要继续遵守: 1. 服务端可以负责下发合法战斗选项和最终快照结构。 2. 但战斗内直接动作的逐帧播放与即时结算,默认仍以前端确定性链路为准。 3. 若未来要把这条链路整体迁回 `server-rs`,必须先补齐“无推理、可逐帧播放、可保留最终受击帧”的等价表现方案,再替换当前实现。 ## 5. 追加修复(2026-04-27 晚) 本日后续排查又发现一处更直接的战斗跳过根因: 1. `src/hooks/combat/battlePlan.ts` 旧实现会在一次点击里继续跑完整段 `turnOrder`,而不是只结算“玩家一次声明动作 + 最多一次敌方反击”。 2. `battle_attack_basic` 还会复用技能挑选逻辑,导致“普通攻击”可能被本地随机映射成别的技能动作。 本轮已追加修复: 1. 本地战斗计划严格收束为单回合结算,不再一次点击连跑多轮。 2. `battle_attack_basic` 固定走基础攻击,不再随机选技能。 3. 战斗内 `inventory_use` 在本地计划中按单次动作消费物品、应用恢复/冷却收益,不再落回攻击型分支。 追加验收口径: 1. 战斗中点击一次普通攻击 / 技能 / 物品 / 调息,不会直接把整场战斗过程跳过。 2. 若敌人未被这一击打死,当前次结算结束后仍停留在战斗态,并刷新下一轮战斗选项。 3. “普通攻击”不会显示成其他技能的动作与耗蓝结果。 ## 6. 再追加修复(2026-04-27 夜间二次排查) 用户复测后又暴露出一类更隐蔽的问题: 1. 面板展示层可能仍在显示战斗选项,但逻辑态 `gameState.inBattle` 已短暂回落为 `false`。 2. 旧实现里,`src/hooks/rpg-runtime-story/choiceActions.ts` 只要看到 `inBattle === false`,就会把 `battle_*` / `inventory_use` 重新分流回服务端 runtime。 3. 这会让用户在“视觉上仍处于战斗”的窗口点击选项时,直接命中服务端快照结果,体感仍然是“点一下就把战斗过程跳过”。 本轮追加修正: 1. 战斗直结算判定不再只依赖 `gameState.inBattle`。 2. 只要当前选项本身属于 `battle_*` / `inventory_use`,并且任一战斗上下文仍然存在 `currentBattleNpcId`、`currentNpcBattleMode`、存活敌人、当前故事仍在显示战斗选项 就必须继续走本地逐帧战斗链。 3. 为此补充了回归测试,覆盖“`inBattle` 已短暂变成 `false`,但战斗展示仍在”的残留窗口。 新增验收口径: 1. 即使 `inBattle` 出现一帧级别的提前回落,只要玩家看到的仍是战斗选项,点击后也不会跳回服务端直结算。 2. 战斗面板残留显示期间点击 `battle_*` / `inventory_use`,仍应播放本地战斗过程,而不是直接跳到结果快照。