Files
Seek-Battle-Evacuate/Docs/Tech/04_TurnBasedCombatDevelopmentTODO.md
T
huangjun 7dcac0ac87 M1 回合制战斗架构收敛与 1v1 攻击闭环:BattleRuntime 内核、组件唯一入口、UI 数据驱动与 EditMode 测试
- 删除中间契约(BattleStartRequest/BattlePlayerInput/BattleReturnPlan/桥接与策略接口/FlowState),收敛为 BattleViewState/BattleStep/一层 BattleEvent
- 新增内部 BattleRuntime/BattleUnit/BattleStatus 共享上下文;TurnBattleComponent 成为唯一外部入口,吸收启动/暂停/回写职责
- 内核支持 1v1 攻击闭环:效果公式、HP/MP 钳制、阵亡、Victory/TotalDefeat、敌人随机决策;敌人行动间歇 0.6s 由组件协程编排并经 StepListener 推送
- UI:玩家卡片/敌人槽/回合槽按战局重建,图标配置驱动(含新增 EnemyConfig.Icon),每轮刷新一次 TurnSlots 且轮内只移动高亮
- 新增 Tests 程序集 + InternalsVisibleTo,25 个 BattleRuntime 纯逻辑用例全部通过
2026-09-02 08:42:04 +08:00

37 KiB
Raw Blame History

回合制战斗开发 TODO

状态:进行中(M0 已完成,M1 起按本文的新架构收敛)

创建日期:2026-08-31

架构修订:2026-09-01

相关设计:Docs/GameDesign/04_TurnBasedCombat.md、Docs/GameDesign/03_RunExploration.md、Docs/GameDesign/06_PrototypeScope.md

目标:在 Main 场景地图上完成半透明多对多回合制战斗,由 TurnBattleComponent 统一承接战斗启动、运行和结果回写,并保持规则内核可独立测试。

一、完成纪律

  • 里程碑是否完成只看标题中的 [ ];第八节的 22 条设计验收不代替里程碑完成状态。
  • 只有完成该里程碑列出的 Unity 编译、EditMode 测试和 Play Mode 可见验收,并记录证据后,才能改为 [x]。
  • 每次只实现当前里程碑;后续能力不得以“预留接口”为理由提前进入当前阶段。
  • 生成代码不能手改。Luban 结构或数据需要调整时,修改源表后运行 数据表/gen_cli.sh。
  • 战斗内核不直接读写 SaveData,不直接查找或操作地图敌人物体、掉落物和局外结算。
  • 静态检查、Unity 编译、EditMode 和 Play Mode 分别记录,不互相代替。

每个里程碑完成后按以下格式补证据:

代码状态:未开始 / 进行中 / 已完成
Unity 编译:未验证 / 通过(日期、日志或截图)
EditMode:未验证 / 通过(用例数量、结果)
Play Mode:未验证 / 通过(操作步骤、可见结果、截图或录像)
提交或差异:commit / diff 路径
剩余问题:无 / 具体问题

二、总体进度

里程碑 可见结果 当前状态
M0 战斗壳层与单局基础状态 Main 场景可打开和关闭半透明战斗壳层,地图更新暂停 已完成
M1 架构收敛与 1v1 攻击闭环 玩家攻击、敌人反击并产生胜负 已完成
M2 4v4 调度、先制与确定性随机 多人队伍按实时速度行动,普通和先制顺序正确 未开始
M3 技能、目标、MP 与数值效果 攻击和技能可合法选目标并结算 HP/MP 未开始
M4 状态、眩晕与速度变化 眩晕跳过行动,速度变化立即刷新顺序 未开始
M5 逃跑、结束矩阵与单局回写 四种结果正确,连续战斗继承 HP/MP 未开始
M6 真实探索遭遇与地图恢复 碰撞进入战斗,胜利、逃跑、掉落和保护闭环 未开始
M7 战斗 UI 与输入限制完成 1 至 4 人战斗信息和操作完整可用 未开始
M8 全量回归与原型完成 22 条设计验收和最低测试矩阵全部留证 未开始

验证记录

里程碑 代码 Unity 编译 EditMode Play Mode 证据
M0 已完成 通过(2026-08-31,dotnet build SepCore.Runtime.csproj 0 错误;Unity 编辑器内 Play Mode 实测编译通过) 不适用(M0 无 EditMode 用例) 通过(2026-08-31,见 M0 证据) 已记录(M0 小节)
M1 已完成 通过(2026-09-02,dotnet build SepCore.Runtime.csproj 0 错误 0 警告;Unity 编辑器内编译通过) 通过(2026-09-02,25 个 BattleRuntime 纯逻辑用例全部通过) 通过(2026-09-02,见 M1 证据) 已记录(M1 小节)
M2 未开始 未验证 未验证 未验证 未记录
M3 未开始 未验证 未验证 未验证 未记录
M4 未开始 未验证 未验证 未验证 未记录
M5 未开始 未验证 未验证 未验证 未记录
M6 未开始 未验证 未验证 未验证 未记录
M7 未开始 未验证 未验证 未验证 未记录
M8 未开始 未验证 未验证 未验证 未记录

