Files
Genarrative/docs/technical/BATTLE_DIRECT_SETTLEMENT_PRESENTATION_FIX_2026-04-27.md
2026-04-27 22:50:18 +08:00

5.5 KiB
Raw Blame History

战斗直结算与表现修复记录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,并且任一战斗上下文仍然存在 currentBattleNpcIdcurrentNpcBattleMode、存活敌人、当前故事仍在显示战斗选项 就必须继续走本地逐帧战斗链。
  3. 为此补充了回归测试,覆盖“inBattle 已短暂变成 false,但战斗展示仍在”的残留窗口。

新增验收口径:

  1. 即使 inBattle 出现一帧级别的提前回落,只要玩家看到的仍是战斗选项,点击后也不会跳回服务端直结算。
  2. 战斗面板残留显示期间点击 battle_* / inventory_use,仍应播放本地战斗过程,而不是直接跳到结果快照。