# 依頼 v9：ロボットが地図の上で自分で道を引く（2026-10-24）

いまのロボットは、サイトの道順（routes）の点を順にたどっている。道順は通れる範囲の中の最短の線なので、通路の端に寄った1本になり、行きと帰りが同じ線を走る。広い所でも2台が正面から出会い、回り込めずに60秒止まる（v8b の確かめの③2台・種0：60秒の止まり57回のうち51回がロボットどうし）。戻りの線・横へどく・角の決まりは、これを後から補うために足したもの。

現実の配膳ロボットは、動ける範囲を地図で持ち、道はその都度自分で引き、人や障害物にはセンサーで止まる・回り込む・引き直す。v9 でこの形に変える。サイトは地図を正として渡し（版 2fa10547a2b7 で公開済み）、道は Unity のロボットが地図の上で引く。NavMesh はロボットの道探しには使わない。

## 変えないもの

- ロボットの速さ・加速・減速・向きを変える速さ・前のセンサー・動き出すまでの1秒（RobotSpec の値）
- 卓での25秒、60秒で立ち往生してスタッフを呼ぶこと、スタッフが動かす流れ、1往復の打ち切り600秒と v8b の置き直し
- 止まる位置・受け渡し口の点・待機場所（サイトの JSON のまま）
- 人のよけ方（ロボット＞スタッフ＞客の順。PersonRobotYield）。人がロボットの先の道を見る `PathAhead`・`CurrentLegSegment` は、新しい道を返すようにして残す
- 料理はロボット優先・2分で人が運ぶ、仕事の順番（ServiceOps）
- 細い通路の入口で1台ずつ入る（oneRobotPerAisle・aisleYielding）
- 人に自分をよけてもらうための NavMeshAgent（自動でよける機能は切ったまま）
- v8・v8b で直した所

## 0. サイトの新しい版を写す

ネットから取らず、Mac に置いたここから写す。

```
~/Library/Mobile Documents/com~apple~CloudDocs/デスクトップ/ADAPT DESIGN/じぶんの/PixelstoMotors/デジタルツイン学習R2R2R/サービスロボット導入シミュレーション/unity/site_copy_2fa10547a2b7/
  summary.json
  serving-robot-params.json      （試算 v2.7。版の欄だけ 2fa10547a2b7 にした）
  unity/p1_efficient_corner.json  unity/p1_efficient_corner_map.json
  unity/p5_crammed.json           unity/p5_crammed_map.json   （ほかの店の分もあるが、今は③⑦だけ）
```

- Store/Layouts に summary.json・p1・p5 の JSON と、新しい `_map.json` の2つ。Experiment に serving-robot-params.json
- 店の形の中身は前の版（82023936eb99）と同じ。変わったのは版の番号と、規則の説明文だけ
- 4つの版の番号（summary の generation、店の JSON の meta.generation、地図の meta.generation、params の layout_generation）が、すべて 2fa10547a2b7 でそろっていることを確かめる。そろわなければ回さない（今の確かめに地図を足す）

## 1. 地図を読む

JsonStoreLayout に地図の欄（TextAsset `mapJson`）を足し、店の JSON と一緒に読む。読み方は地図の JSON の `layout`・`encoding` に書いてある。

- `free_class`・`clear_mm` は base64 → gzip を解く（GZipStream）。free_class は1マス1バイト、clear_mm は1マス2バイト（小さい桁が先）
- マス (ix, iy) の中心は x=(ix+0.5)*res、y=(iy+0.5)*res。Unity では x→X、y→Z。配列は iy=0 の行から、1行は ix=0 から nx 個
- free_class：2＝ふだんの道（1.0m）が通れる、1＝通れる幅（0.8m）の道だけ、0＝通れない。clear_mm：いちばん近い障害物までの距離（mm）
- 読んだら確かめる：2・1・0 の数が JSON の `counts` と同じ。受け渡し口の点・全卓の止まる位置が1以上のマス。待機場所の真ん中も1以上（壁ぎわなので1になる）

地図は店の形を変えない。Unity で地図を塗り直したり、通れる所を足したりしない。

## 2. 道の引き方（新しいファイル Robot/GridPlanner.cs。ロボットだけ）

**ふだんの道か狭い道か（試算と同じ規則）**
- 行き先まで2のマスだけで行けるなら、2のマスだけで道を引く
- 行けなければ、1のマスも使って引く。ただし1のマスは重くして（`narrowCellCost`、目安3倍）、狭い所は必要な所だけ通る
- 道に1のマスが入った区間を「狭い道を使った」とする（今の LegWasNarrow・UsedNarrowAisle に入れる）

**通路の真ん中寄りを走る（エイイチの決定）**
- 1歩の重さに、障害物に近いほど重くなる分を足す：`centerWeight × max(0, preferClearM − clear) / preferClearM`。目安は preferClearM 0.6m（1.2mの通路なら真ん中）、centerWeight 2
- まっすぐにつなぐとき（曲がり角の点だけにするとき）は、もとの道で通っていた所より障害物に近づかない所だけで近道する。端に寄った線に戻さないため
- 引いた道の長さは、サイトの距離の決め方を変えない。延びた分は Unity が測る値として記録する

**道探しの決まり**
- 8方向。斜めは両隣が通れるときだけ（サイトと同じ）
- 同じ条件なら毎回同じ道になるようにする（乱数を使わない。同じ重さのときの選び方を決めておく）。のちのち学習に使うため
- 引き直しは1台につき0.5秒に1回まで。1回の時間（ms）を測り、往復ごとの最大を記録する

