Files
TankIC/3DTank/docs/Hornet_Railgun_Lock.md
T
TermiNexus 12e547f32f docs(3DTank): 电磁炮锁定蓄力改造的计划与进度(机制/门槛/改动/待办/教训)
含:z4t 三条上行(amr/o1ag/tnl)、三个时长(w1al/w187/x187)、FSM(TWt/vWt)结构、实测门槛(1.5s 最低满伤)、已落地改动与 commit 列表、离线解码流水线命令、关键行号速查。
同批提交 decode_space_frames.js 的 --side s2c/c2s 选项(MD 里的解码命令依赖它)。
2026-10-12 11:20:34 +08:00

9.3 KiB
Raw Blame History

电磁炮(RAILGUN / z4t)锁定蓄力「加速」改造 —— 计划与进度

状态:功能已可用(锁定 3~5s → 1.5s,满伤,面板可实时调);归因(离线解码)待做。 本文档是「日后再说」的交接手记:机制、实测门槛、已落地改动、待办与命令、踩过的坑。


0. 一句话目标

原版:按住开火 → 锁定/蓄力 3~5s → 才发(满伤)。 目标:缩短这段等待,且必须保持满伤 —— 服务端会按「锁定开始 → 开火」的时间差裁决。

当前结果:锁定/蓄力 1.5s(实测满伤的最小值),比我方原版快 2~3 倍,且可在 Alt+T 面板实时调。


1. 已确证的机制(含证据)

1.1 玩家坦克的武器组件 = z4t(不是 xen)

  • 现场探针实测:__gameMode__.y100_1.oyt_1.c2a_1.93,ctor = function z4t(),字段含 h1ah_1 / i1ah_1 / l1ah_1 / o1ah_1 / w1ah_1 …
  • xen(代码自标注 railgunWeapon)不在玩家车上 —— 早期改它是空转(教训)。

1.2 三条相关上行命令

命令 语义 触发链
amr 普通弹(点一下) J6t.z1aq → z4t.z1al → UYt → N4t.amr
o1ag 锁定弹(长按/蓄力) K6t.z1aq → z4t.a1am(目标) → Q6t → N4t.o1ag
tnl 锁定开始(无载荷、只带坦克 id) 进 vWt 锁定态时 iyz(new Mdn(n,i,b189_1)) → N4t:n.h18v_1 && P4t(t).tnl(...)
  • 走 o1ag 还是 amr,由 V6t 里 「按下时刻 + 阈值」 判定:W6t().a1ar_1 = Hj(0.2)(按住 ≥0.2s → A6t → K6t;否则 R6t → J6t)。
  • tnl 只带坦克 id ⇒ 服务端只能用自己收到它的时刻计时 ⇒ 这是「短于门槛就没伤害」的根因。

1.3 三个时长参数(都在 k4t(this) = gaussParams 组件上)

方法 原式 语义 实测
w1al() LR(0.001 * ojs_1) 带锁定弹的冷却/装填 ~6.6s(探针采样 o1ah_1 从 0 涨到 1)
w187() LR(0.001 * njs_1) 锁定/蓄力时长 ★ 改这里 原版 3~5s
x187() LR(0.001 * mjs_1) 另一条 未仔细测
(普通弹那支) X6t.l1ar_1 ← L6t(i1ah_1) 普通弹冷却 i1ah_1 = 1.639

1.4 状态机结构

  • z4t.y1ah 建 FSM:e=G6t(Idle) / u=Z6t / s=V6t(按下) / o=J6t(普通弹) / _=K6t(锁定弹) / a=X6t(累积) / h=Y6t / l=$4t(t)=TWt
  • TWt = new L9("StrikerTargetSelection")(内嵌目标选择 FSM);其内部 vWt 状态用 w187() 当累积上限: y188_1 从 w187() 递减到 0 → 发 bWt(选中目标)→ K6t.z1aq() → a1am(目标) → Q6t → 上行 o1ag。
  • z4t.t187(y188_1 / w187()) 写出的 o1ah_1 就是 HUD 上那条倒计时/进度条。

2. 实测门槛(二分结果)

锁定值 结果
原版 3~5s 满伤
2.0s 满伤
1.5s 满伤(当前采用值)
1.25s 用户实测:不行 ⇒ 1.5s 为最低可用值
1.0s / 0.1s 无伤害(另一号完全看不到弹药 ⇒ 服务端整发拒收)

结论:服务端对「tnl → 开火」的时间差有硬门槛,约 1.5s。


3. 已落地改动

3.1 游戏侧(3dtank.com/play/static/js/unpacked/module_87072.js)

(m$(z4t).w187 = function () {
  // HORNET-TAG: 电磁炮 锁定/蓄力时长 = 面板默认 1.5s(可实时调)  (z4t.w187)
  var O = window.__hornetLockSec__;
  if (typeof O === 'number' && O > 0) return LR(O);   // 运行时覆盖
  var t = k4t(this).njs_1;                            // 回退原版参数
  return LR(0.001 * t);
}),

3.2 面板(tools/hornet_panel.js → 经 python build_bundle.py 进 bundle)

  • Alt+T → 「电磁炮设置」 → 启用时长覆盖(checkbox,默认开) + 锁定/蓄力(s) 滑块 0.1~5.0 / 步进 0.05
  • 拖动立即生效(window.__hornetLockSec__),持久化到 localStorage:hornet_lock_on / hornet_lock_sec
  • 取消勾选 = 回原版(3~5s)
  • 控制台等价:__hornetLockSec__ = 1.25 / 恢复原版 __hornetLockSec__ = undefined

3.3 观测/调试设施(本次会话顺带产出)

