MassBattle Docsv1.19.20

MassBattleDesyncAutoDebugger.h

struct(全静态)

Source/MassBattle/Public/Debug/MassBattleDesyncAutoDebugger.h

概述 Overview

锁步失步自动取证工具(2026-09-12 引入),替代低效的「插桩 → 运行 → 缩小范围 → 再插桩」循环,改为「全量转储再比对」流程:① 首个发散 tick 当帧,模拟先暂停(Net->bSimulationPaused),赶在 resync 机制覆盖现场之前冻结证据;② 双端各自把完整世界状态转储到 Saved/MassBattleDesync/dump_<role>_t<tick>.mbdmp;③ 用 Tools/desync_dump_diff.py 离线逐字段比对两份转储,打印所有差异值;④ [实验性] 种子化输入模糊器从 INI 种子复现一段确定性的「人类」会话(MoveTo / ChaseAttack / 编队移动随机批次),使发现的任何发散都可逐位复现。实现为全静态成员的普通 C++ struct——无实例、无反射类型,配置经 UMassBattleSubsystem 的 Config 属性一次性载入。

Lockstep desync auto-forensics tool (introduced 2026-09-12). It replaces the slow probe → run → narrow → probe instrumentation loop with a dump-everything-then-diff flow: ① on the FIRST divergent tick the sim pauses first (Net->bSimulationPaused), freezing the evidence before resync machinery can overwrite it; ② both peers dump FULL world state to Saved/MassBattleDesync/dump_<role>_t<tick>.mbdmp; ③ Tools/desync_dump_diff.py diffs the two dumps offline and prints every differing value; ④ [experimental] a seeded input fuzzer replays a deterministic "human" session (MoveTo / ChaseAttack / formation-move batches at random intervals) from an INI seed so any found divergence is reproducible bit-for-bit. Implemented as a plain C++ struct with all-static members — no instances, no reflected type; config is loaded once from UMassBattleSubsystem Config properties.

公开方法 Public Methods

OnDivergence static void

发散回调,由双端各自的失步检测点调用(客户端的延迟校验 / 服务端的回执哈希失配,见管线角色)。流程:① LoadConfigOnce 首次调用时从 UMassBattleSubsystem 读取 Config 属性并打日志;② 按世界名去重(函数内 static TSet<FName>)——PIE 双 World 共进程时,进程级静态闸门会挡掉另一端的转储,按世界名键控既保留「每世界每会话只转储一次」的防刷闸,又兼容 PIE(PIE 世界名按实例唯一);③ 置 Net->bSimulationPaused = true 冻结现场;④ 调 DumpWorldState 全量转储;⑤ 打 Error 日志(含转储路径)。任一指针无效或该世界已转储过则直接返回,不暂停。

Divergence callback, invoked from both peers' divergence-detection sites (the client-side deferred check / the server-side acked-hash mismatch — see Pipeline Role). Flow: ① LoadConfigOnce reads the Config properties from UMassBattleSubsystem on first call and logs them; ② deduplicates per world name (a function-local static TSet<FName>) — with two PIE worlds in one editor process a process-static gate would block the other peer's dump; keying by world name keeps the "one dump per session per world" spam guard while remaining PIE-safe (PIE world names are unique per instance); ③ sets Net->bSimulationPaused = true to freeze the scene; ④ dumps full state via DumpWorldState; ⑤ logs an Error with the dump path. Returns immediately (no pause) if either pointer is invalid or this world was already dumped.

Input
参数 Param类型 Type说明 Description
MBUMassBattleSubsystem*MassBattle 子系统,提供实体枚举与 Mass API 访问;无效时本函数直接返回。MassBattle subsystem providing entity enumeration and Mass API access; the function returns early when invalid.
NetUMassBattleNetworkSubsystem*网络子系统,其 bSimulationPaused 被置 true 以冻结模拟。Network subsystem whose bSimulationPaused is set true to freeze the simulation.
Tickint32检测到发散的逻辑 tick 号(写入转储文件名与日志)。Logic tick at which divergence was detected (written into the dump filename and logs).
Roleconst FString&端角色字符串("client" / "server"),用于双端转储文件命名区分。Peer role string ("client" / "server"), distinguishing the two dump files.
Output

