MassBattle Docsv1.19.20

UMassBattlePhysicsProcessor

UCLASS Processor

Source/MassBattle/Public/Processors/MassBattlePhysicsProcessor.h

概述 Overview

独立多次子步 PBD 物理引擎。命名「Physics」而非「AgentPhysics」:body 概念今天为 agent 形状但不绑定——非 agent 物理体日后可进同一槽位数组。帧生命周期为三段(GridUpdate+Move 两段式模型):REGISTER(计数波 → 票号组装波把 PreData/Selves 直写最终槽位)把每个 agent 打包为一条 64 字节网格记录,持久化网格按 AgentGrid 式填充(自旋锁追加 → 串行 flush → 逐格 UID 打包成员排序——槽位序依赖调度,确定性住在格子里),障碍引用每帧只缓存一次,fragment 读只发生在此段;SUBSTEP×N(只遍历槽位数组——无 fragment、无 TMap)每 body 只读 Pre(自身+邻居,各一条缓存行)、只写自身 Current(Pre/Current 双版本),子步间的网格更新把 Current 提升为 Pre(零拷贝 swap),cell 成员身份保持;WRITEBACK(并行 chunk)把最终 Pre 一次性应用进 Locating.Location、旧速度反馈公式、RotationalResistance 与标志发布,fragment 写只在此段。每子步刚度 k′ = 1−(1−k)^(1/N) 保持 PositionRelaxation_Agent 的帧等效语义,场景面阻尼 0.95^(1/N) 同理。仅当 MassBattle.Physics.Substeps > 1 且非定点模式时启用——默认 1 时 MoveProcessor 内联单遍 PBD 位级不变、本处理器早退。

Standalone multi-substep PBD physics engine. Named "Physics" (not "AgentPhysics"): the body concept is agent-shaped today but not agent-bound — non-agent bodies can join the same slot arrays later. Frame lifecycle is three passes (the GridUpdate+Move two-pass model): REGISTER (count wave → ticket-claimed assembly wave writing PreData/Selves DIRECTLY at final slots) packs every agent into ONE 64-byte grid record; the persistent grid fills AgentGrid-style (spinlock appends → serial flush → per-cell UID-packed member sort — slot order is scheduling-dependent, determinism lives in the cells); obstacle references are cached ONCE per frame; fragment reads happen ONLY in this pass. SUBSTEP×N (parallel over SLOT ARRAYS — no fragments, no TMap): every body READS ONLY Pre (self + neighbors — one cache line each) and WRITES ONLY its own Current record (Pre/Current double version); between substeps the GRID UPDATE promotes Current → Pre (zero-copy swap) while cell membership stays. WRITEBACK (parallel chunks) applies the final Pre → Locating.Location once, the legacy velocity-feedback formula, RotationalResistance, and the flag publications; fragment writes happen ONLY here. Per-substep stiffness k′ = 1−(1−k)^(1/N) keeps PositionRelaxation_Agent's frame-equivalent meaning; scene-plane damping 0.95^(1/N) likewise. Armed only when MassBattle.Physics.Substeps > 1 and not in fixed-point mode — at the default 1, MoveProcessor's inline single-pass PBD stays bit-identical and this processor early-outs.

诊断:MassBattle.Physics.DebugResidual 1 输出逐子步 agent 对总重叠——跨子步单调衰减的序列即收敛证明(注意:默认 PositionRelaxation_Agent=1.0 时每子步都是满修正,N=2 可能已与 N=8 视觉无异——请与 N=1 对比并读残差序列)。与旧路径相比的已知一帧滞后(结构性——本阶段在 Move 之后):yaw/tilt 下一 tick 才见 PBD 速度反馈;Section 9 压力门控读上一 tick 的 agent 接触;RotationalResistance 存活到下一 tick 的 yaw(独立模式下 Move 侧每帧重置被门控关闭)。确定性:槽数组 UID 排序、cell 切片槽位排序、每 body 只写自身 Current、退化法线兜底 RNG 由(UniqueID, 子步, 接触计数)派生——不消耗共享随机流。

