---
title: "サービスロボット導入シミュレーション Log#24 | 配膳ロボットの試算が、混んだファミレスの実物に近づいた"
slug: register-priority-serving-robot
date: 2026-10-30
day: 405
meta_description: "混んだファミレスでは会計のレジを先に回すと卓の片付けが追いつかず、配膳ロボット2台でもスタッフは3人から減らなかった。元店員の実感で試算（SimPy）を直し、Unityの3Dシミュレーションとの差が縮んだ。"
tags: [サービスロボット導入シミュレーション, 配膳ロボット, 飲食店, ファミレス, SimPy, Unity, シミュレーション, 人手不足]
series: "導入シミュレーション"
---

混んだファミレスでは、会計のレジを先に回すと食べ終えた卓の片付けが追いつかなくなり、42卓の店（1時間42組）に配膳ロボットを2台入れても、店を回すスタッフは3人から減らせませんでした。元ファミレス店員の実感に合わせて、配膳ロボットの試算（SimPy）を「会計のスタッフはレジに並んだ順に付く」形に直したところ、Unity の3Dのシミュレーションとの差は、普通の混み具合ではほぼなくなりました。

配膳ロボットを入れるか迷っている店や、ロボットを提案する会社が知りたいのは、「入れたら何人で回るのか」だと思います。Pixels to Motors では、その答えをサイトの試算（計算で出す待ち時間）と、Unity で建てた3Dの店（人とロボットが実際に通路を歩く）の両方で出しています。数字の正はサイトの試算で、Unity は通路で起きることを測る役です。ところが、混んだ日を Unity で一晩回すと、**席は空いているのにお客さんを案内できない時間が、試算の3〜6倍になっていました。**

原因は、スタッフの仕事の量ではありませんでした。調べると、試算と Unity で「会計のスタッフを呼ぶタイミング」が違っていました。試算では、レジの順番が来てからスタッフを呼んでいたので、会計が後回しになり、食べ終えたお客さんが卓に座ったまま会計を待っていました。卓が空かないので、片付けの仕事も入ってきません。Unity では、お客さんがレジに並んだら、すぐスタッフが付きます。会計がどんどん済むぶん片付けが追いつかず、汚れた卓がたまります。同じ詰まりが、試算では「会計待ち」、Unity では「片付け待ち」として見えていました。

どちらが実際の店に近いかは、ファミレスでフロントをしていた経験で決めました。

> フロントスタッフやっていた頃、基本はAでレジや案内を優先に仕事していたね。

試算を v2.9 に直すと、LP の最初の画面の「店舗が回る人数 3人 → 2人」は「3人 → 3人」に変わりました。前の版で2人でも回っていたのは、お客さんが卓で24分会計を待っていたからでした。

| やったこと | なぜ | 結果 |
|---|---|---|
| 6店の混み具合を「空いている・混んでいる」に広げ、Unity で一晩360回回した | これまで Unity は「普通」しか回していなかった | 混んでいるとき、席は空いているのに待つ時間が試算の3〜6倍 |
| スタッフの手がふさがった時間を、仕事ごとに分けて試算と比べた | 仕事の量の差か、取り方の差かを分けるため | 仕事の量は5〜10%しか違わない。客が帰ってから片付けにスタッフが付くまでが Unity 約10分・試算約20秒（③） |
| Claude Code に Unity のスタッフの仕事の選び方を読んでもらった | 取り方のどこが違うかを知るため | 会計のスタッフを呼ぶタイミングが違っていた |
| 試算を v2.9 にし、全32,850条件を計算し直した | フロントの実感（レジと案内を優先）に合わせるため | 普通の混み具合で Unity とほぼ一致。「店が回らない」1,922 → 2,617条件、「レジが詰まる」5,372 → 2,626条件（レジ1つ） |
| 試算のページと LP を v2.9 にした | 人件費が浮かない結果も正直に見せるため | LP の最初の画面は 3人 → 3人、ひと月の差し引きは −14.4万円 |

【図解】LOG405_checkout_order.html

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

【バナー】serving-robot-one-less-staff


## 作業ログ ― 6店の混み具合を「空いている・混んでいる」に広げ、Unity で一晩360回回した

### サイトの試算・Unity・依頼箱は、このシリーズで何をしているか

このシリーズでは、同じファミレスを2つの道具で回しています。

