Isaac Sim 實戰筆記 GitHub

29 · 長跑才會浮現的兩件事:錯誤預算與時鐘分歧

開發期間跑 20 到 30 分鐘的短輪,一切正常。等到要跑完整流程、一輪 80 分鐘,連續三輪死在同一個地方——而且死法看起來完全不像同一件事

這一篇講兩個只在長跑才會浮現的機制。它們的共同點是:短測試在結構上就碰不到,所以不管你跑幾次短測試,都不會發現它們存在。

驗證狀態:內容來自一段現地場域的 Isaac Sim 6.0 調校(2026-08)。錯誤上限那一段有 Isaac 自己的 log 原句佐證;⚠ 「哪些錯誤計入、計數何時重置」沒有查證,「這些錯誤無害」也沒有被獨立驗證——原始紀錄怎麼標,這裡就怎麼標。時鐘那一段的換算式只在一個工作點驗過

1. PhysX 的錯誤上限:填滿它的多半是無害的警告

三輪長跑(80、77、73 分鐘)死在同一件事。Isaac 印的是:

PhysX error: Exceeding maximum number of PhysX errors (1000).
[omni.physxui.scripts.extension] PhysX has reported too many errors,
                                 simulation has been stopped.

這不是物理問題。 PhysX 有一個「累計錯誤筆數上限」,預設 1000,滿了就把模擬停掉。它算的是筆數,不管那些錯誤有多無害。

1.1 症狀完全不像「模擬停了」

這是最貴的部分:

容器活著、還在輸出 log、UDP 探針照樣回應、畫面也還在。只有物理不動。

其中一輪因此讓控制端對著已經死掉的物理跑了 82 分鐘,每一步都「成功」送出、每一步都沒有效果。

會發現是因為控制端有一道獨立的檢查:

PACE_SIM_DEAD 模擬時鐘連續 3 次沒有前進(卡在 855.533)

判「還活著」不能看容器、log、探針回應或畫面,這四個在物理死掉之後全部照常。唯一有鑑別力的訊號是模擬時鐘有沒有前進。這與12 篇的靜默失敗是同一個家族,只是這次連「畫面還在動」都不能當證據。

1.2 填滿配額的是什麼

死亡前的統計窗口裡:

錯誤 筆數 佔比
PxArticulationLink::getGlobalPose() not allowed while simulation is running 951 86%
其他 157 14%
總計 1108 ← 上限 1000

這些是 API 檢查的警告(讀寫順序不對),值照樣拿得到、資料一直合理。把它當成「停掉模擬」的門檻才是問題。

🔑 修掉任何一類都不夠,要看總量。 先前修掉另一類(非剛體查詢),把它從約 36,000 筆壓到 104 筆——結果換 articulation 這一類自己撐到上限。配額是共用的,你只是把誰先撞線換了一個。

1.3 「是探針造成的」——一半對,一半被量測否掉

非剛體那一類確實是探針驅動的,正對照很乾淨:

沒有場景記錄探針在跑 → 0 筆 / 60s
有                    → 468 筆 / 60s

但 articulation 這一類不是。把取樣間隔從 11.8 秒拉到 30.0 秒:

改前 改後
取樣次數 / 10 分 51 22(減半)
articulation 錯誤 / 10 分 131 129(不變)
速率 13.1 筆/分 13.4 筆/分

取樣次數減半而錯誤速率不變,「探針驅動」的假說在這一類上被推翻

這一步值得學:同一個現象的兩個來源,一個是探針造成的、一個不是,而如果只做了第一個正對照就下結論,會把整個修法方向弄錯

1.4 治本的路走不通,所以繞過

想改從 Fabric 讀世界變換(那在模擬進行中是合法的)。實測:

probe_pose target=/World/RT-A/main/fork_liftA1 → HasWorldXform=False
probe_pose target=/World/RT-A/main/fork_tilt   → HasWorldXform=False

這個場景的 Fabric 根本沒有替那些 prim 填世界變換。死路,標記起來不要再試。

最後採用的是提高上限本身:

--/physics/maxNumberOfPhysXErrors=200000      # 預設 1000

(對應容器內 omni/physx/bindings/_physx.pyiSETTING_MAX_NUMBER_OF_PHYSX_ERRORS。)

以實測的 13.4 筆/分算,200000 等於 250 小時,真正失控時仍然留了後備。驗收方式是跑一輪 80 分鐘、確認全程 0 筆 too many errors 且走到最後階段——做到了

提高上限的副作用:原本那個「模擬死掉」的訊號不見了。 判準必須改用模擬時鐘有沒有前進。繞過一個問題的同時要問:我剛剛拆掉了哪個警報?

1.5 誠實標註

1.6 為什麼短測試碰不到

原因 說明
只殺長跑 短輪(20~30 分)累積不到 1000,開發期間從來不會遇到
失敗長得像物理失敗 停在某個動作組、等待逾時——看起來像參數沒調對
錯誤被歸類為「無害的雜訊」 既有文件寫「不是物理訊號,是量測工具的雜訊」。那句是對的,但漏了下半句:雜訊照樣計入上限

第三條最值得記:一個正確的判斷可以因為漏了半句而變成陷阱。「這些訊息無害」與「這些訊息不會造成後果」是兩件事。

2. 模擬時鐘與牆鐘分歧

第二個機制更隱蔽,因為它不會讓任何東西停下來——它只是讓物理跑得跟你以為的不一樣

2.1 三個各自合理的事實,湊起來是災難

控制端 use_sim_time = False   → 它所有的計時器都是牆鐘
Isaac 沒有發布 /clock          → ROS 端根本拿不到模擬時間(實測 topic 不存在)
Isaac 的 real-time factor 0.21 → 模擬時間比牆鐘慢約 5 倍

