MassBattle Docsv1.19.20

FlowFieldSync.h

FFlowFieldSyncPayload FFlowFieldSubstrateSync 宏 ×2

Source/FlowFieldCanvas/Public/FlowFieldSync.h

概述 Overview

流场「基底同步」的线格式(Wire Format)定义头:把 AFlowField 的寻路基底(网格格子或 NavMesh tile)压成紧凑量化字节流,供锁步网络全端重建同一份场数据。本头定义两种流——全量流(魔数 'FFS1',版本 v4)与差分流(魔数 'FFD1',独立版本 v1,只在基底几乎未变时重发变更部分)。导出/导入的 AFlowField 成员函数声明在 FlowField.h、实现在 FlowFieldSync.cpp;本头提供的是两侧共用的「单一事实源」:流魔数、量化助手、内容哈希与纯函数差分构造器 BuildSubstrateDeltaBytes。任何 FVector(double)都不上线——世界坐标全部量化为相对 Origin 的 int32 厘米,法线量化为 int8 SNORM(×127)。

The wire-format definition header for flow-field "substrate sync": compresses an AFlowField's pathing substrate (grid cells or NavMesh tiles) into a compact quantized byte stream so every lockstep peer rebuilds identical field data. Two stream kinds are defined — the full stream (magic 'FFS1', version v4) and the delta stream (magic 'FFD1', independent version v1, a bandwidth-efficient re-send form for barely-changed substrates). The exporter/importer member functions of AFlowField are declared in FlowField.h and implemented in FlowFieldSync.cpp; this header is the single source of truth shared by both sides: stream magics, quantization helpers, the content hash, and the pure-function delta builder BuildSubstrateDeltaBytes. No FVector(double) ever crosses the wire — world coordinates are quantized to int32 centimeters relative to Origin, normals to int8 SNORM (×127).

版本宏 Version Macros

名称 Name值 Value说明 Description
FLOWFIELD_SYNC_STREAM_VERSION4u全量流版本号——格式变更时递增,双端必须一致。v3:NavMesh 载体换为原样 dtNavMesh tile blob + init 参数 + 64 浮点区域代价表。v4:头后新增目标集快照段——全端以流内目标洪泛,绝不读本地 GoalActors(晚加入导入与 actor 解析存在时序竞态;2026-09-10 实测:服务器洪泛 623 格 vs 客户端 0 格)。Full-stream version — bump on format change (both peers must match). v3: NavMesh carrier replaced by verbatim dtNavMesh tile blobs + init params + a 64-float area-cost table. v4: goal-set snapshot section added after the header — every peer floods from the stream goals, never local GoalActors (late-join imports race actor resolution; observed 2026-09-10: server flooded 623 cells vs client 0).
FLOWFIELD_DELTA_STREAM_VERSION1u差分流版本号——与全量流版本相互独立(独立格式、独立演进;全量流停留在 v4 不受影响)。Delta-stream version — independent of the full-stream version above (separate format, separate lineage; the full stream stays untouched at v4).

FFlowFieldSyncPayload USTRUCT

紧凑量化基底字节流的 Blueprint 可见载体(BlueprintType)。原始流字节(头+体)整包存放,对传输层完全不透明——传输层只搬运字节,不解析内容。

The Blueprint-visible carrier (BlueprintType) of the compact quantized substrate byte stream. It holds the raw stream bytes (header + body) as one opaque blob — the transport layer moves the bytes without parsing them.

成员变量 Members

名称 Name类型 Type默认值说明 Description
DataTArray<uint8>空原始流字节(头+体)。对传输层不透明。Raw stream bytes (header + body). Opaque to the transport layer.

FFlowFieldSubstrateSync class

AFlowField 基底同步的静态助手类(纯静态,无实例状态;实现见 FlowFieldSync.cpp)。承载:流魔数常量、坐标/法线量化助手、载荷内容哈希,以及从两条全量流构造差分流的纯函数。DeltaMagic 是 FFC 与 MassBattle 执行分支共用的单一事实源——传输层以首 u32 判别全量/差分。

Static helper class for AFlowField substrate sync (purely static, no instance state; implemented in FlowFieldSync.cpp). It carries the stream magic constants, coordinate/normal quantization helpers, the payload content hash, and the pure function that builds a delta stream from two full streams. DeltaMagic is the single source of truth shared by the FFC and MassBattle execution branches — transports dispatch full vs delta by peeking the first u32.

常量 Constants