三、已确认的范围与规则

项目 约定
战斗承载 不新增 Battle 场景;在 Main 场景地图上覆盖半透明顶级 BattleForm
地图暂停 战斗期间暂停地图角色、敌人 AI、碰撞、搜索和单局计时,战斗 UI 继续响应
模块边界 外界通过 TurnBattleComponent 启动战斗、提交操作和接收结果;战斗内部不访问场景对象和存档
内部协作 调度、指令校验、效果、状态和敌人决策共享当前 BattleRuntime,不为内部函数调用建立请求/响应 DTO
随机数 地图生成、敌人决策、逃跑和掉落依次消费同一个本局随机源,战斗不重新播种
眩晕 DurationRounds=N 影响目标未来 N 次行动机会;Stun(1) 跳过下一次行动后失效
状态重复施加 同类状态不叠层,保留较长的剩余持续次数
友方目标 SingleAlly 包含施法者;Self 只能选择施法者自己
阵亡目标 阵亡和已逃跑单位不是合法目标;首版没有战斗内复活
战斗表现 首版使用面板式即时结算,不做站位动画、攻击动画、VFX、Timeline 或音效同步
操作方式 首版使用鼠标点击行动和目标
行动顺序 UI 显示当前行动者和本轮剩余行动者;速度变化后立即刷新,不预测下一轮

四、架构与数据所有权

4.1 唯一外部入口

TurnBattleComponent 是战斗模块的门面和生命周期所有者。外部模块不直接创建或持有 BattleRuntime、BattleUnit、调度器和效果结算器。

它负责:

  • 持有单局临时玩家状态、计时、探索暂停和战斗占用。
  • 使用 BattleEncounter、单局玩家状态、配置和本局随机源创建当前战斗。
  • 接收 UI 的 BattleCommand 并同步推进战斗。
  • 向 UI 提供当前只读视图和本次行动记录。
  • 战斗结束时把玩家 HP/MP 写回单局临时状态。
  • 完成单局状态回写后,通过开战方提供的一次性完成回调返回 BattleResult;探索层据此处理敌人实例、掉落、保护和单局失败。
  • 关闭战斗时清理当前战斗数据并恢复允许恢复的地图更新。

对外最小调用面:

bool TryStartBattle(BattleEncounter encounter, Action<BattleResult> onCompleted);
BattleStep SubmitCommand(BattleCommand command);
BattleViewState GetViewState();
bool IsBattleActive { get; }

调试入口可以不提供完成回调。具体 C# 返回形式可以随实现调整,但完成通知必须由 Component 统一发出,且不得把内部可变对象暴露给 UI 或探索层。

4.2 内部共享运行时

TurnBattleComponent 持有唯一的当前战斗:

TurnBattleComponent
├─ 单局状态
│  ├─ RunPlayerState[]
│  ├─ RunElapsedMs
│  ├─ pause state
│  └─ IRunRandomSource
│
└─ BattleRuntime(仅战斗期间存在)
   ├─ BattleUnit[]
   ├─ RoundNumber
   ├─ CurrentActorUnitId
   ├─ ActedUnitIds
   ├─ IsPreemptive
   ├─ PendingEvents
   └─ BattleResult

内部规则可以由多个普通 C# 类协作完成,例如:

  • TurnScheduler:选择下一行动者、开始下一轮。
  • CommandExecutor:校验并执行玩家或敌人指令。
  • EffectResolver:按顺序结算数值和状态效果。
  • EnemyDecision:从当前可用行动和合法目标中选择指令。

这些名称不是必须提前建立的类型。只有单个类已经承担独立规则且需要独立测试时才提取;简单逻辑保留在 BattleRuntime 内。

内部协作规则:

  • 所有协作者操作同一个 BattleRuntime,不复制单位、轮次和状态集合。
  • 一次 StartBattle 或 SubmitCommand 在主线程同步完成状态推进,不能留下多个并行写入者。
  • BattleRuntime 是当前战斗事实的唯一来源;UI 视图和行动记录从它生成,不反向驱动规则。
  • 战斗内部不使用全局事件总线传递调度、效果和结束状态。
  • 内部类型优先使用 internal;只有外部真实消费的契约才使用 public。

4.3 必要的外部数据

数据 方向 作用
BattleEncounter 探索层 -> Component 指定触发敌人实例、敌人队伍预设和是否先制
BattleCommand UI -> Component 当前玩家的攻击、技能、目标或逃跑选择
BattleViewState Component -> UI 当前单位数值、行动者、剩余顺序和可用行动
BattleStep Component -> UI 本次同步推进产生的行动记录、最新视图和可选最终结果
BattleResult Component -> 单局/探索 最终 Outcome 和玩家战后 HP/MP
IRunRandomSource 单局系统 -> Component 共享随机序列和 EditMode 可注入随机源
IBattleConfigProvider 配置系统 -> Component 隔离 Luban 与纯逻辑测试

