## 結論

必須は 1 件です。L2+R2 では、HOME の途中の動きを止められません。それ以外の流れは筋が通っています。長押しを離したとき・ほかのボタン・進まない・端・停止のどの終わり方でも、halt は送った動きの後に送られます。長押しの目標を送っている最中に離した場合も、`released_while_sending` で後から止めています。gate・busy・halt_pending まわりに競合は見つかりませんでした。

（行番号は review_gamepad_teleop.py のものです）

## 必須

**1. L2+R2 を押しても HOME（と 1 刻み）が止まらない。stop が届くのは着いた後になる**
- 場所: `DaemonArm.send_and_check` の届いたかを確かめるループ（470〜503 行）と `_dispatch_halt`（752 行）
- 何が起きるか: halt は「走っている仕事が終わってから」送る作りです。ところが、届いたかを確かめるループは `quitting` しか見ていません。gate の取り消しを見るのは送り直しのときだけです。
- 例: HOME を送ってアームが動いている途中で L2+R2 を押す。
  - `enter_stop` が gate を取り消し、halt を「送る予定」にする。
  - 仕事は HOME に着くか待ちの上限（8 秒以上）まで、0.3 秒おきに角度を読み続ける。
  - stop が送られるのは HOME に着いた後です。
- 「L2+R2 で HOME の途中の動きも止める」という今回の目的が実機では果たせません。
- テスト `test_stop_during_home_sends_halt` は、FakeArm がすぐ着いた後に L2+R2 を押しているので、この場合を通っていません。
- 直し方: ループの頭で `if gate.cancel.is_set(): return dict(base, reached=None, halted_during_check=True, ...)` として抜ける。こうすれば halt が次の読み取り（最大でおよそ 0.3〜3.7 秒）の後にすぐ出ます。テストには「HOME の確かめの途中で L2+R2 → 仕事が早く戻る → halt」を足します。

## 推奨

**1. daemon の stop が失敗しても「止めた」扱いになる**
- 場所: daemon_add_stop.sh で足す stop の処理と、teleop の 519 行
- 何が起きるか: daemon の stop は、`jog_stop` と `stop` の両方が例外を出しても `{"ok": True}` を返します。teleop は ok だけを見るので、今の角度へ送る代わりの止め方に移りません。
- 特に危ないのは、Pi の daemon でアームの変数が `_mc` という名前でない場合です。どちらの呼び出しも NameError になります。それでも構文の確かめ（`compile`）は通り、最後の確かめでも `ok: True` が表示されます。
- 直し方: daemon 側で「2 つとも例外なら `ok: False`」にする。またはスクリプトで、足す前に `_mc` と `log` が daemon の中にあるかを grep で確かめる。

**2. 送信が詰まって停止になったとき（arm_error）に stop を送らない**
- 場所: 797 行の `kind != "arm_error"`
- 何が起きるか: 長押しの 1 回目の 15° 先を送る通信がタイムアウトすると、届いたかどうか分かりません。指令が届いていればアームは動いています。しかし `hold_last_sent_id` がまだ付いていないので、`_end_hold` からも halt は出ません。結果、最大 15° 先まで動きます。
- halt が失敗しても停止にはしない作りなので、arm_error を外す理由（止める → 失敗 → 止めるの繰り返しを防ぐ）はもうありません。1 回だけ stop を試す形にできます。

**3. 終わるとき（ウィンドウを閉じる・Ctrl+C）に stop を送らない**
- 場所: 1168 行 `shutdown`
- 何が起きるか: 長押しのまま Ctrl+C を押すと、予定していた halt は捨てられます。アームは 15° 先（約 0.4 秒分）まで、HOME の途中なら HOME まで動き続けます。
- 「アームを動かさない」という終わり方の決まりと stop は矛盾しません。直し方: 仕事が戻るのを待った後、`hold_last_sent_id` か `halt_pending` が残っていれば `arm.halt()` を 1 回呼ぶ。

**4. 長押しの 1 回目が無視されると、1 秒後に「進まない」で止まる**
- 場所: 987〜991 行の lead_ok と、`_hold_progress` の進まない判定
- 何が起きるか: 以前の作りは 0.1 秒ごとに送り直していたので、無視された指令も次の送信で取り戻せました。今の作りは、15° 先の目標を一度送ると残りが 5° を切るまで送り直しません。daemon がこの 1 回を無視すると、1 秒後に「どこかに当たっていれば」と表示して止まります。
- 直し方: 送ってから一度も動いていないとき（IGNORED_MOVE_DEG 未満）に限って、1 回だけ送り直す。

**5. 長押しを離した直後の押し直しが受け付けられない**
- 何が起きるか: 離すと halt の仕事が走ります（通信と 0.3 秒の待ち、全体で 0.4 秒ほど）。その間に逆向きを押すと「前の操作が終わっていません」になり、押し直しが必要です。危険ではありませんが、操作感が悪くなります。
- 直し方: 下の 7 で daemon が待たないと分かれば、`HALT_SETTLE_SEC` を短くする。または halt の後の角度の読み取りを記録だけにする。

**6. 端の手前で止まることがある**
- 場所: 993〜1003 行
- 何が起きるか: 端の目標まで残り 3° 以下になると at_limit で長押しを終え、halt を送ります。そのため、端の 1〜3° 手前で止まることがあります。安全側の動きなので、直さずに記事やログに書いておくだけで十分です。

**7. 実機で確かめること: 離してから stop が届くまでの遅れ**
- 何が起きるか: 走っている仕事が終わるまで halt は出ません。長押しの送信の返事が遅い場合（`rpc.SEND_WAIT` の設定次第で最長 3.7 秒、LOG352）、遅れはその分伸びます。15° はおよそ 0.4 秒で動き切る量なので、stop が着く前に目標に着いてしまうおそれがあります。
- 確かめ方: log.jsonl の `t_halt` と、離した時刻・前の送信の `t_send` の差を一度見る。

**8. daemon_add_stop.sh で失敗を見逃す・戻し方がない箇所がある**
- `cp "$WORK/before.py" "$BK/..."` の結果を見ていません。写しを残せなくても、そのまま Pi のファイルを上書きします。`|| exit 1` を付けます。
- Pi のファイルを `scp` で直接上書きしています。途中で切れると、壊れた daemon が残ります（次に起動したとき立ち上がらない）。一時名で送ってから `mv` で入れ替えるべきです。
- `systemctl restart` の結果と、port が開いたかの待ちは、どちらも失敗しても先へ進みます。最後の確かめが例外で落ちても `記録:` と表示して終わります。失敗したときは、`$BK/mecharm_daemon.py` を戻すコマンドを表示して `exit 1` にします。
- `$ROOT/mecharm_daemon.py` を確かめずに上書きしています。Mac の手元版に Pi と違う変更があれば消えます。上書きする前に `git diff --quiet` で確かめます。
- daemon を起動し直すと MyCobot の初期化が走ります。teleop を止め、アームを安全な姿勢にしてから実行する、と注意書きに足すのがよいです。