Diagnostics: MassBattle.Physics.DebugResidual 1 logs the per-substep total agent-pair overlap — a monotonically decaying series across substeps is the convergence proof (note: with the default PositionRelaxation_Agent=1.0 every substep applies full corrections, so N=2 can already look identical to N=8 — compare against N=1 and read the residual series). Known one-frame lags vs legacy (structural — the stage runs after Move): yaw/tilt see the PBD velocity feedback next tick; the Section 9 pressure gate reads last tick's agent contact; RotationalResistance survives until next tick's yaw (the Move-side per-frame reset is gated off in standalone mode). Determinism: slot arrays UID-sorted, cell slices slot-sorted, each body writes only its own Current, and the degenerate-normal fallback RNG is derived from (UniqueID, substep, contact count) — no shared stream consumption.

继承 Inheritance

UMassProcessor → UMassBattlePhysicsProcessor

FSolvePassCtx 逐子步求解上下文

逐子步求解上下文——相位运行器、兜底派发器与 SolveOneBody 共用(定义于本头文件,非反射 struct)。CVar 值在游戏线程读取后经它传入 worker(避免 worker 读 CVar 触发 ensure)。

Per-substep solve context shared by the phased runner, the fallback dispatcher and SolveOneBody (defined in this header, non-reflected struct). CVar values are read on the game thread and passed to workers through it (worker-side CVar reads trip an ensure).

名称 Name类型 Type默认值说明 Description
DeltaTimefloat0.f帧步长(秒,取 GetCalculatedStepTime())。Frame delta time (seconds, from GetCalculatedStepTime()).
SceneDampfloat1.f场景面阻尼 0.95^(1/N)——N 次应用收敛到旧单次 0.95。Scene-plane damping 0.95^(1/N) — N applications converge to the legacy single 0.95.
Substeps / Substepint321 / 0子步总数与当前子步索引。Total substeps and the current substep index.
bGatherbooltrue采集趟(走网格重建接触缓存)vs 消费趟(只读缓存)。Gather substep (cell-walk + pair-cache rebuild) vs cached consume.
bAVBDboolfalseAVBD 求解开关(MassBattle.Physics.Solver == 1;增广顶点块下降,见 references/avbd-solver-design.md)。AVBD solve toggle (MassBattle.Physics.Solver == 1; Augmented Vertex Block Descent — see references/avbd-solver-design.md).
OverRelaxfloat1.fSOR 过松弛系数 ω(游戏线程读取)。SOR over-relaxation factor ω (read on the game thread).
bDebugStackingboolfalse堆叠诊断探针开关。Stacking-diagnostic probes toggle.
MaxCumDispPhasefloat0.f遍历余量输入(已提升的 MaxCumDisp 值)。Walk-margin input (the promoted MaxCumDisp value).
MaxDispShared / MaxCorrSharedstd::atomic<float>*nullptr每子步累计漂移 / 修正幅的 CAS-取最大目标。Per-substep cumulative-drift / correction-magnitude CAS-max targets.

公开方法 Public Methods

UMassBattlePhysicsProcessor 构造函数

构造函数。设置 bAutoRegisterWithProcessingPhases = false(手动派发——由 UMassBattleSubsystem 注册进 SimStages[4] 尾部)、bRequiresGameThreadExecution = true、ExecutionFlags = Standalone | Server | Client。

Constructor. Sets bAutoRegisterWithProcessingPhases = false (manually dispatched — registered at the SimStages[4] tail by UMassBattleSubsystem), bRequiresGameThreadExecution = true, and ExecutionFlags = Standalone | Server | Client.

Input

无参数。

No parameters.

Output

无返回值。副作用:注册为手动派发、游戏线程执行的处理器。

No return value. Side effect: registers as a manually-dispatched, game-thread-executing processor.

PushHandoff void

Move 尾部的逐 agent 交接推送——实体索引直写:每实体一个不相交槽位,零同步;记录落入 HandoffRecByEntityIndex[Entity.Index] 并打上序列号与本帧代戳。超容(波中生成超出预定容)走 TLS 溢出 gatherer,由 Execute 注册开头排空(罕见路径)。由两个 Move 处理器调用(见管线角色)。

Move-tail per-agent handoff push — a direct entity-indexed write: one disjoint slot per entity, no synchronization; the record lands in HandoffRecByEntityIndex[Entity.Index] stamped with the serial number and this frame's generation. A capacity miss (mid-wave spawn past the pre-sized bound) goes to the TLS overflow gatherer, drained at register start by Execute (rare). Called by both Move processors (see Pipeline Role).