无返回值。副作用:向世界名集合登记、置 bSimulationPaused = true(模拟从此暂停)、写出一份 .mbdmp 全量转储文件、打一条 Error 日志。开关 bEnabled 为 false 时无任何副作用。

No return value. Side effects: registers the world name, sets bSimulationPaused = true (the sim stays paused), writes one full .mbdmp dump file, logs one Error. With bEnabled false there are no side effects at all.

DumpWorldState static FString

完整世界转储。文件头 MBDMP v1 tick=… role=…;实体来自 MB->DiagUIDEnumerateForDump() 并按 UID 升序排序;每个实体先输出 FEntityFlagFragment 旗标(低/高 32 位十六进制),再对 14 个热 fragment 做反射全量转储:FMoving、FNavigating、FLocating、FRotating、FTracing、FBehaving、FAttacking、FDeterminism、FMove、FChase、FCollider、FAttack、FAvoiding。属性序列化规则:对象/弱引用/软引用/委托/接口/Class 属性一律剥离;浮点以位模式十六进制(%016llx)输出以暴露最低位差异;整数/枚举输出十进制;名称/字符串原样;数组先记 LEN(真实长度)再最多转储 32 个元素;嵌套结构递归(深度上限 6);实体句柄类(EntityHandle / MassEntityHandle / FlowFieldLayerHandle)跳过。最后 TASKS 段反射转储每个活跃的 MoveTo 任务对象(MB->GetActiveMoveToTasks())的全部属性。整串以 UTF-8(无 BOM)写盘。

Full world dump. Header MBDMP v1 tick=… role=…; entities come from MB->DiagUIDEnumerateForDump() sorted ascending by UID; each entity first emits its FEntityFlagFragment flags (low/high 32-bit hex), then a full reflection dump of 14 hot fragments: FMoving, FNavigating, FLocating, FRotating, FTracing, FBehaving, FAttacking, FDeterminism, FMove, FChase, FCollider, FAttack, FAvoiding. Property serialization rules: object/weak/soft/delegate/interface/class properties are stripped; floats print as bit-pattern hex (%016llx) to expose last-bit differences; ints/enums print decimal; names/strings verbatim; arrays record LEN (true length) then dump up to 32 elements; nested structs recurse (depth cap 6); handle-typed structs (EntityHandle / MassEntityHandle / FlowFieldLayerHandle) are skipped. A final TASKS section reflects every active MoveTo task object (MB->GetActiveMoveToTasks()). The whole string is written UTF-8 without BOM.

Input
参数 Param类型 Type说明 Description
MBUMassBattleSubsystem*MassBattle 子系统,提供实体枚举、Mass API(GetMassAPISubsystem)与活跃任务列表。MassBattle subsystem providing entity enumeration, the Mass API (GetMassAPISubsystem) and the active-task list.
Tickint32转储对应的逻辑 tick 号(写入文件名与文件头)。Logic tick the dump belongs to (filename and header).
Roleconst FString&端角色字符串,拼入文件名 dump_<role>_t<tick>.mbdmp。Peer role string used in the filename dump_<role>_t<tick>.mbdmp.
Output

返回输出文件路径(Saved/MassBattleDesync/ 下,目录按需递归创建)。副作用:写盘一份转储文件;无效句柄实体以 <INVALID-HANDLE> 标记;不修改任何模拟状态。

Returns the output file path (under Saved/MassBattleDesync/, directory created recursively as needed). Side effect: one dump file written to disk; entities with invalid handles are marked <INVALID-HANDLE>; no simulation state is modified.

FuzzerRand static uint32

模糊器的确定性 LCG(线性同余发生器):FuzzerRngState = FuzzerRngState * 1664525u + 1013904223u,返回状态右移 8 位。同种子必得同序列——这是「任何发现的发散可逐位复现」的基础。

