# gamepad_teleop.py 長押しと範囲変更のレビュー（2026-11-03）

**確かめた範囲：** 依頼文・`gamepad_teleop.py` 全体・`test_gamepad_teleop.py` を読みました。ここには pygame と、定数を読む元のファイル（`run_policy_client.py`・`waypoint_replay.py`）がないので、64 件のテストは流せていません。代わりに、`gamepad_teleop.py` をスクラッチに写し、定数と pygame を仮のものに差し替えて、下の A・B・C の道を実際に起こして確かめました。元のファイルは何も変えていません。

## 必須

| 項目 | 判定 | 確認結果・修正案 |
|---|---|---|
| A. 動いている間に押した十字キーが、受付なしと出たあとで長押しとして動き出す（L723-736, L789-793） | 必須 | **起きる順番：** HOME や爪の仕事で busy の間に十字キー下を押す。`press` が L731 で `self.hold` を作ってから `_request` を呼び、`_request` は busy で断る。画面には「[受付なし] 前の操作が終わっていません（ためません）」と出る。それでも `hold` は残っているので、busy が解けたあとの `tick`（L766-776）が 0.2 秒おきに送り始める。<br>**確かめた結果：** 爪の仕事の間に下を押して断られたあと、tick 4 回で j1 が 0.5→1.0→1.5→2.0 と送られた。<br>停止中に押した場合も `hold` は残る。ただしこちらは、再開に使う OPTIONS が L703 で長押しを消すので動かない。<br>HOME の長押しには「押し始めに受け付けられなければ、続けても送らない」という決まりがある（L715-717、codex 必須 4）。十字キーの長押しはこの決まりと食い違っている。<br>**直し方：** `press` で `_request` のあとに `if not e.get("accepted"): self.hold = None` を足す。または、停止中・busy のときは `hold` を作らない。直したらテストを 1 件足す（busy の間に押して押し続ける → 送られた数が増えない）。 |

## 推奨

| 項目 | 判定 | 確認結果・修正案 |
|---|---|---|
| B. 離したあとに 1 刻み送られることがある（L816-838, L856） | 推奨 | 待つループ（L820-831）は、先に角度の差を見て 3° 以内ならすぐ抜ける。長押しが生きているかを見るのは、差が 3° を超えているときだけ（L826）。そのため次の 2 つの道で、離したあとに 1 刻みが出る。<br>(1) 仕事が始まって角度を読んでいる間に離した。<br>(2) 待っている途中で離し、ちょうど次に読んだ角度で追いついた。<br>**確かめた結果：** 2 つとも実際に送られた（j0 が 0.5→1.0、2.0→4.0）。<br>出るのは 1 刻み（2° まで）なので、大きく走り続ける道ではありません。ただ、読み取りが遅いと（`read_angles` の読み直し）、離してから数秒たって 1 刻みが届くことがあります。説明（L15「離したら直前の指令までで止まる」）とも合いません。<br>**直し方：** `release` で、走っている仕事がこの長押しの 2 回目以降（`hold_n>=1`）なら `self.gate.cancel_now()` を呼ぶ。SendGate の鍵で「送ると決める」と順番が決まるので、決める前なら必ず止まる。あわせて `_apply` で、停止していないときの `halted_before_send` は「離した」と表示を分ける。1 回目（`hold_n=0`、短く押した分）は取り消さないこと。 |
| C. アームより先へ出る量は 3° ではなく「3° + 刻み」（L821, L842-846） | 推奨 | 送ってよいかの判断は「直前の指令と読んだ角度の差 ≤ 3°」で、そのあと指令 + 刻みを送る。なので、読んだ角度より先へ出る量は最大 3 + 2 = 5°（R1 のとき）になる。止まらない道ではなく、上限が 5° に決まっている。読む角度が古い場合は、アームは実際にはもっと進んでいるので、安全な側にずれる。<br>**直し方：** 説明と `HOLD_LEAD_MAX_DEG` の注記を「最大 3° + 刻み」に直す。または、作った目標で `max|target − q| ≤ 3` になるまで待つ。 |
| D. 離した合図を取りこぼすと、物理可動域の端まで動き続ける（L744-746, L1033-1039） | 推奨 | 長押しを止めるのは、ボタンを離した合図・停止・ほかのボタンだけ。コントローラーの今の状態は見直していない。合図を取りこぼす道（Bluetooth の乱れで接続が切れたことにならないとき、手前にないウィンドウで合図が届かないとき）では、範囲の端まで送り続ける。前の ±10° の枠がなくなったので、この道で動く量は大きくなった。<br>**直し方：** `run_loop` で毎コマ `js.get_button(tp.hold["button"])` を見て、押されていなければ `tp.release(...)` を呼ぶ。手を離せば止まる仕組みを、合図とボタンの状態の 2 本立てにする。 |
| E. 物理可動域まで広げたので、机や本体に当たる道がある（L256-271） | 推奨 | j1 は 90.87 で切られる。しかし j2・j4 も手先を下げる向きに効く（L205 のコメント）。j1 を 90.87 にしたまま j2 や j4 を下へ長押しすると、手先が机の高さより下へ行く指令を止める守りはない。j2 の ±177 などは、ぶつからない範囲ではない。範囲を広げたのはエイイチの決定なので、禁止にはしません。<br>**案：** FK で手先の高さが机 + 余白を下回る目標を断る守りを足す（`fk_lookup` が使えるか確かめる）。または、j2・j4 だけ PICK のデータが届く範囲までにする。<br>**初回の試し方：** R1 なし（0.5°）で、机に近いところでは短い長押しだけ。L2+R2 は、送った 1 刻み（先へ出る量 5° まで）は止めないことを前提にする。 |
| F. 机などに押し当たって追いつかないとき、最後の指令が残り続ける（L829-831, L933-940） | 推奨 | 当たって追いつかないと 3 秒で長押しは止まる。しかし最後の指令（読んだ角度より最大 5° 先）は残るので、サーボは押し続ける。逆向きを 1 回押せば、読んだ角度を出発点にして戻れる（L842-843）。<br>**直し方：** `arm_not_following` の表示に「当たっていれば逆向きを 1 回押すか、HOME で戻してください」と足す。 |