Input
参数 Param类型 Type说明 Description
Entityconst FMassEntityHandle目标实体(索引决定写入槽位;序列号用于陈旧句柄校验)。Target entity (its index picks the slot; the serial number validates stale handles).
RecFPhysicsHandoffRecord&&交接记录(右值——直写时移入,溢出时同样移入 TLS 缓冲)。The handoff record (rvalue — moved into the direct slot, or into the TLS buffer on overflow).
Output

无返回值。副作用:写交接运输数组(或溢出 gatherer),记录代戳与序列号;可在 Move 的并行尾部安全调用(不相交写)。

No return value. Side effects: writes the handoff transport array (or the overflow gatherer), stamping generation and serial; safe from Move's parallel tail (disjoint writes).

ConfigureQueries virtual void

初始化单个 EntityQuery:门限 All<FAgentTag>;All<FScaling, FCollider, FMove, FDeath, FDying, FDeterminism, FPhysicsShared> 与 All<FLocating, FMoving, FRotating, FEntityFlagFragment>。fragment 范围与 Move 处理器相同、减去纯转向/导航部分——引擎读移动状态、绝不产生它;FLocating/FMoving 为 MARW(写回),其余 MARO。

Initializes the single EntityQuery: gate All<FAgentTag>; All<FScaling, FCollider, FMove, FDeath, FDying, FDeterminism, FPhysicsShared> and All<FLocating, FMoving, FRotating, FEntityFlagFragment>. Same fragment universe as the Move processors minus the pure steering/navigation fragments — the engine reads movement state, never produces it; FLocating/FMoving register MARW (writeback), everything else MARO.

Input
参数 Param类型 Type说明 Description
EntityManagerconst TSharedRef<FMassEntityManager>Mass 实体管理器,查询初始化目标。Mass entity manager; query initialization target.
Output

无返回值。副作用:查询注册进处理器。

No return value. Side effect: the query registers with the processor.

Execute virtual void

执行入口,前置门:子系统可解析、PhysGrid(每次 Execute 缓存的持久化网格子系统)可解析、MB.bStandalonePhysicsActive(= Substeps > 1 且非定点)开启、MB.IsSubFrameScheduled(ESubFrame::Subtick2)(完整移动阶段已在本阶段链执行完)、DeltaTime > KINDA_SMALL_NUMBER——任一不满足即早退。随后三段:① 注册段——排空交接 TLS 溢出(按最大实体索引扩容并打本帧代戳);A 波计数合格 body(已激活、未待销毁、GridRegisterMode ≠ None;同时 fetch_max 实体索引上界为簿记定容),据此 SetNum 三条槽数组并清零票号;BeginFrame 复位网格;B 波票号组装——每 chunk 一次 fetch_add 认领连续下标块,逐 agent 从 fragment 打包热记录(位置/朝向/PBD 半径/轴半高/质量/角速度/预算界/旗标位/碰撞组/多格判定)与冷自身状态(几何、门控、每子步刚度 k′、摩擦/转向配置、交接输入按代戳+序列号读取)、槽位一次性落位、打写回查找戳、内联注册格子(单格直注;巨型按圆盘脚印 AABB 多格注册)并就地构建障碍缓存;网格收尾 FlushPending → FinishFrame(逐格 UID 打包成员排序)→ ShrinkGrids。② 求解段——逐 sweep(= 子步)一次 ParallelFor(Jacobi:读 Pre、写 Current、swap 提升);首 sweep 或全局累计漂移超 PairRefreshMargin 时为采集趟(重建接触对缓存),否则消费趟;每 sweep 结束提升 MaxCumDisp/FrameMaxCorrection;可选残差记录与日志。③ 写回段——并行 chunk,按实体索引直接查找(序列号校验)定位槽位,应用最终位置、速度反馈、RotationalResistance 与标志发布;帧末排空溢出 gatherer 并递增代戳计数器(单独区分帧,无需 memset)。