不建立以下中间结构:

  • 不建立 Component 到内部 Runtime 的 BattleStartRequest;Component 直接使用 Encounter 和单局状态初始化 Runtime。
  • 不建立 BattleController;Component 是对外控制入口,Runtime 是内部状态和规则核心。
  • 不建立 BattleReturnPlan;Component 根据 BattleResult 回写单局状态,探索层根据 Outcome 执行地图后果。
  • 不建立 IExplorationBattleBridge;真实探索接入使用现有组件的直接调用或一个明确回调。
  • 首版只有随机敌人行为,不建立 IEnemyBattlePolicy;出现第二种可替换策略时再提取。
  • 不在 BattleStep 和 BattleViewState 中重复保存流程状态;Result != null 表示战斗完成。

4.4 必要的内部数据

数据 生命周期 说明
RunPlayerState 整个单局 跨战斗保留玩家当前 HP/MP 和已结算战斗属性
BattleRuntime 单场战斗 单位、轮次、行动机会、事件和最终结果的唯一所有者
BattleUnit 单场战斗 运行时 ID、阵营、属性、行动、状态、阵亡和逃跑
BattleStatus 单场战斗 状态类型和剩余行动机会次数
BattleEvent 单次推进 UI 需要展示的行动者、目标和数值/状态变化

BattleViewState 可以包含单位视图,但内部调度和效果结算不得读取该视图。8 个单位规模下,在外部边界生成只读视图的成本可接受。

4.5 目录与程序集

位置 所有权
Assets/GameMain/Scripts/Runtime/Battle/ BattleRuntime、内部单位和规则、必要的外部数据与配置接口
Assets/GameMain/Scripts/Runtime/CustomComponent/Run/ TurnBattleComponent、RunPlayerState 和单局回写
Assets/GameMain/Scripts/Runtime/CustomComponent/Random/ 本局种子与共享随机源
Assets/GameMain/Scripts/UI/Battle/ BattleForm 和子 View 的表现逻辑
Assets/GameMain/Tests/EditMode/Battle/ 直接针对 BattleRuntime 和规则协作者的纯逻辑测试

不新增战斗运行时程序集。Runtime 不反向引用 Presentation;BattleForm 通过 GameEntry.TurnBattle 调用公开战斗入口。EditMode 测试建立独立测试程序集,并通过 InternalsVisibleTo 访问内部战斗规则,不为测试把 BattleRuntime 改成公开 API。

4.6 完整调用链

队长与地图敌人碰撞
    -> 探索层创建 BattleEncounter
    -> TurnBattleComponent.TryStartBattle
    -> 拒绝重复战斗并校验 Encounter、玩家、配置和随机源
    -> 创建唯一 BattleRuntime
    -> 提交战斗占用,暂停探索更新与计时
    -> 打开 BattleForm
    -> BattleRuntime 推进到等待玩家或战斗结束
    -> BattleForm 读取 BattleViewState
    -> 玩家提交 BattleCommand
    -> TurnBattleComponent 同步校验并推进 BattleRuntime
    -> 内部直接完成调度、效果、敌人自动行动和结束判定
    -> 返回 BattleStep,UI 展示 BattleEvent 和最新视图
    -> BattleResult 产生后,TurnBattleComponent 回写 RunPlayerState
    -> TurnBattleComponent 调用本场战斗的一次性完成回调
    -> 探索层按 BattleResult 的 EncounterId 和 Outcome 处理敌人、掉落、保护或失败
    -> 清理 BattleRuntime,关闭 BattleForm
    -> 允许继续单局时恢复探索更新与计时

五、边界契约

5.1 身份

标识 用途
EncounterId 地图敌人实例的单局唯一标识;不能用敌人配置 ID 代替
BattleUnitId 本场战斗单位的唯一运行时标识;重复敌人配置必须拥有不同 ID
CharacterId 玩家角色配置标识,也是战斗结果回写键
EnemyConfigId 敌人种类配置标识,同一敌人队伍内可以重复
PartyOrder 同阵营同速度时的最终并列顺序

所有 UI 指令和目标选择都使用 BattleUnitId。

5.2 遭遇输入

BattleEncounter 只包含:

字段 说明
EncounterId 触发碰撞的地图敌人实例
EnemyPartyConfigId 该实例代表的敌人队伍预设
IsPreemptive 碰撞时敌人警惕值未满为 true

地图坐标、碰撞器、GameObject 和警惕组件由探索层持有,不进入战斗。

5.3 单局玩家状态

RunPlayerState 保存 CharacterId、PartyOrder、当前及上限 HP/MP、最终 ATK/MAT/Speed、普通攻击 ID 和技能 ID。

TurnBattleComponent 开战时直接从这些状态创建玩家 BattleUnit。不再建立字段相同的 BattlePlayerInput。战斗只修改 BattleUnit,结束时统一回写当前 HP/MP。

5.4 玩家指令

BattleCommand 至少包含:

字段 说明
ActorUnitId 当前获得行动机会的玩家单位
CommandType Attack、Skill、Item 或 Escape
ActionConfigId 攻击或技能配置 ID;逃跑为 0
TargetUnitIds 运行时目标 ID 列表
  • Item 按钮可见但首版禁用,不产生指令。
  • 只有当前玩家单位能提交指令。
  • 全体目标由战斗内核展开,UI 不自行拼装。
  • 非法指令不消耗 MP、行动机会或随机数。

