Team Hayes(Ajayaditya Lokchandra, Nithisha Venkatesh)です。自動運転AIチャレンジ2026 のスターターキットの MPC(multi_purpose_mpc_ros)で、速度上限を実行中に下げると逆に速くなる不具合を見つけたので、原因と直し方を短くまとめます。
結論: ros2 param set /mpc_controller v_max 10.0(10 km/h のつもり)を実行すると、MPC が使う速度上限は 20 km/h から 30 km/h に上がります。km/h の値が m/s の変数にそのまま入るためです。
同じ変数に、2 つの単位が入る
MPCConfig.v_max は m/s で持つ設計です。起動時はそのとおり変換されています(mpc_controller.py:384 付近)。
kmh_to_m_per_sec(self.BUG_VEL if self.USE_BUG_ACC else cfg_mpc.v_max),
ところが、パラメータを実行中に変えたときのコールバック(:266)は、km/h の値をそのまま入れています。
if param.name == "v_max" and param.type_ == Parameter.Type.DOUBLE:
mpc_cfg.v_max = param.value # km/h のまま
self._mpc.update_v_max(kmh_to_m_per_sec(param.value))
直後の update_v_max() は m/s に変換しているので、変更した瞬間は正しく見えます。問題は次の制御周期です。
次の周期で上書きされる
参照速度を区間ごとに決める処理(:811-815)が、毎周期この値で上限をかけ直します。
ref_vel_mps = self._ref_vel_configulator.get_ref_vel(self._mpc.model.wp_id)
ref_vel_kmph = min(
kmh_to_m_per_sec(ref_vel_mps),
self._mpc_cfg.v_max)
self._mpc.update_v_max(ref_vel_kmph)
v_max に 10 を設定すると、ここでは「10 m/s(36 km/h)」として比べられます。ref_vel.yaml のほとんどの区間は 30 km/h(8.33 m/s)なので min(8.33, 10) は 8.33 m/s、つまり 30 km/h が上限になります。起動時の上限 20 km/h より速くなります。制御を止めたときのブレーキ処理(:826)も同じ v_max を m/s として使っています。
実車で v_max を安全のための速度制限として使うと、下げたつもりで上がる、という一番困る向きに壊れます。
変数名も逆だった
上のコードをもう一度見ると、ref_vel_mps には ref_vel.yaml の km/h の値が入り、ref_vel_kmph には m/s に変換した値が入っています。名前が中身と逆です。名前を信じて読むと、min() で km/h と m/s を比べていることに気づけません。
直し方
起動時と同じく、コールバックでも m/s に変換してから保存します。ログは km/h のままです。
- mpc_cfg.v_max = param.value
- self._mpc.update_v_max(kmh_to_m_per_sec(param.value))
- v_ref: List[float] = [kmh_to_m_per_sec(param.value)] * len(self._reference_path.waypoints)
+ mpc_cfg.v_max = kmh_to_m_per_sec(param.value)
+ self._mpc.update_v_max(mpc_cfg.v_max)
+ v_ref: List[float] = [mpc_cfg.v_max] * len(self._reference_path.waypoints)
rclpy をスタブにして、実際のパラメータコールバックを呼ぶテストを付けました。修正前は 2 件失敗し(assert 10.0 == 2.777... と assert 8.333... == 2.777...)、修正後は test/ 全体の 17 件が通ります。
学んだこと
- 単位は変数名に入れる。ただし名前と中身が一致しているかを、変換関数を通るたびに確認する(今回は名前が逆だった)。
- 同じ値を設定する経路が 2 つ(起動時と実行中)あるときは、両方をテストで通す。起動時だけ正しい、はよくあります。
- 「下げたら上がる」系の不具合は、ログ(
v_max was updated to '10.0' [km/h])だけ見ると正しく見えます。値を使う側で確かめる必要があります。
作り方について
調査とパッチ作成には AI コーディングエージェントを使いました。数値は、修正前に失敗し修正後に通るテストで確認しています。
English
Team Hayes (Ajayaditya Lokchandra, Nithisha Venkatesh). A short note on a bug in the starter-kit MPC (multi_purpose_mpc_ros) of the JSAE AI Challenge 2026: lowering the speed limit at runtime made the kart faster.
In one line: after ros2 param set /mpc_controller v_max 10.0 (meant as 10 km/h), the speed cap the MPC uses goes up from 20 km/h to 30 km/h, because a km/h value is stored in a variable that holds m/s everywhere else (figure above).
One variable, two units
MPCConfig.v_max is meant to be m/s, and at start-up it is converted that way (around mpc_controller.py:384). The runtime parameter callback (:266) stores the raw km/h value instead. The update_v_max() call right after it does convert, so the change looks correct at first.
Overwritten on the next cycle
Every control cycle, the per-section reference-velocity code (:811-815) re-applies the cap with min(kmh_to_m_per_sec(ref_vel), self._mpc_cfg.v_max). With v_max set to 10, that compares against 10 m/s (36 km/h). Most sections in ref_vel.yaml are 30 km/h (8.33 m/s), so the cap becomes 8.33 m/s: 30 km/h, faster than the 20 km/h start-up cap. The brake path used when control is disabled (:826) also reads v_max as m/s.
On a real kart where v_max is used as a safety limit, this fails in the worst direction: you lower it, and the car speeds up.
The names were backwards too
In that same code, ref_vel_mps holds the km/h value from ref_vel.yaml, and ref_vel_kmph holds the converted m/s value. If you trust the names, you will not see that min() is comparing km/h with m/s.
The fix
Convert to m/s in the callback, as at start-up (diff above); the log message stays in km/h. A test with rclpy stubbed calls the real parameter callback: 2 failures before the fix (assert 10.0 == 2.777..., assert 8.333... == 2.777...), and all 17 tests in test/ pass after it.
What we took from it
- Put the unit in the name, and check the name still matches the value every time a conversion happens (here the names were swapped).
- When a value can be set on two paths (start-up and runtime), test both. "Correct at start-up only" is common.
- A "lower it and it goes up" bug looks fine in the log (
v_max was updated to '10.0' [km/h]). Check the value where it is used.
How this was made
We used AI coding agents for the investigation and the patch. The numbers were checked with a test that fails before the fix and passes after it.
Team Hayes の自動運転AIチャレンジ2026 シリーズ / series
- 最近傍 waypoint の argmin が 1.5 m で逆向きの区間に飛ぶ / argmin snaps to the opposite leg
- v_max を下げたら速くなった / Lowering v_max made it faster
- quaternion の z を yaw だと思っていませんか / quaternion z is not yaw
- make eval が止まる・提出物が入れ替わる: スターターキットの罠と PR / starter-kit traps and fixes
- AI エージェント 100 体で学んだこと / Lessons from 100 AI agents
- 日本語が読めなくても参加できるように: ドキュメントとツールを英語対応 / Making the challenge readable without Japanese
- ツール集 / toolkit: aichallenge-toolkit
