fix(missile): 修复充能弹 (Charged Bolt #38) 随机游走路径缺陷:横向摆动过小 (仅±3px) 且纵向推进过快 #435

Closed
opened 2026-09-24 08:15:43 +00:00 by troytt · 0 comments
Owner

1. 缺陷概述 (Issue Summary)

当前网页端实现中,女巫 1 级闪电技能 充能弹 (Charged Bolt, Skill ID: 38 / Missile ID: 56) 的弹道运动学(Kinematics)与原版暗黑破坏神 II (v1.13c) 存在显著不符:

  1. 横向游走严重不足(几乎笔直推进):充能弹在飞行过程中,横向摆幅被局限在固定的 [-3px, 0, +3px] 微小高频震颤内,并没有形成原版中如同电蛇一般在地面左右大范围大幅度蜿蜒曲折(蛇形游走)的视觉轨迹;
  2. 纵向推进过快:由于弹体没有将速度分配到真实的横向迂回分量中,充能弹以恒定的 300 px/s (12 px/frame) 直冲屏幕外,飞行显得过快过僵硬,缺失了原版充能弹在地面悠然散开、爬行蔓延的电火花质感。

2. Diablo II v1.13c Ground Truth 权威数据与机制分析

严格依据暗黑破坏神 II v1.13c 官方数据表(Skills.txt, Missiles.txt)及原版弹道算法:

2.1 Skills.txt (Row 38: Charged Bolt)

  • srvdofunc: 17(服务端充能弹多路发射与随机路径驱动函数)
  • cltdofunc: 23(客户端充能弹运动与渲染函数)
  • srvmissilea / cltmissilea: chargedbolt (ID: 56)
  • calc1: min(24, ln12)(弹丸数量:1 级 3 颗,每级 +1,上限 24 颗)

2.2 Missiles.txt (Row 56: chargedbolt)

  • Vel: 12(标称速度)
  • Range: 98(生命周期 98 帧 = 近 4 秒)
  • AnimLen: 10, AnimSpeed: 16, NumDirections: 16

2.3 原版 1.13c 随机游走运动学原理

  • 在原版 D2 (srvdofunc 17 / cltdofunc 23) 中:
    • 充能弹绝非一条笔直向前外加微小抖动的射线,而是具有**航向角动态偏转(Angular Drift / Brownian Steering)**的布朗运动/蛇形随机漫步;
    • 每一枚弹体在向前推进的同时,其前进矢量不断向左右大幅偏转,横向摆幅可达数个 Subtiles(数十至上百像素);
    • 由于大量速度矢量被分配到了横向迂回与曲折往复上,其沿主发射方向的纵向实际有效推进速度明显慢于其线速度,因而在屏幕上能够停留很长时间并形成一大片错综复杂的闪电陷阱网;
    • 弹幕出发时的扩散角度也更加开阔,在视野内铺展成扇面。

3. 当前代码根因剖析 (Root Cause Analysis)

3.1 横向位移硬编码为极窄的 ±3px 且未累积航向

在 src/game/skills.ts(第 4233–4247 行)与 src/game/engine/missile-engine.ts(第 1018–1029 行)中:

if (m.name === 'chargedbolt' || m.sourceSkillId === 38 || m.record.pSrvDoFunc === 11 || m.record.pCltDoFunc === 11) {
  const speed = Math.hypot(m.vx, m.vy)
  if (speed > 0) {
    const perpX = -m.vy / speed
    const perpY = m.vx / speed
    const idx = Math.abs((m.jitterIndex ?? m.ageTicks) % 32)
    const offset = GAT_CHARGED_BOLT_OFFSETS[idx]! * 3
    m.x += perpX * offset
    m.y += perpY * (offset * ISO_GROUND_ASPECT_RATIO)
    m.jitterIndex = (idx + 1) % 32
  }
}

问题点:

  1. 表 GAT_CHARGED_BOLT_OFFSETS 的取值为循环的 [-1, 0, 1, -1, 0, 1...],乘以硬编码常数 3 后,单帧偏移量仅仅为 -3px, 0, +3px。
  2. 该位移每 3 帧净位移相加为 0(-3 + 0 + 3 = 0),未对航向角产生持久偏转,也未累加横向位置。
  3. 结果导致弹体在长达 1176 像素的整个飞行距离中,横向最大偏离绝对值永远不会超过 3 像素(1176 : 3 = 392:1),在屏幕肉眼观察下几乎就是一条笔直射线带点细微震颤,完全没有原版的“游走”感!

3.2 纵向速度未受横向摆动分流,初始散射角过窄

在 src/scene/act-scene.ts(第 1948–1960 行 及 2175–2185 行):

speed = missile.speedPxPerSec || 300
spreadAngle = 0.55 // 仅约 31.5 度
...
const angle = startAngle + i * step
const pVx = Math.cos(angle) * perTick // perTick = 12 px/frame
const pVy = Math.sin(angle) * perTick
  • 速度直接全额分配给射线方向(12 px/frame),没有横向蛇形消耗速度,纵向飞驰过快;
  • 初始散射角 spreadAngle = 0.55 过于集中收拢,多枚充能弹几乎挤在一小束平行直冲。

4. 修复方案设计 (Proposed Architecture & Implementation Plan)

4.1 实施动态航向角偏转 / 连续平滑随机游走 (Steering Random Walk)

  1. 航向角动态偏转(Angular Drift):
    • 弹体维护其瞬时运动角度 currentAngle,每若干帧(或基于低频噪声/平滑步进表)施加角速度扰动 dAngle(例如 ±10°~±25°);
    • 设置最大偏离初始发射轴夹角(例如 ±45°~±60°)作为弹簧约束;
    • 瞬时速度由角度分解:
      vx = Math.cos(currentAngle) * speed
      vy = Math.sin(currentAngle) * speed * ISO_GROUND_ASPECT_RATIO
      
    • 这样保证了总速率恒定的前提下,横向出现大幅度曲折蛇行,同时沿发射方向的纵向有效推进速度自然显著放缓!
  2. 复合正弦横向波浪模型(备选极简方案):
    • 若保留主射线推进,横向位移必须进行大幅度连续累加(横向摆幅振幅提升至 30~80 像素,波动周期约为 20~30 帧),并将纵向前进速度适当下调(如标称 300 下调 25%~35%),实现慢速铺展的视觉效果。

4.2 扩展初始发射扇面

  • 将 spreadAngle 从当前过窄的 0.55 rad 适度调整至 0.85 ~ 1.05 rad(约 50° ~ 60°),使 3~24 枚充能弹在发射瞬间就呈现更加宽广的扇形覆盖面。

4.3 单元测试与验收标准 (Verification Plan)

  1. 运动学测试断言更新:
    • 更新 tests/skills/sor/skill-038-charged-bolt.test.ts,断言单颗充能弹在生命周期中横向最大偏离距离(Max Lateral Deviation)必须显著大于 25px(达到 30~80px 级别),不再拘泥于 [-3, 0, 3]。
  2. 游戏实机观测:
    • 施放充能弹,多颗电光球在屏幕前方向前慢慢爬行、大幅蛇形扭动扭曲,如同一团活生生的电网在地面徐徐蔓延,完美对齐 1.13c 原版表现。
### 1. 缺陷概述 (Issue Summary) 当前网页端实现中,女巫 1 级闪电技能 **充能弹 (Charged Bolt, Skill ID: 38 / Missile ID: 56)** 的弹道运动学(Kinematics)与原版暗黑破坏神 II (v1.13c) 存在显著不符: 1. **横向游走严重不足(几乎笔直推进)**:充能弹在飞行过程中,横向摆幅被局限在固定的 `[-3px, 0, +3px]` 微小高频震颤内,并没有形成原版中如同电蛇一般在地面左右大范围大幅度蜿蜒曲折(蛇形游走)的视觉轨迹; 2. **纵向推进过快**:由于弹体没有将速度分配到真实的横向迂回分量中,充能弹以恒定的 300 px/s (12 px/frame) 直冲屏幕外,飞行显得过快过僵硬,缺失了原版充能弹在地面悠然散开、爬行蔓延的电火花质感。 --- ### 2. Diablo II v1.13c Ground Truth 权威数据与机制分析 严格依据暗黑破坏神 II v1.13c 官方数据表(`Skills.txt`, `Missiles.txt`)及原版弹道算法: #### 2.1 `Skills.txt` (Row 38: Charged Bolt) - **srvdofunc**: `17`(服务端充能弹多路发射与随机路径驱动函数) - **cltdofunc**: `23`(客户端充能弹运动与渲染函数) - **srvmissilea / cltmissilea**: `chargedbolt` (ID: 56) - **calc1**: `min(24, ln12)`(弹丸数量:1 级 3 颗,每级 +1,上限 24 颗) #### 2.2 `Missiles.txt` (Row 56: chargedbolt) - **Vel**: `12`(标称速度) - **Range**: `98`(生命周期 98 帧 = 近 4 秒) - **AnimLen**: `10`, **AnimSpeed**: `16`, **NumDirections**: `16` #### 2.3 原版 1.13c 随机游走运动学原理 - 在原版 D2 (`srvdofunc 17` / `cltdofunc 23`) 中: - 充能弹绝非一条笔直向前外加微小抖动的射线,而是具有**航向角动态偏转(Angular Drift / Brownian Steering)**的布朗运动/蛇形随机漫步; - 每一枚弹体在向前推进的同时,其前进矢量不断向左右大幅偏转,横向摆幅可达数个 Subtiles(数十至上百像素); - 由于大量速度矢量被分配到了横向迂回与曲折往复上,其**沿主发射方向的纵向实际有效推进速度明显慢于其线速度**,因而在屏幕上能够停留很长时间并形成一大片错综复杂的闪电陷阱网; - 弹幕出发时的扩散角度也更加开阔,在视野内铺展成扇面。 --- ### 3. 当前代码根因剖析 (Root Cause Analysis) #### 3.1 横向位移硬编码为极窄的 ±3px 且未累积航向 在 [`src/game/skills.ts`](file:///usr/local/google/home/taodao/diablo2-web/src/game/skills.ts)(第 4233–4247 行)与 [`src/game/engine/missile-engine.ts`](file:///usr/local/google/home/taodao/diablo2-web/src/game/engine/missile-engine.ts)(第 1018–1029 行)中: ```typescript if (m.name === 'chargedbolt' || m.sourceSkillId === 38 || m.record.pSrvDoFunc === 11 || m.record.pCltDoFunc === 11) { const speed = Math.hypot(m.vx, m.vy) if (speed > 0) { const perpX = -m.vy / speed const perpY = m.vx / speed const idx = Math.abs((m.jitterIndex ?? m.ageTicks) % 32) const offset = GAT_CHARGED_BOLT_OFFSETS[idx]! * 3 m.x += perpX * offset m.y += perpY * (offset * ISO_GROUND_ASPECT_RATIO) m.jitterIndex = (idx + 1) % 32 } } ``` **问题点**: 1. 表 `GAT_CHARGED_BOLT_OFFSETS` 的取值为循环的 `[-1, 0, 1, -1, 0, 1...]`,乘以硬编码常数 `3` 后,单帧偏移量仅仅为 `-3px, 0, +3px`。 2. 该位移每 3 帧净位移相加为 0(`-3 + 0 + 3 = 0`),未对航向角产生持久偏转,也未累加横向位置。 3. 结果导致弹体在长达 1176 像素的整个飞行距离中,**横向最大偏离绝对值永远不会超过 3 像素**(1176 : 3 = 392:1),在屏幕肉眼观察下几乎就是一条笔直射线带点细微震颤,完全没有原版的“游走”感! #### 3.2 纵向速度未受横向摆动分流,初始散射角过窄 在 [`src/scene/act-scene.ts`](file:///usr/local/google/home/taodao/diablo2-web/src/scene/act-scene.ts)(第 1948–1960 行 及 2175–2185 行): ```typescript speed = missile.speedPxPerSec || 300 spreadAngle = 0.55 // 仅约 31.5 度 ... const angle = startAngle + i * step const pVx = Math.cos(angle) * perTick // perTick = 12 px/frame const pVy = Math.sin(angle) * perTick ``` - 速度直接全额分配给射线方向(12 px/frame),没有横向蛇形消耗速度,纵向飞驰过快; - 初始散射角 `spreadAngle = 0.55` 过于集中收拢,多枚充能弹几乎挤在一小束平行直冲。 --- ### 4. 修复方案设计 (Proposed Architecture & Implementation Plan) #### 4.1 实施动态航向角偏转 / 连续平滑随机游走 (Steering Random Walk) 1. **航向角动态偏转(Angular Drift)**: - 弹体维护其瞬时运动角度 `currentAngle`,每若干帧(或基于低频噪声/平滑步进表)施加角速度扰动 `dAngle`(例如 ±10°~±25°); - 设置最大偏离初始发射轴夹角(例如 ±45°~±60°)作为弹簧约束; - 瞬时速度由角度分解: ```typescript vx = Math.cos(currentAngle) * speed vy = Math.sin(currentAngle) * speed * ISO_GROUND_ASPECT_RATIO ``` - 这样保证了**总速率恒定**的前提下,横向出现大幅度曲折蛇行,同时**沿发射方向的纵向有效推进速度自然显著放缓**! 2. **复合正弦横向波浪模型(备选极简方案)**: - 若保留主射线推进,横向位移必须进行**大幅度连续累加**(横向摆幅振幅提升至 **30~80 像素**,波动周期约为 20~30 帧),并将纵向前进速度适当下调(如标称 300 下调 25%~35%),实现慢速铺展的视觉效果。 #### 4.2 扩展初始发射扇面 - 将 `spreadAngle` 从当前过窄的 `0.55` rad 适度调整至 `0.85 ~ 1.05` rad(约 50° ~ 60°),使 3~24 枚充能弹在发射瞬间就呈现更加宽广的扇形覆盖面。 #### 4.3 单元测试与验收标准 (Verification Plan) 1. **运动学测试断言更新**: - 更新 `tests/skills/sor/skill-038-charged-bolt.test.ts`,断言单颗充能弹在生命周期中横向最大偏离距离(Max Lateral Deviation)必须显著大于 25px(达到 30~80px 级别),不再拘泥于 `[-3, 0, 3]`。 2. **游戏实机观测**: - 施放充能弹,多颗电光球在屏幕前方向前慢慢爬行、大幅蛇形扭动扭曲,如同一团活生生的电网在地面徐徐蔓延,完美对齐 1.13c 原版表现。
Sign in to join this conversation.
No Label
No Milestone
No project
No Assignees
1 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: troytt/diablo2-web#435
No description provided.