The fuzzer's deterministic LCG (linear congruential generator): FuzzerRngState = FuzzerRngState * 1664525u + 1013904223u, returning the state shifted right by 8. Same seed ⇒ same sequence — the foundation of "any found divergence is reproducible bit-for-bit".

Input

无参数。

No parameters.

Output

返回一个伪随机 32 位值(高 24 位有效)。副作用:推进 FuzzerRngState。

Returns one pseudo-random 32-bit value (top 24 bits). Side effect: advances FuzzerRngState.

TickFuzzer static void

模糊器逐 tick 推进,仅服务器端调用(命令经正常锁步通道复制到客户端)。首次调用为初始化:置 bFuzzerInitialized、以 FuzzerSeed | 1 播种 LCG、下次触发定在当前 tick + 60、打布防日志。到达触发 tick 时:① 在 [Min, Max] 区间抽下次触发间隔;② 经 DiagUIDEnumerateForDump 枚举实体(不足 4 个则本 tick 放弃);③ 在 [MinBatch, MaxBatch] 抽批量、以 FuzzerRand 无放回抽取有效实体;④ 抽动作(0=MoveTo / 1=Chase / 2=Formation)与是否可打断;⑤ 构造 FMassBattleNetCommand(CommandTag = "MB.FuzzTask",IssuedTick = T,ExecuteTick = T + 2,IntPayload = [action, canInterrupt, targetUid, n, uid1..uidN],FloatPayload = [X, Y]——MoveTo/Formation 为 [-10000, 10000) 随机坐标、Chase 为 0),经 AMassBattleNetworkGameModeBase::EnqueueAuthorityCommandForFuzz 入队。走锁步命令通道的原因:任务工厂仅可在 ExecuteNetCommand 内合法激活。

Per-tick fuzzer advance, called on the SERVER only (commands replicate to clients through the normal lockstep path). First call initializes: sets bFuzzerInitialized, seeds the LCG with FuzzerSeed | 1, schedules the first fire at current tick + 60, and logs the arming. On a fire tick: ① draws the next interval within [Min, Max]; ② enumerates entities via DiagUIDEnumerateForDump (fewer than 4 ⇒ skip this tick); ③ draws a batch size within [MinBatch, MaxBatch] and picks valid entities without replacement via FuzzerRand; ④ draws the action (0=MoveTo / 1=Chase / 2=Formation) and the interrupt flag; ⑤ builds an FMassBattleNetCommand (CommandTag = "MB.FuzzTask", IssuedTick = T, ExecuteTick = T + 2, IntPayload = [action, canInterrupt, targetUid, n, uid1..uidN], FloatPayload = [X, Y] — random coordinates in [-10000, 10000) for MoveTo/Formation, 0 for Chase) and enqueues it via AMassBattleNetworkGameModeBase::EnqueueAuthorityCommandForFuzz. The lockstep-command routing exists because task factories may only activate legally inside ExecuteNetCommand.

Input
参数 Param类型 Type说明 Description
MBUMassBattleSubsystem*MassBattle 子系统,提供 tick 计数(GetTickCount)、实体枚举与 UID 反查;无效时直接返回。MassBattle subsystem providing the tick count (GetTickCount), entity enumeration and UID lookup; returns early when invalid.
Output

无返回值。副作用:bFuzzerEnabled 关闭时无操作;开启时推进模糊器状态,可能向权威命令队列压入一条 MB.FuzzTask 锁步命令并打一条 Warning 日志。多次 LCG 消耗(状态推进)。

No return value. Side effects: no-op when bFuzzerEnabled is off; otherwise advances the fuzzer state and may push one MB.FuzzTask lockstep command into the authority queue plus a Warning log. Consumes several LCG draws (state advances).

ExecuteFuzzCommand static void