5.5 UI 视图与行动记录

BattleViewState 是 UI 唯一读取面,至少包含:

  • 当前轮次和当前行动者。
  • 全部单位的 ID、阵营、配置 ID、显示顺序、HP/MP、速度、阵亡、逃跑和状态。
  • 本轮从当前行动者开始的剩余顺序。
  • 当前玩家可用的行动 ID。

BattleStep 至少包含:

  • 本次推进产生的有序 BattleEvent。
  • 推进结束后的 BattleViewState。
  • 仅在战斗结束时存在的 BattleResult。

BattleEvent 使用一层记录表达行动者、行动、目标以及数值或状态变化。只有出现无法由一层记录清晰表达的真实表现需求时,才增加子记录类型。

5.6 战斗结果

BattleOutcome 固定为:

结果 条件 单局是否继续
Victory 所有敌人阵亡 是
AllEscaped 所有玩家成功逃跑且没有玩家阵亡 是
PartialEscapeDefeat 至少一人逃跑,其余仍在战斗中的玩家全部阵亡 是
TotalDefeat 所有玩家阵亡且无人逃跑 否

BattleResult 包含 EncounterId、Outcome 和每名玩家的 CharacterId、战后 HP/MP、是否阵亡、是否逃跑。

战斗结果保留原始值,阵亡者 HP 为 0。TurnBattleComponent 在非 TotalDefeat 结果中统一应用阵亡角色恢复 1 HP/1 MP 的规则。

5.7 随机和配置依赖

public interface IRunRandomSource
{
    int NextInt(int minInclusive, int maxExclusive);
    bool RollPermille(int successPermille);
}
  • 本局种子由局外带入,RandomComponent.BeginRun(seed) 初始化共享随机源。
  • 地图生成、敌人决策、逃跑和掉落依次消费同一个实例。
  • 战斗不能按 EncounterId、回合数或时间重新播种。
  • RollPermille(0) 必须失败,RollPermille(1000) 必须成功。
  • EditMode 使用可注入序列随机源。

IBattleConfigProvider 隔离战斗规则与 GameEntry.Luban。配置缺失导致启动失败;正常行动不重复扫描全表完整性。

5.8 探索暂停与返回

  • TurnBattleComponent.IsExplorationPaused 是地图更新统一门禁。
  • 玩家移动、队员跟随、敌人巡逻/警惕/追击、搜索、地图碰撞和单局计时遵守该门禁。
  • 战斗 UI 和使用 unscaled time 的 UI 反馈不受该门禁影响。
  • 不以 Time.timeScale = 0 作为唯一暂停机制。
  • 开战校验全部通过后才能提交暂停;启动失败不消耗随机数、不修改单局状态。
  • Victory 返回 EncounterId 并要求探索层移除触发敌人和结算掉落。
  • AllEscaped、PartialEscapeDefeat 返回 EncounterId 并要求探索层恢复触发敌人和开启保护。
  • TotalDefeat 进入单局失败,不短暂恢复探索。
  • 探索层根据 Outcome 执行固定后果,不重新根据玩家 HP 或逃跑数量判定 Outcome。

六、固定战斗规则

6.1 下一行动者

每次行动或跳过结束后,从本轮尚未获得行动机会且仍在战斗中的单位中选择:

  1. 先制第一轮仍有玩家未行动时,只在玩家中选择。
  2. 当前速度高者优先。
  3. 当前速度相同时,玩家优先于敌人。
  4. 同阵营同速度时,PartyOrder 小者优先。
  5. 已行动单位不会因速度变化再次行动。
  6. 候选集为空时开启下一轮;先制限制只适用于第一轮。

阵亡和逃跑单位不进入候选集。眩晕单位会获得行动机会,随后跳过、标记已行动并消耗一次眩晕持续次数。

6.2 数值与效果

  • 每个 BattleEffect 按配置顺序结算。
  • 目标变化值为 FlatValue + SourceStat * SourceScalePermille / 1000。
  • 使用整数运算;首版没有暴击、防御、命中、浮动伤害和元素克制。
  • HP/MP 钳制到 0 与对应上限之间。
  • HP 到 0 时立即阵亡,之后不能成为合法目标或行动者。
  • MP 不足的行动不可执行,不消耗行动机会。
  • 战斗中的属性和状态变化不写回探索;只回写最终 HP/MP。

6.3 眩晕

  • Stun(1) 跳过目标未来一次行动机会。
  • 目标本轮尚未行动时被眩晕,跳过本轮即将到来的行动。
  • 目标本轮已经行动时被眩晕,跳过下一轮行动。
  • 重复施加时取当前和新持续次数中的较大值,不相加。
  • 战斗结束时清除全部战斗状态。

6.4 敌人行动

  • 敌人从当前 MP 足够的行动中等概率选择。
  • 单体行动从合法目标中等概率选择;全体行动不额外随机目标。
  • 没有可执行行动时跳过本次机会,不能形成无限循环。
  • 首版逻辑直接读取 BattleRuntime 并产生内部指令,不建立策略接口。

