含:z4t 三条上行(amr/o1ag/tnl)、三个时长(w1al/w187/x187)、FSM(TWt/vWt)结构、实测门槛(1.5s 最低满伤)、已落地改动与 commit 列表、离线解码流水线命令、关键行号速查。 同批提交 decode_space_frames.js 的 --side s2c/c2s 选项(MD 里的解码命令依赖它)。
9.3 KiB
9.3 KiB
电磁炮(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)=TWtTWt=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 采一轮整帧数据(需要人工在游戏里操作一次)
- F5(探针自动加载;徽标第二行应为「面板默认 1.5s(可实时调)」)
- 进战场 → 打 2~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. 教训与注意事项(★ 重要)
- 不能动冷却:
X6t/l1ar_1(普通弹i1ah_1=1.639;锁定弹w1al()≈6.6s)—— 服务端按它裁决; 短路它 = 出弹但无伤害(我第一次就踩了这个)。 - 别用假载荷预置
tnl:我一度在冷却开始时提前发Mdn(0,0,!0)→ 那一轮连弹药都看不到(整发被拒),已回滚。 - 开关只能用
localStorage:location.hash会被游戏当路由(进不去战场);查询串会被当 config 参数(握手 1005)。 - 改完必须走对应构建:
- 改
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)
- 改
- 面板滑块实时生效的前提:
z4t.w187()只在每次开始锁定时被读 ⇒ 改值影响下一发,当前这发不变。 - 探针整帧记录是硬要求:流密码状态跨消息连续,截断/漏帧会让后续解密全错。
- 验证版本永远看徽标第二行(
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 |