# サービスロボット導入シミュレーション Log#31 | 配膳ロボットのにらみ合い、解くのは店員だった

- 記録: LOG434（サービスロボット導入シミュレーション）
- 公開: 2026/11/3
- 記事のページ: https://pixelstomotors.com/record/serving-robot-faceoff-staff-resolves
- サイト: Pixels to Motors（ADAPT DESIGN / 島津 栄一） https://pixelstomotors.com/
- 要約: 配膳ロボット2台が狭い通路で向かい合うと、実際の店でもロボットどうしでは解けず、店員が来て手で片方をどかしていた。Unity の店で9分止まった詰まりを30秒で店員が助ける作りに変え、試算のページで店ごとの動画を見られるようにした。

> AI が読むための記事の全文です（コード・実行ログを含む）。引用するときは上の「記事のページ」の住所を使ってください。

---

配膳ロボット2台が狭い通路で正面から向かい合うと、実際の店でもロボットどうしでは解けず、店員が来て手で片方をどかしている。PUDU・KEENON・Bear Robotics などのメーカーは、どちらのロボットが譲るかの決まりを公開しておらず、これまでに集めた実物の動画4本はどれも、店員が来るまで18〜53秒以上かかり、手で直す作業は5秒前後だった。**Unity（ゲームや3Dの映像を作るソフト）で再現したファミレスでは、同じ向かい合いでロボットが9分にらみ合い、最後は料理の取り消しでやっと動いていたので、30秒動けなければ店員が助けに来る作りに変えた。**

この回で、配膳ロボットの導入を考える人に見せられるものが2つ増えました。

- 試算のページ（/series8）で店の形を選ぶと、その店を Unity で動かした動画を見られるようになった。 引きで店全体を見たあと、ロボットが長く止まった場所に寄って何が起きたかを映す。図面のある6店ぶんで、動画は押したときだけ読み込むので試算のページは重くならない
- シミュレーションの詰まり方が、実物の店の運用に近づく。 これまでは2台がにらみ合うと、10分の打ち切りまでだれも助けなかった。30秒で店員が助けに来るようにしたので、ロボットが詰まったときに店員がどれだけ手を取られるかも数字で出るようになる（数字は2026-11-03 の夜の取り直しで出る。いまは見込み）
- 動画の書き出しが速くなった。 字幕付きの動画を1店作るのに約1分36秒かかっていたのが、約27秒になった（同じ店・同じ回で測った値）

きっかけは、試算のページに動画を載せる作業の途中で見た1本の動画でした。2台のロボットが受け渡し口の近くで向かい合い、どちらも道は引けているのに動かない。記録を開くと、1号機が「よける場所」に入って待っているのに、その場所が2号機の進む道をふさいでいて、2台とも相手がどくのを待ち続けていました。では実物の配膳ロボットは、ロボットどうしの譲り合いをどう決めているのか。メーカーの資料・特許・規格と、これまでに集めた実物の動画を調べ直しました。

| やったこと | なぜ | 結果 |
|---|---|---|
| 試算のページに「平面図／Unity で動かすと」の切り替えを足した | 数字だけでは店で何が起きるか伝わらないため | 図面のある6店で、選んだ店の動画が流れる。字幕はスマホでも読める大きさ（1.5倍）に |
| 動画の書き出しを、4K の録画の使う場面へ飛んでから切る形にした | 1店に1分半かかり、Mac 連携の1回の作業時間（3分）に収まらなかったため | 1店 約96秒 → 約27秒。切り出した場面は前と同じ |
| ロボット2台のにらみ合いに、30秒で店員が助けに来る作りを足した | Unity では9分止まり、実物の店は店員が手で解いているため | 2026-11-03 の夜の基準データの取り直し（6店・180回）に最初から入る |
| 実物の配膳ロボットの「ロボットどうしの決まり」を調べた | 将来、ロボット側の作りで詰まりを無くせると言えるようにするため | 取扱説明書で分かるのは2つだけ。譲る側の決め方は特許と、日本の管制の仕組み・規格にあった |

配膳ロボットを自分の店に入れたらどうなるかは、このシリーズの試算をもとにした別のページで試せます。