6.5 逃跑

  • 只有当前玩家行动者可以逃跑。
  • 每次尝试使用 GlobalConfig.EscapeSuccessPermille 和本局随机源独立判定。
  • 成功和失败都消耗当前角色本轮行动机会。
  • 成功后角色立即离开候选集和合法目标集合,但不视为阵亡。
  • 仍有玩家留在战斗中时,单个角色逃跑不结束战斗。

七、开发里程碑

[x] M0 战斗壳层与单局基础状态

目标:在 Main 场景得到第一个可见战斗壳层,并验证暂停和重复进入门禁。

已完成:

  • RandomComponent(GameEntry.Random,Launcher.unity 的 "Random" GameObject)、IRunRandomSource 和 RunRandomSource。
  • TurnBattleComponent(GameEntry.TurnBattle,Launcher.unity 的 "Turn Battle" GameObject)的玩家临时状态、计时、暂停和战斗占用。
  • RunBattleCoordinator M0 空流程(GameEntry.RunBattle)负责预留占用、构建调试请求、暂停/恢复和打开/关闭 BattleForm;M1 将这些职责并回 Component。
  • BattleForm.Logic.cs 半透明壳层、按钮绑定、道具禁用和占位反馈。
  • BattleDebuggerWindow 调试入口:仅 Editor/Development Build,路径 Battle/Shell,固定 EncounterId=1、敌人队伍预设 1;缺少单局状态时使用配表角色和固定种子初始化。
  • UIFormType.BattleForm = 103 和对应 UIFormConfig 已导出。
  • M0 同时提前建立了一批后续 DTO 和接口;它们不是新架构的既定合同,M1 按第四、五节删除、合并或改名。

已知约束:

  • 单局计时暂停来源不止战斗;暂停菜单接入后必须组合暂停来源,不能由战斗结束无条件解除全部暂停。
  • RunRandomSource 使用 System.Random;只要求同一构建内以相同输入复现,不承诺跨平台、跨运行时版本产生相同序列。
  • BattleView 的回合槽和敌人槽使用运行时实例化模板(turnSlotsRoot / enemySlotsRoot 和 template),数据接入时由 Logic 创建显示项。

验证证据:

代码状态:已完成
Unity 编译:通过(2026-08-31,dotnet build SepCore.Runtime.csproj 0 错误;Unity 编辑器内编译与 Play Mode 实测通过)
EditMode:不适用(M0 无 EditMode 用例)
Play Mode:通过(2026-08-31,Debugger -> Battle/Shell 实测:
  1. Main 场景未切换、未卸载,半透明 BattleForm 覆盖在地图上;
  2. 调试入口 Open 打开战斗壳层,按钮可点击响应;
  3. 打开后 RunElapsedMs 停止增长,IsTimerPaused、IsExplorationPaused、IsBattleActive 为 true,Close 后计时恢复且标志复位;
  4. 重复点击 Open 被占用检查和 allowMultiInstance=false 拒绝,不会打开第二场。
  截图:未提供)
提交或差异:原验收记录未填写 commit;M0 代码当前已在仓库基线
剩余问题:M0 的提前契约按 2026-09-01 架构修订在 M1 收敛

[x] M1 架构收敛与 1v1 攻击闭环

目标:用最终所有权完成第一场可打完的战斗,不把 M0 的中间契约继续扩散。

实现范围:

  • TurnBattleComponent 成为唯一外部入口,吸收 RunBattleCoordinator 的启动、暂停、UI 打开和结束回写职责。
  • 新增内部 BattleRuntime 和 BattleUnit,Component 持有唯一当前实例。
  • 删除 BattleController 规划;删除或停止使用 BattleStartRequest、BattlePlayerInput、BattleReturnPlan、IExplorationBattleBridge 和 IEnemyBattlePolicy。
  • 将现有 Snapshot/Advance/Record 类型收敛为 BattleViewState、BattleStep 和一层 BattleEvent;不保留重复 FlowState。
  • 支持 1 名玩家和 1 名敌人的轮次、当前行动者和每轮一次行动机会。
  • 支持普通攻击、HP 伤害、HP/MP 钳制、阵亡、Victory 和 TotalDefeat。
  • 敌人直接使用首版随机决策并自动反击。
  • UI 显示双方 HP、当前行动者和攻击按钮。
  • 增加纯逻辑 EditMode 用例,直接验证 BattleRuntime,不依赖 MonoBehaviour 和 UI。
  • 建立 Battle EditMode 测试程序集和 InternalsVisibleTo,内部规则不因测试改成 public。

明确不做:多人队伍、技能、状态、先制和逃跑。

Play Mode 验收:

  • 玩家点击攻击后敌人 HP 立即变化。
  • 未结束时敌人自动反击,玩家 HP 立即变化。
  • 任一方 HP 归零后停止接受指令并显示结果。
  • UI 结果与 BattleEvent、BattleViewState 一致。
  • 战斗结束后不存在残留 BattleRuntime,能够再次打开调试战斗。

验证证据:

代码状态:已完成
Unity 编译:通过(2026-09-02,dotnet build SepCore.Runtime.csproj 0 错误 0 警告;Unity 编辑器内 Play Mode 实测编译通过)
EditMode:通过(2026-09-02,新建 Tests 程序集,BattleRuntime 纯逻辑用例 25 个全部通过,
  覆盖创建失败零副作用、攻击伤害公式、HP 钳制、阵亡、Victory/TotalDefeat、
  非法指令不消耗行动、敌人随机决策与固定序列复现、视图字段、连续战斗不继承状态)
Play Mode:通过(2026-09-02,Debugger -> Battle/Shell 实测:
  1. 1v1(角色1 vs 敌人1)开局显示双方 HP、图标与顶部 TurnSlots 一致;
  2. 攻击后敌人 HP 立即变化,0.6s 间歇后敌人自动反击,TurnSlots 高亮逐单位移动且已行动单位不隐藏;
  3. 敌人 HP 归零显示"胜利!",攻击按钮禁用,高亮熄灭;
  4. 关闭后再次开启可重开新战斗,无残留 BattleRuntime;
  5. 连续两场战斗第二场继承第一场回写的 HP/MP(单局内连续战斗设计预期)。
  截图:未提供)
提交或差异:未提交(working tree 差异:契约收敛、BattleRuntime 内核、组件入口、UI 接线与图标、EditMode 测试)
剩余问题:无阻塞。敌人行动间歇时长暂为组件常量 AutoAdvanceDelaySeconds=0.6f;
  调度仍按单位创建顺序轮流,M2 由完整速度/先制规则替换

[ ] M2 1 至 4 人多对多、先制与确定性调度

目标:一次完成完整的行动顺序模型。

实现范围:

  • 从 RunPlayerState 和 EnemyPartyConfig 创建最多 4v4 的 BattleUnit。
  • 重复敌人配置生成不同 BattleUnitId。
  • 实现速度、阵营和 PartyOrder 并列规则。
  • 每次行动后按当前 BattleRuntime 重新选择本轮下一行动者。
  • 阵亡单位在轮到前死亡时不再行动。
  • 实现先制第一轮玩家全部先行,第二轮恢复普通排序。
  • 敌人行动和目标选择只消费注入的本局随机源。
  • UI 显示当前行动者和本轮剩余顺序。
  • 增加固定随机序列的 EditMode 回归用例。

明确不做:技能、状态、速度修改效果和逃跑。

Play Mode 验收:

  • 调试入口可启动 1v1、2v2 和 4v4。
  • 普通模式按速度、阵营和队伍顺序行动。
  • 先制第一轮所有玩家先于敌人,第二轮恢复普通规则。
  • 每个仍在战斗中的单位每轮最多行动一次。
  • 两个相同 EnemyConfig 的敌人可以分别选择、受伤和死亡。
  • 相同初始状态和随机序列产生相同行动记录。

[ ] M3 技能、目标、MP 与数值效果

目标:让当前配表中的数值型攻击和技能完整可用。

实现范围:

  • 支持 Attack 和 Skill 行动配置。
  • 支持 Self、SingleAlly、AllAllies、SingleEnemy 和 AllEnemies。
  • 实现 MP 校验、成功后的 MP 扣除、伤害和治疗。
  • EffectResolver 只在效果逻辑已需要独立测试时从 BattleRuntime 提取;仍直接操作同一 Runtime。
  • UI 根据行动配置进入或跳过目标选择,并禁用 MP 不足技能。
  • 内核拒绝错误行动者、行动、阵营、目标数量、阵亡目标和逃跑目标。
  • BattleEvent 一层记录能够表达当前数值变化;不为未来动画建立额外记录树。

明确不做:眩晕、速度修改、逃跑和道具效果。

Play Mode 验收:

  • 角色 1 至 3 的单体伤害、单体治疗和全体伤害完整结算。
  • SingleAlly 可以选择自己。
  • 全体技能一次结算全部合法目标。
  • MP 不足或伪造非法指令不消耗行动机会。
  • 治疗不超过 MaxHP,伤害不低于 0。

[ ] M4 状态、眩晕与实时速度变化

目标:补齐会改变行动机会和当前轮次顺序的战斗内状态。

实现范围:

  • 实现 BattleStatusType.Stun 和已确认的行动机会持续语义。
  • 同类状态刷新持续次数,不叠层。
  • 实现 Speed 数值修改并立即重算本轮剩余顺序。
  • 已行动单位不会因加速再次行动。
  • BattleStatus 只存在于 BattleRuntime,不写回 RunPlayerState。
  • BattleViewState 和 BattleEvent 增加当前阶段真实需要的状态字段。

明确不做:复杂状态、每轮伤害、状态动画和永久属性写回。

Play Mode 验收:

  • 角色 4 的眩晕使尚未行动目标跳过本次行动。
  • 已行动目标被眩晕后跳过下一轮行动。
  • 重复眩晕只保留较长持续次数。
  • 未行动单位速度变化后,剩余顺序立即更新。
  • 已行动单位加速后不会再次行动。

[ ] M5 逃跑、结束矩阵与单局回写

目标:完成战斗内部全部结束分支,并把结果正确写回同一局状态。

