在线联机原型全集:第 17 章 战棋对战

战棋对战(Tactical Lockstep|Command Buffer & Replay) 类别:回合制战术对战(TBS) + 锁步一致性 目标:验证战术锁步下的输入收集→命令冻结→顺序结算的确定性流程;以Command Buffer 作为唯一权威输入面向事件溯源与回放;覆盖 Fog-of-War、断线恢复 …

战棋对战(Tactical Lockstep|Command Buffer & Replay)

  • 类别:回合制战术对战(TBS) + 锁步一致性
  • 目标:验证战术锁步下的输入收集→命令冻结→顺序结算的确定性流程;以Command Buffer 作为唯一权威输入面向事件溯源与回放;覆盖 Fog-of-War、断线恢复、悔棋/分支回放与观战。
  • 原型代号proto-017-tactic-lockstep
  • 依赖模块proto-002-rps(时序/提交窗口基元)、proto-014-room-escape-sync(事件溯源/快照)、proto-016-mini-moba(关键帧+增量恢复思路)
  • 推荐栈:Server(Go/Java,Go 优先)、Client(TypeScript/Unity),可选 WASM 共享规则库
  • 协议栈:HTTP(配对/断线恢复) + WebSocket(指令/状态广播);Tick=回合步(非实时帧)

1. 核心玩法与范围

  • 棋盘:六边或方格网(推荐六边以简化移动/射线判定),尺寸 12×12~18×18。
  • 单位:每队 4–6 枚单位,职业互补(近战/远程/支援/控场)。
  • 回合结构:I-GO-U-GO 变体:计划阶段(Plan)锁步(Lock)结算(Resolve)
  • 情境:Fog-of-War、地形高低差、覆盖(Cover)、连携(Combo)与 Overwatch(守望射击,可选)。

2. 战术锁步(Tactical Lockstep)模型

2.1 回合相位(Phases)

  1. PLAN(计划):各客户端离线规划路径、技能与目标(可多步);
  2. LOCK(冻结):到时/全员确认后,服务器冻结本回合的 Command Buffer
  3. RESOLVE(结算):服务器使用确定性解释器按规则推进,生成一串事件日志(Event Log)
  4. SYNC(广播):向客户端广播结算事件流与回合哈希
  5. CLEANUP(清理):刷新状态(CD、Buff、地形效果),进入下回合。