在锁步门内解码并激活一条 "MB.FuzzTask" 命令——由 ExecuteNetCommand 在双端调用,保证双端同构执行(任务工厂因此可合法激活)。先做载荷长度校验(IntPayload.Num() >= 4 + N,不足直接返回);经 UMassBattleSubsystem::GetPtr 取子系统,按 UID 反查并过滤有效实体(空批次返回)。分发:action 1 → UMassBattleBPTaskAgentsChaseAttack::AgentsChaseAttack(目标为 TargetUid 实体);action 0 → 单点 AgentsMoveTo(目标 (X, Y, 0));action 2 → 编队网格 AgentsMoveTo(列数 = √N,间距 150cm,自 (X, Y) 起排布)。

Decodes and activates one "MB.FuzzTask" command inside the lockstep gate — called from ExecuteNetCommand on both peers so both execute identically (task factories thereby activate legally). Validates payload length first (IntPayload.Num() >= 4 + N, early return otherwise); resolves the subsystem via UMassBattleSubsystem::GetPtr, maps UIDs back to valid entities (empty pick returns). Dispatch: action 1 → UMassBattleBPTaskAgentsChaseAttack::AgentsChaseAttack (target = the TargetUid entity); action 0 → single-point AgentsMoveTo (goal (X, Y, 0)); action 2 → formation-grid AgentsMoveTo (columns = √N, 150 cm spacing from (X, Y)).

Input
参数 Param类型 Type说明 Description
WorldContextUObject*世界上下文,用于解析 UMassBattleSubsystem 并传给任务工厂。World context used to resolve UMassBattleSubsystem and passed to the task factories.
IntPayloadconst TArray<int32>&[action(0=MoveTo,1=Chase,2=Formation), canInterrupt, targetUid, n, uid1..uidN]。[action (0=MoveTo, 1=Chase, 2=Formation), canInterrupt, targetUid, n, uid1..uidN].
FloatPayloadconst TArray<float>&[X, Y] 目标坐标(厘米);Chase 动作不使用。[X, Y] goal coordinates (cm); unused by the Chase action.
Output

无返回值。副作用:激活一个 BP 任务(MoveTo 或 ChaseAttack),改写被选中实体的行为状态;载荷非法或批次为空时无副作用。

No return value. Side effect: activates one BP task (MoveTo or ChaseAttack), redirecting the selected entities' behavior; no side effects on malformed payload or empty pick.

静态成员变量 Members

名称 Name类型 Type默认值说明 Description
bEnabledboolfalse总开关(发散时转储+暂停)。由 Config 属性 bDesyncAutoDump 首次载入。Master switch (dump + pause on divergence). Loaded once from the Config property bDesyncAutoDump.
bDumpedThisSessionboolfalse[已被取代 2026-09-12] 转储闸门改为按世界键控(OnDivergence 内的 TSet)——进程级静态闸门曾在 PIE 中挡掉第二个 peer 的转储。成员保留但不再作为闸门。[superseded 2026-09-12] the dump gate is now per-WORLD (the TSet inside OnDivergence) — a process-static gate blocked the 2nd peer's dump in PIE. The member remains but no longer gates anything.
bFuzzerEnabledboolfalse模糊器开关,来自 Config 属性 bDesyncAutoFuzzer。Fuzzer switch, from the Config property bDesyncAutoFuzzer.
FuzzerSeedint3220260912模糊器种子(DesyncFuzzerSeed);同种子同会话,发散可复现。Fuzzer seed (DesyncFuzzerSeed); same seed ⇒ same session, divergences reproducible.
FuzzerMinInterval / FuzzerMaxIntervalint3240 / 160两次任务批次之间的随机 tick 间隔上下界(DesyncFuzzerMinInterval/MaxInterval)。Random tick-interval bounds between task batches (DesyncFuzzerMinInterval/MaxInterval).
FuzzerMinBatch / FuzzerMaxBatchint328 / 120每批次随机实体数上下界(DesyncFuzzerMinBatch/MaxBatch)。Random per-batch entity-count bounds (DesyncFuzzerMinBatch/MaxBatch).
FuzzerRngStateuint321LCG 当前状态;初始化时被覆写为 Seed | 1。Current LCG state; overwritten with Seed | 1 at initialization.
FuzzerNextFireTickint32-1下次触发模糊命令的 tick(初始化时 = 当前 tick + 60)。Next tick to fire a fuzz command (= current tick + 60 at initialization).
bFuzzerInitializedboolfalse模糊器是否已初始化(首次 TickFuzzer 完成播种)。Whether the fuzzer has initialized (first TickFuzzer seeds it).

