第 3 章:基础数据类型
规范状态: Draft
本章范围: 本章定义 VFP 1.3 在所有 Header、Footer、Directory 和 Section Payload 中共用的字节序、数值类型、浮点数、字符串和 Hash 规则。具体区段字段仍以各区段章节为准。
除非某个字段明确另有规定,所有 VFP 1.3 实现必须遵循本章规则。写入器不得依赖宿主语言的内存布局、结构体对齐、字符串实现或哈希库默认行为。
3.1 Endian
3.1.1 小端序
VFP 1.3 的所有多字节数值字段均采用 Little-endian(小端序)。低有效字节必须存放在较小的文件偏移处。
例如,uint32 值 0x12345678 必须按以下四个字节写入:
78 56 34 12
uint16 值 0x1A2B 必须按以下两个字节写入:
2B 1A
该规则适用于 Header、Footer、Directory、所有整数、IEEE 754 浮点数、长度、偏移、CRC32 和枚举值。
3.1.2 Tag 的字节顺序
四字节 Tag 不按整数解释,而是按 ASCII 字符顺序原样存储。例如:
| Tag | 十六进制字节 |
|---|---|
VFPK | 56 46 50 4B |
VFPF | 56 46 50 46 |
DIR0 | 44 49 52 30 |
VOX0 | 56 4F 58 30 |
实现不得先把 Tag 转成宿主端序整数再比较。应按四个原始字节或 ASCII 字符串进行比较。
3.1.3 对齐与未对齐读取
文件的 Section 起始偏移要求 8 字节对齐,但字段本身不保证自然对齐。例如 Footer 中的 directoryOffset 位于字节偏移 12。实现不得将文件字节直接强制转换为依赖 CPU 对齐的原生结构体。
推荐做法是使用显式的小端读取函数、经过验证的二进制读取器,或使用禁用隐式对齐的序列化定义。
3.2 Integer
3.2.1 标准整数类型
VFP 1.3 只使用下列固定宽度整数类型:
| 类型 | 字节数 | 范围 | 典型用途 |
|---|---|---|---|
uint8 | 1 | 0 至 255 | Palette Index、局部坐标、小型枚举、布尔值 |
int8 | 1 | -128 至 127 | 有符号小型偏移或枚举 |
uint16 | 2 | 0 至 65,535 | 版本、尺寸、短计数 |
int16 | 2 | -32,768 至 32,767 | 有符号短整数 |
uint32 | 4 | 0 至 4,294,967,295 | 计数、Flags、Codec、CRC32 |
int32 | 4 | -2,147,483,648 至 2,147,483,647 | chunkId、有符号枚举 |
uint64 | 8 | 0 至 18,446,744,073,709,551,615 | 文件 Offset、文件长度、Payload 长度 |
int64 | 8 | -9,223,372,036,854,775,808 至 9,223,372,036,854,775,807 | 仅在后续章节明确要求时使用 |
除明确规定的类型外,VFP 不使用依赖平台宽度的 int、long、size_t 或指针类型。
3.2.2 无符号与有符号解释
字段必须按其定义的有符号性读取。chunkId 是 int32,因此全局区段必须存储 -1,其 Little-endian 字节为:
FF FF FF FF
读取器不得将 chunkId = 0xFFFFFFFF 当作无符号数 4,294,967,295。反之,长度、Offset 和计数必须按无符号数读取,不得因最高位为 1 而被解释为负数。
3.2.3 布尔、枚举与 Flags
除非字段章节另有定义:
- 布尔值使用
uint8:0为 false,1为 true,其他值无效; - 枚举使用字段指定的整数类型;未定义枚举值不得被猜测解释;
- 位 Flags 使用无符号整数;未定义位必须为
0; - 写入器不得输出保留枚举值或保留 Flag 位。
读取未知枚举或 Flag 时的处理取决于其影响范围:
| 情况 | 行为 |
|---|---|
| 影响权威数据解释 | 必须拒绝受影响文件或区段 |
| 位于必需区段 | 必须拒绝资产 |
| 位于可选缓存或展示区段 | 可忽略整个区段并报告兼容性提示 |
3.2.4 数值转换和溢出
读取器必须在将二进制字段转换为语言内数值前确认目标类型可安全承载该值。尤其是 JavaScript、ArkTS 等以 IEEE 754 Number 表示普通整数的环境,不能精确表示大于 2^53 - 1 的 uint64。
这类实现必须:
- 使用
BigInt、64 位整数库或等价方式读取uint64; - 在转换为普通 Number 前确认值不超过安全整数范围;
- 在执行
offset + length、count × stride等计算前进行显式溢出检查; - 在资源上限之外拒绝文件,而不是发生回绕或静默截断。
3.2.5 体素坐标和 Palette Index
VFP 1.3 的单体资产以 8 位体素坐标与 8 位 Palette Index 为基础:
- 资产单轴逻辑尺寸不得超过
255; - 每个 Chunk 单轴尺寸不得超过
255; uint8Palette Index 的0固定表示空体素;1..255表示引用PAL0中的非空颜色条目。
VFP 1.3 使用右手坐标系:+X 向右、+Y 向上、+Z 向前。一个体素占据一个单位立方体;整数体素坐标指向该立方体的最小角点。
三维体素数组的标准线性顺序为 X 最快变化、随后 Y、最后 Z:
linearIndex = x + sizeX × (y + sizeY × z)
该规则适用于 VOX0 的 RAW8、RLE8 解码结果及任何引用连续体素序列的标准区段。
3.3 Float
3.3.1 标准浮点类型
VFP 1.3 的浮点数使用 IEEE 754 binary32,也即 float32:
| 类型 | 字节数 | 格式 | 典型用途 |
|---|---|---|---|
float32 | 4 | IEEE 754 binary32,小端序 | 相机、变换、光照、展示和渲染参数 |
除非后续章节明确规定,VFP 1.3 不使用 float64。这能保持跨平台实现简单,并避免不同语言默认双精度行为造成不必要的二进制差异。
3.3.2 有限值要求
除非字段章节明确允许,所有 float32 字段必须为有限数值:
NaN无效;+Infinity无效;-Infinity无效;- 子正常数、负零和普通有限数均可被读取;写入器应避免无必要地输出负零。
验证器发现非有限值时必须报告错误。读取器不得将 NaN 自动替换为 0 后继续把该字段视为有效;对权威语义或结构有影响的浮点字段,应拒绝受影响区段。
3.3.3 坐标、变换与范围
浮点数通常用于展示层,例如资产根变换、默认相机、光源、动画插值或渲染参数。体素占据关系、Chunk 索引和 Palette Index 不应依赖浮点值表达。
后续章节若定义向量、矩阵或四元数,将使用连续的 float32 分量,且必须明确分量顺序。除非字段另有定义:
- 三维向量按
(x, y, z)顺序存储; - 四元数按
(x, y, z, w)顺序存储; - 矩阵按字段章节声明的行主序或列主序解释,读取器不得自行假设。
3.3.4 确定性注意事项
浮点运算在不同硬件和编译器上的末位结果可能不同。因此:
- 写入器应在写入前将结果量化为
float32; - Source Hash 不应依赖未规范化的运行时临时浮点计算;
- 缓存生成算法若使用浮点,应在其章节中规定稳定的量化或比较策略;
- 对于可用整数表达的权威数据,必须优先使用整数而不是浮点。
3.4 String
3.4.1 编码与布局
除非字段章节另有规定,VFP 字符串使用无 BOM 的 UTF-8,并采用下列长度前缀布局:
uint32 byteLength
uint8[byteLength] utf8Bytes
byteLength 是 UTF-8 编码后的字节数,不是 Unicode 码点数量、UTF-16 code unit 数量或显示字符数量。字符串不带 NUL 终止符。
例如字符串 VFP 的布局为:
03 00 00 00 56 46 50
3.4.2 有效性规则
一个有效字符串必须满足:
byteLength不得超过该区段剩余可读字节;- 字节序列必须是有效 UTF-8;
- 不得包含 UTF-8 BOM;
- 除非字段章节显式允许,不得包含 U+0000 NUL;
- 写入器应采用 Unicode NFC 规范化形式;
- 单个字符串默认不得超过
1 MiB,实现可采用更低上限但必须报错而非截断。
读取器可以原样保留字符串字节用于重新写入,也可以解码为宿主语言字符串。若解码失败,权威字段中的字符串错误必须使区段无效;可选展示字段可按其章节规则忽略。
3.4.3 字符串比较与标识
VFP 的 Tag、Magic、Codec 名称和规范关键字不是 UTF-8 可变长度字符串,而是固定定义的 ASCII 字节。它们必须按精确字节比较,不能进行大小写折叠、Unicode 规范化或本地化比较。
资产名称、作者、描述等人类可读文本应使用本节字符串布局,但不得被用作唯一资产 ID、Chunk ID 或跨文件引用键。需要稳定机器身份时,应使用 UUID、Hash 或后续章节明确规定的二进制标识。
3.5 Hash
3.5.1 CRC32
VFP 1.3 使用 IEEE 802.3 CRC-32 校验存储损坏。参数固定如下:
| 参数 | 值 |
|---|---|
| 标准名称 | CRC-32/ISO-HDLC |
| Normal Polynomial | 0x04C11DB7 |
| Reflected Polynomial | 0xEDB88320 |
| Initial Value | 0xFFFFFFFF |
| RefIn / RefOut | true / true |
| Final XOR | 0xFFFFFFFF |
| 输出宽度 | 32 bits |
CRC32 结果以 uint32 Little-endian 存储。它的用途如下:
| 字段 | 被校验的字节范围 |
|---|---|
DirectoryEntry.crc32 | 对应 Payload 的存储后字节,即 offset .. offset + length |
Footer.directoryCrc32 | 当前 DIR0 的全部字节,含 Directory Header 与全部 Entry |
CRC32 只用于检测意外损坏,不提供加密安全性、身份认证或防篡改能力。
3.5.2 SHA-256 Source Hash
VFP 1.3 使用 SHA-256 表示权威资产内容的 Source Hash。结果为固定 32 字节,不以十六进制文本形式存储。
Source Hash 的目的是判断缓存是否仍属于当前权威资产:当 META、PAL0、CHIX 或任一 VOX0 的权威内容发生变化时,新的 Source Hash 必须不同,既有缓存必须重新验证或重建。
Source Hash 的具体规范化输入由第 16 章定义。VFP 1.3 的总原则为:
- 输入只包含权威资产层所需的规范化数据;
- 不包含 Header、Footer、Directory、文件偏移、时间戳、UUID、填充字节或缓存数据;
- 各 Chunk 的输入顺序必须稳定,通常按
chunkId升序; META中自身保存 Source Hash 的字段在计算时必须视为全零,避免自引用;- 实现不得用文件整体 Hash 代替 Source Hash,因为同一资产可以拥有不同的缓存、排列和追加历史。
3.5.3 缓存 Hash 校验
缓存区段在被使用前必须能关联到生成时的 Source Hash。读取器必须比较:
cache.sourceHash == authoritative.sourceHash
仅当两者完整 32 字节一致时,缓存才可被视为与当前资产匹配。CRC32 通过但 Source Hash 不匹配的缓存不是损坏数据,而是过期数据;读取器应忽略或重建它,而不应拒绝权威资产。
3.5.4 Hash 实现要求
- 写入器必须使用标准 SHA-256,不能替换为截断 Hash、平台私有 Hash 或随机标识;
- 验证器必须按规范化输入重新计算 Source Hash;
- 任何 Hash 比较必须比较完整字节序列,不得比较字符串前缀;
- 实现可以为索引或去重额外计算其他 Hash,但这些 Hash 不属于 VFP 1.3 语义;
- Hash 计算失败、输入不完整或权威区段无法解析时,验证器必须将 Source Hash 视为不可验证,而不是通过校验。
本章总结
本章统一了 VFP 的基础二进制规则:所有数值使用小端序,所有整数具有明确宽度,浮点数必须可验证,字符串使用长度前缀 UTF-8,CRC32 用于检测存储损坏,SHA-256 Source Hash 用于确认权威资产与缓存的一致性。
后续所有区段章节必须引用本章类型与编码规则,不得重复定义冲突的字节序、数值宽度、字符串格式或 Hash 语义。