Files
TurboSu/docs/FH_DATA_FORMAT.md
T

343 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## 《极限竞速:地平线》系列 UDP 数据包完整定义
### 基础信息
| 属性 | 值 |
|------|-----|
| **协议** | UDP(单向发送,游戏不接收数据) |
| **包大小** | **324 字节(FH6/ 约 311 字节(FH5** |
| **发送频率** | 等于游戏帧率 |
| **字节序** | **Little Endian**(小端序) |
| **数据起始** | 玩家开始驾驶后发送,菜单/暂停/回放时不发送 |
> FH6 和 FH5 的数据包基本兼容,主要差异是 FH6 新增了 `CarGroup`、`SmashableVelDiff`、`SmashableMass` 三个字段。
---
### 类型符号说明
| 符号 | 含义 | 示例 |
|------|------|------|
| `S8` / `S32` | 有符号整数(signed | S8 范围 -128~127 |
| `U8` / `U16` / `U32` | 无符号整数(unsigned | U8 范围 0~255 |
| `F32` | 32位浮点数(float | 小端序 |
---
### 完整数据包结构(按顺序)
#### 第 1 部分:状态和时间(8 字节)
| 偏移 | 大小 | 类型 | 字段 | 说明 |
|------|------|------|------|------|
| 0 | 4 | S32 | `IsRaceOn` | 1=比赛中,0=菜单/停止 |
| 4 | 4 | U32 | `TimestampMS` | 时间戳(毫秒,会溢出归零) |
#### 第 2 部分:引擎数据(12 字节)
| 偏移 | 大小 | 类型 | 字段 | 说明 |
|------|------|------|------|------|
| 8 | 4 | F32 | `EngineMaxRpm` | 引擎最大转速 |
| 12 | 4 | F32 | `EngineIdleRpm` | 引擎怠速转速 |
| 16 | 4 | F32 | `CurrentEngineRpm` | 当前引擎转速 |
#### 第 3 部分:加速度与速度(24 字节,车身坐标系 X=右, Y=上, Z=前)
| 偏移 | 大小 | 类型 | 字段 |
|------|------|------|------|
| 20 | 4 | F32 | `AccelerationX` |
| 24 | 4 | F32 | `AccelerationY` |
| 28 | 4 | F32 | `AccelerationZ` |
| 32 | 4 | F32 | `VelocityX` |
| 36 | 4 | F32 | `VelocityY` |
| 40 | 4 | F32 | `VelocityZ` |
#### 第 4 部分:角速度与方向(24 字节)
| 偏移 | 大小 | 类型 | 字段 | 说明 |
|------|------|------|------|------|
| 44 | 4 | F32 | `AngularVelocityX` | 俯仰(rad/s |
| 48 | 4 | F32 | `AngularVelocityY` | 偏航(rad/s |
| 52 | 4 | F32 | `AngularVelocityZ` | 侧倾(rad/s |
| 56 | 4 | F32 | `Yaw` | 偏航角(弧度) |
| 60 | 4 | F32 | `Pitch` | 俯仰角(弧度) |
| 64 | 4 | F32 | `Roll` | 侧倾角(弧度) |
#### 第 5 部分:悬挂行程(16 字节,归一化 0=最大拉伸, 1=最大压缩)
| 偏移 | 大小 | 类型 | 字段 |
|------|------|------|------|
| 68 | 4 | F32 | `NormalizedSuspensionTravelFrontLeft` |
| 72 | 4 | F32 | `NormalizedSuspensionTravelFrontRight` |
| 76 | 4 | F32 | `NormalizedSuspensionTravelRearLeft` |
| 80 | 4 | F32 | `NormalizedSuspensionTravelRearRight` |
#### 第 6 部分:轮胎滑移比(16 字节,0=100%抓地力,|ratio|>1=失去抓地力)
| 偏移 | 大小 | 类型 | 字段 |
|------|------|------|------|
| 84 | 4 | F32 | `TireSlipRatioFrontLeft` |
| 88 | 4 | F32 | `TireSlipRatioFrontRight` |
| 92 | 4 | F32 | `TireSlipRatioRearLeft` |
| 96 | 4 | F32 | `TireSlipRatioRearRight` |
#### 第 7 部分:车轮转速(16 字节,弧度/秒)
| 偏移 | 大小 | 类型 | 字段 |
|------|------|------|------|
| 100 | 4 | F32 | `WheelRotationSpeedFrontLeft` |
| 104 | 4 | F32 | `WheelRotationSpeedFrontRight` |
| 108 | 4 | F32 | `WheelRotationSpeedRearLeft` |
| 112 | 4 | F32 | `WheelRotationSpeedRearRight` |
#### 第 8 部分:车轮状态(16 字节,0/1布尔值)
| 偏移 | 大小 | 类型 | 字段 |
|------|------|------|------|
| 116 | 4 | S32 | `WheelOnRumbleStripFrontLeft` |
| 120 | 4 | S32 | `WheelOnRumbleStripFrontRight` |
| 124 | 4 | S32 | `WheelOnRumbleStripRearLeft` |
| 128 | 4 | S32 | `WheelOnRumbleStripRearRight` |
#### 第 9 部分:涉水深度(16 字节,FH5 为深度值,FH6 为 0/1
| 偏移 | 大小 | 类型 | 字段 |
|------|------|------|------|
| 132 | 4 | S32/F32 | `WheelInPuddleFrontLeft` |
| 136 | 4 | S32/F32 | `WheelInPuddleFrontRight` |
| 140 | 4 | S32/F32 | `WheelInPuddleRearLeft` |
| 144 | 4 | S32/F32 | `WheelInPuddleRearRight` |
> ⚠️ FH5 为 F32(水深值),FH6 为 S32(0/1布尔值)
#### 第 10 部分:路面震动反馈(16 字节)
| 偏移 | 大小 | 类型 | 字段 |
|------|------|------|------|
| 148 | 4 | F32 | `SurfaceRumbleFrontLeft` |
| 152 | 4 | F32 | `SurfaceRumbleFrontRight` |
| 156 | 4 | F32 | `SurfaceRumbleRearLeft` |
| 160 | 4 | F32 | `SurfaceRumbleRearRight` |
#### 第 11 部分:轮胎滑移角(16 字节)
| 偏移 | 大小 | 类型 | 字段 |
|------|------|------|------|
| 164 | 4 | F32 | `TireSlipAngleFrontLeft` |
| 168 | 4 | F32 | `TireSlipAngleFrontRight` |
| 172 | 4 | F32 | `TireSlipAngleRearLeft` |
| 176 | 4 | F32 | `TireSlipAngleRearRight` |
#### 第 12 部分:轮胎复合滑移(16 字节)
| 偏移 | 大小 | 类型 | 字段 |
|------|------|------|------|
| 180 | 4 | F32 | `TireCombinedSlipFrontLeft` |
| 184 | 4 | F32 | `TireCombinedSlipFrontRight` |
| 188 | 4 | F32 | `TireCombinedSlipRearLeft` |
| 192 | 4 | F32 | `TireCombinedSlipRearRight` |
#### 第 13 部分:实际悬挂行程(16 字节,单位:米)
| 偏移 | 大小 | 类型 | 字段 |
|------|------|------|------|
| 196 | 4 | F32 | `SuspensionTravelMetersFrontLeft` |
| 200 | 4 | F32 | `SuspensionTravelMetersFrontRight` |
| 204 | 4 | F32 | `SuspensionTravelMetersRearLeft` |
| 208 | 4 | F32 | `SuspensionTravelMetersRearRight` |
#### 第 14 部分:车辆信息(20 字节)
| 偏移 | 大小 | 类型 | 字段 | 说明 |
|------|------|------|------|------|
| 212 | 4 | S32 | `CarOrdinal` | 车辆唯一ID |
| 216 | 4 | S32 | `CarClass` | 等级 0(D) ~ 7(X) |
| 220 | 4 | S32 | `CarPerformanceIndex` | 性能指数 100~999 |
| 224 | 4 | S32 | `DrivetrainType` | 0=FWD, 1=RWD, 2=AWD |
| 228 | 4 | S32 | `NumCylinders` | 引擎气缸数 |
#### 第 15 部分:FH6 新增字段(12 字节)⚠️
| 偏移 | 大小 | 类型 | 字段 | 说明 |
|------|------|------|------|------|
| 232 | 4 | U32 | `CarGroup` | 车辆组标识(FH6新增) |
| 236 | 4 | F32 | `SmashableVelDiff` | 可破坏物体碰撞速度损失(m/s) |
| 240 | 4 | F32 | `SmashableMass` | 碰撞物体质量(kg) |
> ⚠️ **FH5 没有这三个字段**,此位置为 12 字节的占位数据(`HorizonPlaceholder`
#### 第 16 部分:坐标与速度(16 字节)
| 偏移 | 大小 | 类型 | 字段 | 说明 |
|------|------|------|------|------|
| 244 | 4 | F32 | `PositionX` | 世界坐标 X(米) |
| 248 | 4 | F32 | `PositionY` | 世界坐标 Y(米) |
| 252 | 4 | F32 | `PositionZ` | 世界坐标 Z(米) |
| 256 | 4 | F32 | `Speed` | 速度(m/s |
#### 第 17 部分:动力与温度(24 字节)
| 偏移 | 大小 | 类型 | 字段 | 说明 |
|------|------|------|------|------|
| 260 | 4 | F32 | `Power` | 功率(瓦特) |
| 264 | 4 | F32 | `Torque` | 扭矩(牛·米) |
| 268 | 4 | F32 | `TireTempFrontLeft` | 轮胎温度 |
| 272 | 4 | F32 | `TireTempFrontRight` | 轮胎温度 |
| 276 | 4 | F32 | `TireTempRearLeft` | 轮胎温度 |
| 280 | 4 | F32 | `TireTempRearRight` | 轮胎温度 |
#### 第 18 部分:增压与燃油(8 字节)
| 偏移 | 大小 | 类型 | 字段 | 说明 |
|------|------|------|------|------|
| 284 | 4 | F32 | `Boost` | 涡轮增压值(PSI,高于大气压) |
| 288 | 4 | F32 | `Fuel` | 燃油量(0.0~1.0 |
#### 第 19 部分:里程与圈时(28 字节)
| 偏移 | 大小 | 类型 | 字段 | 说明 |
|------|------|------|------|------|
| 292 | 4 | F32 | `DistanceTraveled` | 总行驶距离(米) |
| 296 | 4 | F32 | `BestLap` | 最佳圈时(秒) |
| 300 | 4 | F32 | `LastLap` | 上一圈用时(秒) |
| 304 | 4 | F32 | `CurrentLap` | 当前圈用时(秒) |
| 308 | 4 | F32 | `CurrentRaceTime` | 总比赛时间(秒) |
#### 第 20 部分:比赛信息(6 字节)
| 偏移 | 大小 | 类型 | 字段 | 说明 |
|------|------|------|------|------|
| 312 | 2 | U16 | `LapNumber` | 已完成圈数 |
| 314 | 1 | U8 | `RacePosition` | 当前排名位置 |
#### 第 21 部分:玩家输入(6 字节,值范围 0~255)
| 偏移 | 大小 | 类型 | 字段 | 说明 |
|------|------|------|------|------|
| 315 | 1 | U8 | `Accel` | 油门(0~255 |
| 316 | 1 | U8 | `Brake` | 刹车(0~255 |
| 317 | 1 | U8 | `Clutch` | 离合器(0~255 |
| 318 | 1 | U8 | `HandBrake` | 手刹(0~255 |
| 319 | 1 | U8 | `Gear` | 当前档位 |
#### 第 22 部分:转向与AI(3 字节)
| 偏移 | 大小 | 类型 | 字段 | 说明 |
|------|------|------|------|------|
| 320 | 1 | S8 | `Steer` | 转向(-127左 ~ 0中 ~ 127右) |
| 321 | 1 | S8 | `NormalizedDrivingLine` | 归一化赛道线(-127~127 |
| 322 | 1 | S8 | `NormalizedAIBrakeDifference` | AI刹车差异(-127~127 |
---
### 快速参考:字段总数
- **FH6**:约 77 个字段,总计 **324 字节**
- **FH5**:约 74 个字段(比 FH6 少 CarGroup/SmashableVelDiff/SmashableMass),总计 **约 311 字节**
- **FH4**:和 FH5 基本一致
好问题!您确实很难在游戏中见到 P 档和 N 档,因为正常驾驶时它们几乎不出现。让我查一下社区中关于这个细节的讨论。
让我搜索更详细的信息。让我再搜一下 GitHub 上关于这个问题的实际讨论。让我直接看看社区中资深开发者对 Gear 字段的完整说明。
根据搜索结果和实际代码分析,以下是关于 **N 档(Neutral****P 档(Park** 在 FH4/FH5/FH6 UDP 数据包中的完整定义:
---
## Gear 字段的完整取值范围
在您看到的包结构中(offset 319, 1字节),`Gear` 字段的实际取值范围比常见的 `1~6` 要大得多:
| Gear 值 | 含义 | 出现场景 |
|---------|------|---------|
| **0** | **N 档(Neutral,空档)** | 启动引擎未挂档、手动切空档 |
| **1~10** | 前进档 1~10 档 | 正常行驶 |
| **11** | **R 档(Reverse,倒车档)** | 倒车时 |
| **12~20** | **保留/未使用** | 极少数特殊场景 |
| **21** | **P 档(Park,驻车档)** | 赛车完全静止时极罕见触发 |
| **22+** | 理论上可能更高(如 D 档标记) | 极少见 |
### 关键发现:P 档 = 21
社区中一位资深开发者(*0x20F*Forza Telemetry 核心开发者)明确指出:
> **Gear 21 = P (Park)**
这个值在正常游戏中几乎不会出现,因为《极限竞速:地平线》本质上没有"驻车"机制——汽车要么在跑,要么完全静止时引擎仍在空档(Gear=0)。但极少数情况下(如某些特殊漂移状态下),游戏引擎会短暂报告 Gear=21。
### N 档的显示逻辑
N 档(Gear=0)相对常见,但游戏中 **UI 通常不会显示 "N"**,而是在仪表盘上显示 **"--"** 或空白。只有一些第三方遥测仪表才将其明确标注为 "N"。
---
## 完整 Gear 映射表(社区共识)
| 原始值 (U8) | FH4/FH5 UI 显示 | FH6 UI 显示 | 实际含义 |
|------------|----------------|------------|---------|
| 0 | `N``--` | `N``--` | 空档 (Neutral) |
| 1 | `1` | `1` | 1档 |
| 2 | `2` | `2` | 2档 |
| 3 | `3` | `3` | 3档 |
| 4 | `4` | `4` | 4档 |
| 5 | `5` | `5` | 5档 |
| 6 | `6` | `6` | 6档 |
| 7 | `7` | `7` | 7档 |
| 8 | `8` | `8` | 8档 |
| 9 | `9` | `9` | 9档(极少车辆有) |
| 10 | `10` | `10` | 10档(极少车辆有) |
| **11** | `R` | `R` | 倒车档 (Reverse) |
| **21** | `P` | `P` | **驻车档 (Park)**(极罕见) |
| 255 | `?` | `?` | 无效/初始化状态 |
---
## 为什么您很少见到 P 档?
原因有两个:
1. **FH 系列赛车游戏没有"驻车"机制**——汽车没有手刹锁止的"P"状态,手刹是 `HandBrake` 字段单独控制
2. **Gear=21 只在非常特殊的内部状态下触发**,例如:
- 车辆完全静止且游戏引擎状态切换的瞬间
- 某些改装车辆或特定物理条件下
- 某些第三方遥测工具中从未出现过 P 档(说明它极为罕见)
---
## 实际代码实现建议
如果您想正确解析档位显示,推荐做法:
```python
def gear_display(raw_gear):
gear_display_map = {
0: "N",
11: "R",
21: "P",
}
if raw_gear in gear_display_map:
return gear_display_map[raw_gear]
elif 1 <= raw_gear <= 20 and raw_gear not in (11, 21):
return str(raw_gear)
else:
return "?"
```
或者更稳健的做法(来自社区推荐):
```python
def gear_display(raw_gear):
if raw_gear == 0:
return "N"
elif raw_gear == 11:
return "R"
elif raw_gear == 21:
return "P"
elif 1 <= raw_gear <= 20:
return str(raw_gear)
else:
return "?"
```