[配膳ロボットの導入前シミュレーション](https://pixelstomotors.com/serving-robot-one-less-staff)


## 作業ログ ― 試算のページに、店ごとの Unity の動画を入れた

数字の効果は、測ったものは測った値として、まだ測っていないものは見込みとして書きます。

### Unity の動画を、平面図の枠の中の切り替えにした理由

動画の置き場所は2つ考えました。平面図の下に縦に並べる形と、平面図の枠の中で「平面図／Unity で動かすと」を切り替える形です。縦に並べると、試算の操作（人数・台数・使い方を選ぶ所）が約550px下に押し出されるので、切り替えにしました。動画は図面のある店にだけあり、図面のない「標準ファミレス」を選んだときは切り替えが出ません。

- ねらい：試算の数字（入店待ち・料理が届くまで）と、その店で実際に何が起きるかを、同じ画面で見比べられるようにする
- 変わったこと：店の形を選ぶと、平面図の右上に切り替えが出る。押すと同じ枠の中で動画に替わり、そのまま再生が始まる。動画は押すまで読み込まない
- 見込める効果：試算のページを開いただけでは動画を読み込まないので、ページの重さは変わらない。動画が見られたかは、サイトの記録（押したもの）で確かめる

![PCで見た試算のページ。⑦の店を選ぶと平面図が出て、右上に平面図とUnityで動かすとの切り替えがある](https://pixelstomotors.com/media/LOG434_02_pc_plan.png)

![PCで見た試算のページ。Unityで動かすとを押すと、同じ枠の中で⑦の店の動画が流れる](https://pixelstomotors.com/media/LOG434_01_pc_unity.png)

![スマホの幅で見た試算のページ。切り替えのボタンは横いっぱいに並び、動画の下に条件の一言が出る](https://pixelstomotors.com/media/LOG434_03_phone_unity.png)

動画の下には、動画の条件（ふつうの混み具合・その回のスタッフ人数・ロボット2台）が、上で選んでいる試算の条件とは別であることと、Unity の中で起きたことで、実際のロボットや人が同じ動きをするとは限らないことを一言添えました。

### serving-robot.html に足したコード（動画の行・切り替え・読み込み）

動画と表紙の画像は、Cloudflare の [R2](https://www.cloudflare.com/developer-platform/products/r2/)（画像や動画を置いて配るための入れ物）に置き、サイトの `/media/` から配っています。R2 は同じ名前で上書きできないので、作り直したら名前の版（v1 → v2）を上げて、下の `const UNITY` の行を書き換えます。

#### serving-robot.html の script（UNITY の行・unityPane・bigPlan・buildLayouts の全文）

```javascript
// Unity で動かした様子（2026-11-03）。図面のある店ごとに、引き→問題の場所→寄りの動画を1本。
// 条件は Unity の録画の回のもの（試算で選んでいる人数・台数とは別）。動画と表紙の画像は R2（/media/）
const UNITY = {
  fam_p0: { video: "/media/series8_unity_fam_p0_v1.mp4", poster: "/media/series8_unity_fam_p0_v1.jpg", staff: 2, note: "引きで店全体を見たあと、ロボットが長く止まった場所に3か所寄ります（約70秒）" },
  fam_p1: { video: "/media/series8_unity_fam_p1_v1.mp4", poster: "/media/series8_unity_fam_p1_v1.jpg", staff: 4, note: "引きで店全体を見たあと、ロボットが長く止まった場所に3か所寄ります（約70秒）" },
  fam_p2: { video: "/media/series8_unity_fam_p2_v1.mp4", poster: "/media/series8_unity_fam_p2_v1.jpg", staff: 2, note: "引きで店全体を見たあと、ロボットが長く止まった場所に3か所寄ります（約70秒）" },
  fam_p3: { video: "/media/series8_unity_fam_p3_v1.mp4", poster: "/media/series8_unity_fam_p3_v1.jpg", staff: 4, note: "引きで店全体を見たあと、ロボットが長く止まった場所に3か所寄ります（約55秒）" },
  fam_p4: { video: "/media/series8_unity_fam_p4_v1.mp4", poster: "/media/series8_unity_fam_p4_v1.jpg", staff: 3, note: "ロボットが30秒以上止まった場所がなかったので、引きだけです（8秒）" },
  fam_p5: { video: "/media/series8_unity_fam_p5_v1.mp4", poster: "/media/series8_unity_fam_p5_v1.jpg", staff: 4, note: "引きで店全体を見たあと、ロボットが長く止まった場所に3か所寄ります（約70秒）" }
};
let planView = "plan";
function unityPane(k) {
  const u = UNITY[k]; if (!u) return "";
  return `<div class="pv-unity"${planView === "unity" ? "" : " hidden"}>`
    + `<video controls muted playsinline preload="none" poster="${u.poster}" data-src="${u.video}" aria-label="${STORES[k].short}を Unity で動かした様子"></video>`
    + `<p class="pn">${u.note}。動画の条件はふつうの混み具合・スタッフ${u.staff}人・ロボット2台で、上で選んでいる条件とは別です。Unity の中で起きたことで、実際のロボットや人が同じ動きをするとは限りません。</p></div>`;
}
function bigPlan(k) {
  const s = STORES[k], p = PLANS[k];
  if (!p) return "";
  const seats = p.tables.reduce((n, a) => n + a[4], 0);
  const u = UNITY[k], v = u ? planView : "plan";
  const sw = u ? `<div class="pv-sw" role="tablist" aria-label="平面図と動画"><button type="button" role="tab" data-pv="plan" aria-selected="${v === "plan"}">平面図</button><button type="button" role="tab" data-pv="unity" aria-selected="${v === "unity"}">Unity で動かすと</button></div>` : "";
  return `<div class="plan-big"><div class="ph"><b>${s.short}</b><span>${s.tables}卓・${seats}席・外形 ${p.w}m×${p.d}m・厨房（受け渡し口）から卓まで ${s.dist_m[0]}〜${s.dist_m[1]}m（平均${s.dist_mean}m）</span>${sw}</div>`
    + `<div class="pv-plan"${v === "plan" ? "" : " hidden"}>` + bigPlanSvg(p)
    + `<p class="pn">白い四角が卓（数字は席数）、黒い帯が料理の受け渡し口、点線がロボットの待機場所、灰色の線が壁・仕切りです。寸法はすべて仮の値です。</p></div>`
    + unityPane(k) + `</div>`;
}
function buildLayouts() {
  const el = document.getElementById("layouts");
  const base = baseOf(state.store);
  const on = LAYOUT_BASES.includes(base);
  el.hidden = !on;
  if (!on) return;
  const fam = STORES[base];
  const items = [[base, base === "family" ? "① 図面なし" : "図面なし", `${fam.tables}卓・${fam.dist_m[0]}〜${fam.dist_m[1]}m`, `<div class="noplan">距離の幅だけで試算</div>`]]
    .concat(LAYOUT_KEYS.filter(k => baseOf(k) === base).map(k => { const s = STORES[k]; return [k, s.short, `${s.tables}卓・${s.dist_m[0]}〜${s.dist_m[1]}m`, PLANS[k] ? planSvg(PLANS[k]) : ""]; }));
  el.innerHTML = `<p class="lay-h">店の形を選ぶ（${fam.label}）。平面図から、卓の数と厨房から卓までの距離を取ります</p>`
    + `<div class="lay-grid" role="radiogroup" aria-label="店の形">`
    + items.map(([k, name, sub, pic], i) => `<input type="radio" name="layout" id="layout-${i}" value="${k}"${state.store === k ? " checked" : ""}><label for="layout-${i}">${pic}<b>${name}</b><span>${sub}</span></label>`).join("")
    + `</div>` + (isLayout(state.store) ? bigPlan(state.store) : "");
  // 動画を見ている状態で店を替えたら、替えた先の店の動画をそのまま読み込んで流す（2026-11-03）
  const vid = planView === "unity" && el.querySelector(".pv-unity:not([hidden]) video");
  if (vid) { vid.src = vid.dataset.src; vid.play().catch(() => {}); }
  el.onchange = e => { state.store = e.target.value; state.staff = S().staff[1]; state.cbase = S().staff[1]; state.reg = S().registers; buildStores(); buildLayouts(); buildControls(); render(); };
  // 平面図と動画の切り替え。動画は押したときに初めて読み込み、そのまま再生する
  el.onclick = e => {
    const b = e.target.closest("[data-pv]"); if (!b) return;
    planView = b.dataset.pv;
    const box = b.closest(".plan-big");
    box.querySelectorAll("[data-pv]").forEach(x => x.setAttribute("aria-selected", String(x === b)));
    box.querySelector(".pv-plan").hidden = planView !== "plan";
    const pane = box.querySelector(".pv-unity"); pane.hidden = planView !== "unity";
    const vid = pane.querySelector("video");
    if (planView === "unity") { if (!vid.src) vid.src = vid.dataset.src; vid.play().catch(() => {}); } else vid.pause();
  };
}
```

#### serving-robot.html の style（足した部分の全文）

```css
.plan-big .ph .pv-sw { margin-left: auto; display: inline-grid; grid-auto-flow: column; gap: 2px; padding: 3px; border-radius: 8px; background: #e3e3df; }
.pv-sw button { border: 0; border-radius: 6px; padding: 6px 12px; background: transparent; font: inherit; font-size: 13px; font-weight: 600; color: #555; cursor: pointer; white-space: nowrap; }
.pv-sw button[aria-selected="true"] { background: #111; color: #fff; }
.pv-sw button:focus-visible { outline: 2px solid #111; outline-offset: 2px; }
.pv-unity video { display: block; width: 100%; aspect-ratio: 16 / 9; background: #3a3a3a; border-radius: 4px; }
.pv-plan[hidden], .pv-unity[hidden] { display: none; }
@media (max-width: 599px) { .plan-big .ph .pv-sw { margin-left: 0; width: 100%; grid-auto-columns: 1fr; } }
```

#### p2m-site の README.md に足した節

```markdown
## 試算の「Unity で動かすと」の動画（/series8・2026-11-03）

配膳ロボットの試算（`public/sim/serving-robot.html`）で図面のある店を選ぶと、平面図の右上に「平面図／Unity で動かすと」の切り替えが出る。押すとその店を Unity で動かした動画（引き→長く止まった場所→寄り）に替わり、そのとき初めて動画を読み込んで再生する。
- 動画と表紙の画像：R2 の `series8_unity_{店}_{版}.mp4`・`.jpg`（店は fam_p0〜fam_p5・large_p6・large_p7）。R2 は同じ名前で上書きできないので、作り直したら版（v1→v2）を上げる
- 作り方：KIOXIA の `r2r2r-data/service_sim/unity/series8_web_{日付}/make_web.sh`（iCloud の `サービスロボット導入シミュレーション/unity/focus_clips/focus_clips.py` を字幕1.5倍で回す）。できた `upload_{版}/` を `tools/upload_media.py` で R2 に入れる
- 差し替え：`serving-robot.html` の `const UNITY` の行（動画・表紙・その回のスタッフ人数・説明の一文）を直す。行がない店には切り替えが出ない
```

### 字幕を1.5倍にした（スマホの幅で読めなかったため）

最初の試作をスマホの幅（390px）で見ると、動画の中の字幕（「寄り：ロボットどうしが道をふさぎ合って止まる」など）が小さすぎて読めませんでした。動画を作るスクリプト（focus_clips.py）に字幕の倍率を足し、サイト用は1.5倍で書き出しています。倍率を指定しなければ今までと同じ大きさなので、Claude Code が動かしている撮り直しの処理（依頼75）の出力は変わりません。問題の場所に付ける輪の大きさには倍率をかけていません。

![同じ場面の字幕。左が今までの大きさ、右がサイト用に1.5倍にした大きさ](https://pixelstomotors.com/media/LOG434_04_subtitle_before_after.jpg)

### 4K の録画を頭から読まない形にした（1店 96秒 → 27秒）

最初に6店をまとめて書き出したとき、1店に約1分36秒かかりました。動画は、4K（3840×2160）で撮った引きの録画から場面を切り出して作ります。ffmpeg（動画を変換・切り出しする道具）の切り出しの指定が「頭から全部読んで、使う場面だけ残す」形になっていて、店の時刻で2時間を超える録画を毎回頭から読んでいました。

- ねらい：Mac 連携の1回の作業（最長3分）の中で、何店かをまとめて書き出せるようにする
- 変わったこと：使う場面の手前まで飛んでから読み始める形（`-ss` を入力の前に置く）にした。録画は1秒30コマなので、コマ番号を30で割った秒へ飛ぶ。書き出し直すので、コマ単位でずれない
- 見込める効果ではなく測った値：③の店で 約96秒 → 約27秒。直す前と後で同じ場面が切り出されることを、映像のずれの大きさ（PSNR）で確かめた

PSNR は、2つの映像がどれだけ同じかを表す数字で、大きいほど同じ、inf（無限大）なら1コマ残らず同じです。寄りの2本は完全に同じで、引きの1本（p00_open）は 42.9 でした。40 を超えると目で見て違いは分かりません。

#### 直す前と後の書き出しの比べ（③・fam_p1）

```text
（直す前）
== fam_p1 22:00:14
1 ('ロボットどうしが道をふさぎ合って止まる', '店の時刻 23:59〜33:38・止まった合計 600秒（10回）') (20.6, 3.8)
2 ('スタッフとすれ違えず、ロボットが止まる', '店の時刻 100:47〜109:08・止まった合計 334秒（6回）') (5.8, 4.3)
3 ('ロボットどうしが道をふさぎ合って止まる', '店の時刻 43:40〜47:24・止まった合計 176秒（3回）') (16.5, 5.9)
OK fam_p1/focus_v1/focus_trial.mp4
OK fam_p1 22:01:50

（直した後）
OK fam_p1/focus_v1/focus_trial.mp4

real	0m26.857s
user	0m56.834s
sys	0m9.082s

（同じ場面か：直した後と直す前の PSNR）
== p00_open
[Parsed_psnr_0 @ 0xbe9ca599d2b0] PSNR y:41.377981 u:49.483028 v:56.007633 average:42.938245 min:37.412721 max:63.142575
== p01b_focus
[Parsed_psnr_0 @ 0xca35816bde20] PSNR y:inf u:inf v:inf average:inf min:inf max:inf
== p03b_focus
[Parsed_psnr_0 @ 0xb71877932e20] PSNR y:inf u:inf v:inf average:inf min:inf max:inf
```

#### focus_clips.py（字幕の倍率と、場面へ飛んでから切る形を足した版の全文）

```python
"""問題に寄る動画を作る（サービスロボット導入シミュレーション・2026-11-02）

カメラの目的を分ける（エイイチと決めた）
- 引き：全体の流れを見る（60°見下ろし・左30°／右30°の店全体）
- 寄り：問題が起きている所を見る（ロボットが動けなくなった・長く止まった所と時刻）

問題がどこでいつ起きるかは回してみないと分からないので、1回の回で引きを高解像度（3840×2160）で撮り、
その回の stops.csv から問題を拾って、引きの映像から切り出して寄りを作る（寄りのカメラを別に置かない）。

流れ
1. stops.csv の止まりのうち、長いもの（MIN_STOP 秒以上、または「動けなくなった」）を拾う
2. 同じ所（SAME_M 以内）で続けて起きた止まりを1つの「問題」にまとめ、止まっていた秒の合計で並べる
3. 上位 TOP 件それぞれについて：
   - 引き：問題が始まる少し前から、その場所に輪を付けて見せる（どこで起きたか）
   - 寄り：問題の場所を中心に 1280×720 で切り出す（何が起きたか）。左30°・右30°のうち、その場所が画面の真ん中に近い方を使う
4. 引き（全体の流れ）→ 問題ごとに［引きで場所 → 寄り］をつなぐ

使い方: python3 focus_clips.py <録画のフォルダ> <回のフォルダ> <出力フォルダ>
  要るもの：python3・numpy・Pillow・ffmpeg。問題の数は FOCUS_TOP（既定 3）、字は FOCUS_FONT、字幕の大きさは FOCUS_TEXT_SCALE（既定 1.0・サイト用は 1.5）で替えられる
  問題が1つもない回（30秒以上の止まりがない）は、引きだけの動画になる
  録画のフォルダ：{名前}.mp4・{名前}_frames.csv・{名前}_camera.json（wide_l30・wide_r30）
"""
import csv
import json
import os
import math
import subprocess
import sys
from pathlib import Path

import numpy as np
from PIL import Image, ImageDraw, ImageFont

MIN_STOP = 30.0      # これ以上の止まりを問題として拾う（秒・店の時刻）
SAME_M = 1.5         # この距離以内の止まりは同じ所
SAME_GAP = 120.0     # 前の止まりが終わってからこの秒以内なら同じ問題
TOP = int(os.environ.get("FOCUS_TOP", "3"))   # 動画にする問題の数
# 字幕の大きさの倍率（2026-11-03）。サイト（/series8）に載せる版はスマホの幅で字が小さすぎたので 1.5 で書き出す
TEXT_SCALE = float(os.environ.get("FOCUS_TEXT_SCALE", "1.0"))
CROP = (1280, 720)   # 寄りの切り出しの大きさ（3840×2160 の 1/3）
LEAD = 10.0          # 問題が始まる前から見せる秒（店の時刻）
TAIL = 6.0
MAX_FOCUS = 120.0    # 寄りの長さの上限（店の時刻）
WIDE_SEC = 3.0       # 問題ごとの「引きで場所」の長さ（動画の秒）
import os
# 字（Linux の Noto、Mac のヒラギノ）。見つかった最初のものを使う
FONT = next((f for f in (os.environ.get("FOCUS_FONT", ""),
                         "/usr/share/fonts/opentype/noto/NotoSansCJK-Bold.ttc",
                         "/System/Library/Fonts/ヒラギノ角ゴシック W6.ttc",
                         "/System/Library/Fonts/Hiragino Sans GB.ttc",
                         "/Library/Fonts/Arial Unicode.ttf") if f and os.path.exists(f)), None)
CAMS = ("wide_l30", "wide_r30")


def load_cam(rec, name):
    J = json.load(open(next(rec.glob(f"*{name}_camera.json")), encoding="utf-8"))
    V = np.array(J["worldToCameraMatrix"], dtype=float).reshape(4, 4)
    P = np.array(J["projectionMatrix"], dtype=float).reshape(4, 4)
    W, H = J["width"], J["height"]
    mp4 = next(f for f in rec.glob(f"*{name}.mp4"))
    frames = [(int(r["frame"]), float(r["t_s"])) for r in csv.DictReader(open(next(rec.glob(f"*{name}_frames.csv"))))]

    def to_px(x, y):
        """店の座標 (x, y)（床）→ 画面の画素 (u, v)。Unity は x→X、y→Z、床の高さ Y=0"""
        c = P @ V @ np.array([x, 0.0, y, 1.0])
        nx, ny = c[0] / c[3], c[1] / c[3]
        return (nx + 1) / 2 * W, (1 - ny) / 2 * H

    return dict(name=name, mp4=mp4, W=W, H=H, frames=frames, to_px=to_px)


def t_to_frame(frames, t):
    return min(frames, key=lambda f: abs(f[1] - t))[0]


def episodes(run):
    rows = list(csv.DictReader(open(run / "stops.csv", encoding="utf-8")))
    ev = [r for r in rows if float(r["duration_s"]) >= MIN_STOP or r["resolved"] == "動けなくなった"]
    ev.sort(key=lambda r: float(r["t_s"]))
    eps = []
    for r in ev:
        t0, d = float(r["t_s"]), float(r["duration_s"])
        x, y = float(r["x"]), float(r["z"])
        for e in eps:
            if math.hypot(e["x"] - x, e["y"] - y) <= SAME_M and t0 - e["t1"] <= SAME_GAP:
                e["t1"] = max(e["t1"], t0 + d)
                e["total"] += d
                e["n"] += 1
                e["robots"].add(r["robot_id"])
                e["causes"][r["cause"]] = e["causes"].get(r["cause"], 0) + d
                e["stuck"] += r["resolved"] == "動けなくなった"
                break
        else:
            eps.append(dict(x=x, y=y, t0=t0, t1=t0 + d, total=d, n=1, robots={r["robot_id"]},
                            causes={r["cause"]: d}, stuck=int(r["resolved"] == "動けなくなった")))
    eps.sort(key=lambda e: -e["total"])
    return eps


def mmss(t):
    return f"{int(t // 60)}:{int(t % 60):02d}"


def caption(e):
    cause = max(e["causes"], key=e["causes"].get)
    what = {"もう1台のロボット": "ロボットどうしが道をふさぎ合って止まる",
            "人が立ち止まっていた": "人が立ち止まっていて、ロボットが止まる",
            "お客さんとすれ違った": "お客さんとすれ違えず、ロボットが止まる",
            "スタッフとすれ違った": "スタッフとすれ違えず、ロボットが止まる"}.get(cause, f"ロボットが止まる（{cause}）")
    return what, f"店の時刻 {mmss(e['t0'])}〜{mmss(e['t1'])}・止まった合計 {int(e['total'])}秒（{e['n']}回）"


def overlay_png(path, W, H, title, sub, ring=None, ring_r=60):
    im = Image.new("RGBA", (W, H), (0, 0, 0, 0))
    d = ImageDraw.Draw(im)
    s0 = W / 1280          # 輪の大きさはこちら（字幕の倍率をかけない）
    s = s0 * TEXT_SCALE    # 字幕の字・余白・帯の高さ
    if FONT is None:
        raise SystemExit("日本語の字が見つからない。FOCUS_FONT に .ttc/.ttf の場所を入れて回す")
    f1, f2 = ImageFont.truetype(FONT, int(30 * s)), ImageFont.truetype(FONT, int(22 * s))
    pad = int(14 * s)
    tw = max(d.textlength(title, font=f1), d.textlength(sub, font=f2)) + 2 * pad
    d.rectangle([0, 0, tw, int(86 * s)], fill=(17, 17, 17, 210))
    d.text((pad, int(8 * s)), title, font=f1, fill=(245, 245, 240, 255))
    d.text((pad, int(50 * s)), sub, font=f2, fill=(220, 220, 214, 255))
    if ring:
        u, v = ring
        r = ring_r * s0
        d.ellipse([u - r, v - r, u + r, v + r], outline=(232, 89, 12, 255), width=max(3, int(6 * s0)))
    im.save(path)


def run_ff(args):
    subprocess.run(["ffmpeg", "-y", "-loglevel", "error", *args], check=True)


def clip(cam, f0, f1, out, crop=None, ov=None, speed=1.0):
    # 先頭から読まずに、始まりの少し手前へ飛んでから切る（2026-11-03。4K を頭から読むと1本に数分かかっていた）
    # 録画は 30 コマ/秒なので、コマ番号 f0 は f0/30 秒。-ss を入力の前に置いても、書き出し直すのでコマ単位で合う
    ss = f0 / 30
    vf = [f"trim=end_frame={f1 - f0}", "setpts=PTS-STARTPTS"]
    if speed != 1.0:
        vf.append(f"setpts={1 / speed}*PTS")
    if crop:
        cx, cy = crop
        vf.append(f"crop={CROP[0]}:{CROP[1]}:{cx}:{cy}")
    else:
        vf.append(f"scale={CROP[0]}:{CROP[1]}")
    chain = ",".join(vf)
    if ov:
        run_ff(["-ss", f"{ss:.4f}", "-i", str(cam["mp4"]), "-i", str(ov), "-filter_complex",
                f"[0:v]{chain}[b];[b][1:v]overlay=0:0,format=yuv420p,fps=30[v]", "-map", "[v]",
                "-c:v", "libx264", "-preset", "veryfast", "-crf", "20", str(out)])
    else:
        run_ff(["-ss", f"{ss:.4f}", "-i", str(cam["mp4"]), "-vf", chain + ",format=yuv420p,fps=30", "-c:v", "libx264", "-preset", "veryfast", "-crf", "20", str(out)])


def main():
    rec, run, out = map(Path, sys.argv[1:4])
    out.mkdir(parents=True, exist_ok=True)
    cams = {n: load_cam(rec, n) for n in CAMS}
    eps = episodes(run)
    json.dump([{**e, "robots": sorted(e["robots"])} for e in eps], open(out / "episodes.json", "w", encoding="utf-8"), ensure_ascii=False, indent=1)
    parts = []
    # 引き：全体の流れ（左30°・回の最初の問題の少し前から 8 秒）
    L = cams["wide_l30"]
    t_open = max(L["frames"][0][1], (min(e["t0"] for e in eps[:TOP]) if eps else L["frames"][0][1] + 900) - 120)
    f0 = t_to_frame(L["frames"], t_open)
    ov = out / "ov_open.png"
    overlay_png(ov, CROP[0], CROP[1], "引き：店全体の流れ", "60°見下ろし・左30°")
    clip(L, f0, f0 + 240, out / "p00_open.mp4", ov=ov)
    parts.append(out / "p00_open.mp4")
    for i, e in enumerate(eps[:TOP], 1):
        # その場所が画面の真ん中に近いカメラを使う
        best = min(cams.values(), key=lambda c: math.hypot(c["to_px"](e["x"], e["y"])[0] - c["W"] / 2, c["to_px"](e["x"], e["y"])[1] - c["H"] / 2))
        u, v = best["to_px"](e["x"], e["y"])
        what, sub = caption(e)
        # 引きで場所（輪を付ける）
        fs = t_to_frame(best["frames"], e["t0"] - LEAD)
        ov = out / f"ov_{i}_wide.png"
        k = CROP[0] / best["W"]
        overlay_png(ov, CROP[0], CROP[1], f"問題{i}：ここで起きる", "引き・" + ("左30°" if best["name"].endswith("l30") else "右30°"), ring=(u * k, v * k), ring_r=40)
        clip(best, fs, fs + int(WIDE_SEC * 30), out / f"p{i:02d}a_wide.mp4", ov=ov)
        # 寄り：問題の場所を中心に切り出す
        cx = int(min(max(u - CROP[0] / 2, 0), best["W"] - CROP[0]))
        cy = int(min(max(v - CROP[1] / 2, 0), best["H"] - CROP[1]))
        f0 = t_to_frame(best["frames"], e["t0"] - LEAD)
        f1 = t_to_frame(best["frames"], min(e["t1"] + TAIL, e["t0"] + MAX_FOCUS))
        ov = out / f"ov_{i}_focus.png"
        overlay_png(ov, CROP[0], CROP[1], f"寄り：{what}", sub, ring=(u - cx, v - cy), ring_r=70)
        clip(best, f0, f1, out / f"p{i:02d}b_focus.mp4", crop=(cx, cy), ov=ov, speed=0.5)
        parts += [out / f"p{i:02d}a_wide.mp4", out / f"p{i:02d}b_focus.mp4"]
    lst = out / "list.txt"
    lst.write_text("".join(f"file '{p.name}'\n" for p in parts))
    run_ff(["-f", "concat", "-safe", "0", "-i", str(lst), "-c", "copy", str(out / "focus_trial.mp4")])
    for i, e in enumerate(eps[:TOP], 1):
        print(i, caption(e), (round(e["x"], 1), round(e["y"], 1)))
    print("OK", out / "focus_trial.mp4")


if __name__ == "__main__":
    main()
```

### make_web.sh で6店を書き出して R2 に入れた

6店の動画は、サイト用の名前（`series8_unity_{店}_v1.mp4` と表紙の `.jpg`）で書き出しました。最初は2つの理由で失敗しています。1つは、外付けの SSD（KIOXIA。exFAT という形式）の上で書いた画像を、すぐあとに ffmpeg が「見つからない」と言ったこと。作業は Mac 連携の作業場所の中の一時フォルダで行い、最後に KIOXIA へ写す形に変えました。もう1つは、裏で動かした書き出しが、Mac 連携の1回の作業が終わると一緒に止まったこと。こちらは上の速くした版で、1回の作業の中で数店ずつ回して済ませました。

#### make_web.sh（全文）

```bash
#!/bin/bash
# /series8 に載せる「Unity で動かすと」の動画を作る（2026-11-03）。字幕 1.5 倍で focus_clips.py を回し、
# サイト用の名前（series8_unity_{店}_{版}.mp4・.jpg）に書き出す。使い方: make_web.sh <版> <店 録画のフォルダ 回のフォルダ>...
D=${OUTD:?}; S="$(cd "$(dirname "$0")" && pwd)"; V=$1; shift
# KIOXIA（exFAT）の上で書いてすぐ ffmpeg に読ませると見つからないことがあるので、作業は手元の一時フォルダで行い、最後に写す
T=$HOME/s8work; mkdir -p $T; cd $T
export FOCUS_TEXT_SCALE=1.5 FOCUS_FONT=/usr/share/fonts/opentype/noto/NotoSansCJK-Bold.ttc
mkdir -p upload_$V
while [ $# -ge 3 ]; do
  k=$1 rec=$2 run=$3; shift 3
  echo "== $k $(date +%T)"
  python3 "$S/focus_clips.py" "$rec" "$run" "$k/focus_$V" || { echo "NG $k"; continue; }
  ffmpeg -v error -y -i "$k/focus_$V/focus_trial.mp4" -c copy -movflags +faststart "upload_$V/series8_unity_${k}_$V.mp4"
  ffmpeg -v error -y -ss 1.5 -i "upload_$V/series8_unity_${k}_$V.mp4" -frames:v 1 -q:v 3 "upload_$V/series8_unity_${k}_$V.jpg"
  mkdir -p "$D/$k" "$D/upload_$V"; cp -r "$k/focus_$V" "$D/$k/"; cp upload_$V/series8_unity_${k}_$V.* "$D/upload_$V/"
  echo "OK $k $(date +%T)"
done
echo DONE
```

#### 最初の書き出しのログ（⑦の途中で、裏の処理が止まった）

```text
== fam_p1 22:00:14
1 ('ロボットどうしが道をふさぎ合って止まる', '店の時刻 23:59〜33:38・止まった合計 600秒（10回）') (20.6, 3.8)
2 ('スタッフとすれ違えず、ロボットが止まる', '店の時刻 100:47〜109:08・止まった合計 334秒（6回）') (5.8, 4.3)
3 ('ロボットどうしが道をふさぎ合って止まる', '店の時刻 43:40〜47:24・止まった合計 176秒（3回）') (16.5, 5.9)
OK fam_p1/focus_v1/focus_trial.mp4
OK fam_p1 22:01:50
== fam_p5 22:01:50
```

#### 速くした版で残りの4店を書き出したログ

```text
== fam_p2 22:12:31
OK fam_p2/focus_v1/focus_trial.mp4
OK fam_p2 22:12:57
== fam_p3 22:12:57
OK fam_p3/focus_v1/focus_trial.mp4
OK fam_p3 22:13:20
== fam_p4 22:13:20
OK fam_p4/focus_v1/focus_trial.mp4
OK fam_p4 22:13:24
== fam_p0 22:13:24
OK fam_p0/focus_v1/focus_trial.mp4
OK fam_p0 22:13:49
DONE
```

#### 書き出した動画の長さと大きさ・R2 に入れたときのログ

```text
upload_v1/series8_unity_fam_p0_v1.mp4 1315020 68.669000
upload_v1/series8_unity_fam_p1_v1.mp4 1429350 68.669000
upload_v1/series8_unity_fam_p2_v1.mp4 1478114 68.669000
upload_v1/series8_unity_fam_p3_v1.mp4 1284319 53.536000
upload_v1/series8_unity_fam_p4_v1.mp4 55098 7.967000
upload_v1/series8_unity_fam_p5_v1.mp4 1630210 68.669000

total 12800
drwx------ 1 rcw-01levexvdomwvaskecpngafe rcw-01levexvdomwvaskecpngafe  524288 Nov  2 22:13 .
drwx------ 1 rcw-01levexvdomwvaskecpngafe rcw-01levexvdomwvaskecpngafe  524288 Nov  2 22:14 ..
-rwx------ 1 rcw-01levexvdomwvaskecpngafe rcw-01levexvdomwvaskecpngafe   58370 Nov  2 22:14 series8_unity_fam_p0_v1.jpg
-rwx------ 1 rcw-01levexvdomwvaskecpngafe rcw-01levexvdomwvaskecpngafe 1315020 Nov  2 22:14 series8_unity_fam_p0_v1.mp4
-rwx------ 1 rcw-01levexvdomwvaskecpngafe rcw-01levexvdomwvaskecpngafe   62443 Nov  2 22:14 series8_unity_fam_p1_v1.jpg
-rwx------ 1 rcw-01levexvdomwvaskecpngafe rcw-01levexvdomwvaskecpngafe 1429350 Nov  2 22:14 series8_unity_fam_p1_v1.mp4
-rwx------ 1 rcw-01levexvdomwvaskecpngafe rcw-01levexvdomwvaskecpngafe   60083 Nov  2 22:14 series8_unity_fam_p2_v1.jpg
-rwx------ 1 rcw-01levexvdomwvaskecpngafe rcw-01levexvdomwvaskecpngafe 1478114 Nov  2 22:14 series8_unity_fam_p2_v1.mp4
-rwx------ 1 rcw-01levexvdomwvaskecpngafe rcw-01levexvdomwvaskecpngafe   65819 Nov  2 22:14 series8_unity_fam_p3_v1.jpg
-rwx------ 1 rcw-01levexvdomwvaskecpngafe rcw-01levexvdomwvaskecpngafe 1284319 Nov  2 22:14 series8_unity_fam_p3_v1.mp4
-rwx------ 1 rcw-01levexvdomwvaskecpngafe rcw-01levexvdomwvaskecpngafe   57747 Nov  2 22:14 series8_unity_fam_p4_v1.jpg
-rwx------ 1 rcw-01levexvdomwvaskecpngafe rcw-01levexvdomwvaskecpngafe   55098 Nov  2 22:14 series8_unity_fam_p4_v1.mp4
-rwx------ 1 rcw-01levexvdomwvaskecpngafe rcw-01levexvdomwvaskecpngafe   62563 Nov  2 22:14 series8_unity_fam_p5_v1.jpg
-rwx------ 1 rcw-01levexvdomwvaskecpngafe rcw-01levexvdomwvaskecpngafe 1630210 Nov  2 22:14 series8_unity_fam_p5_v1.mp4
入れる 12・入っている 0・名前の重なり 0・合計 7.6MB
  12/12
おわり
200 58370
200 62563
```

⑥（fam_p4）は、30秒以上止まった場所も動けなくなった所もなかったので、引きだけの8秒の動画です。動画に寄った3か所は、店ごとに次のとおりでした（x・y は店の中の位置 m、t0〜t1 は店の時刻の秒）。

#### 店ごとに寄った上位3か所（episodes.json から）

```text
== fam_p0
12.3 5.5 4812 4949 120 2 2 {'もう1台のロボット': 120.0988}
11.3 5.4 6085 6294 120 2 2 {'もう1台のロボット': 120.0988}
12.3 5.5 4316 4490 88 2 0 {'もう1台のロボット': 87.89920000000001}
== fam_p1
20.6 3.8 1440 2019 600 10 10 {'もう1台のロボット': 600.494}
5.8 4.3 6048 6548 334 6 5 {'スタッフとすれ違った': 334.09680000000003}
16.5 5.9 2621 2844 176 3 2 {'もう1台のロボット': 176.0982}
== fam_p2
3.6 2.3 1258 1788 600 10 10 {'もう1台のロボット': 240.1976, '人が立ち止まっていた': 120.0988, 'スタッフとすれ違った': 240.1976}
8.9 2.4 3996 4498 600 10 10 {'もう1台のロボット': 180.1482, '人が立ち止まっていた': 240.1976, '椅子・荷物・ベビーカー': 120.0988, 'お客さんとすれ違った': 60.0494}
7.1 2.3 5256 5764 540 9 9 {'もう1台のロボット': 420.3458, 'お客さんとすれ違った': 120.0988}
== fam_p3
13.4 8.9 2746 2857 120 2 2 {'スタッフとすれ違った': 60.0494, '人が立ち止まっていた': 60.0494}
14.3 8.8 4872 4932 60 1 1 {'もう1台のロボット': 60.0494}
15.1 8.8 2420 2477 57 1 0 {'スタッフとすれ違った': 56.9994}
== fam_p4
== fam_p5
20.6 4.0 1473 2021 762 13 11 {'もう1台のロボット': 761.5424}
1.8 6.4 2126 2383 180 3 3 {'スタッフとすれ違った': 120.0988, '人が立ち止まっていた': 60.0494}
1.8 8.4 4257 4477 164 3 2 {'お客さんとすれ違った': 164.3484}
```

### 公開後に、PC とスマホの幅で動画が流れることを確かめた

Playwright（ブラウザを自動で動かす道具）で公開したページを開き、⑦（PC の幅）と②（スマホの幅）で切り替えを押して、3秒あまりで再生が進んでいることを確かめました。

```text
1280 {
  src: 'https://pixelstomotors.com/media/series8_unity_fam_p5_v1.mp4',
  t: '3.3',
  paused: false,
  w: 1280
}
390 {
  src: 'https://pixelstomotors.com/media/series8_unity_fam_p0_v1.mp4',
  t: '3.2',
  paused: false,
  w: 1280
}
```

### 動画を見たまま店を切り替えると流れなかった不具合

公開して約30分後、「Unity で動かすと」を押したまま店の形を切り替えると、動画が流れないと分かりました。下の画面は、③で動画を見たあと④に切り替えたところで、表紙の絵は出ているのに 0:00 のまま止まっています。

![動画を見たまま④に切り替えた画面。表紙の絵は出ているが、再生は0:00のまま始まらない](https://pixelstomotors.com/media/LOG434_05_switch_not_playing.png)

店を切り替えると平面図の枠を作り直します。動画を読み込む処理が「切り替えを押したとき」にしか動かない作りだったので、作り直した枠の動画は表紙だけになっていました。動画を見ている状態で店を替えたときにも読み込むようにしました（上の buildLayouts の全文に入っています）。

#### 直した所（git の差分）

```diff
diff --git a/public/sim/serving-robot.html b/public/sim/serving-robot.html
index ee449da..ae6c684 100644
--- a/public/sim/serving-robot.html
+++ b/public/sim/serving-robot.html
@@ -540,6 +540,9 @@ function buildLayouts() {
     + `<div class="lay-grid" role="radiogroup" aria-label="店の形">`
     + items.map(([k, name, sub, pic], i) => `<input type="radio" name="layout" id="layout-${i}" value="${k}"${state.store === k ? " checked" : ""}><label for="layout-${i}">${pic}<b>${name}</b><span>${sub}</span></label>`).join("")
     + `</div>` + (isLayout(state.store) ? bigPlan(state.store) : "");
+  // 動画を見ている状態で店を替えたら、替えた先の店の動画をそのまま読み込んで流す（2026-11-03）
+  const vid = planView === "unity" && el.querySelector(".pv-unity:not([hidden]) video");
+  if (vid) { vid.src = vid.dataset.src; vid.play().catch(() => {}); }
   el.onchange = e => { state.store = e.target.value; state.staff = S().staff[1]; state.cbase = S().staff[1]; state.reg = S().registers; buildStores(); buildLayouts(); buildControls(); render(); };
   // 平面図と動画の切り替え。動画は押したときに初めて読み込み、そのまま再生する
   el.onclick = e => {
```

#### 直したあとの確かめ（公開したページで③ → ④・⑥・② と切り替え）

```text
fam_p2 { src: 'series8_unity_fam_p2_v1.mp4', t: '2.5', paused: false }
fam_p4 { src: 'series8_unity_fam_p4_v1.mp4', t: '2.3', paused: false }
fam_p0 { src: 'series8_unity_fam_p0_v1.mp4', t: '2.6', paused: false }
```

いま載っている動画は、客が急に席に現れる不具合（瞬間移動）を直す前の回のものです。直した版の撮り直し（依頼75）がそろった店から入れ替えます。入れ替えた結果は、この記事に追記します。


## 作業ログ ― 配膳ロボット2台のにらみ合いを、30秒で店員が助ける形にした

### ⑦の店で、1号機と2号機が約9分止まっていた記録

⑦の店（42卓・通路を95cmにした店）の夜の回で、ロボットが受け渡し口の右下（店の中の位置 x 20.4〜20.6m・y 4.0〜5.1m）で、店の時刻 1472秒から 2022秒まで、約9分10秒止まっていました。記録（stops.csv）を見ると、2台とも道は引けている（solve_status が「引けた」）のに、動けなくなった印が60秒ごとに並ぶだけで、だれも助けに来ていません。1号機は「よける場所で待った」と書かれていますが、その場所が2号機の進む道の上でした。

詰まりが解けたのは、2号機が運んでいた料理が600秒待って打ち切られ（requeues.csv）、2号機の行き先が変わったときです。そのあいだ、同じ卓へ料理を運ぼうとしたスタッフが5回あきらめていました（give_ups.csv）。

#### stops.csv（⑦・種7の、もう1台のロボットが原因で30秒以上止まった行の全部）

```csv
condition_id,seed,robot_id,trip_id,t_s,x,z,aisle,in_narrow,cause,cause_detail,path_blocked,duration_s,resolved,clearance_left_cm,clearance_right_cm,min_gap_cm,blocked_s,no_path_s,yield_s,clear_wait_s,replans,solve_from,solve_to,solve_status,solve_corners,off_mesh_m
fam_p5_s4_r2_a42,7,1,7,1133.65,16.587,5.9491,,0,もう1台のロボット,ServingRobot02,0,60.0494,動けなくなった,33.3371,181.3968,33.3371,60.0494,0,0,0,19,16.587;5.949,16.9;6.05,引けた（狭い道）,2,0.0116
fam_p5_s4_r2_a42,7,2,10,1475.4,20.349,5.0467,,0,もう1台のロボット,ServingRobot01,0,60.0494,動けなくなった,60.7642,275,60.7642,60.0494,0,0,0,19,20.349;5.047,21.362;2.188,引けた,19,0.0147
fam_p5_s4_r2_a42,7,2,10,1573.8,20.349,5.0467,,0,もう1台のロボット,ServingRobot01,0,44.1496,自分で動き出した,60.7642,275,60.7642,43.1996,0,0,0.95,14,20.349;5.047,21.362;2.188,引けた,19,0.0147
fam_p5_s4_r2_a42,7,2,10,1618,20.3454,5.0587,,0,もう1台のロボット,,0,60.0494,動けなくなった,64.0912,275,64.0912,60.0494,0,0,0,19,20.345;5.059,21.362;2.188,引けた,20,0.0088
fam_p5_s4_r2_a42,7,2,10,1700.7,20.3454,5.0587,,0,もう1台のロボット,,0,60.0494,動けなくなった,64.0912,275,64.0912,60.0494,0,0,0,20,20.345;5.059,21.362;2.188,引けた,20,0.0088
fam_p5_s4_r2_a42,7,2,10,1792.7,20.3454,5.0587,,0,もう1台のロボット,ServingRobot01,0,60.0494,動けなくなった,64.318,275,64.318,60.0494,0,0,0,20,20.345;5.059,21.362;2.188,引けた,20,0.0088
fam_p5_s4_r2_a42,7,2,10,1873.75,20.3454,5.0587,,0,もう1台のロボット,ServingRobot01,0,60.0494,動けなくなった,64.318,275,64.318,60.0494,0,0,0,19,20.345;5.059,21.362;2.188,引けた,20,0.0088
fam_p5_s4_r2_a42,7,2,10,1964.6,20.3454,5.0587,,0,もう1台のロボット,ServingRobot01,0,56.8494,自分で動き出した,64.318,275,64.318,56.8494,0,0,0,19,20.345;5.059,21.362;2.188,引けた,20,0.0088
fam_p5_s4_r2_a42,7,1,11,1472.6,20.5578,3.9894,,0,もう1台のロボット,よける場所で待った：ServingRobot02,0,60.0494,動けなくなった,275,65.5595,65.5595,60.0494,0,0,0,31,20.556;3.975,19.813;4.388,引けた（狭い道）,7,0.0138
fam_p5_s4_r2_a42,7,1,11,1553.95,20.5558,3.9746,,0,もう1台のロボット,ServingRobot02,0,60.0494,動けなくなった,30.6132,64.9799,30.6132,60.0494,0,0,0,28,20.556;3.975,19.913;4.388,引けた（狭い道）,7,0.0138
fam_p5_s4_r2_a42,7,1,11,1635.95,20.5558,3.9746,,0,もう1台のロボット,ServingRobot02,0,60.0494,動けなくなった,30.6132,64.9799,30.6132,60.0494,0,0,0,314,20.556;3.975,16.9;6.05,引けた（狭い道）,6,0.0138
fam_p5_s4_r2_a42,7,1,11,1731.75,20.5558,3.9746,,0,もう1台のロボット,ServingRobot02,0,60.0494,動けなくなった,30.6132,64.9799,30.6132,56.6494,0,0,3.4,318,20.556;3.975,16.9;6.05,引けた（狭い道）,3,0.0138
fam_p5_s4_r2_a42,7,1,11,1828.35,20.5558,3.9746,,0,もう1台のロボット,ServingRobot02,0,60.0494,動けなくなった,30.5928,64.9468,30.5928,49.6995,0,0,10.35,331,20.556;3.975,16.9;6.05,引けた（狭い道）,3,0.0138
fam_p5_s4_r2_a42,7,1,11,1935.05,20.5558,3.9746,,0,もう1台のロボット,ServingRobot02,0,60.0494,動けなくなった,30.5928,64.9468,30.5928,48.9995,0,0,11.05,331,20.556;3.975,16.9;6.05,引けた（狭い道）,3,0.0138
```

#### requeues.csv（⑦・種7）

```csv
condition_id,seed,t_s,dish_id,table_id,robot_id,fragile,ready_s,waited_s,requeued_so_far,why,to_staff
fam_p5_s4_r2_a42,7,2022.267,11,40,2,0,1421.581,600.686,1,打ち切り,0
```

#### give_ups.csv（⑦・種7の全行）

```csv
condition_id,seed,t_s,who,job,table_id,to_x,to_z,x,z,aisle,waited_s,action
fam_p5_s4_r2_a42,7,1831.431,スタッフ,料理を運ぶ,41,22.463,2.188,19.9815,4.4102,,60.0494,あきらめた
fam_p5_s4_r2_a42,7,1920.668,スタッフ,料理を運ぶ,41,22.463,2.188,19.9802,4.411,,60.0494,あきらめた
fam_p5_s4_r2_a42,7,1941.238,スタッフ,料理を運ぶ,41,15.3,5.95,20.25,3.3142,,60.0494,あきらめた
fam_p5_s4_r2_a42,7,1981.828,スタッフ,料理を運ぶ,41,22.463,2.188,19.9763,4.4244,,60.0494,あきらめた
fam_p5_s4_r2_a42,7,2001.397,スタッフ,料理を運ぶ,40,15.3,5.95,20.25,3.3119,,60.0494,あきらめた
fam_p5_s4_r2_a42,7,2187.178,スタッフ,料理を運ぶ,4,15.3,5.95,1.8388,8.244,A01,60.0494,あきらめた
fam_p5_s4_r2_a42,7,2262.001,スタッフ,料理を運ぶ,8,15.3,5.95,1.824,8.3095,A01,60.0494,あきらめた
fam_p5_s4_r2_a42,7,2272.261,スタッフ,呼び出し,6,1.763,6.812,1.9,6.9821,A01,60.0494,あきらめた
fam_p5_s4_r2_a42,7,2275.264,客,ドリンクバー,1,1.763,4.762,5.3126,4.2272,,60,移した
fam_p5_s4_r2_a42,7,2311.299,客,ドリンクバー,1,1.763,4.762,5.3185,4.5807,,60,移した
fam_p5_s4_r2_a42,7,2319.357,スタッフ,料理を運ぶ,2,1.763,6.613,1.5,8.0908,A01,60.0494,あきらめた
fam_p5_s4_r2_a42,7,2332.42,スタッフ,呼び出し,6,1.763,6.812,1.5,7.3867,A01,60.0494,あきらめた
fam_p5_s4_r2_a42,7,2346.434,客,ドリンクバー,4,16.5,4.4,1.5,9.229,A01,60,あきらめた
fam_p5_s4_r2_a42,7,2358.195,スタッフ,料理を運ぶ,8,15.3,5.95,1.9,7.449,A01,60.0494,あきらめた
fam_p5_s4_r2_a42,7,2365.252,客,ドリンクバー,3,19.5,4.4,1.9,9.0322,A01,60,あきらめた
fam_p5_s4_r2_a42,7,2381.067,客,ドリンクバー,1,1.763,4.762,5.3407,4.5464,,60,移した
fam_p5_s4_r2_a42,7,2387.424,客,トイレ,4,1.763,10.312,4.9063,12.9489,A02,60,移した
fam_p5_s4_r2_a42,7,2392.579,スタッフ,呼び出し,6,1.763,6.812,1.5,7.4134,A01,60.0494,移した
fam_p5_s4_r2_a42,7,2403.389,客,ドリンクバー,5,1.763,4.962,5.3024,3.7284,,60,移した
fam_p5_s4_r2_a42,7,2418.354,スタッフ,料理を運ぶ,8,15.3,5.95,1.9945,4.6555,A01,60.0494,あきらめた
fam_p5_s4_r2_a42,7,2498.932,客,ドリンクバー,4,1.763,10.312,4.3885,12.5314,,60,移した
fam_p5_s4_r2_a42,7,2527.911,スタッフ,料理を運ぶ,8,15.3,5.95,8.1282,3.5192,,60.0494,移した
fam_p5_s4_r2_a42,7,2559.341,客,ドリンクバー,37,18,4.4,18.062,2.188,,0,あきらめた
fam_p5_s4_r2_a42,7,2616.397,客,ドリンクバー,4,1.763,10.312,1.771,9.8748,A01,60,移した
fam_p5_s4_r2_a42,7,2741.169,客,トイレ,40,21.362,2.188,16.8931,5.0203,,60,移した
fam_p5_s4_r2_a42,7,3335.999,客,ドリンクバー,3,18,4.4,1.763,8.462,A01,0,あきらめた
fam_p5_s4_r2_a42,7,3435.546,客,ドリンクバー,10,5.212,6.812,6.5332,4.55,,60,移した
fam_p5_s4_r2_a42,7,3768.271,客,ドリンクバー,11,18,4.4,5.212,8.662,A02,0,あきらめた
fam_p5_s4_r2_a42,7,3804.306,客,ドリンクバー,10,5.212,6.812,8.1158,4.5489,,60,移した
fam_p5_s4_r2_a42,7,3877.627,客,ドリンクバー,14,5.212,6.812,6.8244,4.2297,,60,移した
fam_p5_s4_r2_a42,7,3908.958,客,トイレ,36,16.963,2.188,16.0862,1.9209,,60,移した
fam_p5_s4_r2_a42,7,3971.469,客,ドリンクバー,7,1.763,8.662,1.7953,7.0888,A01,60,移した
fam_p5_s4_r2_a42,7,4071.867,客,ドリンクバー,15,5.212,8.662,5.7454,4.463,,60,移した
fam_p5_s4_r2_a42,7,4143.704,客,ドリンクバー,8,1.763,10.513,12.066,4.5933,,60,移した
fam_p5_s4_r2_a42,7,4184.294,客,ドリンクバー,15,5.212,8.662,5.5608,4.5117,,60,移した
fam_p5_s4_r2_a42,7,4234.099,スタッフ,片付け,6,1.763,6.812,3.1647,4.55,,60.0494,あきらめた
fam_p5_s4_r2_a42,7,4243.114,客,ドリンクバー,12,5.212,10.513,5.22,4.9201,A02,60,移した
fam_p5_s4_r2_a42,7,4262.886,客,会計,5,2.4,1.65,1.5,8.7591,A01,60,移した
fam_p5_s4_r2_a42,7,4293.964,スタッフ,料理を運ぶ,2,15.3,5.95,15.3,5.95,,60.0494,あきらめた
fam_p5_s4_r2_a42,7,4305.121,客,ドリンクバー,8,16.5,4.4,1.9,8.8436,A01,60,あきらめた
fam_p5_s4_r2_a42,7,4316.775,客,ドリンクバー,8,17.5,4.4,1.5,8.7757,A01,60,あきらめた
fam_p5_s4_r2_a42,7,4364.936,客,ドリンクバー,8,1.763,10.513,1.9,8.8407,A01,60,移した
fam_p5_s4_r2_a42,7,4376.59,客,ドリンクバー,8,1.763,10.513,1.5,8.7757,A01,60,移した
fam_p5_s4_r2_a42,7,4416.982,スタッフ,会計,36,4.45,1.65,1.8527,5.6974,A01,60.0494,あきらめた
fam_p5_s4_r2_a42,7,4417.43,客,ドリンクバー,4,18.5,4.4,1.9,8.8453,A01,60,あきらめた
fam_p5_s4_r2_a42,7,4422.012,スタッフ,案内,3,1.763,8.462,1.181,2.3429,,60.0494,あきらめた
fam_p5_s4_r2_a42,7,4477.246,客,ドリンクバー,4,1.763,10.312,1.9,8.8453,A01,60,移した
fam_p5_s4_r2_a42,7,4503.293,スタッフ,片付け,6,1.763,6.812,5.3414,4.2469,,60.0494,あきらめた
fam_p5_s4_r2_a42,7,4804.363,スタッフ,片付け,6,15.3,5.95,9.8106,4.55,,60.0494,移した
fam_p5_s4_r2_a42,7,4824.334,組,片付け,6,1.763,6.812,1.763,6.812,A01,600.027,卓を空けた
fam_p5_s4_r2_a42,7,4918.665,客,ドリンクバー,12,5.212,10.513,8.2143,4.5442,,60,移した
fam_p5_s4_r2_a42,7,4937.79,客,ドリンクバー,16,18,5.3,7.2953,4.55,,60,あきらめた
fam_p5_s4_r2_a42,7,4995.165,客,ドリンクバー,1,1.763,4.762,4.1692,4.5488,,60,移した
fam_p5_s4_r2_a42,7,5012.248,客,ドリンクバー,13,19.5,4.4,18.7069,4.3784,,60,あきらめた
fam_p5_s4_r2_a42,7,5060.209,客,ドリンクバー,21,8.662,12.038,8.9425,12.4489,A03,60,移した
fam_p5_s4_r2_a42,7,5497.295,客,ドリンクバー,17,5.113,13.888,5.3,6.9254,A02,60,移した
fam_p5_s4_r2_a42,7,5515.673,客,ドリンクバー,24,18,4.4,8.662,6.812,A03,0,あきらめた
fam_p5_s4_r2_a42,7,5596.955,客,ドリンクバー,35,18,4.4,15.862,2.188,,0,あきらめた
fam_p5_s4_r2_a42,7,5638.791,客,ドリンクバー,26,18,4.4,8.662,12.038,A03,0,あきらめた
fam_p5_s4_r2_a42,7,5647.905,客,ドリンクバー,29,18,4.4,12.338,8.662,,0,あきらめた
fam_p5_s4_r2_a42,7,5665.087,客,ドリンクバー,21,8.662,12.038,11.0065,12.45,,60,移した
fam_p5_s4_r2_a42,7,6059.192,客,ドリンクバー,3,1.763,8.462,5.3398,4.5495,,60,移した
fam_p5_s4_r2_a42,7,6202.53,客,ドリンクバー,30,12.338,10.513,14.6572,6.25,,60,移した
fam_p5_s4_r2_a42,7,6231.068,客,ドリンクバー,5,18,4.4,1.763,4.962,A01,0,あきらめた
fam_p5_s4_r2_a42,7,6231.118,客,ドリンクバー,23,18,4.4,8.662,4.962,A03,0,あきらめた
fam_p5_s4_r2_a42,7,6245.511,客,ドリンクバー,3,18,4.4,1.763,8.462,A01,0,あきらめた
fam_p5_s4_r2_a42,7,6309.61,スタッフ,片付け,15,5.212,8.662,6.2089,4.3568,,60.0494,あきらめた
fam_p5_s4_r2_a42,7,6365.192,客,ドリンクバー,30,18,4.4,12.338,10.513,,0,あきらめた
fam_p5_s4_r2_a42,7,6486.068,客,ドリンクバー,1,1.763,4.762,5.2843,4.5864,,60,移した
fam_p5_s4_r2_a42,7,6743.857,客,ドリンクバー,25,18,4.4,8.662,8.662,A03,0,あきらめた
fam_p5_s4_r2_a42,7,6745.849,客,ドリンクバー,14,18,4.4,5.212,6.812,A02,0,あきらめた
fam_p5_s4_r2_a42,7,6754.465,客,ドリンクバー,3,1.763,8.462,6.7983,4.55,,60,移した
fam_p5_s4_r2_a42,7,6755.362,客,トイレ,24,8.662,6.812,5.9186,4.55,,60,移した
fam_p5_s4_r2_a42,7,6772.993,客,ドリンクバー,11,5.212,8.662,6.4312,4.55,,60,移した
fam_p5_s4_r2_a42,7,6801.68,客,ドリンクバー,3,19.5,4.4,5.5493,4.55,,60,あきらめた
fam_p5_s4_r2_a42,7,6817.468,スタッフ,料理を運ぶ,12,5.212,10.513,6.4568,4.55,,60.0494,あきらめた
fam_p5_s4_r2_a42,7,6872.154,客,ドリンクバー,3,16.5,4.4,5.9423,4.55,,60,あきらめた
fam_p5_s4_r2_a42,7,6875.042,客,ドリンクバー,14,18,4.4,5.212,6.812,A02,0,あきらめた
fam_p5_s4_r2_a42,7,6886.249,スタッフ,料理を運ぶ,12,5.212,10.513,6.5375,4.5446,,60.0494,あきらめた
fam_p5_s4_r2_a42,7,6886.996,客,ドリンクバー,11,17.5,4.4,15.0821,4.2052,,60,あきらめた
fam_p5_s4_r2_a42,7,6946.114,スタッフ,料理を運ぶ,12,15.3,5.95,7.9098,3.4595,,60.0494,移した
fam_p5_s4_r2_a42,7,6946.811,客,ドリンクバー,11,5.212,8.662,6.7988,4.55,,60,移した
fam_p5_s4_r2_a42,7,7086.214,客,ドリンクバー,7,1.763,8.662,5.245,4.5495,,60,移した
fam_p5_s4_r2_a42,7,7118.289,客,ドリンクバー,4,19.5,4.4,15.3664,4.3784,,60,あきらめた
fam_p5_s4_r2_a42,7,7218.147,客,ドリンクバー,6,1.763,6.812,2.2858,4.1502,,60,移した
```

### 詰まる理由は、どちらが譲るか決まらないことと、よけた先が相手の道の上だったこと

いまの Unity の決まりは、料理を載せているロボットが先に行き、空で戻るロボットがよける、です。この決まりは、2台とも料理を載せているときや2台とも空のときに、どちらが譲るかを決めません。2台とも相手を待ちます。さらに、よける側が入った「よける場所」が、相手の進む道をふさいでいることがありました。

解き方の案は3つ出しました。

1. 下がる1台を必ず決める。同じ優先なら、よけられる場所に近い方が、相手の道から外れた所までバックで戻る
2. 狭い通路の入口で待つ。向こうからロボットが来ている細い通路には入らず、手前で待つ
3. 30秒動けなければ、店員が来て片方を動かす。試算の「立ち往生」（スタッフが動かしに行く）と同じ扱い

2 は LOG423 で④の店に「1本道は1台ずつ」として試していて、ロボットの世話は3〜4割減ったものの、1台のときには届きませんでした。選んだのは3です。

> 30秒動けなければスタッフが来て片方を動かす。試算の立ち往生（スタッフが動かしに行く）と同じ扱いです。今はこれでいいよ。実際の店舗もそんな運用だし。

> ただし、将来的にロボットにこういう動きの設計を組み込めばこういった詰まりが無くせますと言えるように最新の配膳ロボットの仕様を追いかけたいね

- ねらい：Unity の詰まり方を、実物の店の運用（詰まったら店員が手で解く）と、サイトの試算の決まり（立ち往生はスタッフが動かしに行く・作業15秒）にそろえる
- 変わったこと：ロボットが30秒以上動けなかったら、いちばん優先の仕事として、手の空いた近いスタッフが歩いて行き、着いてから15秒で片方を動かす。ロボットどうしなら空の方、同じなら行き先まで遠い方を動かす。入を標準にした切り替えで入れる
- 見込める効果：⑦のような9分のにらみ合いが、30秒＋スタッフが着くまで＋15秒で解ける見込み。店員が助けに使った回数と時間（rescues・rescue_staff_s）が店ごとに出るので、試算の立ち往生の割合（1往復の1%）と比べられる。結果は2026-11-03 の夜の取り直しで出る

### 依頼76 の最初に、店員の助けを足した

この作りは、2026-11-03 の夜の基準データの取り直し（依頼76。瞬間移動を直した版で6店を180回回す）の最初に入れました。助けを入れないまま取り直すと、すぐにもう一度取り直すことになるためです。依頼箱は依頼をファイル名の順に1件ずつ回すので、別の依頼に分けると、取り直しのあとに回ってしまいます。そこで依頼76 のファイルの頭に「0. 回す前に入れること」を足し、入れたあと⑦で向かい合いが解けることを確かめてから180回を回すようにしました。

#### 依頼76 のファイル（足した後の全文）

```markdown
---
作業フォルダ: /Users/shimazueiichi/Robotics_UX/ServiceSim
上限時間: 720
---
# 依頼76：瞬間移動を直した版で、6店の基準データを取り直す（依頼75 の後・夜）

エイイチと決めたこと（2026-11-03）：瞬間移動の直し（依頼74）で客の動きが変わったので、6店の基準データを取り直す。
**エイイチは寝ている。人に聞かないと進めない所に当たったら、その回は止めて報告に書き、ほかの回は続ける。時刻で止めず、並べた回が全部終わるまで回す。**

## 0. 回す前に入れること：立ち往生をスタッフが解く（2026-11-03 エイイチと決めた・Claude が後から足した）

基準データを取り直す前に、これを今の標準に入れる。入れてから下の 180 回を回す（入れないまま取り直すと、すぐ取り直しになるため）。

**いま起きていること**：2台のロボットが向かい合うと、どちらも相手を待ち、誰も解かない。20261102 の夜の⑦（fam_p5・種7）では、店の時刻 1472〜2022 秒のあいだ (20.4, 5.0) 付近で1号機と2号機が約9分止まり、最後は料理の打ち切り（requeues.csv・600 秒）でやっと動いた。そのあいだ料理を運ぼうとしたスタッフも3回あきらめた（give_ups.csv）。stops.csv では 60 秒ごとに「動けなくなった」が並ぶだけで、助けが来ていない。

**入れる決まり**（試算の立ち往生と同じ扱い。サイトの tools/serving_sim/haizen_sim.py の maybe_stop・assumptions.json の rescue_s）：
- ロボットが **30 秒以上** 動けなかったら（今の立ち往生の判定の線と同じ）、スタッフ1人が助けに行く。人と向かい合った場合も、ロボットどうしの場合も同じ
- 助けは **いちばん優先の高い仕事**（試算の staff_job の priority 0 と同じ）。いま手が空いているスタッフのうち近い人が行く。全員ふさがっていれば、今の仕事が終わった人から行く（待った時間も記録する）
- スタッフは歩いて行き、着いてから **15 秒**（試算の rescue_s・決まりの値。変えない）でロボットを動かす。動かすのは片方だけ
- ロボットどうしなら、動かすのは **空の方**（料理を載せていない方）。どちらも同じなら、行き先までまだ遠い方。人とロボットならロボットを動かす
- 動かした先は、相手の道（いま引いている道）から外れた、いちばん近くの空いた所。動かされたロボットはそこで道を引き直して仕事を続ける
- 作りは切り替えで入れ、**入を標準**にする（切にすると今までどおり）。ラベルなどへの載せ方は今までの切り替えと同じでよい

**記録**：回のフォルダに rescues.csv（いつ・どこで・どのロボット・相手は何か・行ったスタッフ・手が空くまで待った秒・歩いた m・解けるまでの秒）。summary.json に rescues（回数）と rescue_staff_s（スタッフが助けに使った秒の合計）。試算の meta.metrics.rescues と同じ意味にする。モニターに「助けに行く」を出す。

**確かめ**：⑦（fam_p5・種7・ロボット2台）を1回回し、30 秒以上の向かい合いが 30 秒＋スタッフが着くまで＋15 秒で解けることを（瞬間移動の直しで同じ時刻に起きるとは限らない。起きなければ向かい合いの出る種を探して確かめる） rescues.csv と stops.csv で確かめる。ほかに変な動き（助けに行ったスタッフが詰まる・同じ所で何度も助けが要る）が出たら、下の 180 回は回さずに報告して止める。

## 始める前に確かめること（違えば回さずに報告して終わる）
1. 上の 0 が入り、⑦の確かめで解けている
2. 依頼74 が合格している（依頼75 は終わっていればよい。不合格でも回してよい）
3. Unity エディタ・night_runner／rec_runner が動いていない。KIOXIA がマウントされ、unity_ledger_v1.lock がない。台帳を退避
4. ServiceSim の保留が 0

## 回すもの（画面なし・今の標準・直した版の実行ファイル）
- 基準データ v5 と同じ並び：③⑦ × 0・1・2台 × 種10個（60回）
- ④⑤⑥② の今までの夜と同じ並び：店ごとに 0・1・2台 × 種10個（各30回・計120回）
- 夜の名前は 20261103、ラベルは `tpfix_base`（立ち往生の助け 入）。同時に回す本数は今のまま
- 判定（judge_night.py）は今までどおり自動で出してよい（基準は v5 のまま。差が出るのは当たり前なので、印が付いても止めない）

## 報告
依頼箱の結果に加えて、claude_code_reports に 20261103_tpfix_base.txt として。
- 180回の completed の数・失敗・止まった回・最長の実時間
- 店ごと・台数ごとに、瞬間移動・入店待ち・料理の待ち・会計待ち・30秒以上の止まり（1往復あたり）・動けなくなった の種10個の中央値を、v5（③⑦）と今までの夜（④⑤⑥②）と並べた表
- 店ごと・台数ごとの rescues（1往復あたり）と rescue_staff_s の中央値。試算の stuck_rate 0.01 と並べる
- 0 の作りの変更点（ファイル・切り替えの名前）と⑦の確かめの結果
- 台帳の行数の変化と md5
```


## 作業ログ ― 実物の配膳ロボットは、ロボットどうしの譲り合いをどう決めているか

### 1台ごとの仕様ではなく、何台かまとめた動かし方（群制御）として見る

最初は、最新の配膳ロボットの仕様（センサー・通れる幅）を追うつもりでした。調べはじめると、詰まりを決めているのは1台ごとの性能ではなく、何台かをまとめてどう動かすかだと分かりました。この、何台かのロボットの通り方・待ち方・譲り方をまとめて決めることを、この記事では群制御と呼びます。

> ロボットの仕様というより群制御のやり方を見直すって感じだね

> ロボットの制御では難しいのかな？もっと学習が必要なんだろうか？

ロボットどうしの向かい合いだけなら、学習はほとんど要らず、決まりで解けます。倉庫では何百台もの搬送ロボットが、まとめ役の予約と順番の決まりで止まらずに回っています。学習が効くのは、客がどちらへ動くか・混む前にどの通路を避けるかのように、人の動きが入る所です。そこで Unity の進め方は、ロボットどうしは決まりで詰まりを消し、残った人との詰まりを学習で減らす二段構えにしました。道の選び方を差し替えられる部品にしておく考え（別のチャットで話した、人混みの中を進むロボットの研究室の動画から出た考え）とも合っています。

### 調べて分かったこと：取扱説明書に書いてある決まりは2つだけ

メーカーの公式資料・取扱説明書・特許・規格・日本の導入事例を、PUDU・KEENON・Bear Robotics と LG とソフトバンク・日本の管制の仕組みと規格、の4つに分けて調べました。全文は下の報告書です。

- 2台がすれ違えない幅の通路は、1台ずつ交互に通す。すれ違いに要る幅は、PUDU が 1.6m 超、KEENON T8 が 1.5m 超
- 塞がれたら待って道を組み直す。秒数まで公開しているのは PUDU BellaBot Pro だけで、初期値は「30秒待って組み直し、ふさがれた区間を180秒避ける」

**どちらが譲るかの決まりは、どのメーカーも公式には公開していませんでした。** 手がかりは特許だけで、KEENON は優先の低い方が壁ぎわか分かれ道の退避エリアで待つ形、PUDU は交差点で遅れて着く方が待つ形です。特許は、その方法が製品に入っている証拠にはなりません。いちばん Unity に写しやすいのは、三菱電機のロボット管制と、日本の規格 RFA B 0004 でした。狭い区間を予約制にして入口で順番を待たせ、一度に一方向だけ通し、失敗したら区間を閉じて担当者に知らせます。

### これまでに集めた実物の動画4本と突き合わせた

research フォルダの観察台帳に、これまで共有された実物の配膳ロボットの動画を15本まとめてあります。調べはじめた時点ではこれを使っておらず、あとから突き合わせました。ロボットどうしが関わる場面は4本で、どれも PUDU の機体です。

| 見た日 | 店 | 起きたこと | 止まってから店員が来るまで | 店員の作業 |
|---|---|---|---|---|
| 2026-10-26 | ガスト | BellaBot 2台が1台分の通路で正面から向き合う | 41秒以上 | 約5秒（画面操作と手で押してよける） |
| 2026-10-27 | ファミレス（ドリンクバー横の待機場所） | 並んだ隣の機体に出口をふさがれ、声かけを繰り返す。同じ日に何度も起きた（投稿者談） | 45秒以上 | 1〜2秒（画面で一時停止） |
| 2026-10-27 | ガスト（ドリンクバーの前） | もう1台のそばでぶつかって止まり、向きを変えようとしたが抜けられない | 約18秒 | 約10秒（手で向きを変える・画面操作・2台目の位置直し） |
| 2026-10-28 | 和食系の店 | KettyBot 2台（料理あり・空）が待機場所の横で向かい合い、2台ともその場で向きを変え続けて進めない | 53秒以上 | 約4〜5秒（空の機体を手で横へ寄せる） |

4本とも、ロボットだけでは解けず、店員が手で解いています。2台とも同じ動き（その場で向きを変える・声をかける）を繰り返していて、どちらが譲るかが決まっていないように見えます。店員が来るまでは18〜53秒以上と長く、手で直す作業は5秒前後と短い。試算の決まりの値（助けの作業15秒）より短く、時間の大半は人が来るまでの待ちでした。店員が動かしたのは、分かる範囲では空の機体で、Unity で「空の方を動かす」とした決まりと同じです。

[図解: LOG434_faceoff_timeline.html](https://pixelstomotors.com/embeds/LOG434_faceoff_timeline.html)

この突き合わせから、Unity の記録では「スタッフが着くまでの時間」と「手で直す時間」を分けて見ることにしました。動画は4本だけで、どれも PUDU なので、ほかのメーカーにそのまま当てはまるかは分かりません。

### Unity の人は、ロボットのそばでだけ遠慮しすぎている

別のチャットで Unity のプロジェクトを読んで分かったことも、数字を読むときの注意として書いておきます。Unity の人は半径0.2m（幅40cm）の円柱で、人どうしで細い通路に詰まって1秒たつと、横を向いて幅24cmに細くなります。ところがロボットのそばでは横を向かず、ロボットとの間を0.3m空けてよけます。実際の店は、人がギリギリまで寄れて横向きにもなれる前提で作られているので、ロボットのそばのすり抜けは実物より詰まりやすく出ているかもしれません。2026-11-03 の夜の取り直しで出る店員の助けの回数も、このぶん多めに出る可能性があります。

### 調査報告（research_serving_robot_fleet_rules_20261103.md の全文）

```markdown
# 配膳ロボットは狭い通路を「順番待ち」で解いている

実際の配膳ロボットが複数台で同じ店を走るとき、公式の取扱説明書に書かれている決まりは驚くほど単純で、**「2台がすれ違える幅（PUDU は 1.6m 超、KEENON T8 は 1.5m 超）より狭い通路は、1台ずつ交互に通す」**と**「動けなくなったら一定時間待ってから経路を組み直す（PUDU BellaBot Pro の初期値は 30 秒待ち、その区間を 180 秒避ける）」**の2つにほぼ尽きる。どちらが道を譲るかを決める規則（荷物を運んでいる方が優先、など）は、PUDU・KEENON・Bear Robotics／ソフトバンク・LG のどのメーカーも公式文書で公開していない。手がかりになるのは特許で、KEENON は「優先度の低い方が壁ぎわや分かれ道の退避エリアに入って待つ」「用事のないロボットが仕事中のロボットに道を譲る」、PUDU は「交差点では遅れて着く方が手前で待つ」という方法を特許にしている。ただし特許は「その方法を製品に載せている」証拠にはならない。動かせる台数は、公式の数字では PUDU と LG が「最大20台」、ソフトバンクの Servi アイリスエディションが「最大5台」で、日本の大手チェーンの実際は1店あたり1〜3台が中心だった。そして決まりで解けない向かい合いは現場で実際に起きていて、ガストでは2台が狭い通路で向き合ったまま動けなくなり、店員が1台の電源を切って解いた。日本の建物側の仕組み（三菱電機 Ville-feuille、RFA の規格）は、この問題を「狭い区間を予約制にし、一度に一方向だけ通し、失敗したら区間を閉じて人を呼ぶ」という形ではっきり決めていて、Unity のシミュレーションの部品にいちばん写しやすいのはこちらである。

この報告で使う言葉を先に決めておく。**群制御**は、複数のロボットが互いにぶつからず、詰まらずに動くための決まりと仕組みのこと。**向かい合いの詰まり（デッドロック）**は、狭い所で2台が互いを障害物と見なし、両方とも止まって動けなくなる状態のこと。情報の確かさは次の5つに分けて書く。**【公式】**は取扱説明書・仕様表・規格・企業の技術論文で確かめたもの、**【特許】**は特許公報に書かれた方法（製品に入っている保証はない）、**【宣伝】**はメーカーの宣伝文で仕組みの説明が付かないもの、**【報道】**は報道・販売店・第三者の記事、**【未確認】**は資料が見つからないか推測のもの。

## 公式の上限は「20台」が目安、日本の店の実際は1〜3台

台数の上限をはっきり書いた公式資料は少ない。PUDU は BellaBot の仕様表で**「同じバージョンのロボットなら最大20台を同時に配車できる」【公式】**と書き、PuduBot 2 と SwiftBot の仕様表も「最大20台」としている ([BellaBot 紹介 PDF](https://static.generation-robots.com/media/presentation-pudu-bellabot-en.pdf), [PuduBot 2 brochure](https://static.generation-robots.com/media/pudubot2-brochure-en-2.pdf), [SwiftBot PDF](https://cdn.robotshop.com/media/P/Pud/RB-Pud-06/pdf/swiftbot-20230425-en-presentation.pdf))。同じ BellaBot の資料には古い調度システムの宣伝として「同じ環境で最大100台」という値も並んでいて【宣伝】、20台が地図1枚あたりの数なのかは書かれていない【未確認】。LG は 2024年の CLOi ServeBot（LDLIM31）について**「最大20台のほかの CLOi ServeBot と通信できる」【公式】**とした ([LG ドイツ ニュースルーム](https://www.lg.com/de/newsroom/240626_LG_Corporate_LG_CLOi-ServerBot/))。ソフトバンクロボティクスは 2022年11月のアップデートで Servi アイリスエディションを**「最大5台を同時稼働」【公式】**にし、約800席のサッポロビール園 ポプラ館で4台を動かしている ([ソフトバンクロボティクス リリース](https://digitalpr.jp/r/64806))。

KEENON は取扱説明書にも仕様表にも上限を書いていない。報道では 2023年の T9 Pro／T3 発表で**「最大20台まで、異なる機種どうしでも共同稼動できる」【報道】** ([ロボスタ 2023-08-21](https://robotstart.info/2023/08/21/keenonrobotics-2-robots-release.html))、2021年の販売店のリリースでは「複数台（制限なし）」とあり ([日刊工業新聞 企業リリース](https://www.nikkan.co.jp/releases/view/127861))、両者は食い違う。Bear Robotics も上限を示さず、韓国の焼肉店 Bangujeong で約28〜30台が1日1,100回配膳しているという自社の数字を出している【宣伝】 ([Bear ブログ](https://bearrobotics.ai/blog/empower-your-restaurant-with-a-servi-plus-team-revolutionize-service-and-boost-profits), [Restaurant Technology News 2026-07-08](https://restauranttechnologynews.com/2026/07/bear-robotics-brings-physical-ai-service-robots-and-autonomous-cleaning-to-restaurants-and-hotels/))。

日本の実際の台数はずっと少ない。すかいらーくは BellaBot 約3,000台を約2,100店に入れ、**通常は1店1〜2台、大型店で最大3台【報道】** ([東洋経済 2025-05-20](https://toyokeizai.net/articles/-/877785?display=b))。物語コーポレーション（焼肉きんぐ・ゆず庵）は Servi 443台を310店に入れた【報道】 ([日本食糧新聞 2020-10-19](https://news.nissyoku.co.jp/news/kinbara20201015020536420))。報じられた台数を店の数で割ると、どちらも平均で約1.4台になる（この割り算はこちらの計算）。つまり日本の店でロボット同士の詰まりが表に出にくいのは、そもそも同時に走る台数が少ないからだと考えられる【未確認・推測】。3台以上や狭い店で何が起きるかは、シミュレーションで確かめる価値のある問いとして残る。

## ロボット同士の通信は各社ばらばらで、中身はどこも非公開

ロボット同士がどう情報をやり取りするかは、メーカーごとに説明が違い、何を送り合っているか（位置だけか、予定の経路か、通路の予約か）はどこも公開していない。PUDU は公式ページで、調度システムを**中央の計算機を持たない分散型とし、「同じネットワーク内のどのロボットとも直接通信する」【公式・宣伝】**と説明している ([PUDU BellaBot 製品ページ](https://www.pudurobotics.com/product/detail/bellabot))。2019年の HolaBot 発表時には、Wi-Fi なしで通信できる独自の自己組織ネットワーク（ロボット同士が自分たちで網をつくる無線）だと報じられた【報道】 ([极客公园](https://www.geekpark.net/news/253255))。よく言われる「LoRa（遠くまで届く省電力の無線）でロボット同士が通信している」という説は、公式資料では**呼び出し用の Pager とロボットの間でしか確認できなかった** ([PUDU Academy](https://academy.pudutech.com/en/docs/bei-la-pei-jian), [Semtech blog](https://blog.semtech.com/holabot-service-robot-delivers-with-lora))。なお「PUDU Link」は通信方式ではなく、ロボットの呼び出しと通知を扱うアプリの名前である【公式】 ([App Store](https://apps.apple.com/us/app/pudu-link/id1598340894))。

KEENON の取扱説明書で確かめられた無線は **2.4GHz の Wi-Fi だけ**で【公式】 ([ManualsLib T5](https://www.manualslib.com/manual/2453378/Keenon-Robotics-T5.html), [ManualsLib T9](https://www.manualslib.com/manual/3449972/Keenon-Robotics-Dinerbot-T9.html))、販売店は「各ロボットが直接通信する」とも「中央のハブを通して協調する」とも書いていて定まらない【報道】 ([ALI](https://ali-jp.com/product/keenon/), [zanrobot.com](https://zanrobot.com/aitransport/))。Bear Robotics はさらに分かりにくく、旧来の Servi・Servi Plus の製品ページは**「集中制御（centralized control system）」**、2026年の Servi Q の発表は**「ピアツーピアで直接通信し、中央のネットワークに頼らない」**と、同じサイトの中で方式の説明が逆になっている【宣伝】 ([bearrobotics.ai/servi-plus](https://bearrobotics.ai/servi-plus), [Bear ブログ Servi Q](https://ms.bearrobotics.ai/blog/bear-robotics-introduces-servi-q))。世代で方式が変わったのか、書き方の違いなのかは分からない【未確認】。LG は日本向けの CLOi ServeBot 3.0 で「ロボット間のダイレクト通信」と Wi-Fi を挙げている【公式】 ([LG Japan](https://www.lg.com/jp/business/robot/ldltr30))。

シミュレーションにとっての要点は、**「店に1つの管理役が通路を割り当てる集中型」と「ロボット同士で順番を決める分散型」の両方が実在する**ということだ。どちらが優れているかを示す公開データはないので、両方を比べる条件として持つのが筋がよい。

## 狭い通路の決まり：公式は「幅で分けて交互に通す」まで

### 取扱説明書に書いてあるのは「すれ違える幅」と「交互に通す」だけ

PUDU の BellaBot Pro の操作ガイド（2024年6月）は、**2台が正面からすれ違うには通路幅 1.6m 超を推奨し、それより狭ければ交互に通す（pass in turn）【公式】**と書く ([BellaBot Pro Operation Guide](https://cdn.robotshop.com/media/P/Pud/RB-Pud-20/pdf/bellabot-pro-guide-v-1-240624.pdf))。KettyBot Pro の操作ガイドでは、地図をつくるときに **1.6m を超える経路を「両方向に通れる経路（two-way paths）」として設定する**手順まである【公式】 ([KettyBot PRO Operation Guide](https://cdn.robotshop.com/media/P/Pud/RB-Pud-12/pdf/kettybot-pro-v-1-1-231117-userguide.pdf))。裏返せば、それ以外の通路は「1台分の幅しかない区間」として扱われていると読める【推測】。古い BellaBot のマニュアルではすれ違いに 2m 超を求めていた ([Bella user manual V1.3](https://helpcentre.sparkepos.com/hubfs/2022%20Robotics/EN-Bella%20user%20manual.pdf?hsLang=en))。KEENON も T8 の取扱説明書で**「1.5m を超える通路なら2台が向かい合って通れる、それ以外は1台ずつ（one by one）」【公式】**としている ([ManualsLib.mx KBS Keenbot T8](https://www.manualslib.com/manual/757750/Kbs-Keenbot-T8.html))。どちらのマニュアルも、交互に通すときに**どちらが先に行くか**は書いていない。

### 譲る側の決め方は特許にしか出てこない

譲り方の中身が書かれているのは特許である。KEENON の米国特許 US 12,228,934 は、**優先度の低いロボットだけが退避エリアに入って停まって待ち、高い方は向きを変えずにそのまま進む**方法で、退避エリアには「壁ぎわの決められた場所」と「分かれ道」の2種類がある。待つときは、次に進む方向に近い向きで停まり、動き出しを早くする【特許】 ([Justia US12228934](https://patents.justia.com/patent/12228934))。優先度の決め方そのものは書かれていない。同じく KEENON の US 12,275,153 は、**用事のない（空いている）ロボットが仕事中のロボットの前をふさいでいたら、受け取り口（ホームポイント）までの距離で避け方を決めて譲らせ、避けている間は音声や映像で知らせる**方法である【特許】 ([USPTO Official Gazette US12275153](https://patentsgazette.uspto.gov/week15/OG/html/1533-3/US12275153-20250415.html))。PUDU の中国特許 CN113741424A は、全機が同じ経路の地図を使って位置・向き・目的地を配り合い、**交差点では遅れて着く方が手前で止まり、正面から向き合ったら距離のしきい値（例として 2m・3m）で別の車線へ移ってすれ違う**方法を書いている【特許・審査中】 ([Google Patents CN113741424A](https://patents.google.com/patent/CN113741424A/en))。この特許が「床の反射や死角のせいでセンサーでは相手のロボットを見つけられないことがある」ことを課題として挙げている点は、ロボット同士の通信がなぜ必要かの説明として役に立つ。

LG の米国特許 US11400593B2 は集中型の考え方で、**サーバーが優先度の高い順（または空間に入る速さの順）に経路を計画し、高い方が通るマスを予約して、低い方を別のマスへ回す**【特許】 ([Google Patents US11400593](https://patents.google.com/patent/US11400593))。韓国の Woowa Brothers 発で今は B-Robotics が持つ US12172294B2 は、経路が重なる席を「配膳ゾーン」にまとめ、同じゾーンで待ちが出ると予測したら**一時待機の場所に後ろのロボットを止める、または席の割り当てや配膳の順番を入れ替える**【特許】 ([Google Patents US12172294](https://patents.google.com/patent/US12172294))。

### 製品の挙動として語られているのは「待つ」「並ぶ」「下がる」

製品としての挙動を述べた資料は宣伝と報道に限られる。Bear は Servi Plus について**「狭い所は1台が先に通るのを待つ」「並んで、先頭が出たら前へ詰める」【宣伝】**と書き ([Bear ブログ](https://bearrobotics.ai/blog/empower-your-restaurant-with-a-servi-plus-team-revolutionize-service-and-boost-profits))、外部の画面に重ねて出す状態の一覧に**「Multi-Robot: Waiting（ほかのロボットを待っている）」**という独立した状態があることは公式の画面仕様で確かめられる【公式】 ([Bear SDK 3rd-party UI User Guide](https://sdk.bearrobotics.ai/3rdpartyui/userguide/))。2026年9月に日本で発売された Servi Q は、**向かい合うと一方が後退して道を譲る**と報じられている【報道】 ([Impress Watch 2026-09-28](https://www.watch.impress.co.jp/docs/news/2143854.html), [DIME](https://dime.jp/genre/2158831/))。どちらが下がるかの決め方は公開されていない【未確認】。PUDU 側では、しゃぶ葉の店員の声として「狭い所では2台が自動で回避しあう」「1台が配膳に出ると、待っていたもう1台が肉を載せる場所へ戻ってくる」が紹介されている【宣伝・PUDU 日本法人の PR】 ([PR TIMES](https://prtimes.jp/main/html/rd/p/000000013.000087027.html))。また PUDU の配車の「Waiting Mode」は、**受け取り場所の近くに1台いる間は、ほかのロボットがそこへ来ない**という決まりで【公式】、受け取り場所が埋まっていれば「仮の停車点」で待つ ([BellaBot Pro Guide](https://cdn.robotshop.com/media/P/Pud/RB-Pud-20/pdf/bellabot-pro-guide-v-1-240624.pdf), [KettyBot PRO Guide](https://cdn.robotshop.com/media/P/Pud/RB-Pud-12/pdf/kettybot-pro-v-1-1-231117-userguide.pdf))。これは「待つ場所を区間の外に置く」という考え方の、製品で確かめられる数少ない例である。

## 詰まったときは「待つ → 組み直す → 人を呼ぶ」の3段

### 秒数まで公開しているのは PUDU だけ

動けなくなったときの扱いを数字で公開しているのは PUDU の BellaBot Pro だけだった。**障害物に当たると止まり、N 秒待ってから新しい経路を計画する（N は 10〜600 秒、初期値 30 秒）。ふさがれた経路はしばらくロックされ、その間はそこを通る計画をしない（30〜600 秒、初期値 180 秒）。一時停止は 10 秒（初期値）で自動的に再開する【公式】** ([BellaBot Pro Operation Guide](https://cdn.robotshop.com/media/P/Pud/RB-Pud-20/pdf/bellabot-pro-guide-v-1-240624.pdf))。この「30 秒」はロボット同士の詰まり専用ではなく障害物全般への設定だが、いま Unity に入れた「30 秒動けなければ」とちょうど同じ値である。違いは、PUDU では 30 秒の後にまずロボット自身が経路を組み直し、人を呼ぶのはその先だという点にある。Bear も Servi Plus の設定に「障害物で遅れたときに再び試すまでの待ち時間」の項目を持つが、既定の秒数は公開ページに出ていない【公式・秒数は未確認】 ([Servi Plus マニュアル](https://www.bearrobotics.ai/servi-plusmanual-3))。画面の状態としては「I'm Stuck」「Narrow Space」「Mission Failed」がある【公式】 ([Bear SDK](https://sdk.bearrobotics.ai/3rdpartyui/userguide/))。KEENON は障害物に当たると音声で知らせること、位置を見失ったら充電台まで押して戻すことまでは取扱説明書で確かめられたが、ロボット同士の詰まりを何秒で判断するかは公開していない【公式・秒数は未確認】 ([ManualsLib.mx KBS W3](https://manualslib.mx/manual/757741/Kbs-W3.html?page=20), [ManualsLib T9](https://www.manualslib.com/manual/3449972/Keenon-Robotics-Dinerbot-T9.html))。

### 同じ決まりを持つ2台は、同時に同じ判断をして詰まる

決まりで解けない場面は実際にある。ガストで、猫の顔の配膳ロボット2台が狭い通路で正面から向き合い、「どいてニャ」「通りたいニャ」と言い合ったまま動けなくなり、**店員が1台の電源を切って解消した**【報道・日付不明】 ([grape](https://grapee.jp/1155721))。両方が同じ待ち時間で同じ判断をするなら、片方が引く理由がない。どちらかを先に動かす「差」（優先度・番号・待ち時間のずれ）を決まりに入れておかないと、この場面は人の手でしか解けない、というのがこの例から読める設計上の教訓である【推測】。

### 建物側の仕組みは「区間を閉じて、決めた相手に知らせる」まで決めている

人を呼ぶ段をはっきり決めているのは、三菱電機の Ville-feuille である。狭い通路や交差する通路を「調停エリア」として登録し、そこで想定外の物などのために通れなかったロボットは**「救出待ち状態」になり、そのエリアはほかのロボットが入れないよう閉じられ、事前に登録したメールアドレスへ知らせが送られる【公式・企業の技術論文】** ([三菱電機技報 2025-11](https://www.giho.mitsubishielectric.co.jp/giho/pdf/2025/2511102.pdf))。RFA（ロボットフレンドリー施設推進機構）の施設向け冊子も、**詰まりからの復旧には一般に人手が要り、管理の手間が増える**と書いている【公式】 ([経産省／RFA 施設向け冊子](https://www.meti.go.jp/policy/mono_info_service/mono/robot/2503_facilitybook.pdf))。配膳ロボットのメーカー側では、詰まりをスタッフのスマホや時計に知らせる公式の機能は確認できなかった。PUDU Link アプリに通知機能があることまでは確かめられたが、何が何秒続いたら通知されるかは書かれていない【未確認】 ([App Store](https://apps.apple.com/us/app/pudu-link/id1598340894))。

## 店が決められることは少なく、通路の使い方は導入時の地図で決まる

店側の画面で設定できる項目のうち、群制御に関わるものを公式マニュアルで確かめられたのは PUDU がいちばん多い。仮想の壁（入ってはいけない線）、1.6m 超の通路を両方向の経路にする設定、仮の停車点、受け取り場所の割り当て方（1対1／空いている所／待機モード）、配車モード、通れる幅の設定（初期値 65cm、幅の広い物を運ぶときは 75〜85cm）、速度（0.2〜1.2 m/s、初期値 0.7 m/s）、経路を組み直すまでの待ちと区間のロックの時間である【公式】 ([BellaBot Pro Guide](https://cdn.robotshop.com/media/P/Pud/RB-Pud-20/pdf/bellabot-pro-guide-v-1-240624.pdf), [KettyBot PRO Guide](https://cdn.robotshop.com/media/P/Pud/RB-Pud-12/pdf/kettybot-pro-v-1-1-231117-userguide.pdf))。Bear の Servi Plus のマニュアルには**「2台以上なら View を押して複数台の詳しい設定を開く」**という項目があるが、中身は公開ページに出ていない【公式・中身は未確認】 ([Servi Plus マニュアル](https://www.bearrobotics.ai/servi-plusmanual-3))。KEENON は地図と配置を導入担当者がつくるのが基本で、店側で変えられるのは音量・稼働時間・充電に戻る電池残量などに限られる【公式】 ([ManualsLib T5](https://www.manualslib.com/manual/2453378/Keenon-Robotics-T5.html))。**一方通行・区域ごとの台数の上限・すれ違い用の退避点を店が設定できる、と書いた公式資料はどのメーカーにもなかった【未確認】。**

実際の運用でも、通路の使い方は導入時に人が決めている。すかいらーくでは、インストラクターが店ごとに地図と卓ごとの停まる位置を設定し、ドリンクバーの前は「少し真ん中より左側」を通すなど店ごとに経路を調整し、改装すれば設定し直す【報道】 ([Impress Watch 2023-05-24](https://www.watch.impress.co.jp/docs/topic/1501163.html))。最初は**通路に余裕のある店から入れ、古い店では通路の狭さと段差が課題になった**【報道】 ([Business Insider Japan 2023-04-03](https://www.businessinsider.jp/article/267599/))。日本の実証では「通行する左右の側や、非常口付近での待機ルールなど、基本的なルール作りが非常に大事」という知見も出ている【報道】 ([MONOist 2023-03-13](https://monoist.itmedia.co.jp/mn/articles/2303/13/news044_2.html))。シミュレーションで「店が選ぶ規則」を置くなら、メーカーの設定画面の写しではなく、**導入のときに地図の上で決まる条件**として扱うのが実態に近い。

### メーカーを混ぜると、ロボット同士は話せない

同じメーカーの中でも混在には条件がある。PUDU の 20台は「同じバージョン」が条件で、HolaBot は旧版と現行版が「互換性なし」とされている【公式】 ([BellaBot 紹介 PDF](https://static.generation-robots.com/media/presentation-pudu-bellabot-en.pdf), [HolaBot PDF](https://cdn.robotshop.com/media/P/Pud/RB-Pud-01/pdf/holabot-20220628-en-presentation.pdf))。KEENON の「異なる機種どうし」も Bear の「機種を混ぜられる」も、自社の機種の間の話である ([ロボスタ](https://robotstart.info/2023/08/21/keenonrobotics-2-robots-release.html), [jp.bearrobotics.ai/servi-q](https://jp.bearrobotics.ai/servi-q))。違うメーカーのロボット同士が直接やり取りして譲り合う配膳ロボットの例は見つからなかった【未確認】。サイゼリヤは Servi と BellaBot を同じ店で使っているが、協調しているとは書かれていない ([Business Insider Japan 2021-05-27](https://www.businessinsider.jp/article/235521/))。

メーカーをまたぐ答えを出しているのは建物側である。RFA は 2024年9月に**「RFA B 0004 ロボット群管理インタフェイス定義」**を発行し、施設の中の狭い空間を「利用権が必要なリソース」としてサーバーが管理して、違うメーカーのロボットのすれ違いを調停する形を規格にした【公式・本文は有料で未確認】 ([ASCII 2024-09-25](https://ascii.jp/elem/000/004/223/4223792/))。実装した Octa Robotics の報告は、**エリアの利用権を取ってから入り、抜けたら返す、取れなければ取れるまで手前で待つ**ことで、逃げ場のない所での「お見合い・立ち往生」を防ぐと説明している【公式】 ([日本ロボット工業会 facility_03.pdf](https://jara.jp/various/koubo/img/2024/facility_03.pdf))。Ville-feuille はさらに具体的で、**調停エリアの入口ごとに要求順の待ち行列をつくり、一度に許可するのは一つの入口だけ、待ちがいなくなるか一定時間ごとに許可する入口を切り替える、ロボットごとの優先度で切り替え先を選ぶ、事前に問題がないと確かめたエリアだけ同じ向きの続けての進入を許す**【公式】 ([三菱電機技報](https://www.giho.mitsubishielectric.co.jp/giho/pdf/2025/2511102.pdf))。国際的な枠組みでは、MassRobotics の標準は位置と状態を見せ合うだけで交通整理はしない ([MassRobotics](https://www.massrobotics.org/what-is-the-massrobotics-amr-interoperability-standard/))。工場の搬送車の規格 VDA 5050 は、管理側が「ここまで進んでよい」という区間を少しずつ渡し、渡されなければロボットは区切りの点で止まる仕組みを持つが、詰まりの解き方そのものは管理側に任せている ([VDA 5050 V2.1.0](https://vda.de/dam/jcr:f0c9c019-1506-4dee-998a-e92723fbf025/VDA5050-V2p1p0.pdf))。Open-RMF は複数のロボットの計画を突き合わせて両立する組み合わせを探し、見つからなければ数秒後に探し直す ([Open Robotics Discourse](https://discourse.openrobotics.org/t/scalability-of-openrmf-for-large-multi-robot-deployments-in-dynamic-environments-e-g-airports/53697))。

## メーカー別の比較

| | 台数の上限 | ロボット同士の通信 | 狭い通路の扱い | 詰まったとき | 1台の最小通路幅 | 2台のすれ違い幅 |
|---|---|---|---|---|---|---|
| **PUDU**（BellaBot・KettyBot ほか） | 最大20台（同じバージョン）【公式】。旧調度の宣伝に100台【宣伝】 | 中央なしの分散型、直接通信【公式・宣伝】。LoRa は呼び出し機用のみ確認 | すれ違い幅未満は交互に通す【公式】。譲る側の規則は非公開。交差点は遅く着く側が待つ【特許】 | 30秒待って経路を組み直し、区間を180秒避ける（初期値）【公式】。ガストで2台が向き合い店員が電源を切った【報道】 | 55cm（KettyBot）〜80cm（BellaBot 旧版・PuduBot 2）、BellaBot Pro 65cm【公式】 | 1.6m超推奨（BellaBot Pro・KettyBot）、旧 BellaBot は2m超【公式】 |
| **KEENON**（T5〜T11） | 公式記載なし。最大20台・異機種可【報道 2023】、「制限なし」【販売店 2021】 | 取説で確認できるのは 2.4GHz Wi-Fi のみ【公式】。ロボット同士の方式は非公開 | 1.5m以下は1台ずつ（T8）【公式】。優先度の低い方が退避エリアで待つ／用事のない方が譲る【特許】 | 障害物で音声【公式】。詰まりの判断秒数は非公開 | T11 49cm、T8 55cm、T10 59cm、T9 70cm【公式】 | 1.5m超（T8）【公式】。他機種は記載なし |
| **Bear Servi / Servi Plus**（ソフトバンク販売を含む） | 公式上限なし。アイリスエディションは最大5台【公式】。韓国1店で約28〜30台【宣伝】 | 「集中制御」と「完全分散」で説明が食い違う【宣伝】。高速インターネットが必要【公式】 | 1台が先に通るのを待つ、並んで詰める【宣伝】。「Multi-Robot: Waiting」状態あり【公式】 | 再試行までの待ちを設定可（秒数は非公開）。I'm Stuck・Mission Failed 状態【公式】 | Servi（アイリス）60cm、Servi Plus 65cm【公式】 | 記載なし |
| **Servi Q**（Bear とソフトバンクの共同開発、日本 2026-09） | 記載なし。機種を混ぜられる【公式】 | ピアツーピア、中央に頼らない【宣伝】 | 向かい合うと一方が後退して譲る【報道】。下がる側の決め方は非公開 | 後退中にぶつかりを感じたら止まる【宣伝】 | 45cm【公式】 | 100cm以上【報道】 |
| **LG CLOi ServeBot**（2025年から Bear と同じグループと報道） | 最大20台と通信（LDLIM31）【公式】 | ロボット間のダイレクト通信、Wi-Fi【公式】 | 非公開。優先度順にマスを予約する方法【特許】 | 非公開 | 80cm（3.0）【公式】 | 記載なし |
| **参考：建物側**（三菱電機 Ville-feuille・RFA B 0004） | 非公開 | サーバーが一元管理【公式】 | 区間を予約制にし一度に一入口だけ通す、要求順＋優先度、一定時間で向きを切り替え【公式】 | 救出待ち、区間を閉じてメールで知らせる【公式】 | 出入口の有効寸法 90cm【公式】 | ロボット同士 1.8m、ロボットと人 1.2m の想定【公式】 |

出典は本文の各段落と末尾の一覧を参照（LG の Bear 傘下入りは [Seoulz 2026-06-19](https://www.seoulz.com/korea-restaurant-robots-2026/)【報道】、RFA の幅は [RFA 施設向け冊子](https://www.meti.go.jp/policy/mono_info_service/mono/robot/2503_facilitybook.pdf)）。2台のすれ違い幅は、本体幅の約2倍に 30cm 前後を足した値に揃っている（Servi Q は 35cm×2＋30cm＝100cm）。他の機種にも同じ比率が当てはまるかは確かめていない【推測】。

## Unity の群制御の部品にするための候補

以下は決定ではなく、設計の材料である。それぞれの決まりについて、どの製品・規格から来たか、その結び付きがどれくらい確かかを並べる。「確かさ」は、**高**＝配膳ロボットの取扱説明書か規格・技術論文に書かれている、**中**＝配膳ロボットの宣伝・報道、または建物側の仕組みとして公式に確かめられたもの、**低**＝特許だけ、または推測、の3段階で付けた。

| 候補の決まり | 中身（Unity で何が起きるか） | 元になった製品・規格 | 確かさ |
|---|---|---|---|
| **1. 狭い区間は1方向ずつ通す予約** | 通路幅がすれ違い幅未満の区間を「1方向区間」として地図に置く。入る前に予約を取り、取れたら入る、抜けたら返す。取れなければ入らない | すれ違い幅未満は交互に通す：PUDU・KEENON の取扱説明書。予約の仕組み：Ville-feuille、RFA B 0004、VDA 5050 | 交互に通すこと自体は**高**。配膳ロボットの製品が予約で実装している証拠はないので、予約の形は**中** |
| **2. すれ違い幅は機種の値で切り替え** | 1方向区間にするしきい値を、PUDU 1.6m／KEENON T8 1.5m／Servi Q 1.0m／RFA 1.8m から選べるようにする | 各社の取扱説明書・発表、RFA B 0003 | **高**（Servi Q の値だけ**中**） |
| **3. 待つ場所は区間の外、入口の手前** | 予約が取れないロボットは区間に入らず、入口手前の待機点で止まる。区間の中では待たせない | Ville-feuille の入口ごとの待ち、VDA 5050 の区切りの点、PUDU の仮の停車点（受け取り場所用）、B-Robotics の一時待機の場所 | 建物側では**高**、配膳ロボットの通路用としては**中** |
| **4. 入口ごとの先着順と、時間での向きの切り替え** | 区間の両端にそれぞれ待ち行列。先着順に通し、待ちがいなくなるか一定時間が過ぎたら許可する向きを切り替える。同じ向きの続けての進入は、追い越しがない区間だけ許す | Ville-feuille | 建物側の仕組みとして**高**。配膳ロボットの製品では**低**（未確認）。切り替え時間の数値は非公開なので自分で置く |
| **5. 優先度の低い方が退避エリアで待つ** | 壁ぎわの決めた場所や分かれ道を「退避エリア」として置き、向かい合ったら優先度の低い方だけがそこに入って停まる。高い方はそのまま進む。停まる向きは次に進む方向に近い向き | KEENON の特許 US12228934 | **低**（特許のみ。製品に入っているかは未確認） |
| **6. 待機中・帰り道のロボットが配膳中に譲る** | 優先度を「配膳中（料理を載せている）＞帰り道・待機中（空）」にする。空の方が退避するか受け取り口へ戻る | KEENON の特許 US12275153、PUDU の Waiting Mode（受け取り場所の近くに1台いる間は他が来ない）、しゃぶ葉の「待っていた1台が戻ってくる」 | 特許の方法としては**低**、受け取り場所での待ちは**高**、現場の様子は**中**。「空の方が譲る」という今の Unity の考えと同じ向き |
| **7. 交差点では遅れて着く方が手前で待つ** | 交差点に着くまでの時間を比べ、遅い方が交差点の手前で止まる | PUDU の特許 CN113741424A | **低**（特許のみ・審査中） |
| **8. 向かい合ったら一方が後退する** | 1方向区間で向かい合ってしまったとき、片方が後ろの待機点まで下がる。下がる側の決め方（空の方／後ろの待機点に近い方／番号）は比べる条件として置く | Servi Q | **中**（報道・宣伝。下がる側の決め方は非公開） |
| **9. 30秒で経路の組み直し、ふさがれた区間を避ける** | 30秒動けなければ、まずロボット自身が別の経路を探す。その区間は一定時間（初期値 180秒）経路の計画から外す。待ちと避ける時間は店ごとに変えられるようにする | PUDU BellaBot Pro の設定（10〜600秒・初期値30秒、30〜600秒・初期値180秒） | **高**（ただし障害物全般への設定で、ロボット同士の詰まり専用ではない） |
| **10. 同時に同じ判断をさせない** | 2台の待ち時間や優先度に必ず差を付け、同じ瞬間に両方が引く・両方が進むことを防ぐ | ガストで2台が向き合ったまま止まった例からの推測 | **低**（推測。ただし実例に基づく） |
| **11. 解けなければ救出待ち＝スタッフ** | 組み直しでも解けなければ「救出待ち」にし、その区間をほかのロボットが入れないよう閉じ、スタッフを呼ぶ。スタッフは空の方を動かす | Ville-feuille の救出待ち・区間を閉じる・メール、RFA 冊子「復旧には一般に人手が要る」、ガストで店員が電源を切った例 | 建物側では**高**、配膳ロボットの現場の例は**中** |
| **12. 状態の名前を実物に合わせる** | 走行中／ほかのロボット待ち（Multi-Robot: Waiting）／狭い所（Narrow Space）／動けない（I'm Stuck）／失敗（Mission Failed）を状態として持ち、それぞれの時間を記録する | Bear の画面仕様 | **高**（状態の名前があることまで。各状態に移る秒数は未確認） |
| **13. 集中型と分散型を比べる条件にする** | 「店に1つの管理役が予約を配る」と「ロボット同士が近くの相手を見て決める」を切り替えられるようにする | 集中：Ville-feuille、旧 Servi の説明、LG の特許。分散：PUDU、Servi Q | 両方式が実在することは**中**（主に宣伝）。どちらが良いかのデータはない |
| **14. 台数の想定** | 日本の現実に合わせた1〜3台を基本にし、上限の確かめとして 5台・20台も回す | すかいらーくの1〜3台、Servi アイリスエディションの5台、PUDU・LG の20台 | **高**（数字の出典は公式と報道） |

いまの Unity の「30秒動けなければスタッフが来て空の方を動かす」と比べると、実物との差は2つある。1つ目は、PUDU では 30 秒の後にまずロボット自身が経路を組み直し（候補9）、人を呼ぶのはさらにその先だという点。2つ目は、実物の資料はどれも「詰まってから解く」より前に、**狭い区間に2台を入れない（候補1・3・4）**ことに重きを置いている点である。予約で入り口を絞れば、30 秒の待ちに入る回数そのものが減るはずで、その差を数えることがシミュレーションの成果になりうる。

## 結論

公開資料を並べて分かったのは、配膳ロボットのメーカーが群制御の中身を「売り」として語りながら、その決まりをほとんど公開していないことだ。公式に言えるのは「この幅より狭ければ交互に通す」と「待ってから組み直す」までで、どちらが譲るかは特許と宣伝の中にしかない。一方で、日本の建物側（Ville-feuille・RFA）は、区間の予約、入口の手前での順番待ち、向きの切り替え、失敗したら区間を閉じて人を呼ぶ、という一連の決まりを文書にしている。Unity で「ロボットどうしは決まりで解く」方針を取るなら、骨組みは建物側の方式から借り、優先度の付け方は KEENON の特許（空の方が譲る・退避エリアで待つ）を、時間の値は PUDU のマニュアル（30秒・180秒）を、それぞれ出典付きの初期値として置くのが、根拠を説明しやすい組み立てになる。

もう一つの示唆は、日本の店で詰まりの報告が少ないのは決まりが優れているからではなく、1店1〜3台という少ない台数で、通路に余裕のある店から入れてきたからだという点である。ガストの向かい合いは、同じ決まりを持つ2台が同時に同じ判断をすると、決まりだけでは解けないことを示している。シミュレーションで「何台から、どの幅から、人の手が要るようになるか」を数字で示せれば、メーカーが公開していない部分を外から測った材料になる。

## 出典

**PUDU**
- BellaBot 紹介 PDF（Génération Robots 配布）: https://static.generation-robots.com/media/presentation-pudu-bellabot-en.pdf
- PuduBot 2 brochure: https://static.generation-robots.com/media/pudubot2-brochure-en-2.pdf
- SwiftBot presentation PDF（2023-04-25）: https://cdn.robotshop.com/media/P/Pud/RB-Pud-06/pdf/swiftbot-20230425-en-presentation.pdf
- HolaBot presentation PDF（2022-06-28）: https://cdn.robotshop.com/media/P/Pud/RB-Pud-01/pdf/holabot-20220628-en-presentation.pdf
- BellaBot Pro Operation Guide V1.0（2024-06）: https://cdn.robotshop.com/media/P/Pud/RB-Pud-20/pdf/bellabot-pro-guide-v-1-240624.pdf
- KettyBot PRO Operation Guide V1.1（2023-11-17）: https://cdn.robotshop.com/media/P/Pud/RB-Pud-12/pdf/kettybot-pro-v-1-1-231117-userguide.pdf
- Kettybot User Manual V1.1: https://6083570.fs1.hubspotusercontent-na1.net/hubfs/6083570/2022%20Robotics/Kettybot%20User%20Manual.pdf
- Bella user manual V1.3: https://helpcentre.sparkepos.com/hubfs/2022%20Robotics/EN-Bella%20user%20manual.pdf?hsLang=en
- PUDU BellaBot 製品ページ: https://www.pudurobotics.com/product/detail/bellabot
- PUDU Academy（BellaBot 付属品）: https://academy.pudutech.com/en/docs/bei-la-pei-jian
- PUDU Link（App Store）: https://apps.apple.com/us/app/pudu-link/id1598340894
- Semtech blog（HolaBot と LoRa）: https://blog.semtech.com/holabot-service-robot-delivers-with-lora
- 极客公园（2019-12-20）: https://www.geekpark.net/news/253255
- 特許 CN113741424A: https://patents.google.com/patent/CN113741424A/en
- PR TIMES しゃぶ葉（2022-05-20）: https://prtimes.jp/main/html/rd/p/000000013.000087027.html
- grape（ガストでの向かい合い）: https://grapee.jp/1155721

**KEENON**
- ロボスタ（2023-08-21）: https://robotstart.info/2023/08/21/keenonrobotics-2-robots-release.html
- 日刊工業新聞 企業リリース（2021-09-22）: https://www.nikkan.co.jp/releases/view/127861
- ManualsLib T5: https://www.manualslib.com/manual/2453378/Keenon-Robotics-T5.html
- ManualsLib T9: https://www.manualslib.com/manual/3449972/Keenon-Robotics-Dinerbot-T9.html
- ManualsLib.mx KBS Keenbot T8: https://www.manualslib.com/manual/757750/Kbs-Keenbot-T8.html
- ManualsLib.mx KBS W3: https://manualslib.mx/manual/757741/Kbs-W3.html?page=20
- ALI（販売店）: https://ali-jp.com/product/keenon/
- zanrobot.com: https://zanrobot.com/aitransport/
- 特許 US12228934（Justia）: https://patents.justia.com/patent/12228934
- 特許 US12275153（USPTO Official Gazette）: https://patentsgazette.uspto.gov/week15/OG/html/1533-3/US12275153-20250415.html

**Bear Robotics／ソフトバンクロボティクス／LG ほか**
- ソフトバンクロボティクス リリース（2022-11-01）: https://digitalpr.jp/r/64806
- Bear Servi Plus 製品ページ: https://bearrobotics.ai/servi-plus
- Bear ブログ（Servi Plus Team）: https://bearrobotics.ai/blog/empower-your-restaurant-with-a-servi-plus-team-revolutionize-service-and-boost-profits
- Bear ブログ（Servi Q 発表）: https://ms.bearrobotics.ai/blog/bear-robotics-introduces-servi-q
- Servi Q 日本語ページ: https://jp.bearrobotics.ai/servi-q
- Bear SDK 3rd-party UI User Guide: https://sdk.bearrobotics.ai/3rdpartyui/userguide/
- Servi Plus マニュアル（Robot Settings）: https://www.bearrobotics.ai/servi-plusmanual-3
- Restaurant Technology News（2026-07-08）: https://restauranttechnologynews.com/2026/07/bear-robotics-brings-physical-ai-service-robots-and-autonomous-cleaning-to-restaurants-and-hotels/
- Impress Watch（Servi Q、2026-09-28）: https://www.watch.impress.co.jp/docs/news/2143854.html
- DIME（2026-10-02）: https://dime.jp/genre/2158831/
- LG ドイツ ニュースルーム（2024-06-26）: https://www.lg.com/de/newsroom/240626_LG_Corporate_LG_CLOi-ServerBot/
- LG Japan CLOi ServeBot 3.0: https://www.lg.com/jp/business/robot/ldltr30
- 特許 US11400593（LG）: https://patents.google.com/patent/US11400593
- 特許 US12172294（B-Robotics）: https://patents.google.com/patent/US12172294
- Seoulz（2026-06-19）: https://www.seoulz.com/korea-restaurant-robots-2026/

**日本の導入事例**
- 東洋経済（2025-05-20）: https://toyokeizai.net/articles/-/877785?display=b
- Impress Watch（すかいらーく、2023-05-24）: https://www.watch.impress.co.jp/docs/topic/1501163.html
- Business Insider Japan（すかいらーく、2023-04-03）: https://www.businessinsider.jp/article/267599/
- Business Insider Japan（サイゼリヤ、2021-05-27）: https://www.businessinsider.jp/article/235521/
- 日本食糧新聞（物語コーポレーション、2020-10-19）: https://news.nissyoku.co.jp/news/kinbara20201015020536420

**建物側の仕組み・規格**
- 三菱電機技報 Vol.99 No.11（2025-11）: https://www.giho.mitsubishielectric.co.jp/giho/pdf/2025/2511102.pdf
- 経済産業省 RFA 規格一覧: https://www.meti.go.jp/policy/mono_info_service/mono/robot/240920_robotfriendly.html
- 経産省／RFA 施設向け冊子: https://www.meti.go.jp/policy/mono_info_service/mono/robot/2503_facilitybook.pdf
- ASCII（RFA B 0004、2024-09-25）: https://ascii.jp/elem/000/004/223/4223792/
- 日本ロボット工業会 Octa Robotics 報告: https://jara.jp/various/koubo/img/2024/facility_03.pdf
- MONOist（2023-03-13）: https://monoist.itmedia.co.jp/mn/articles/2303/13/news044_2.html
- MassRobotics AMR Interoperability Standard: https://www.massrobotics.org/what-is-the-massrobotics-amr-interoperability-standard/
- VDA 5050 V2.1.0: https://vda.de/dam/jcr:f0c9c019-1506-4dee-998a-e92723fbf025/VDA5050-V2p1p0.pdf
- Open Robotics Discourse（Open-RMF、2026-04-01）: https://discourse.openrobotics.org/t/scalability-of-openrmf-for-large-multi-robot-deployments-in-dynamic-environments-e-g-airports/53697

## 追記：これまでに見た実物の動画との突き合わせ（2026-11-03）

research フォルダの観察台帳（robot_video_ledger.md）に、共有された実物の動画を15本まとめてあります。この調査では最初これを使っていませんでした。ロボットどうしが関わる場面は4本で、どれも PUDU の機体です。

| 見た日 | 店 | 起きたこと | 止まってから人が来るまで | 人の作業 |
|---|---|---|---|---|
| 2026-10-26 | ガスト | BellaBot 2台が1台分の通路で正面から向き合う | 41秒以上 | 約5秒（画面操作と手で押してよける） |
| 2026-10-27 | ファミレス（ドリンクバー横の待機場所） | 並んだ隣の機体に出口をふさがれ、声かけを繰り返す。同じ日に何度も起きた（投稿者談） | 45秒以上 | 1〜2秒（画面で一時停止） |
| 2026-10-27 | ガスト（ドリンクバーの前） | もう1台のそばでぶつかって止まり、向きを変えようとしたが抜けられない | 約18秒 | 約10秒（手で向きを変える・画面操作・2台目の位置直し） |
| 2026-10-28 | 和食系の店 | KettyBot 2台（料理あり・空）が待機場所の横で向かい合い、2台ともその場で向きを変え続けて進めない | 53秒以上 | 約4〜5秒（空の機体を手で横へ寄せる） |

読み取れること：
- 4本とも、ロボットだけでは解けず店員が手で解いています。公式資料にある「待って道を組み直す」は、狭い所では効いていません。
- 2台とも同じ動き（その場で向きを変える・声をかける）を繰り返していて、どちらが譲るかが決まっていない様子です。メーカーが譲る決まりを公開していないことと合います。
- 店員が来るまでは18〜53秒以上、手で直す作業は1〜10秒（多くは5秒前後）でした。試算の決まりの値（助けの作業15秒）より短く、来るまでの待ちの方がずっと長い形です。
- 起きる場所は待機場所・ドリンクバーの前・1台分の通路に集中しています。
- 店員が動かしたのは、分かる範囲では空の機体でした（10/28）。Unity で「空の方を動かす」とした決まりと同じです。

Unity への当てはめ：今夜入れる「30秒動けなければスタッフが来て片方を動かす」は、実物の運用の形に合っています。数字の比べ方としては、スタッフが着くまでの時間（実物は18〜53秒以上）と、手で直す作業（実物は5秒前後）を分けて記録して見るとよい、という材料になります。動画は4本だけで、どれも PUDU なので、ほかのメーカーにそのまま当てはまるかは未確認です。
```


## このあと追記すること

この記事は、待っている2つの作業が終わったら追記します。

- 撮り直した動画（依頼75）への入れ替え。そろった店から試算のページの動画を v2 に替え、⑧⑨の大型店も直っていれば足す
- 依頼76 の結果。店員の助けを入れた版で、6店の基準データを取り直した結果（助けの回数・使った時間・入店待ちなど）を、前の基準データと並べる


## 追記 ― 店員の助けを入れた⑦の確かめと、動画の入れ替えを見送った理由（2026-11-03 夕方）

### 9分のにらみ合いは、助け2回・約105秒で解けた

依頼76 で、Claude Code が店員の助けを切り替え（`-ssStuckRescue on|off`、既定は入）として入れ、⑦（fam_p5・種7・ロボット2台）を1回回しました。前に約9分止まっていた向かい合い（店の時刻 1472秒〜）は、助け2回・約105秒で解けました。1回目の助けは、30秒＋手が空くまで0.05秒＋歩き5.9秒＋作業15秒＝51.3秒で、1号機を1.16m動かしています。この回で助けが入った35回の、解けるまでの秒の中央値は51.0秒でした。

| | 止まってから店員が着くまで | 手で直す作業 | 解けるまで |
|---|---|---|---|
| Unity（助けなし・2026-11-02） | 来ない | なし | 約9分10秒（料理の取り消しで解けた） |
| 実物の動画4本（PUDU） | 18〜53秒以上 | 1〜10秒（多くは5秒前後） | 来てから数秒 |
| Unity（助けあり・依頼76） | 30秒＋手が空くまで＋歩き（1回目は約36秒） | 15秒（試算の決まりの値） | 1回目51.3秒・35回の中央値51.0秒 |

店員が着くまでの時間は実物の幅の中に入りました。手で直す作業は、Unity の15秒が実物（5秒前後）より長いままです。ここは試算の決まりの値（rescue_s）に合わせているので、変えるかは試算の側で考えます。

### 新しく見えた詰まり：止まっている台に、何度も助けが要る

一方で、依頼に書いておいた「180回は回さずに止める」変な動きが2つとも出たので、基準データの180回は回していません。

- 同じ所で何度も助けが要った。往復を終えて止まっている2号機に、1号機が阻まれ続け、待機場所の手前と受け渡し口の近くでそれぞれ8回、助けが入った。2号機には行き先がないので、1号機の方が動かされ、35回中25回は0.05m未満しか動いていない
- 助けに行ったスタッフが細い通路で着けず、2回あきらめたあと、3回目で瞬間移動になった

実物の動画でも、待機場所で隣の機体に出口をふさがれて声かけを繰り返す場面（2026-10-27 のファミレス）がありました。止まっている台をどう扱うかは、群制御で決めておく必要がある所だと分かりました。このあとの依頼（77〜85）で、止まっている台を動かす先の直し・同じ種で同じ結果にする直し・瞬間移動を0にする直しが進み、6店180回のベースのデータ（依頼83）も取り終えています。その結果は別の記事で扱います。

#### 依頼76 の Claude Code の報告（止められた操作の一覧を除く全文）

```markdown
# 結果：20261103_76_直した版で基準データを取り直す

- 状態：完了
- 始めた時刻：2026-11-03 10:56
- 終わった時刻：2026-11-03 11:21（25分）
- 作業フォルダ：/Users/shimazueiichi/Robotics_UX/ServiceSim
- この続きを Claude Code で話すとき：作業フォルダで `claude --resume 9b93b5af-88af-4683-aa03-1c46052c18ed`
- 止められた操作：1件（いちばん下に一覧）

## Claude Code の報告

やったこと
- 始める前の確かめ：依頼74 は「済み」で、合格とみました。依頼75 は見張り係が止まったため「失敗」に回り、終わっています。Unity エディタと night_runner／rec_runner は動いておらず、KIOXIA はマウント済み、鍵ファイルはなし、保留は 0 でした。台帳は先に退避しました。
- 依頼の 0「立ち往生をスタッフが解く」を、切り替え（実行ファイルの引数 `-ssStuckRescue on|off`、queue の `stuck_rescue`）として入れました。
  - 既定は入です。入の回は、出力フォルダ名の末尾に `_rescue` が付きます。
  - 切の回の動きは今までと同じです。
  - params には項目が無いので、params は変えずに、既定はコードの側に置きました。
- 入の回の動き：
  - ロボットが 30 秒続けて動けなかったら、助けを優先 0 で呼びます。
  - 手が空いている人のうち、道の長さで近い人が行きます。全員ふさがっていれば、仕事を終えた人が行きます。
  - 着いてから 15 秒で片方を、相手の道から外れた一番近い空いた所へ動かし、そこで道を引き直させます。
  - ロボットどうしなら空の方を、同じなら行き先まで遠い方を動かします。人とならロボットを動かします。
  - スタッフが着く前に自分で動き出したら、取り消します。
- 記録：rescues.csv を書き、summary.json に `rescues`・`rescue_staff_s` を出し、モニターに「助けに行く」を出すようにしました。判定（judge_night.py）の条件にもこの入切を足しました。
- cs:63 で check-in し、画面なしの実行ファイルを作り直しました（build_id cs63_20261103_1114・保留 0）。
- ⑦（fam_p5・種7・2台）を 1 回回して確かめました。

結果
- 前に約 9 分止まっていた向かい合い（1472 秒〜）は、助け 2 回、約 105 秒で解けました。
  - 1 回目は 30 秒＋手が空くまで 0.05 秒＋歩き 5.9 秒＋15 秒＝51.3 秒で、1号機を 1.16 m 動かしました。
  - 解けるまでの秒は、35 回の中央値で 51.0 秒です。どの行も「30 秒＋待ち＋歩き＋15 秒」と合っていました。
- ただし、依頼が「180 回は回さずに止める」と決めている変な動きが 2 つとも出ました。
  - **同じ所で何度も助けが要る**：往復を終えて止まっている2号機に、1号機が阻まれ続けました。
    - 待機場所の手前 (16.5, 5.9) で 8 回、受け渡し口の近く (15.7, 5.0) で 8 回、助けが入りました。
    - 2号機は止まっていて道が無いので、1号機はほとんど動かされず（35 回中 25 回が 0.05 m 未満）、同じ所でまた止まりました。
    - この詰まりは、助けを入れる前の cs:62 の同じ回でも起きていました（同じあたりで 30 秒以上の止まりが 12 回）。
  - **助けに行ったスタッフが詰まる**：4028 秒、通路 A01 で、助けに行ったスタッフが 2 回あきらめ、3 回目に「移した」になりました。
- このため、基準データの 180 回は回していません。店ごと・台数ごとの表と、試算の stuck_rate 0.01 との並べは作れていません。
- 確かめの 1 回の数：呼んだ 41 回、動かした 35 回、取り消し 6 回、`rescue_staff_s` 943.7 秒、往復 127 回でした。
- 台帳は 1658 → 1659 行で、追記 1 行だけです。
  - 始める前の md5：7896338a6483643ca5f88f1950795d27（退避と同じ）
  - 終わったあとの md5：69510edef7fe2a792ba5e27a62421310
  - 先頭 1658 行の md5 は始める前と同じで、前の行は変わっていません。

変えたファイル
- Assets/ServiceSim/Service/ServiceOps.cs
- Assets/ServiceSim/Robot/ServingRobotController.cs
- Assets/ServiceSim/People/StaffAgent.cs、PersonRobotYield.cs
- Assets/ServiceSim/Experiment/ExperimentRunner.cs、HeadlessRun.cs、TripLogger.cs
- Assets/ServiceSim/Store/KitchenMonitor.cs
- tools/make_queue.py、night_runner.py、judge_night.py
- 以上 11 ファイルを cs:63 で check-in しました。
- Builds/headless を作り直しました。
- 報告は claude_code_reports/20261103_tpfix_base.txt に書きました。

決めてほしいこと
1. 相手が往復を終えて止まっている台（待機場所や受け渡し口の点にいる台）のときの決まり。今の決まりのままでは、同じ所で何度も助けが入ります。案は次の 3 つです。
   - 止まっている台の方を動かす
   - 相手の体から離れた所へ、呼んだ台を動かす
   - 店の形の側で、待機場所どうしの間を広げる（今は中心どうし約 0.76 m）
2. 助けに行くスタッフが細い通路で着けないとき、今は 60 秒であきらめ、3 回目で「移した」（瞬間移動）になります。このままにするか、助けのときだけ別の決まりにするか。
3. 1・2 を決めたあと、⑦の確かめをやり直して 180 回を回すか。並べ方と比べる相手は用意できています。
4. 画面なしの実行ファイルの既定を「助け 入」のままにしておくか。次の依頼77 は入を前提にしているので、入のまま残しています。
```

### 試算のページの動画は、入れ替えを見送った

撮り直し（依頼75）は Mac の見張り係が止まって一度失敗し、その後 6店の録画（夜の名前 20261103_rec_focus2・実行ファイル cs:62）が別に回っていました。ただ、この録画を作った cs:62 は、店員の助けが入る前（助けは cs:63 から）で、瞬間移動もまだ残っていた版です（瞬間移動が0になったのは依頼81 の cs:69。依頼80 の⑦の同じ回では22回）。さらに④と⑤は時間の上限で止まり（stopped）、大型店⑧⑨の録画はありません。入れ替えても「瞬間移動を直した版」にならないので、試算のページは v1 の動画のままにしています。

#### 20261103_rec_focus2 の runs.csv と実行係のまとめ

```text
folder,condition,seed,pass_by,tryno,launch_running,parallel,started_iso,ended_iso,started_unix,ended_unix,started_mono,ended_mono,wall_s,status,exit_code
20261103_rec_focus2_cs62_fam_p1_r2_s7_passon_sbret_ek_post,fam_p1_s4_r2_a42,7,on,1,1,2,2026-11-03T07:01:08,2026-11-03T08:09:52,1793656868.884,1793660992.788,337481.761,341605.665,4123.904,completed,0
20261103_rec_focus2_cs62_fam_p5_r2_s7_passon_sbret_ek_post,fam_p5_s4_r2_a42,7,on,1,2,2,2026-11-03T07:01:13,2026-11-03T08:14:03,1793656873.891,1793661243.360,337486.769,341856.237,4369.469,completed,0
20261103_rec_focus2_cs62_fam_p2_r2_s1_passon_sbret_ek_post,fam_p2_s2_r2_a28,1,on,1,2,2,2026-11-03T08:09:52,2026-11-03T09:18:39,1793660992.658,1793665119.539,341605.704,345732.585,4126.881,stopped,2
20261103_rec_focus2_cs62_fam_p3_r2_s1_passon_sbret_ek_post,fam_p3_s4_r2_a46,1,on,1,2,2,2026-11-03T08:14:03,2026-11-03T09:22:50,1793661243.238,1793665370.437,341856.259,345983.458,4127.200,stopped,2
20261103_rec_focus2_cs62_fam_p0_r2_s8_passon_sbret_ek_post,fam_p0_s2_r2_a19,8,on,1,2,2,2026-11-03T09:34:13,2026-11-03T10:35:18,1793666053.954,1793669718.025,345983.555,349647.627,3664.072,completed,0
20261103_rec_focus2_cs62_fam_p4_r2_s1_passon_sbret_ek_post,fam_p4_s3_r2_a38,1,on,1,2,2,2026-11-03T09:30:03,2026-11-03T10:36:08,1793665803.152,1793669768.117,345732.739,349697.704,3964.965,completed,0

2026-11-03 10:36:08  --- まとめ ---
2026-11-03 10:36:08    completed: 4 回
2026-11-03 10:36:08    stopped: 2 回
2026-11-03 10:36:08    completed の実時間: 平均 67.2 分 / 最長 72.8 分
```

試算のページの動画は、瞬間移動が0で店員の助けも入った今の標準（cs:69 以降）で撮り直してから入れ替えます。