Execution entry, front gates: subsystem resolvable, PhysGrid (the persistent grid subsystem cached per Execute) resolvable, MB.bStandalonePhysicsActive (= Substeps > 1 and not fixed-point) on, MB.IsSubFrameScheduled(ESubFrame::Subtick2) (the FULL move phase already ran in this stage chain), and DeltaTime > KINDA_SMALL_NUMBER — any miss early-outs. Then three passes: ① REGISTER — drains the handoff TLS overflow (growing by max entity index and stamping this frame's generation); wave A counts eligible bodies (activated, not pending-destroy, GridRegisterMode ≠ None; also fetch_max of the entity-index bound to pre-size bookkeeping), then SetNums the three slot arrays and zeroes the ticket; BeginFrame resets the grid; wave B assembles by ticket — each chunk claims a contiguous index block with one fetch_add, packing per agent the hot record (position/up/PBD radius/shaft half/mass/angular velocity/bound/flag bits/collision group/multi-cell test) and the cold self state (geometry, gates, per-substep stiffness k′, friction/turn config, handoff inputs read via generation + serial), landing the slot in ONE write, stamping the writeback lookup, registering cells INLINE (single cell directly; giants via a disc-footprint AABB over multiple cells) and building the obstacle cache in place; grid close does FlushPending → FinishFrame (per-cell UID-packed member sort) → ShrinkGrids. ② SOLVE — one ParallelFor per sweep (= substep; Jacobi: read Pre, write Current, swap promotes); sweep 0 or global cumulative drift beyond PairRefreshMargin is a gather substep (contact-pair cache rebuild), otherwise a consume substep; each sweep end promotes MaxCumDisp/FrameMaxCorrection; optional residual recording and logging. ③ WRITEBACK — parallel chunks resolve slots via the direct entity-index lookup (serial-validated), applying final position, velocity feedback, RotationalResistance and flag publications; frame close drains the overflow gatherer and bumps the generation counter (the counter alone separates frames — no memset).

Input
参数 Param类型 Type说明 Description
EntityManagerFMassEntityManager&实体管理器。Entity manager.
ContextFMassExecutionContext&执行上下文。Execution context.
Output

无返回值。副作用:重建槽位数组与物理网格、推进 N 个子步的碰撞求解、写回 FLocating.Location / FMoving(速度、RotationalResistance、三个缓存标志)——这是本引擎唯一的 fragment 写。

No return value. Side effects: rebuilds the slot arrays and the physics grid, advances N substeps of collision solving, and writes back FLocating.Location / FMoving (velocity, RotationalResistance, three cached flags) — the engine's only fragment writes.

私有方法 Private Methods

SolveOneBody void

单 body 的完整子步:场景面(硬约束;穿透即沿法线推出 × SceneDamp,Z 关闭时清 Z 分量;缺 GroundPlanes——地面采样留在 Move)→ agent 对(接触对缓存:采集趟走 2D cell,按余量界准入候选;消费趟只迭代缓存,帧常量过滤已在准入时完成——精确包围球复测、双竖直胶囊快路径(2D 圆距 + Z 区间,约 10 次浮点)vs 通用 3D 线段最近点(约 60 次)、退化重合对走 (UniqueID, 子步) 派生 RNG;支撑/滑移判定(StackStrength 三档,纯 Z 接触沿邻居胶囊轴垂直方向滚落);质量加权 + 优势/不可推/触障门控求自身份额;扫掠弧占位经 MassBattleFastTrig::Asin 累计;AVBD 分支构建局部增广系统行(λ/ρ 镜像)或旧分支求和累积)→ 缓存障碍(胶囊/圆柱、线段墙、盒 OBB 各自的 float 路径接触几何)→ 写出(求和(过松弛)→ 钳制到本趟最深单对重叠 → 每子步刚度 k′;障碍刚度恒 1.0(硬墙);Z 门控、接地钳制(可上推不可下压)、库仑静摩擦横向/纵向帧预算;AVBD 为锚 + 活跃行的 3×3 SPD Newton 步 + 稳定钳制 + sweep 末对偶更新(λ 锚项封顶、ρ 递增));整条 64B 记录拷贝进 Current;累计漂移 CAS-取最大回传。

One body's full substep: scene planes (hard constraints; penetration pushes out along the normal × SceneDamp, Z cleared when Z is disabled; no GroundPlanes — the ground trace lives in Move) → agent pairs (contact-pair cache: gather substeps walk 2D cells admitting candidates margin-bound; consume substeps iterate only the cache, frame-constant filters applied at admission — exact bounding-sphere retest, vertical-vertical capsule fast path (2D circle distance + Z interval, ~10 flops) vs the general 3D segment-segment closest points (~60 flops), degenerate coincident pairs via the (UniqueID, substep)-derived RNG; support/slip decision (three StackStrength regimes; pure-Z contacts roll off perpendicular to the neighbor's capsule axis); mass weighting + dominance/immovable/touching-obst gates for the self share; sweep-arc occupancy accumulated via MassBattleFastTrig::Asin; the AVBD branch builds local augmented-system rows (λ/ρ mirrors), the legacy branch sums corrections) → cached obstacles (float-path contact geometry for capsule/cylinder, segment walls, box OBBs) → write-out (sum (over-relaxed) → clamp to the deepest single overlap of the pass → per-substep stiffness k′; obstacle stiffness stays 1.0 — hard walls; Z gates, grounded clamp (pushed up, never down), Coulomb static-friction lateral/longitudinal frame budgets; AVBD runs the anchor + active-rows 3×3 SPD Newton step + stability clamp + the end-of-sweep dual update (λ anchor cap, ρ ramp)); the whole 64-byte record is copied into Current; cumulative drift CAS-maxes back.

