人物/NPC/怪物动作系统:严格对齐暗黑 2 v1.13c #145

Closed
opened 2026-09-21 12:16:17 +00:00 by troytt · 0 comments
Owner

实现完整的 D2 动作集并在正确时刻按正确速度播放,判定绑定到动画帧事件而非计时器。

详细计划与实测 ground truth 见分支 feat/animation-system。

范围(已决策)

决策点 结论
烘培模式 玩法关键子集:玩家 NU WL RN A1 A2 SC GH DT DD TN TW,怪物 NU WL RN A1 A2 SC GH DT DD(按 MonStats2 的 m* 位过滤)。全保真见 #143
武器类 本次接入 wclass 感知的 COF 选择(从 Weapons.txt 读 wclass/2handedwclass)
攻速 仅 AnimData 基础速度;完整 1.13c EIAS 公式见 #144
确定性摘要 重算并更新 tests/combat.test.ts 的黄金摘要(承认为有意的行为变更)
包围盒 改为每帧紧包围盒 + 显式锚点(同时是正确性修复与体积优化)

关键 Ground Truth(已实测,勿重复调研)

AnimData.D2

  • data\global\AnimData.D2,570,304 字节。另有 EAnimData.D2。
  • 布局:256 个 hash block,每块 = u32 recordCount + recordCount × 160 B。
  • 记录 160 B:char[8] name(NUL 补齐) + u32 framesPerDirection(LE) + u32 animationSpeed(LE) + u8[144] frameEventFlags。
  • 算术自证:3558×160 + 256×4 = 570,304 = 文件大小。
  • Hash:sum(大写名字符码) % 256 === blockIndex,3558/3558 全中。

时序

ticksPerCycle = framesPerDirection × 256 / animationSpeed,25 Hz tick。等价:每 tick 8.8 定点累加器 += animationSpeed,frameIndex = acc >> 8。

帧事件

code=1 = ATTACK(命中判定),code=2 = MISSILE(生成投射物)。例:SOA1HTH 帧 9→1,SOTHHTH 帧 10→2,SKA1HTH 帧 8→2。

⚠️ COF 的 speed 字节常为 0(sowlhth cofSpeed=0 而 SOWLHTH speed=256)⇒ AnimData 是速度唯一权威。

陷阱

MonStats2.txt 存在于 MPQ 但不在 (listfile) 里。必须用 archives.has()/read(),用 listFiles() 会误报 MISSING。

现状缺陷

  • 只烘了 2 个 clip(wl/nu)
  • 帧推进是全局 modulo 计数器,所有怪物共用相位,速度写死 /3 /5
  • 站立永远冻结在第 0 帧
  • 尸体从不绘制(act-scene.ts L3328 continue)
  • 怪物 attack 状态当 stand 画
  • 锚点被丢弃,绘制用 -w/2 近似
  • 全仓库无 AnimData 解码器
实现完整的 D2 动作集并在正确时刻按正确速度播放,判定绑定到**动画帧事件**而非计时器。 详细计划与实测 ground truth 见分支 `feat/animation-system`。 ## 范围(已决策) | 决策点 | 结论 | |---|---| | 烘培模式 | **玩法关键子集**:玩家 `NU WL RN A1 A2 SC GH DT DD TN TW`,怪物 `NU WL RN A1 A2 SC GH DT DD`(按 MonStats2 的 `m*` 位过滤)。全保真见 #143 | | 武器类 | **本次接入** wclass 感知的 COF 选择(从 `Weapons.txt` 读 `wclass`/`2handedwclass`) | | 攻速 | **仅 AnimData 基础速度**;完整 1.13c EIAS 公式见 #144 | | 确定性摘要 | 重算并更新 `tests/combat.test.ts` 的黄金摘要(承认为有意的行为变更) | | 包围盒 | 改为**每帧紧包围盒 + 显式锚点**(同时是正确性修复与体积优化) | ## 关键 Ground Truth(已实测,勿重复调研) ### AnimData.D2 - `data\global\AnimData.D2`,**570,304 字节**。另有 `EAnimData.D2`。 - 布局:**256 个 hash block**,每块 = `u32 recordCount` + `recordCount × 160 B`。 - 记录 160 B:`char[8] name`(NUL 补齐) + `u32 framesPerDirection`(LE) + `u32 animationSpeed`(LE) + `u8[144] frameEventFlags`。 - 算术自证:`3558×160 + 256×4 = 570,304` = 文件大小。 - Hash:`sum(大写名字符码) % 256 === blockIndex`,3558/3558 全中。 ### 时序 `ticksPerCycle = framesPerDirection × 256 / animationSpeed`,25 Hz tick。等价:每 tick 8.8 定点累加器 `+= animationSpeed`,`frameIndex = acc >> 8`。 ### 帧事件 `code=1` = ATTACK(命中判定),`code=2` = MISSILE(生成投射物)。例:`SOA1HTH` 帧 9→1,`SOTHHTH` 帧 10→2,`SKA1HTH` 帧 8→2。 ⚠️ **COF 的 speed 字节常为 0**(`sowlhth` cofSpeed=0 而 `SOWLHTH` speed=256)⇒ **AnimData 是速度唯一权威**。 ### 陷阱 `MonStats2.txt` **存在于 MPQ 但不在 `(listfile)` 里**。必须用 `archives.has()/read()`,用 `listFiles()` 会误报 MISSING。 ## 现状缺陷 - 只烘了 2 个 clip(`wl`/`nu`) - 帧推进是**全局 modulo 计数器**,所有怪物共用相位,速度写死 `/3` `/5` - 站立永远冻结在第 0 帧 - 尸体从不绘制(`act-scene.ts` L3328 `continue`) - 怪物 `attack` 状态当 stand 画 - 锚点被丢弃,绘制用 `-w/2` 近似 - 全仓库**无 AnimData 解码器**
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#145
No description provided.