D-20261028-05|bounded-step 接受容差放寬至 0.2 毫米¶
背景與驅動力¶
issue 001074 的 cost-r22 量測顯示,每幀至少 2 次原生試算,P95 以上的尖峰幀幾乎都有拒絕細分。試算 2 次的幀佔 70%,4 次佔 19%,6 次以上佔 11%。取捨分析把 bounded-step 尾巴修正(4a)列為「不回滾底線」的必要條件。使用者裁決(2026-10-27):細分可以放寬,但先研究;CCD 維持現狀。
另外,001077 記錄停損常數 BOUNDED_STEP_STALL_FRAMES(60)的量測依據已經過時:依據寫「正常賽況最長連續細分 40 幀」,但 Gr 起跑實測達 82/95 幀,並觸發停損 23/36 幀。
現行機制(src/physics-engine/bounded-step.ts)只有程式預設值,canon 沒有寫:
- 初始分段
initial: 'half':每幀先做兩個半幀試算。 - 接受條件:原生
jointConstraintErrors的兩個值都要 ≤ 0.1 mm,且 CCD 查詢不可未決(needsSubdivision)。 - 第一個值是關節錨點的線性誤差,取幀末與子步峰值的較大者。
- 第二個值是旋轉表面位移:非自由軸的角誤差乘以 body2 包圍半徑,取
2r·sin(θ/2)。 - 原生只回傳全世界的最大值。
- 被拒時還原,二分重試。
depthLimit為 10,全部分支共用最多 63 次試算;用盡就整幀失敗。 - 細分停損:依 D-20261025-01。
研究方法¶
研究在 src 凍結複本上進行,src 樹 digest 為 c689b5cc…,核心 SDK 為 8493a474…(wasm cf640ca2)。只替換 bounded-step.ts 的政策。不設定變體時,state hash 與原檔逐位元相同。
- 篩選:15 個變體,各跑公版三場地 × mixed8/heavy8 × 2 種起跑(G、P0),每組 600 幀,逐試算記錄拒絕原因、接受段品質與關節歸因。另以 Gr 起跑 900 幀量測停損。
- 品質:
- CI 品質閘:track-collision-gates 的 CI 格、105 次穿牆守門、動態薄目標 4 組。
- 公版平地航向掃描:3 車 × 24 航向 × 20 s。
- 單車完賽:3 車 × 3 場 × 15 起跑(S01–S15,原定 30 起跑,因時程只跑一半)。
- 成本:B0、T2、T5 三組。三場地 × 兩車組 × 三種起跑(P0、L+、G),每組 60 幀暖機後量 600 幀。同一場景內各變體交錯串行執行,量測前 CPU 忙碌 6–13%。每幀實付=
engine.step+saveState;saveState 是 D-20261028-01 以前的舊版,在變體之間固定不變。
機制與研究結果¶
拒絕來自哪裡(現況 B0)¶
- 篩選共 20,988 次試算,其中 3,294 次被拒:
- 89% 只因旋轉表面位移超標;只因錨點超標佔 3%,兩者都超標佔 8%。
- CCD 未決只有 1 次。
- 拒絕當下的最大誤差多數來自輪胎鉸接(tire hinge),其次是導輪(roller),武器關節很少。
- 被拒試算的錨點誤差 P95 只有 0.35 mm,旋轉位移 P95 為 0.53 mm。
哪些拒絕是真的需要¶
- 關節爆開的上界(不檢查容差):
- 整幀試算時,接受段的錨點誤差最大 58.7 mm(CC),等於關節已經爆開。所以接受檢查不能拿掉。
- 兩個半幀時,最大錨點誤差 2.3 mm、旋轉位移最大 2.2 mm(CCh)。
- 0.5 mm 容差下仍被拒的試算:錨點 P95 2.4 mm、旋轉 P95 1.6 mm,屬於 mm 級的真實事件。
- 0.1–0.5 mm 之間的拒絕:佔現況拒絕的大多數,誤差在輪胎半徑(約 13 mm)的 4% 以下。研究判定這部分是保守過度。
initial: 'half' 的理由(新確認)¶
所有整幀起跑的變體(F1、F5、FA2R5、FR2、CC)都在動態薄目標守門失敗:自旋刀片從 0.3 m 外以 30 m/s 打 2 mm 板,7 次中 7 次嚴格穿過,幀末穿入 3.7–4.4 mm。所有半幀起跑的變體(B0、T2、T5、R1、CCh)都通過。
D-20261027-02 的薄目標保證是在兩個半幀上驗證的。半幀起跑等於薄目標品質的一部分,不是成本上的保守設定。半幀起跑的成本下限因此是每幀 2 次試算。
depthLimit 降低加「到底就接受」¶
- D2:depthLimit 2。試算均 2.66,接受段錨點最大 1.2 mm。
- FD1:整幀起跑、depthLimit 1。試算均 1.93,接受段錨點最大 5.6 mm。
這兩個變體在誤差最大的地方停止細分,把真正需要細分的事件也一起接受。收益小,品質最差,否決。
篩選摘要(12 場景合計)¶
| 變體 | 設定 | 試算均 | 拒絕幀/600 | 接受段錨點/旋轉 max mm | 最長連續細分 | 薄目標 |
|---|---|---|---|---|---|---|
| B0(現況) | 半幀,0.1 mm | 2.92 | 179 | 0.100/0.100 | 31 | 過 |
| T2 | 半幀,0.2 mm | 2.17 | 41 | 0.19/0.20 | 8 | 過 |
| T5 | 半幀,0.5 mm | 2.04 | 9 | 0.48/0.49 | 32 | 過 |
| R1 | 半幀,錨點 0.1/旋轉 1.0 mm | 2.10 | 20 | 0.10/0.68 | 4 | 過 |
| CCh | 半幀,只看 CCD | 2.00 | 0 | 2.34/2.24 | 0 | 過 |
| F1 | 整幀,0.1 mm | 2.54 | 281 | 0.10/0.10 | 41 | 敗 |
| F5 | 整幀,0.5 mm | 1.13 | 34 | 0.47/0.50 | 4 | 敗 |
| FA2R5 | 整幀,錨點 0.2/旋轉 0.5 mm | 1.15 | 36 | 0.20/0.50 | 9 | 敗 |
| CC | 整幀,只看 CCD | 1.00 | 0 | 58.7/8.6 | 0 | 敗 |
| D2 | 半幀,depth 2+到底接受 | 2.66 | 160 | 1.20/1.86 | 47 | — |
成本(串行,18 組場景與起跑,幾何平均)¶
| 變體 | world P50/P95/P99 ms | 對 B0 | 每幀實付 P50/P95/P99 對 B0 | 試算 2 次/3–4 次/5 次以上 |
|---|---|---|---|---|
| B0 | 40.2/86.3/— | 1.00 | 1.00/1.00/1.00 | 71%/18%/11% |
| T2 | 33.4/59.2/— | 0.83/0.69/0.67 | 0.89/0.75/0.73 | 93%/6%/1.4% |
| T5 | 32.0/50.6/— | 0.80/0.59/0.48 | 0.88/0.69/0.57 | 99%/1%/0.4% |
- 改善最大的是 jungle-mixed8-G:world P95 169→60 ms,P99 237→84 ms。
- 尾巴最差的仍是 urban-mixed8-G:T5 P95 77 ms,拒絕 61 幀。
品質¶
- CI 閘(22 項):
- B0 有 3 項紅,都是既有紅燈:A 20° yaw 差、B 斜向 15°、B 沿格線。
- T5 也是 3 項紅:A 20° 轉綠;新增 A 牆端「試算拒絕 0–2,上限 ≤1(基線 0)」。該閘的拒絕上限是以 0.1 mm 容差的拒絕頻率訂的。
- 105 次穿牆守門與薄目標 4 組全過。
- 航向掃描(一次諧波幅/平均 |偏航|,單位 °):
| 車 | B0 | T5 |
|---|---|---|
| speed | 0.92/1.46 | 1.01/1.58 |
| heavy | 0.40/0.55 | 0.31/0.59 |
| control | 0.51/0.80 | 0.52/0.81 |
T5 與 B0 相當。最大偏航 speed 8.1→10.5°。 - 單車完賽(135 起跑):
| 指標 | B0 | T5 |
|---|---|---|
| 完賽 | 79 | 70 |
| 翻覆 | 12 | 12 |
| 掉頭 | 7 | 15 |
| 卡停 | 37 | 38 |
完賽差 9 場,兩比例 z≈1.1,不顯著;但 control-urban 從 6/15 完賽降到 1/15,掉頭從 4 增到 8,以完整 30 起跑確認(見下方「30 起跑定案比較」)。 - 停損: - B0 在 Gr 起跑最長連續細分 82/95 幀,觸發停損 23/36 幀,重現了 001077 的觀察。 - T2 在 Gr 起跑最長 6 幀,T5 最長 21 幀,兩者都未觸發停損。 - T5 在完賽集合中最長 9 幀,B0 為 80 幀,並觸發停損 32 幀。 - 決定性:同變體同場景的串行成本趟(每幀 saveState)與並行篩選趟(不呼叫 saveState),每 60 幀的 state hash 共 36 組全同。另外,各變體在無設定時與原檔逐位元相同。
30 起跑定案比較(3 車 × 3 場 × 30 起跑,1,080 場,零錯誤)¶
串行對並行逐幀 hash 9/9 相同;與前一輪 S01–S15 的 B0/T5 結果 270/270 逐值相同。
| 變體 | 完賽/270 | 對 B0 | 翻覆/掉頭/卡停 | 停損觸發幀 | 最長連續細分 |
|---|---|---|---|---|---|
| B0(0.1 mm) | 149 | — | 24/16/81 | 32 | 80 |
| T2(0.2 mm) | 146 | −1.1 pt(Fisher p 0.86、McNemar 24/21) | 28/17/79 | 0 | 9 |
| T5(0.5 mm) | 137 | −4.4 pt(Fisher p 0.34、McNemar 31/19) | 28/21/84 | 0 | 10 |
| R1(錨點 0.1/旋轉 1.0 mm) | 148 | −0.4 pt | 32/11/79 | 0 | 29 |
- 完賽率三個變體都不顯著,但次要訊號都對 T5、R1 不利:
- 翻覆提早:翻覆秒中位 B0 16.5 s、T2 11.5 s、R1 9.9 s、T5 7.8 s。speed-jungle 完賽 B0 5、T2 3、T5 與 R1 皆 0。
- 輪胎磨耗:heavy 兩者皆完賽配對的磨耗比值中位 T2 ×1.10(p 0.0045)、T5 ×1.16(p 0.0001)、R1 ×1.11。
- 研究閘 C 弧牆抬車:T2 4.9 mm,T5 與 R1 12.9–14.6 mm(上限 10 mm)。
- control-urban(15 起跑時 T5 退步的格):B0 7、T2 8、T5 3、R1 14;T5 掉頭 7→13。
- 結論:採 T2。實付 P95 ×0.75,比 T5 少省約 6 個百分點,換取完賽與翻覆行為和現況相當。
考慮過的選項¶
- 維持現況(半幀,0.1 mm):P95 尾巴主要來自拒絕細分,不回滾底線無法達標。否決。
- 整幀起跑(initial
'full'),可搭配任何容差:成本最低(試算均 1.0–1.15,world P50 約 ×0.5),但動態薄目標守門 7/7 嚴格穿過,牴觸 D-20261027-02。否決。若要整幀起跑,必須先讓 CCD 在整幀下達到薄目標保證;這屬於 CCD 範圍,與「CCD 維持現狀」的裁決衝突。 - 只看 CCD 未決、不檢查關節誤差(CCh):成本降到下限(每幀 2 次試算),但關節誤差上界升到 mm 級(2.3 mm),失去爆開保護。否決。
- depthLimit 降低加到底接受(D2、FD1):只在誤差最大的事件上停止細分。收益小,品質最差。否決。
- 分車、分零件類別或依物體尺度的相對容差:原生
jointConstraintErrors只回傳全世界的最大值,要分類或分車就得改 vendor,讓原生回傳逐關節(或逐類)的值與半徑。研究以 JS 逐關節重算做歸因,可行,但沒有量到這個方案的收益。旋轉位移本身已經乘上包圍半徑,是一種尺度化的指標。不在本決策範圍,視 T2 實施後的殘餘尾巴再評估。 - 錨點與旋轉分開設容差(R1):30 起跑完賽 148,但翻覆提早、弧牆抬車超限。否決。
- 容差 0.5 mm(T5):每幀實付 P50 ×0.88、P95 ×0.69、P99 ×0.57。30 起跑完賽 137(不顯著),但翻覆提早、heavy 磨耗 ×1.16、弧牆抬車超過 10 mm。否決。
- 容差 0.2 mm(T2):每幀實付 P50 ×0.89、P95 ×0.75、P99 ×0.73;完賽 146,與 B0 相當;停損不觸發。採納。
決定¶
- bounded-step 的接受容差由 0.1 mm 改為 0.2 mm,錨點線性誤差與旋轉表面位移共用同一個值。
- 維持半幀起跑:明文記錄理由是 D-20261027-02 的動態薄目標保證以半幀驗證;不得改為整幀起跑,除非另立 ADR 在整幀下重新取得薄目標保證。
- 維持
depthLimit10、全部分支最多 63 次試算、CCD 未決必細分,以及用盡即整幀失敗。 - 以上四個值改為 physics-pinned 的 protocol 常數,寫入
程式參數/protocol.md,不再只是程式預設值: BOUNDED_STEP_TOLERANCE_M=0.0002(Math.fround)BOUNDED_STEP_INITIAL_SPLIT=halfBOUNDED_STEP_DEPTH_LIMIT=10BOUNDED_STEP_TRIAL_MAX=63BOUNDED_STEP_STALL_FRAMES維持 60,只改寫量測依據:- 0.2 mm 下,篩選 G/P0 起跑最長連續細分 8 幀,Gr 起跑最長 6 幀,30 起跑完賽集合最長 9 幀,都未觸發停損。
- 0.1 mm 下,Gr 起跑最長 82/95 幀。
- 停損改為只保護真實卡住的情境。
後果與影響¶
- 玩家可見:關節允許的瞬時誤差上限由 0.1 mm 升為 0.2 mm,約為公版輪胎半徑的 1.5%,在遊戲鏡頭距離下為次像素。完賽率與 B0 相當;heavy 輪胎磨耗約 ×1.10。
- 成本:
- 每幀實付 P50 ×0.89、P95 ×0.75、P99 ×0.73。
- 每幀試算 2 次的比例從 71% 升到 93%。
- 成本下限是每幀 2 次試算,無法再透過 bounded-step 降低。要再降,只能從 CCD(薄目標保證)或原生單步成本下手。
- 決定性與共識:容差是 physics-pinned 常數,改值會改變軌跡。全部 peer 同值,同幀同判,決定性不受影響(已以兩趟加串行對並行驗證)。
- 範圍:
- vendor:不改。
- protocol 常數:新增上列四個常數;
BOUNDED_STEP_STALL_FRAMES改寫依據說明。 - src:
bounded-step.ts的預設值改從 protocol 讀取;deterministic-world.ts的呼叫端不變。 - 重基線:canonical golden 不變(見已裁決第 3 點);conformance
physics/track-entity-snapshot的軌跡雜湊不變,快照雜湊隨 D-20261028-02 同批重基線;pre_launch 不保留相容。 - 測試:
bounded-step.spec.ts的預設值案例需要更新。bounded-stall-world.spec.ts的 001077 頂牆回歸(試算 ≤ 2)在新容差下仍應成立。- track-collision-gates 中以拒絕次數訂的上限依新容差重量,不得單純放寬(結果見已裁決第 4 點)。
- 動態薄目標守門加入「半幀起跑」前提的明文斷言。
- canon:
程式架構/physics-engine.md第 2.8 節補 bounded-step 接受條件、容差、半幀理由與上限。程式參數/protocol.md新增常數。程式架構/testing.md在量測口徑中加入試算數分布。
- pinning:vendored 的版本值若涉及 physics version,需另行取得同意(比照 001057)。
- 與 D-20261025-01 的關係:本決策修訂其「停損常數初值於實作研究時量測後定」的依據;世界層計數與接受規則不變。
- 與 D-20261028-03/04 的關係:本決策只降低每步成本,不改 D、R 的推導;L0 與 L2 是否達標,要在三項降本(本案、D-20261028-01、D-20261028-02)都完成後重新量測。
已裁決¶
- 完賽率:30 起跑比較完成,採 T2(見上節)。產品實作與研究 T2 的三場抽樣完賽逐幀 hash 鏈相同 (urban S17、snowy S19、jungle S24,各 842/2,982/4,931 幀)。
- 常數與測試:四個常數寫入
src/system-constants/protocol/physics.ts,bounded-step.ts的預設值與deterministic-world.ts的初始分段改讀常數。bounded-step.spec.ts新增預設值案例(容差相等時接受、 超出一個 ulp 即細分、0.1–0.2 mm 之間接受);動態薄目標守門新增「正式 bounded-step 以兩個半幀起跑」的 明文前提斷言;001077 頂牆回歸(每幀試算 ≤ 2)在 0.2 mm 下仍成立。 - golden 與 conformance:canonical golden 不需重基線:0.2 mm 下 canonical 兩車 120 幀沒有誤差落在
0.1–0.2 mm 之間而被拒的試算,revision-19
80b1c05a…在 Node 與 Chromium 不變;八車公版場景的軌跡則改變 (snowy-mixed8 自第 35 幀)。conformancephysics/track-entity-snapshot的 state hash 與 runtime hash 不變,snapshotSha256因 D-20261028-02 的線上格式改變而與該決策同批重基線。 - 閘門判準:track-collision-gates 的試算拒絕判準在 0.2 mm 下重量,未放寬任何上限:倍數規則(分段 max ≤ 基線 max × 1.6)與基線無拒絕時「≤ 1 次」不變;騎縫例外的固定上限依 0.2 mm 研究閘重量(平地 沿格線與坡道沿格線上坡最大 3 次),保留同樣的 1.6 倍裕度由 8 次收緊為 5 次;量測容差寫成常數並守門, 容差改值時「閘設定」測試會紅。0.2 mm 下拒絕次數多為 0–5 次,小整數集合的 max 比值主要反映抽樣: 研究格 C 弧牆 heavy x=−0.8 基線 1–5、分段 0–10(上限 8)轉紅(0.1 mm 時 12–33/10–22)。曾試以拒絕來源的 量級(統計窗內逐幀全部試算最大接受指標超出容差之和)取代次數:該格 ×1.24 可過,但 CI 格 A 貼牆 20° 基線 0–55.4、分段 10–129.4 µm(×2.3)新紅,同樣受小樣本支配,未採用,改列觀察。判準如何在小計數下 維持鑑別力,交由維護者裁決(見後續 issue)。CI 格紅燈仍為既有的三格軌跡判準(A 20°、B 斜向 15°、 B 沿格線),拒絕列全過。
- 研究閘:C、D 以產品 T2 重跑,與研究 T2 逐值相同:C heavy x=−0.7 牆穿入 2.24 mm(上限 2 mm,另案 調查)、C heavy x=−0.8 拒絕列(見上點)、D 直行上坡地穿入 2.56 mm 與 C 判準窗長 0 的既有紅燈。
- 能力邊界與導輪:13 邊形起步、坡道等公版能力邊界案例已在 30 起跑研究以 T2 重驗通過;導輪觸地與 衝量比值中位 0.98,無退步。分類容差或相對容差視殘餘尖峰再評估,不在本決策範圍。