**前の動き方は切り替えで残す**
- RobotSpec に `pathMode` を足す：`MapPlanner`（新しい・既定）／`SiteRoutes`（今まで）
- 戻りの線（useReturnLine）・横へどく（yieldOrder の横へどく点）・角の箱（cornerMin/Max）は、SiteRoutes のときだけ働く。MapPlanner では使わない
- コードは消さない。前の版と同じ条件で比べられるようにする

## 3. 人や物で前がふさがったとき

- いまのとおり前のセンサーで止まる。空いたら1秒で動き出す
- **止まって3秒たっても前が空かなければ（`detourAfterSec` 3秒。エイイチの決定）、回り込む道を探す**
  - いまロボットの近く（目安3m以内）にいる人・ほかのロボットを、その1回の道探しの間だけ通れないマスとして置く（人は半径＋ロボットの半径＋0.1m、ロボットは2台の半径の和＋0.1m）
  - 道が引ければその道で進む。延びた距離が大きすぎる道（目安：もとの残りの道の2倍＋5m より長い）は取らずに待つ
  - 引けなければ待ち、3秒ごとに探し直す
- 止まる位置そのものに人が立っているときは、回り込まずに待つ
- MapPlanner では、今の「止まって10秒ごとに引き直す」と、途中までの道から通路の入口へ出る（escape）は使わない
- 60秒動けなければ立ち往生（今と同じ）

## 4. ロボットどうし（エイイチの決定：料理を載せている方が先）

**優先の順番**
1. 料理を載せているロボット（卓へ向かっている。卓から次の卓へ向かう間も含む）
2. 空で戻るロボット（受け渡し口・待機場所へ向かっている、待機場所へ動いている）
3. 同じ状態なら1号機が先

**優先が低いロボットのよけ方**
- 道を引くとき、優先が高いロボットの先の道（PathAhead で目安4m分）から、2台の半径の和＋0.2m以内のマスを重くし、相手の体は通れないマスとして置く。相手の道とかぶらない道があれば、そちらを選ぶ
- 引き直すのは、優先が高いロボットが3m以内に来て、先の道がかぶるとき
- かぶらない道が引けないとき：近く（2.5m以内）で、相手の先の道から2台の半径の和＋0.1m以上離れ、自分が入れるマス（1以上）を探し、そこへ動いて相手が通り過ぎるまで待つ。待つのは最大20秒（今の robotYieldMaxSec）。過ぎたら自分の道で進む
- 優先が高いロボットは、相手を気にせず道を引く。ただし前のセンサーで止まり、3秒空かなければ3の回り込みをする（どちらも止まり続けることがないように）
- 2台が向かい合って10秒止まったら、優先が低い方が上のよける場所へ動く

細い通路の入口で1台ずつ入る決まりは今のまま（1.2mの通路に2台は並べない）。

## 5. 記録

trips.csv の後ろに足す（今の列は変えない）：
- `trip_end_s`：往復が実際に終わった店の中の時刻（卓での25秒を含む。v8 の確かめで決めた分。total_s はそのまま）
- `path_mode`：MapPlanner / SiteRoutes
- `replans`：道を引き直した回数（回り込み・よけを含む）
- `detours`：3の回り込みで道を変えた回数
- `robot_yields`：4でよけた回数
- `site_route_m`：その往復でサイトの道順をたどったときの長さ（routes の length の和）
- `detour_extra_m`：走った距離 − site_route_m
- `narrow_used`：狭い道（1のマス）を通ったか
- `plan_ms_max`：その往復で1回の道探しにかかった最大の時間

stops.csv：止まりの解け方に `Detoured`（回り込んで動き出した）と `YieldedToRobot`（よける場所へ動いた）を足す。止まりの原因の分け方は今のまま。
run_info.json：path_mode、地図の版の番号、narrowCellCost・centerWeight・preferClearM・detourAfterSec の値。

## 6. 見せる（測る回でも出してよい。切れるように）

- 各ロボットのいま引いている道を、床の上に細い線で出す（料理を載せているときと空のときで色を変える）
- キッチンの屋根のモニターに、ロボットのいまの様子（走る・止まる・回り込む・よける）を出す

## 7. 確かめ

**A. 人のいない店で（Claude Code が回す）**
③と⑦で、受け渡し口から全卓への道を引く：
- 狭い道を使った卓が、サイトの routes の use（③は0卓、⑦は24卓）と同じ
- 道の長さとサイトの routes の length の比（最小・平均・最大）。真ん中寄りにした分だけ延びるはず
- 道探しの時間（平均・最大）
- 道が引けない卓がない。待機場所へも引ける

**B. 回す（エイイチが押す）**
ServiceSim / Site Run：③ ロボット2台・種0 を1回。
比べる相手は v8b の同じ回（`20261024_p1_efficient_corner_fam_p1_s4_r2_a42_s0_1004`）：
- 60秒の止まりの数と、そのうちロボットどうしの数（v8b：57回・51回）
- 逃がし弁（v8b：あきらめた61・移した54・卓を空けた19）
- 1往復で止まった秒（平均）、ロボットが運んだ皿の割合
- detours・robot_yields・detour_extra_m の平均
- 最後まで回り切る（全員が店を出る）。来た組は v8b と同じ64組

## 報告

- 直したファイルと、変えた所の説明（新しいファイルは全文）
- 確かめ A・B の結果（数字つき）と、出力フォルダ名
- 決めきれなかった値や、この依頼どおりにすると困る所があれば、直す前に書いて止める
- B が最後まで回り切ったら Check-in して番号を報告

段階Bの18回の回し直しは、私（Claude）が B の結果を見てから頼む。