名称 Name类型 Type值 Value说明 Description
DeltaMagicstatic constexpr uint320x31444646('FFD1')差分流魔数——传输层以首 u32 判别全量/差分(全量 = 'FFS1')。FFC 与 MassBattle 执行分支共用的单一事实源。Delta-stream magic — transports dispatch full vs delta by peeking the first u32 (full = 'FFS1'). Single source of truth for both the FFC and the MassBattle execution branch.
FullMagicstatic constexpr uint320x31534646('FFS1')全量流魔数(DeltaMagic 的伴生)。Full-stream magic (companion of DeltaMagic).

方法 Methods

QuantCm static FORCEINLINE int32 (double WorldValue, double Origin)

把一个世界坐标轴值量化为相对 Origin 的 int32 厘米:先减去 Origin,再 RoundHalfFromZero 取整,最后钳制到 ±2147483000(避开 int32 极值边界)。全量流/差分流中所有世界坐标(Origin、目标点、格子 worldZ)都经它上线。

Quantizes one world-space axis value to int32 centimeters relative to Origin: subtracts Origin, rounds with RoundHalfFromZero, then clamps to ±2147483000 (keeping clear of the int32 extremes). Every world coordinate on the wire (origin, goals, cell worldZ) goes through it.

Input
参数 Param类型 Type说明 Description
WorldValuedouble世界坐标轴值(厘米,UE 世界单位)。World-space axis value (centimeters, UE world units).
Origindouble量化基准(通常是场 Actor 位置的对应轴)。Quantization origin (typically the field actor's location on that axis).
Output

返回相对厘米值,范围 [-2147483000, 2147483000]。

Returns the relative centimeter value in [-2147483000, 2147483000].

DequantCm static FORCEINLINE double (int32 Rel, double Origin)

QuantCm 的逆运算:Origin + Rel 还原世界坐标。导入端用它把线上的相对厘米还原为世界值。

Inverse of QuantCm: Origin + Rel restores the world value. Importers use it to turn on-wire relative centimeters back into world coordinates.

Input
参数 Param类型 Type说明 Description
Relint32线上的相对厘米值。On-wire relative centimeter value.
Origindouble量化基准(与导出时一致)。Quantization origin (same one used at export).
Output

返回还原后的世界坐标值(厘米)。

Returns the restored world value (centimeters).

QuantNormalAxis static FORCEINLINE int8 (float N)

法线单轴 SNORM 量化:RoundToInt(N * 127) 后钳制到 [-127, 127],每轴一个 int8。用于网格格子法线上线。

Per-axis SNORM quantization for normals: RoundToInt(N * 127) clamped to [-127, 127], one int8 per axis. Used to put grid cell normals on the wire.

Input
参数 Param类型 Type说明 Description
Nfloat法线单轴分量(期望 |N| ≤ 1)。One normal axis component (expected |N| ≤ 1).
Output

返回 int8 量化值,范围 [-127, 127]。

Returns the int8 quantized value in [-127, 127].

DequantNormal static FORCEINLINE FVector3f (int8 X, int8 Y, int8 Z)

QuantNormalAxis 的逆运算:三分量各除以 127 得 FVector3f,再 Normalize() 归一化(消除量化误差带来的长度偏差)。

Inverse of QuantNormalAxis: divides each component by 127 into an FVector3f, then Normalize()s it (removing the length error introduced by quantization).

Input
参数 Param类型 Type说明 Description
X / Y / Zint8三个量化后的法线轴分量。The three quantized normal axis components.
Output

返回归一化后的单精度法线向量。

Returns the normalized single-precision normal vector.

ComputeContentHash static uint32 (const uint8* Data, int64 Num)

对载荷体字节计算内容哈希(实现为 FCrc::MemCrc32)。作为重发幂等键:导入端内容哈希相同的流直接跳过,同哈希的重复派发不产生任何状态变化。

Computes the content hash over the payload bytes (implemented as FCrc::MemCrc32). Serves as the idempotency key for re-delivery: importers skip streams whose content hash was already applied, so duplicate dispatches of the same hash cause no state change.

Input
参数 Param类型 Type说明 Description
Dataconst uint8*待哈希的字节起始指针。Start pointer of the bytes to hash.
Numint64字节数。Number of bytes.
Output

返回 CRC32 内容哈希。无副作用(纯读)。

Returns the CRC32 content hash. No side effects (pure read).

BuildSubstrateDeltaBytes static bool (const TArray<uint8>& BaseFull, const TArray<uint8>& NewFull, TArray<uint8>& OutDelta, bool& bOutAnyChange)

从两条全量流(均为 'FFS1')构造差分流。纯函数——只解析两条字节流,不碰任何场状态。构造失败(返回 false)的情形:任一流无法解析、模式/流版本不符、identity(Origin/Yaw)不一致、网格尺寸或 cellSize 变化、NavMesh 的 dtNavMeshParams 字节变化——此时调用方改发新全量流。构造过程:先解析两条全量流;Grid 模式对每格 9 字节打包数据做 Memcmp 两遍对比(先计数再一次分配写体),只写变更格(index + 9 字节);NavMesh 模式做 tile 级差分——两侧槽位线性归并为升序并集,逐槽对比 blob(增/换/删:dataSize=0 表示删除),并附上完整 64 浮点区域代价表与 dtNavMeshParams 尺寸守卫。目标段始终取新流的完整目标集(洪泛永远以最新目标为准)。产出的差分流携带 BaseHash = 基准流 ContentHash、NewHash = 新流 ContentHash。

Builds a delta stream from two FULL streams (both 'FFS1'). Pure function — parses the two byte streams only, no field state involved. Returns false when the delta is impossible: either stream unparseable, mode/stream-version mismatch, identity (origin/yaw) mismatch, grid dims/cellSize change, or the NavMesh dtNavMeshParams bytes changed — the caller then sends the new FULL stream instead. Build flow: parse both full streams; Grid mode memcmps each cell's 9-byte packed record in two passes (count first, single body alloc) and writes only changed cells (index + 9 bytes); NavMesh mode diffs at tile granularity — both slot lists merge linearly into a slot-ascending union, blobs compare per slot (add/replace/remove: dataSize=0 means removal), with the full 64-float area-cost table and a dtNavMeshParams size guard attached. The goal section always takes the NEW stream's full goal set (the flood always targets the latest goals). The produced delta carries BaseHash = the base stream's ContentHash and NewHash = the new stream's.

Input
参数 Param类型 Type说明 Description
BaseFullconst TArray<uint8>&基准全量流(接收端已应用的状态)。The base FULL stream (the state receivers already applied).
NewFullconst TArray<uint8>&新全量流(接收端要到达的状态)。The new FULL stream (the state receivers must reach).
OutDeltaTArray<uint8>&输出:构造出的差分流字节(仅在返回 true 且有变更时有效)。Output: the built delta stream bytes (valid only when true is returned and something changed).
bOutAnyChangebool&输出:无任何差异(无格子、无 tile、目标集相同)时为 false——调用方可整体跳过派发。仅目标变化报 true(微型仅洪泛差分仍需上线)。注意函数入口先置 true 作为 fail-loud 默认:中途 bail 绝不会被误读为「无变动」。Output: false when NOTHING differs (no cells, no tiles, identical goal set) — the caller may skip dispatching entirely. A goal-only change reports true (a tiny flood-only delta must still ship). Note the entry sets it to true as a fail-loud default: an early bail can never be misread as "no change".
Output

返回 true 表示差分构造成功(或「无变更」短路:true + 空 OutDelta + bOutAnyChange=false);返回 false 表示 delta 不可构造,调用方应改发新全量流。副作用:仅写入 OutDelta / bOutAnyChange 两个出参,不触碰场状态。变更判定:任一变更格、任一变更 tile 或目标集不同(目标比较用浮点精确比较——两套目标都经过同一 int32 厘米量化往返,精确比较安全)。

Returns true when the delta builds (or short-circuits as "no change": true + empty OutDelta + bOutAnyChange=false); returns false when a delta cannot be built and the caller should send the new FULL stream instead. Side effects: writes the two out-parameters only, never field state. Change verdict: any changed cell, any changed tile, or a differing goal set (goal compare is exact float — both sets round-tripped through the same int32-cm quantization, so exact compare is safe).

流格式 Stream Formats

两种流均为小端 FMemoryWriter 序列。以下布局直接摘自头文件注释,是导入/导出实现的权威规格。

Both streams are little-endian FMemoryWriter serializations. The layouts below are taken directly from the header comment — the authoritative spec for the importer/exporter implementations.

全量流 'FFS1' Full Stream

uint32 Magic 'FFS1' | uint32 StreamVersion | uint8 Mode(0=Grid, 1=NavMesh)| uint32 ContentHash → identity 段:int32 OriginXYZcm(actor 位置)+ int32 YawCentideg → 目标段 v4:int32 goalCount + 每目标 int32 xCm,yCm,zCm(相对 Origin;服务器导出时刻的目标快照,全端以此洪泛、绝不读本地 GoalActors)→ Grid 附加 int32 xNum, yNum, cellSizeCm。Grid 体:每格(线序 X*yNum+Y)uint8 cost | uint8 type | int32 worldZ_cm_rel | int8 nx,ny,nz(SNORM ×127),世界 X/Y 由导入端从格子坐标 + identity 反推(EnvQuery 逆映射)。NavMesh 体 v3:int32 sizeof(dtNavMeshParams) + 结构体原样字节 + float32 ×64 区域代价表(UNavArea CDO 代价——客户端没有 navmesh actor)+ int32 tileCount + 每 tile(槽位升序)int32 slot | int32 dataSize | bytes;blob 与服务器 dtCreateNavMeshData 输出逐字节一致,addTile 自有解析器重建 polys/links/detail/BVtree/offmeshCons。

uint32 Magic 'FFS1' | uint32 StreamVersion | uint8 Mode (0=Grid, 1=NavMesh) | uint32 ContentHash → identity: int32 OriginXYZcm (actor location) + int32 YawCentideg → goals v4: int32 goalCount + per goal int32 xCm,yCm,zCm (rel origin; the server's export-time goal snapshot — every peer floods from these, never local GoalActors) → Grid adds int32 xNum, yNum, cellSizeCm. Grid body: per cell (wire order X*yNum+Y) uint8 cost | uint8 type | int32 worldZ_cm_rel | int8 nx,ny,nz (SNORM ×127); world X/Y are DERIVED on import from gridCoord+identity (EnvQuery inverse). NavMesh body v3: int32 sizeof(dtNavMeshParams) + the struct bytes verbatim + a float32 ×64 area-cost table (UNavArea CDO costs — clients have no navmesh actor) + int32 tileCount + per tile (slot ascending) int32 slot | int32 dataSize | bytes; blobs are byte-identical to the server's dtCreateNavMeshData output, and addTile's own parser rebuilds polys/links/detail/BVtree/offmeshCons.

差分流 'FFD1' Delta Stream

uint32 Magic 'FFD1' | uint32 DeltaStreamVersion | uint8 Mode | uint32 NewContentHash | uint32 BaseContentHash → identity:int32 OriginXYZcm | int32 YawCentideg(必须与基准一致,由构造器校验;不一致无法差分,调用方改发全量)→ 目标段:完整目标集(与全量流同构——洪泛始终从中重跑)→ Grid 体:uint32 changedCellCount + 每变更格 uint32 index | uint8 cost | uint8 type | int32 worldZ_cm_rel | int8 nx,ny,nz;未变更格 riding 在现行基底上;网格尺寸/cellSize 在差分中不可变(构造器拒绝 → 全量重发)。NavMesh 体:int32 sizeof(dtNavMeshParams)(守卫,必须与本地一致;params 字节本身不可变,构造器拒绝 → 全量重发,故差分不携带 params 载荷)+ float32 ×64 区域代价表(全表 256B)+ uint32 changedTileCount + 每 tile(槽位升序)uint32 slot | int32 dataSize | bytes——dataSize=0 表示删除该槽位 tile;否则 blob 替换之(removeTile + addTile,Detour 自有 tile 更新机制;全端应用顺序一致 ⇒ 相同槽位/salt 历史 ⇒ 相同 PolyRefs)。仅当本地已应用版本等于 BaseContentHash 才应用,否则调用方回退全量重发。

uint32 Magic 'FFD1' | uint32 DeltaStreamVersion | uint8 Mode | uint32 NewContentHash | uint32 BaseContentHash → identity: int32 OriginXYZcm | int32 YawCentideg (must equal the base's — validated by the builder; a mismatched identity cannot be delta'd, the caller sends full instead) → goals: the FULL goal set (same shape as the full stream — the flood always re-runs from it) → Grid body: uint32 changedCellCount + per cell uint32 index | uint8 cost | uint8 type | int32 worldZ_cm_rel | int8 nx,ny,nz; unchanged cells ride on the standing substrate; grid dims/cellSize may NOT change in a delta (the builder refuses → full re-send). NavMesh body: int32 sizeof(dtNavMeshParams) (guard, must match local; the params bytes themselves CANNOT change in a delta — the builder refuses → full re-send — so no params payload rides the delta) + the float32 ×64 area-cost table (full, 256B) + uint32 changedTileCount + per tile (slot ascending) uint32 slot | int32 dataSize | bytes — dataSize=0 means REMOVE the slot's tile; otherwise the blob replaces it (removeTile + addTile, Detour's own tile-update mechanics; identical order on every peer ⇒ identical slot/salt history ⇒ identical PolyRefs). A peer applies it ONLY when its applied version equals BaseContentHash — otherwise the caller falls back to a full re-send.

管线角色 Pipeline Role

本头的量化助手与差分构造器贯穿 MassBattle 锁步基底同步链路(以下调用点均已在 1.19.20 发布树 grep 验证): 服务器导出——AMassBattleNetworkGameModeBase::SendLockstepCommandToClients_RebuildFlowField 调 AFlowField::ExportSubstrateBytes(FlowFieldSync.cpp,内部逐字段使用 QuantCm/QuantNormalAxis),随后做服务器自导入; 差分分支——同一函数以 UMassBattleNetworkSubsystem 缓存的上次全量导出为基准调 FFlowFieldSubstrateSync::BuildSubstrateDeltaBytes(MassBattleNetworkGameModeBase.cpp):无变更则整体跳过派发;Incremental 恒走差分,Auto 仅在差分体积小于「比例 × 全量」时保留,否则弃差分发全量; 客户端分发——AMassBattleNetworkGameStateBase 执行 FF.RebuildFlowField 命令时以首 u32 对比 FFlowFieldSubstrateSync::DeltaMagic 分流:差分走 AFlowField::ImportSubstrateDeltaBytes,全量走 ImportSubstrateBytes;差分被拒(基准脱节/畸形)时客户端经修复 RPC 请求全量重发; 晚加入——UMassBattleNetworkSubsystem 在快照应用中、实体恢复之前对每个 FFlowFieldSubstrateEntry 调 ImportSubstrateBytes 并写回缓存(供重放块旁路)。 导入/导出成员函数本身的文档见 FlowField.h;网络侧入口见 MassBattleNetworkGameModeBase、MassBattleNetworkGameStateBase 与 MassBattleNetworkSubsystem。

This header's quantization helpers and delta builder run through the MassBattle lockstep substrate-sync chain (all call sites below verified by grep in the 1.19.20 release tree): server export — AMassBattleNetworkGameModeBase::SendLockstepCommandToClients_RebuildFlowField calls AFlowField::ExportSubstrateBytes (FlowFieldSync.cpp, which uses QuantCm/QuantNormalAxis per field), then self-imports on the server; delta branch — the same function calls FFlowFieldSubstrateSync::BuildSubstrateDeltaBytes (MassBattleNetworkGameModeBase.cpp) against the last full export cached in UMassBattleNetworkSubsystem: a true no-change skips dispatch entirely; Incremental always rides the delta, Auto keeps it only while it is smaller than ratio × full, otherwise full re-send; client dispatch — while executing the FF.RebuildFlowField command, AMassBattleNetworkGameStateBase peeks the first u32 against FFlowFieldSubstrateSync::DeltaMagic: deltas go to AFlowField::ImportSubstrateDeltaBytes, fulls to ImportSubstrateBytes; a rejected delta (base mismatch / malformed) makes the client request a full re-send through the repair RPC; late join — UMassBattleNetworkSubsystem imports each FFlowFieldSubstrateEntry via ImportSubstrateBytes during snapshot application, BEFORE entity restore, and re-caches it (for the replay block-bypass). The importer/exporter member functions themselves are documented under FlowField.h; the network-side entry points under MassBattleNetworkGameModeBase, MassBattleNetworkGameStateBase and MassBattleNetworkSubsystem.

补充说明 Notes

版本谱系:差分流的「版本号」即全量流的内容哈希——delta 声明其应用基准(BaseHash)与到达版本(NewHash),两种格式的版本号可直接互比。FFlowFieldSyncPayload 是面向 Blueprint 的字节载体;当前 C++ 锁步链路中,流字节直接经命令 BytePayload 与快照 FFlowFieldSubstrateEntry 传输。FlowFieldSync.cpp 另设健全上限以防损坏/恶意流:格子总数 ≤ 64M、tile 数 ≤ 65536、单 tile ≤ 64MB。

Version lineage: the delta stream's "version" IS the full stream's content hash — a delta names the base it applies on (BaseHash) and the hash the substrate reaches (NewHash), so versions stay comparable across both formats. FFlowFieldSyncPayload is the Blueprint-facing byte carrier; in the current C++ lockstep chain the stream bytes ride commands (BytePayload) and snapshot entries (FFlowFieldSubstrateEntry) directly. FlowFieldSync.cpp additionally enforces sanity caps against corrupt/hostile streams: ≤ 64M total cells, ≤ 65536 tiles, ≤ 64MB per tile.