| 言葉 | 何か | このシリーズでの使い方 |
|---|---|---|
| サイトの試算 | Python の [SimPy](https://simpy.readthedocs.io/)（出来事を時刻の順に並べて進める、待ち行列のシミュレーションの道具）で書いた計算（`haizen_sim.py`） | 店・スタッフの人数・ロボットの台数・混み具合の組み合わせ32,850通りを、それぞれ40回回した平均を [試算のページ](https://pixelstomotors.com/sim/serving-robot) に出している。数字の正 |
| Unity | ゲームなどを作る3Dの開発環境（[Unity](https://unity.com/ja)） | サイトの店の形どおりに店を建て、スタッフ・お客さん・ロボットを通路で歩かせて、止まる回数や待ち時間を測る |
| 依頼箱 | Claude が書いた依頼を、Mac の見張り係が [Claude Code](https://claude.com/claude-code)（コードを書いて動かす AI）に自動で渡す仕組み | Unity を動かす作業はすべてここから頼む。夜のあいだも続けて回せる |
| 種 | 乱数の出発点。同じ種なら同じお客さんの来方になる | Unity は1つの条件を種10個で回し、10回の中央値（真ん中の値）で見る |
| 店が回らない | 席は空いているのに、片付けや案内が追いつかず待った時間が平均10分以上 | サイトの試算の印。LP の「店舗が回る人数」は、この印が出ない最少の人数 |

Unity はこれまで、6店（③⑦④⑤⑥②）とも、混み具合は「普通」、スタッフは真ん中の人数だけを回していました。サイトの試算は「空いている・普通・混んでいる」を選べるので、Unity でも空いている日と混んでいる日を回すことにしました。

### Unity が読む条件のファイルに、混み具合の36条件を足した

Unity は、サイトの `public/sim/serving-robot-params.json` を読んで条件を決めます（値を手で写さないため）。書き出しの `export_params.py` に、店ごとに「空いている 0・1・2台 → 混んでいる 0・1・2台」の6条件を後ろに足しました。前の18条件（普通）は、並びも中身も変えていません。条件は18件から54件になりました（p2m-site commit 29a5a62）。

```diff
--- /sessions/rcw-01levexvnksg51fwlydnyzff/mnt/PixelstoMotors/デジタルツイン学習R2R2R/サービスロボット導入シミュレーション/simulation/v2_8/_前の版_gendffc100b269c/export_params.py	2026-10-29 08:00:12.448367603 +0000
+++ /sessions/rcw-01levexvnksg51fwlydnyzff/mnt/PixelstoMotors/デジタルツイン学習R2R2R/サービスロボット導入シミュレーション/simulation/v2_8/export_params.py	2026-10-29 14:37:18.641975486 +0000
@@ -6,7 +6,7 @@
 使い方: python3 export_params.py results.json <出力先>   （出力先は p2m-site の public/sim/serving-robot-params.json）
 - common：店に共通の値。role が "unity_measures" の値は Unity で測ってサイトに戻すための仮の値なので、Unity は読まない
 - stores：Unity で回す店（fam_p1・fam_p5・fam_p2・fam_p3・fam_p4・fam_p0）の値。registers はレジの数（v2.8）
-- unity_conditions：Unity で回す条件（店ごとに3つ。③⑦④⑤⑥②で18条件）と、同じ条件の試算の結果（40回の平均）。Unity の店はレジの内側に店員が1人だけなので、レジは1つ
+- unity_conditions：Unity で回す条件（0〜17番は店ごとに混み具合が普通の3つ。18〜53番は2026-10-29に足した空いている・混んでいるの各3つ。計54条件）と、同じ条件の試算の結果（40回の平均）。Unity の店はレジの内側に店員が1人だけなので、レジは1つ
 """
 import json
 import sys
@@ -30,7 +30,7 @@
 }
 common = {k: {"value": v, "role": ROLE.get(k, "site_rule")} for k, v in h.COMMON.items()}
 
-UNITY_STORES = ["fam_p1", "fam_p5", "fam_p2", "fam_p3", "fam_p4", "fam_p0"]   # ④は2026-10-27に追加（スタッフ2人・28組/時）。⑤は2026-10-28に追加（スタッフ4人・46組/時）。⑥も2026-10-28に追加（スタッフ3人・38組/時）。②も2026-10-29に追加（スタッフ2人・19組/時）
+UNITY_STORES = ["fam_p1", "fam_p5", "fam_p2", "fam_p3", "fam_p4", "fam_p0"]   # ④は2026-10-27に追加（スタッフ2人・28組/時）。⑤は2026-10-28に追加（スタッフ4人・46組/時）。⑥も2026-10-28に追加（スタッフ3人・38組/時）。②も2026-10-29に追加（スタッフ2人・19組/時。同じ日に T06 を右へ1.0m 動かして②だけ計算し直した）
 UNITY_REGISTERS = 1
 stores = {}
 for k in UNITY_STORES:
@@ -50,6 +50,18 @@
         r = rows[h.key(c)]
         conds.append({"condition": c, "site_result": {m: r[m] for m in r if m not in c}})
 
+# 2026-10-29 エイイチと決めた：混み具合を「空いている・混んでいる」にも広げて夜に回す。
+# 前の18条件（0〜17番）は並びも中身も変えず、後ろに足す（店ごとに 空いている0・1・2台 → 混んでいる0・1・2台）
+for k in UNITY_STORES:
+    s = h.STORES[k]
+    staff = s["staff"][1]           # 真ん中の人数のまま
+    for arr in (s["arrivals"][0], s["arrivals"][2]):   # 空いている・混んでいる
+        for robots in (0, 1, 2):
+            c = {"store": k, "staff": staff, "robots": robots, "arrivals_per_h": arr, "mode": "serve",
+                 "trays": 3 if robots else 1, "fragile_by": "any", "registers": UNITY_REGISTERS}
+            r = rows[h.key(c)]
+            conds.append({"condition": c, "site_result": {m: r[m] for m in r if m not in c}})
+
 D = {
     "meta": {
         "schema_version": "serving-robot-params/0.4",
```

足した条件の来客のペース（組/時）は、③ 30・54／⑦ 30・54／④ 20・36／⑤ 33・59／⑥ 27・49／② 14・24 です。台帳（Unity の回ごとの記録を1行ずつ足していく表）は、回す前に KIOXIA の `archive/unity_ledger_v1_prenight_20261106_busy/` に控えました（672行・md5 79ded907583535ed5ac120e56595ac64）。

### 依頼30「混み具合を広げて夜を回す」の全文

````markdown
---
作業フォルダ: /Users/shimazueiichi/Robotics_UX/ServiceSim
上限時間: 600
---
# 依頼：6店の混み具合を「空いている・混んでいる」に広げて、夜（360回）を回す（2026-10-29）

エイイチと決めたこと：今までの Unity は、6店とも混み具合が「普通」だけだった。サイトの試算は「空いている・普通・混んでいる」を選べるので、Unity でも空いている・混んでいるを回す。6店に共通の待機場所のまわりの詰まりが、混むとどれだけ増えるかも見る。

サイト（正）はもう更新した（p2m-site commit 29a5a62）。
- `public/sim/serving-robot-params.json`：unity_conditions が 18件 → 54件。0〜17番（普通）は並びも中身も前と同じ。18〜53番に、店ごとに「空いている 0・1・2台 → 混んでいる 0・1・2台」の6条件を足した
- スタッフは今までどおり真ん中の人数。上げ膳だけ・1往復で3卓（0台は1）・レジ1つも同じ。meta・common・unity_rules・stores は前と同じ。店の形の版は 08db8a0d067c のまま
- 足した条件の来客のペース（組/時）：③ 30・54／⑦ 30・54／④ 20・36／⑤ 33・59／⑥ 27・49／② 14・24
- 台帳の退避は Claude が済ませた：`/Volumes/KIOXIA/r2r2r-data/service_sim/unity/archive/unity_ledger_v1_prenight_20261106_busy/unity_ledger_v1.csv`（672行・md5 79ded907583535ed5ac120e56595ac64）

## 始める前に確かめること（違えば手を止めて報告）

1. Unity エディタが起動していない（起動していたら閉じずに報告して終わる）
2. /Volumes/KIOXIA がマウントされている。鍵ファイル unity_ledger_v1.lock がない
3. 台帳の md5 が上の退避と同じ
4. ServiceSim の保留が 0
5. https://pixelstomotors.com/sim/serving-robot-params.json の unity_conditions が54件、meta.layout_generation が 08db8a0d067c（違えば数分おきに取り直す。30分たっても違えば報告して終わる）

## A. 条件を写して、並びを作る

1. サイトの params を、今までと同じやり方でプロジェクトに写す（手で値を写さない）。前の写しと比べて、変わったのが unity_conditions の18〜53番の追加だけであることを確かめる。店の形のファイルは写し直さなくてよい（版が同じため）
2. 実行ファイルが params を中に持っている作りなら、今までと同じやり方で画面なしの実行ファイルを作り直す。コードは変えない。check-in して保留 0
3. 並びを作る。6店 × 空いている・混んでいる × 0・1・2台 × 種0〜9 = **360回**（すれ違い入・待機場所へ戻る 入・案B 切。今の標準のまま）
   - make_queue.py に、混み具合（来客のペース）で選ぶ指定がなければ足してよい。**何も指定しないときの並び（③⑦の60回）と、--stores だけ指定したときの並び（その店の普通の30回）は変えない**
   - 回の名前（フォルダ名）に来客のペースが入らず、普通の回や空いている・混んでいる同士で名前が重なるなら、名前に `a{組/時}`（例 `a54`）を入れて重ならないようにする。台帳の列は変えない（arrivals_per_h の列がある）
   - 並べる順は、混んでいる180回 → 空いている180回が望ましい（朝までに終わらなくても混んでいる方が先に揃うように）。できなければ順はそのままでよい
   - 回す前に、queue が360回で、名前に重なりがなく、普通の条件（0〜17番）が入っていないことを確かめる。違えば回さずに報告

## B. 夜（360回）

```
python3 tools/night_runner.py --queue queue_busy.json --parallel 4 --night 20261106 --record-name 20261106_busy
```

- 夜の名前は 20261106。記録は night/20261106_busy/ に置く。オプションの名前が違えば、同じ意味になるように合わせる
- 同時に回す本数は4のまま。1回おおよそ4分なので、6〜7時間かかる見込み。時刻で止めず、並べた回が全部終わるまで回す
- 回している間は、ほかの作業をしない。Assets・tools・設定ファイルを開かない・変えない。Unity を開かない
- Ctrl+C で止めない。途中で止まっても、同じ夜を回し直さない（そのまま報告する）
- 混んでいる0台の回は、全員が帰る前に打ち切られることがある。それは失敗として扱わず、そのまま記録する
- 終わったら queue_busy.json を作業フォルダに残さない
- judge_night.py は回してよい。基準（v5）は③⑦の普通にしかないので、今回の条件は「基準なし」になってよい。judge が今回の条件で止まっても、judge は直さずにその旨を報告する

## 変えないもの

- 店の回し方・人とロボットの動き・速さ・Stiffness などの物理の設定・同時に回す本数（N=4）
- 台帳の列（59列）と今までの行。今までの night/ と出力の記録
- night_runner.py・judge_night.py。make_queue.py は A-3 の追加だけ

## 報告

依頼箱の結果に加えて、claude_code_reports に 1つ書く（場所：~/Library/Mobile Documents/com~apple~CloudDocs/デスクトップ/ADAPT DESIGN/じぶんの/PixelstoMotors/デジタルツイン学習R2R2R/サービスロボット導入シミュレーション/unity/claude_code_reports/）。

**20261106_night_busy.txt**
- 始める前の確かめ5つの結果。写した params の比べ方と結果。作り直したなら cs 番号・build_id
- make_queue.py に足したもの（差分の全文）と、回の名前の付け方
- runner_log のまとめ（completed の回数・失敗・止まった回・打ち切られた回・最長の実時間・夜全体の始まりと終わりの時刻）
- 台帳の行数（672 → 1032 になるはず）と、先頭672行の md5 が退避と同じか
- 全員が帰らなかった回の名前（原因の調べはしない）
- 店ごと × 混み具合ごと × 台数ごと（0・1・2台）に、種10個の中央値と最小・最大：入店待ち・そのうち席は空いているのに待った時間・料理の待ち・会計待ち・入れた組の数・ロボットが運んだ皿の割合・往復の数・30秒以上の止まり（1往復あたり）・動けなくなった（60秒）・瞬間移動・打ち切り・卓を空けた・冷めた皿の割合。試算の値（params の site_result）も並べる
- 同じ店の普通の夜の中央値も横に並べる（③⑦ 20261102・④ 20261030・⑤ 20261103・⑥ 20261104・② 20261105）。標準（待機場所へ戻る・すれ違い・cs）が今回と違う夜は、その違いを書く
- ロボットを1台増やしたとき（0→1・1→2）の向き（入店待ち・料理の待ち）が、種の過半数で試算と同じか逆か。店 × 混み具合ごとに
- 2台の30秒以上の止まりを、店 × 混み具合ごとに、種10個の合計で、次の3つに分けて：待機場所の四角の中／受け渡し口の点から 2.5 m 以内（待機場所の中を除く）／そのほか（多い所があれば 2 m 四方のマスで上位3つ）。待機場所と受け渡し口の点は各店の店の形から取る
- judge.md ができたか（中身の判断はしない）
````

### 依頼30 の結果（依頼箱の報告）

23:40 に始まり、6:09 に終わりました（389分）。360回とも最後まで回り、失敗は0です。

```markdown
# 結果：20261029_30_混み具合を広げて夜を回す

- 状態：完了
- 始めた時刻：2026-10-29 23:40
- 終わった時刻：2026-10-30 06:09（389分）
- 作業フォルダ：/Users/shimazueiichi/Robotics_UX/ServiceSim
- この続きを Claude Code で話すとき：作業フォルダで `claude --resume 093a5918-86a3-4c91-8716-ef6a03b33f1c`

## Claude Code の報告

**やったこと**
- 始める前の確かめ5つは、すべて合格でした。
  - Unity エディタは起動していません（Unity Hub だけ）。
  - KIOXIA はつながっていて、鍵ファイルはありません。
  - 台帳は 672 行で、md5 が退避と同じです。
  - 保留は 0 です。
  - 公開中の params は条件 54 件・版 08db8a0d067c で、1回目の取得で合いました。
- サイトの params を cp でそのまま写しました（控えは `site_copy_08db8a0d067c_29a5a62/`）。
  - JSON として前の写しと比べると、違いは条件 18〜53 番の追加だけでした（meta・common・unity_rules・stores と 0〜17 番は同じ）。
  - check-in は cs:44 です。
- 実行ファイルは params を中に持つ作りなので、今までと同じやり方で作り直しました。
  - build_id は cs44_20261029_2343 で、エラーは 0、保留も 0 です。
  - 実行ファイルが知っている条件は 54 件でした（何も書かない起動のしかたで確かめました）。
  - コードは変えていません。
  - 前の夜（cs:40）のあとに入った録画のしくみ（cs:41〜43）も、この実行ファイルに入っています。
- make_queue.py に `--crowd` を足しました（cs:45）。
  - 店ごとに来客のペースの少ない順で、空いている・普通・混んでいるに分けます。付けないときは普通だけです。
  - 普通でない回は、名札を `night_a{組/時}` にしました（例 `20261106_night_a54_cs44_fam_p1_r0_s0_passon_sbret`）。
  - 前の版と比べて、何も指定しない60回・`--stores` だけの30回（6店とも）・`--conditions` を付けた場合は、どれも同じでした。足さずに新しい params を使うと、60回が180回、30回が90回に増えていました。
- 並びは 360 回で、名前の重なり 0・普通の条件 0・今までの回との重なり 0 でした。混んでいる180回 → 空いている180回の順に並べました。
- 夜を最後まで回し、終わったあと queue_busy.json を消しました。

**結果**
- **夜全体:** 2026-10-29 23:45 に始まり、10-30 06:04 に終わりました（6時間19分）。
  - completed 360 回、失敗 0、止まった回 0、やり直し 0 です。
  - 最長の実時間は 7.9 分でした（④混んでいる 2台 種2）。
- **全員が帰らなかった回・打ち切り:** 全員が帰らなかった回はありません。回ごと打ち切られた回もありません。混んでいる 0台の60回も、全員帰りました。
- **台帳:** 672 → 1032 行になりました。先頭 672 行の md5 は退避と同じです。列は 59 列のままです。
- **混んでいるときの入店待ち:** どの店でも試算より長くなりました。

  | 店・台数 | 入店待ち（中央値） | 試算 |
  |---|---|---|
  | ③ 0台 | 19.02 分 | 13.03 分 |
  | ④ 2台 | 45.92 分 | 14.69 分 |

  このうち「席は空いているのに待った」は、③0台で 8.94 分（試算 1.55 分）でした。
- **2台の30秒以上の止まり（種10個の合計、空いている → 混んでいる）:**

  | 店 | 合計 | うち待機場所の中 |
  |---|---|---|
  | ③ | 174 → 242 | 45 → 26 |
  | ⑦ | 173 → 255 | 50 → 58 |
  | ④ | 496 → 861 | 70 → 116 |
  | ⑤ | 113 → 129 | 27 → 9 |
  | ⑥ | 85 → 147 | 69 → 76 |
  | ② | 147 → 292 | 52 → 60 |

- **1台増やしたときの向き:** 種の過半数で試算と逆になったのは5か所です。
  - ⑦混んでいる 0→1台 の入店待ち（逆 9/10）
  - ④空いている 0→1台 の入店待ち（逆 7/10）
  - ④空いている 1→2台 の料理の待ち（逆 6/10）
  - ④混んでいる 1→2台 の入店待ち（逆 9/10）
  - ④混んでいる 1→2台 の料理の待ち（逆 7/10）
- **judge.md:** できました。360 回とも「基準なし」で、judge は止まりませんでした。
- **普通の夜との違い:** ④の普通の夜（20261030）だけ、待機場所へ戻るが「切」です。cs はどの夜も今回と違い、報告ファイルに書きました。
- 店 × 混み具合 × 台数の全部の表・試算の値・普通の夜の中央値・止まりの場所の内訳は、報告ファイルに書きました。

**変えたファイル**
- `Assets/ServiceSim/Experiment/serving-robot-params.json`（cs:44。サイトの写し）
- `tools/make_queue.py`（cs:45。`--crowd` の追加だけ）
- 新しく作ったもの
  - `unity/site_copy_08db8a0d067c_29a5a62/`（params の控えと README）
  - `unity/claude_code_reports/20261106_night_busy.txt`（報告）
- 夜の記録は KIOXIA の `night/20261106_busy/` と、出力フォルダ 360 個です。

**決めてほしいこと**
1. 混んでいるときの入店待ちと「席は空いているのに待った」が、どの店でも試算よりかなり長いです。原因を調べるかどうか。
2. 向きが種の過半数で試算と逆になった5か所（⑦が1か所、④が4か所）をどう扱うか。
3. 2台が止まる場所で、どこを先に見るか。
   - ④は混んでいると止まりが 861 回に増えます。多いのは、マス x 2〜8・y 2〜4 と受け渡し口のまわりです。
   - ②は混んでいると、入口・レジのまわり（x 12〜14・y 0〜2）が 108 回に増えます。
```

### Claude Code の報告 20261106_night_busy.txt の全文

```text
====================================================================
6店の混み具合を「空いている・混んでいる」に広げた夜 360 回
依頼：20261029_30_混み具合を広げて夜を回す.md
夜の名前 20261106　記録 night/20261106_busy/　回した日時 2026-10-29 23:45〜2026-10-30 06:04
build_id cs44_20261029_2343（ServiceSim/cs:44・保留 0）　make_queue.py の追加は cs:45
====================================================================

結論

  ・360 回とも completed・全員帰った。失敗 0・止まった回 0・やり直し 0・回ごと打ち切られた回 0。最長の実時間 7.9 分。
  ・台帳 672 → 1032 行（360 行足した。追記だけ）。先頭 672 行の md5 は退避と同じ。列は 59 列のまま。
  ・混んでいるときの入店待ちは、どの店でも試算より長い（例 ③0台 19.02 分／試算 13.03、④2台 45.92 分／試算 14.69）。
    中でも「席は空いているのに待った」が試算の数倍（③0台 8.94／試算 1.55）。原因は調べていない。
  ・2台の 30 秒以上の止まり（種10個の合計）は、空いている → 混んでいる で
    ③ 174→242・⑦ 173→255・④ 496→861・⑤ 113→129・⑥ 85→147・② 147→292。
    そのうち待機場所の四角の中は ③ 45→26・⑦ 50→58・④ 70→116・⑤ 27→9・⑥ 69→76・② 52→60。
  ・ロボットを1台増やしたときの向きが、種の過半数で試算と逆になったのは5か所：
    ⑦混んでいる 0→1台の入店待ち（逆 9/10）・④空いている 0→1台の入店待ち（7/10）・④空いている 1→2台の料理の待ち（6/10）・
    ④混んでいる 1→2台の入店待ち（9/10）・④混んでいる 1→2台の料理の待ち（7/10）。
  ・judge.md はできた（360 回とも「基準なし」。依頼のとおり）。


====================================================================
1. 始める前の確かめ（5つとも合格。2026-10-29 23:20 ごろ）
====================================================================

  1. Unity エディタ：起動していない（Unity Hub だけが起動していた）
  2. /Volumes/KIOXIA：マウントされている。unity_ledger_v1.lock：なし
  3. 台帳 unity_ledger_v1.csv：672 行・md5 79ded907583535ed5ac120e56595ac64（退避と同じ）
  4. ServiceSim の保留：0（cs:43 head）
  5. 公開中の serving-robot-params.json：unity_conditions 54 件・meta.layout_generation 08db8a0d067c（1回目の取得で合った）


====================================================================
2. 写した params と作り直し
====================================================================

  写し元：https://pixelstomotors.com/sim/serving-robot-params.json（curl で取得）
    控え：unity/site_copy_08db8a0d067c_29a5a62/（params と README.txt。前の控え site_copy_08db8a0d067c は変えていない）
  比べ方：前の写し（cs:43 の Assets/ServiceSim/Experiment/serving-robot-params.json。site_copy_08db8a0d067c の params とバイト単位で同じ）と
          JSON として読んで比べた
    meta・common・unity_rules・stores：同じ
    unity_conditions：18 件 → 54 件。0〜17 番は並びも中身も同じ。違いは 18〜53 番の追加だけ
  cp でそのまま写した（md5 64845ccf6c17ee50e39723416a18e87d）。店の形のファイルは写し直していない。
  check-in：cs:44（params 1 ファイルだけ）
  実行ファイル：params を TextAsset として中に持つ作りなので、今までと同じコマンド（BuildHeadless.Build・-batchmode）で作り直した
    build_id cs44_20261029_2343 / unity_cs ServiceSim/cs:44 / pending_changes 空 / Build Finished, Result: Success・error CS 0
    確かめ：知らない条件の名前で起動し（台帳にもフォルダにも何も書かない道）、実行ファイルが知っている条件が 54 件であることを見た
  コードは変えていない。保留は check-in のあと 0、夜のあとも 0。
  ※ cs:41〜43 は前の夜（cs:40）のあとに入った録画のしくみ（-ssRecord のときだけ動く）。今回の実行ファイルにはこれも入っている。


====================================================================
3. make_queue.py に足したもの（cs:45）と回の名前
====================================================================

  足したもの：--crowd（quiet / normal / busy をカンマ区切り。挙げた順に混み具合ごとにまとめて並べ、その中は今までどおり種ごと）。
    params の条件は混み具合の名前を持たないので、店ごとに来客のペースの少ない順で 空いている・普通・混んでいる とした。
    --crowd を付けないときは普通だけ。
  変わらないことの確かめ：前の make_queue.py ＋前の params と、新しい make_queue.py ＋新しい params で queue を作って比べた
    （params_source 以外すべて同じ）
    何も付けない：60 回で同じ／--stores fam_p1・fam_p5・fam_p2・fam_p3・fam_p4・fam_p0 をそれぞれ1つ：30 回で同じ／
    --conditions を付けた場合：同じ
    （足さずに新しい params を使うと、何も付けない 60 → 180 回、--stores だけ 30 → 90 回に増えていた）
  回の名前：普通でない回は、回ごとの名札を night_a{来客/時} にした（night_runner.py と実行ファイルの名札 -ssLabel の仕組みをそのまま使う）。
    例 20261106_night_a54_cs44_fam_p1_r0_s0_passon_sbret。普通の回の名前は今までどおり（名札 night）。
  回す前の確かめ：360 回・名前の重なり 0・普通の条件（0〜17 番）0・台帳の今までのフォルダ名との重なり 0・
    並べた順は混んでいる 180 回 → 空いている 180 回・360 回ともすれ違い入・待機場所へ戻る入・案B切
  使ったコマンド
    python3 tools/make_queue.py --stores fam_p1,fam_p5,fam_p2,fam_p3,fam_p4,fam_p0 --crowd busy,quiet --out queue_busy.json
    python3 tools/night_runner.py --queue queue_busy.json --parallel 4 --night 20261106 --record-name 20261106_busy
  終わったあと queue_busy.json は消した（記録の queue.json は night/20261106_busy/ に残っている）。
  気づいたこと：make_queue の表示「種0 の全36条件」は、混み具合ごとにまとめたときは実際は 18 条件ずつ（表示だけ。並びは正しい）。

  差分の全文
--- /tmp/busy_dl/make_queue.orig.py	2026-10-29 23:43:46
+++ tools/make_queue.py	2026-10-29 23:44:29
@@ -38,6 +38,16 @@
 （_ruleb の後ろ。既定が入になっても、入の回には今までどおり付ける）。
 決まりの値（pass_clear_m など）は Unity が params の unity_rules から読む（ここでは写さない）。
 
+混み具合（2026-10-29。params の unity_conditions が 54 件になり、店ごとに来客のペースが3つになった）:
+  params の条件は混み具合の名前を持たないので、店ごとに来客のペース（arrivals_per_h）の違う値を並べ、
+  いちばん少ないものを「空いている quiet」、真ん中を「普通 normal」、いちばん多いものを「混んでいる busy」とする
+  （1つしかない店はそれが普通）。
+  --crowd を付けないときは **普通だけ**（今までと同じ。何も付けない ③⑦ の60回・--stores だけの 30回は変わらない）。
+  --crowd busy,quiet のように挙げると、挙げた順に混み具合ごとにまとめて並べる（その中は今までどおり種ごと）。
+  普通でない回は、出力フォルダ名が普通の回や別の混み具合の回とぶつからないよう、
+  回ごとの名札を {--label}_a{来客/時}（例 night_a54）にする（普通の回の名前は今までどおり）。
+  --conditions を付けたときは、その名前の条件をそのまま使う（--crowd の既定では絞らない）。
+
 使い方（プロジェクトの中で）
   # 60回（すれ違い入・待機場所へ戻る入・案B切。2026-10-28 から標準）
   python3 tools/make_queue.py --out /tmp/queue.json
@@ -110,6 +120,28 @@
     return meta, conds
 
 
+CROWDS = ('quiet', 'normal', 'busy')
+
+
+def crowd_of(conds):
+    """条件の名前 → 混み具合（quiet / normal / busy）。店ごとに来客のペースの少ない順で決める"""
+    rates = {}
+    for c in conds:
+        rates.setdefault(c['store'], set()).add(int(c['arrivals_per_h']))
+    out = {}
+    for c in conds:
+        r = sorted(rates[c['store']])
+        v = int(c['arrivals_per_h'])
+        if len(r) == 1:
+            out[condition_id(c)] = 'normal'
+        elif len(r) == 3:
+            out[condition_id(c)] = CROWDS[r.index(v)]
+        else:
+            sys.exit('%s の来客のペースが %d 通り（%s）で、空いている・普通・混んでいるに分けられません'
+                     % (c['store'], len(r), ','.join(str(x) for x in r)))
+    return out
+
+
 def rule_value(rules, key):
     """unity_rules の値（role=site_rule のものだけ。Unity の SimParams.UnityRule と同じ）。
     無ければ止める（既定を手で埋めない）"""
@@ -129,6 +161,9 @@
                          '既定は ③ fam_p1・⑦ fam_p5 だけ（--conditions を付けたときは絞らない）')
     ap.add_argument('--conditions', default='',
                     help='条件の名前をカンマ区切りで絞る（既定は選んだ店の条件ぜんぶ）')
+    ap.add_argument('--crowd', default='',
+                    help='混み具合をカンマ区切りで選ぶ（quiet / normal / busy。挙げた順に並べる）。'
+                         '既定は normal だけ（--conditions を付けたときは絞らない）')
     ap.add_argument('--label', default='night', help='出力フォルダに入る名札')
     ap.add_argument('--pass-by-extra', default='',
                     help='すれ違い「入」でも回す条件（カンマ区切り）。'
@@ -162,7 +197,16 @@
     if not seeds:
         sys.exit('--seeds が空です')
 
+    crowd = crowd_of(conds)       # 店を絞る前の全条件で決める（2026-10-29）
     want = [s.strip() for s in a.conditions.split(',') if s.strip()]
+    crowds = [s.strip() for s in a.crowd.split(',') if s.strip()]
+    bad = [s for s in crowds if s not in CROWDS]
+    if bad:
+        sys.exit('知らない混み具合です: %s\n  使える混み具合: %s' % (', '.join(bad), ', '.join(CROWDS)))
+    if len(set(crowds)) != len(crowds):
+        sys.exit('--crowd に同じ混み具合が2回あります: %s' % a.crowd)
+    if not crowds and not want:
+        crowds = ['normal']       # 付けないときは普通だけ（今までと同じ並び）
     stores = [s.strip() for s in a.stores.split(',') if s.strip()]
     if stores:
         known_stores = []
@@ -183,6 +227,8 @@
             sys.exit('知らない条件です: %s\n  使える条件: %s'
                      % (', '.join(bad), ', '.join(sorted(known))))
         conds = [c for c in conds if condition_id(c) in want]
+    if crowds:
+        conds = [c for c in conds if crowd[condition_id(c)] in crowds]
 
     extra = [s.strip() for s in a.pass_by_extra.split(',') if s.strip()]
     if extra and a.pass_by == 'on':
@@ -197,7 +243,7 @@
                  % (', '.join(bad), ', '.join(sorted(known))))
 
     def make(c, seed, pass_by):
-        return {
+        r = {
             'condition': condition_id(c),
             'seed': seed,
             'pass_by': pass_by,          # 'on' / 'off'（実行ファイルへ -ssPassBy で渡す）
@@ -212,6 +258,10 @@
             'fragile_by': c.get('fragile_by', 'any'),
             'registers': int(c.get('registers', 1)),
         }
+        # 普通でない回は名札に来客のペースを入れる（出力フォルダ名がぶつからないように。2026-10-29）
+        if crowd[condition_id(c)] != 'normal':
+            r['label'] = '%s_a%d' % (a.label, int(c['arrivals_per_h']))
+        return r
 
     # 1種ぶんの「条件の並び」を先に作る。
     #   ふだんの6条件（すれ違いは --pass-by。既定 on）
@@ -237,10 +287,16 @@
     #   分かっているので、1つの条件に偏らせない。
     #   種ごとに並べれば、薄くなるのは「最後の種の後ろの数条件」だけになり、
     #   どの条件も同じ扱いになる。
+    #   --crowd を挙げたときは、挙げた順に混み具合ごとにまとめ、その中を種ごとに並べる
+    #   （例 busy,quiet：混んでいるを先に揃える。2026-10-29）
+    groups = [crowds] if not a.crowd else [[g] for g in crowds]
     runs = []
-    for s in seeds:
-        for c, pb in units:
-            runs.append(make(c, s, pb))
+    for g in groups:
+        for s in seeds:
+            for c, pb in units:
+                if crowds and crowd[condition_id(c)] not in g:
+                    continue
+                runs.append(make(c, s, pb))
 
     q = {
         'schema': 'servicesim-night-queue/0.1',
@@ -262,6 +318,8 @@
         'count': len(runs),
         'runs': runs,
     }
+    if a.crowd:
+        q['crowd'] = crowds          # 混み具合（並べた順。2026-10-29）
 
     out_dir = os.path.dirname(os.path.abspath(a.out))
     if out_dir and not os.path.isdir(out_dir):
@@ -282,6 +340,8 @@
                                              else '切', src[rule_b_from]))
     print('  待機場所へ戻る: %s（%s）' % ('入（出力フォルダ名の末尾に _sbret）' if a.standby_return == 'on'
                                        else '切（往復を終えた台は受け渡し口の点で止まる）', src[standby_from]))
+    if a.crowd:
+        print('  混み具合: %s の順（普通でない回の名札は %s_a{来客/時}）' % (' → '.join(crowds), a.label))
     for cid in q['conditions']:
         mark = '（＋すれ違い入も %d 回）' % len(seeds) if cid in extra else ''
         print('    %s%s' % (cid, mark))


====================================================================
4. runner_log のまとめ
====================================================================

  始まり 2026-10-29 23:45:15　終わり 2026-10-30 06:04:12（6 時間 19 分）　runner の終了コード 0
  completed 360 / 失敗 0 / 止まった（stopped）0 / やり直し 0 / 終了コード すべて 0
  実時間：最短 2.2 分・平均 4.1 分・最長 7.9 分（④混んでいる 2台 種2・476 秒。上限 90 分）
  回ごと打ち切られた回（全員が帰る前に止まった回）：0。混んでいる 0台の回も 60 回とも全員帰った
  （参考）往復の打ち切り（timeouts 列）が1つ以上あった回は 125 回・合わせて 532


====================================================================
5. 台帳
====================================================================

  行数：672 → 1032（360 行足した。360 行とも unity_cs ServiceSim/cs:44・layout_generation 08db8a0d067c・completed）
  先頭 672 行の md5：79ded907583535ed5ac120e56595ac64（退避と同じ）
  列は 59 列のまま。鍵ファイルは残っていない


====================================================================
6. 全員が帰らなかった回
====================================================================

  なし（360 回とも全員帰った）


====================================================================
7. 表・向き・止まりの場所
====================================================================

  表の各欄：Unity の種10個の中央値［最小〜最大］／試算（params の site_result。種10個の平均。種によらず同じ値）／同じ店の普通の夜の中央値
    単位は分。割合は 0〜1。「席は空いているのに待った」は judge と同じ staff_wait（片付け待ち＋案内待ち）。blocked_wait も下の行に並べた
    入れた組の数は summary.json の site_metrics.groups、試算は site_result.groups
    30 秒以上の止まりは stops.csv の duration_s が 30 秒以上のもの（judge と同じ）
  普通の夜：③⑦ 20261102（cs:34）・④ 20261030（cs:29）・⑤ 20261103（cs:36）・⑥ 20261104（cs:38）・② 20261105（cs:40）
    標準の違い：すれ違いはどの夜も入。待機場所へ戻るは ④ 20261030 だけ切（_sbret なし）、ほかは入。案B はどれも切。
    cs は今回 cs:44。前の夜との間に入ったのは、録画のしくみ（cs:41〜43）と params の写し（cs:44）
  向き：judge と同じ数え方（5%・0.2 分より小さい変化は「変わらない」）。試算の向きは種10個の試算の平均で決めた。
    試算が「変わらない」ところは向きを比べず、Unity の増減の数だけ書いた
  止まりの場所：待機場所の四角は、店の形（site_copy_08db8a0d067c/unity/*.json）の RobotStandby の rect をまとめた四角。
    受け渡し口の点は FoodPass の point。重なる所は待機場所の中に数えた。stops.csv の x・z を使った
    ・各店の最後に（参考）として普通の夜の2台も同じ分け方で並べた（② 普通は合計 192 で、前の報告の数と同じ）

■ ③ 空いている（30 組/時）  ※各欄：Unity の種10個の中央値［最小〜最大］／試算／普通の夜の中央値
  入店待ち               0台 1.34［1.24〜1.84］／0.40／4.09　1台 1.42［1.29〜1.76］／0.25／5.00　2台 1.42［1.28〜1.68］／0.23／3.99
  うち席は空いているのに待った     0台 0.16［0.05〜0.63］／0.14／2.88　1台 0.21［0.07〜0.56］／0.06／3.30　2台 0.20［0.05〜0.46］／0.05／1.68
  （片付け待ちだけ blocked）  0台 0［0〜0］／0.04／2.48　1台 0［0〜0］／0.01／2.90　2台 0［0〜0.03］／0／1.28
  料理の待ち              0台 0.65［0.48〜1.05］／0.30／0.98　1台 2.16［1.49〜2.44］／1.61／2.48　2台 1.39［1.12〜2.34］／0.74／1.95
  会計待ち               0台 2.80［1.26〜3.97］／2.49／4.45　1台 2.84［1.46〜4.78］／2.35／6.13　2台 3.03［1.32〜4.68］／2.20／7.08
  入れた組の数             0台 58［48〜72］／60.2／82　1台 58［48〜72］／60.9／82　2台 58［48〜72］／61.3／82
  ロボットが運んだ皿の割合       0台 0.000［0.000〜0.000］／0.000／0.000　1台 0.754［0.680〜0.933］／0.826／0.706　2台 0.930［0.788〜0.971］／0.998／0.832
  往復の数               0台 0［0〜0］／—／0　1台 64［58〜72］／—／65.5　2台 106［90〜122］／—／119.5
  30秒以上の止まり          0台 0［0〜0］／—／0　1台 7.5［2〜12］／—／6　2台 18.5［10〜22］／—／24.5
  （1往復あたり）           0台 0.000［0.000〜0.000］／—／0.000　1台 0.124［0.030〜0.182］／—／0.083　2台 0.169［0.110〜0.244］／—／0.173
  動けなくなった（60秒）       0台 0［0〜0］／—／0　1台 5［1〜8］／—／3.5　2台 9［4〜17］／—／18
  瞬間移動               0台 9［8〜14］／—／10.5　1台 36.5［29〜58］／—／49　2台 45［30〜65］／—／60
  打ち切り               0台 0［0〜0］／—／0　1台 0［0〜1］／—／0　2台 0.5［0〜8］／—／4.5
  卓を空けた              0台 0［0〜0］／—／0　1台 0［0〜0］／—／0　2台 0［0〜1］／—／0
  冷めた皿の割合            0台 0.000［0.000〜0.028］／0.000／0.000　1台 0.167［0.042〜0.296］／0.041／0.286　2台 0.091［0.041〜0.152］／0.003／0.163
  （参考）あきらめた          0台 7［0〜16］／—／12.5　1台 28［13〜48］／—／45.5　2台 31［21〜46］／—／51.5
  回の数 0台 10・1台 10・2台 10（普通の夜 10・10・10）

■ ③ 混んでいる（54 組/時）  ※各欄：Unity の種10個の中央値［最小〜最大］／試算／普通の夜の中央値
  入店待ち               0台 19.02［4.71〜31.87］／13.03／4.09　1台 16.40［3.01〜34.86］／11.95／5.00　2台 15.81［1.88〜32.67］／11.10／3.99
  うち席は空いているのに待った     0台 8.94［3.50〜11.03］／1.55／2.88　1台 4.44［1.79〜11.20］／1.27／3.30　2台 5.73［0.58〜9.73］／1.17／1.68
  （片付け待ちだけ blocked）  0台 8.48［3.10〜10.64］／1.37／2.48　1台 4.13［1.43〜10.95］／1.21／2.90　2台 5.54［0.14〜9.30］／1.14／1.28
  料理の待ち              0台 0.98［0.92〜1.08］／0.42／0.98　1台 2.59［2.35〜3.06］／2.01／2.48　2台 1.82［1.64〜2.57］／1.01／1.95
  会計待ち               0台 5.72［3.81〜7.22］／15.44／4.45　1台 7.08［4.98〜10.72］／12.05／6.13　2台 7.65［5.94〜13.04］／11.45／7.08
  入れた組の数             0台 108［86〜128］／106.2／82　1台 108［86〜128］／106.5／82　2台 108［86〜128］／107.2／82
  ロボットが運んだ皿の割合       0台 0.000［0.000〜0.000］／0.000／0.000　1台 0.684［0.610〜0.717］／0.697／0.706　2台 0.862［0.760〜0.912］／0.986／0.832
  往復の数               0台 0［0〜0］／—／0　1台 78.5［66〜93］／—／65.5　2台 151.5［125〜180］／—／119.5
  30秒以上の止まり          0台 0［0〜0］／—／0　1台 9［4〜30］／—／6　2台 23.5［12〜46］／—／24.5
  （1往復あたり）           0台 0.000［0.000〜0.000］／—／0.000　1台 0.130［0.053〜0.326］／—／0.083　2台 0.142［0.083〜0.348］／—／0.173
  動けなくなった（60秒）       0台 0［0〜0］／—／0　1台 6［2〜21］／—／3.5　2台 14.5［5〜29］／—／18
  瞬間移動               0台 15.5［8〜22］／—／10.5　1台 65.5［39〜77］／—／49　2台 76［40〜103］／—／60
  打ち切り               0台 0［0〜0］／—／0　1台 0［0〜2］／—／0　2台 2.5［0〜8］／—／4.5
  卓を空けた              0台 0［0〜0］／—／0　1台 0［0〜1］／—／0　2台 0［0〜2］／—／0
  冷めた皿の割合            0台 0.000［0.000〜0.010］／0.000／0.000　1台 0.350［0.237〜0.394］／0.083／0.286　2台 0.157［0.120〜0.261］／0.012／0.163
  （参考）あきらめた          0台 14.5［9〜33］／—／12.5　1台 57.5［33〜107］／—／45.5　2台 74.5［33〜133］／—／51.5
  回の数 0台 10・1台 10・2台 10（普通の夜 10・10・10）

■ ⑦ 空いている（30 組/時）  ※各欄：Unity の種10個の中央値［最小〜最大］／試算／普通の夜の中央値
  入店待ち               0台 1.33［1.22〜1.70］／0.28／4.49　1台 1.40［1.27〜1.89］／0.23／3.22　2台 1.43［1.28〜1.97］／0.22／3.56
  うち席は空いているのに待った     0台 0.15［0.04〜0.49］／0.10／3.27　1台 0.18［0.06〜0.67］／0.05／1.47　2台 0.21［0.06〜0.71］／0.04／1.66
  （片付け待ちだけ blocked）  0台 0［0〜0.01］／0／2.85　1台 0［0〜0.10］／0／1.09　2台 0［0〜0.20］／0／1.23
  料理の待ち              0台 0.69［0.48〜1.03］／0.30／0.96　1台 1.90［1.60〜2.69］／1.65／2.51　2台 1.57［1.13〜3.49］／0.73／1.61
  会計待ち               0台 2.73［1.34〜3.89］／2.55／4.40　1台 2.83［1.23〜6.07］／2.23／5.12　2台 3.23［1.29〜5.70］／2.07／7.18
  入れた組の数             0台 58［48〜72］／59.7／82　1台 58［48〜72］／61.5／82　2台 58［48〜72］／60.2／82
  ロボットが運んだ皿の割合       0台 0.000［0.000〜0.000］／0.000／0.000　1台 0.762［0.708〜0.856］／0.820／0.669　2台 0.888［0.651〜0.973］／0.998／0.916
  往復の数               0台 0［0〜0］／—／0　1台 65.5［62〜71］／—／67.5　2台 98［85〜109］／—／124
  30秒以上の止まり          0台 0［0〜0］／—／0　1台 6［2〜15］／—／8　2台 16.5［7〜32］／—／26.5
  （1往復あたり）           0台 0.000［0.000〜0.000］／—／0.000　1台 0.087［0.032〜0.242］／—／0.127　2台 0.165［0.064〜0.344］／—／0.197
  動けなくなった（60秒）       0台 0［0〜0］／—／0　1台 4［0〜11］／—／5.5　2台 10［3〜25］／—／16.5
  瞬間移動               0台 15.5［13〜21］／—／13　1台 41［28〜52］／—／48.5　2台 43.5［32〜76］／—／62.5
  打ち切り               0台 0［0〜0］／—／0　1台 0［0〜0］／—／0　2台 2［0〜16］／—／3.5
  卓を空けた              0台 0［0〜0］／—／0　1台 0［0〜1］／—／0　2台 0［0〜1］／—／0
  冷めた皿の割合            0台 0.000［0.000〜0.000］／0.000／0.000　1台 0.137［0.068〜0.298］／0.052／0.328　2台 0.115［0.032〜0.205］／0.003／0.117
  （参考）あきらめた          0台 8［0〜16］／—／13　1台 31.5［12〜44］／—／40.5　2台 30［15〜56］／—／54.5
  回の数 0台 10・1台 10・2台 10（普通の夜 10・10・10）

■ ⑦ 混んでいる（54 組/時）  ※各欄：Unity の種10個の中央値［最小〜最大］／試算／普通の夜の中央値
  入店待ち               0台 19.35［4.96〜32.81］／12.33／4.49　1台 17.68［3.06〜29.23］／13.74／3.22　2台 16.57［6.81〜30.39］／10.99／3.56
  うち席は空いているのに待った     0台 9.30［3.74〜11.46］／1.60／3.27　1台 4.99［1.86〜7.87］／1.32／1.47　2台 5.22［2.77〜7.43］／1.14／1.66
  （片付け待ちだけ blocked）  0台 8.86［3.35〜10.92］／1.41／2.85　1台 4.74［1.42〜7.63］／1.26／1.09　2台 5.02［2.63〜7.25］／1.10／1.23
  料理の待ち              0台 1.01［0.96〜1.14］／0.44／0.96　1台 2.56［2.30〜2.76］／2.02／2.51　2台 1.75［1.44〜2.27］／1.03／1.61
  会計待ち               0台 5.68［3.40〜7.40］／15.33／4.40　1台 7.19［4.41〜9.11］／12.25／5.12　2台 7.41［3.82〜12.09］／11.84／7.18
  入れた組の数             0台 108［86〜128］／105.6／82　1台 108［86〜128］／109.3／82　2台 108［86〜128］／105.8／82
  ロボットが運んだ皿の割合       0台 0.000［0.000〜0.000］／0.000／0.000　1台 0.682［0.642〜0.708］／0.700／0.669　2台 0.902［0.838〜0.942］／0.985／0.916
  往復の数               0台 0［0〜0］／—／0　1台 81.5［69〜97］／—／67.5　2台 158［139〜184］／—／124
  30秒以上の止まり          0台 0［0〜0］／—／0　1台 8.5［2〜18］／—／8　2台 27.5［16〜36］／—／26.5
  （1往復あたり）           0台 0.000［0.000〜0.000］／—／0.000　1台 0.112［0.027〜0.217］／—／0.127　2台 0.171［0.105〜0.259］／—／0.197
  動けなくなった（60秒）       0台 0［0〜0］／—／0　1台 3.5［0〜13］／—／5.5　2台 15.5［7〜25］／—／16.5
  瞬間移動               0台 20.5［11〜31］／—／13　1台 55［41〜77］／—／48.5　2台 76.5［60〜93］／—／62.5
  打ち切り               0台 0［0〜0］／—／0　1台 0［0〜2］／—／0　2台 4［0〜6］／—／3.5
  卓を空けた              0台 0［0〜0］／—／0　1台 0［0〜1］／—／0　2台 1［0〜4］／—／0
  冷めた皿の割合            0台 0.000［0.000〜0.013］／0.000／0.000　1台 0.297［0.228〜0.353］／0.087／0.328　2台 0.138［0.087〜0.200］／0.012／0.117
  （参考）あきらめた          0台 18［10〜31］／—／13　1台 50.5［29〜94］／—／40.5　2台 73.5［45〜98］／—／54.5
  回の数 0台 10・1台 10・2台 10（普通の夜 10・10・10）

■ ④ 空いている（20 組/時）  ※各欄：Unity の種10個の中央値［最小〜最大］／試算／普通の夜の中央値
  入店待ち               0台 1.74［1.43〜7.49］／0.91／12.95　1台 2.50［1.63〜5.84］／0.44／13.78　2台 4.05［1.84〜16.48］／0.58／26.85
  うち席は空いているのに待った     0台 0.58［0.25〜6.36］／0.74／11.49　1台 1.22［0.33〜4.55］／0.30／12.12　2台 2.77［0.58〜15.17］／0.44／23.93
  （片付け待ちだけ blocked）  0台 0［0〜5.78］／0.32／10.92　1台 0.19［0〜3.70］／0.00／11.36　2台 1.25［0〜13.08］／0.10／21.91
  料理の待ち              0台 1.13［0.81〜1.56］／0.68／1.26　1台 2.11［1.27〜2.81］／1.02／2.10　2台 3.15［1.06〜4.11］／0.42／3.47
  会計待ち               0台 1.57［1.03〜2.37］／2.23／1.86　1台 1.91［1.24〜5.09］／1.44／2.34　2台 2.87［1.41〜5.54］／1.93／3.83
  入れた組の数             0台 38.5［30〜50］／38.5／53.5　1台 38.5［30〜50］／39.0／53.5　2台 38.5［30〜50］／39.8／53.5
  ロボットが運んだ皿の割合       0台 0.000［0.000〜0.000］／0.000／0.000　1台 0.864［0.733〜0.974］／0.971／0.851　2台 0.928［0.789〜0.989］／1.000／0.868
  往復の数               0台 0［0〜0］／—／0　1台 54［48〜73］／—／79　2台 67.5［55〜87］／—／89.5
  30秒以上の止まり          0台 0［0〜0］／—／0　1台 16［8〜24］／—／23　2台 47.5［11〜80］／—／104
  （1往復あたり）           0台 0.000［0.000〜0.000］／—／0.000　1台 0.287［0.123〜0.462］／—／0.314　2台 0.687［0.180〜1.250］／—／1.031
  動けなくなった（60秒）       0台 0［0〜0］／—／0　1台 11［4〜19］／—／13　2台 39.5［8〜63］／—／78.5
  瞬間移動               0台 0［0〜0］／—／0　1台 12.5［4〜19］／—／20　2台 25［7〜57］／—／53.5
  打ち切り               0台 0［0〜0］／—／0　1台 0［0〜1］／—／0　2台 8［3〜15］／—／12.5
  卓を空けた              0台 0［0〜0］／—／0　1台 0［0〜0］／—／0　2台 0［0〜1］／—／1
  冷めた皿の割合            0台 0.008［0.000〜0.157］／0.000／0.026　1台 0.232［0.046〜0.489］／0.021／0.235　2台 0.346［0.058〜0.495］／0.000／0.406
  （参考）あきらめた          0台 0［0〜2］／—／0　1台 24.5［7〜38］／—／32.5　2台 50［14〜74］／—／95.5
  回の数 0台 10・1台 10・2台 10（普通の夜 10・10・10）

■ ④ 混んでいる（36 組/時）  ※各欄：Unity の種10個の中央値［最小〜最大］／試算／普通の夜の中央値
  入店待ち               0台 32.87［14.48〜54.09］／23.93／12.95　1台 31.57［17.39〜59.65］／16.09／13.78　2台 45.92［18.64〜71.88］／14.69／26.85
  うち席は空いているのに待った     0台 21.49［13.26〜24.86］／14.25／11.49　1台 20.76［14.06〜25.63］／5.34／12.12　2台 25.64［17.24〜36.02］／5.21／23.93
  （片付け待ちだけ blocked）  0台 20.78［12.68〜24.40］／13.59／10.92　1台 20.10［13.31〜25.16］／4.78／11.36　2台 23.61［16.31〜34.97］／4.65／21.91
  料理の待ち              0台 1.28［1.14〜1.41］／0.92／1.26　1台 2.26［1.80〜2.71］／1.31／2.10　2台 2.87［1.80〜5.18］／0.49／3.47
  会計待ち               0台 2.10［1.58〜2.99］／5.34／1.86　1台 2.95［1.85〜3.62］／7.04／2.34　2台 4.48［2.03〜7.83］／7.34／3.83
  入れた組の数             0台 72［57〜85］／70.0／53.5　1台 72［57〜85］／72.1／53.5　2台 72［57〜85］／70.8／53.5
  ロボットが運んだ皿の割合       0台 0.000［0.000〜0.000］／0.000／0.000　1台 0.849［0.771〜0.880］／0.949／0.851　2台 0.888［0.785〜0.930］／1.000／0.868
  往復の数               0台 0［0〜0］／—／0　1台 104［82〜117］／—／79　2台 128.5［99〜157］／—／89.5
  30秒以上の止まり          0台 0［0〜0］／—／0　1台 31.5［22〜38］／—／23　2台 82［48〜161］／—／104
  （1往復あたり）           0台 0.000［0.000〜0.000］／—／0.000　1台 0.299［0.209〜0.358］／—／0.314　2台 0.665［0.371〜1.184］／—／1.031
  動けなくなった（60秒）       0台 0［0〜0］／—／0　1台 21［15〜32］／—／13　2台 63.5［33〜136］／—／78.5
  瞬間移動               0台 0［0〜2］／—／0　1台 26［15〜40］／—／20　2台 44［32〜71］／—／53.5
  打ち切り               0台 0［0〜0］／—／0　1台 1［0〜1］／—／0　2台 12［3〜25］／—／12.5
  卓を空けた              0台 0［0〜0］／—／0　1台 0［0〜1］／—／0　2台 0［0〜2］／—／1
  冷めた皿の割合            0台 0.009［0.000〜0.075］／0.001／0.026　1台 0.228［0.141〜0.358］／0.046／0.235　2台 0.272［0.185〜0.402］／0.000／0.406
  （参考）あきらめた          0台 1［0〜5］／—／0　1台 39.5［31〜58］／—／32.5　2台 81.5［41〜144］／—／95.5
  回の数 0台 10・1台 10・2台 10（普通の夜 10・10・10）

■ ⑤ 空いている（33 組/時）  ※各欄：Unity の種10個の中央値［最小〜最大］／試算／普通の夜の中央値
  入店待ち               0台 1.40［1.26〜1.72］／0.45／8.14　1台 1.60［1.42〜1.89］／0.38／6.60　2台 1.54［1.44〜2.55］／0.31／5.46
  うち席は空いているのに待った     0台 0.25［0.10〜0.57］／0.25／6.50　1台 0.40［0.18〜0.65］／0.12／4.56　2台 0.31［0.21〜1.33］／0.09／3.41
  （片付け待ちだけ blocked）  0台 0［0〜0.11］／0.05／5.93　1台 0［0〜0］／0.03／4.20　2台 0［0〜0.60］／0.02／2.99
  料理の待ち              0台 0.76［0.56〜1.08］／0.44／1.12　1台 2.37［1.93〜2.82］／1.86／2.59　2台 1.77［1.25〜2.43］／0.94／2.19
  会計待ち               0台 3.52［1.51〜6.01］／5.60／5.72　1台 3.98［1.56〜5.71］／3.89／7.17　2台 3.75［1.54〜9.30］／3.27／6.98
  入れた組の数             0台 63.5［52〜80］／67.5／91.5　1台 63.5［52〜80］／67.6／91.5　2台 63.5［52〜80］／66.9／91.5
  ロボットが運んだ皿の割合       0台 0.000［0.000〜0.000］／0.000／0.000　1台 0.756［0.673〜0.827］／0.768／0.669　2台 0.898［0.770〜0.955］／0.995／0.819
  往復の数               0台 0［0〜0］／—／0　1台 62［56〜69］／—／68　2台 105.5［86〜117］／—／132.5
  30秒以上の止まり          0台 0［0〜0］／—／0　1台 5［1〜11］／—／5　2台 12［4〜17］／—／17
  （1往復あたり）           0台 0.000［0.000〜0.000］／—／0.000　1台 0.086［0.016〜0.186］／—／0.071　2台 0.112［0.035〜0.175］／—／0.131
  動けなくなった（60秒）       0台 0［0〜0］／—／0　1台 3［0〜7］／—／1　2台 7.5［2〜14］／—／11.5
  瞬間移動               0台 8［4〜14］／—／10　1台 40.5［28〜55］／—／48　2台 48.5［32〜57］／—／62
  打ち切り               0台 0［0〜0］／—／0　1台 0［0〜1］／—／0　2台 3［0〜7］／—／4
  卓を空けた              0台 0［0〜0］／—／0　1台 0.5［0〜2］／—／0.5　2台 1［0〜4］／—／3
  冷めた皿の割合            0台 0.000［0.000〜0.010］／0.000／0.000　1台 0.244［0.137〜0.383］／0.073／0.352　2台 0.129［0.051〜0.158］／0.008／0.250
  （参考）あきらめた          0台 0.5［0〜4］／—／2　1台 30［23〜44］／—／32.5　2台 47.5［28〜77］／—／79.5
  回の数 0台 10・1台 10・2台 10（普通の夜 10・10・10）

■ ⑤ 混んでいる（59 組/時）  ※各欄：Unity の種10個の中央値［最小〜最大］／試算／普通の夜の中央値
  入店待ち               0台 24.79［7.57〜36.54］／19.38／8.14　1台 23.70［8.36〜36.75］／17.63／6.60　2台 24.37［3.68〜36.34］／12.69／5.46
  うち席は空いているのに待った     0台 12.22［6.44〜15.01］／2.07／6.50　1台 8.86［6.61〜11.71］／1.42／4.56　2台 8.00［2.41〜13.57］／1.25／3.41
  （片付け待ちだけ blocked）  0台 11.72［6.03〜14.52］／1.80／5.93　1台 8.51［6.31〜11.37］／1.33／4.20　2台 7.67［2.15〜13.17］／1.21／2.99
  料理の待ち              0台 1.06［1.04〜1.21］／0.56／1.12　1台 2.68［2.44〜2.85］／2.16／2.59　2台 1.77［1.50〜2.65］／1.24／2.19
  会計待ち               0台 7.23［4.23〜9.11］／22.88／5.72　1台 8.21［4.89〜10.47］／17.83／7.17　2台 9.75［5.21〜14.26］／16.99／6.98
  入れた組の数             0台 119［94〜136］／116.7／91.5　1台 119［94〜136］／119.5／91.5　2台 119［94〜136］／114.5／91.5
  ロボットが運んだ皿の割合       0台 0.000［0.000〜0.000］／0.000／0.000　1台 0.666［0.625〜0.697］／0.677／0.669　2台 0.901［0.781〜0.947］／0.972／0.819
  往復の数               0台 0［0〜0］／—／0　1台 86.5［67〜96］／—／68　2台 168.5［136〜200］／—／132.5
  30秒以上の止まり          0台 0［0〜0］／—／0　1台 7［3〜14］／—／5　2台 11.5［7〜23］／—／17
  （1往復あたり）           0台 0.000［0.000〜0.000］／—／0.000　1台 0.089［0.036〜0.156］／—／0.071　2台 0.066［0.042〜0.131］／—／0.131
  動けなくなった（60秒）       0台 0［0〜0］／—／0　1台 3.5［0〜11］／—／1　2台 6［1〜17］／—／11.5
  瞬間移動               0台 14［8〜22］／—／10　1台 59.5［41〜92］／—／48　2台 81.5［58〜108］／—／62
  打ち切り               0台 0［0〜0］／—／0　1台 0［0〜1］／—／0　2台 2.5［0〜8］／—／4
  卓を空けた              0台 0［0〜0］／—／0　1台 0.5［0〜3］／—／0.5　2台 2.5［0〜6］／—／3
  冷めた皿の割合            0台 0.000［0.000〜0.010］／0.000／0.000　1台 0.350［0.275〜0.453］／0.115／0.352　2台 0.152［0.118〜0.302］／0.025／0.250
  （参考）あきらめた          0台 2［0〜9］／—／2　1台 44.5［19〜63］／—／32.5　2台 84.5［38〜122］／—／79.5
  回の数 0台 10・1台 10・2台 10（普通の夜 10・10・10）

■ ⑥ 空いている（27 組/時）  ※各欄：Unity の種10個の中央値［最小〜最大］／試算／普通の夜の中央値
  入店待ち               0台 1.54［1.37〜4.48］／0.82／9.60　1台 1.37［1.23〜1.95］／0.33／6.04　2台 1.35［1.25〜1.74］／0.32／4.83
  うち席は空いているのに待った     0台 0.42［0.27〜3.33］／0.62／8.42　1台 0.21［0.10〜0.79］／0.15／4.84　2台 0.22［0.10〜0.57］／0.14／3.49
  （片付け待ちだけ blocked）  0台 0［0〜2.73］／0.29／7.92　1台 0［0〜0］／0.00／4.41　2台 0［0〜0］／0／3.17
  料理の待ち              0台 0.97［0.66〜1.25］／0.56／1.12　1台 1.66［1.05〜2.25］／1.55／2.09　2台 0.93［0.70〜1.20］／0.65／1.27
  会計待ち               0台 1.98［1.27〜3.03］／4.16／3.03　1台 2.03［0.94〜3.13］／1.97／3.00　2台 2.05［1.06〜2.71］／1.89／3.31
  入れた組の数             0台 53［42〜68］／54.6／73.5　1台 53［42〜68］／53.7／73.5　2台 53［42〜68］／54.2／73.5
  ロボットが運んだ皿の割合       0台 0.000［0.000〜0.000］／0.000／0.000　1台 0.856［0.805〜0.974］／0.875／0.831　2台 0.985［0.930〜1.000］／1.000／0.960
  往復の数               0台 0［0〜0］／—／0　1台 71［61〜80］／—／79　2台 107.5［97〜135］／—／137.5
  30秒以上の止まり          0台 0［0〜0］／—／0　1台 1.5［0〜9］／—／2.5　2台 8.5［5〜14］／—／12.5
  （1往復あたり）           0台 0.000［0.000〜0.000］／—／0.000　1台 0.020［0.000〜0.134］／—／0.030　2台 0.077［0.037〜0.126］／—／0.092
  動けなくなった（60秒）       0台 0［0〜0］／—／0　1台 0.5［0〜7］／—／1　2台 4［0〜11］／—／8
  瞬間移動               0台 1［0〜2］／—／1　1台 26.5［13〜33］／—／25.5　2台 29［16〜36］／—／28.5
  打ち切り               0台 0［0〜0］／—／0　1台 0［0〜1］／—／0　2台 0［0〜2］／—／1.5
  卓を空けた              0台 0［0〜0］／—／0　1台 0［0〜0］／—／0　2台 0［0〜2］／—／0
  冷めた皿の割合            0台 0.000［0.000〜0.012］／0.000／0.008　1台 0.120［0.010〜0.231］／0.059／0.209　2台 0.024［0.000〜0.053］／0.001／0.069
  （参考）あきらめた          0台 3.5［1〜10］／—／7　1台 11.5［3〜28］／—／19　2台 9［4〜22］／—／28.5
  回の数 0台 10・1台 10・2台 10（普通の夜 10・10・10）

■ ⑥ 混んでいる（49 組/時）  ※各欄：Unity の種10個の中央値［最小〜最大］／試算／普通の夜の中央値
  入店待ち               0台 30.08［10.86〜45.15］／21.37／9.60　1台 21.62［4.94〜34.64］／13.62／6.04　2台 19.76［4.00〜34.81］／12.40／4.83
  うち席は空いているのに待った     0台 18.73［9.69〜20.61］／5.87／8.42　1台 11.28［3.71〜12.49］／1.72／4.84　2台 10.72［2.77〜12.83］／1.51／3.49
  （片付け待ちだけ blocked）  0台 18.14［9.16〜20.18］／5.34／7.92　1台 11.05［3.35〜12.27］／1.44／4.41　2台 10.43［2.43〜12.56］／1.29／3.17
  料理の待ち              0台 1.17［1.05〜1.25］／0.80／1.12　1台 2.07［1.92〜2.30］／2.06／2.09　2台 1.35［1.04〜2.37］／0.87／1.27
  会計待ち               0台 3.68［2.38〜5.26］／16.74／3.03　1台 4.23［2.76〜5.00］／12.41／3.00　2台 4.31［2.90〜5.57］／12.46／3.31
  入れた組の数             0台 99.5［76〜115］／96.8／73.5　1台 99.5［76〜115］／96.2／73.5　2台 99.5［76〜115］／96.8／73.5
  ロボットが運んだ皿の割合       0台 0.000［0.000〜0.000］／0.000／0.000　1台 0.821［0.810〜0.860］／0.785／0.831　2台 0.948［0.866〜0.990］／0.995／0.960
  往復の数               0台 0［0〜0］／—／0　1台 96.5［77〜118］／—／79　2台 169.5［155〜208］／—／137.5
  30秒以上の止まり          0台 0［0〜0］／—／0　1台 3.5［0〜10］／—／2.5　2台 15.5［2〜21］／—／12.5
  （1往復あたり）           0台 0.000［0.000〜0.000］／—／0.000　1台 0.037［0.000〜0.110］／—／0.030　2台 0.095［0.011〜0.128］／—／0.092
  動けなくなった（60秒）       0台 0［0〜0］／—／0　1台 1［0〜6］／—／1　2台 12［1〜15］／—／8
  瞬間移動               0台 1［0〜2］／—／1　1台 30［21〜41］／—／25.5　2台 35.5［24〜55］／—／28.5
  打ち切り               0台 0［0〜0］／—／0　1台 0［0〜1］／—／0　2台 2［0〜14］／—／1.5
  卓を空けた              0台 0［0〜0］／—／0　1台 0［0〜1］／—／0　2台 0［0〜0］／—／0
  冷めた皿の割合            0台 0.004［0.000〜0.031］／0.001／0.008　1台 0.187［0.153〜0.266］／0.145／0.209　2台 0.097［0.040〜0.149］／0.007／0.069
  （参考）あきらめた          0台 7［3〜14］／—／7　1台 23［12〜47］／—／19　2台 28.5［12〜34］／—／28.5
  回の数 0台 10・1台 10・2台 10（普通の夜 10・10・10）

■ ② 空いている（14 組/時）  ※各欄：Unity の種10個の中央値［最小〜最大］／試算／普通の夜の中央値
  入店待ち               0台 1.28［1.07〜2.71］／0.43／1.96　1台 1.25［1.06〜2.23］／0.34／1.69　2台 1.45［1.12〜2.02］／0.43／2.10
  うち席は空いているのに待った     0台 0.22［0.02〜1.42］／0.25／0.77　1台 0.15［0.00〜0.85］／0.17／0.43　2台 0.31［0.05〜0.70］／0.19／0.77
  （片付け待ちだけ blocked）  0台 0［0〜0.97］／0.04／0.39　1台 0［0〜0.62］／0.02／0.11　2台 0［0〜0.47］／0.04／0.10
  料理の待ち              0台 0.69［0.43〜1.02］／0.38／0.89　1台 1.07［0.65〜1.49］／0.73／1.04　2台 0.82［0.53〜1.75］／0.37／1.04
  会計待ち               0台 0.84［0.46〜1.32］／0.82／1.13　1台 0.61［0.40〜1.53］／0.63／1.06　2台 0.88［0.34〜2.27］／0.57／1.33
  入れた組の数             0台 28.5［18〜36］／27.9／36.5　1台 28.5［18〜36］／27.2／36.5　2台 28.5［18〜36］／27.5／36.5
  ロボットが運んだ皿の割合       0台 0.000［0.000〜0.000］／0.000／0.000　1台 0.960［0.900〜1.000］／0.988／0.956　2台 1.000［0.955〜1.000］／1.000／0.967
  往復の数               0台 0［0〜0］／—／0　1台 52.5［41〜63］／—／64　2台 62［47〜83］／—／77.5
  30秒以上の止まり          0台 0［0〜0］／—／0　1台 1.5［0〜4］／—／2　2台 10.5［4〜35］／—／18.5
  （1往復あたり）           0台 0.000［0.000〜0.000］／—／0.000　1台 0.025［0.000〜0.075］／—／0.028　2台 0.168［0.073〜0.538］／—／0.210
  動けなくなった（60秒）       0台 0［0〜0］／—／0　1台 1［0〜3］／—／0　2台 8.5［4〜31］／—／12.5
  瞬間移動               0台 1.5［0〜6］／—／1.5　1台 6.5［1〜14］／—／9　2台 8.5［3〜17］／—／13.5
  打ち切り               0台 0［0〜0］／—／0　1台 0［0〜0］／—／0　2台 1.5［0〜6］／—／1.5
  卓を空けた              0台 0［0〜0］／—／0　1台 0［0〜0］／—／0　2台 0［0〜1］／—／0
  冷めた皿の割合            0台 0.000［0.000〜0.000］／0.000／0.000　1台 0.022［0.000〜0.138］／0.006／0.041　2台 0.035［0.000〜0.145］／0.000／0.055
  （参考）あきらめた          0台 2［0〜4］／—／2　1台 3［0〜6］／—／3.5　2台 9［4〜25］／—／10.5
  回の数 0台 10・1台 10・2台 10（普通の夜 10・10・10）

■ ② 混んでいる（24 組/時）  ※各欄：Unity の種10個の中央値［最小〜最大］／試算／普通の夜の中央値
  入店待ち               0台 11.17［2.58〜26.99］／9.98／1.96　1台 11.37［1.91〜26.54］／7.39／1.69　2台 10.69［2.22〜23.36］／6.95／2.10
  うち席は空いているのに待った     0台 6.24［1.49〜8.01］／3.59／0.77　1台 5.34［0.79〜6.29］／1.68／0.43　2台 3.92［0.87〜9.72］／1.62／0.77
  （片付け待ちだけ blocked）  0台 5.81［0.90〜7.43］／3.06／0.39　1台 4.90［0.49〜6.00］／1.34／0.11　2台 3.57［0.49〜9.11］／1.27／0.10
  料理の待ち              0台 1.02［0.90〜1.11］／0.72／0.89　1台 1.98［0.89〜2.56］／1.01／1.04　2台 1.39［0.68〜3.06］／0.43／1.04
  会計待ち               0台 1.54［1.00〜1.81］／2.02／1.13　1台 1.53［0.90〜2.63］／1.56／1.06　2台 1.66［0.91〜2.45］／1.63／1.33
  入れた組の数             0台 47［38〜63］／47.5／36.5　1台 47［38〜63］／48.3／36.5　2台 47［38〜63］／48.2／36.5
  ロボットが運んだ皿の割合       0台 0.000［0.000〜0.000］／0.000／0.000　1台 0.812［0.691〜0.990］／0.977／0.956　2台 0.944［0.829〜1.000］／1.000／0.967
  往復の数               0台 0［0〜0］／—／0　1台 71.5［62〜92］／—／64　2台 102［77〜140］／—／77.5
  30秒以上の止まり          0台 0［0〜0］／—／0　1台 13.5［0〜25］／—／2　2台 31［9〜42］／—／18.5
  （1往復あたり）           0台 0.000［0.000〜0.000］／—／0.000　1台 0.201［0.000〜0.368］／—／0.028　2台 0.306［0.095〜0.408］／—／0.210
  動けなくなった（60秒）       0台 0［0〜0］／—／0　1台 13［0〜25］／—／0　2台 23.5［3〜37］／—／12.5
  瞬間移動               0台 2［1〜9］／—／1.5　1台 10.5［4〜16］／—／9　2台 16.5［10〜22］／—／13.5
  打ち切り               0台 0［0〜0］／—／0　1台 2［0〜5］／—／0　2台 4.5［0〜10］／—／1.5
  卓を空けた              0台 0［0〜0］／—／0　1台 0［0〜0］／—／0　2台 0［0〜0］／—／0
  冷めた皿の割合            0台 0.000［0.000〜0.024］／0.000／0.000　1台 0.144［0.000〜0.285］／0.009／0.041　2台 0.124［0.000〜0.183］／0.000／0.055
  （参考）あきらめた          0台 2.5［0〜9］／—／2　1台 10［3〜17］／—／3.5　2台 16［6〜25］／—／10.5
  回の数 0台 10・1台 10・2台 10（普通の夜 10・10・10）

■ ロボットを1台増やしたときの向き（同じ／逆／変わらない。5%・0.2 分より小さい変化は変わらない）
  ③ 空いている（30）
    0→1台 入店待ち：試算 変わらない（0.40→0.25）　試算は変わらない（Unity 長く 1・短く 1・変わらない 8）
    0→1台 料理の待ち：試算 長くなる（0.30→1.61）　10／0／0 → 同じ（過半数）
    1→2台 入店待ち：試算 変わらない（0.25→0.23）　試算は変わらない（Unity 長く 0・短く 1・変わらない 9）
    1→2台 料理の待ち：試算 短くなる（1.61→0.74）　9／1／0 → 同じ（過半数）
  ③ 混んでいる（54）
    0→1台 入店待ち：試算 短くなる（13.03→11.95）　8／1／1 → 同じ（過半数）
    0→1台 料理の待ち：試算 長くなる（0.42→2.01）　10／0／0 → 同じ（過半数）
    1→2台 入店待ち：試算 短くなる（11.95→11.10）　5／5／0 → 過半数なし
    1→2台 料理の待ち：試算 短くなる（2.01→1.01）　9／0／1 → 同じ（過半数）
  ⑦ 空いている（30）
    0→1台 入店待ち：試算 変わらない（0.28→0.23）　試算は変わらない（Unity 長く 2・短く 0・変わらない 8）
    0→1台 料理の待ち：試算 長くなる（0.30→1.65）　10／0／0 → 同じ（過半数）
    1→2台 入店待ち：試算 変わらない（0.23→0.22）　試算は変わらない（Unity 長く 1・短く 0・変わらない 9）
    1→2台 料理の待ち：試算 短くなる（1.65→0.73）　7／2／1 → 同じ（過半数）
  ⑦ 混んでいる（54）
    0→1台 入店待ち：試算 長くなる（12.33→13.74）　0／9／1 → 逆（過半数）
    0→1台 料理の待ち：試算 長くなる（0.44→2.02）　10／0／0 → 同じ（過半数）
    1→2台 入店待ち：試算 短くなる（13.74→10.99）　3／3／4 → 過半数なし
    1→2台 料理の待ち：試算 短くなる（2.02→1.03）　10／0／0 → 同じ（過半数）
  ④ 空いている（20）
    0→1台 入店待ち：試算 短くなる（0.91→0.44）　2／7／1 → 逆（過半数）
    0→1台 料理の待ち：試算 長くなる（0.68→1.02）　10／0／0 → 同じ（過半数）
    1→2台 入店待ち：試算 変わらない（0.44→0.58）　試算は変わらない（Unity 長く 8・短く 2・変わらない 0）
    1→2台 料理の待ち：試算 短くなる（1.02→0.42）　3／6／1 → 逆（過半数）
  ④ 混んでいる（36）
    0→1台 入店待ち：試算 短くなる（23.93→16.09）　3／4／3 → 過半数なし
    0→1台 料理の待ち：試算 長くなる（0.92→1.31）　10／0／0 → 同じ（過半数）
    1→2台 入店待ち：試算 短くなる（16.09→14.69）　1／9／0 → 逆（過半数）
    1→2台 料理の待ち：試算 短くなる（1.31→0.49）　1／7／2 → 逆（過半数）
  ⑤ 空いている（33）
    0→1台 入店待ち：試算 変わらない（0.45→0.38）　試算は変わらない（Unity 長く 4・短く 0・変わらない 6）
    0→1台 料理の待ち：試算 長くなる（0.44→1.86）　10／0／0 → 同じ（過半数）
    1→2台 入店待ち：試算 変わらない（0.38→0.31）　試算は変わらない（Unity 長く 1・短く 1・変わらない 8）
    1→2台 料理の待ち：試算 短くなる（1.86→0.94）　8／0／2 → 同じ（過半数）
  ⑤ 混んでいる（59）
    0→1台 入店待ち：試算 短くなる（19.38→17.63）　5／1／4 → 過半数なし
    0→1台 料理の待ち：試算 長くなる（0.56→2.16）　10／0／0 → 同じ（過半数）
    1→2台 入店待ち：試算 短くなる（17.63→12.69）　4／4／2 → 過半数なし
    1→2台 料理の待ち：試算 短くなる（2.16→1.24）　9／0／1 → 同じ（過半数）
  ⑥ 空いている（27）
    0→1台 入店待ち：試算 短くなる（0.82→0.33）　4／1／5 → 過半数なし
    0→1台 料理の待ち：試算 長くなる（0.56→1.55）　10／0／0 → 同じ（過半数）
    1→2台 入店待ち：試算 変わらない（0.33→0.32）　試算は変わらない（Unity 長く 0・短く 1・変わらない 9）
    1→2台 料理の待ち：試算 短くなる（1.55→0.65）　10／0／0 → 同じ（過半数）
  ⑥ 混んでいる（49）
    0→1台 入店待ち：試算 短くなる（21.37→13.62）　10／0／0 → 同じ（過半数）
    0→1台 料理の待ち：試算 長くなる（0.80→2.06）　10／0／0 → 同じ（過半数）
    1→2台 入店待ち：試算 短くなる（13.62→12.40）　5／0／5 → 過半数なし
    1→2台 料理の待ち：試算 短くなる（2.06→0.87）　9／0／1 → 同じ（過半数）
  ② 空いている（14）
    0→1台 入店待ち：試算 変わらない（0.43→0.34）　試算は変わらない（Unity 長く 1・短く 2・変わらない 7）
    0→1台 料理の待ち：試算 長くなる（0.38→0.73）　8／0／2 → 同じ（過半数）
    1→2台 入店待ち：試算 変わらない（0.34→0.43）　試算は変わらない（Unity 長く 2・短く 1・変わらない 7）
    1→2台 料理の待ち：試算 短くなる（0.73→0.37）　4／3／3 → 過半数なし
  ② 混んでいる（24）
    0→1台 入店待ち：試算 短くなる（9.98→7.39）　6／1／3 → 同じ（過半数）
    0→1台 料理の待ち：試算 長くなる（0.72→1.01）　8／0／2 → 同じ（過半数）
    1→2台 入店待ち：試算 短くなる（7.39→6.95）　2／2／6 → 過半数なし
    1→2台 料理の待ち：試算 短くなる（1.01→0.43）　6／3／1 → 同じ（過半数）

■ 2台の 30 秒以上の止まり（種10個の合計）
  ③ 空いている（30）：合計 174　待機場所の中 45・受け渡し口から2.5m 22・そのほか 107
      待機場所の中 の理由：もう1台のロボット 44・分からない 1
      受け渡し口から2.5m の理由：もう1台のロボット 14・人が立ち止まっていた 3・スタッフとすれ違った 3・お客さんとすれ違った 2
      そのほか の理由：お客さんとすれ違った 34・スタッフとすれ違った 28・人が立ち止まっていた 27・もう1台のロボット 18
      そのほかの多いマス（2m四方）：x 4〜6・y 4〜6 が 22／x 2〜4・y 4〜6 が 13／x 8〜10・y 4〜6 が 13
  ③ 混んでいる（54）：合計 242　待機場所の中 26・受け渡し口から2.5m 29・そのほか 187
      待機場所の中 の理由：もう1台のロボット 25・分からない 1
      受け渡し口から2.5m の理由：もう1台のロボット 22・人が立ち止まっていた 5・スタッフとすれ違った 2
      そのほか の理由：スタッフとすれ違った 56・もう1台のロボット 49・お客さんとすれ違った 47・人が立ち止まっていた 34・椅子・荷物・ベビーカー 1
      そのほかの多いマス（2m四方）：x 4〜6・y 4〜6 が 35／x 8〜10・y 4〜6 が 24／x 20〜22・y 4〜6 が 18
  ③ （参考）普通の夜：合計 230　待機場所の中 41・受け渡し口から2.5m 46・そのほか 143
      待機場所の中 の理由：もう1台のロボット 41
      受け渡し口から2.5m の理由：もう1台のロボット 28・スタッフとすれ違った 9・人が立ち止まっていた 6・お客さんとすれ違った 3
      そのほか の理由：もう1台のロボット 41・スタッフとすれ違った 33・お客さんとすれ違った 32・人が立ち止まっていた 29・椅子・荷物・ベビーカー 6・分からない 2
      そのほかの多いマス（2m四方）：x 4〜6・y 4〜6 が 26／x 20〜22・y 2〜4 が 17／x 8〜10・y 4〜6 が 15
    （待機場所の四角 x 16.50〜18.90・y 5.65〜6.45／受け渡し口の点 (15.30, 5.95)）
  ⑦ 空いている（30）：合計 173　待機場所の中 50・受け渡し口から2.5m 13・そのほか 110
      待機場所の中 の理由：もう1台のロボット 50
      受け渡し口から2.5m の理由：スタッフとすれ違った 8・もう1台のロボット 4・分からない 1
      そのほか の理由：人が立ち止まっていた 34・お客さんとすれ違った 29・スタッフとすれ違った 26・もう1台のロボット 19・椅子・荷物・ベビーカー 2
      そのほかの多いマス（2m四方）：x 4〜6・y 4〜6 が 31／x 4〜6・y 10〜12 が 9／x 4〜6・y 6〜8 が 8
  ⑦ 混んでいる（54）：合計 255　待機場所の中 58・受け渡し口から2.5m 15・そのほか 182
      待機場所の中 の理由：もう1台のロボット 55・分からない 2・お客さんとすれ違った 1
      受け渡し口から2.5m の理由：人が立ち止まっていた 6・スタッフとすれ違った 5・もう1台のロボット 4
      そのほか の理由：スタッフとすれ違った 52・人が立ち止まっていた 50・お客さんとすれ違った 50・もう1台のロボット 21・椅子・荷物・ベビーカー 7・分からない 2
      そのほかの多いマス（2m四方）：x 4〜6・y 4〜6 が 47／x 8〜10・y 4〜6 が 36／x 20〜22・y 2〜4 が 11
  ⑦ （参考）普通の夜：合計 247　待機場所の中 31・受け渡し口から2.5m 45・そのほか 171
      待機場所の中 の理由：もう1台のロボット 31
      受け渡し口から2.5m の理由：スタッフとすれ違った 22・もう1台のロボット 14・人が立ち止まっていた 8・お客さんとすれ違った 1
      そのほか の理由：スタッフとすれ違った 46・人が立ち止まっていた 41・お客さんとすれ違った 40・もう1台のロボット 35・椅子・荷物・ベビーカー 9
      そのほかの多いマス（2m四方）：x 8〜10・y 4〜6 が 41／x 4〜6・y 4〜6 が 19／x 2〜4・y 14〜16 が 18
    （待機場所の四角 x 16.50〜18.90・y 5.65〜6.45／受け渡し口の点 (15.30, 5.95)）
  ④ 空いている（20）：合計 496　待機場所の中 70・受け渡し口から2.5m 48・そのほか 378
      待機場所の中 の理由：もう1台のロボット 67・分からない 2・スタッフとすれ違った 1
      受け渡し口から2.5m の理由：人が立ち止まっていた 19・もう1台のロボット 16・スタッフとすれ違った 7・お客さんとすれ違った 6
      そのほか の理由：もう1台のロボット 140・人が立ち止まっていた 126・スタッフとすれ違った 77・お客さんとすれ違った 27・椅子・荷物・ベビーカー 6・分からない 2
      そのほかの多いマス（2m四方）：x 2〜4・y 2〜4 が 128／x 4〜6・y 2〜4 が 59／x 2〜4・y 4〜6 が 52
  ④ 混んでいる（36）：合計 861　待機場所の中 116・受け渡し口から2.5m 141・そのほか 604
      待機場所の中 の理由：もう1台のロボット 111・分からない 4・人が立ち止まっていた 1
      受け渡し口から2.5m の理由：人が立ち止まっていた 57・もう1台のロボット 49・スタッフとすれ違った 18・お客さんとすれ違った 17
      そのほか の理由：もう1台のロボット 247・人が立ち止まっていた 148・スタッフとすれ違った 92・お客さんとすれ違った 69・椅子・荷物・ベビーカー 34・分からない 12・通路の幅が足りない 2
      そのほかの多いマス（2m四方）：x 2〜4・y 2〜4 が 147／x 4〜6・y 2〜4 が 123／x 6〜8・y 2〜4 が 91
  ④ （参考）普通の夜：合計 1017　待機場所の中 0・受け渡し口から2.5m 407・そのほか 610
      受け渡し口から2.5m の理由：もう1台のロボット 277・人が立ち止まっていた 74・スタッフとすれ違った 37・お客さんとすれ違った 17・分からない 2
      そのほか の理由：人が立ち止まっていた 204・もう1台のロボット 202・スタッフとすれ違った 122・お客さんとすれ違った 58・椅子・荷物・ベビーカー 16・分からない 7・通路の幅が足りない 1
      そのほかの多いマス（2m四方）：x 2〜4・y 2〜4 が 119／x 6〜8・y 2〜4 が 117／x 4〜6・y 2〜4 が 85
    （待機場所の四角 x 12.00〜13.60・y 4.15〜4.95／受け渡し口の点 (11.05, 4.45)）
  ⑤ 空いている（33）：合計 113　待機場所の中 27・受け渡し口から2.5m 11・そのほか 75
      待機場所の中 の理由：もう1台のロボット 27
      受け渡し口から2.5m の理由：もう1台のロボット 8・人が立ち止まっていた 1・お客さんとすれ違った 1・スタッフとすれ違った 1
      そのほか の理由：スタッフとすれ違った 23・人が立ち止まっていた 19・もう1台のロボット 19・お客さんとすれ違った 13・分からない 1
      そのほかの多いマス（2m四方）：x 2〜4・y 12〜14 が 24／x 2〜4・y 2〜4 が 10／x 20〜22・y 2〜4 が 6
  ⑤ 混んでいる（59）：合計 129　待機場所の中 9・受け渡し口から2.5m 11・そのほか 109
      待機場所の中 の理由：もう1台のロボット 4・人が立ち止まっていた 4・お客さんとすれ違った 1
      受け渡し口から2.5m の理由：もう1台のロボット 9・スタッフとすれ違った 1・人が立ち止まっていた 1
      そのほか の理由：スタッフとすれ違った 34・お客さんとすれ違った 28・人が立ち止まっていた 27・もう1台のロボット 18・分からない 2
      そのほかの多いマス（2m四方）：x 20〜22・y 2〜4 が 18／x 4〜6・y 12〜14 が 16／x 6〜8・y 6〜8 が 11
  ⑤ （参考）普通の夜：合計 184　待機場所の中 51・受け渡し口から2.5m 5・そのほか 128
      待機場所の中 の理由：もう1台のロボット 36・人が立ち止まっていた 9・スタッフとすれ違った 3・分からない 2・お客さんとすれ違った 1
      受け渡し口から2.5m の理由：もう1台のロボット 2・スタッフとすれ違った 2・人が立ち止まっていた 1
      そのほか の理由：もう1台のロボット 56・スタッフとすれ違った 34・人が立ち止まっていた 23・お客さんとすれ違った 13・分からない 2
      そのほかの多いマス（2m四方）：x 4〜6・y 2〜4 が 21／x 8〜10・y 2〜4 が 17／x 20〜22・y 2〜4 が 16
    （待機場所の四角 x 14.20〜16.60・y 8.55〜9.35／受け渡し口の点 (12.50, 8.85)）
  ⑥ 空いている（27）：合計 85　待機場所の中 69・受け渡し口から2.5m 6・そのほか 10
      待機場所の中 の理由：もう1台のロボット 69
      受け渡し口から2.5m の理由：もう1台のロボット 3・スタッフとすれ違った 3
      そのほか の理由：お客さんとすれ違った 6・人が立ち止まっていた 2・スタッフとすれ違った 2
      そのほかの多いマス（2m四方）：x 6〜8・y 4〜6 が 7／x 2〜4・y 12〜14 が 1／x 4〜6・y 4〜6 が 1
  ⑥ 混んでいる（49）：合計 147　待機場所の中 76・受け渡し口から2.5m 23・そのほか 48
      待機場所の中 の理由：もう1台のロボット 73・分からない 2・人が立ち止まっていた 1
      受け渡し口から2.5m の理由：もう1台のロボット 9・スタッフとすれ違った 6・人が立ち止まっていた 4・お客さんとすれ違った 4
      そのほか の理由：もう1台のロボット 20・お客さんとすれ違った 16・スタッフとすれ違った 6・人が立ち止まっていた 3・椅子・荷物・ベビーカー 2・分からない 1
      そのほかの多いマス（2m四方）：x 6〜8・y 4〜6 が 14／x 20〜22・y 2〜4 が 10／x 16〜18・y 2〜4 が 8
  ⑥ （参考）普通の夜：合計 122　待機場所の中 60・受け渡し口から2.5m 9・そのほか 53
      待機場所の中 の理由：もう1台のロボット 59・お客さんとすれ違った 1
      受け渡し口から2.5m の理由：スタッフとすれ違った 7・もう1台のロボット 1・お客さんとすれ違った 1
      そのほか の理由：もう1台のロボット 17・お客さんとすれ違った 13・人が立ち止まっていた 12・スタッフとすれ違った 10・分からない 1
      そのほかの多いマス（2m四方）：x 6〜8・y 4〜6 が 27／x 20〜22・y 2〜4 が 8／x 16〜18・y 4〜6 が 5
    （待機場所の四角 x 15.50〜17.90・y 5.65〜6.45／受け渡し口の点 (14.30, 5.95)）
  ② 空いている（14）：合計 147　待機場所の中 52・受け渡し口から2.5m 53・そのほか 42
      待機場所の中 の理由：もう1台のロボット 52
      受け渡し口から2.5m の理由：もう1台のロボット 25・スタッフとすれ違った 17・人が立ち止まっていた 7・お客さんとすれ違った 4
      そのほか の理由：もう1台のロボット 35・スタッフとすれ違った 3・人が立ち止まっていた 3・お客さんとすれ違った 1
      そのほかの多いマス（2m四方）：x 8〜10・y 6〜8 が 21／x 8〜10・y 8〜10 が 20／x 18〜20・y 4〜6 が 1
  ② 混んでいる（24）：合計 292　待機場所の中 60・受け渡し口から2.5m 39・そのほか 193
      待機場所の中 の理由：もう1台のロボット 60
      受け渡し口から2.5m の理由：もう1台のロボット 26・スタッフとすれ違った 6・お客さんとすれ違った 5・人が立ち止まっていた 2
      そのほか の理由：人が立ち止まっていた 120・もう1台のロボット 56・椅子・荷物・ベビーカー 9・お客さんとすれ違った 7・スタッフとすれ違った 1
      そのほかの多いマス（2m四方）：x 12〜14・y 0〜2 が 108／x 12〜14・y 2〜4 が 35／x 8〜10・y 6〜8 が 27
  ② （参考）普通の夜：合計 192　待機場所の中 54・受け渡し口から2.5m 32・そのほか 106
      待機場所の中 の理由：もう1台のロボット 54
      受け渡し口から2.5m の理由：もう1台のロボット 19・スタッフとすれ違った 11・人が立ち止まっていた 2
      そのほか の理由：もう1台のロボット 54・人が立ち止まっていた 39・スタッフとすれ違った 8・お客さんとすれ違った 3・分からない 2
      そのほかの多いマス（2m四方）：x 12〜14・y 0〜2 が 37／x 8〜10・y 6〜8 が 28／x 12〜14・y 2〜4 が 21
    （待機場所の四角 x 12.20〜14.60・y 5.10〜5.90／受け渡し口の点 (10.90, 5.45)）

■ 全員が帰らなかった回 0：なし
■ completed でない回 0：[]


====================================================================
8. judge.md
====================================================================

  できた：night/20261106_busy/judge.md（judge.csv も）。振り分けは 360 回とも「基準なし」（基準 v5 は③⑦の普通だけのため）。
  judge は止まらなかった。中身の判断はしていない。
  ※ judge.md の「基準なし」の一覧は来客のペースを書かないので、同じ店・同じ台数が2行ずつ並ぶ（空いている・混んでいる）。


====================================================================
9. 決めてほしいこと
====================================================================

  1. 混んでいるときの入店待ちと「席は空いているのに待った」が、どの店でも試算よりかなり長い
     （例 ③0台 19.02／試算 13.03、うち席は空いているのに待った 8.94／1.55）。調べるかどうか。
  2. 向きが種の過半数で試算と逆になった5か所（⑦混んでいる 0→1台 入店待ち・④の4か所）の扱い。
  3. ④は混んでいると2台の 30 秒以上の止まりが 861（空いているときは 496）。多いのは x 2〜8・y 2〜4 のマスと、受け渡し口のまわり。
     ②は混んでいると x 12〜14・y 0〜2（入口・レジのまわり）が 108 に増える。どこを先に見るか。
```

### 混んでいるときは、席が空いているのに待つ時間が試算の3〜6倍だった

ロボット0台の入店待ち（店に来てから卓に着くまで）と、そのうち「席は空いているのに待った」時間（分。Unity は種10個の中央値、試算は40回の平均）。

| 店（混んでいる・0台） | 入店待ち Unity／試算 | 席は空いているのに待った Unity／試算 |
|---|---|---|
| ③ 効率重視・入口が角 | 19.02／13.03 | 8.94／1.55 |
| ⑤ ゆったり・入口が正面 | 24.79／19.38 | 12.22／2.07 |
| ⑥ ゆったり・入口が横 | 30.08／21.37 | 18.73／5.87 |
| ④ 効率重視・小さめの店 | 32.87／23.93 | 21.49／14.25 |

空いているときは、どの店も入店待ちは1〜2分で、試算とのずれは小さいままでした。満席で待った時間は、混んでいても試算とほぼ同じでした。ずれは「席は空いているのに待った」に集まっていました。


## 作業ログ ― スタッフの手がふさがった時間を、仕事ごとに分けて試算と比べた

### 仕事の量の差か、仕事の取り方の差か

席は空いているのに待つのは、片付けか案内のスタッフの手が空かないからです。Unity のスタッフが1組あたりに使う時間が長いのか（仕事の量）、それとも仕事を取る順番が違うのか（取り方）を分けたいと考えました。

Unity は60秒ごとに、仕事ごとのスタッフの人数を `timeline.csv` に書いています。試算のほうは、試算のコードを変えずに、スタッフの手がふさがった時間を足す所で、どの仕事かを見分けて別に足すようにしました。新しく Unity は回していません。

### 仕事ごとの時間を比べた staff_breakdown.py

```python
"""混んでいるときの「席は空いているのに待った」のずれを、スタッフの時間の内訳で比べる（2026-10-30）

Unity：夜 20261106_busy の各回の timeline.csv（60秒ごとに、仕事ごとのスタッフの人数）と groups.csv、台帳の待ちの列
試算：haizen_sim.py（v2.8）を同じ条件で 40 回（試算と同じ種 0〜39）回し、スタッフの手がふさがった時間を仕事ごとに分けて数える
      （試算のコードは変えない。手がふさがった時間を足す所で、どの仕事かを見分けて別に足すだけ）

使い方: python3 staff_breakdown.py <haizen_sim.py のフォルダ> <service_sim/unity のフォルダ> <出力フォルダ>
"""
import csv, glob, json, os, statistics, sys, collections

SIM_DIR, UNITY_DIR, OUT = sys.argv[1], sys.argv[2], sys.argv[3]
sys.path.insert(0, SIM_DIR)
import haizen_sim as h

JOBS = ["guide", "serve", "call", "pay", "clear", "rescue"]
JA = {"guide": "案内", "serve": "配膳", "call": "呼び出し", "pay": "会計", "clear": "片付け", "rescue": "ロボット救出"}

# ---- 試算：仕事ごとの手がふさがった時間
class S(h.Store):
    def __init__(self, *a, **k):
        object.__setattr__(self, "by_job", collections.Counter())
        super().__init__(*a, **k)

    def __setattr__(self, name, val):
        if name == "staff_busy_s" and hasattr(self, "staff_busy_s"):
            import inspect
            fn = inspect.currentframe().f_back.f_code.co_name
            add = val - self.staff_busy_s
            if fn == "group":
                job = "guide"
            elif fn == "_staff_carry":
                job = "serve"
            elif fn == "bus":
                job = "clear"
            elif fn == "staff_job":
                f = inspect.currentframe().f_back
                work = f.f_locals.get("work_s")
                job = "pay" if work == self.p["pay_s"] and f.f_locals.get("walk_m") == 0 else (
                      "call" if work == self.p["call_s"] else "clear")
            elif fn == "maybe_stop":
                job = "rescue"
            else:
                job = "other:" + fn
            self.by_job[job] += add
        object.__setattr__(self, name, val)

def site_run(cond, seed):
    store = h.STORES[cond["store"]]
    p = {**h.COMMON, **store, **cond}
    env = h.simpy.Environment()
    s = S(env, h.random.Random(seed), p)
    env.process(s.arrivals())
    env.run()
    n = len(s.entry_waits)
    return {"groups": n, "end_min": env.now / 60,
            "per_group_min": {j: s.by_job.get(j, 0) / 60 / n for j in JOBS},
            "util": s.staff_busy_s / (p["staff"] * env.now),
            "blocked": statistics.mean(s.blocked_waits), "guide_wait": statistics.mean(s.guide_waits),
            "entry": statistics.mean(s.entry_waits), "other": {k: v for k, v in s.by_job.items() if k.startswith("other")}}

# ---- Unity：夜の各回
def unity_run(folder, staff_n):
    tl = list(csv.DictReader(open(os.path.join(folder, "timeline.csv"), encoding="utf-8")))
    gr = [g for g in csv.DictReader(open(os.path.join(folder, "groups.csv"), encoding="utf-8")) if g["state"] == "Done"]
    dt = 60.0
    n = len(gr)
    sec = {j: sum(float(r.get("staff_" + j, 0) or 0) for r in tl) * dt for j in JOBS}
    idle = sum(float(r["staff_idle"]) for r in tl) * dt
    busy = sum(sec.values())
    dirty_untaken = statistics.mean(float(r["tables_dirty_untaken"]) for r in tl)
    return {"groups": n, "per_group_min": {j: sec[j] / 60 / n for j in JOBS},
            "util": busy / (busy + idle) if busy + idle else 0, "dirty_untaken_mean": dirty_untaken,
            "end_min": float(tl[-1]["t_s"]) / 60}

ledger = {r["run_folder"]: r for r in csv.DictReader(open(os.path.join(UNITY_DIR, "unity_ledger_v1.csv"), encoding="utf-8"))}

def med(xs):
    return statistics.median(xs) if xs else float("nan")

params = json.load(open(os.path.join(SIM_DIR, "serving-robot-params.json"), encoding="utf-8"))
rows = []
for c in params["unity_conditions"][18:]:
    cond = c["condition"]
    if cond["arrivals_per_h"] != h.STORES[cond["store"]]["arrivals"][2]:
        continue   # 混んでいるだけ
    pat = os.path.join(UNITY_DIR, f"20261106_night_a{cond['arrivals_per_h']}_cs44_{cond['store']}_r{cond['robots']}_s*_passon_sbret")
    us = []
    for f in sorted(glob.glob(pat)):
        u = unity_run(f, cond["staff"])
        L = ledger.get(os.path.basename(f), {})
        u["blocked"] = float(L.get("blocked_wait_mean") or "nan")
        u["staff_wait"] = float(L.get("staff_wait_mean") or "nan")
        u["entry"] = float(L.get("entry_wait_mean") or "nan")
        us.append(u)
    ss = [site_run(cond, seed) for seed in range(h.REPS)]
    row = {"store": cond["store"], "label": h.STORES[cond["store"]]["label"], "staff": cond["staff"],
           "robots": cond["robots"], "arrivals": cond["arrivals_per_h"], "n_unity": len(us)}
    for side, xs in (("unity", us), ("site", ss)):
        row[side] = {
            "groups": med([x["groups"] for x in xs]),
            "util": med([x["util"] for x in xs]),
            "per_group_min": {j: med([x["per_group_min"][j] for x in xs]) for j in JOBS},
            "total_per_group_min": med([sum(x["per_group_min"].values()) for x in xs]),
            "entry": med([x["entry"] for x in xs]),
            "blocked": med([x["blocked"] for x in xs]),
        }
    row["unity"]["staff_wait"] = med([x["staff_wait"] for x in us])
    row["unity"]["dirty_untaken_mean"] = med([x["dirty_untaken_mean"] for x in us])
    row["site"]["guide_wait"] = med([x["guide_wait"] for x in ss])
    row["site_other"] = sorted({k for x in ss for k in x["other"]})
    rows.append(row)
    print(row["label"], cond["robots"], "台 done", flush=True)

os.makedirs(OUT, exist_ok=True)
json.dump(rows, open(os.path.join(OUT, "staff_breakdown.json"), "w", encoding="utf-8"), ensure_ascii=False, indent=1)
with open(os.path.join(OUT, "staff_breakdown.txt"), "w", encoding="utf-8") as fo:
    fo.write("混んでいるとき：1組あたりのスタッフの手がふさがった時間（分、中央値。Unity 種10個／試算 40回）\n")
    fo.write("店 台数 | 組数 U/S | 稼働率 U/S | " + " | ".join(JA[j] + " U/S" for j in JOBS) + " | 合計 U/S | 入店待ち U/S | 片付け待ち U/S\n")
    for r in rows:
        u, s = r["unity"], r["site"]
        cells = [f"{u['per_group_min'][j]:.2f}/{s['per_group_min'][j]:.2f}" for j in JOBS]
        fo.write(f"{r['label']} {r['robots']}台 | {u['groups']:.0f}/{s['groups']:.0f} | {u['util']:.2f}/{s['util']:.2f} | "
                 + " | ".join(cells) + f" | {u['total_per_group_min']:.2f}/{s['total_per_group_min']:.2f} | "
                 f"{u['entry']:.2f}/{s['entry']:.2f} | {u['blocked']:.2f}/{s['blocked']:.2f}\n")
print("wrote", OUT)
```

### staff_breakdown.txt（混んでいるとき・1組あたりの分）

```text
混んでいるとき：1組あたりのスタッフの手がふさがった時間（分、中央値。Unity 種10個／試算 40回）
店 台数 | 組数 U/S | 稼働率 U/S | 案内 U/S | 配膳 U/S | 呼び出し U/S | 会計 U/S | 片付け U/S | ロボット救出 U/S | 合計 U/S | 入店待ち U/S | 片付け待ち U/S
③ 効率重視・入口が角 0台 | 108/107 | 0.84/0.76 | 1.21/1.11 | 1.59/1.29 | 0.43/0.52 | 1.54/1.50 | 2.23/2.04 | 0.00/0.00 | 7.01/6.47 | 19.02/12.84 | 8.48/1.47
③ 効率重視・入口が角 1台 | 108/106 | 0.78/0.69 | 1.30/1.11 | 0.63/0.40 | 0.47/0.50 | 1.55/1.50 | 2.45/2.05 | 0.02/0.00 | 6.52/5.56 | 16.40/11.14 | 4.13/1.26
③ 効率重視・入口が角 2台 | 108/106 | 0.79/0.65 | 1.30/1.10 | 0.42/0.02 | 0.52/0.52 | 1.57/1.50 | 2.70/2.05 | 0.06/0.00 | 6.50/5.17 | 15.81/10.98 | 5.54/1.21
⑦ ③の通路を95cmに 0台 | 108/106 | 0.84/0.77 | 1.20/1.11 | 1.58/1.33 | 0.41/0.51 | 1.54/1.50 | 2.29/2.06 | 0.00/0.00 | 7.04/6.50 | 19.35/12.17 | 8.86/1.45
⑦ ③の通路を95cmに 1台 | 108/112 | 0.79/0.69 | 1.24/1.12 | 0.68/0.41 | 0.47/0.51 | 1.56/1.50 | 2.52/2.07 | 0.02/0.00 | 6.50/5.59 | 17.68/14.05 | 4.74/1.30
⑦ ③の通路を95cmに 2台 | 108/106 | 0.78/0.65 | 1.29/1.11 | 0.30/0.02 | 0.49/0.52 | 1.55/1.50 | 2.67/2.06 | 0.07/0.00 | 6.41/5.21 | 16.57/11.25 | 5.02/1.20
④ 効率重視・小さめの店 0台 | 72/70 | 0.87/0.89 | 1.12/1.03 | 1.37/1.14 | 0.38/0.47 | 1.58/1.50 | 2.04/1.89 | 0.00/0.00 | 6.47/6.01 | 32.87/24.06 | 20.78/14.32
④ 効率重視・小さめの店 1台 | 72/74 | 0.85/0.82 | 1.36/1.03 | 0.38/0.06 | 0.49/0.48 | 1.63/1.50 | 2.39/1.90 | 0.12/0.00 | 6.32/4.97 | 31.57/17.32 | 20.10/4.83
④ 効率重視・小さめの店 2台 | 72/70 | 0.87/0.82 | 1.58/1.03 | 0.42/0.00 | 0.51/0.47 | 1.67/1.50 | 2.45/1.91 | 0.48/0.00 | 7.17/4.91 | 45.92/13.04 | 23.61/4.54
⑤ ゆったり・入口が正面 0台 | 119/116 | 0.86/0.81 | 1.13/1.17 | 1.70/1.45 | 0.43/0.55 | 1.53/1.50 | 2.33/2.17 | 0.00/0.00 | 7.15/6.84 | 24.79/19.85 | 11.72/1.80
⑤ ゆったり・入口が正面 1台 | 119/120 | 0.84/0.73 | 1.25/1.17 | 0.78/0.47 | 0.52/0.56 | 1.53/1.50 | 2.74/2.17 | 0.01/0.00 | 6.86/5.86 | 23.70/18.24 | 8.51/1.39
⑤ ゆったり・入口が正面 2台 | 119/113 | 0.85/0.69 | 1.36/1.17 | 0.34/0.04 | 0.59/0.54 | 1.54/1.50 | 3.09/2.18 | 0.02/0.00 | 6.99/5.44 | 24.37/12.38 | 7.67/1.28
⑥ ゆったり・入口が横 0台 | 100/96 | 0.89/0.87 | 1.18/1.11 | 1.53/1.33 | 0.42/0.52 | 1.53/1.50 | 2.19/2.06 | 0.00/0.00 | 6.83/6.52 | 30.08/20.14 | 18.14/5.13
⑥ ゆったり・入口が横 1台 | 100/96 | 0.84/0.80 | 1.22/1.11 | 0.32/0.29 | 0.43/0.52 | 1.55/1.50 | 2.35/2.05 | 0.00/0.00 | 5.87/5.47 | 21.62/13.30 | 11.05/1.52
⑥ ゆったり・入口が横 2台 | 100/99 | 0.83/0.79 | 1.23/1.11 | 0.10/0.00 | 0.47/0.52 | 1.54/1.50 | 2.37/2.06 | 0.04/0.00 | 5.78/5.17 | 19.76/11.15 | 10.43/1.39
② 定番の郊外型 0台 | 47/46 | 0.76/0.75 | 1.07/1.04 | 1.30/1.14 | 0.40/0.47 | 1.55/1.50 | 2.05/1.91 | 0.00/0.00 | 6.33/6.02 | 11.17/8.76 | 5.81/3.32
② 定番の郊外型 1台 | 47/48 | 0.66/0.63 | 1.15/1.04 | 0.24/0.02 | 0.39/0.45 | 1.54/1.50 | 2.18/1.92 | 0.09/0.00 | 5.58/4.94 | 11.37/6.01 | 4.90/1.41
② 定番の郊外型 2台 | 47/48 | 0.65/0.65 | 1.17/1.03 | 0.10/0.00 | 0.44/0.50 | 1.56/1.50 | 2.09/1.91 | 0.17/0.00 | 5.61/4.93 | 10.69/6.12 | 3.57/1.32
```

ロボット0台では、1組あたりのスタッフの仕事は Unity のほうが5〜10%長いだけでした（③ 7.01／6.47分）。量の差では、3〜6倍のずれは出ません。

ロボットがいると差が広がります。④の2台では 7.17／4.91分です。案内と片付けが長くなり、ロボットを動かしに行く時間（試算は0）も入ります。

### 客が帰ってから片付けにスタッフが付くまでを比べた clear_wait_check.py

```python
"""片付け待ちのずれは仕事の量か、仕事の取り方か（2026-10-30）
客が帰ってから片付けのスタッフが付くまでの待ち・汚れた卓の平均の数を、Unity と試算で比べる（混んでいる・0台）
使い方: python3 clear_wait_check.py <haizen_sim.py のフォルダ> <service_sim/unity のフォルダ>
"""
import csv, glob, os, statistics, sys
SIM_DIR, UNITY_DIR = sys.argv[1], sys.argv[2]
sys.path.insert(0, SIM_DIR)
import haizen_sim as h

class S(h.Store):
    def __init__(self, *a, **k):
        super().__init__(*a, **k)
        self.cw = []          # 片付けの依頼からスタッフが付くまで（秒）
        self.dirty_area = 0.0; self._lt = 0.0; self._ld = 0
    def __setattr__(self, n, v):
        if n == "dirty" and hasattr(self, "_lt"):
            now = self.env.now; self.dirty_area += self._ld * (now - self._lt); self._lt = now; self._ld = v
        object.__setattr__(self, n, v)
    def staff_job(self, priority, walk_m, work_s, waited=None):
        if priority == 2:
            t0 = self.env.now; w = []
            yield from super().staff_job(priority, walk_m, work_s, w)
            self.cw.append(w[0])
        else:
            yield from super().staff_job(priority, walk_m, work_s, waited)

def ledger():
    return {r["run_folder"]: r for r in csv.DictReader(open(os.path.join(UNITY_DIR, "unity_ledger_v1.csv"), encoding="utf-8"))}
L = ledger()
print("店 | 片付けに付くまで 秒 U/S | 汚れた卓の平均数 U(未着手+片付け中)/S | 満席で待った 分 U/S | 片付け待ち 分 U/S")
for st in ["fam_p1", "fam_p5", "fam_p2", "fam_p3", "fam_p4", "fam_p0"]:
    s = h.STORES[st]; arr = s["arrivals"][2]; staff = s["staff"][1]
    cond = {"store": st, "staff": staff, "robots": 0, "arrivals_per_h": arr, "mode": "serve", "trays": 1, "fragile_by": "any", "registers": 1}
    sc, sd, sf, sb = [], [], [], []
    for seed in range(h.REPS):
        p = {**h.COMMON, **s, **cond}
        env = h.simpy.Environment(); x = S(env, h.random.Random(seed), p); env.process(x.arrivals()); env.run()
        sc.append(statistics.mean(x.cw)); sd.append(x.dirty_area / env.now)
        sb.append(statistics.mean(x.blocked_waits))
        sf.append(statistics.mean(x.entry_waits) - statistics.mean(x.blocked_waits) - statistics.mean(x.guide_waits))
    uc, ud, uf, ub = [], [], [], []
    for f in glob.glob(os.path.join(UNITY_DIR, f"20261106_night_a{arr}_cs44_{st}_r0_s*_passon_sbret")):
        r = L[os.path.basename(f)]
        uc.append(float(r["clear_wait_mean_s"])); ub.append(float(r["blocked_wait_mean"]))
        uf.append(float(r["entry_wait_mean"]) - float(r["staff_wait_mean"]))
        tl = list(csv.DictReader(open(os.path.join(f, "timeline.csv"), encoding="utf-8")))
        ud.append(statistics.mean(float(t["tables_dirty_untaken"]) + float(t["tables_clearing"]) for t in tl))
    m = statistics.median
    print(f"{s['label']} | {m(uc):.0f}/{m(sc):.0f} | {m(ud):.2f}/{m(sd):.2f} | {m(uf):.2f}/{m(sf):.2f} | {m(ub):.2f}/{m(sb):.2f}")
```

### clear_wait_check.txt

```text
店 | 片付けに付くまで 秒 U/S | 汚れた卓の平均数 U(未着手+片付け中)/S | 満席で待った 分 U/S | 片付け待ち 分 U/S
③ 効率重視・入口が角 | 617/20 | 5.78/1.14 | 10.18/11.31 | 8.48/1.47
⑦ ③の通路を95cmに | 629/20 | 5.86/1.15 | 10.07/10.40 | 8.86/1.45
④ 効率重視・小さめの店 | 1539/1124 | 7.45/6.03 | 11.38/9.61 | 20.78/14.32
⑤ ゆったり・入口が正面 | 915/33 | 8.50/1.29 | 12.63/17.84 | 11.72/1.80
⑥ ゆったり・入口が横 | 1352/290 | 9.54/2.79 | 11.04/13.41 | 18.14/5.13
② 定番の郊外型 | 358/192 | 1.84/1.27 | 5.21/4.74 | 5.81/3.32
```

③では、客が帰ってから片付けにスタッフが付くまで、Unity は617秒（約10分）、試算は20秒でした。汚れた卓の平均の数も 5.78卓と1.14卓です。仕事の量はほぼ同じなのに、Unity では片付けが長く後回しになっています。取り方のどこかが違う、と見ました。


## 作業ログ ― Claude Code に Unity のスタッフの仕事の選び方を読んでもらった（依頼31）

### 依頼31「スタッフの仕事の選び方を読んで調べる」の全文

Unity も記録も変えず、新しく回さない、読むだけの依頼にしました。試算の決まり（優先0：ロボットを動かす、優先1：案内・配膳・呼び出し・会計、優先2：片付け。同じ優先なら先に頼まれた順）を書き、Unity の選び方と比べてもらいました。

```markdown
---
作業フォルダ: /Users/shimazueiichi/Robotics_UX/ServiceSim
上限時間: 90
---
# 依頼：混むと片付けが詰まる理由を、Unity のスタッフの仕事の選び方から読んで調べる（読むだけ・2026-10-30）

**この依頼では何も変えない。** Assets・tools・設定ファイル・台帳・今までの出力は変えない。Unity エディタは開かない。新しく回さない。check-in しない。コードと今までの記録を読み、集計のスクリプトを書いて走らせるのはよい（スクリプトと出力は下の報告の場所にだけ置く）。

## なぜ調べるか

夜 20261106_busy（混み具合を広げた360回）で、混んでいるときの「席は空いているのに待った」が、どの店でも試算の3〜6倍になった。Claude が集計したところ、スタッフの仕事の量の差は小さく、客が帰ってから片付けにスタッフが付くまでの時間が大きく違った（集計：`/Volumes/KIOXIA/r2r2r-data/service_sim/unity/night/20261106_busy/staff_breakdown/` の staff_breakdown.txt・clear_wait_check.txt とスクリプト）。

| 混んでいる・0台 | 1組あたりのスタッフの仕事（分） Unity／試算 | 片付けにスタッフが付くまで（秒） Unity／試算 | 汚れた卓の平均の数 Unity／試算 |
|---|---|---|---|
| ③ | 7.0／6.5 | 617／20 | 5.8／1.1 |
| ⑤ | 7.2／6.8 | 915／33 | 8.5／1.3 |
| ⑥ | 6.8／6.5 | 1352／290 | 9.5／2.8 |
| ② | 6.3／6.0 | 358／192 | 1.8／1.3 |

ロボットを入れると、Unity では1組あたりの案内と片付けの時間が増える（④2台で 7.2 分／試算 4.9 分）。

回し方の決まりはサイトが正。Unity の仕事の選び方が試算と違うなら、どこが違うかを知りたい。直すかどうかはエイイチが決めるので、この依頼では直す案は並べるだけにする。

## 試算（haizen_sim.py v2.8）の決まり（比べる相手）

- スタッフは場所を持たない。手が空いた人が、待っている仕事を優先順で取る。同じ優先なら先に頼まれた順
  - 優先0：ロボットを動かしに行く
  - 優先1：案内・配膳・呼び出し・会計
  - 優先2：片付け
- 片付け（客が会計を終えて帰ったら頼まれる）：卓まで歩く＋皿をまとめる40秒＋卓を拭く40秒＋皿を持って戻る。往復は2回（bus_trips）。洗い場までの追加の距離は0。洗い場で待つことはない
- 案内：卓まで歩く → 席に着く → 説明45秒 → 戻る。会計：レジで90秒（レジ1つにつき1組ずつ）
- 「席は空いているのに待った」＝片付け待ち（汚れた卓の数までの列の前の組）＋案内のスタッフの手が空くのを待った時間

## 調べること

1. **スタッフの仕事の選び方：** 仕事の種類ごとの優先順、同じ優先の中での順番（先に頼まれた順か、近い順か）、スタッフがどこで待ち、どこから仕事を取るか（待つ点・持ち場・決まった人だけが取る仕事があるか）、仕事の途中で別の仕事に割り込まれるか
2. **片付けの中身：** だれが・いつ頼むか（客が帰った時か、会計の後か）、歩く道（卓 → 洗い場 → 卓など）、往復の回数、皿をまとめる・拭くの時間（params の clear_s・wipe_s・bus_trips を使っているか）、洗い場で待つ・置ける量に上限があるか
3. **数え方：** clear_wait_mean_s・blocked_wait_mean・staff_wait_mean・timeline の tables_dirty_untaken／tables_clearing を、どの時刻からどの時刻までで数えているか。params の meta.metrics の定義と同じか
4. **記録で追う：** ③混んでいる0台の種0（`20261106_night_a54_cs44_fam_p1_r0_s0_passon_sbret`）で、片付けを待った時間が長かった卓を5つ選び、客が帰った時刻 → スタッフが片付けを取った時刻 → 終わった時刻と、その間スタッフ4人がそれぞれ何をしていたかを並べる（記録から分かる範囲で。分からなければ分からないと書く）
5. **ロボットがいるとスタッフの仕事が増える理由：** ④混んでいる2台で、案内・片付けが長くなる分が何から来ているか（ロボットをよける・道を空ける・ロボットを動かしに行く・2分待って代わりに運ぶ・遠回りなど）を、記録から分かる範囲で分ける

## 報告

依頼箱の結果に加えて、claude_code_reports に書く（場所：~/Library/Mobile Documents/com~apple~CloudDocs/デスクトップ/ADAPT DESIGN/じぶんの/PixelstoMotors/デジタルツイン学習R2R2R/サービスロボット導入シミュレーション/unity/claude_code_reports/）。

**20261030_HHMM_staff_job_choice.txt**
- 1〜5 の答え。読んだファイルと行の番号を添える
- 試算との違いの表：項目／試算／Unity／片付け待ちのずれにどのくらい効いていそうか（大・中・小・分からない）
- 4 の5つの卓の時刻の表
- 直すとしたら考えられる形を、並べるだけ（どれを選ぶかは書かない）。サイトの試算の側に足すもの（たとえば洗い場の受け入れ）と、Unity を試算の決まりに合わせるもの、の両方
- 書いた集計のスクリプトの全文（置いた場所も）
- 変えたファイルがないこと（ServiceSim の保留 0・台帳の行数 1032 と md5 が始めと同じ）
```

### Mac が止まり、依頼31は途中で終わった

7:38 に始めた依頼31は、Mac が止まって再起動したため途中で終わりました。見張り係は 8:13 に自動で立ち上がり、途中だった依頼を「失敗」に回していました。

```markdown
# 結果：20261030_31_スタッフの仕事の選び方を読んで調べる

- 状態：失敗
- 始めた時刻：2026-10-30 08:13
- 終わった時刻：2026-10-30 08:13（0分）
- 作業フォルダ：不明

## Claude Code の報告

見張り係が止まった（Mac の再起動など）ため、この依頼は途中で終わっています。途中まで変わったファイルがあるかもしれません。やり直すかどうかは、確かめてから決めます。
```

読むだけの依頼なので、変わったのは Claude Code が書きかけた集計のスクリプト1つと、中身が空の結果ファイルだけでした。台帳は1032行のままで、最後の行は夜の最後の回、夜の前の672行は控えと同じ md5（79ded907583535ed5ac120e56595ac64）でした。そこで、書きかけのファイルは使えるなら続きから使ってよいことと、始める前に Unity の保留が0で前の作業で変わったファイルがないことを確かめることを、依頼の頭に足して出し直しました（8:16 に開始）。

```markdown
---
作業フォルダ: /Users/shimazueiichi/Robotics_UX/ServiceSim
上限時間: 90
---
# 依頼：混むと片付けが詰まる理由を、Unity のスタッフの仕事の選び方から読んで調べる（読むだけ・2026-10-30）

**やり直し（2026-10-30 08:20）：** 07:38 に始めた同じ依頼が、Mac の再起動で途中で止まった。途中のファイル（claude_code_reports/20261030_0744_staff_job_choice_files/ の compare_timeline.py・空の compare_timeline.txt）は消さずに残っている。使えるなら続きから使ってよい。始める前に、ServiceSim の保留が 0 で、前の作業で変わったファイルがないことを確かめ、報告に書く（変わっていたら手を止めて報告）。

**この依頼では何も変えない。** Assets・tools・設定ファイル・台帳・今までの出力は変えない。Unity エディタは開かない。新しく回さない。check-in しない。コードと今までの記録を読み、集計のスクリプトを書いて走らせるのはよい（スクリプトと出力は下の報告の場所にだけ置く）。

## なぜ調べるか

夜 20261106_busy（混み具合を広げた360回）で、混んでいるときの「席は空いているのに待った」が、どの店でも試算の3〜6倍になった。Claude が集計したところ、スタッフの仕事の量の差は小さく、客が帰ってから片付けにスタッフが付くまでの時間が大きく違った（集計：`/Volumes/KIOXIA/r2r2r-data/service_sim/unity/night/20261106_busy/staff_breakdown/` の staff_breakdown.txt・clear_wait_check.txt とスクリプト）。

| 混んでいる・0台 | 1組あたりのスタッフの仕事（分） Unity／試算 | 片付けにスタッフが付くまで（秒） Unity／試算 | 汚れた卓の平均の数 Unity／試算 |
|---|---|---|---|
| ③ | 7.0／6.5 | 617／20 | 5.8／1.1 |
| ⑤ | 7.2／6.8 | 915／33 | 8.5／1.3 |
| ⑥ | 6.8／6.5 | 1352／290 | 9.5／2.8 |
| ② | 6.3／6.0 | 358／192 | 1.8／1.3 |

ロボットを入れると、Unity では1組あたりの案内と片付けの時間が増える（④2台で 7.2 分／試算 4.9 分）。

回し方の決まりはサイトが正。Unity の仕事の選び方が試算と違うなら、どこが違うかを知りたい。直すかどうかはエイイチが決めるので、この依頼では直す案は並べるだけにする。

## 試算（haizen_sim.py v2.8）の決まり（比べる相手）

- スタッフは場所を持たない。手が空いた人が、待っている仕事を優先順で取る。同じ優先なら先に頼まれた順
  - 優先0：ロボットを動かしに行く
  - 優先1：案内・配膳・呼び出し・会計
  - 優先2：片付け
- 片付け（客が会計を終えて帰ったら頼まれる）：卓まで歩く＋皿をまとめる40秒＋卓を拭く40秒＋皿を持って戻る。往復は2回（bus_trips）。洗い場までの追加の距離は0。洗い場で待つことはない
- 案内：卓まで歩く → 席に着く → 説明45秒 → 戻る。会計：レジで90秒（レジ1つにつき1組ずつ）
- 「席は空いているのに待った」＝片付け待ち（汚れた卓の数までの列の前の組）＋案内のスタッフの手が空くのを待った時間

## 調べること

1. **スタッフの仕事の選び方：** 仕事の種類ごとの優先順、同じ優先の中での順番（先に頼まれた順か、近い順か）、スタッフがどこで待ち、どこから仕事を取るか（待つ点・持ち場・決まった人だけが取る仕事があるか）、仕事の途中で別の仕事に割り込まれるか
2. **片付けの中身：** だれが・いつ頼むか（客が帰った時か、会計の後か）、歩く道（卓 → 洗い場 → 卓など）、往復の回数、皿をまとめる・拭くの時間（params の clear_s・wipe_s・bus_trips を使っているか）、洗い場で待つ・置ける量に上限があるか
3. **数え方：** clear_wait_mean_s・blocked_wait_mean・staff_wait_mean・timeline の tables_dirty_untaken／tables_clearing を、どの時刻からどの時刻までで数えているか。params の meta.metrics の定義と同じか
4. **記録で追う：** ③混んでいる0台の種0（`20261106_night_a54_cs44_fam_p1_r0_s0_passon_sbret`）で、片付けを待った時間が長かった卓を5つ選び、客が帰った時刻 → スタッフが片付けを取った時刻 → 終わった時刻と、その間スタッフ4人がそれぞれ何をしていたかを並べる（記録から分かる範囲で。分からなければ分からないと書く）
5. **ロボットがいるとスタッフの仕事が増える理由：** ④混んでいる2台で、案内・片付けが長くなる分が何から来ているか（ロボットをよける・道を空ける・ロボットを動かしに行く・2分待って代わりに運ぶ・遠回りなど）を、記録から分かる範囲で分ける

## 報告

依頼箱の結果に加えて、claude_code_reports に書く（場所：~/Library/Mobile Documents/com~apple~CloudDocs/デスクトップ/ADAPT DESIGN/じぶんの/PixelstoMotors/デジタルツイン学習R2R2R/サービスロボット導入シミュレーション/unity/claude_code_reports/）。

**20261030_HHMM_staff_job_choice.txt**
- 1〜5 の答え。読んだファイルと行の番号を添える
- 試算との違いの表：項目／試算／Unity／片付け待ちのずれにどのくらい効いていそうか（大・中・小・分からない）
- 4 の5つの卓の時刻の表
- 直すとしたら考えられる形を、並べるだけ（どれを選ぶかは書かない）。サイトの試算の側に足すもの（たとえば洗い場の受け入れ）と、Unity を試算の決まりに合わせるもの、の両方
- 書いた集計のスクリプトの全文（置いた場所も）
- 変えたファイルがないこと（ServiceSim の保留 0・台帳の行数 1032 と md5 が始めと同じ）
```

### 依頼31 のやり直しの結果（依頼箱の報告）

```markdown
# 結果：20261030_31_スタッフの仕事の選び方を読んで調べる_やり直し

- 状態：完了
- 始めた時刻：2026-10-30 08:16
- 終わった時刻：2026-10-30 08:58（43分）
- 作業フォルダ：/Users/shimazueiichi/Robotics_UX/ServiceSim
- この続きを Claude Code で話すとき：作業フォルダで `claude --resume 714640fd-feee-4e3e-a54a-fddf8d649b41`

## Claude Code の報告

**やったこと**
- 始める前に確かめました。ServiceSim の保留は 0（cs:45 head）、台帳は 1032 行で md5 は 52e433713d4f7d41dfa8ccfb889492cd、Assets・tools に前の作業で変わったファイルはありませんでした。
- Unity のコード（StaffAgent.cs・ServiceOps.cs・GroupManager.cs・TimelineLogger.cs・LedgerRow.cs）と試算 haizen_sim.py v2.8 を読み、1〜5 に行の番号つきで答えました。
- 前の作業の compare_timeline.py が終わらなかった理由がわかりました。試算の window() を走らせていないため「全員が帰った」の合図が来ず、1分ごとに数える処理が止まりません。前のファイルは残し、window() を足した写しを今回のフォルダに置いて走らせました。
- 集計のスクリプトを3つ書いて走らせました。pay_flow_check（会計と片付けの流れ）、trace_clear（4 の卓を追う）、robot_staff_extra（5 のロボットありの分け方）です。

**結果**
- 仕事の選び方の決まりは、Unity と試算で同じでした。優先は 0／1／2 で、同じ優先なら先に頼まれた順です。近い順ではなく、持ち場も割り込みもありません。
- 片付け待ちのずれは、主に次の二つから来ています。
  - **会計の頼みを出す時刻が違う（効き：大）。** 試算はレジが空いてからスタッフを頼むので、ほかの仕事の後ろに並び、そのあいだレジが止まります（レジを持っていた時間の 12%）。Unity は客がレジの並ぶ点に着いた時に頼むので、前の会計が終わるとすぐ取られ、ほぼいつも1人がレジにいます。
    - そのため ③ の 60〜160分では、会計が済む組が Unity 1時間に 38.7、試算 34.8 で、片付けの仕事も Unity のほうが速く入ってきます。
  - **片付け1回が Unity のほうが長い（効き：中）。** ③で 135 秒、試算は 123 秒です。Unity は前の仕事が終わった所から歩き出し、卓で終わるためです。片付けが済む卓は1時間に Unity 32.4、試算 35.1 です。
  - 二つを合わせると、入ってくる数と済む数の差は ③ で Unity 1時間に +6.3 卓、試算 −0.3 卓です。100分で約10卓たまり、記録の汚れた卓（12前後）と合います。② では差が小さく（0.3／0.6）、ずれが小さいことと合います。
  - 試算では、たまる分は「卓で会計を待つ」（15分）として出ます。Unity では「汚れた卓」として出ます。
- **数え方：** clear_wait・blocked_wait・staff_wait の数え方は試算とほぼ同じで、数え方の違いで 3〜6 倍は出ません。
- **4（③0台・種0の5つの卓）：** 客が帰ってからスタッフが取るまで 18.5〜21 分、取ってから終わるまで 1〜3 分でした。
  - 割り出した待ちの平均は 555 秒で、記録の 548 秒とほぼ合います。T36 は次の組が来なかったので、終わった時刻は分かりません。
  - スタッフ4人それぞれの動きは記録にないので分かりません。人数で見ると、待機は 0、レジに約1人、ほかの仕事に約2人、片付けに約1人でした。
- **5（④2台）：** 1回あたり、案内が +24.7 秒、片付けが +25.6 秒長くなりました。
  - どちらもおよそ、あきらめるまでの60秒が約12秒、ロボットをよけて止まった分が 4〜5 秒、残りが約9秒です。よけた分はどの仕事のときか記録にないので、歩いた時間の割合で分けた見積りです。
  - ほかに、ロボットを動かしに行く時間が 1組あたり 30.6 秒増えました（試算は 0）。
- **変えたものがないこと：** 終わりも保留 0、台帳は 1032 行で md5 も始めと同じでした。Unity エディタは開かず、新しく回さず、check-in もしていません。
  - 例外が二つあります。一つは simulation/v2_8/__pycache__/haizen_sim.cpython-312.pyc です。1回目の走らせで python が自動で書いたキャッシュで、haizen_sim.py そのものは変わっていません。
  - もう一つは、試算を走らせる simpy がこの Mac に入っていなかったため、uv の一時の環境で使ったことです。入るのは uv のキャッシュの中だけで、プロジェクトと設定は変えていません。

**変えたファイル**
- 新しく作ったもの（claude_code_reports/ の中だけ）：
  - 20261030_0823_staff_job_choice.txt（報告。スクリプトの全文入り）
  - 20261030_0823_staff_job_choice_files/ の中の compare_timeline.py／.txt、pay_flow_check.py／.txt、trace_clear.py／.txt、robot_staff_extra.py／.txt／_p1.txt
- 前の作業の 20261030_0744_staff_job_choice_files/compare_timeline.txt は、今回の1回目の走らせで空のまま書き直されました（前も空でした）。

**決めてほしいこと**
- 直すかどうかと、どの形にするか。案は報告にサイト側・Unity 側の両方を並べました。大きく効いているのは「会計の頼みを出す時刻」で、試算の頼み方に合わせるか、Unity の頼み方を試算に入れるかで方向が逆になります。
- 試算のフォルダにできたキャッシュ haizen_sim.cpython-312.pyc を消すかどうか。前からあったか分からないので、消さずに残しています。
```

### Claude Code の報告 20261030_0823_staff_job_choice.txt の全文

報告の中に、集計のスクリプト（pay_flow_check・trace_clear・robot_staff_extra）の全文も入っています。

```text
混むと片付けが詰まる理由を、Unity のスタッフの仕事の選び方から読んで調べる（読むだけ・2026-10-30 08:23〜08:56・やり直し）

■ まとめ
・仕事を選ぶ決まり（優先0／1／2、同じ優先なら先に頼まれた順、持ち場なし、途中で割り込まれない）は、Unity と試算で同じ。
・ずれは主に二つから来ている。どちらも「片付けの仕事が入ってくる速さ」と「片付けが済む速さ」の差になる。
  (ア) 会計の頼みを出す時刻が違う（大）。試算はレジが空いてからスタッフを頼むので、ほかの優先1の仕事の後ろに並び、
       そのあいだレジが止まる（レジを持っていた時間のうち、会計していたのは ③ 88%）。Unity は会計の人がレジの並ぶ点に
       着いた時に板に出すので、板の優先1の中で古くなり、前の会計が終わるとすぐ取られる（会計のスタッフ 0.97 人＝ほぼいつも1人）。
       その分、Unity は会計が速く回る（③ 60〜160分 38.7 組/時、試算 34.8）→ 片付けの仕事も速く入ってくる。
  (イ) 片付け1回が Unity のほうが長い（中）。③ 135 秒、試算 123 秒。片付けに付く人数はほぼ同じ（③ 1.17〜1.26 人／試算 1.15〜1.22 人）
       なので、済む速さは Unity が遅い（③ 32.4 卓/時、試算 35.1）。
  ③で、入ってくる − 済む ＝ Unity 1時間に＋6.3 卓、試算 −0.3 卓。60〜160分の100分で Unity は約10卓たまる。記録の汚れた卓（12前後）と合う。
  試算では、たまる分が「会計を卓で待つ」（pay_wait 15 分）に出て、卓はふさがったまま（＝満席で待つ）。
  Unity では、たまる分が「汚れた卓」に出る（片付け待ち）。来た組が待つ長さは同じくらいでも、待ちの名前が入れ替わる。
・② 定番の郊外型は、Unity も試算も入ってくる−済むが 0 に近く（0.3／0.6）、ずれが小さい（358 秒／192 秒）のと合う。

■ 始める前の確かめ（08:20 ごろ）
・ServiceSim の保留：0（cm status。/main@ServiceSim cs:45 head、保留の行なし）
・台帳 /Volumes/KIOXIA/r2r2r-data/service_sim/unity/unity_ledger_v1.csv：1032 行・md5 52e433713d4f7d41dfa8ccfb889492cd（最後に書かれたのは 10/30 06:04。07:38 の作業より前）
・unity_ledger_v1.lock：なし
・Assets・tools・ProjectSettings・Packages に 10/30 06:10 より後に変わったファイル：なし
・前の作業の途中のファイル（20261030_0744_staff_job_choice_files/）：compare_timeline.py と空の compare_timeline.txt が残っていた。
  → 前の作業で変わったファイルはないと判断して始めた。

■ 前の作業（07:44 の compare_timeline.py）について
・今回も同じものを走らせたら、10 分たっても何も出さずに止められた。
・理由：試算の window() を走らせていない。試算の「全員が帰った」の合図（finished）は window() の中で出るので、
  合図が来ないまま、1分ごとに数える sampler が回り続ける（試算の run_once は window() を走らせている）。
  07:44 の作業が空の txt のまま止まっていたのも、Mac の再起動の前にこれで回り続けていたためと思われる。
・07:44 のファイルはそのまま残し、window() を足した写しを今回のフォルダに置いて走らせた（2.5 秒で終わった）。
・試算を走らせる simpy はこの Mac の python に入っていなかった。uv の一時の環境（uv run --no-project --with simpy、
  入るのは ~/.cache/uv の中だけ）で走らせた。プロジェクト・設定は変えていない。

■ 1. スタッフの仕事の選び方
読んだところ：
  Assets/ServiceSim/People/StaffAgent.cs 7-16（決まりの説明）、92-101（待つ点）、143-198（Update）、201-216（Idle）、
    233-291（Begin：仕事ごとの行き先）、467-503（GiveUpIfLate）
  Assets/ServiceSim/Service/ServiceOps.cs 77-110（StaffJob）、1658-1686（PostJob／PostJobFront）、1692-1713（TakeNextJob）、
    1716-1747（FinishJob／AbandonJob）、1266-1276（PostRescue）、1594-1647（戻された皿を板の先頭に出す）
  Assets/ServiceSim/Service/GroupManager.cs 411-434（案内を出す）、536（呼び出し）、577-587（片付けを出す）、684・710（会計を出す）
  haizen_sim.py（v2.8）279-288（staff_job）、414-458（bus）、474-543（group）
答え：
・優先：0＝立ち往生のロボットを動かす、1＝案内・料理・呼び出し・会計、2＝片付け。試算と同じ（StaffJob の priority。
  ServiceOps.cs 78、GroupManager.cs 432・536・584・684・710）。
・同じ優先の中の順番：板（_jobs）の並び順＝頼まれた順。近い順ではない（TakeNextJob 1692-1713 は「優先が小さいもの、
  同じなら前のもの」）。例外は二つ：立ち往生は板の先頭に入れる（PostRescue 1272）、ロボットから戻された料理も先頭に入れる
  （PostJobFront 1647）。料理は誰かが持った・届いたものは飛ばす。会計は、レジの内側に誰かがいる間は飛ばす（RegisterBackBusy、1700）。
・どこで待つか：手が空いたら、スタッフごとに1つ決まった「スタッフが待つ点」へ歩く（StaffAgent.cs 92-101、210）。③は
  受け渡し口の横の6点（x 13.75、z 6.8〜9.8）。待つ点へ歩いている途中でも、毎コマ板を見て仕事を取る（Idle 201-216）。
・どこから仕事を取るか：場所を見ない。いまいる所から、その仕事の最初の行き先へ歩き出す。持ち場はない。決まった人だけの仕事もない
  （会計は1人ずつ、というだけ）。
・仕事の終わる場所が試算と違う：試算はどの仕事も受け渡し口から出て戻る（往復）。Unity は戻らない。
  案内は卓で、呼び出しは卓で、片付けは卓で（最後に拭くため）、料理は受け渡し口で、会計はレジの内側で終わる。
  次の仕事はそこから歩く（StaffAgent.cs 243-286）。
・割り込み：ない。取った仕事は終わるまで続ける。止まるのは二つだけ：ロボットをよけている間（182）、1つの行き先に 60 秒着けない
  ときにあきらめて板に戻す（3回目はその場所へ移して続ける。467-503）。③0台の種0では、スタッフのあきらめ・移したは 0 件。

■ 2. 片付けの中身
読んだところ：GroupManager.cs 723-735（OnPaid）、577-587（Left で片付けを出す）、749-755（OnCleared）、
  StaffAgent.cs 267-278、ServiceOps.cs 1208-1215（ReleaseTable）、SiteValues.cs 111-112・136-137、ServiceSpec.cs 118-124、
  Assets/ServiceSim/Store/Layouts/p1_efficient_corner.json の places
答え：
・頼む人・時：会計が済んだ時（OnPaid で left_s を入れ、状態を Left にする）。次のコマに GroupManager が片付けの仕事を
  優先2で板に出す（584）。客が歩いて店を出るのを待たない。試算も会計の staff_job が終わったらすぐ dirty を1足して bus に入る（534-536）。同じ。
・歩く道（bus_trips＝2 のとき）：いた所 → 卓（clear_s 40 秒）→ 返却口 → 卓 → 返却口 → 卓（wipe_s 40 秒）で終わり。
  歩くのは「いた所 → 卓」と、卓と返却口のあいだを4本。試算は 受け渡し口と卓のあいだを4本（2 ×（d＋dish_extra_m）× bus_trips）。
  Unity は最初の1本が多く、最後に戻る1本がない。③は返却口と受け渡し口が同じ場所（rect [14.3, 6.5, 16.3, 6.9]）。
  なお StaffAgent.cs 268 の説明文は「返却口へ運び、戻って拭いて、もう一度運ぶ」だが、コードは「運ぶ→戻る→運ぶ→戻って拭く」。
・往復の回数・時間：params の clear_s・wipe_s（サイトの決まり）と、店の bus_trips（family＝2）を使っている（SiteValues.cs 111-112・136-137）。
・洗い場：返却口はただの点。待つことも、置ける量の上限もない。試算も同じ（dish_extra_m＝0、待ちなし）。
・1回の時間（スタッフの手がふさがった秒、中央値）：③ Unity 135／試算 123、⑤ 141／130、⑥ 131／124、② 123／114。

■ 3. 数え方
読んだところ：GroupManager.cs 322-343（CountBlockedWait）、450-465（OnGuided）、ServiceOps.cs 545-565（RecordJobWait）・
  1226-1243（RecordEntryWait）・1249-1258（ClearingTableCount）、TimelineLogger.cs 12・118-145・168-183・199-213、
  LedgerRow.cs 227-238、params の meta.metrics、haizen_sim.py 241-248（_acc）・485-504
答え：
・clear_wait_mean_s（Unity だけの欄。params の meta.metrics には無い）：片付けの仕事が板に出た時刻（postedS＝会計が済んだ次のコマ）から、
  スタッフが取った時刻まで。取られるたびに1回数え、あきらめて戻った仕事は取り直すたびに、最初に出た時刻からもう一度数える。
  卓を空けて板から外した仕事は入らない。慣らしの組も外さず、店が開いてから全員が帰るまでの全部。
  比べた試算の 20 秒は、staff_breakdown の clear_wait_check.py が優先2の staff_job の「頼んでから手が空くまで」を数えたもの。0台では同じものを数えている。
・blocked_wait_mean：毎コマ、片付け待ちの卓（客が帰ってから拭き終わるまで。clearing）の数だけ、入口の列の前から dt を足す。
  卓に着いた時（案内が済んだ時）に1組ずつ残す。試算の _acc と同じ数え方（列の前から、dirty の数まで）。
  ちがいは一つ：Unity は 2人・4人・6人席があり、列の先頭が入れない卓だと飛ばして次の組を座らせるが、blocked は席の大きさを見ずに前から数える（322-335 の注にあるとおり）。
・staff_wait_mean：blocked_wait ＋ guide_wait（案内の仕事が板に出てからスタッフが取るまで）。meta.metrics の「席は空いているのに片付け・案内が追いつかず待った時間」と同じ。
・turn_mean（meta.metrics「客が帰ってから卓が使えるまで」）：Unity は ReleaseTable の時刻 − left_s。試算は bus の終わり − 始まり。同じ。
・timeline の tables_dirty_untaken／tables_clearing：60 秒ごとのその瞬間の数（平均にしない）。clearing の卓を、その卓の片付けの仕事が
  取られていれば tables_clearing、まだなら tables_dirty_untaken に入れる。0 秒から全員が帰るまで。サイトの指標ではない。
  依頼の表の「汚れた卓の平均の数」は、Unity が timeline の行の平均、試算が dirty の時間の重みつき平均（どちらも全部の時間）。
・まとめ：数え方の違いで 3〜6 倍は出ない。

■ 4. 記録で追う（③ 混んでいる・0台・種0：20261106_night_a54_cs44_fam_p1_r0_s0_passon_sbret）
記録に「取った時刻」「終わった時刻」は無いので、割り出した（やり方は trace_clear.py の頭に書いた）：
  ・片付けはみな優先2で、出た順に取られる。この回にスタッフのあきらめ・卓を空けたは 0 件なので、順番は崩れていない。
    → t までに取られた数 ＝ t までに頼まれた数 − tables_dirty_untaken(t)。60 秒ごとの幅で出る。
  ・終わった時刻は、同じ卓の次の組の案内が始まった時刻を上の端にした（入口で待つ組がいれば、卓が空いたコマに案内が始まる）。
  ・確かめ：割り出した待ちの平均 555 秒、summary.json の片付けの job_wait_mean_s 548 秒。
時刻は店が開いてからの分：
  卓  | 席 | 組 | 客が帰った（会計済み）| スタッフが取った | 終わった（次の組の案内が始まった。上の端）| そのとき入口で待つ組 | 帰ってから取るまで | 取ってから終わりまで
  T31 | 6 | 42 | 111.0 | 131.0〜132.0 | 133.1 | 2 | 20.0〜21.0分 | 1.1〜2.1分
  T29 | 4 | 38 | 105.0 | 125.0〜126.0 | 127.0 | 7 | 20.0〜21.0分 | 1.0〜2.0分
  T36 | 2 | 41 | 115.5 | 134.0〜135.0 | 分からない（この卓に次の組は来なかった） | - | 18.5〜19.5分 | 分からない
  T01 | 4 | 45 | 112.5 | 131.0〜132.0 | 133.7 | 2 | 18.5〜19.5分 | 1.7〜2.7分
  T30 | 4 | 39 | 106.5 | 125.0〜126.0 | 128.0 | 7 | 18.5〜19.5分 | 2.0〜3.0分
そのあいだスタッフ4人がしていたこと：
  ・1人ずつの動きは記録に無い（timeline は仕事ごとの人数だけ。StaffAgent に1人ずつの記録はない）。分からない。
  ・人数で見ると（帰ってから取られるまでの行の平均）：待機 0.00、案内 0.55〜0.70、料理 1.10〜1.15、呼び出し 0.15〜0.30、
    会計 0.90〜0.95、片付け 1.05〜1.15。板には優先1の仕事（会計を除く）が 1.4〜1.9、片付けが 11.5〜12.0 残っていた。
  ・つまり4人とも手がふさがっていて、1人はほぼいつもレジ。残り3人のうち約2人が優先1を、約1人が片付けをしていた。
    片付けは、板に優先1が無くなった瞬間にまとめて取られている（例：115〜116分に3人、127分に4人、132分に3人が片付け）。
  ・T31 の1分ごとの表は trace_clear.txt にある。前に頼まれてまだ取られていない片付けが 10 → 0 になるまで 16 分かかった。

■ 5. ロボットがいるとスタッフの仕事が増える理由（④ 効率重視・小さめの店・混んでいる・2台、種10個の中央値）
読んだところ：StaffAgent.cs 147-153・182・467-503、PersonRobotYield.cs 100-125、PeopleSpec.cs 205-222、
  ServingRobotController.cs 1765-1785（EnterStuck → PostRescue）、各回の summary.json・give_ups.csv・yields.csv
・Unity のスタッフの時間は「仕事を持っている間」を仕事の名前ごとに足す。よけて止まった時間・あきらめるまでの 60 秒も、その仕事の時間に入る。
・案内1回：0台 68.5 秒 → 2台 93.2 秒（＋24.7。試算は 0台も2台も 1.03 分＝約 62 秒のまま）
    あきらめるまでの 60 秒：＋11.9 秒（1組あたり 0.20 回あきらめた。0.05 回は3回目で移した）
    ロボットをよけて止まった：＋4.1 秒（按分。どの仕事のときかは記録に無いので、歩いている時間の割合で分けた）
    そのほか：＋8.7 秒（あきらめたあと最初から歩き直す分・遠回り・人どうしのつかえ・ゆっくり歩く。記録からは分けられない）
・片付け1回：0台 122.9 秒 → 2台 148.5 秒（＋25.6。試算は 1.89〜1.91 分のまま）
    あきらめるまでの 60 秒：＋11.8 秒（0.20 回/組）、よけて止まった（按分）：＋5.3 秒、そのほか：＋8.5 秒
・案内・片付けの外で増える分：
    ロボットを動かしに行く（立ち往生）：0 → 30.6 秒/組（1組あたり 0.84 回。試算は 0.00 分）。4つの中でいちばん大きい。
    2分待って代わりに運ぶ：0.29 皿/組。これは料理の時間に入り、案内・片付けには入らない。
    スタッフがよけて止まった秒の合計：18.0 秒/組（仕事を持っている時間の 3.8%）
    人のつかえ（客も入る）：0.6 → 38.6 秒/組。ほとんどがロボットに道をふさがれたつかえ（種0で 98%）
    スタッフが歩いた距離：127 → 98 m/組（料理をロボットが運ぶ分へる）。遠回りは、距離の合計からは見えない。
・③ 2台（参考）：案内 ＋5.2 秒、片付け ＋27.5 秒（あきらめ 9.4・よけ 13.7（按分）・そのほか 4.4）、立ち往生 3.7 秒/組。

■ 試算との違いの表
項目 | 試算（v2.8） | Unity | 片付け待ちのずれに効いていそうか
会計のスタッフを頼む時刻 | レジが空いてから（ほかの優先1の後ろに並ぶ。レジが止まる時間がある：③ 12%） | レジの並ぶ点に着いた時に板に出す（古い順で前にいるので、前の会計が終わるとすぐ取られる。レジの並ぶ点は3つ） | 大（③ 会計 38.7／34.8 組/時。片付けが入ってくる速さの差 3.9 卓/時）
片付け1回の道 | 受け渡し口⇔卓を4本 | いた所→卓＋卓⇔返却口を4本、卓で終わる | 中（③ 135／123 秒。済む速さの差 2.7 卓/時）
仕事を始める・終える場所 | 毎回 受け渡し口から出て戻る | 前の仕事が終わった所から歩く。戻らない | 中（上の片付けの差と、案内の差に入っている）
案内の道 | 受け渡し口→卓（着いたら席に着く）→説明45秒→戻る | いた所→待合（45秒）→卓 | 小（③ 1.21／1.11 分）
会計の道 | 歩かない | いた所→レジの内側 | 小（93／90 秒）
優先順・同じ優先の順番 | 0／1／2、先に頼まれた順 | 同じ（立ち往生と戻された料理は先頭） | 小（0台では違いなし）
持ち場・待つ点 | 場所なし | 待つ点が1人に1つ。持ち場なし | 小
割り込み | なし | なし（あきらめ60秒で板に戻す。③0台は0件） | 小（0台）
洗い場 | 上限・待ちなし | 上限・待ちなし | 小（違いなし）
卓の大きさ | 無い | 2・4・6人席。入れない組は飛ばす | 分からない
数え方（片付け待ち・blocked） | _acc | 同じ形。clear_wait は取り直しも数える | 小
ロボットがいるときの人のよけ・あきらめ・立ち往生 | 入っていない（stuck_rate 0.01 だけ） | 入る（④2台で案内＋25秒・片付け＋26秒・立ち往生 31 秒/組） | ロボットありの条件で大

■ 直すとしたら考えられる形（並べるだけ。どれを選ぶかは書かない）
サイトの試算の側に足すもの：
  ・会計のスタッフを、レジの並ぶ点に着いた時（レジを取る前）に頼む形にする。または、会計の頼みだけ先に頼まれた順を前にする
  ・スタッフの居場所を持たせ、次の仕事は前の仕事が終わった所から歩く（戻りの1本を入れない）
  ・片付けの道を「卓まで1本＋卓⇔返却口を 2×bus_trips 本」にする
  ・案内を「待合へ迎えに行く→卓」の道にする。会計にレジまで歩く時間を足す
  ・洗い場（返却口）の受け入れの上限や置ける量、待ちを足す（今はどちらにも無い。足すなら両方に要る）
  ・ロボットがいるときの、スタッフのよけ・あきらめ・歩き直し・立ち往生の時間を unity_measures として戻して入れる
  ・片付けの優先を、待った時間で上げる（待つほど前に出る）などの決まりを足す（サイトの決まりを変えることになる）
Unity を試算の決まりに合わせるもの：
  ・会計の仕事を板に出すのを、レジの内側が空いてからにする（＝試算と同じく、ほかの優先1の後ろに並ぶ）
  ・どの仕事も終わったら受け渡し口（または待つ点）に戻ってから次を取る
  ・片付けを「受け渡し口⇔卓を 2×bus_trips 本」の道にし、受け渡し口で終える
  ・案内を「受け渡し口→卓→説明45秒→戻る」の形にする（待合に迎えに行かない）
  ・会計のスタッフをレジの内側から動かさない（歩きを 0 にする）
  ・clear_wait_mean_s を、取り直しを数えない形にそろえる（0台ではもともと同じ）

■ 置いたファイル（どれも claude_code_reports/ の中だけ）
  20261030_0823_staff_job_choice.txt（この報告）
  20261030_0823_staff_job_choice_files/
    compare_timeline.py（07:44 の版に window() を足した写し）／compare_timeline.txt（その出力）
    pay_flow_check.py／pay_flow_check.txt（会計と片付けの流れ）
    trace_clear.py／trace_clear.txt（4 の卓の時刻）
    robot_staff_extra.py／robot_staff_extra.txt（5 の ④）、robot_staff_extra_p1.txt（同じスクリプトで ③）
  07:44 のフォルダ（20261030_0744_staff_job_choice_files/）は消していない。中の compare_timeline.txt は、今回の1回目の
  走らせ（止められた）で、空のまま書き直された。

■ 変えたファイルがないこと（終わりの確かめ 08:56）
  ・ServiceSim の保留：0（cm status、cs:45 head、保留の行なし）。始めと同じ
  ・台帳：1032 行・md5 52e433713d4f7d41dfa8ccfb889492cd。始めと同じ
  ・Assets・tools・ProjectSettings・Packages：10/30 06:10 より後に変わったファイルなし
  ・Unity エディタは開いていない。新しく回していない。check-in していない
  ・一つだけ、報告の場所の外にファイルができた：simulation/v2_8/__pycache__/haizen_sim.cpython-312.pyc（08:23）。
    1回目の走らせで python -I を使ったため、PYTHONDONTWRITEBYTECODE が効かず、試算を読み込んだ時に python が自動で書いた
    キャッシュ。haizen_sim.py そのものは変わっていない（10/25 08:58 のまま）。前からあったかは分からないので、消していない。
    2回目からは -B をつけて書かせていない。

■ 書いた集計のスクリプトの全文

----- pay_flow_check.py（置いた場所：claude_code_reports/20261030_0823_staff_job_choice_files/pay_flow_check.py）-----
"""会計の流れが、片付けの仕事の入ってくる速さを決めているかを見る（2026-10-30・読むだけ）
片付けは会計が済むと頼まれる。会計が速く回れば、片付けの仕事も速く入ってくる。
  Unity：会計の仕事は、会計の人がレジの並ぶ点に着いた時に板に出る（GroupManager.SendPayerIfPossible）。
         前の会計が終わるより前に出ているので、板の優先1の中で古く、前の会計が終わるとすぐ取られる
  試算：会計のスタッフの頼みは、レジが空いてから出す（haizen_sim.py group の register.request のあと staff_job）。
         ほかの優先1の頼みの後ろに並ぶので、そのあいだレジは止まる
試算は中を変えず、staff_job を外からくるんで数えるだけ。Unity は groups.csv・timeline.csv を読むだけ。
使い方: python3 pay_flow_check.py <haizen_sim.py のフォルダ> <service_sim/unity のフォルダ>
"""
import csv, glob, os, statistics, sys
SIM_DIR, UNITY_DIR = sys.argv[1], sys.argv[2]
sys.path.insert(0, SIM_DIR)
import haizen_sim as h

W0, W1 = 60 * 60, 160 * 60      # 混んでいる時間（60〜160分）で数える
med = statistics.median


class S(h.Store):
    def __init__(self, *a, **k):
        super().__init__(*a, **k)
        self.pay_wait_staff = []    # レジを取ってから、会計のスタッフが付くまで（秒）
        self.pay_done = []          # 会計が済んだ時刻
        self.clear_done = []        # 片付けが済んだ時刻
        self.reg_busy = 0.0         # W0〜W1 にレジを持っていた秒（スタッフ待ちも入る）
        self.reg_paying = 0.0       # そのうち、スタッフが付いて会計していた秒

    def staff_job(self, priority, walk_m, work_s, waited=None):
        is_pay = priority == 1 and walk_m == 0 and work_s == self.p["pay_s"]
        if not is_pay:
            yield from super().staff_job(priority, walk_m, work_s, waited)
            if priority == 2:
                self.clear_done.append(self.env.now)
            return
        w = []
        yield from super().staff_job(priority, walk_m, work_s, w)
        now = self.env.now
        t_reg = now - work_s - w[0]
        self.pay_wait_staff.append(w[0])
        self.pay_done.append(now)
        self.reg_busy += max(0.0, min(now, W1) - max(t_reg, W0))
        self.reg_paying += max(0.0, min(now, W1) - max(now - work_s, W0))


def rate(ts):
    return sum(1 for t in ts if W0 <= t < W1) / ((W1 - W0) / 3600)


print("混んでいる・0台。60〜160分のあいだで数える（Unity 種10個・試算 40回の中央値）")
print("店 | 会計が済んだ組/時 U/S | 片付けが済んだ卓/時 U/S | 入ってきた−済んだ 卓/時 U/S | "
      "レジで会計のスタッフが付くまで（秒）U/S | レジを持っていた時間のうち会計していた割合 S | Unity 会計のスタッフ人数の平均")
for st in ["fam_p1", "fam_p3", "fam_p4", "fam_p0"]:
    s = h.STORES[st]
    arr, staff = s["arrivals"][2], s["staff"][1]
    cond = {"store": st, "staff": staff, "robots": 0, "arrivals_per_h": arr, "mode": "serve",
            "trays": 1, "fragile_by": "any", "registers": 1}
    sp, sc, sw, sr = [], [], [], []
    for seed in range(h.REPS):
        p = {**h.COMMON, **s, **cond}
        env = h.simpy.Environment()
        x = S(env, h.random.Random(seed), p)
        env.process(x.arrivals())
        env.process(x.window())
        env.run()
        sp.append(rate(x.pay_done)); sc.append(rate(x.clear_done))
        sw.append(statistics.mean(x.pay_wait_staff)); sr.append(x.reg_paying / max(1.0, x.reg_busy))
    up, uc, uw, us = [], [], [], []
    for f in sorted(glob.glob(os.path.join(UNITY_DIR, f"20261106_night_a{arr}_cs44_{st}_r0_s*_passon_sbret"))):
        g = list(csv.DictReader(open(os.path.join(f, "groups.csv"), encoding="utf-8")))
        tl = list(csv.DictReader(open(os.path.join(f, "timeline.csv"), encoding="utf-8")))
        pays = [float(r["pay_s"]) for r in g if r["pay_s"] not in ("", "-1")]
        up.append(rate(pays))
        # 片付けが済んだ数 ＝ 頼まれた数 − 汚れた卓（未着手＋片付け中）の増え方
        def dirty_at(t):
            prev = [r for r in tl if float(r["t_s"]) <= t]
            r = prev[-1]
            return int(r["tables_dirty_untaken"]) + int(r["tables_clearing"])
        posted = sum(1 for t in pays if W0 < t <= W1)
        uc.append((posted - (dirty_at(W1) - dirty_at(W0))) / ((W1 - W0) / 3600))
        rows = [r for r in tl if W0 <= float(r["t_s"]) < W1]
        us.append(statistics.mean(int(r["staff_pay"]) for r in rows))
    # Unity の「会計の仕事が板に出てから取られるまで」は台帳の pay_job_wait_mean_s
    L = {r["run_folder"]: r for r in csv.DictReader(open(os.path.join(UNITY_DIR, "unity_ledger_v1.csv"), encoding="utf-8"))}
    uw = [float(L[os.path.basename(f)]["pay_job_wait_mean_s"]) for f in
          sorted(glob.glob(os.path.join(UNITY_DIR, f"20261106_night_a{arr}_cs44_{st}_r0_s*_passon_sbret")))]
    print(f"{s['label']} | {med(up):.1f}/{med(sp):.1f} | {med(uc):.1f}/{med(sc):.1f} | "
          f"{med(up)-med(uc):.1f}/{med(sp)-med(sc):.1f} | {med(uw):.0f}（板に出てから。並ぶ点に着いた時から）/{med(sw):.0f}（レジを取ってから） | "
          f"{med(sr):.2f} | {med(us):.2f}")

----- trace_clear.py（置いた場所：claude_code_reports/20261030_0823_staff_job_choice_files/trace_clear.py）-----
"""③混んでいる0台の1回で、片付けを長く待った卓を追う（2026-10-30・読むだけ）
今までの groups.csv・timeline.csv・give_ups.csv を読むだけ。何も書き換えない。

記録に「片付けをスタッフが取った時刻」「終わった時刻」は無いので、次のように割り出す。
  頼んだ時刻   … groups.csv の left_s（会計が済んだ時刻。GroupManager.OnPaid で left_s を入れ、
                 次のコマで片付けの仕事を板に出す）
  取った時刻   … 片付けの仕事はみな優先2で、板の後ろに足され（ServiceOps.PostJob）、
                 同じ優先の中では出た順に取られる（ServiceOps.TakeNextJob）。
                 あきらめて板に戻った片付けも、卓を空けた片付けも、この回には無い（give_ups.csv で確かめる）。
                 だから t の時点で取られた片付けの数 ＝ t までに頼まれた数 − tables_dirty_untaken(t)。
                 k 番目に頼まれた片付けは、その数が k 以上になった最初の行（60秒ごと）までに取られた。
                 → 「前の行の時刻 〜 その行の時刻」の幅で出す
  終わった時刻 … 卓が空いたら、待っている組がいればそのコマで案内が始まる（GroupManager.SeatWaiting）。
                 だから同じ卓の次の組の guide_start_s が上の端。そのとき入口で待つ組がいれば、ほぼ終わった時刻そのもの
スタッフ4人それぞれの動きは記録に無い。timeline.csv の、仕事ごとのスタッフの人数（60秒ごと）を並べる。

使い方: python3 trace_clear.py <回のフォルダ> [卓の数=5]
"""
import csv, os, sys

RUN = sys.argv[1]
TOP = int(sys.argv[2]) if len(sys.argv) > 2 else 5

groups = list(csv.DictReader(open(os.path.join(RUN, "groups.csv"), encoding="utf-8")))
tl = list(csv.DictReader(open(os.path.join(RUN, "timeline.csv"), encoding="utf-8")))
gu = list(csv.DictReader(open(os.path.join(RUN, "give_ups.csv"), encoding="utf-8")))

staff_gu = [r for r in gu if r["who"] == "スタッフ"]
freed = [r for r in gu if r["action"] == "卓を空けた"]
print(f"回: {os.path.basename(RUN)}")
print(f"スタッフのあきらめ・移した: {len(staff_gu)} 件　卓を空けた: {len(freed)} 件"
      "（どちらも 0 なら、片付けは頼まれた順に取られたと見てよい）")

F = lambda x: float(x) if x not in ("", None) else -1.0
for g in groups:
    for k in ("arrive_s", "guide_start_s", "seated_s", "pay_s", "left_s"):
        g[k] = F(g[k])
done = sorted([g for g in groups if g["left_s"] >= 0], key=lambda g: (g["left_s"], int(g["group_id"])))
lefts = [g["left_s"] for g in done]

rows = [{k: (float(v) if k not in ("condition_id",) else v) for k, v in r.items()} for r in tl]


def posted_by(t):
    return sum(1 for x in lefts if x <= t)


# 各行で、取られた片付けの数
taken_n = [posted_by(r["t_s"]) - int(r["tables_dirty_untaken"]) for r in rows]

res = []
for k, g in enumerate(done, start=1):
    hi = next((i for i, n in enumerate(taken_n) if n >= k and rows[i]["t_s"] >= g["left_s"]), None)
    if hi is None:
        continue
    t_hi = rows[hi]["t_s"]
    t_lo = max(g["left_s"], rows[hi - 1]["t_s"]) if hi > 0 else g["left_s"]
    # 同じ卓の次の組
    nxt = [h for h in groups if h["table_id"] == g["table_id"] and h["guide_start_s"] > g["left_s"]]
    nxt = min(nxt, key=lambda h: h["guide_start_s"]) if nxt else None
    end = nxt["guide_start_s"] if nxt else None
    # 終わった時刻のころ、入口で待つ組がいたか
    q = None
    if end is not None:
        prev = [r for r in rows if r["t_s"] <= end]
        q = int(prev[-1]["waiting_groups"]) if prev else None
    res.append(dict(k=k, g=g, t_lo=t_lo, t_hi=t_hi, end=end, nxt=nxt, q=q, wait=(t_lo + t_hi) / 2 - g["left_s"]))

res.sort(key=lambda r: -r["wait"])
pick = res[:TOP]

m = lambda s: f"{s/60:6.1f}"
print(f"\n片付けを頼んだ数: {len(done)}　割り出せた数: {len(res)}")
print(f"片付けに付くまで（割り出し・幅の真ん中）の平均: {sum(r['wait'] for r in res)/len(res):.0f} 秒"
      "（summary.json の job_wait_mean_s 片付け と比べる）")

print(f"\n片付けを長く待った卓 {TOP} つ（時刻は店が開いてからの分）")
print("順 | 卓 | 席 | 組 | 客が帰った（会計済み） | スタッフが取った（幅） | 次の組の案内が始まった＝終わった時刻の上の端 | そのとき入口で待つ組 | 帰ってから取るまで（分） | 取ってから次の案内まで（分）")
for r in pick:
    g = r["g"]
    end = f"{m(r['end'])}" if r["end"] is not None else "  なし"
    span = f"{(r['end'] - r['t_hi'])/60:.1f}〜{(r['end'] - r['t_lo'])/60:.1f}" if r["end"] is not None else "-"
    print(f"{r['k']:3d} | T{int(g['table_id']):02d} | {g['table_seats']} | {g['group_id']} | {m(g['left_s'])} | "
          f"{m(r['t_lo'])}〜{m(r['t_hi'])} | {end} | {r['q']} | {(r['t_lo']-g['left_s'])/60:.1f}〜{(r['t_hi']-g['left_s'])/60:.1f} | {span}")

# そのあいだスタッフが何をしていたか（60秒ごとの人数の平均）
cols = [("staff_idle", "待機"), ("staff_guide", "案内"), ("staff_serve", "料理"), ("staff_call", "呼び出し"),
        ("staff_pay", "会計"), ("staff_clear", "片付け"), ("staff_rescue", "立ち往生")]
bcols = [("board_guide", "案内"), ("board_serve", "料理"), ("board_call", "呼び出し"), ("board_pay", "会計"), ("board_clear", "片付け")]
print("\n帰ってから取られるまでのあいだ、スタッフ4人が持っていた仕事（60秒ごとの人数の平均）と、板に残っていた仕事の数の平均")
print("卓 | 行の数 | " + " ".join(n for _, n in cols) + " || 板: " + " ".join(n for _, n in bcols) + " | 汚れた卓（未着手+片付け中） | 入口で待つ組")
for r in pick:
    g = r["g"]
    rr = [x for x in rows if g["left_s"] <= x["t_s"] <= r["t_hi"]]
    av = lambda c: sum(x[c] for x in rr) / len(rr)
    print(f"T{int(g['table_id']):02d} | {len(rr)} | " + " ".join(f"{av(c):.2f}" for c, _ in cols) + " || " +
          " ".join(f"{av(c):.2f}" for c, _ in bcols) +
          f" | {av('tables_dirty_untaken'):.1f}+{av('tables_clearing'):.1f} | {av('waiting_groups'):.1f}")

# 1分ごとの並び（いちばん長く待った卓）
r = pick[0]; g = r["g"]
print(f"\nいちばん長く待った卓 T{int(g['table_id']):02d} の、帰ってから取られるまでの1分ごとの様子")
print("分 | 待機 案内 料理 呼出 会計 片付け | 板: 案内 料理 呼出 会計 片付け | 汚れた卓 未着手/片付け中 | 入口で待つ組 | この卓より前に頼まれてまだ取られていない片付け")
for x in rows:
    if not (g["left_s"] - 60 <= x["t_s"] <= r["t_hi"]):
        continue
    ahead = sum(1 for j, lt in enumerate(lefts, start=1) if j < r["k"] and lt <= x["t_s"]) - \
            max(0, posted_by(x["t_s"]) - int(x["tables_dirty_untaken"]))
    print(f"{x['t_s']/60:5.0f} | " + " ".join(f"{int(x[c])}" for c, _ in cols[:6]) + " | " +
          " ".join(f"{int(x[c])}" for c, _ in bcols) +
          f" | {int(x['tables_dirty_untaken'])}/{int(x['tables_clearing'])} | {int(x['waiting_groups'])} | {max(0, ahead)}")

----- robot_staff_extra.py（置いた場所：claude_code_reports/20261030_0823_staff_job_choice_files/robot_staff_extra.py）-----
"""ロボットがいると案内・片付けが長くなる分を、記録から分ける（2026-10-30・読むだけ）
今までの summary.json・give_ups.csv・yields.csv・groups.csv を読むだけ。何も書き換えない。

Unity のスタッフの時間（summary.json の staff_time_s）は、仕事を持っているあいだの秒数を仕事の名前ごとに足したもの
（StaffAgent.Update → ServiceOps.AddStaffTime）。ロボットをよけて止まっている間・あきらめるまでの 60 秒も、
その仕事の時間に入る。
  案内 1回 ＝ いた所 → 待合まで歩く ＋ guide_s 45 秒 ＋ 待合 → 卓まで歩く
  片付け 1回 ＝ いた所 → 卓 ＋ clear_s 40 秒 ＋（卓 → 返却口 → 卓）× bus_trips … ＋ wipe_s 40 秒
なので「歩いている分」＝ 1回の時間 − 決まった作業の秒数。ロボットがいると増える分をこう分ける：
  あきらめ … give_ups.csv のスタッフの「あきらめた」1件につき staffGiveUpSec（60 秒）。そのあと仕事は板に戻り、
             取り直した人は最初の行き先から歩き直す（StaffAgent.Begin で行き先を組み直す）。歩き直しの分は下の「そのほか」に入る
  よけて止まった … yields.csv のスタッフの waited_s の合計。どの仕事のときかは記録に無いので、
             仕事ごとの「歩いている分」の割合で按分する（按分であって、記録ではない）
  そのほか … 残り。遠回り・歩き直し・人どうしのつかえ・ゆっくり歩く などで、記録からは分けられない
使い方: python3 robot_staff_extra.py <service_sim/unity のフォルダ> [店=fam_p2] [来客=36]
"""
import csv, glob, json, os, statistics, sys

U = sys.argv[1]
ST = sys.argv[2] if len(sys.argv) > 2 else "fam_p2"
ARR = sys.argv[3] if len(sys.argv) > 3 else "36"
GIVEUP_S = 60.0
FIXED = {"案内": 45.0, "片付け": 80.0}   # guide_s、clear_s + wipe_s（その店の bus_trips は歩く回数だけに効く）
med = statistics.median


def one(f):
    sm = json.load(open(os.path.join(f, "summary.json"), encoding="utf-8"))
    g = list(csv.DictReader(open(os.path.join(f, "groups.csv"), encoding="utf-8")))
    gu = list(csv.DictReader(open(os.path.join(f, "give_ups.csv"), encoding="utf-8")))
    yd = list(csv.DictReader(open(os.path.join(f, "yields.csv"), encoding="utf-8")))
    n = len(g)
    t = sm["staff_time_s"]
    out = {"groups": n}
    for job in ("案内", "片付け"):
        per = t.get(job, 0.0) / n
        gave = sum(1 for r in gu if r["who"] == "スタッフ" and r["job"] == job and r["action"] == "あきらめた")
        warp = sum(1 for r in gu if r["who"] == "スタッフ" and r["job"] == job and r["action"] == "移した")
        out[job] = per
        out[job + "_walk"] = per - FIXED[job]
        out[job + "_giveup_s"] = gave * GIVEUP_S / n
        out[job + "_gave"] = gave / n
        out[job + "_warp"] = warp / n
    # よけて止まった秒を、仕事ごとの「歩いている分」の割合で按分する。
    # 歩いている分の合計 ＝ 仕事を持っている時間 − 決まった作業の秒数
    #   （案内 45・片付け 80・会計 90 は1組に1回、料理は皿ごとに staff_place_s 10、立ち往生は1回 rescue_s 15。
    #    呼び出しの回数は summary に無いので、呼び出しの 30 秒は引かない＝歩いている分に入れたまま）
    staff_wait = sum(float(r["waited_s"]) for r in yd if r["who"].startswith("スタッフ"))
    busy = sum(v for k, v in t.items() if k != "待機")
    dish = sm["sim_like"].get("staff_dishes", 0)
    resc = sm["site_run"].get("rescues_done", 0)
    walk_busy = busy - (45.0 + 80.0 + 90.0) * n - 10.0 * dish - 15.0 * resc
    share = staff_wait / max(1.0, walk_busy)
    out["yield_total_per_group"] = staff_wait / n
    out["yield_share_busy"] = staff_wait / busy if busy else 0.0
    out["yield_share_walk"] = share
    for job in ("案内", "片付け"):
        out[job + "_yield_s"] = out[job + "_walk"] * share
    out["rescue_per_group"] = t.get("立ち往生", 0.0) / n
    out["walk_km_per_staff"] = sm["site_metrics"]["walk_km_per_staff"]
    out["staff_util"] = sm["site_metrics"]["staff_util"]
    out["robot_jams_s"] = sm["sim_like"].get("people_jam_total_s", 0.0) / n
    out["late_staff_dishes"] = sm["sim_like"].get("late_staff_dishes", 0) / n
    out["rescues"] = sm["site_run"].get("rescues_done", 0) / n
    out["staff_n"] = sm["timeline_check"]["staff"]
    out["walk_m_per_group"] = out["walk_km_per_staff"] * 1000 * out["staff_n"] / n
    return out


res = {}
for r in (0, 1, 2):
    fs = sorted(glob.glob(os.path.join(U, f"20261106_night_a{ARR}_cs44_{ST}_r{r}_s*_passon_sbret")))
    res[r] = [one(f) for f in fs]

keys = [("groups", "組の数", "{:.0f}"),
        ("案内", "案内1回（秒）", "{:.1f}"), ("案内_walk", "　うち歩いている分（秒）", "{:.1f}"),
        ("案内_giveup_s", "　あきらめた60秒の分（秒/組）", "{:.1f}"), ("案内_gave", "　あきらめた回数（回/組）", "{:.2f}"),
        ("案内_warp", "　移した回数（回/組）", "{:.2f}"), ("案内_yield_s", "　よけて止まった分（按分・秒/組）", "{:.1f}"),
        ("片付け", "片付け1回（秒）", "{:.1f}"), ("片付け_walk", "　うち歩いている分（秒）", "{:.1f}"),
        ("片付け_giveup_s", "　あきらめた60秒の分（秒/組）", "{:.1f}"), ("片付け_gave", "　あきらめた回数（回/組）", "{:.2f}"),
        ("片付け_warp", "　移した回数（回/組）", "{:.2f}"), ("片付け_yield_s", "　よけて止まった分（按分・秒/組）", "{:.1f}"),
        ("yield_total_per_group", "スタッフがよけて止まった秒（全部の仕事・秒/組）", "{:.1f}"),
        ("yield_share_busy", "　仕事を持っている時間のうちの割合", "{:.3f}"),
        ("yield_share_walk", "　歩いている分のうちの割合（按分に使う）", "{:.3f}"),
        ("rescue_per_group", "立ち往生を動かしに行った時間（秒/組）", "{:.1f}"),
        ("rescues", "立ち往生を動かした回数（回/組）", "{:.2f}"),
        ("late_staff_dishes", "2分待ってスタッフが運んだ皿（皿/組）", "{:.2f}"),
        ("robot_jams_s", "人のつかえの時間（秒/組・客も入る）", "{:.1f}"),
        ("walk_m_per_group", "スタッフが歩いた距離（m/組）", "{:.0f}"),
        ("staff_util", "スタッフの稼働率", "{:.3f}")]
print(f"店 {ST}・来客 {ARR} 組/時・各台数 種 {len(res[0])}/{len(res[1])}/{len(res[2])} 個の中央値")
print("項目 | 0台 | 1台 | 2台 | 2台−0台")
for k, name, fmt in keys:
    v = [med([x[k] for x in res[r]]) for r in (0, 1, 2)]
    print(f"{name} | " + " | ".join(fmt.format(a) for a in v) + f" | {fmt.format(v[2]-v[0])}")

# 2台−0台の増えた分を分ける
print("\n2台で1回あたり増えた分の分け方（中央値どうしの差。秒）")
for job in ("案内", "片付け"):
    d = med([x[job] for x in res[2]]) - med([x[job] for x in res[0]])
    gu = med([x[job + "_giveup_s"] for x in res[2]]) - med([x[job + "_giveup_s"] for x in res[0]])
    yl = med([x[job + "_yield_s"] for x in res[2]]) - med([x[job + "_yield_s"] for x in res[0]])
    print(f"{job}: 増えた {d:.1f} ＝ あきらめるまでの60秒 {gu:.1f} ＋ よけて止まった（按分） {yl:.1f} ＋ そのほか {d-gu-yl:.1f}")

----- compare_timeline.py（置いた場所：claude_code_reports/20261030_0823_staff_job_choice_files/compare_timeline.py）-----
"""混んでいる・0台で、スタッフの手と片付け待ちの時間の流れを、Unity と試算で並べる（2026-10-30・読むだけ）
試算（haizen_sim.py v2.8）は中を変えず、外から1分ごとに数えるだけ。Unity は今までの timeline.csv・summary.json を読むだけ。
07:44 の版（20261030_0744_staff_job_choice_files）を写して1か所直した：試算の run_once と同じく window() も走らせる。
  07:44 の版は window() を走らせていなかったので、試算の「全員が帰った」の合図（finished）が来ず、
  1分ごとに数える sampler が止まらないまま回り続けていた（compare_timeline.txt が空のままだった理由）。
使い方: python compare_timeline.py <haizen_sim.py のフォルダ> <service_sim/unity のフォルダ>
"""
import csv, glob, json, os, statistics, sys
SIM_DIR, UNITY_DIR = sys.argv[1], sys.argv[2]
sys.path.insert(0, SIM_DIR)
import haizen_sim as h

STEP = 60          # Unity の timeline と同じ 60 秒ごと
BIN = 20 * 60      # 表は 20 分ごとにまとめる


class S(h.Store):
    """試算の店。決まりは変えず、1分ごとの様子と片付けの待ちを外から数える"""
    def __init__(self, *a, **k):
        super().__init__(*a, **k)
        self.rows = []
        self.cw = []            # 片付けの依頼からスタッフが付くまで（秒）
        self.clear_dur = []     # 片付けでスタッフの手がふさがった秒数
        self.env.process(self.sampler())

    def sampler(self):
        env = self.env
        while not self.finished.triggered:
            busy = len(self.staff.users)
            busy2 = sum(1 for u in self.staff.users if u.priority == 2)
            q1 = sum(1 for r in self.staff.queue if r.priority <= 1)
            q2 = sum(1 for r in self.staff.queue if r.priority == 2)
            self.rows.append({"t": env.now, "busy": busy, "busy_clear": busy2, "q_p1": q1, "q_clear": q2,
                              "dirty": self.dirty, "dirty_untaken": max(0, self.dirty - busy2),
                              "waiting": len(self.tables.queue), "reg_q": len(self.register.queue)})
            yield env.timeout(STEP)

    def staff_job(self, priority, walk_m, work_s, waited=None):
        if priority == 2:
            w = []
            t0 = self.env.now
            yield from super().staff_job(priority, walk_m, work_s, w)
            self.cw.append(w[0])
            self.clear_dur.append(self.env.now - t0 - w[0])
        else:
            yield from super().staff_job(priority, walk_m, work_s, waited)


def sim_runs(st, arr, staff):
    s = h.STORES[st]
    cond = {"store": st, "staff": staff, "robots": 0, "arrivals_per_h": arr, "mode": "serve",
            "trays": 1, "fragile_by": "any", "registers": 1}
    out = []
    for seed in range(h.REPS):
        p = {**h.COMMON, **s, **cond}
        env = h.simpy.Environment()
        x = S(env, h.random.Random(seed), p)
        env.process(x.arrivals())
        env.process(x.window())      # run_once と同じ。これが無いと finished が来ない
        env.run()
        out.append(x)
    return out


def unity_runs(st, arr):
    out = []
    for f in sorted(glob.glob(os.path.join(UNITY_DIR, f"20261106_night_a{arr}_cs44_{st}_r0_s*_passon_sbret"))):
        tl = list(csv.DictReader(open(os.path.join(f, "timeline.csv"), encoding="utf-8")))
        sm = json.load(open(os.path.join(f, "summary.json"), encoding="utf-8"))
        g = list(csv.DictReader(open(os.path.join(f, "groups.csv"), encoding="utf-8")))
        out.append((tl, sm, g))
    return out


def binned(rows_per_run, key, tmax):
    """20 分ごとの平均（全部の回をまとめる）"""
    res = []
    for b0 in range(0, tmax, BIN):
        v = [r[key] for rows in rows_per_run for r in rows if b0 <= r["t"] < b0 + BIN]
        res.append(statistics.mean(v) if v else float("nan"))
    return res


m = statistics.median
for st in ["fam_p1", "fam_p3", "fam_p4", "fam_p0"]:
    s = h.STORES[st]
    arr, staff = s["arrivals"][2], s["staff"][1]
    sims = sim_runs(st, arr, staff)
    uns = unity_runs(st, arr)
    # Unity の timeline を同じ形にそろえる
    urows = []
    for tl, sm, g in uns:
        rr = []
        for t in tl:
            idle = int(t["staff_idle"]); clr = int(t["staff_clear"])
            rr.append({"t": float(t["t_s"]), "busy": staff - idle, "busy_clear": clr,
                       "q_p1": int(t["board_guide"]) + int(t["board_serve"]) + int(t["board_call"]) + int(t["board_pay"]),
                       "q_clear": int(t["board_clear"]),
                       "dirty": int(t["tables_dirty_untaken"]) + int(t["tables_clearing"]),
                       "dirty_untaken": int(t["tables_dirty_untaken"]),
                       "waiting": int(t["waiting_groups"]), "pay_line": int(t["pay_in_line"]) + int(t["pay_waiting_seat"]),
                       "busy_pay": int(t["staff_pay"])})
        urows.append(rr)
    srows = [x.rows for x in sims]
    tmax = 4 * 3600
    print(f"\n=== {s['label']}（{st}・来客 {arr} 組/時・スタッフ {staff} 人・0台）　Unity 種{len(uns)}個 / 試算 {len(sims)}回 ===")
    print("20分ごと | 手がふさがった人数 U/S | うち片付け U/S | 板の優先1の仕事 U/S | 板の片付け U/S | 汚れた卓 U/S | 入口で待つ組 U/S")
    cols = {k: (binned(urows, k, tmax), binned(srows, k, tmax)) for k in ["busy", "busy_clear", "q_p1", "q_clear", "dirty", "waiting"]}
    for i, b0 in enumerate(range(0, tmax, BIN)):
        line = f"{b0//60:3d}-{(b0+BIN)//60:3d}分"
        for k in ["busy", "busy_clear", "q_p1", "q_clear", "dirty", "waiting"]:
            u, sv = cols[k][0][i], cols[k][1][i]
            line += f" | {u:4.2f}/{sv:4.2f}"
        print(line)
    # 全員がふさがっていた分の割合（来客がある 0〜120 分）
    def full_share(rows_per_run):
        v = [r["busy"] >= staff for rows in rows_per_run for r in rows if r["t"] < 7200]
        return sum(v) / len(v)
    def last_minute(rows_per_run):
        return m([max(r["t"] for r in rows) / 60 for rows in rows_per_run])
    u_clear_job = m([sm["staff_time_s"].get("片付け", 0) / max(1, len(g)) for tl, sm, g in uns])
    s_clear_job = m([statistics.mean(x.clear_dur) for x in sims])
    u_pay_job = m([sm["staff_time_s"].get("会計", 0) / max(1, len(g)) for tl, sm, g in uns])
    print(f"来客中（0〜120分）に全員の手がふさがっていた分の割合 U/S: {full_share(urows):.2f}/{full_share(srows):.2f}")
    print(f"片付け1回でスタッフの手がふさがった秒数（中央値） U/S: {u_clear_job:.0f}/{s_clear_job:.0f}")
    print(f"会計1回でスタッフの手がふさがった秒数（中央値） U/S: {u_pay_job:.0f}/90")
    print(f"片付けに付くまで（秒・中央値） U/S: {m([sm['job_wait_mean_s'].get('片付け', 0) for tl, sm, g in uns]):.0f}/{m([statistics.mean(x.cw) for x in sims]):.0f}")
    print(f"組の数（中央値） U/S: {m([len(g) for tl, sm, g in uns])}/{m([x.done for x in sims])}　最後の客が出た分（中央値） U/S: {last_minute(urows):.0f}/{last_minute(srows):.0f}")
    up = [statistics.mean(r["busy_pay"] for r in rows if 60*60 <= r["t"] < 120*60) for rows in urows]
    print(f"Unity：60〜120分に会計の仕事を持っていたスタッフの人数（平均の中央値）: {m(up):.2f}")
```

### 違いは「会計のスタッフを呼ぶタイミング」だった

| 項目 | 試算 v2.8 | Unity |
|---|---|---|
| 仕事の優先順 | 0 ロボットを動かす → 1 案内・配膳・呼び出し・会計 → 2 片付け。同じ優先なら先に頼まれた順 | 同じ |
| 会計のスタッフを呼ぶとき | レジの順番が来てから。ほかの優先1の後ろに並び、そのあいだレジが止まる（③でレジを持っていた時間の12%） | お客さんがレジの並ぶ点に着いたとき。前の会計が終わるとすぐ取られ、会計のスタッフがほぼいつも1人レジにいる |
| 会計が済む組（③ 60〜160分） | 1時間に34.8組 | 1時間に38.7組 |
| 片付け1回 | 123秒 | 135秒（前の仕事が終わった所から歩き出すため） |
| たまる所 | 卓で会計を待つ（約15分）。卓は客が座ったまま埋まる | 汚れた卓（③で1時間に約6卓ずつたまる） |

同じ詰まりが、試算では「会計待ち」、Unity では「片付け待ち」として出ていました。前に記録だけにしていた「詰まる所が試算はレジ、Unity は片付け」というずれの正体は、これでした。


## 作業ログ ― フロントの実感で、試算を Unity の会計の頼み方に合わせると決めた

回し方の決まりはサイトの試算が正なので、どちらにそろえるかで直す方向が逆になります。

| 案 | 中身 | 影響 |
|---|---|---|
| A | 試算を Unity の呼び方に変える（客がレジに並んだら、すぐスタッフが行く） | サイトの試算の数字が変わる |
| B | Unity を試算の呼び方に合わせる | サイトの数字は変わらない |
| C | 記録だけにする | 何も変えない |

どちらにするかは、お客さんがレジに来たとき、片付けの途中でも会計に行っていたかどうか、フロントをしていたころの動き方で決めることにしました。

> フロントスタッフやっていた頃、基本はAでレジや案内を優先に仕事していたね。

A に決めました。


## 作業ログ ― 試算 v2.9：会計のスタッフをレジに並んだ順に付ける

### 1回目の直しでは、会計がほとんど速くならなかった

最初は、会計のスタッフの頼みを「レジに並んだ時刻」にさかのぼらせるだけにしました。SimPy の `PriorityResource`（優先順と頼んだ時刻の順に、手の空いた人を割り当てる仕組み）は、頼みを（優先、頼んだ時刻）の順に並べます。頼んだ時刻を古くすれば、同じ優先の案内や配膳より先に会計が取られるはず、という考えです。

ところが、Unity の54条件で比べると、効きは小さいままでした。③の普通・0台で、会計待ちは 12.3分 → 9.9分（Unity は4.5分）。1時間に済む会計も 35.0組 → 35.9組で、Unity の 38.7組に届きません。

### 会計が済む速さと、レジの使われ方を測った throughput_check.py・register_idle_check.py

```python
import sys, statistics, importlib
sys.path.insert(0, sys.argv[1]); import haizen_sim as h
class S(h.Store):
    def __setattr__(s, n, v):
        if n == "dirty" and hasattr(s, "dirty") and v > s.dirty: s.__dict__.setdefault("paid", []).append(s.env.now)
        object.__setattr__(s, n, v)
for arr in (42, 54):
    c = {'store':'fam_p1','staff':4,'robots':0,'arrivals_per_h':arr,'mode':'serve','trays':1,'fragile_by':'any','registers':1}
    thr, pw = [], []
    for seed in range(40):
        p = {**h.COMMON, **h.STORES['fam_p1'], **c}; env = h.simpy.Environment(); s = S(env, h.random.Random(seed), p); env.process(s.arrivals()); env.run()
        thr.append(sum(3600 <= t < 9600 for t in s.paid) / (6000/3600)); pw.append(statistics.mean(s.pay_waits))
    print(sys.argv[1], arr, "会計/時(60-160分)", round(statistics.mean(thr),1), "会計待ち", round(statistics.mean(pw),1))
```

```python
import sys, statistics
sys.path.insert(0, sys.argv[1]); import haizen_sim as h
c = {'store':'fam_p1','staff':4,'robots':0,'arrivals_per_h':42,'mode':'serve','trays':1,'fragile_by':'any','registers':1}
p = {**h.COMMON, **h.STORES['fam_p1'], **c}
env = h.simpy.Environment(); s = h.Store(env, h.random.Random(0), p)
reg = s.register; log = []
def mon():
    while True:
        log.append((env.now, len(reg.users), len(reg.queue), len(s.staff.users), len(s.staff.queue), [r.priority for r in s.staff.queue]))
        yield env.timeout(5)
env.process(s.arrivals()); env.process(mon()); env.run(until=9600)
w = [x for x in log if 3600 <= x[0] < 9600]
print("レジが使われている割合", round(sum(x[1] for x in w)/len(w),2), "レジの列の平均", round(statistics.mean(x[2] for x in w),1), "スタッフ稼働", round(statistics.mean(x[3] for x in w),2))
```

1回目の直しのあとで、レジを取ってから会計のスタッフが付くまでを数えると、すぐ（0秒で）付いたのは33%だけで、平均9.8秒、最長44秒待っていました。頼みは確かに列の先頭に並んでいるのに、会計を終えたスタッフが次の会計を取っていません。

### SimPy の出来事の順番で、次の組の頼みより先にスタッフの手を放していた

SimPy では、同じ時刻の出来事は、予定に入った順に処理されます。1回目の直しでは、会計が終わると、レジを空ける → スタッフの手を放す、の順に書いていました。ところが、レジを空けたことで次の組がレジに入るのは、ひとつ後の出来事になります。そのため、次の組の会計の頼みが並ぶ前にスタッフの手が放され、そのスタッフは別の仕事を取っていました。

レジを空けたことが処理されるのを待ち（`yield self.register.release(rq)`）、さらに1つ待って（`yield env.timeout(0)`）次の組の頼みが並んでから、スタッフの手を放すように直しました。会計を終えたスタッフが、そのまま次の会計を取ります。Unity の「会計の係がほぼいつも1人レジにいる」と同じ形になりました。

- ねらい：お客さんがレジに並んだら、片付けの途中でもスタッフが会計に行く、というフロントの動き方を試算に入れる
- 変わったこと：レジを取ってから会計のスタッフが付くまでが 0秒だった割合が 33% → 98%（平均 9.8秒 → 0.5秒）。③の普通・0台で、1時間に済む会計は 35.0組 → 38.1組（Unity 38.7組）、会計待ちは 12.3分 → 4.8分（Unity 4.5分）
- 見込める効果：混んだ日に詰まる場所が、試算でも「片付け」として出るようになる。どの店で何人いれば回るかを、店の動き方に近い形で答えられる（下の54条件の表で確かめた）

### haizen_sim.py の v2.8 から v2.9 への差分

```diff
--- /sessions/rcw-01levexvnksg51fwlydnyzff/mnt/PixelstoMotors/デジタルツイン学習R2R2R/サービスロボット導入シミュレーション/simulation/v2_8/haizen_sim.py	2026-10-24 23:58:07.432138539 +0000
+++ /sessions/rcw-01levexvnksg51fwlydnyzff/mnt/PixelstoMotors/デジタルツイン学習R2R2R/サービスロボット導入シミュレーション/simulation/v2_9/haizen_sim.py	2026-10-30 00:13:52.903726056 +0000
@@ -1,5 +1,16 @@
 """
-サービスロボット導入シミュレーション #1 配膳ロボット（試作 v2.4）
+サービスロボット導入シミュレーション #1 配膳ロボット（試作 v2.9）
+
+v2.9 で直したこと（2026-10-30。エイイチと決めた：フロントの実感では、レジと案内を優先して仕事をしていた）
+  会計のスタッフを頼む時刻を「客が食べ終えてレジに並んだとき」にした。v2.8 までは、レジの順番が来てから
+  スタッフを頼んだので、ほかの優先1の仕事（案内・料理・呼び出し）の後ろに並び、そのあいだレジが止まっていた。
+  v2.9 では、会計の頼みはレジに並んだ時刻の古さで並ぶ（優先は1のまま）。スタッフが会計に付くのは、
+  これまでどおりレジが空いてから（レジ1つにつき1組ずつ・pay_s 秒）。会計が終わると先にレジを空け、
+  次の組の会計の頼みが並んでからスタッフの手を放すので、レジに並ぶ組がいれば会計の係がそのまま次を取る。
+  Unity（ServiceSim）の頼み方と同じ（依頼31の調べ。Unity は会計のスタッフがほぼいつも1人レジにいる）。
+  会計が速く回るぶん、片付けの仕事が速く入ってくる。乱数の使い方は変えていない。
+
+（以下、v2.4 の説明）
 
 v2.4 で直したこと（2026-10-22。動画の台本を codex で検証したときに見つかった2点。直し方はエイイチと決めた）
   1. 入店待ちの終わりを「案内されて卓に着いたとき」にした。v2.3 までは、案内したスタッフが説明（guide_s）を
@@ -95,7 +106,7 @@
     "util_sample_s": 10,      # 手がふさがっている人数・台数を数える間隔（秒）
     "max_extra_min": 600,     # 数える時間のあと、追いかけるのを打ち切る上限（分）。店が回らない条件の保険
     "robot_first_wait_s": 120,  # v2.7 ロボット優先：料理はロボットを待つ。これだけ待っても来なければスタッフが運ぶ（秒）
-    "model": "v2.8",          # モデルの版（前の版の結果を使い回さないための印）
+    "model": "v2.9",          # モデルの版（前の版の結果を使い回さないための印）
 }
 
 # ---------------------------------------------------------------
@@ -203,6 +214,17 @@
     return rng.triangular(lo, hi, mode)
 
 
+class _AgedRequest(simpy.resources.resource.PriorityRequest):
+    """v2.9：頼んだ時刻を since にさかのぼらせたスタッフの頼み（同じ優先の中で、since の古い順に並ぶ）"""
+    def __init__(self, resource, priority, since):
+        self.priority = priority
+        self.preempt = False
+        self.time = since
+        self.usage_since = None
+        self.key = (self.priority, self.time, not self.preempt)
+        simpy.resources.resource.Request.__init__(self, resource)
+
+
 class Store:
     def __init__(self, env, rng, p):
         self.env, self.rng, self.p = env, rng, p
@@ -276,9 +298,11 @@
             self.finished.succeed()
 
     # ---- スタッフの仕事（priority 0=ロボット救出, 1=配膳・案内・会計, 2=下げ膳）
-    def staff_job(self, priority, walk_m, work_s, waited=None):
+    def staff_job(self, priority, walk_m, work_s, waited=None, since=None):
         t0 = self.env.now
-        with self.staff.request(priority=priority) as req:
+        # v2.9：since があれば、その時刻に頼んだものとして並ぶ（会計は、レジに並んだ時刻の古さで並ぶ）
+        req0 = _AgedRequest(self.staff, priority, since) if since is not None else self.staff.request(priority=priority)
+        with req0 as req:
             yield req
             if waited is not None:
                 waited.append(self.env.now - t0)   # 手が空くのを待った時間（案内待ち）
@@ -525,10 +549,18 @@
         if extras:
             yield simpy.AllOf(env, extras)
         # v2.8 会計：卓に座ったままレジの順番を待ち、順番が来たらスタッフの手が空くのを待って会計する
+        # v2.9 会計のスタッフの頼みは、レジに並んだ時刻（p0）の古さで並ぶ。会計が終わったら先にレジを空け、
+        #      次にレジに入った組の頼みが並んでからスタッフの手を放す（会計の係がそのまま次の会計を取れる。Unity と同じ）
         p0 = env.now
-        with self.register.request() as rq:
-            yield rq
-            yield env.process(self.staff_job(1, 0, p["pay_s"]))
+        rq = self.register.request()
+        yield rq
+        sreq = _AgedRequest(self.staff, 1, p0)
+        yield sreq
+        self.staff_busy_s += p["pay_s"]
+        yield env.timeout(p["pay_s"])
+        yield self.register.release(rq)      # レジを空ける（次の組がレジに入る）
+        yield env.timeout(0)                 # 次の組の会計の頼みが並ぶのを待ってから
+        self.staff.release(sreq)             # スタッフの手を放す
         if counted:
             self.pay_waits.append((env.now - p0 - p["pay_s"]) / 60)
         self._acc()
```

### 試算 v2.9 の haizen_sim.py の全文

```python
"""
サービスロボット導入シミュレーション #1 配膳ロボット（試作 v2.9）

v2.9 で直したこと（2026-10-30。エイイチと決めた：フロントの実感では、レジと案内を優先して仕事をしていた）
  会計のスタッフを頼む時刻を「客が食べ終えてレジに並んだとき」にした。v2.8 までは、レジの順番が来てから
  スタッフを頼んだので、ほかの優先1の仕事（案内・料理・呼び出し）の後ろに並び、そのあいだレジが止まっていた。
  v2.9 では、会計の頼みはレジに並んだ時刻の古さで並ぶ（優先は1のまま）。スタッフが会計に付くのは、
  これまでどおりレジが空いてから（レジ1つにつき1組ずつ・pay_s 秒）。会計が終わると先にレジを空け、
  次の組の会計の頼みが並んでからスタッフの手を放すので、レジに並ぶ組がいれば会計の係がそのまま次を取る。
  Unity（ServiceSim）の頼み方と同じ（依頼31の調べ。Unity は会計のスタッフがほぼいつも1人レジにいる）。
  会計が速く回るぶん、片付けの仕事が速く入ってくる。乱数の使い方は変えていない。

（以下、v2.4 の説明）

v2.4 で直したこと（2026-10-22。動画の台本を codex で検証したときに見つかった2点。直し方はエイイチと決めた）
  1. 入店待ちの終わりを「案内されて卓に着いたとき」にした。v2.3 までは、案内したスタッフが説明（guide_s）を
     終えて受け渡し口まで戻るときまで数えていた。説明の時間と戻りの時間は、スタッフの手がふさがる時間には
     これまでどおり入れ、お客さんの流れ（注文を待つ）もこれまでどおりスタッフが戻ってから進める。
  2. 「下げ膳にも使う」（bus）で、ロボットがスタッフより先に卓へ行き、皿を載せる前に帰っていたのを直した。
     スタッフが片付けに向かうとき、空いているロボットがいれば一緒に卓へ向かう。ロボットはスタッフが皿を
     載せ終えるまで卓で待ち、それから洗い場へ戻る（待っている間もロボットはふさがる）。空いていなければ人だけで下げる。
  上げ膳だけ（serve）・食事中に集めて回る（patrol）は、入店待ち（と、その内訳の案内）のほかは v2.3 と同じ数字になる（図面のある店も同じ）。

飲食店のピーク時間を SimPy で再現する。

図面のある店（v2.3）
  標準ファミレスの中に、店の形（図面）から作った店を6つ足した（store が fam_p0〜fam_p5）。
  卓の数と、卓ごとの厨房（料理の受け渡し口）から卓までの距離は店の形から取る（layouts/ の *_analysis.json。
  正は p2m-site の public/sim/layouts/）。来た組には、卓ごとの距離の中から1つを選んで使う。
  来客のペースとスタッフの人数は、標準ファミレス（35卓）を卓の数に合わせて増減させる。それ以外は標準ファミレスと同じ。
  今までの5つの店は、乱数の使い方も含めて何も変えていない（数字は v2.2 と同じ）。

入店待ちの内訳と「店が回らない」（v2.2）
  入店待ちは、店に来てから案内されて卓に着くまで（entry_wait_mean。v2.4 で終わりを直した）。
  そのうち、満席で待った時間（full_wait_mean）と、席は空いているのに片付け（下げ膳・卓拭き）や
  案内が終わらず待った時間（staff_wait_mean）に分ける。staff_wait_mean が NG_MIN 分以上なら「店が回らない」。
  片付け待ちは、客が帰ってから下げ膳が終わるまでの卓の数だけ、入口の列の前から数えた組が
  「席はあるのに待っている」とみなして数える。乱数の使い方は変えていないので、ほかの数字は v2 と同じ。

時間の区切り方（v2.1）
  ページに出す数字（ピークの回し方）：空の店に peak_min 分だけ選んだペースで来客し、全員が帰るまで回す。
    ランチ・ディナーのように、混む時間は限られている前提。v2 と同じで、数字も v2 と同じになる。
  回るかの確かめ（--sustain。v2.2 のページでは使わない）：空の店から始め、来客は選んだペースのまま止めない。
    最初の warmup_min 分は数えず、そのあとの peak_min 分に来た組を、卓が空くまで追いかける。
    その時間のスタッフの手がふさがっている割合（staff_util）を出し、STAFF_FULL 以上なら「店が回らない」とする。
    入店待ちの延び方（seat_wait_trend）は、卓の数を超える客が来ると人手に関係なく延びるので、判定には使わない
    （混雑は多くの店で卓の数を超えている。そのときの行列は、ピーク2時間の入店待ちの数字に出ている）。
店のタイプ（5種）、ロボットの使い方（3種）、1往復で回る卓の数（段の数）、
倒れやすい物（デザート・ドリンク）を誰が運ぶか、を切り替えて比べられる。
数字はすべて仮置き。公開事例・メーカー仕様・観察で順に置き換える。

SimPy が扱うのは「店の図面がなくても答えられる問い」（時間・順番・数）だけ。
通路での鉢合わせ・接触・揺れ・こぼれは Unity で扱い、その結果を
「一時停止」「立ち往生」の数字や、ロボットに載せる皿の決まりとしてここに入れる。

ロボットの使い方
  serve   : 上げ膳だけ。下げ膳は人がやる（ガスト型）
  bus     : 下げ膳にも使う。皿はスタッフがロボットに載せる（サイゼリヤ・ブロンコビリー型）
  patrol  : 食事中に席を回り、客が空いた皿を載せる（焼肉の和民・かみむら牧場型）

使い方:
    pip install simpy
    python haizen_sim.py            # 総当たりを回して results.json を書く
    python haizen_sim.py --main     # よく使う人数・台数（0〜3台）だけ先に回す
    途中経過は results_partial.json に保存され、止まっても続きから回る
    python haizen_sim.py --one      # 1条件だけ回して結果を表示

    python haizen_sim.py --sustain  # 回るかの確かめを総当たりで回して results_sustain.json を書く

依存: Python 3.10 以降 / simpy 4.1
"""
import json
import random
import statistics
import sys
from itertools import product
from multiprocessing import Pool

import simpy

# ---------------------------------------------------------------
# 共通の前提（すべて仮）
# ---------------------------------------------------------------
COMMON = {
    "staff_speed": 1.0,       # スタッフが歩く速さ（m/秒）
    "robot_speed": 0.5,       # ロボットが店内を走る速さ（m/秒）
    "guide_s": 45,            # 案内（歩く時間は別）
    "staff_place_s": 10,      # スタッフが皿を置く
    "robot_handoff_s": 25,    # 客がロボットから皿を取る
    "pay_s": 90,              # 会計
    "call_s": 30,             # 呼び出し対応（歩く時間は別）
    "clear_s": 40,            # テーブルの皿をまとめる
    "wipe_s": 40,             # 卓を拭いてセットする
    "load_s": 15,             # まとめた皿をロボットに載せる
    "guest_load_s": 20,       # 客が空いた皿をロボットに載せる（巡回回収）
    "pause_rate": 0.3,        # 一時停止：1往復で、人が前を横切って止まる確率（自然に解ける）
    "pause_s": (3, 10),       # 一時停止の長さ（秒）
    "stuck_rate": 0.01,       # 立ち往生：1往復で、スタッフが動かしに行かないと解けない確率
    "rescue_s": 15,           # スタッフがロボットを動かす作業
    "hop_m": 3,               # 1往復で複数卓を回るとき、卓から次の卓への寄り道（m）
    "fragile_cook": (2, 4, 7),  # デザート・ドリンクができるまで（分）
    "cold_min": 3,            # これを超えて届いた皿を「待たせた皿」とする
    "dish_extra_m": 0,        # 洗い場までの追加距離（店のタイプで上書き）
    "sustain": False,         # True：回るかの確かめ（来客を止めない回し方）
    "warmup_min": 60,         # 回るかの確かめで、数えない慣らしの時間（分）
    "util_sample_s": 10,      # 手がふさがっている人数・台数を数える間隔（秒）
    "max_extra_min": 600,     # 数える時間のあと、追いかけるのを打ち切る上限（分）。店が回らない条件の保険
    "robot_first_wait_s": 120,  # v2.7 ロボット優先：料理はロボットを待つ。これだけ待っても来なければスタッフが運ぶ（秒）
    "model": "v2.9",          # モデルの版（前の版の結果を使い回さないための印）
}

# ---------------------------------------------------------------
# 店のタイプ（すべて仮）
#   dist_m        : 厨房（洗い場）から卓までの距離の幅
#   first_dishes  : 最初の注文の皿。料理ができるまで（最短, 最頻, 最長 分）
#   extra_orders  : 食事中の追加注文の回数（食べ放題）
#   bus_trips     : 下げ膳を人だけでやるときの往復回数（皿の量）
#   dish_extra_m  : 洗い場が厨房の受け渡し口よりさらに奥にあるときの追加距離（ホテル小倉の事例）
#   fragile       : 1組あたりの倒れやすい物（デザート・パフェ・ドリンク）の数の平均
#   staff         : 最初に見せる人数
#   staff_all     : 「その他」で選べる人数（総当たりはこちら）
#   arrivals      : 来客ペース（空いている・普通・混雑）
#   registers     : 最初に見せるレジの数（v2.8）。registers_all は選べる数（総当たりはこちら）
# ---------------------------------------------------------------
STORES = {
    "small": {
        "label": "小型店", "tables": 18, "dist_m": (4, 12), "peak_min": 120,
        "first_dishes": [(4, 7, 11), (6, 9, 14)], "extra_orders": 0, "extra_cook": (3, 5, 8),
        "eat_min": (15, 25, 40), "call_rate": 0.4, "bus_trips": 1, "fragile": 0.3,
        "staff": [1, 2, 3], "staff_all": [1, 2, 3, 4, 5], "arrivals": [15, 22, 30],
        "registers": 1, "registers_all": [1, 2],
    },
    "family": {
        "label": "標準ファミレス", "tables": 35, "dist_m": (6, 22), "peak_min": 120,
        "first_dishes": [(5, 8, 12), (8, 12, 18)], "extra_orders": 0, "extra_cook": (3, 5, 8),
        "eat_min": (20, 30, 45), "call_rate": 0.6, "bus_trips": 2, "fragile": 0.5,
        "staff": [2, 3, 4], "staff_all": [1, 2, 3, 4, 5, 6, 7], "arrivals": [25, 35, 45],
        "registers": 1, "registers_all": [1, 2],
    },
    "large": {
        "label": "大型店", "tables": 60, "dist_m": (8, 35), "peak_min": 120,
        "first_dishes": [(5, 8, 12), (8, 12, 18)], "extra_orders": 0, "extra_cook": (3, 5, 8),
        "eat_min": (20, 30, 45), "call_rate": 0.6, "bus_trips": 2, "fragile": 0.5,
        "staff": [4, 5, 6], "staff_all": [2, 3, 4, 5, 6, 7, 8, 9, 10], "arrivals": [45, 60, 75],
        "registers": 2, "registers_all": [1, 2],
    },
    "hotel": {
        "label": "洗い場が遠い店", "tables": 30, "dist_m": (6, 20), "dish_extra_m": 30, "peak_min": 120,
        "first_dishes": [(5, 8, 12), (8, 12, 18)], "extra_orders": 2, "extra_cook": (4, 6, 10),
        "eat_min": (30, 45, 60), "call_rate": 0.6, "bus_trips": 2, "fragile": 0.6,
        "staff": [2, 3, 4], "staff_all": [1, 2, 3, 4, 5, 6, 7], "arrivals": [16, 22, 28],
        "registers": 1, "registers_all": [1, 2],
    },
    "buffet": {
        "label": "焼肉食べ放題", "tables": 35, "dist_m": (6, 22), "peak_min": 150,
        "first_dishes": [(2, 4, 8), (3, 5, 9), (4, 7, 12)], "extra_orders": 5, "extra_cook": (2, 4, 7),
        "eat_min": (60, 80, 90), "call_rate": 0.8, "bus_trips": 3, "fragile": 1.5,
        "staff": [2, 3, 4], "staff_all": [1, 2, 3, 4, 5, 6, 7], "arrivals": [14, 20, 26],
        "registers": 1, "registers_all": [1, 2],
    },
}

# ---------------------------------------------------------------
# 図面のある店（v2.3）：標準ファミレスの中の、店の形から作った店
# ---------------------------------------------------------------
import os as _os
LAYOUT_DIR = _os.path.join(_os.path.dirname(_os.path.abspath(__file__)), "layouts")
LAYOUT_LABELS = {
    "p0": "② 定番の郊外型", "p1": "③ 効率重視・入口が角", "p2": "④ 効率重視・小さめの店",
    "p3": "⑤ ゆったり・入口が正面", "p4": "⑥ ゆったり・入口が横", "p5": "⑦ ③の通路を95cmに",
}


def _scale(vals, t, base_t):
    return [max(1, round(v * t / base_t)) for v in vals]


def layout_stores():
    """layouts/ の店の形から、標準ファミレスをもとにした店を作る"""
    out = {}
    base = STORES["family"]
    names = sorted(f for f in _os.listdir(LAYOUT_DIR) if f.endswith("_analysis.json")) if _os.path.isdir(LAYOUT_DIR) else []
    for f in names:
        a = json.load(open(_os.path.join(LAYOUT_DIR, f), encoding="utf-8"))
        nm = f.split("_")[0]
        dists = [t["dist"] for t in a["tables"]]
        if any(d is None for d in dists):
            raise ValueError(f"{f}: ロボットが届かない卓がある（人が運ぶ卓はまだ試算に入れていない）")
        t = len(dists)
        c = max(2, round(base["staff"][1] * t / base["tables"]))      # 真ん中の人数
        st = {**base}
        st.update({
            "label": LAYOUT_LABELS.get(nm, nm), "base": "family", "layout": nm,
            "tables": t, "dist_m": (round(min(dists), 1), round(max(dists), 1)), "dist_list": dists,
            "arrivals": _scale(base["arrivals"], t, base["tables"]),
            "staff": [c - 1, c, c + 1], "staff_all": list(range(max(1, c - 2), c + 5)),
        })
        out["fam_" + nm] = st
    return out


STORES.update(layout_stores())

ROBOTS = [0, 1, 2, 3]          # 最初に見せる台数
ROBOTS_ALL = list(range(7))    # 「その他」で選べる台数（総当たりはこちら）
MODES = ["serve", "bus", "patrol"]
TRAYS = [1, 3]                 # 1往復で回る卓の数（1卓ずつ／最大3卓）
FRAGILE_BY = ["any", "staff"]  # 倒れやすい物を ロボットも運ぶ／人だけが運ぶ
REPS = 40


def tri(rng, lmh):
    lo, mode, hi = lmh
    return rng.triangular(lo, hi, mode)


class _AgedRequest(simpy.resources.resource.PriorityRequest):
    """v2.9：頼んだ時刻を since にさかのぼらせたスタッフの頼み（同じ優先の中で、since の古い順に並ぶ）"""
    def __init__(self, resource, priority, since):
        self.priority = priority
        self.preempt = False
        self.time = since
        self.usage_since = None
        self.key = (self.priority, self.time, not self.preempt)
        simpy.resources.resource.Request.__init__(self, resource)


class Store:
    def __init__(self, env, rng, p):
        self.env, self.rng, self.p = env, rng, p
        self.tables = simpy.Resource(env, capacity=p["tables"])
        self.staff = simpy.PriorityResource(env, capacity=p["staff"])
        self.register = simpy.Resource(env, capacity=p["registers"])   # v2.8 レジ。会計はレジ1つにつき1組ずつ
        self.pay_waits = []             # 組ごと：食べ終えてから会計が始まるまで（分）（v2.8）
        self.robots = simpy.PriorityResource(env, capacity=p["robots"]) if p["robots"] else None
        self.dish_waits, self.seat_waits, self.turns = [], [], []
        self.staff_walk_m = self.staff_busy_s = self.robot_busy_s = 0.0
        self.robot_trips = self.staff_trips = self.rescues = self.collects = self.done = 0
        self.robot_bus = self.staff_bus = 0
        self.fragile_robot = self.pauses = 0
        self.pass_q = []  # 受け渡し口でロボットを待っている料理
        # 回るかの確かめ（sustain）で数える時間。ピークの回し方では全部の組を数える
        self.t0 = p["warmup_min"] * 60 if p["sustain"] else 0
        self.t1 = self.t0 + p["peak_min"] * 60
        self.counted = self.counted_done = 0
        self.window_closed = False
        self.finished = env.event()
        self.capped = False
        self.seat_wait_by_arrival = []  # (来た時刻, 入店待ち 分)。待ちが伸び続けるかを見る
        self.staff_busy_n = []          # 数える時間のあいだ、一定間隔で数えた手がふさがっている人数
        self.robot_busy_n = []
        self.snap0 = self.snap1 = None
        # 入店待ちの内訳（v2.2）
        #   片付けが終わらず使えない卓（客は帰ったが下げ膳・卓拭きが済んでいない）の数
        #   入口の列の前から数えて、その数までの組は「席はあるのに待っている」とみなす
        self.dirty = 0
        self._acc_t = 0.0
        self.blocked = {}               # 卓の順番待ちの request → 片付け待ちで待った秒数
        self.blocked_waits = []         # 組ごと：席はあるのに片付けが終わらず待った時間（分）
        self.guide_waits = []           # 組ごと：卓が決まってから、案内のスタッフの手が空くまで（分）
        self.entry_waits = []           # 組ごと：店に来てから、案内されて席に着くまで（分）

    def _acc(self):
        """前回からの時間を、列の前から片付け待ちの卓の数までの組に足す（列や卓の数が変わる直前に呼ぶ）"""
        now = self.env.now
        dt = now - self._acc_t
        if dt > 0 and self.dirty > 0:
            for req in self.tables.queue[:self.dirty]:
                self.blocked[req] = self.blocked.get(req, 0.0) + dt
        self._acc_t = now

    SNAP = ("staff_walk_m", "robot_trips", "staff_trips", "rescues", "collects",
            "robot_bus", "staff_bus", "fragile_robot", "pauses")

    def _snap(self):
        return {k: getattr(self, k) for k in self.SNAP}

    def window(self):
        """数える時間の始めと終わりに数を控え、そのあいだは手がふさがっている人数・台数を数える"""
        env, p = self.env, self.p
        yield env.timeout(self.t0)
        self.snap0 = self._snap()
        while env.now < self.t1:
            self.staff_busy_n.append(self.staff.count)
            if self.robots is not None:
                self.robot_busy_n.append(self.robots.count)
            yield env.timeout(p["util_sample_s"])
        self.snap1 = self._snap()
        self.window_closed = True
        self._check_finished()
        yield env.timeout(p["max_extra_min"] * 60)
        if not self.finished.triggered:   # 店が回らず、数える組が出きらない
            self.capped = True
            self.finished.succeed()

    def _check_finished(self):
        if self.window_closed and self.counted_done >= self.counted and not self.finished.triggered:
            self.finished.succeed()

    # ---- スタッフの仕事（priority 0=ロボット救出, 1=配膳・案内・会計, 2=下げ膳）
    def staff_job(self, priority, walk_m, work_s, waited=None, since=None):
        t0 = self.env.now
        # v2.9：since があれば、その時刻に頼んだものとして並ぶ（会計は、レジに並んだ時刻の古さで並ぶ）
        req0 = _AgedRequest(self.staff, priority, since) if since is not None else self.staff.request(priority=priority)
        with req0 as req:
            yield req
            if waited is not None:
                waited.append(self.env.now - t0)   # 手が空くのを待った時間（案内待ち）
            t = walk_m / self.p["staff_speed"] + work_s
            self.staff_walk_m += walk_m
            self.staff_busy_s += t
            yield self.env.timeout(t)

    # ---- ロボットが途中で止まる（1往復に1回判定）
    def maybe_stop(self, d, helper_waiting=False):
        p, rng = self.p, self.rng
        if rng.random() < p["pause_rate"]:      # 一時停止：数秒で自然に解ける
            self.pauses += 1
            yield self.env.timeout(rng.uniform(*p["pause_s"]))
        if rng.random() < p["stuck_rate"]:      # 立ち往生：スタッフが動かしに行く
            self.rescues += 1
            if helper_waiting:                  # 卓でロボットを待っているスタッフがそのまま動かす
                yield self.env.timeout(d / p["staff_speed"] + p["rescue_s"])
            else:
                yield self.env.process(self.staff_job(0, d, p["rescue_s"]))

    # ---- 配膳
    # v2.7 ロボット優先（2026-10-23 エイイチと決めた）：料理ができたら、まずロボットを待つ。スタッフは料理を持たず、
    # ほかの仕事（案内・片付け・会計）を続ける。robot_first_wait_s（2分）待ってもロボットが空かなければ、
    # そこからはロボットとスタッフのうち先に空いた方が運ぶ（料理が冷める前の逃げ道）。
    # 割れ物を人が運ぶ設定（fragile_by=staff）と、ロボット0台のときは、今までどおりスタッフが運ぶ。
    # v2.6 までは、はじめから「先に空いた方」だったため、受け渡し口の横で待つスタッフが多く運んでいた。
    # ロボットが出るときは、受け渡し口で待っている料理を段の数までまとめて載せ、近い卓から回る。
    def deliver(self, d, fragile=False, counted=True):
        env, p = self.env, self.p
        rec = {"d": d, "ready": env.now, "fragile": fragile, "counted": counted,
               "taken": env.event(), "arrived": env.event()}
        robot_ok = self.robots is not None and not (fragile and p["fragile_by"] == "staff")
        if not robot_ok:
            sreq = self.staff.request(priority=1)
            yield sreq
            yield env.process(self._staff_carry(rec, sreq))
            return
        rreq = self.robots.request(priority=0)
        rec["sreq"], rec["rreq"] = None, rreq
        self.pass_q.append(rec)
        yield rreq | rec["taken"] | env.timeout(p["robot_first_wait_s"])   # まずロボットだけを待つ
        if not (rreq.triggered or rec["taken"].triggered):                # 2分たった：ここからはスタッフも運べる
            sreq = self.staff.request(priority=1)
            rec["sreq"] = sreq
            yield rreq | sreq | rec["taken"]
        sreq = rec["sreq"]
        if rec["taken"].triggered:          # ほかの料理と一緒にロボットに載った
            yield rec["arrived"]
            return
        self.pass_q.remove(rec)
        if rreq.triggered:                  # ロボットが運ぶ
            if sreq is not None:
                self._drop(sreq, self.staff)
            batch = [rec]
            for o in list(self.pass_q):
                if len(batch) >= p["trays"]:
                    break
                self.pass_q.remove(o)
                self._drop(o["rreq"], self.robots)
                if o["sreq"] is not None:
                    self._drop(o["sreq"], self.staff)
                o["taken"].succeed()
                batch.append(o)
            env.process(self._robot_run(batch, rreq))
            yield rec["arrived"]
            return
        rreq.cancel()                       # スタッフが運ぶ
        yield env.process(self._staff_carry(rec, sreq))

    @staticmethod
    def _drop(req, res):
        if req.triggered:
            res.release(req)
        else:
            req.cancel()

    def _robot_run(self, batch, rreq):
        env, p = self.env, self.p
        start = env.now
        batch.sort(key=lambda r: r["d"])
        pos = 0.0
        for i, rec in enumerate(batch):
            leg = rec["d"] - pos + (p["hop_m"] if i else 0)
            yield env.timeout(max(leg, 0) / p["robot_speed"])
            if i == 0:
                yield env.process(self.maybe_stop(rec["d"]))
            if rec["counted"]:
                self.dish_waits.append((env.now - rec["ready"]) / 60)
            self.robot_trips += 1
            self.fragile_robot += rec["fragile"]
            rec["arrived"].succeed()
            yield env.timeout(p["robot_handoff_s"])
            pos = rec["d"]
        yield env.timeout(pos / p["robot_speed"])
        self.robot_busy_s += env.now - start
        self.robots.release(rreq)

    def _staff_carry(self, rec, sreq):
        env, p = self.env, self.p
        self.staff_trips += 1
        go = rec["d"] / p["staff_speed"]
        yield env.timeout(go)
        if rec["counted"]:
            self.dish_waits.append((env.now - rec["ready"]) / 60)
        rec["arrived"].succeed()
        yield env.timeout(p["staff_place_s"] + go)
        self.staff_walk_m += 2 * rec["d"]
        self.staff_busy_s += 2 * go + p["staff_place_s"]
        self.staff.release(sreq)

    # ---- 巡回回収（食事中にロボットが来て、客が空いた皿を載せる）
    def collect_visit(self, d, deadline, box):
        env, p = self.env, self.p
        req = self.robots.request(priority=2)
        timer = env.timeout(max(deadline - env.now, 0))
        res = yield req | timer
        if req not in res:
            req.cancel()
            return
        start = env.now
        t = d / p["robot_speed"]
        back = (d + p["dish_extra_m"]) / p["robot_speed"]
        yield env.timeout(t)
        yield env.process(self.maybe_stop(d))
        yield env.timeout(p["guest_load_s"] + back)
        self.robot_busy_s += env.now - start
        self.robots.release(req)
        self.collects += 1
        box[0] += 1

    # ---- 下げ膳（客が帰ってから卓が使えるようになるまで）
    def bus(self, d, collected, counted=True):
        env, p = self.env, self.p
        start = env.now
        trips = max(p["bus_trips"] - collected, 0)
        if p["mode"] == "bus" and self.robots is not None and trips > 0:
            # v2.4: スタッフの手が空いて片付けに向かうときに、空いているロボットがいれば一緒に卓へ向かわせる
            with self.staff.request(priority=2) as sreq:
                yield sreq
                s0 = env.now
                rreq = self.robots.request(priority=1)
                if rreq.triggered:
                    self.robot_bus += 1
                    arrive, loaded = env.event(), env.event()
                    env.process(self._robot_bus_trip(d, rreq, arrive, loaded))
                    # スタッフは卓まで行き、皿をまとめ、ロボットが着いたら載せ、卓を拭いて戻る
                    yield env.timeout(d / p["staff_speed"] + p["clear_s"])
                    yield arrive
                    yield env.timeout(p["load_s"])
                    loaded.succeed()                 # 載せ終えた。ロボットは洗い場へ
                    yield env.timeout(p["wipe_s"])
                    done_at = env.now
                    yield env.timeout(d / p["staff_speed"])
                    self.staff_walk_m += 2 * d
                    self.staff_busy_s += env.now - s0
                    if counted:
                        self.turns.append((done_at - start) / 60)
                    return
                rreq.cancel()
                # 空いているロボットがいない：そのまま人だけで下げる（皿の量に応じて往復）
                self.staff_bus += 1
                walk = 2 * (d + p["dish_extra_m"]) * trips
                tt = walk / p["staff_speed"] + p["clear_s"] + p["wipe_s"]
                self.staff_walk_m += walk
                self.staff_busy_s += tt
                yield env.timeout(tt)
            if counted:
                self.turns.append((env.now - start) / 60)
            return
        # 人だけで下げる（皿の量に応じて往復）。皿がもうなければ拭きに行くだけ
        self.staff_bus += 1
        walk = 2 * (d + p["dish_extra_m"]) * trips if trips > 0 else 2 * d
        work = (p["clear_s"] if trips > 0 else 0) + p["wipe_s"]
        yield env.process(self.staff_job(2, walk, work))
        if counted:
            self.turns.append((env.now - start) / 60)

    def _robot_bus_trip(self, d, rreq, arrive, loaded):
        env, p = self.env, self.p
        start = env.now
        t = d / p["robot_speed"]
        yield env.timeout(t)
        yield env.process(self.maybe_stop(d, helper_waiting=True))
        arrive.succeed()
        yield loaded                                  # v2.4: スタッフが載せ終えるまで卓で待つ
        back = (d + p["dish_extra_m"]) / p["robot_speed"]
        yield env.timeout(back + 15)  # 洗い場で降ろすのは厨房側
        self.robot_busy_s += env.now - start
        self.robots.release(rreq)

    # ---- 1組の流れ
    def group(self):
        env, p, rng = self.env, self.p, self.rng
        arrive = env.now
        counted = (self.t0 <= arrive < self.t1) if p["sustain"] else True   # 数える時間に来た組だけを記録する
        if counted:
            self.counted += 1
        # 図面のある店は卓ごとの距離から選ぶ。ほかの店は今までどおり（乱数の使い方も同じ）
        d = rng.choice(p["dist_list"]) if p.get("dist_list") else rng.uniform(*p["dist_m"])
        self._acc()
        table = self.tables.request()
        yield table
        blocked = self.blocked.pop(table, 0.0)
        if counted:
            w = (env.now - arrive) / 60
            self.seat_waits.append(w)
            self.seat_wait_by_arrival.append((arrive, w))
        # 案内（v2.4）: スタッフの手が空いたら卓まで一緒に歩く。卓に着いた時点を「席に着いた」とする。
        # 説明（guide_s）と受け渡し口への戻りは、スタッフの手がふさがる時間には入れ、お客さんの注文はそのあとに進む（v2.2 と同じ）
        g0 = env.now
        with self.staff.request(priority=1) as greq:
            yield greq
            gwait = env.now - g0
            yield env.timeout(d / p["staff_speed"])
            seated_at = env.now
            yield env.timeout(p["guide_s"] + d / p["staff_speed"])
            self.staff_walk_m += 2 * d
            self.staff_busy_s += 2 * d / p["staff_speed"] + p["guide_s"]
        if counted:
            self.blocked_waits.append(blocked / 60)
            self.guide_waits.append(gwait / 60)
            self.entry_waits.append((seated_at - arrive) / 60)
        firsts = [env.process(self._cook_then_deliver(c, d, False, counted)) for c in p["first_dishes"]]
        yield simpy.AllOf(env, firsts)
        eat = tri(rng, p["eat_min"]) * 60
        end = env.now + eat
        # 食事中の追加注文・呼び出し・巡回回収
        extras = []
        for _ in range(p["extra_orders"]):
            extras.append(env.process(self._order_later(rng.uniform(0, eat * 0.7), d, p["extra_cook"], False, counted)))
        n_fragile = int(p["fragile"]) + (rng.random() < p["fragile"] - int(p["fragile"]))
        for _ in range(n_fragile):
            extras.append(env.process(self._order_later(rng.uniform(0.3, 0.85) * eat, d, p["fragile_cook"], True, counted)))
        calls = sum(rng.random() < p["call_rate"] / 2 for _ in range(2))
        for _ in range(calls):
            env.process(self._call_later(rng.uniform(0, eat), d))
        collected = [0]
        if p["mode"] == "patrol" and self.robots is not None:
            for k in range(p["bus_trips"]):
                at = eat * (k + 1) / (p["bus_trips"] + 1)
                env.process(self._collect_later(at, d, end, collected))
        yield env.timeout(eat)
        if extras:
            yield simpy.AllOf(env, extras)
        # v2.8 会計：卓に座ったままレジの順番を待ち、順番が来たらスタッフの手が空くのを待って会計する
        # v2.9 会計のスタッフの頼みは、レジに並んだ時刻（p0）の古さで並ぶ。会計が終わったら先にレジを空け、
        #      次にレジに入った組の頼みが並んでからスタッフの手を放す（会計の係がそのまま次の会計を取れる。Unity と同じ）
        p0 = env.now
        rq = self.register.request()
        yield rq
        sreq = _AgedRequest(self.staff, 1, p0)
        yield sreq
        self.staff_busy_s += p["pay_s"]
        yield env.timeout(p["pay_s"])
        yield self.register.release(rq)      # レジを空ける（次の組がレジに入る）
        yield env.timeout(0)                 # 次の組の会計の頼みが並ぶのを待ってから
        self.staff.release(sreq)             # スタッフの手を放す
        if counted:
            self.pay_waits.append((env.now - p0 - p["pay_s"]) / 60)
        self._acc()
        self.dirty += 1                      # 客は帰った。片付けが済むまで卓は使えない
        yield env.process(self.bus(d, collected[0], counted))
        self._acc()
        self.dirty -= 1
        self.tables.release(table)
        self.done += 1
        if counted:
            self.counted_done += 1
            self._check_finished()

    def _cook_then_deliver(self, cook, d, fragile=False, counted=True):
        yield self.env.timeout(tri(self.rng, cook) * 60)
        yield self.env.process(self.deliver(d, fragile, counted))

    def _order_later(self, delay, d, cook, fragile, counted=True):
        yield self.env.timeout(delay)
        yield self.env.process(self._cook_then_deliver(cook, d, fragile, counted))

    def _call_later(self, delay, d):
        yield self.env.timeout(delay)
        yield self.env.process(self.staff_job(1, 2 * d, self.p["call_s"]))

    def _collect_later(self, delay, d, deadline, box):
        yield self.env.timeout(delay)
        yield self.env.process(self.collect_visit(d, deadline, box))

    def arrivals(self):
        env, p, rng = self.env, self.p, self.rng
        rate = p["arrivals_per_h"] / 3600
        while not self.finished.triggered:
            yield env.timeout(rng.expovariate(rate))
            if not p["sustain"] and env.now > p["peak_min"] * 60:
                return                       # ピークの回し方：peak_min 分で来客が止まる
            env.process(self.group())        # 回るかの確かめ：来客は止めない（数える組が出きるまで）


UNSTABLE_MIN = 5    # 参考：数える時間のうちに入店待ちがこれ以上延びていく条件（判定には使わない。卓が足りない店の見分けに使う）
NG_MIN = 10         # 席は空いているのに待った時間（staff_wait_mean）がこれ以上なら「店が回らない」とする（分）
STAFF_FULL = 0.95   # 回るかの確かめで、スタッフの手がこれ以上ふさがっていたら「店が回らない」とする


def run_once(params, seed):
    store = STORES[params["store"]]
    p = {**COMMON, **store, **params}
    env = simpy.Environment()
    s = Store(env, random.Random(seed), p)
    env.process(s.arrivals())
    if p["sustain"]:
        return _sustain_result(env, s, p)
    env.run()
    end = env.now
    w = s.dish_waits
    return {
        "dish_wait_mean": statistics.mean(w),
        "dish_wait_p90": sorted(w)[int(0.9 * (len(w) - 1))],
        "cold_share": sum(x > p["cold_min"] for x in w) / len(w),
        "seat_wait_mean": statistics.mean(s.seat_waits),
        "turn_mean": statistics.mean(s.turns) if s.turns else 0.0,
        "walk_km_per_staff": s.staff_walk_m / p["staff"] / 1000,
        "staff_util": s.staff_busy_s / (p["staff"] * end),
        "robot_util": (s.robot_busy_s / (p["robots"] * end)) if p["robots"] else 0.0,
        "robot_share": s.robot_trips / max(s.robot_trips + s.staff_trips, 1),
        "robot_bus_share": s.robot_bus / max(s.robot_bus + s.staff_bus, 1),
        "collects": s.collects,
        "rescues": s.rescues,
        "pauses": s.pauses,
        "fragile_robot": s.fragile_robot,
        "groups": s.done,
        # 入店待ちの内訳（v2.2）
        "entry_wait_mean": statistics.mean(s.entry_waits),            # 来てから案内されて卓に着くまで（v2.4）
        "blocked_wait_mean": statistics.mean(s.blocked_waits),        # 席はあるのに片付けが終わらず待った
        "guide_wait_mean": statistics.mean(s.guide_waits),            # 卓は決まったが案内の手が空かず待った
        "staff_wait_mean": statistics.mean(b + g for b, g in zip(s.blocked_waits, s.guide_waits)),  # 席はあるのに待った（合計）
        "full_wait_mean": statistics.mean(w - b for w, b in zip(s.seat_waits, s.blocked_waits)),    # 席が埋まっていて待った
        "pay_wait_mean": statistics.mean(s.pay_waits),                # v2.8 食べ終えてから会計が始まるまで（レジの順番とスタッフの手）
    }


def _sustain_result(env, s, p):
    """回るかの確かめ：来客を止めずに回し、数える時間の結果を出す"""
    env.process(s.window())
    env.run(until=s.finished)
    win_s = p["peak_min"] * 60
    q = win_s / 4
    first = [x for t, x in s.seat_wait_by_arrival if t < s.t0 + q]
    last = [x for t, x in s.seat_wait_by_arrival if t >= s.t1 - q]
    trend = (statistics.mean(last) - statistics.mean(first)) if first and last else 0.0
    return {
        "seat_wait_mean": statistics.mean(s.seat_waits),
        "seat_wait_trend": trend,
        "staff_util": statistics.mean(s.staff_busy_n) / p["staff"],
        "capped": 1.0 if s.capped else 0.0,
    }


def conditions(main_only=False):
    for key, st in STORES.items():
        staffs = st["staff"] if main_only else st["staff_all"]
        robots_list = ROBOTS if main_only else ROBOTS_ALL
        for staff, robots, arr, mode, trays, fb, reg in product(staffs, robots_list, st["arrivals"], MODES, TRAYS, FRAGILE_BY,
                                                                st["registers_all"]):
            if robots == 0 and (mode != "serve" or trays != 1 or fb != "any"):
                continue  # ロボットなしなら、使い方・段の数・使い分けは関係ない
            yield {"store": key, "staff": staff, "robots": robots, "arrivals_per_h": arr,
                   "mode": mode, "trays": trays, "fragile_by": fb, "registers": reg}


def run_condition(params):
    runs = [run_once(params, seed) for seed in range(REPS)]
    agg = {k: round(statistics.mean(r[k] for r in runs), 4) for k in runs[0]}
    return {**params, **agg}


def key(c):
    return (c["store"], c["staff"], c["robots"], c["arrivals_per_h"], c["mode"], c["trays"], c["fragile_by"], c["registers"])


def model_sig(common, stores):
    """結果に効く前提だけを取り出す（人数・台数の選択肢や名前は含めない）"""
    skip = {"label", "staff", "staff_all", "arrivals", "registers", "registers_all"}
    return json.loads(json.dumps({"common": common, "reps": REPS,
                                  "stores": {k: {a: b for a, b in v.items() if a not in skip} for k, v in stores.items()}}))


if __name__ == "__main__":
    if "--one" in sys.argv:
        for mode in MODES:
            for trays, fb in product(TRAYS, FRAGILE_BY):
                r = run_condition({"store": "family", "staff": 3, "robots": 2, "arrivals_per_h": 35,
                                   "mode": mode, "trays": trays, "fragile_by": fb})
                print(mode, trays, fb, {k: round(v, 2) for k, v in r.items() if isinstance(v, float)})
    elif "--sustain" in sys.argv:
        # 回るかの確かめ：全条件を、来客を止めない回し方で回す
        conds = [{**c, "sustain": True} for c in conditions()]
        done = {}
        try:
            with open("results_sustain_partial.json", encoding="utf-8") as f:
                old = json.load(f)
            if old.get("common") == COMMON and old.get("reps") == REPS and old.get("unstable_min") == UNSTABLE_MIN:
                done = {key(r): r for r in old["rows"]}
        except (FileNotFoundError, KeyError, TypeError, json.JSONDecodeError):
            pass

        def save_s(path, rows):
            out = {"common": COMMON, "reps": REPS, "unstable_min": UNSTABLE_MIN, "rows": rows}
            with open(path + ".tmp", "w", encoding="utf-8") as f:
                json.dump(out, f, ensure_ascii=False)
            import os
            os.replace(path + ".tmp", path)

        todo = [c for c in conds if key(c) not in done]
        print(f"{len(conds)} conditions, {len(conds) - len(todo)} reused, {len(todo)} to run", flush=True)
        with Pool() as pool:
            for i, r in enumerate(pool.imap_unordered(run_condition, todo, chunksize=2), 1):
                done[key(r)] = r
                if i % 100 == 0:
                    save_s("results_sustain_partial.json", list(done.values()))
                    print(f"  {i}/{len(todo)}", flush=True)
        save_s("results_sustain_partial.json", list(done.values()))
        save_s("results_sustain.json", [done[key(c)] for c in conds])
        print(len(conds), "conditions written", flush=True)
    else:
        conds = list(conditions(main_only="--main" in sys.argv))
        sig = model_sig(COMMON, STORES)
        # 前提が同じなら、前回の結果と途中経過を使い回して足りない条件だけ回す
        cache = {}
        for path in ("results.json", "results_partial.json"):
            try:
                with open(path, encoding="utf-8") as f:
                    old = json.load(f)
                if old.get("reps") == REPS:
                    osig = model_sig(old["common"], old["stores"])
                    same = {k for k in sig["stores"] if osig["common"] == sig["common"] and osig["stores"].get(k) == sig["stores"][k]}
                    cache.update({key(r): r for r in old["rows"] if r["store"] in same})
            except (FileNotFoundError, KeyError, TypeError, json.JSONDecodeError):
                pass

        def save(path, rows):
            out = {"common": COMMON, "stores": STORES, "robots": ROBOTS, "robots_all": ROBOTS_ALL,
                   "modes": MODES, "trays": TRAYS, "fragile_by": FRAGILE_BY, "reps": REPS, "rows": rows}
            with open(path + ".tmp", "w", encoding="utf-8") as f:
                json.dump(out, f, ensure_ascii=False)
            import os
            os.replace(path + ".tmp", path)

        todo = [c for c in conds if key(c) not in cache]
        print(f"{len(conds)} conditions, {len(conds) - len(todo)} reused, {len(todo)} to run", flush=True)
        with Pool() as pool:
            for i, r in enumerate(pool.imap_unordered(run_condition, todo, chunksize=2), 1):
                cache[key(r)] = r
                if i % 100 == 0:
                    save("results_partial.json", list(cache.values()))
                    print(f"  {i}/{len(todo)}", flush=True)
        save("results_partial.json", list(cache.values()))
        save("results.json", [cache[key(c)] for c in conds])
        print(len(conds), "conditions written", flush=True)
```

### Unity の54条件で、v2.8・v2.9・Unity を並べた quick_compare_unity54.py

```python
"""v2.9（会計の頼みをレジに並んだ時刻で並べる）を、Unity で回している54条件だけ先に回し、v2.8 と Unity の夜と比べる（2026-10-30）
使い方: python3 quick_compare_unity54.py <service_sim/unity のフォルダ>  → quick_compare_unity54.json / .txt"""
import csv, glob, json, os, statistics, sys
from multiprocessing import Pool
import haizen_sim as h
U = sys.argv[1]
P = json.load(open("serving-robot-params.json", encoding="utf-8"))
conds = [c["condition"] for c in P["unity_conditions"]]
with Pool() as pool:
    new = pool.map(h.run_condition, conds)
L = list(csv.DictReader(open(os.path.join(U, "unity_ledger_v1.csv"), encoding="utf-8")))
NIGHT = {"fam_p1": "20261102", "fam_p5": "20261102", "fam_p2": "20261030", "fam_p3": "20261103", "fam_p4": "20261104", "fam_p0": "20261105"}
def unity(c):
    s = h.STORES[c["store"]]
    if c["arrivals_per_h"] == s["arrivals"][1]:
        pre = NIGHT[c["store"]] + "_night_cs"
    else:
        pre = f"20261106_night_a{c['arrivals_per_h']}_cs"
    rows = [r for r in L if r["run_folder"].startswith(pre) and r["store"] == c["store"] and r["robots"] == str(c["robots"]) and r["status"] == "completed"]
    m = lambda k: statistics.median(float(r[k]) for r in rows) if rows else float("nan")
    return {"n": len(rows), "entry": m("entry_wait_mean"), "staff_wait": m("staff_wait_mean"), "pay_wait": m("pay_wait_mean")}
out = []
lines = ["店 混み 台 | 入店待ち v2.8/v2.9/Unity | 席は空いているのに待った v2.8/v2.9/Unity | 会計待ち v2.8/v2.9/Unity | 店が回らない v2.8→v2.9"]
for c, old, n in zip(conds, P["unity_conditions"], new):
    o = old["site_result"]; u = unity(c)
    lab = h.STORES[c["store"]]["label"].split()[0]
    crowd = ["空", "普", "混"][h.STORES[c["store"]]["arrivals"].index(c["arrivals_per_h"])]
    ng = lambda r: "×" if r["staff_wait_mean"] >= h.NG_MIN else "○"
    lines.append(f"{lab} {crowd} {c['robots']} | {o['entry_wait_mean']:.1f}/{n['entry_wait_mean']:.1f}/{u['entry']:.1f} | "
                 f"{o['staff_wait_mean']:.1f}/{n['staff_wait_mean']:.1f}/{u['staff_wait']:.1f} | {o['pay_wait_mean']:.1f}/{n['pay_wait_mean']:.1f}/{u['pay_wait']:.1f} | {ng(o)}→{ng(n)}")
    out.append({"condition": c, "v28": o, "v29": {k: v for k, v in n.items() if k not in c}, "unity_median": u})
json.dump(out, open("quick_compare_unity54.json", "w", encoding="utf-8"), ensure_ascii=False, indent=1)
open("quick_compare_unity54.txt", "w", encoding="utf-8").write("\n".join(lines) + "\n")
print("\n".join(lines))
```

### quick_compare_unity54.txt（直した後の v2.9）

```text
店 混み 台 | 入店待ち v2.8/v2.9/Unity | 席は空いているのに待った v2.8/v2.9/Unity | 会計待ち v2.8/v2.9/Unity | 店が回らない v2.8→v2.9
③ 普 0 | 3.3/4.1/4.1 | 1.0/2.7/2.9 | 12.3/4.8/4.5 | ○→○
③ 普 1 | 2.6/1.7/5.0 | 0.6/0.5/3.3 | 8.0/5.6/6.1 | ○→○
③ 普 2 | 1.2/1.1/4.0 | 0.4/0.4/1.7 | 8.0/6.6/7.1 | ○→○
⑦ 普 0 | 2.8/3.1/4.5 | 0.9/2.1/3.3 | 11.7/4.9/4.4 | ○→○
⑦ 普 1 | 1.8/1.6/3.2 | 0.5/0.5/1.5 | 8.1/5.8/5.1 | ○→○
⑦ 普 2 | 2.0/1.6/3.6 | 0.4/0.5/1.7 | 8.0/6.6/7.2 | ○→○
④ 普 0 | 10.4/10.9/13.0 | 8.4/9.4/11.5 | 4.2/1.7/1.9 | ○→○
④ 普 1 | 2.9/4.8/13.8 | 2.0/3.9/12.1 | 4.9/1.5/2.3 | ○→○
④ 普 2 | 3.5/5.6/26.8 | 2.0/4.2/23.9 | 5.3/1.5/3.8 | ○→○
⑤ 普 0 | 6.2/7.5/8.1 | 1.5/5.5/6.5 | 18.8/6.1/5.7 | ○→○
⑤ 普 1 | 3.4/3.2/6.6 | 0.8/1.4/4.6 | 13.0/8.6/7.2 | ○→○
⑤ 普 2 | 3.7/2.4/5.5 | 0.7/0.7/3.4 | 13.0/9.9/7.0 | ○→○
⑥ 普 0 | 6.9/7.9/9.6 | 4.2/6.6/8.4 | 12.0/2.4/3.0 | ○→○
⑥ 普 1 | 3.1/3.7/6.0 | 1.0/2.5/4.8 | 8.5/3.0/3.0 | ○→○
⑥ 普 2 | 1.9/2.1/4.8 | 0.6/1.6/3.5 | 7.0/3.0/3.3 | ○→○
② 普 0 | 2.6/2.6/2.0 | 1.6/1.5/0.8 | 1.4/0.9/1.1 | ○→○
② 普 1 | 1.7/1.8/1.7 | 0.7/0.9/0.4 | 1.3/0.7/1.1 | ○→○
② 普 2 | 1.2/1.3/2.1 | 0.5/0.6/0.8 | 1.1/0.7/1.3 | ○→○
③ 空 0 | 0.4/0.3/1.3 | 0.1/0.1/0.2 | 2.5/1.7/2.8 | ○→○
③ 空 1 | 0.3/0.2/1.4 | 0.1/0.1/0.2 | 2.4/1.9/2.8 | ○→○
③ 空 2 | 0.2/0.2/1.4 | 0.0/0.1/0.2 | 2.2/2.0/3.0 | ○→○
③ 混 0 | 13.0/12.9/19.0 | 1.5/5.2/8.9 | 15.4/6.5/5.7 | ○→○
③ 混 1 | 12.0/11.5/16.4 | 1.3/1.4/4.4 | 12.0/9.8/7.1 | ○→○
③ 混 2 | 11.1/11.1/15.8 | 1.2/1.2/5.7 | 11.5/10.3/7.7 | ○→○
⑦ 空 0 | 0.3/0.3/1.3 | 0.1/0.2/0.2 | 2.6/1.7/2.7 | ○→○
⑦ 空 1 | 0.2/0.2/1.4 | 0.0/0.1/0.2 | 2.2/1.9/2.8 | ○→○
⑦ 空 2 | 0.2/0.2/1.4 | 0.0/0.1/0.2 | 2.1/1.9/3.2 | ○→○
⑦ 混 0 | 12.3/12.3/19.4 | 1.6/5.2/9.3 | 15.3/6.4/5.7 | ○→○
⑦ 混 1 | 13.7/13.3/17.7 | 1.3/1.5/5.0 | 12.2/9.7/7.2 | ○→○
⑦ 混 2 | 11.0/11.6/16.6 | 1.1/1.2/5.2 | 11.8/10.3/7.4 | ○→○
④ 空 0 | 0.9/1.0/1.7 | 0.7/0.9/0.6 | 2.2/1.2/1.6 | ○→○
④ 空 1 | 0.4/0.6/2.5 | 0.3/0.4/1.2 | 1.4/0.8/1.9 | ○→○
④ 空 2 | 0.6/0.6/4.0 | 0.4/0.5/2.8 | 1.9/0.9/2.9 | ○→○
④ 混 0 | 23.9/23.8/32.9 | 14.2/16.3/21.5 | 5.3/2.0/2.1 | ×→×
④ 混 1 | 16.1/16.5/31.6 | 5.3/9.3/20.8 | 7.0/1.8/2.9 | ○→○
④ 混 2 | 14.7/16.8/45.9 | 5.2/9.5/25.6 | 7.3/2.0/4.5 | ○→○
⑤ 空 0 | 0.5/0.5/1.4 | 0.2/0.3/0.3 | 5.6/2.8/3.5 | ○→○
⑤ 空 1 | 0.4/0.3/1.6 | 0.1/0.1/0.4 | 3.9/2.8/4.0 | ○→○
⑤ 空 2 | 0.3/0.3/1.5 | 0.1/0.1/0.3 | 3.3/2.4/3.7 | ○→○
⑤ 混 0 | 19.4/22.7/24.8 | 2.1/10.1/12.2 | 22.9/7.4/7.2 | ○→×
⑤ 混 1 | 17.6/15.6/23.7 | 1.4/2.0/8.9 | 17.8/13.5/8.2 | ○→○
⑤ 混 2 | 12.7/12.3/24.4 | 1.3/1.4/8.0 | 17.0/14.7/9.7 | ○→○
⑥ 空 0 | 0.8/0.7/1.5 | 0.6/0.5/0.4 | 4.2/1.6/2.0 | ○→○
⑥ 空 1 | 0.3/0.4/1.4 | 0.2/0.2/0.2 | 2.0/1.4/2.0 | ○→○
⑥ 空 2 | 0.3/0.4/1.4 | 0.1/0.2/0.2 | 1.9/1.4/2.1 | ○→○
⑥ 混 0 | 21.4/25.1/30.1 | 5.9/15.2/18.7 | 16.7/3.2/3.7 | ○→×
⑥ 混 1 | 13.6/15.0/21.6 | 1.7/7.1/11.3 | 12.4/3.5/4.2 | ○→○
⑥ 混 2 | 12.4/13.6/19.8 | 1.5/6.0/10.7 | 12.5/4.1/4.3 | ○→○
② 空 0 | 0.4/0.5/1.3 | 0.2/0.3/0.2 | 0.8/0.5/0.8 | ○→○
② 空 1 | 0.3/0.3/1.2 | 0.2/0.2/0.2 | 0.6/0.4/0.6 | ○→○
② 空 2 | 0.4/0.4/1.5 | 0.2/0.2/0.3 | 0.6/0.4/0.9 | ○→○
② 混 0 | 10.0/10.6/11.2 | 3.6/4.4/6.2 | 2.0/1.1/1.5 | ○→○
② 混 1 | 7.4/7.6/11.4 | 1.7/2.4/5.3 | 1.6/0.9/1.5 | ○→○
② 混 2 | 7.0/7.3/10.7 | 1.6/2.2/3.9 | 1.6/0.9/1.7 | ○→○
```

普通の混み具合のロボット0台では、入店待ち・席は空いているのに待った・会計待ちが、ほぼ Unity と同じになりました（③ 4.1／2.7／4.8分、Unity 4.1／2.9／4.5分。⑤ 7.5／5.5／6.1分、Unity 8.1／6.5／5.7分）。混んでいるときは、まだ Unity のほうが長くなります（③ 12.9／5.2／6.5分、Unity 19.0／8.9／5.7分）。残りは、片付け1回の長さの差（Unity は前の仕事の場所から歩き出す）と、ロボットがいるときにスタッフの仕事が増える分と見ています。


## 作業ログ ― 全32,850条件を Mac とクラウドで分けて計算し直した

### 分けて回す run_shard.py と、まとめる merge_shards_v29.py

条件の並びの番号を n で割った余りが k のものだけを回す `run_shard.py` で分けました。Mac につないだ shell は1回の呼び出しが3分までで、裏で動かしたままにすると呼び出しの終わりに止まるので、約170秒ずつ区切って回しました（途中経過は100条件ごとに書かれ、次の呼び出しで続きから回ります）。並行して、Claude のクラウドの作業環境でも別の番号を回しました。Mac は1秒に約4〜7条件、クラウドは約2〜3条件だったので、途中で分け方を6つに細かくし、遅い側の残りを Mac が後ろから回しました（`run_timed_rev.py`。v2.8 のときと同じ道具）。

```python
"""v2.8 の全条件を、番号で分けて回す（2026-10-25。計算機を2台に分けて速くするため）。
使い方: python3 run_shard.py <k> <n> <出力.json> [使い回す結果.json ...]
- 条件の並び（haizen_sim.conditions()）の番号を n で割った余りが k のものだけ回す
- 使い回す結果に同じ条件（同じ前提）があれば回さない。100条件ごとに出力へ途中経過を書く"""
import json, os, sys
from multiprocessing import Pool
import haizen_sim as h

k, n, out = int(sys.argv[1]), int(sys.argv[2]), sys.argv[3]
common = json.loads(json.dumps(h.COMMON))
done = {}
for path in sys.argv[4:] + [out]:
    try:
        old = json.load(open(path, encoding="utf-8"))
        if old.get("common") == common and old.get("reps") == h.REPS:
            done.update({h.key(r): r for r in old["rows"]})
    except FileNotFoundError:
        pass
conds = [c for i, c in enumerate(h.conditions()) if i % n == k]
todo = [c for c in conds if h.key(c) not in done]
print(f"shard {k}/{n}: {len(conds)} conditions, {len(todo)} to run", flush=True)

def save():
    rows = [done[h.key(c)] for c in conds if h.key(c) in done]
    with open(out + ".tmp", "w", encoding="utf-8") as f:
        json.dump({"common": h.COMMON, "reps": h.REPS, "shard": [k, n], "rows": rows}, f, ensure_ascii=False)
    os.replace(out + ".tmp", out)

with Pool() as pool:
    for i, r in enumerate(pool.imap_unordered(h.run_condition, todo, chunksize=2), 1):
        done[h.key(r)] = r
        if i % 100 == 0:
            save(); print(f"  {i}/{len(todo)}", flush=True)
save(); print("done", flush=True)
```

```python
"""v2.9 の分けて回した結果（shards/*.json）を1つの results.json にまとめる（2026-10-30）
使い方: python3 merge_shards_v29.py   （v2_9 のフォルダで）→ results.json
- 同じ前提（common・reps）の行だけを使う。条件の並びは haizen_sim.conditions() の順
- 同じ条件が2つの計算機で回っていたら、数字が同じかを確かめる"""
import glob, json
import haizen_sim as h
common = json.loads(json.dumps(h.COMMON))
rows, dup, diff = {}, 0, 0
for f in sorted(glob.glob("shards/*.json")):
    d = json.load(open(f, encoding="utf-8"))
    if d.get("common") != common or d.get("reps") != h.REPS:
        print("前提が違うので使わない:", f); continue
    for r in d["rows"]:
        k = h.key(r)
        if k in rows:
            dup += 1
            if any(abs(rows[k][m] - r[m]) > 1e-9 for m in r if isinstance(r[m], (int, float))):
                diff += 1
        else:
            rows[k] = r
conds = list(h.conditions())
missing = [c for c in conds if h.key(c) not in rows]
print("条件", len(conds), "揃った", len(conds) - len(missing), "足りない", len(missing), "重なり", dup, "うち数字が違う", diff)
if not missing:
    json.dump({"common": h.COMMON, "reps": h.REPS, "rows": [rows[h.key(c)] for c in conds]},
              open("results.json", "w", encoding="utf-8"), ensure_ascii=False)
    print("results.json を書いた")
```

まとめた結果です。

```
条件 32850 揃った 32850 足りない 0 重なり 8000 うち数字が違う 0
results.json を書いた
```

Mac とクラウドの両方で回った8,000条件は、数字がすべて一致しました（simpy 4.1.2）。

### v2.8 と v2.9 の違い compare_v28_v29.txt

```text
試算 v2.8 → v2.9 の違い（2026-10-30。全32,850条件・各40回。v2.9 は Mac とクラウドで分けて回し、重なった8,000条件の数字はすべて一致）

変えたこと：会計のスタッフの頼みを、食べ終えてレジに並んだ時刻の古さで並べる。会計が終わると先にレジを空け、次の組の頼みが並んでからスタッフの手を放す（会計の係がそのまま次を取る）。Unity と同じ頼み方（依頼31）。エイイチの実感（フロントはレジと案内を優先していた）で決めた

印の数（レジ1つ／レジ2つ、各16,425条件）
- 店が回らない（席は空いているのに10分以上）：1,922 → 2,617 ／ 2,880 → 2,929
- レジが詰まる（会計待ち10分以上）：5,372 → 2,626 ／ 490 → 45
平均の差（v2.9 − v2.8）：入店待ち +0.35分・席は空いているのに待った +1.07分・会計待ち −2.18分・料理の待ち +0.04分

Unity と比べた（ロボット0台の例。入店待ち／席は空いているのに待った／会計待ち、分）
- ③普通：v2.8 3.3/1.0/12.3 → v2.9 4.1/2.7/4.8　Unity 4.1/2.9/4.5
- ⑤普通：v2.8 6.2/1.5/18.8 → v2.9 7.5/5.5/6.1　Unity 8.1/6.5/5.7
- ③混んでいる：v2.8 13.0/1.5/15.4 → v2.9 12.9/5.2/6.5　Unity 19.0/8.9/5.7
（54条件の表は quick_compare_unity54.txt）

LP（/serving-robot-one-less-staff）で変わる数字
- 最初の画面（③・42組/時・ロボット0台→2台）：店舗が回る人数 3→2人 が 3→3人。入店待ち 3.3→1.2分 が 4.1→1.1分
  ③ スタッフ2人・2台：席は空いているのに待った 8.3 → 18.6分（v2.8 は会計待ちが24.2分で、卓に座ったまま待つ客が片付けの仕事を遅らせていた）
- 2台入れても人が減らない：99条件のうち45 → 38条件
- 1人が歩く距離：約40% → 約41%減
- 図面のある店の入店待ち（0台→2台）：③ 3.3→1.2 が 4.1→1.1／④ 10.4→3.5 が 10.9→5.6／⑤ 6.2→3.7 が 7.5→2.4／⑥ 6.9→1.9 が 7.9→2.1／⑦ 2.8→2.0 が 3.1→1.6／② 2.6→1.2 が 2.6→1.3

ページの確かめ：全37,800画面を描いて欠け・undefined・NaN なし（test_page_v28.js）
```

レジ1つの店では、「店が回らない」が 1,922 → 2,617条件に増え、「レジが詰まる」が 5,372 → 2,626条件に減りました。会計で隠れていた詰まりが、片付けと案内の詰まりとして出てきた形です。


## 作業ログ ― 試算のページと LP を v2.9 にして公開した

### 試算のページに v2.9 の結果を入れる build_page_v29.py

結果の行だけを入れ替え、見出しの版を v2.9 に、前提の「会計とレジ」に v2.9 の決まりを書きました。全37,800画面を描いて、欠け・undefined・NaN がないことを確かめました（`test_page_v28.js`）。

```python
"""試算 v2.9（会計のスタッフの頼みを、レジに並んだ時刻で並べる版）の結果を、公開中の試算のページに入れる（2026-10-30）
使い方: python3 build_page_v29.py <公開中のページ> <v2.9 の results.json> <出力するページ>
- const D：結果の行（r）だけを v2.9 に入れ替える。条件の並び・列・店の情報・平面図は公開中のまま
- 画面：見出しの版を v2.9 にする。前提の「会計とレジ」に v2.9 の決まりを書く
- 費用の欄・LP 用の色・使われ方の記録は公開中のまま"""
import json, sys
import haizen_sim as h

live, res, out = sys.argv[1:4]
s = open(live, encoding="utf-8").read()
N = json.load(open(res, encoding="utf-8"))
assert N["common"] == json.loads(json.dumps(h.COMMON)), "結果が今の haizen_sim.py の前提と違う"
assert N["common"]["model"] == "v2.9"

lines = s.split("\n")
idx = [i for i, l in enumerate(lines) if l.startswith("const D = ")]
assert len(idx) == 1
D = json.loads(lines[idx[0]][len("const D = "):].rstrip(";"))
K = D["k"]
cond = K[:8]
mets = K[8:-1]
assert cond == ["store", "staff", "robots", "arrivals_per_h", "mode", "trays", "fragile_by", "registers"] and K[-1] == "ng"
conds = list(h.conditions())
rows_by_key = {h.key(r): r for r in N["rows"]}
assert len(rows_by_key) == len(conds) == len(D["r"]), (len(rows_by_key), len(conds), len(D["r"]))
new = []
for old in D["r"]:
    c = dict(zip(cond, old[:8]))
    r = rows_by_key[h.key(c)]
    new.append(old[:8] + [round(r[k], 3) for k in mets] + [1 if r["staff_wait_mean"] >= h.NG_MIN else 0])
D["r"] = new
assert {k: v for k, v in D["common"].items() if k != "model"} == {k: v for k, v in N["common"].items() if k != "model"}
D["common"]["model"] = "v2.9"
lines[idx[0]] = "const D = " + json.dumps(D, ensure_ascii=False, separators=(",", ":")) + ";"
s = "\n".join(lines)

def rep(a, b):
    global s
    assert s.count(a) == 1, (a, s.count(a))
    s = s.replace(a, b)

rep("数字はすべて仮置き（試作 v2.8）", "数字はすべて仮置き（試作 v2.9）")
rep("会計はレジ1つにつき1組ずつ。食べ終えた組は卓に座ったままレジの順番を待ち、順番が来たらスタッフの手が空くのを待って会計する。",
    "会計はレジ1つにつき1組ずつ。食べ終えた組は卓に座ったままレジの順番を待つ。会計のスタッフは、食べ終えてレジに並んだ順に付き、同じ優先の配膳・案内・呼び出しより先になる。会計が終わると、同じスタッフがそのまま次の組の会計に付く（v2.9 から。それまでは、レジの順番が来てからスタッフを頼んだので、ほかの仕事の後ろに並び、そのあいだレジが止まっていた）。")
open(out, "w", encoding="utf-8").write(s)
print(out, len(new), "rows")
```

```
rendered 37800 / レジが詰まるが出た画面 3365 / 回らないが出た画面 6822 / 5台以上の注意 10800
```

### LP の最初の画面は「3人 → 2人」から「3人 → 3人」に変わる

LP（/serving-robot-one-less-staff）の数字は、ビルドのときに試算のページのデータから読みます。v2.9 で作り直すと、最初の画面（③に2台）の「店舗が回る人数」が 3人 → 2人 から 3人 → 3人 になりました。③のスタッフ2人・2台では、席は空いているのに待つ時間が 8.3分 → 18.6分です。v2.8 では会計待ちが24.2分あり、お客さんが卓に座ったまま待つあいだ片付けの仕事が入ってこなかったので、2人でも回っているように見えていました。

| LP の数字 | v2.8 | v2.9 |
|---|---|---|
| 最初の画面：店舗が回る人数（③・0台 → 2台） | 3人 → 2人 | 3人 → 3人 |
| 最初の画面：入店待ち | 3.3分 → 1.2分 | 4.1分 → 1.1分 |
| 2台入れても人が減らない条件 | 99条件のうち45 | 99条件のうち38 |
| 人数そのままで、1人が歩く距離 | 約40%減 | 約41%減 |
| ④の入店待ち（0台 → 2台） | 10.4 → 3.5分 | 10.9 → 5.6分 |
| ⑤の入店待ち（0台 → 2台） | 6.2 → 3.7分 | 7.5 → 2.4分 |

公開前に、そのまま出すか、最初の画面の例を差し替えるかを相談し、そのまま公開すると決めました。ロボットを入れても人件費が浮かない結果も正直に見せる、という前に決めた方針のままです。試算のページと、Unity が読む条件のファイル（条件は54件のまま、試算の値だけ v2.9）を一緒に公開しました（p2m-site commit c05dc77）。

### 最初の画面の「ひと月の差し引き」を、減った人数で計算する形に直した

最初の画面の「ひと月の差し引き −0.3万円」は、「1人減らせた」として、1人分の人件費からロボット2台の月額を引いた数字でした。人数が 3人 → 3人 の隣に置くと、人は減らないのに −0.3万円で済む、と読めてしまいます。減った人数分の人件費 − ロボット2台の月額、で出すように直しました。人数が減らないので、月額の14.4万円がそのままかかります（−14.4万円）。下の費用の節（1人減らせたときの計算）はそのまま残しました（p2m-site commit 0f6074b）。

```diff
commit 0f6074b4f7d4c07802718e149b7bbd89cb139085
Author: PixelstoMotors <studio@adaptdesign.jp>
Date:   Fri Oct 30 10:36:24 2026 +0900

    LP：最初の画面の差し引きを、試算で減った人数分の人件費 − ロボット2台の月額にする（人数が減らなければ月額がそのままかかる）
    
    Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01LevEXVNKSg51fWLydNyZFf

diff --git a/src/pages/serving-robot-one-less-staff.astro b/src/pages/serving-robot-one-less-staff.astro
index 0158afa..0e02d53 100644
--- a/src/pages/serving-robot-one-less-staff.astro
+++ b/src/pages/serving-robot-one-less-staff.astro
@@ -51,6 +51,8 @@ const planMax = Math.max(...plans.map((p) => p.from));
 const p1 = plans.find((p) => p.key === 'fam_p1')!;
 const p1Store = STORES.fam_p1, p1Arr = p1Store.arrivals[1];
 const p1Staff0 = minStaff('fam_p1', 0, p1Arr)!, p1Staff2 = minStaff('fam_p1', 2, p1Arr)!;
+// 最初の画面の差し引き：試算で減った人数分の人件費 − ロボット2台の月額。人数が減らなければ月額がそのままかかる（2026-10-30。試算 v2.9 で ③ が 3人 → 3人 になったため）
+const p1Net = savedPeak * Math.max(p1Staff0 - p1Staff2, 0) - cost2;
 const MENU = [
   { n: '01', id: 'trouble', t: '導入後のつまずき' }, { n: '02', id: 'know', t: '導入前に分かること' }, { n: '03', id: 'shape', t: 'なぜ自分の店舗でやるのか' },
   { n: '04', id: 'sim', t: 'まず、近い店舗で試す' }, { n: '05', id: 'flow', t: '頼むときの流れ' }, { n: '06', id: 'roles', t: '立場ごとの使い方' }, { n: '07', id: 'limits', t: 'この試算で言えないこと' },
@@ -71,7 +73,7 @@ const MENU = [
         <dl>
           <div><dt>店舗が回る人数</dt><dd><b>{p1Staff0}</b><small>人</small><i>→</i><em>{p1Staff2}</em><small>人</small></dd></div>
           <div><dt>入店待ち</dt><dd><b>{min1(p1.from)}</b><small>分</small><i>→</i><em>{min1(p1.to)}</em><small>分</small></dd></div>
-          <div><dt>ひと月の差し引き</dt><dd><em>{man(savedPeak - cost2)}</em><small>万円</small></dd></div>
+          <div><dt>ひと月の差し引き</dt><dd><em>{man(p1Net)}</em><small>万円</small></dd></div>
         </dl>
         <p class="ft-note">効率重視の店舗・1時間{p1Arr}組・ロボット0台 → 2台（月額）</p>
       </figure>
```

- ねらい：最初の画面の3つの数字（人数・入店待ち・お金）を、同じ条件の同じ結果から出す
- 変わったこと：ひと月の差し引き −0.3万円 → −14.4万円（人数 3人 → 3人 のとき）
- 見込める効果：ロボットを入れると「人は減らないが、入店待ちは 4.1分 → 1.1分に縮む」ことが、お金の数字と食い違わずに伝わる。読む人がどう受け取ったかは、LP からの相談の数で見ていく

### simulation/v2_9/README.md

````markdown
# 配膳ロボットの試算 v2.9（2026-10-30 回す・公開）

会計のスタッフを、客が食べ終えてレジに並んだ順に付ける版。

## なぜ直したか

混み具合を広げた Unity の夜（20261106_busy・360回）で、混んでいるときの「席は空いているのに待った」が試算の3〜6倍になった。依頼31（Claude Code が Unity のコードと記録を読んだ調べ）で、仕事の優先順は試算と Unity で同じだが、会計のスタッフを頼む時刻が違うと分かった。

- v2.8 の試算：レジの順番が来てからスタッフを頼む。ほかの優先1（案内・配膳・呼び出し）の後ろに並ぶので、そのあいだレジが止まる（③で、レジを持っていた時間の12%）。食べ終えた客が卓に座ったまま会計を待ち、片付けの仕事が入ってこない
- Unity：客がレジの並ぶ点に着いたときに頼む。前の会計が終わるとすぐ取られ、会計のスタッフがほぼいつも1人レジにいる

エイイチの実感（フロントをしていたころ、基本はレジと案内を優先して仕事をしていた）で、Unity の頼み方を試算に入れると決めた（2026-10-30）。調べと集計は KIOXIA の service_sim/unity/night/20261106_busy/staff_breakdown/ と、unity/claude_code_reports/20261030_0823_staff_job_choice.txt。

## 決めたこと

- 会計のスタッフの頼みは、食べ終えてレジに並んだ時刻の古さで並ぶ（優先は1のまま）。同じ優先の案内・配膳・呼び出しより先になる
- スタッフが会計に付くのは、これまでどおりレジが空いてから（レジ1つにつき1組ずつ・90秒）
- 会計が終わると先にレジを空け、次の組の頼みが並んでからスタッフの手を放す（会計の係がそのまま次の会計を取る）
- 「店が回らない」「レジが詰まる」の決め方、LP の「店舗が回る人数」の決め方は変えていない
- LP の最初の画面（③に2台）は「3人 → 2人」から「3人 → 3人」に変わる。人件費が浮かない結果も正直に見せる方針のまま、そのまま公開すると決めた（エイイチ）

## 確かめたこと

- Unity で回している54条件で先に比べた（quick_compare_unity54.txt）。普通の混み具合のロボット0台では、入店待ち・席は空いているのに待った・会計待ちがほぼ Unity と同じになった（③ 4.1/2.7/4.8 分、Unity 4.1/2.9/4.5 分）
- 全32,850条件を Mac とクラウドで分けて回し、重なった8,000条件の数字はすべて一致した（simpy 4.1.2）
- 全37,800画面を描いて、欠け・undefined・NaN がない（v2_8 の test_page_v28.js）
- v2.8 との違いは compare_v28_v29.txt

## ファイル

- haizen_sim.py：試算 v2.9（layouts/ は v2_8 と同じ店の形 08db8a0d067c）
- results.json：全32,850条件（40回の平均）。shards/ と merge_shards_v29.py で分けて回したものをまとめた
- build_page_v29.py → serving-robot.v29.html（p2m-site の public/sim/serving-robot.html）
- export_params.py → serving-robot-params.json（p2m-site の public/sim/serving-robot-params.json。条件は54件のまま、試算の値だけ v2.9）
- quick_compare_unity54.py：Unity の54条件で v2.8・v2.9・Unity を並べる
- run_shard.py・run_timed_rev.py：分けて回す道具

## 作り方

```
python3 run_shard.py <k> <n> shards/<名前>.json     # 分けて回す（Mac 側は1回3分までなので区切る）
python3 merge_shards_v29.py                          # → results.json
python3 build_page_v29.py <公開中のページ> results.json serving-robot.v29.html
python3 export_params.py results.json serving-robot-params.json
node ../v2_8/test_page_v28.js serving-robot.v29.html
```

- 公開済みの記事の数字は前の版のまま
````


## 作業ログ ― ここで区切り、残りのずれ2つは次に回す

段階4（サイトとのずれを調べ、取得項目や店の形を見直す）の区切りを「ずれ3つそれぞれについて、サイトの試算を直すか記録だけにするかを決めたら区切る」と決めていました。そのうち1つ（混むと片付けが詰まる）は、試算を v2.9 に直して決着しました。

| ずれ | 今の扱い |
|---|---|
| 混むと片付けが詰まる | 試算を v2.9 に直した（会計のスタッフをレジに並んだ順に付ける） |
| 待機場所のまわりの詰まり（6店すべて） | 次に、待機場所の置き方を店の形を作る決まりとして考える |
| ロボットがいるとスタッフの仕事が増える（④2台で1組あたり約46%多い） | 次に、ロボットを動かしに行く時間・待ってあきらめる時間・よける時間を、試算に入れるかを考える |
| 混んでいるとき、v2.9 でも Unity のほうが入店待ちが長い | 片付け1回の長さの差（135秒・123秒）として記録 |

次に Unity へ夜の依頼を出すときは、v2.9 の条件のファイルを先に写してもらいます。スタッフの人数を広げる夜も、段階4の中で1回回します。