控制端照牆鐘把軌跡串流出去:384 個點 / 12.77 秒 = 30.1 Hz,穩定得很。

但同一段時間裡,物理只前進了 12.77 × 0.21 ≈ 2.7 秒。那條路徑長約 3.7 m,所以在模擬世界裡,車速是 1.4 m/s——而規劃器宣告的是 0.40。

軌跡被以 3.5 倍速播放,而兩邊都沒有報錯,因為兩邊各自都是對的:控制端按時發點,模擬器按步演算,沒有人負責檢查這兩個節拍是不是同一個。

2.2 症狀清單:每一項都會被誤診成別的東西

量到的 宣告值 倍數
叉齒速度峰值 4.35 m/s 0.45 9.7×
沿齒加速度峰值 164 m/s² 門檻 13.73 12×
相鄰兩個物理步的步距差 5.3 → 73 mm 14×
角速度峰值 97°/s 30 3.2×

這四項如果分開看,會分別被歸因成「規劃器有 bug」「摩擦不夠」「CCD 沒開」「轉向增益太大」。它們是同一個原因。

換算式在一個工作點上驗過:暴衝窗的 RTF 是 0.17,預測速度 0.40 / 0.17 = 2.35 m/s,實測 2.2 m/s

只在一個工作點驗過。 要當定律用,得掃至少三個 RTF。

2.3 為什麼降速沒有用

直覺的處置是把命令速度調低。實際:把 max_velocity 從 0.45 降到 0.22,只把 2.35 m/s 降到 1.18——仍然是宣告值的 2.6 倍,而且行程時間變長,暴露在這個問題下的時間反而更久。

根因是比例失調,不是速度設定值。 對著一個比例問題調分子,永遠只能改善那個比例的絕對值,改不掉比例本身。

2.4 RTF 是個未受控的變因

情境 RTF 對應車速
引擎閒置 0.71 0.56 m/s
跑批次(一個場景記錄 + 看門狗 + 接觸探針) 0.21 1.9 m/s
批次中的 30 秒窗(n=50) 0.00 ~ 0.56 變動超過 3 倍

🔴 「探針壓低 RTF」這個歸因被量測否掉了。 同一台、同樣閒置,只差一個場景記錄探針:

A  只有接觸探針             RTF 0.258
B  接觸探針 + 場景記錄      RTF 0.234     → 只吃掉 9%

而同樣「閒置 + 只有接觸探針」,剛重啟引擎時量到 0.707、跑完一輪批次後量到 0.258。當下的 load average 是 14.85,模擬進程 415% CPU、串流轉發 271% CPU(headless 瀏覽器編碼),GPU 只有 20%——機器是 CPU 綁住的

RTF 由機器整體負載主導,不是掛了幾支探針。 這讓問題更難繞過:負載不是能靠「少量測」控制的東西。

⚠ 這一條直接影響實驗設計:在時鐘節拍修好之前,任何物理參數的 A/B 比較都混著 RTF 這個未受控的變因。 兩組跑在不同的機器負載下,比出來的差異可能完全不是參數造成的。

2.5 三個方案,只有一個是解

# 方案 結果
1 少掛探針把 RTF 拉高 🔴 已量測否決(探針只佔 9%)
2 Isaac 發 /clock + 控制端 use_sim_time=True 🔴 讀碼後證偽:單獨做完全沒有作用
3 把串流迴圈的節拍換成模擬時鐘 ✅ 有效

#2 不是白做——它是 #3 的前提。 /clock 有在發、use_sim_time=true,get_clock() 才會回模擬時間。只是單獨做不會改變任何行為,因為串流迴圈根本沒在問時鐘。

這是一個常見的誤判形狀:做了一件正確且必要的事,行為沒變,於是把它當成無效而撤掉——結果把後面那一步的地基也拆了。

2.6 🔴 換成模擬時鐘之後,一定要有牆鐘逃生閘

模擬時間只在模擬器步進時前進。模擬一暫停就永遠等不到——沒有逃生閘的話會無聲地卡死在串流迴圈裡,而節點看起來一切正常。

實作是一個逾時上限(用過 10.0 秒):超過就印警告並改用牆鐘繼續。

換句話說,§1 的錯誤上限問題與這裡是互相咬合的:物理死掉 → 模擬時鐘不前進 → 用模擬時鐘節拍的控制端會卡死。同一個系統裡,兩個獨立的長跑機制在同一個點交會。

2.7 誠實標註

3. 這兩件事給長跑的一般教訓

① 短測試的覆蓋範圍有結構性的洞。 累積型的失效(配額、記憶體、誤差)在短輪裡不存在,不是機率低,是在結構上不可能出現。要驗長跑就得跑長跑,沒有替代品。

② 「活著」的判準要挑最難偽裝的那一個。 容器、log、探針回應、畫面全部可以在物理死掉之後照常。挑判準時問:這個訊號在壞掉的世界裡會不會也長成這樣?(27 篇 §6 是同一條規則。)

③ 繞過一個問題時,清點自己拆掉了哪些警報。 把錯誤上限提高到 200000 之後,「模擬停止」這個訊號消失了,必須另外補一個。

④ 「無害」不等於「沒有後果」。 那 951 筆警告確實無害,而它們照樣把模擬停掉了。

⑤ 未受控的變因會汙染所有 A/B。 RTF 由機器負載主導,而機器負載不在實驗設計裡——所有在它修好之前做的參數比較,證據等級都要降一級。

4. 檢查清單


延伸閱讀:12 長跑維運(狀態分歧、看門狗分層、靜默失敗)、28 誤差累積與補償(時鐘分歧正是誤差注入的根因之一)、19 調參實驗的方法論27 失效模式分類學