2.2 锁步与确定性原则

  • 唯一输入接收“命令”(高层语义,如“单位#7:从 A→B,若遭遇敌人则停靠最近掩体并投掷手雷”),禁止坐标/属性直接写入;
  • 无共享随机源:随机数来自服务器同一 RNG 种子,结算顺序固定(见 6.2);
  • 无时间竞态:锁步期间不接收新命令;计划阶段的并发冲突在锁步冻结时一次裁定。

3. Command Buffer 设计

3.1 命令规范(抽象层次)

  • 原子命令(Atomic):Move(to), Face(dir), Attack(target), Ability(slot, target), Guard/Overwatch, Interact(tile)
  • 复合命令(Composite):Path([p1,p2,...], stop_cond), If(cond)Then(cmdA)Else(cmdB), Sequence(cmds...)
  • 应急条件(Emergency):onContact: Stop | Evade | EngageonSight: OverwatchShot
  • 预算限制:行动点(AP)/能量(EP)预算必须在客户端本地校验 + 服务器冻结再校验

3.2 数据格式(示例 JSON)

{
  "turn": 21,
  "team": "blue",
  "author": 1001,
  "commands": [
    {"unit":7,"type":"Sequence","steps":[
      {"type":"Path","tiles":[[4,9],[5,9],[6,9]],"stop":"onContact"},
      {"type":"Ability","slot":"GREN","target":[6,11]}
    ]},
    {"unit":9,"type":"Overwatch","arc":90,"range":6}
  ],
  "proof":{"ap_budget":6,"checksum":"8b1f..."}
}

3.3 冻结与版本

  • 客户端提交 cmd_buf(ver=n);服务器冻结时写入 frozen_ver=n+1
  • 客户端可在 PLAN 阶段多次覆盖(以最后版本为准);LOCK 后拒绝

4. 冲突裁定(在冻结瞬间)

4.1 冲突来源

  • 同一格目标占用(两方同时尝试占同一格);
  • 行动点预算边界(客户端误判导致溢出);
  • 隐身/视野(对方在 LOCK 时刻改变可见性导致目标非法);
  • 技能互斥(对同一载具/开关/资源竞争)。

4.2 裁定规则(俩层)

  1. 合法性过滤(Hard Reject):预算不足、目标非法、前置条件不满足 → 删除该命令并产生错误事件(便于回放)。

  2. 竞争决胜(Soft Resolve):

    • 优先级序Overwatch / Reaction > Interrupt > Move > Ability > Interact
    • 同类竞争:按单位敏捷值(AGI)降序,若并列→按单位ID升序
    • 同格抢占:赢家落位,其他命令重写为停留/最近合法格
    • 资源竞争:采用抢占锁,失败者命令标记为失败并保留在事件日志。

注:上述裁定只发生在冻结点,结算期间不插入新命令

5. 结算解释器(Deterministic Resolver)

5.1 执行顺序

  • 阶段化执行Move → Reaction/Overwatch → Ability/Attack → DOT/环境 → EndStep
  • 同阶段批处理:按固定排序键(turn, phase, unit_id)执行,确保每次回放一致。

5.2 关键子系统

  • 移动:A* 预烘焙 + 每步占用检查;若撞入 Overwatch 触发反应射击(Reaction)并可能中断剩余 Path。
  • 射击命中hit = f(weapon_acc, range_penalty, cover, height);随机从统一 RNG 抽取;
  • 伤害dmg = base * (1 - armor/(armor+K)) + crit_mult
  • 状态AP/CD/效果在清理阶段统一刷新;
  • 能见度:FoW(视野)每步增量更新,影响目标合法/命中。

5.3 事件日志(Event Log,权威)

事件是不可变可重放的唯一事实来源。典型事件:

TurnStart, CmdAccepted, CmdRejected, MoveStep, EnterTile, LeaveTile,
ReactionShot, AbilityCast, Hit, Miss, Crit, Damage, Kill, Knockback,
ApplyBuff, RemoveBuff, TileChange, TurnEnd, Hash

6. 随机确定性与哈希

6.1 RNG

  • rng = PCG(seed = match_seed ⊕ turn ⊕ salt)
  • 同一结算时段内严格按事件执行顺序取随机数;禁止条件分支漏取

6.2 一致性哈希

  • 每回合末生成 hash(turn) = H(∑ stable_fields_of_entities, rng_cursor, event_crc)
  • 客户端对齐:若 hash_mismatch → 触发强制恢复(见 9)。

7. Fog-of-War 与信息裁剪

  • 可见性visible(tile) = ∃ unit in team s.t. dist(unit,tile) ≤ sight(unit) and not blocked
  • 裁剪:服务器仅向每队下发其可见子集的状态与事件(例如隐藏敌方真实血量、技能 CD)。
  • 回放:对赛后全景回放,使用全景事件流;对玩家个人回放,使用其视角裁剪事件流(保证体验与对局时一致)。

8. 网络协议与时序

8.1 消息(WS)

// 客户端 -> 服务器:提交命令
{ "t":"cmd", "turn":21, "ver":3, "team":"blue", "author":1001, "commands":[...], "sig":"ed25519..." }

// 服务器 -> 客户端:冻结通知
{ "t":"locked", "turn":21, "frozen_ver":4, "deadline_ms":0 }

// 服务器 -> 客户端:回合结算事件流(分片)
{
  "t":"resolve","turn":21,"seq":1,"more":true,
  "events":[{"k":"MoveStep","uid":7,"to":[6,9]}, {"k":"ReactionShot","src":12,"dst":7,"hit":true,"dmg":4}]
}

// 服务器 -> 客户端:回合尾哈希
{ "t":"hash", "turn":21, "hash":"3b29...fa" }

8.2 计划阶段时间

  • 默认 20–30 秒,支持提前确认;超时未确认则:

    • 使用上一版本命令
    • 启动保守 AI(Hold/Guard)

9. 断线恢复与观战

9.1 恢复流程

  1. 客户端上报 last_turn_ack
  2. 服务器返回最近关键快照 KF(k) + events(k+1..T_now)
  3. 客户端重建至 T_now 并比对 hash@T_now
  4. 不符则回退到更早 KF(k-1) 或直接覆盖全量快照。

9.2 观战与复盘

  • 观战:只能接收只读事件流,不可发命令;

  • 复盘

    • 时间轴跳转:按回合号和事件序号定位;
    • 分支/悔棋实验(训练模式):从任意回合快照分叉,写入平行日志(branch_id)

10. 数据结构与存储

10.1 关系表/集合

  • matches(id, seed, map_id, rule, created_at, result)
  • turns(match_id, turn, frozen_ver, hash, snapshot_uri)
  • commands(match_id, turn, team, author, ver, payload, sig)
  • events(match_id, turn, seq, payload)
  • spectators(match_id, user_id, joined_at)

10.2 快照

  • ECS 压缩(仅稳定字段:位置/AP/HP/CD/Buffs/地形状态)
  • 存储为 zstd 二进制,服务器缓存最近 N 个快照。

11. 规则/数值(示例)

项目默认
单位 AP2/回合
移动成本1 AP / 3 格(可拆分)
远程射击命中基础 65%,距离惩罚每 3 格 −5%,遮蔽 −20%
暴击15%,倍率 ×1.5
Grenade扇形 3 格,固定 3–5 伤害,CD 2 回合
Overwatch锁定 90° 扇形、射程 6;对穿越扇形的敌触发一次反应射击

12. 客户端交互与 UX

  • 计划阶段 UI:路径预览(AP 消耗标注),技能范围预览,冲突/非法目标即时标红
  • 锁步提示:顶部显示所有人“已准备/未准备”,冻结倒计时;
  • 结算播放:事件逐条动画化(可 1×/2×/快进),同时显示回合日志面板
  • 信息不对称:雾外单位以“问号”/影子表示;
  • 悔棋/训练:在训练房开启“从回合 N 分支”。

13. 反作弊与安全

风险说明对策
命令注入/篡改客户端伪造命令命令签名(Ed25519)、服务器枚举验证
透视/数据外泄客户端渲染雾外信息服务器裁剪数据;加密通道
RNG 操控客户端预测随机单源 RNG(服务器)、事件顺序消费
重放攻击旧命令重放回合号 + 版本号 + nonce 校验
时间作恶超时后提交冻结后拒绝;只接受 PLAN 期内提交

14. 性能与扩展

  • 结算复杂度:单位数 U、地形格 G、事件 E;解算阶段 O(U log U + E)
  • 缓存:快照内存缓存近 3–5 回合;事件分片批量发送(<= 8KB/片);
  • 并发:房间粒度调度;每回合解算 p95 < 40ms(U≤16)。
  • 多种模式:1v1、2v2、PVE(敌 AI)、草案/排名赛(加 Elo/Glicko-2)。

15. 测试与验证

  1. 确定性回放:相同 seed + commands 重放 100 次哈希完全一致;
  2. 裁定边界:同格竞争、Overwatch 触发次序、多技能互斥;
  3. FoW 正确性:随机地图上可见集与期望一致;
  4. 断线恢复:拔网线 10s → 2s 内回到 T_now 并 hash 对齐;
  5. 安全:拒绝超时命令、重放命令、非法 budget;
  6. 负载压测:并房 1k,事件吞吐与快照 IO 不丢帧。

16. 参考实现骨架(要点)

16.1 Go:回合循环

func (r *Room) RunTurn(t int) {
  // 1) 收集命令(PLAN期已入库)
  cmds := r.FetchFrozenCmds(t)
  // 2) 冲突裁定
  resolved := r.Adjudicate(cmds) // 过滤+优先级+抢占
  // 3) 结算
  ev := r.Resolver.Resolve(t, resolved) // 产出事件序列
  // 4) 快照、哈希、广播
  snap := r.World.SnapshotStable()
  h := StableHash(snap, ev, r.RNG.Cursor())
  r.StoreTurn(t, snap, ev, h)
  r.BroadcastEvents(t, ev)
  r.BroadcastHash(t, h)
}

16.2 TS:客户端计划与提交

const buf = new CommandBuffer(turn, team, uid);
buf.sequence(7)
   .path([[4,9],[5,9],[6,9]], "onContact")
   .ability("GREN", [6,11]);
buf.overwatch(9, 90, 6);

ws.send(JSON.stringify({ t:"cmd", turn, ver: buf.ver, team, author: uid, commands: buf.serialize(), sig: sign(buf) }));

17. MVP 清单

  • 战术锁步三相:Plan/Lock/Resolve
  • Command Buffer(原子 + 组合 + 预算校验)
  • 冲突裁定(合法性过滤 + 竞争优先级)
  • 确定性解释器(移动→反应→技能→DOT→清理)
  • 事件日志 + 关键快照 + 回放
  • FoW 裁剪与视角一致回放
  • 断线恢复(对齐哈希)与观战
  • 基础反作弊(签名/nonce/回合号)

18. 迭代路线

版本目标内容
v0.1最小对战固定地图、Move/Attack、Plan/Lock/Resolve 跑通
v0.2冲突与反应同格竞争、Overwatch、Reaction 中断
v0.3技能与FoWAoE/投掷、视野裁剪、遮蔽与命中模型
v0.4回放/分支平台化回放、分支实验、训练房
v1.0稳定/排位Elo/Glicko-2、对局统计、断线恢复优化

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页