实现范围:

  • 增加 Escape 指令和按钮。
  • 使用 GlobalConfig.EscapeSuccessPermille 与本局随机源判定。
  • 成功和失败都消耗行动机会;成功单位离开候选集和合法目标集合。
  • 实现 Victory、AllEscaped、PartialEscapeDefeat 和 TotalDefeat 的互斥判定。
  • TurnBattleComponent 直接消费 BattleResult 并回写 RunPlayerState,不建立 BattleReturnPlan。
  • 胜利时存活者保留 HP/MP,阵亡者恢复 1/1。
  • 全逃跑保留逃跑时 HP/MP。
  • 部分逃跑失败时逃跑者保留值,阵亡者恢复 1/1。
  • 清除战斗状态和速度修改。
  • TotalDefeat 进入单局失败,不恢复探索更新。
  • 非 TotalDefeat 关闭 BattleForm 并恢复探索更新。

明确不做:真实地图敌人、掉落和保护碰撞门禁。

Play Mode 验收:

  • 逃跑失败后角色仍在战斗,敌人继续行动。
  • 单人逃跑成功不会在队友仍战斗时结束。
  • 四种 BattleOutcome 都能通过调试输入稳定得到。
  • 连续两场战斗中,第二场使用第一场写回的 HP/MP。
  • 阵亡恢复为 1/1,上一场状态和速度修改不会进入下一场。
  • 全员阵亡后不短暂返回可操作地图。
  • 全过程不修改 GameEntry.Save.Data 或写入 save.json。

[ ] M6 真实探索遭遇、地图恢复、掉落与保护

目标:接入真实地图,完成碰撞到战斗返回的垂直流程。

前置依赖:探索层提供唯一 EncounterId、敌人队伍预设 ID、警惕状态、地图暂停门禁和敌人实例生命周期操作。

实现范围:

  • 队长碰撞创建 BattleEncounter 并调用 GameEntry.TurnBattle.TryStartBattle。
  • 探索遭遇在启动时提供一次性完成回调,并在回调中消费 BattleResult;不得让 BattleForm 执行地图后果。
  • 搜索被战斗中断时保留进度和已掉落物。
  • 战斗期间冻结所有地图更新,其他敌人保持战前状态。
  • Victory 按 EncounterId 移除触发敌人,并使用本局随机源结算掉落。
  • 逃跑类结果保留并恢复触发敌人,下次按完整预设重建。
  • 逃跑类结果开启 GlobalConfig.EscapeProtectionMs 保护。
  • 保护期间禁止警惕增长、追击和战斗触发;结束后仍重叠可再次触发。
  • 探索层只根据 Component 返回的 EncounterId 和 Outcome 执行固定后果,不读取 BattleRuntime。

明确不做:地图生成、巡逻算法、警惕算法、背包实现和掉落表设计。

Play Mode 验收:

  • 警惕未满进入先制战斗,警惕已满进入普通战斗。
  • 附近其他敌人不参战,战斗后状态保持。
  • 胜利后只有触发敌人消失并可能掉落。
  • 逃跑后触发敌人仍在,再战面对完整初始队伍。
  • 保护期间不能重入,保护结束仍重叠时可以重入。

[ ] M7 战斗 UI、输入限制与最小表现完成

目标:达到设计要求的最小可读、可操作战斗界面。

实现范围:

  • 我方所有角色显示名称、HP、MP、阵亡和逃跑状态。
  • 所有敌人显示名称、HP 条和不可选状态。
  • 显示当前行动者和本轮剩余顺序。
  • 行动菜单只在等待当前玩家指令时出现。
  • 攻击、技能、逃跑可用;道具占位可见但禁用。
  • 单体目标有明确选中态;取消目标不消耗行动。
  • 战斗期间不能打开共享背包、保险箱或调整保险箱内容。
  • UI 文案走现有本地化和格式化文本流程。
  • 保持半透明覆盖,让玩家确认仍在原地图。
  • UI 只持有 BattleViewState 和当前显示所需 BattleEvent,不持有 BattleRuntime 或 BattleUnit。

明确不做:站位、骨骼或序列帧动画、VFX、Timeline、镜头演出和音效同步。

Play Mode 验收:

  • 1 至 4 名玩家和敌人的最长名称、HP/MP 不溢出或遮挡。
  • 速度变化后顺序显示与下一实际行动者一致。
  • 非当前玩家不能操作,敌人自动行动期间不能提交玩家指令。
  • 背包和保险箱入口在战斗中不可用。
  • 取消目标、MP 不足、阵亡目标和逃跑目标不误消耗行动。

[ ] M8 全量回归与原型完成

目标:关闭全部战斗设计验收,移除旧架构残留和临时代码。

实现范围:

  • 完成第八节 22 条验收映射。
  • 完成 EditMode 规则测试和实际探索 Play Mode 流程。
  • 删除 M0 遗留且不再使用的 DTO、接口、Coordinator 和重复状态类型。
  • 检查公开 Battle 类型;没有外部消费者的实现细节改为 internal。
  • 调试入口只在 Editor/Development Build 可用。
  • 清理临时日志、硬编码测试数据和无用按钮。
  • 检查生成文件、资源收集、UIForm 配表和 Prefab 引用。
  • 更新本文档进度、证据和剩余问题。