设施 位置 说明
版本徽标 3dtank.com/play/static/js/main.9f4e7a89.js 右下角显示「包版本 + 从 __modules__[87072] 源码正则读出的 HORNET-TAG」(防「改了没生效」);每 2s 抗 DOM 裁剪自愈
探针 sockets/probe_railgun.js 组件清单 + 300ms 采样 + 整帧 WS 上行记录;自动加载(等 __gameMode__),localStorage.hornetProbe='0' 关
转储入口 3DTank/tools/serve.js POST /__probe__ → 追加写 3DTank/names/probe_railgun.log;GET /probe_railgun.js 提供探针
解码供给 3DTank/tools/probe_log_to_bins.js 日志 → packets/probe/<label>/session_001/c2s_*.bin

3.4 本次会话的 commit(时间顺序)

09f8769 feat(mirror): 电磁炮锁定时长 3~5s → 2.0s(z4t.w187,实测满伤)
bb3d761 feat(mirror): 右下角版本徽标(包版本 + 从模块源码读出的 HORNET-TAG)
712d24e fix(mirror): 徽标抗 DOM 裁剪 + 探针块加自证日志
09f8769…(同上)
dfdd69b feat(mirror): 探针自动加载并转储(/probe_railgun.js 路由 + 加载器引入)
e154c1a fix(sockets): 探针 WS 字节读取兼容 DataView(此前 hex 恒为空)
8e13aac feat(mirror): 电磁炮锁定时长 2.0s → 1.5s(二分探门槛)
2d2a2c9 chore(mirror): 电磁炮锁定时长试 0.1s(极端对照)
1af3b3a feat(mirror): 电磁炮锁定时长支持运行时覆盖(window.__hornetLockSec__)
db8a92a feat(panel): Alt+T 面板新增「电磁炮设置」
d4c8a84 feat(panel): 电磁炮锁定时长固定默认 1.5s(面板默认开启覆盖)
651e29f feat: 探针改整帧 hex 记录 + 新增 probe_log_to_bins.js

4. 待办(日后接着做)

4.1 采一轮整帧数据(需要人工在游戏里操作一次)

  1. F5(探针自动加载;徽标第二行应为「面板默认 1.5s(可实时调)」)
  2. 进战场 → 打 2~3 发蓄力弹(最好有一次「选中坦克并打中」)
  3. 跑流水线:
node 3DTank\tools\probe_log_to_bins.js
node 3DTank\tools\decrypt_frames.js --dir 3DTank\packets\probe --label probe-01
node 3DTank\tools\dump_plain.js --session probe-01 --out 3DTank\packets\plain\probe-01
node 3DTank\tools\decode_space_frames.js --dir 3DTank\packets\plain\probe-01 --side c2s --fields

4.2 归因目标(两轮对比)

  • 1.5s 轮(满伤)与 0.1s 轮(无伤害)的上行命令序列对比:
    • 命令集合/顺序一致(只是时间被压缩)⇒ 服务端按时长裁决(目前已基本确认,只差这最后一步铁证);
    • 命令不一致 ⇒ 客户端侧问题(那一发压根没发出去 / 参数没就绪),改客户端那一步。

4.3 可选优化方向

  • 查服务端门槛的确切值(1.25s 不行、1.5s 行 ⇒ 门槛在 (1.25, 1.5] 之间,可用面板滑块继续夹)。
  • 若想让服务端「以为」锁更久:需要把 tnl 提前发 —— 但别用假载荷(见 5.2 教训)。
  • 冷却那一支(w1al() ≈ 6.6s / x187())若也想缩短,需先确认服务端是否同样按它裁决(大概率是)。

5. 教训与注意事项(★ 重要)

  1. 不能动冷却:X6t / l1ar_1(普通弹 i1ah_1=1.639;锁定弹 w1al()≈6.6s)—— 服务端按它裁决; 短路它 = 出弹但无伤害(我第一次就踩了这个)。
  2. 别用假载荷预置 tnl:我一度在冷却开始时提前发 Mdn(0,0,!0) → 那一轮连弹药都看不到(整发被拒),已回滚。
  3. 开关只能用 localStorage:location.hash 会被游戏当路由(进不去战场);查询串会被当 config 参数(握手 1005)。
  4. 改完必须走对应构建:
    • 改 3dtank.com/.../unpacked/module_*.js → node tools/pack_unpacked.js(单包)
    • 改 tools/hornet_*.js → python build_bundle.py(hornet bundle)
    • serve.js 只有自身改动才需重启(Start-LocalAndRelay.ps1 -KillExisting)
  5. 面板滑块实时生效的前提:z4t.w187() 只在每次开始锁定时被读 ⇒ 改值影响下一发,当前这发不变。
  6. 探针整帧记录是硬要求:流密码状态跨消息连续,截断/漏帧会让后续解密全错。
  7. 验证版本永远看徽标第二行(HORNET-TAG),不要凭手感判断「改了没生效」。

6. 关键行号速查(3dtank.com/play/static/js/unpacked/module_87072.js)

内容 行
z4t.w1al/w187/x187(三个时长) ~92824-92839
z4t.z1al(普通弹开火) ~93026
z4t.a1am(锁定弹开火) ~93038
z4t.y1ah(FSM 构建 + TWt 实例化) ~92862-92910
V6t(0.2s 阈值判定走哪条) ~93601-93619
X6t(累积/冷却) ~93633-93652
K6t.z1aq(锁定弹入口 → a1am) ~93620
J6t.z1aq(普通弹入口 → z1al) ~93664
TWt = new L9("StrikerTargetSelection") ~23375
vWt(用 w187() 累积 → bWt) ~83807-83826
Mdn 定义(h18v_1 = 第 3 个参数) ~34869
N4t(tnl/amr/o1ag 上行分发) ~93103-93153
PYt(lockingServerInterface 窗口,与本案无关) ~86175-86216