D-20261101-02|持續接觸判定的幾何回退¶
背景與驅動力¶
issue 001175:公版 urban 場地直線上,speed 公版車每 1~2 秒有恰一個物理步的位移被截掉一半(63 mm 掉到 32~37 mm),速度向量與高度不變,玩家在直線上看到固定頻率的小頓挫。無頭重現 2400 幀 13 群;把世界 CCD 子步設 0 後 0 群、終點幾乎相同、每步成本 11.3 ms 降到 2.6 ms。
逐幀歸因:持續接觸政策 0(完整形狀)259 群、政策 1 與 2 都是同樣的 13 群,截斷步輪胎的接觸 piece 集合與 前後幀相同,不是相鄰 piece 的新 pair。kernel 側逐 pair 追蹤(研究版 binding,不進正式 kernel)顯示截斷步的 輪胎對地面 piece 在第二個半幀首「有 manifold、但 manifold 裡沒有任何接觸點」:narrow phase 的內部邊法線 約束把該步的接觸暫時丟掉。D-20261026-02 的持續接觸判定只讀步首 manifold 的接觸點,因此這個 pair 被判成「未貼合」,走父代完整形狀路徑,已貼地的輪胎在 window-start (fraction 0)命中,時序求解器(MotionClock)把該輪胎連同硬關節相連的底盤整台停在事件時刻,整個半幀位移 歸零——比值固定約 0.5 來自 bounded-step 的半幀分段;把研究 bundle 的初始分段改為整幀後凹陷仍在,只是比值 變成 0.06~0.27,證實半幀只決定截多少。
考慮過的選項¶
- A(採納):持續接觸判定在直接 pair 的 manifold 沒有接觸點時,以步首姿態做一次幾何接觸查詢回退。修的是 錯判本身,判面算式、牆、薄目標與動態目標的保護原樣保留。研究版 2400 幀 0 群、終點相同。
- A-n:A 再把判面用的法線改成「parry 目標局部的
point2/normal2投影進被命中三角形擬法線錐」(最初 假設平路內部邊的傾斜原始法線會被判成另一個面)。量測顯示判面法線與凹陷無關(單改法線仍 13 群),而 binding 既有治具diagonal_thin_hull_slides_without_truncation的diag-blade-split-v60穿牆:薄斜刀片以 60 m/s 衝向不同 piece group 的開放薄牆(trimesh 法線背向來車),原算式在命中姿態的接觸查詢回傳牆底邊的 邊法線而判成異面、完整形狀截停;修正後的法線正確判成同一面(牆)改掃核心球,核心球掃掠卻不命中該 trimesh 的背面而穿過(否決;核心球背面不命中另立觀察 issue)。 - A′:A 再加「window-start(fraction 0)的完整命中一律不判面」。有幾何回退後不需要;同組薄牆在上一子步 停在牆面、下一子步首的完整命中就在 fraction 0,不判面等於把薄牆交給核心球(否決)。
- B:對 kind=ground 的 piece 一律只掃核心球、不做完整形狀預查。需要 kernel 多一個 collider 屬性、還原後要重套; 001083 已記錄地面類 piece 常混著陡面(urban 建築、jungle 崖壁、UGC 地板與圍牆同節點),對這些 piece 只掃核心球等於拿掉未宣告牆條帶場地的隧穿保證;而且同樣的錯判在真正的牆條帶上仍會發生(否決;A 修後 若有殘留再以數據評估)。
- C:A+B 併用:在沒有數據前先承擔 B 的保護退化(否決)。
決定¶
1. 持續接觸判定的幾何回退¶
直接 pair 在步首(或 CCD 子步首)narrow phase 有 manifold 但沒有 dist ≤ prediction_distance 的接觸點,
且同一固定剛體、同一非零 piece group 的鄰 piece 也沒有接觸點時,以步首姿態對該 pair 做一次接觸查詢
(query_dispatcher().contact,容差 prediction_distance):距離在容差內即為持續接觸,參考法線取該查詢
的目標面法線轉成來源→目標的世界向量;目標是帶 FIX_INTERNAL_EDGES 擬法線的 trimesh 時,該法線先投影進
被命中三角形的擬法線錐(與 narrow phase 的內部邊修正同一套規則;面與邊擬法線先依原始法線定向,背側或
被錐拒絕時取面法線),平路內部邊上的回退才不會給出傾斜的參考法線。沒有 manifold(寬相未配對)的 pair
不查。判定順序固定為:直接 pair 接觸點 → 同群鄰 piece 接觸點(D-20261026-02)→ 幾何回退,群組規則
優先於回退。研究模式場地閘在貼牆分段場景出現的分段對單顆 yaw 分歧(issue 001178)與此順序無關(改序前後
逐指標相同),歸因另行處理。判定仍只讀步首狀態,與走訪順序無關。
2. 判面算式不變¶
固定目標完整形狀預查命中後的「另一個面」判定維持 D-20261026-02:命中姿態的接觸查詢(容差
prediction_distance+4×allowed linear error)所得的來源側法線轉世界座標、與參考法線比 30°;
window-start(fraction 0)的命中與其他命中同樣判面。
3. 不變¶
PHYSICS_CCD_PERSISTENT_CONTACT_POLICY = 2、30° 與核心球比例、動態目標規則(D-20261027-02)、
感測器與偽 pair、非持續接觸 pair 都不變。
4. vendor 與守門¶
- kernel
sweeps.rs新增PersistentContacts::geometric_contact_normal與persistent_target_normal(native 與 sources 鏡像同步),narrow_phase/queries.rs加一個#[cfg(test)]的 pair 可變存取供治具用。 單元測試:persistent_contact_fallback_tests(已貼地的 pair 把 manifold 接觸點清空後仍判持續接觸、參考 法線指向地面;離地 3 mm 的 pair manifold 無點時判未貼合;同群鄰 piece 有接觸點時參考法線取自鄰 piece 而非回退)與persistent_target_normal_tests(回退參考 法線:平地內部邊上離面 40° 的原始法線被拉回面法線、面內法線不變、垂直牆面的法線不被拉回、背側法線 回退到面法線、繞序朝下的地面仍得 +Y、非 trimesh 目標原樣回傳)。 - binding 既有治具
ccd_persistent_contact(11 案,含diag-blade-split-v60薄牆、薄刀片、動態目標與隧穿 守門)照跑。 - 主 repo 新增
src/testing/integration/builtin-rolling-truncation.spec.ts:speed 公版車在 urban 1500 幀內 沒有任何一步位移低於鄰近 31 步中位數的 0.9 倍(修正前 9 處、比值 0.51~0.88)。 - 重建 WASM、釘 manifest 與 adoption manifest、更新 runtime 完整核心版本;golden、conformance 向量與 完賽率基線重烘;既有持續接觸治具、隧穿治具、track-collision-gates 與成本守門照跑。
後果與影響¶
- 直線上固定頻率的單步截斷消失(研究版 2400 幀 0 群、終點相同);被截掉的位移本來就不進速度,所以完賽 時間與軌跡只有毫米級差異。
- 幾何回退只在「有 manifold 但無點」的 pair 上多做一次接觸查詢,每步成本在量測誤差內。
- state hash 改變(pre_launch,不保留舊 golden);physics-version 釘住的 kernel 身份更新。
- 現況規格:程式架構/physics-engine.md 持續接觸段、
程式參數/network.md 的
RAPIER_VERSION_CURRENT、 程式架構/testing.md 守門清單。 - 與既有決策的關係:amends D-20261026-02 的持續接觸判定 (加幾何回退),判面與核心球規則不變。
定案紀錄¶
2026-11-01 使用者裁決採方案 A(「依照你的建議」):最小修正,先修錯判、不動地面類的掃掠範圍;B 留待 A 修後若有殘留再以數據評估。 同日研究版量測把根因從「判面法線」補正為「manifold 暫時無點被判未貼合」;判面法線修正(A-n)與 window-start 不判面(A′) 分別以 binding 薄牆治具與「有回退後不需要」否決,正式版本只含幾何回退。 同日把順序定為直接 pair → 同群鄰 piece → 幾何回退(群組規則優先)並重建一次 kernel;研究模式場地閘的分段對單顆 yaw 分歧(001178)經量測與順序無關,待另行歸因。