管线角色 Pipeline Role

本类型不在模拟管线中运行,而是挂在锁步失步检测点与服务器 tick 收尾上(以下调用点均在发布树 grep 验证):AMassBattleNetworkGameStateBase 的延迟失步校验触发 [DIVERGE_TICK] 时以角色 "client" 调用 OnDivergence;AMassBattleNetworkGameModeBase 的回执哈希失配在确认窗开始时(streak 起点,服务端早触发——客户端暂停后不再回执,确认窗永不会闭合)与确认满时各以 "server" 调用一次 OnDivergence。UMassBattleNetworkProcessor 的 Net_Fold(逐 tick 网络收尾)在非客户端(NetMode ≠ NM_Client)每 tick 调用 TickFuzzer。模糊命令经权威命令队列复制,最终由 AMassBattleNetworkGameModeBase::ExecuteNetCommand 在锁步门内按 CommandTag == "MB.FuzzTask" 分发给 ExecuteFuzzCommand(双端各执行一次,同构)。配置项位于 DefaultMassBattle.ini 的 [/Script/MassBattle.MassBattleSubsystem] 段(bDesyncAutoDump / bDesyncAutoFuzzer / DesyncFuzzerSeed / 间隔与批量),由 LoadConfigOnce 一次性读入。

This type does not run inside the sim pipeline; it hooks the lockstep divergence-detection sites and the server tick fold (all call sites below grep-verified in the release tree): the client-side deferred desync check in AMassBattleNetworkGameStateBase invokes OnDivergence with role "client" when [DIVERGE_TICK] fires; the acked-hash mismatch in AMassBattleNetworkGameModeBase invokes OnDivergence with "server" both at the start of the confirm window (streak start — the early server dump; a paused client stops acking so the window would never close) and when the streak confirms. The Net_Fold of UMassBattleNetworkProcessor (per-tick network close-out) calls TickFuzzer every tick on non-clients (NetMode ≠ NM_Client). Fuzz commands replicate through the authority command queue and are finally dispatched by AMassBattleNetworkGameModeBase::ExecuteNetCommand inside the lockstep gate on CommandTag == "MB.FuzzTask" into ExecuteFuzzCommand (executed once per peer, identically). Config lives in DefaultMassBattle.ini under [/Script/MassBattle.MassBattleSubsystem] (bDesyncAutoDump / bDesyncAutoFuzzer / DesyncFuzzerSeed / interval & batch bounds), read once by LoadConfigOnce.

补充说明 Notes

离线比对脚本 Tools/desync_dump_diff.py 随发布树提供。头文件注释中的配置段名写的是 [/Script/MassBattle.MassBattleDesyncAutoDebugger],实际生效的是 [/Script/MassBattle.MassBattleSubsystem](属性宿主是子系统,见 Subsystems/MassBattleSubsystem.h)。转储的浮点十六进制位模式专为暴露末位差异而设;数组元素上限 32、真实长度单列,保证大数组差异可见又不失控。模糊器为实验性功能。

The offline diff script Tools/desync_dump_diff.py ships with the release tree. The header comment names the config section [/Script/MassBattle.MassBattleDesyncAutoDebugger]; the section that actually takes effect is [/Script/MassBattle.MassBattleSubsystem] (the properties live on the subsystem — see Subsystems/MassBattleSubsystem.h). Floats dump as bit-pattern hex specifically to expose last-bit differences; arrays cap at 32 elements with the true length recorded separately, keeping large-array diffs visible yet bounded. The fuzzer is experimental.