## 据え置き

| 項目 | 判定 | 確認結果・修正案 |
|---|---|---|
| 停止（L2+R2）・接続が切れたときと SendGate の関係 | 問題なし | 送り口は `gate.run` の中だけ（L420・L458・L460）。ほかに送る道はない。<br>待つループは `gate.cancel` と `quitting` を見ている（L823）。待ち終えて抜けても、送る直前に `gate.run` の鍵の中でもう一度確かめる。<br>`enter_stop` は長押しを消してから取り消しを立てる（L641-642）。`tick` は止まっている間は送らない（L767）。再開は OPTIONS を押したときに長押しも消える（L703）ので、押し直すまで動かない。<br>接続が切れたとき・読み取りに失敗したときも `enter_stop` を通る。今回の変更が SendGate を抜ける道は見つからなかった。 |
| スレッド（`_hold_alive` が `self.hold` を読む） | 問題なし（B を除く） | 送る仕事のスレッドは読むだけで、書くのは pygame のループ側だけ。参照の読み出しは一度に行われ、`h["id"]` は作ったあと変わらない。`tick` が書き換えるのは `n` と `last_req_at` だけ。壊れた値を読む道はない。残るのは、確かめてから送るまでの間の食い違いだけで、これは B として上に書いた。 |
| 出発点が範囲の外のとき・HOME | 問題なし | 枠が固定になっただけで、判定は変わっていない。出発点の 6 関節のどれかが範囲の外なら送らない（L296-302）。長押しでもこれで送れなければ長押しを止める（L939-940）。<br>HOME `[0,0,0,0,0,-30]` は範囲の中にある。HOME を押すと十字キーの長押しは止まる（L703）。 |
| ログに同じ行がたくさん出る・壊れる | 問題なし | 1 刻み = 1 行。busy の間は `tick` が仕事を作らないので「受付なし」の行は出ない。範囲の端・追いつかない・出発点が範囲の外では、1 行出したあと長押しを止める（L939-940、テストあり）。`hold_released` はログに 1 行だけ残り、画面には出ない。<br>小さな改善：送れたときにも `hold_lead_deg` を残すと、あとでアームの遅れを分析できる。 |
| 6 関節すべての差で待つ | このまま | j1 の重さによる 2〜3° の不足が 3° を超えると、j0 の長押しも 3 秒で止まりやすい。それでも、動かす関節だけで判定すると、出発点が読んだ角度になったときに、ほかの関節（j1 の沈み）を毎回読んだ値で送り直して、ずり下がっていく道ができる。今のままが安全な側。初回のログの `hold_lead_deg` と `arm_not_following` の回数を見て決める。 |
| テストが本当のスレッドを使っていない | このまま | `spawn` がその場で呼ぶ形なので、別スレッドとの食い違いは試されていない。A と B を直すときに、`read` に差し込んだ処理で離す・押す再現テストを足すとよい（今回スクラッチで同じやり方で確かめた）。 |

**実機で試してよいか：** 必須 A を直してから、R1 なし（0.5°）の短い長押しで試してください。B〜F は、直す前でも動く量の上限はおおむね 1 刻みか 5° までです。ただし D（離した合図の取りこぼし）と E（机に当たる道）は、範囲を広げたことで影響が大きくなっているので、早めに直すのがよいです。
