32 · 把車做成真的會動的車:五個坑、一個錯的指標、以及為什麼它開不完路網
把車從「一顆會被搬的盒子」換成照 URDF 建的 articulation(兩顆驅動輪 + 四個萬向腳輪),中間卡了五次。每一次的症狀都是「車不動」,而五個成因完全不同。
它們的共同點比個別成因更有用:
USD 檢查、bbox、overlap 查詢在五種情況下全部是綠的。
這一篇講那五個坑、一個算錯的指標怎麼長出一整套合理但錯誤的成因、以及最後「車開不完路網」的五個原因——沒有一個是路線規劃不好。
驗證狀態:內容來自一段多車 + 多樓層的 Isaac Sim 場域前期研究(2026-07 至 08),五台車、三款車型的實測。數字連同量測條件列出。⚠ 幾個結論明確標了「還沒量」,照原始紀錄標。
1. 五個都不報錯的坑
| # | 成因 | 症狀 | 怎麼判出來的 |
|---|---|---|---|
| 1 | physxArticulation:enabledSelfCollisions 預設是 True |
車一放進場景就自己炸開:靜置 150 步從 (30,12,0) 飛到 (188,−165,−51),關節速度 1.8 萬 rad/s | 先關掉驅動只讓它靜置——還是炸,所以問題不在驅動 |
| 2 | 驅動增益填 1e4、上限填 500 N·m | 輪速 3.3×10⁶ rad/s、位移十萬公尺 | 「留兩位數餘裕」不是餘裕:1 kg 的輪子在 500 N·m 下是 12 萬 rad/s² |
| 3 | physxJoint:maxJointVelocity 的單位是度/秒 |
輪速無論怎麼下指令都卡在 0.52 rad/s | 那個數字太整齊——0.52 × 57.3 = 30,正好是填進去的值 |
| 4 | UsdGeom.Cylinder 當碰撞體 |
五台車全部下沉 4.5 cm,cmd_vel 三秒只走 16 mm |
用 0.004 m 的小球去探輪心 ±0.07 m:兩個方向都在碰撞體裡,而真的輪子在轉軸方向只有 ±0.03。改用凸包網格後車正好站在 z=0 |
| 5 | 凸包網格的軸向映射寫反(X 與 Y 對調) | 輪子完全轉不動,500 N·m 也不動 | 沒有 drive 的腳輪加 20 N·m 可以飆到 1.7 萬 rad/s——關節是自由的,所以不是關節的問題,剩下的可能只有幾何。回頭量網格外框:y 方向是半徑而不是厚度 |
第 3 個特別值得記:當一個量卡在某個「太整齊」的數字上,先做單位換算再想別的。0.52 這種數字不會自然出現。
第 5 個示範了一種很有效的排除法:造一個「同類但少了嫌疑物」的對照。腳輪沒有 drive 卻轉得動,一次就把「關節設定」整類嫌疑排掉。
1.1 兩個量測腳本自己製造的假訊號
① USD 的預設 upAxis 是 Y。 量測腳本沒設,於是重力沿 −y、「地板」變成一面牆——量到的落定高度全部沒有意義,而數字看起來剛好支持當時的假設。差一點就把一個錯的結論寫進文件。
② 從高處丟一個輪子去測碰撞是錯的實驗設計。 r=0.09、厚 0.06 的圓柱落地會倒下來平躺,量到的高度是厚度的一半——那是真的物理,不是碰撞失效。
兩條的共同形狀:量測工具本身在製造你正在追的那個現象。這與 27 篇 §6.1 的「為了補救而加的儀器本身量錯東西」是同一件事。
2. 一個算錯的指標,長出一整套合理的成因
這是整篇最有教學價值的一段。
中間有一版把「滑移」量成指令距離 vs 車實際位移,得到 41%,並據此推論「載重跑到腳輪上、驅動輪吃不到正向力」。
那個推論是錯的,而它錯得很有說服力:數字穩定、可重現、而且有一個聽起來合理的力學解釋。
2.1 正確的定義
真正的滑移是輪子轉過的距離 vs 車走的距離,而輪子轉過多遠只能用角速度對時間積分。
兩種簡便讀法都不行:
- 讀
get_joint_positions()不行 —— continuous 關節的角度會繞回(±π),而這一輪要轉一圈半。踩過:整段量到 0.469 m,其中最後三分之一量到 0.893 m,後者比整段還多。 - 讀某一刻的角速度也不行 —— 速度伺服在目標附近震盪,峰值到目標的 1.8 倍。
用積分量出來的滑移是 2~3%。一開始就不是滑移問題。
2.2 錯的指標為什麼特別難發現
指標算錯時,錯的那個數字會自己長出一套合理的成因,而順著它做的每一步(降低腳輪摩擦、加大馬達扭矩、加懸吊)都會「有一點效果」——因為它們確實各自改善了一點別的東西。
這就是為什麼「改了有反應」不能當成「找對方向」。23 篇記過同一個形狀:排查停在第一個「調了有反應」的參數上。
2.3 真正的成因:慣量少了馬達與減速機
URDF 的驅動輪慣量原本只有輪胎本體:½mr² = 0.004 kg·m²。
25 N·m 的力矩在一個 1/60 s 的步長裡可以把它加速 100 rad/s —— 速度伺服因此變成 bang-bang,輪速在目標的兩倍與零之間震盪,平均只有目標的 75%。
真實 AMR 的驅動輪後面接著馬達與減速機,減速比 n 會把轉子慣量放大 n²。補上反射慣量(0.046 kg·m²)之後,穩態追隨誤差從 22.8% 降到 0.7%。
⚠ 同一處還有一個附帶錯誤:URDF 的碰撞圓柱被 rpy 轉成沿 y,而慣量張量沒有跟著轉——自轉慣量寫在 izz,但關節繞的是 y。差 43%。
2.4 懸吊留下來了,但理由改寫了
懸吊(prismatic joint + 彈簧,行程 ±2 cm、剛度 25000 N/m、預壓 1.5 cm)留在模型裡,理由不是修滑移(那個問題不存在)。
真正的理由是:剛體底盤 + 兩顆驅動輪 + 四個腳輪是六個共面接觸點的靜不定問題,載重分配由求解器決定而不是由設計決定。彈簧讓「驅動輪吃多少載重」變成一個寫得下來的數字(k × 預壓 = 375 N/輪)。實測它把輪速震盪的峰值從 37 rad/s 壓到 6 rad/s。
一個措施可以是對的,而支持它的理由是錯的。 根因查清楚之後,要回頭把理由改寫——否則下一個人會照那個錯的理由去調它。
3. 腳輪半徑只有驅動輪的一半,車就在平地上被彈飛
一款車在平坦、空曠、沒有任何障礙的 9 m 走廊上,以 0.6 m/s 前進約 1.3 m 之後被推到離地 0.39 m、傾角 106°,98% 的步數在空中。同一條路、同樣的控制律與速度上限,另外兩款車 15.6 秒走完、傾角 0.1°、零步離地。
唯一相關的差異:
| 車型 | 驅動輪 | 腳輪 | 結果 |
|---|---|---|---|
| 櫃車型 | 0.09 m | 0.09 m | ✅ 15.6 s、傾角 0.1° |
| 平板型 | 0.10 m | 0.10 m | ✅ 15.6 s、傾角 0.1° |
| 拖車型 | 0.09 m | 0.045 m | ✗ 1.3 m 就翻 |
把腳輪改成 0.09(連同 hub 高度與慣量一起)之後,兩台拖車型都變成 15.6 秒、傾角 0.1°、零步離地、輪載 158/173 N——單一變數的干預,症狀完全消失。
3.1 被排除的兩個假設,各自示範一種排除法
① 靜態輪載不足 → 否。 量到五台車靜止時都是 243 N/輪、懸吊 0.0 mm、傾角 0°,三款車完全相同。
靜態正常與動態正常是兩件事。
② 小碰撞體被暴力排出(maxDepenetrationVelocity 預設 3 m/s)→ 否。 壓到 0.5 之後干預確實生效(翻車點從 x=10.3 移到 14.1、離地 390 → 795 mm),而症狀沒解掉。
干預生效但症狀不動,就是假設錯了。 改動已還原。
第二條特別值得記:一個假設可以「被證實有影響」卻「不是成因」。若當時停在「有效果」就收工,那個 maxDepenetrationVelocity=0.5 會永遠留在設定裡,變成一個沒人敢動的魔數。
3.2 守衛寫成「相等」而不是下限
新增的斷言是:腳輪半徑要等於驅動輪半徑。
取「相等」而不是某個下限,因為相等是三款車現在都成立的事實,而「0.045 壞、0.09 好」中間那一段沒有量過——寫一個中間值等於把沒驗過的猜測寫成門檻。負對照跑過:把半徑改回 0.045 之後守衛立刻紅。
⚠ 中間曾經用「分車型速度上限」把那款車壓到 0.3 m/s 繞過去。成因查到之後那個繞過整個移除——留著會變成一個沒有理由的魔數,而且會掩蓋同一類的下一個模型錯誤。(這是 23 篇「捷徑的前提會過期」的正面示範:前提消失了,捷徑跟著拆掉。)
4. 宣告 1.0 m/s 的車撐不住 1.0 m/s,而限速之後反而更快
即時鏈路上量到一台車傾角 85.6°(躺著)跑完整輪並判 pass。
往回查是輪子離地:懸吊壓到行程底(−15.8 mm)、驅動輪載重從 245 N 掉到 0 N、車身抬到離地 16~28 cm。沒有載重就沒有摩擦力,馬達再出力也只是空轉,車靠慣性亂滑,終點是翻覆。
同一條路,只改速度上限:
| 上限 | 最大傾角 | 最高離地 | 最小輪載 | 離地步數 | 航向擺動 | 實際耗時 |
|---|---|---|---|---|---|---|
| 1.0 m/s | 31.1° / 43.4° | 162 / 280 mm | 0.1 / 0.0 N | 13% / 18% | ±33° / ±106° | 12.6 / 22.5 s |
| 0.6 m/s | 0.1° / 0.7° | 1.0 / 2.1 mm | 146 / 156 N | 0% / 0% | ±1.3° / ±9.3° | 10.8 / 16.1 s |
限速之後絕對時間更快(第二段 22.5 → 16.1 秒)。1.0 m/s 的時間大半花在空中與落地後的修正,而不是花在前進——「調高上限 = 跑更快」在有物理的世界裡不成立。
4.1 ⚠ 0.6 不是「室內 AMR 的合理速度」
同級的真實 AMR 跑得到 1.0~1.5 m/s,所以模型裡還有東西不對。三個候選,都還沒量:質心高度、輪徑(反推出來是 9.2 cm,偏小)、懸吊行程(15 mm,而它在平地上就壓到底了)。
在量出來之前不要把 0.6 寫成通則——它是目前這組物理參數撐得住的速度。
4.2 為什麼這個缺陷拖到現在才浮現
車先前根本跑不到 1.0(平均行駛速度 0.28 m/s),所以這個缺陷一直躺在工作範圍外面。把控制律的煞停上限補上之後平均速度變成 0.66、峰值 0.98,缺陷才第一次被觸發。
修好一個問題會把下一個問題帶進工作範圍,而在那之前它在每一份產出裡都是不存在的。
4.3 判準的兩個洞
- 離線工具原本只在每一段結束時量一次傾角。 而彈跳是瞬態的:跳起來、落地、繼續走,終點量到 0.1° 判 pass。改成整段追最大傾角、最高離地、最小輪載與「等於離地的步數」。
- 即時輪次完全沒有量傾角。 躺著的車在其他每一個量尺上都正常:有單、在下指令、在開的步數 92%、空轉率 4%、抵達三個節點。
判準不是太寬,是少了一個維度。
5. 車開不完路網:五個成因,沒有一個是「路線規劃不好」
多車即時鏈路一跑,兩台車都停在半路——而它們一直在下速度指令。九分鐘一輪的鏈路查不動這種問題,所以先做了一支一分鐘的路線檢查工具(不要雲端、不要調度層,只留「路網的一段路 + 這台車的控制律 + 這個物理世界」)。
成因一個一個量出來,五個都不是規劃的問題:
| # | 成因 | 修法 |
|---|---|---|
| 1 | 控制器下階躍指令 → 馬達全力 → 0.65 g 牽引力 → 車頭抬起、懸吊壓到底(1.0 m/s 直線走廊上兩秒內傾角 0.5° → 117°、z 彈到 1.5 m) | 控制器加加速度上限(0.5 m/s²、0.45 rad/s²);驅動力矩照 0.5 m/s² 重推(25 → 18 N·m) |
| 2 | 角速度上限填 1.5 rad/s 而車宣告 1.0 —— 1.5 rad/s 要四個腳輪瞬間轉向 90°(原地轉力矩需求飆到 30 N·m 以上) | 上限照車的規格 |
| 3 | 邊走邊大幅修正對差速車是不穩的(直線走廊上航向擺到 ±60°) | 改成停下、原地轉、再走(15° 轉、40° 遲滯、行進中轉向 ≤0.35 rad/s) |
| 4 | 偏出去還全速前進;轉向量有上限,回到線上需要距離(偏出 1 m 之後再也回不來) | 偏 0.5 m 以上速度收到 25% |
| 5 | 車不看它自己的雷射(1.24 m 的縫裡照標稱速度衝,擦到牆就楔住) | 前方 ±40° 最近回波 −0.45 m ÷ 1.2 s = 這一刻的速度上限(真車就是這樣做的) |
修完之後,那五條任務路線的同層段全部走得完,比「距離 ÷ 標稱速度」慢 2.5~7.6 倍——那個倍數本身就是「有物理」的代價。
5.1 「翻車」與「被牆擋住」要分開
兩者在「輪子照轉而車不動」上完全一樣,而處置相反(一個要改車的動力學,一個要改路線或幾何)。
分開的方法是量車身的傾角:
傾角 117° + z 彈高 = 翻了
傾角 0° + 位置不動 = 真的被擋住
⚠ 這一格是第一項成因現形的關鍵。 在它出現之前,那個現象被讀成「幾何太窄」,而順著那個讀法做的事(移路網、改電梯)都不會有用。
5.2 檢查工具自己的兩個坑
- 路線起點常常正是別台車的停放點。 把待測車直接放上去就是兩個剛體重疊,PhysX 解重疊的方式是把它們彈開——實測位置在幾步之內變成
1e16。要先把其他車搬開並凍住(搬到天上它會掉下來,落地又是一次碰撞)。 - 逾時判準要照實際速度給。 接上雷射限速之後車在窄處只開 0.15~0.5 m/s,一段 9 m 的路要 40~60 秒;沿用 60 秒會把「開得慢」判成「卡住」。
5.3 空轉集中的那一格,就是最窄的那一點
有一輪某台車在某一格空轉 1042 步(佔它全部步數的 13%)。量那一格的路網淨空:0.43 m,而車半寬 0.35 m——只剩 8 cm 餘裕。
空轉的分佈是一張免費的幾何地圖。 不必先知道哪裡窄,讓車跑,然後看它卡在哪。
6. ⚠ 陳舊的輸入不會報錯,它只會給你一組合理的數字
路線檢查報「車翻了,最大傾角 122.8°、97% 的步數輪子離地」。把嫌疑的層門搬走重跑,數字一模一樣——不是層門。
真正的原因:輪次腳本只把程式碼同步到模擬主機,地圖目錄沒有跟,而那台上那份路網停在 18 天前。舊圖裡某個節點在 y=13.6,正好壓在轎廂門口,於是車被放成半截在轎廂裡、半截在門檻上,一開就卡住再被彈開。新圖裡它在 y=12.9,同一條腿通過,傾角 10.2°。
這個陳舊輸入還連帶產生過一個錯誤的機制假說(「車的對角線 0.968 m 大於 0.95 m 的淨開口」)——那個假說不成立,它只是那份舊路網的產物。
修法有兩層:同步範圍補上地圖目錄,而且每一次檢查都印出路網的 sha256 與長度。
那一行的用處不是給讀者看,是讓「這一輪讀的是哪一份」變成可以比對的事實。
7. 檢查清單
- [ ]
enabledSelfCollisions明確設定,不吃預設 - [ ] 驅動增益與力矩上限用
a = τ/I反推過,不靠「留幾位數餘裕」 - [ ] 每個角度/角速度參數確認過單位(卡在「太整齊」的數字先做單位換算)
- [ ] 碰撞體用凸包網格,並實際探過它的外框
- [ ] 幾何的軸向映射驗過(量網格外框,不看程式碼)
- [ ] 量測腳本自己設了
upAxis - [ ] 滑移用角速度積分算,不用位移比、不讀會繞回的關節角度
- [ ] 腳輪半徑與驅動輪半徑相等(或有量過的門檻)
- [ ] 速度上限是量出來的,不是宣告的;而且知道那個上限不是通則
- [ ] 判準含傾角、離地高度、輪載,而且整段追極值不只量段末
- [ ] 為繞過問題設的參數,成因查到後整個移除
- [ ] 每次檢查印出輸入資料的 sha256
延伸閱讀:24 把「會瞬移的底盤」換成「真的有輪子的車」(轉向式底盤的前提鏈,與本篇的差速車互補)、27 失效模式分類學、23 物理模擬不可以偷懶、33 電梯與多樓層。