苍穹对决 · 联机架构

房主小世界 · 星型拓扑 · 双通道 · 客户端判定 + 房主校验

架构 v3.11
房主 · Host Mini-World 权威结算 / 快照 / 转发(唯一裁判) 玩家 A(加入方) 位置上报 · 本地判定命中 玩家 B(加入方) 位置上报 · 本地判定命中 玩家 C(加入方) 位置上报 · 本地判定命中 's' 无序 · 不可靠 upd / sync · 20Hz 丢了等下一个 'e' 可靠 · 有序(默认参数) fire / hitRep / hit … 玩家之间没有直连:所有消息上行到房主,再由房主下发(3v3 / 5v5 同构) 视觉弹道走 'e' 广播重建;位姿快照走 's' 覆盖 —— 两条通道互相独立,没有跨通道顺序保证
's' 状态通道:upd / sync / voice,丢了等下一个
'e' 事件通道:控制与战斗事件,可靠有序(重传 = 延迟而非丢失)
房主小世界:唯一权威

⇄两条通道:谁可靠、谁实时

通道创建方式承载语义后果
s 状态 createDataChannel('s', {ordered:false, maxRetransmits:0}) upd(上报位姿 20Hz)、sync(房主快照 20Hz)、voice(语音帧) 丢了就等下一个。位姿用 lerp 平滑,画面约滞后 1 RTT;语音有 80ms 重排窗,缺包跳过不阻塞。
e 事件 createDataChannel('e')(默认参数) hello / team / roster / fire / blt / fireRocket / rkt / hitRep / hit / hitReject / kill / kstat / died / crash / envDmg / round / rEnd / matchEnd / pickList / go / ping / pong / kick / modeMismatch 可靠有序:不会丢,但一个包丢了后面全排队等重传(队头阻塞 → 几十到几百 ms 的延迟尖峰)。

子弹与命中全部走 e,所以「hitRep 丢包」在网络层不会发生;会失效的只有逻辑丢弃(回合已结束、目标已死、去重表命中)和 40m 位置校验拒绝。 反过来,位姿走 s,所以「开火快照」与「对方看到的机体位置」天然对不齐。

◎一次命中:视觉链与伤害链

时刻
开火者 A
房主 / 其他端
① 出膛
fireGun 生成本地子弹(src='me'),放枪口火光;随后广播 fire{o,d,fid,s,dm,sl}
房主用 o/d/s 重建一颗 foeVis 子弹,再广播 blt;各端按同样参数再建一颗纯视觉弹
② 判定
只有 src==='me' 的子弹做 segSphere(prev→pos) 扫掠;命中即 active=false,子弹当帧消失
foeVis 子弹永不与单位碰撞,只跟地形/建筑交互;它只是曳光
③ 本地反馈
火花 + 命中音 + 准星命中标记 + 部位飘字(命中驾驶舱 ×3)—— 不等网络,但不改血量
没有任何反应(连火花都不放)
④ 上报
hitRep{fid, id, part, p, splash?, dmg?} 发给房主
房主:fid/id 非空 → hitSettled 去重(键 rid:fid:id,5s)→ 目标存活 → |p − tgtPos| ≤ 40m
⑤ 结算
等广播(本地不扣血、不判死)
房主:伤害不采信上报值 —— 直击 = 基础伤害 × 自己按弱点表复核的倍率;溅射 = 上报值夹紧 2~300。扣血 → hp≤0 → peerDie → 记台账 → 广播 kill
⑥ 广播
收到 hit{s,v,part,pos,dmg,hp,fatal,fid}
房主 netBroadcast 给所有人
⑦ 呈现
射手分支:不再放特效(本地已放过),只把目标血量改成权威值
被打者:受击火花 + 屏幕红闪 + me.hp = d.hp;旁观者:火花/爆炸 + 目标血条。所有端同时 killBulletFx(s, fid) 移除那发弹道

⚖权威边界:谁说了算