Input
参数 Param类型 Type说明 Description
Ctxconst FSolvePassCtx&子步上下文(时间步、门控、CAS 目标、诊断开关)。Substep context (timestep, gates, CAS targets, diagnostic toggles).
iconst int32body 的槽位下标(PreData/Selves/CurData 同下标对齐)。The body's slot index (PreData/Selves/CurData aligned by index).
Output

无返回值。副作用:写 CurData[i](仅自身记录)、更新 Selves[i] 的接触/累积器状态、CAS 更新共享漂移/修正最大值;不触碰任何 fragment。

No return value. Side effects: writes CurData[i] (own record only), updates Selves[i]'s contact/accumulator state, CAS-updates the shared drift/correction maxima; touches no fragments.

RunSolvePass void

每子步一次的 MBParallelFor 派发器,对全部槽位并行调用 SolveOneBody(并行开关强制单线程时退化为串行)。历史上的「相位车道运行器」(单派发多相位 + 相位屏障)已按性能回退结论移除——屏障自旋成本超过省下的派发。

Per-substep MBParallelFor dispatcher calling SolveOneBody over all slots in parallel (serial when the parallel toggle forces single-thread). The historical "phased-lane runner" (one dispatch, multi-phase with phase barriers) was removed per the perf round-trip — barrier spinning cost more than the dispatches it saved.

Input
参数 Param类型 Type说明 Description
Ctxconst FSolvePassCtx&透传给 SolveOneBody 的子步上下文。Substep context forwarded to SolveOneBody.
Output

无返回值。副作用:全部 body 的 Current 记录被本子步写出。

No return value. Side effect: every body's Current record written for this substep.

BuildObstacleCacheForSlot void

槽位 k 的一次障碍网格遍历(每帧一次,注册段内紧随记录落位执行):以 SelfTotalHalf + DetectObstDist + ObstCellSize 为遍历范围扫 UMassBattleHashGridSubsystem 的占用障碍格;过滤器与旧路径一致——激活、包围球、精确检查(障碍轴最近球对检测范围)、胶囊/线段/盒的 Z 区间、线段墙的单侧性(只挡 point→nextPoint 右手侧)、SeenObstacles 实体去重、盒的内翻成员检查;合格者打包为 FPhysicsObstRef(盒预计算逆朝向)进 Selves[k].ObstRefs。缓存段保持双精度(每帧一次的冷路径)。