Play Mode 验收:

  • 地图碰撞、战斗、任一结果返回或失败的流程可重复执行。
  • 普通、先制、胜利、全逃跑、部分逃跑失败和全员阵亡均留证。
  • 连续多场战斗没有重复 UI、随机源重置、地图暂停泄漏或残留 BattleRuntime。
  • Unity Console 没有由战斗流程产生的新异常或错误。

八、设计验收映射

以下 22 项对应 Docs/GameDesign/04_TurnBasedCombat.md。只有对应里程碑完成运行验收后才能勾选。

  • 多个玩家和多个敌人可以同时参战。对应 M2。
  • 玩家与敌人上限均为 4,并按战备和敌人队伍预设创建。对应 M2。
  • 普通轮次每个单位只行动一次,每次行动后按当前速度决定下一位。对应 M2、M4。
  • 同速玩家优先,速度变化立即影响尚未行动单位。对应 M2、M4。
  • 同阵营同速按队伍顺序行动。对应 M2。
  • 死亡、眩晕或无法行动单位跳过本轮。对应 M2、M4。
  • 轮到玩家角色时才显示并处理该角色的行动选择。对应 M1、M7。
  • 先制第一轮所有玩家排在所有敌人之前。对应 M2。
  • 先制第二轮恢复普通速度规则。对应 M2。
  • 玩家可选择攻击、技能和逃跑,道具保留但不可用。对应 M3、M5、M7。
  • 战斗中不能打开共享背包或保险箱。对应 M7。
  • 敌人随机选择可用行动和合法目标。对应 M1、M2、M3。
  • 敌人全灭时胜利,玩家全灭时战斗和单局失败。对应 M1、M5。
  • 单个角色可逃跑;只要有人逃跑,其他人阵亡不导致单局失败。对应 M5。
  • 逃跑使用配置成功率,失败后继续留在战斗。对应 M5。
  • 逃跑成功或失败均消耗本轮行动。对应 M5。
  • 逃跑返回战前位置并获得 2 秒保护。对应 M5、M6。
  • 逃跑者保留 HP/MP,部分失败中的阵亡者恢复 1/1。对应 M5。
  • 任何非全员阵亡结果中的阵亡角色恢复 1/1。对应 M5。
  • 逃跑后地图敌人恢复,重战按完整预设创建。对应 M6。
  • 保护期间不警惕、不追击、不触发战斗,结束仍重叠时可重入。对应 M6。
  • 战斗 UI 显示我方 HP/MP、行动顺序和敌人 HP。对应 M7。

九、最低测试矩阵

EditMode

类别 必测情况
单位创建 1v1、4v4、重复 EnemyConfig、不合法队伍人数
普通排序 速度不同、跨阵营同速、同阵营同速、每轮只行动一次
动态排序 未行动单位加速/减速、已行动单位加速、死亡后移出候选集
先制 第一轮玩家全先行、第二轮恢复普通规则
目标 Self、SingleAlly 含自己、全体目标、阵营错误、阵亡和逃跑目标
MP 与效果 MP 不足、伤害、治疗、全体效果、HP/MP 钳制、顺序效果
眩晕 行动前施加、行动后施加、重复施加、跳过后失效
敌人决策 过滤不可用行动、合法目标、无行动时跳过、固定随机序列
逃跑 0/1000 边界、成功/失败消耗行动、四种结束结果
回写 胜利阵亡恢复、全逃跑保留、部分失败恢复、全灭不恢复探索
生命周期 启动失败无副作用、结束清理 Runtime、连续战斗不继承状态

Play Mode

流程 必测情况
UI 生命周期 打开、关闭、重复进入、非当前行动者不可输入
地图暂停 玩家、敌人、搜索、计时停止;战斗 UI 仍响应
遭遇 普通碰撞、先制碰撞、附近敌人不参战
返回 胜利移除敌人、逃跑保留敌人、完整队伍重建
保护 保护期不重入,结束仍重叠可重入
连战 HP/MP 延续、状态清除、共享随机序列不重置
失败 全员阵亡进入单局失败,不短暂恢复地图操作

十、原型完成条件

只有同时满足以下条件,本文档状态才能改为“完成”:

  • M0 至 M8 全部标记为 [x],且每个里程碑都有要求的运行证据。
  • 第八节 22 条设计验收全部通过。
  • EditMode 和 Play Mode 最低测试矩阵全部通过。
  • TurnBattleComponent 是外界唯一战斗入口,当前战斗只有一个 BattleRuntime 所有者。
  • 战斗内部协作者共享 Runtime,没有内部请求/响应 DTO 链和全局事件总线。
  • UI、探索层和存档不持有或修改 BattleRuntime、BattleUnit。
  • 战斗不直接写 SaveData、不切换 Battle 场景、不重建本局随机源。
  • 胜利、逃跑和失败对地图敌人、玩家临时状态、计时和保护期的处理符合设计。
  • 正式构建中没有开发调试入口和测试数据。