事项判定方依据
这一发打没打中开火者本地 segSphere 扫掠(用自己看到的画面)
伤害是多少房主基础伤害 × 房主按目标弱点表复核的倍率
命中位置是否合理房主HIT_TOLERANCE = 40m,超出回 hitReject
血量 / 死亡房主applyPeerDamage → peerDie
谁杀了谁房主lastDmgFrom + 击杀台账,3s 一次 kstat 校正
回合 / 比分 / 终局房主rEnd / matchEnd;非房主用 s0/s1 按阵营换算
撞地 / 出海房主upd 的 y 值 / 弹道位置
弹道长什么样各端自己fire/blt 参数重建,纯视觉,不参与判定

✎三种玩法的开火链路

玩法 / 武器本地实体网络
空战 · 机炮 / 高爆fireGun → bulletsfire{o,d,fid,s,dm,sl}
空战 · 机载火箭firePlaneRocket → rocketsfireRocket → rkt
防空车 · 飞机机炮同上同上
防空车 · 车载机枪fireGunAAGun(520 m/s)fire{o,d,fid,s,dm,sl}
防空车 · 火箭炮fireRocketfireRocket → rkt
直升机 · 机枪fireGun(speed+520)fire v3.11 补齐 s/dm/sl
直升机 · 航空炸弹spawnRocketFx + 炸弹模型fireRocket/rkt v3.11 新增 bomb:1 + v/g/br/lr/life

炸弹的初速是「投弹瞬间的载机速度 − 弹射速度」,所以广播必须带 v;只发方向会让接收端按 260 m/s 直线飞,弹道完全不同。 接收端用模式包钩子 spawnRemoteBomb 重建炸弹模型与物理,结算仍由投弹者的 hitRep 驱动。

⚠掉线、延迟门槛与模式一致性

  • 1
    延迟门槛:连接建立后立刻采样 3 次 RTT(取最小值,首包受 ICE 影响偏大),≥ 80ms 直接 kick 并延迟重载页面。通过检测才算「就绪」,人齐判定用的是就绪人数 —— 所以不合格的玩家会在进入选图/选阵营之前被剔除,不会把房主从选人界面打回大厅;主动踢出也不缩编(3v3 不会悄悄变成 2v2)。
  • 2
    掉线:onClose → 删除连接与 roster 条目 → 该玩家按阵亡结算;回合内剩余玩家继续,房主推进 rEnd。
  • 3
    模式一致性(v3.11):邀请码里带房主的 gm,加入方在生成应答码前就比对;不一致直接拒绝,连 PeerConnection 都不建。连接后 hello 再带一次 gm 兜底(兼容旧版客户端),不一致由房主踢出并提示双方模式名。
  • 4
    回合推进:round 走可靠通道正常先到;丢了由 sync 的 rn 超前兜底。rn 落后的旧快照整段跳过,防局号回退与二次重置。
  • 5
    回菜单是就地软重置(v3.11.2):返回 / 被踢 / 掉线 / 超时都不再 location.reload(),而是按「停输入 → 关连接 → 清实体 → 清状态 → 清 UI → 重建菜单 3D」复位。连接关闭走 Net.close()(先 DataChannel 后 PeerConnection),对端通过 onClose 各自软重置。

◎已知边界(设计取舍,不是 bug)

  • 1
    视觉弹道不判伤:foeVis 子弹不与飞机/防空车碰撞。命中特效出现前,它已经从目标身上穿过去了 —— 移除时机是 hit 到达的那一帧(killBulletFx 按「射手 + fid」精确匹配)。
  • 2
    跨通道没有顺序保证:hit(e 通道)可能先于一条更早生成的 sync(s 通道)到达,旧快照会把血量短暂覆盖回去(约 50~150ms),随后被下一个快照纠正。
  • 3
    特效位置 ≠ 本地弹道位置:命中特效放在开火者上报的命中点 p,接收端的子弹按自己的积分位置渲染。延迟 100ms、弹速 600 m/s 就是 60m 的视觉差。
  • 4
    40m 是防作弊阈值:按「最大 170 m/s × 80ms + 插值滞后」推出来的容差,用来挡错目标/瞬移/改包,不是精确几何校验。
  • 5
    炸弹 AOE 逐目标上报:一次爆炸命中 N 个目标就发 N 条 hitRep,房主按 rid:fid:id 分别去重结算,互不压制。
苍穹对决 Sky Duel · 架构文档随代码更新(v3.11)
本页纯静态说明,无外部资源、不加载游戏模块