One obstacle-grid walk for slot k (once per frame, run in the register pass right after the slot's records land): scans occupied obstacle cells of UMassBattleHashGridSubsystem within SelfTotalHalf + DetectObstDist + ObstCellSize; filters match legacy — activation, bounding sphere, precise check (nearest sphere on the obstacle shaft vs detection range), capsule/segment/box Z intervals, the segment wall's one-sidedness (blocks only the RIGHT side of point→nextPoint), SeenObstacles entity dedup, and the box inside-out membership check; survivors pack into FPhysicsObstRef (inverse orientation precomputed for boxes) appended to Selves[k].ObstRefs. The cache pass stays double precision (once-per-frame cold path).

Input
参数 Param类型 Type说明 Description
kconst int32槽位下标(记录已落位)。Slot index (records already landed).
NeighborGridUMassBattleHashGridSubsystem&哈希网格子系统,提供障碍格枚举。Hash grid subsystem providing obstacle-cell enumeration.
Output

无返回值。副作用:填充 Selves[k].ObstRefs(覆盖式重建);bCollideObst 关闭时直接返回。

No return value. Side effect: fills Selves[k].ObstRefs (overwriting rebuild); returns immediately when bCollideObst is off.

成员变量 Members

EntityQuery FMassEntityQuery (private)

唯一的实体查询(组成见 ConfigureQueries);fragment 读全部发生在注册段、写全部发生在写回段,求解段不经过它。

The single entity query (composition in ConfigureQueries); all fragment reads happen in the register pass, all writes in the writeback pass — the solve pass never touches it.

PreData / CurData / Selves TArray<FAgentPhysicsData> ×2 + TArray<FPhysicsSlotSelf> (private)

槽位数组——票号分配、索引对齐(槽位序依赖调度;一切对顺序敏感的消费方经网格格子的 UID 序成员读取)。Pre 持有最近提升(已定)状态;子步写 Current,网格更新时交换(swap)。元素类型详见 Physics/MassBattlePhysicsTypes.h。

Slot arrays — ticket-assigned, index-aligned (slot order is scheduling-dependent; every order-sensitive consumer reads through the grid cells' UID-sorted members). Pre holds the last-promoted (settled) state; substeps write Current and the grid update swaps them. Element types detailed in Physics/MassBattlePhysicsTypes.h.

SlotTicket std::atomic<int32> (private)

注册波槽位票号——各 chunk 一次宽松 fetch_add 认领下标区间,把 PreData/Selves 直写最终位置(无 TLS body 收集器、无合并、无 UID 排列);每波前清零。

Register-wave slot ticket — chunks claim index ranges with one relaxed fetch_add and write PreData/Selves DIRECTLY at final positions (no TLS body gatherer, no merge, no UID permutation); reset to 0 before each wave.

BodySlotByEntityIndex / BodySerialByEntityIndex TArray<int32> ×2 (private)

写回查找:按实体索引(稠密句柄索引、直接数组)查槽位,序列号校验拒绝陈旧句柄——取代注册段与网格填充之间的逐 body 串行 TMap。只增不减;并行填充(每槽位写各自实体的索引——互不相交、无原子),每帧 memset 清零(INDEX_NONE = 本帧不在场)。

Writeback lookup: slot by ENTITY INDEX (dense handle index, direct array) with serial-number validation against stale handles — replacing the serial per-body TMap build between the register pass and the grid fill. Grow-only; filled in PARALLEL (each slot writes its own entity's index — disjoint, no atomics), cleared per frame via memset (INDEX_NONE = not present this frame).

HandoffRecByEntityIndex / HandoffSerialByEntityIndex / HandoffGenByEntityIndex / HandoffGenCounter / HandoffOverflow TArray<FPhysicsHandoffRecord> + TArray<int32> ×2 + std::atomic<uint32> + FParallelGatherer<FPhysicsHandoffRecord> (private)

交接运输——Move 在其尾部按实体索引直接写记录(PushHandoff)。代戳使陈旧条目失效:计数器独自区分帧(无需 memset;60fps 下 2^32 回绕永不发生),序列号校验覆盖索引复用;超容(波中生成)走 TLS → 注册开头排空(罕见)。

Handoff transport — Move writes records DIRECTLY at entity indices during its tail (PushHandoff). A frame-gen stamp invalidates stale entries — the counter alone separates frames (no memset; 2^32 wrap never happens at 60 fps); serial validation covers index recycling; capacity misses (mid-wave spawns) go TLS → drained at register start (rare).

PhysGrid TObjectPtr<UMassBattlePhysicsSubsystem> (private, UPROPERTY)

持久化网格子系统——每次 Execute 缓存,SolveOneBody 的采集趟读取(取代处理器内建的 FPhysicsHashGrid);类型见 Subsystems/MassBattlePhysicsSubsystem.h。

Persistent grid subsystem — cached per Execute, read by SolveOneBody's gather (replacing the in-processor FPhysicsHashGrid); see Subsystems/MassBattlePhysicsSubsystem.h.

MaxCumDisp / PairRefreshMargin / PairGatherDisp / FrameMaxCorrection float ×4 (private, PairRefreshMargin 为 static constexpr = 24.f)

接触对缓存刷新(C):各 body |当前−起点| 的运行最大值(MaxCumDisp)供给自适应遍历余量——注册格为帧初,超出余量的漂移是子步丢对的唯一途径;两次采集间的漂移受 PairRefreshMargin + 一个子步修正(FrameMaxCorrection)约束,全局累计漂移超阈值即重采;硬化后每帧漂移厘米级 → 通常整帧一次采集,后续子步只消费缓存(零探测、零被拒候选读)。PairGatherDisp 记上次采集时的 MaxCumDisp。

Contact-pair cache refresh (C): the running max of |current − start| across bodies (MaxCumDisp) feeds the adaptive walk margin — registered cells are frame-start, and drift beyond the margin is the only way a substep could miss a pair; drift between gathers is bounded by PairRefreshMargin + one substep correction (FrameMaxCorrection), and any global cumulative drift beyond the margin triggers a re-gather; hard-contact frames settle to cm-scale drift → typically ONE gather per frame, all later substeps consuming the cached lists (zero probes, zero rejected-candidate reads). PairGatherDisp records MaxCumDisp at the last gather.

SubstepResiduals / bLoggedAvbdActiveThisSession / 旗标常量 TArray<float> / bool / static constexpr EEntityFlags ×4 (private)

逐子步总重叠残差(仅 MassBattle.Physics.DebugResidual=1 时填充,输出 [PHYS_RESIDUAL] 序列);AVBD 激活的一次性会话提示(处理器实例按世界存在——每次 PIE 自动重置);EBattleFlags 的 Falling/Launching/Dying/Activated 缓存枚举值(快速 HasFlag,枚举定义见 MassBattleEnums.h)。

Per-substep total-overlap residuals (populated only with MassBattle.Physics.DebugResidual=1, printed as the [PHYS_RESIDUAL] series); the one-time AVBD activation notice per session (processor instances are per-world — resets each PIE session); cached EBattleFlags values Falling/Launching/Dying/Activated for fast HasFlag (enum defined in MassBattleEnums.h).

管线角色 Pipeline Role

以下调用关系全部在发布树 grep 验证:由 UMassBattleSubsystem 创建并注册于 SimStages[4] 尾部(同一阶段视图中两个 Move 变体之后;不涉及生成——同阶段尾部与独立阶段语义等价;MassBattleSubsystem.cpp)。自门控于 Subtick2(= 本阶段链内完整移动阶段已完成)与 bStandalonePhysicsActive(子系统在每帧读取 MassBattle.Physics.Substeps(1–16 钳制)后置位:> 1 且非定点才激活;定点模式在子系统处钳回 1,直到 FP 镜像存在)。上游:UMassBattleAgentMoveProcessor 与 UMassBattleAgentMoveProcessorFP 在独立模式旁路各自内联 SECTION 8 求解器,并在尾部经 MB.GetPhysicsProcessor() 调用 PushHandoff 交接场景平面/陡坡/障碍半径。下游产物:写回的 FMoving::bCachedCollidedAgent 被 Move 的 Section 9 压力门下一 tick 读取(走 fragment——快照恢复安全;旧处理器数组方案在 resync 后会过期,故移除了 HadAgentContactLastTick 接口)。SOR 系数 MassBattle.Physics.OverRelaxation 由 UMassBattleSubsystem 从配置播种(非 static CVar)。诊断 CVar:MassBattle.Physics.DebugResidual、MassBattle.Physics.DebugStacking、MassBattle.Physics.Solver(0=旧求和+钳制罚量 PBD,1=AVBD;AVBD 只替换 agent 对累积与写出,场景/障碍/摩擦/接地/写回契约不动,Substeps 兼作迭代数)。

All relations below grep-verified in the release tree: created and registered at the SimStages[4] tail by UMassBattleSubsystem (after both Move variants in the same stage view; no spawns involved — a same-stage tail is semantics-equivalent to a separate stage; MassBattleSubsystem.cpp). Self-gates on Subtick2 (= the full move phase done within this stage chain) and bStandalonePhysicsActive (set by the subsystem after reading MassBattle.Physics.Substeps (clamped 1–16) each frame: active only when > 1 and not fixed-point; fixed-point mode is clamped back to 1 at the subsystem until an FP mirror exists). Upstream: UMassBattleAgentMoveProcessor and UMassBattleAgentMoveProcessorFP bypass their inline SECTION 8 solvers in standalone mode and call PushHandoff at their tails via MB.GetPhysicsProcessor() to hand off scene planes / steep slope / obstacle reach. Downstream: the written-back FMoving::bCachedCollidedAgent is read next tick by Move's Section 9 pressure gate (via the fragment — snapshot-restore-safe; the old processor-array approach went stale after resyncs, which is why the HadAgentContactLastTick accessor was removed). The SOR factor MassBattle.Physics.OverRelaxation is seeded from config by UMassBattleSubsystem (a non-static CVar). Diagnostic CVars: MassBattle.Physics.DebugResidual, MassBattle.Physics.DebugStacking, MassBattle.Physics.Solver (0 = legacy sum-and-clamp penalty PBD, 1 = AVBD; AVBD replaces ONLY the agent-pair accumulation + write-out — scene/obstacle/friction/grounded/writeback contracts unchanged, and Substeps doubles as the AVBD iteration count).

补充说明 Notes

已移除的机械(注释保留在源码中,防止回归):「GS 仅 UID 序索引」与 Gauss-Seidel 着色相位机器(RecolorPairs / MaxBodyColors / BodyColors / ColorStarts / ColorBodies / NumColors / GSOrder)于 2026-09-08 移除——从非默认、从未使用,其唯一优点(sweep 内信息传播)已被 AVBD 的 λ/ρ 收敛机制覆盖;「相位车道运行器」按性能回退结论移除(屏障自旋成本超过省下的派发);更早的 [0]-头双注册(GPU 消费分支)已回退——非帧分散子帧门无法区分头/尾调用,曾按墙钟随机跳过 CPU 求解导致锁步即刻失步,GPU 物理路径于 2026-09-12 整体移除。AVBD 出处:"Augmented Vertex Block Descent", SIGGRAPH 2025 / ACM TOG 44(4), Chris Giles 等(DOI 10.1145/3731195)——本文件为面向 MassBattle 人群求解器的再实现,未复制任何源代码;镜像对偶分解、滞后对偶更新、锚项封顶与 ρ 日程为 MassBattle 专属适配。共享类型与 AVBD 每对镜像的详细文档见 Physics/MassBattlePhysicsTypes.h;求解中使用的查表反正弦见 Math/MassBattleFastTrig.h。

Removed machinery (comments kept in source to prevent regression): the GS-only UID-order index and the Gauss-Seidel color-phase machinery (RecolorPairs / MaxBodyColors / BodyColors / ColorStarts / ColorBodies / NumColors / GSOrder) were removed 2026-09-08 — never default, never used, and their one advantage (in-sweep information propagation) is subsumed by AVBD's λ/ρ convergence machinery; the phased-lane runner was removed per the perf round-trip (barrier spinning cost more than the saved dispatches); the earlier [0]-head dual registration (GPU consume branch) was reverted — non-frame-spread subtick gates cannot distinguish head/tail invocations and randomly skipped the CPU solve → instant lockstep desync; the GPU physics path was removed wholesale 2026-09-12. AVBD attribution: "Augmented Vertex Block Descent", SIGGRAPH 2025 / ACM TOG 44(4), Chris Giles et al. (DOI 10.1145/3731195) — this file is a re-implementation for MassBattle's crowd solver, no source code copied; the mirrored-dual decomposition, lagged dual update, anchor cap and ρ schedule are MassBattle-specific adaptations. Shared types and the per-pair AVBD mirrors are documented in Physics/MassBattlePhysicsTypes.h; the table-lookup arcsine used by the solve in Math/MassBattleFastTrig.h.