---
title: "サービスロボット導入シミュレーション Log#29 | 狭いファミレスでは、配膳ロボットは1台まで"
slug: serving-robot-one-per-narrow-restaurant
date: 2026-11-02
day: 423
meta_description: "通路の狭いファミレスでは配膳ロボットは1台が上限だった。Unity で再現した28卓の店で2台にすると入店待ちが約2倍に延び、ロボットを動かしに行く回数が2時間で54回に。1本道に1台ずつ入る決まりでも1台には届かない。"
tags: [サービスロボット導入シミュレーション, 配膳ロボット, 飲食店, ファミレス, Unity, シミュレーション, 店舗レイアウト, 通路]
series: "導入シミュレーション"
---

通路の狭いファミレスでは、配膳ロボットは1台が上限だった。Unity（ゲームや3Dの映像を作るソフト）でファミレスを再現したシミュレーションで、28卓・スタッフ2人の店に配膳ロボットを2台入れると、入店待ちは1台のときの約2倍（13.8分 → 25.1分）に延び、動けなくなったロボットをスタッフが動かしに行く回数は、昼のピーク2時間で13回から54回に増えた。**狭い店で2台目が足を引っぱる原因は、ロボットどうしが1本道で向かい合ってふさぎ合うことで、「1本道には1台ずつ入る」という決まりにすると不利は半分ほどに縮むが、それでも1台には届かなかった。**

この結果で、配膳ロボットの台数を決める前に言えることが増えました。

- これから入れる店は、台数を「店の通路」で決められる。 卓の数や混み具合だけでなく、1本道の通路が多い店では2台目を足しても待ちは縮まず、スタッフの手間が増える。ロボットが止まった場所を店の平面図に重ねると、どの通路で詰まるかが見える
- もう2台入れている狭い店には、席を減らさずにできる直し方がある。 「1本道に向かいからロボットが来ていたら、手前で待つ」という決まりにすると、ロボットの世話（動かしに行く回数）は3〜4割減り、料理は1〜2分早く届いた（どちらもシミュレーションの値）
- シミュレーションの比べ方を変えるだけで、言えることが3組から37組に増えた。 同じ条件・同じ客の来方どうしで差を取る比べ方に変えると、夜に回す数（1条件10回）を増やさずに、ロボットを1台足した効果のある・なしがはっきりした。4つのやり方 × 2つの混み具合 × 10回＝80回の比べは、Mac が夜のうちに約2時間で回した

きっかけは、同じ条件で回しても Unity の数字が毎回少し違うことでした。乱数の種（客がいつ来るか・何人組かを決める数字の出発点。同じ種なら同じ客の来方になる）をそろえても、ロボットが動けなくなった回数が20回の時もあれば35回の時もあります。これでは「ロボットを2台にしたら待ちが延びた」と出ても、本当の差なのか、たまたまなのかが言えません。調べると、ずれは種を変えたときの差の約10分の1で、同じ種どうしで比べれば十分に見分けられると分かりました。そこで、店の人が知りたいこと（ロボットの煩わしさ・スタッフの負担・お客さんの待ち・回転・安定）の順に、6つの店の夜の結果を並べ直し、いちばん結果の悪かった④の店で、ロボットどうしが向かい合わないための3つの案を試しました。

| やったこと | なぜ | 結果 |
|---|---|---|
| 同じ条件・同じ種でくり返した回を集め、ずれの大きさを測った | 夜の結果の差が本当の差か、ずれかを見分けるため | ずれは種を変えたときの約10分の1。同じ種どうしで比べると、はっきり言える組が76組中3組 → 37組 |
| 店で測る観察シートに、10回測ったら「早いとき・遅いとき」も出す表示を足した | 実際の店の作業時間のばらつきを、あとで試算に入れられるようにするため | 試算には入れず、記録として残す形で公開 |
| ロボットが止まった場所を、店の平面図に重ねた | どこで・なぜ詰まるかを店の人に見せるため | ④では島の下の狭い通路と1本道に集中。相手の半分はもう1台のロボット |
| スタッフの負担と待ち時間を、6店 × 0〜2台で並べた | 店が望むこと（負担・煩わしさ・待ち）を1つの表で見るため | 1台目は歩く距離を2〜3割減らす。2台目は、動かしに行く回数を一気に増やす |
| ④で、一方通行・1本道は1台ずつ・席を外したよけ場所の3案を、夜に80回回した | 2台を入れてしまった店がとれる打ち手を確かめるため | 1本道は1台ずつが最もよく効く。一方通行は混むと逆に悪くなる。どの案も1台には届かない |

下の図で、④の店の0台・1台・2台と3つの案を切り替えると、入店待ち・ロボットを動かしに行った回数・料理が届くまでの時間が変わります。

【図解】LOG423_narrow_store_robots.html

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

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


## 作業ログ ― Unity の夜の結果のずれを、同じ種どうしで比べ直した

ここから下は作業ログです。コード・依頼文・報告・出力は全量で載せます。効果の数字は、断りがない限りすべて Unity のシミュレーションで測った値で、実際の店で測った値ではありません。

### このシリーズで使う Unity・乱数の種・依頼箱

| 言葉 | 何か | このシリーズでの使い方 |
|---|---|---|
| [Unity](https://unity.com/ja) | ゲームや3Dの映像を作るソフト | ファミレスを3Dで建て、客・スタッフ・配膳ロボットを歩かせて、待ち時間や止まった回数を測る |
| サイトの試算 | 計算だけで待ち時間を出すシミュレーション（[試算のページ](https://pixelstomotors.com/sim/serving-robot.html)） | 店の形・人数・決まりの正。Unity はこれを読んで店を建てる |
| 乱数の種 | 乱数（でたらめに見える数）の出発点の数 | 種が同じなら、客がいつ来るか・何人組かが同じになる。1つの条件を種0〜9の10回で回す |
| 夜の回し | Mac が夜のうちに、画面なしで Unity を何十回も回す仕組み | 4本ずつ同時に回し、1回ごとに台帳（unity_ledger_v1.csv）へ1行書く |
| 依頼箱 | Claude が書いた依頼を、Mac の見張り係が Claude Code に渡して実行させるフォルダ | Unity のコードを変える作業と夜の回しは、ここから Claude Code が行う |
| [Claude Code](https://docs.claude.com/ja/docs/claude-code/overview) | Anthropic のプログラミング用の AI | Mac の上で Unity のコードを書き、回し、報告を書く |

### 同じ種でも Unity の数字が毎回少し違うことは、10月25日に一度調べていた

LOG418 で、道の線を消すだけの撮り直しなのに、ロボットが動けなくなった回数が20回から35回に変わりました。原因は10月25日に Claude Code が調べていて、人の位置が0.1mm違うくらいの小数の最後の桁からずれが始まること、人の道の計算の遅れや人どうしのよけ方の計算を止めてもずれが残ることまで分かっていました。そのとき「同じ種なら必ず同じ、は今は追わない。ずれとして扱い、種10個の中央値で比べる」と決めています。

今回は原因を追うのではなく、ずれの大きさを測りました。いちばん疑わしいのは、Unity が中で複数の計算を同時に進めるとき、その順番が毎回少し変わることです。Mac で同時に回す本数を増やすとずれが大きくなるのも、その見方と合います。ただし確かめてはいません。

### 同じ条件・同じ種でくり返した回を、台帳から集めた var1.py

台帳1,533行のうち、条件も種も同じでくり返した回は、10月26日の「1本ずつ5回・4本同時8回」（③・ロボット1台・種1）と、10月31日の「1本ずつ3回・4本同時3回」（④・ロボット2台・種0）、それに LOG418 の道の線あり・なしの4組でした。

```python
import csv,re,collections,statistics as st,random
L='/sessions/'+__import__('os').environ['HOME'].split('/sessions/')[1]+'/mnt/KIOXIA/r2r2r-data/service_sim/unity/unity_ledger_v1.csv' if False else __import__('os').path.expanduser('~/mnt/KIOXIA/r2r2r-data/service_sim/unity/unity_ledger_v1.csv')
R=list(csv.DictReader(open(L)))
M=['entry_wait_mean','staff_wait_mean','stuck_count','robot_share','turn_mean']
f=lambda r,m: float(r[m]) if r[m] not in('','nan') else None
# 1) within-seed repeats
def sd(x): return st.pstdev(x) if len(x)>1 else 0
print('== 同じ条件・同じ種のくり返し')
groups={'repeat1(1本ずつ3回)':'20261031_repeat1','repeat4(4本同時3回)':'20261031_repeat4','busy1(1本ずつ5回)':'20261026_busy1','busy4(4本同時8回)':'20261026_busy4'}
for n,p in groups.items():
  rs=[r for r in R if r['run_folder'].startswith(p)]
  print(n, rs[0]['store'],'staff',rs[0]['staff'],'robots',rs[0]['robots'],'seed',rs[0]['seed'])
  for m in M:
    x=[f(r,m) for r in rs]; print('   %-16s'%m, ['%.2f'%v for v in x], 'sd %.2f'%sd(x))
# rec(線あり) vs nopath(線なし) same seed same condition
print('== 線あり(cs52) と 線なし(cs53)：同じ条件・同じ種7')
A={re.sub(r'^20261031_rec_|_cs52','',r['run_folder']):r for r in R if r['run_folder'].startswith('20261031_rec') }
B={re.sub(r'^20261101_rec_nopath_|_cs53','',r['run_folder']):r for r in R if r['run_folder'].startswith('20261101_rec_nopath')}
for k in sorted(set(A)&set(B)):
  print('  ',k[:40], 'entry %.2f / %.2f'%(f(A[k],'entry_wait_mean'),f(B[k],'entry_wait_mean')),'stuck %s / %s'%(A[k]['stuck_count'],B[k]['stuck_count']))
```

```text
== 同じ条件・同じ種のくり返し
repeat1(1本ずつ3回) fam_p2 staff 2 robots 2 seed 0
   entry_wait_mean  ['18.51', '19.85', '18.41'] sd 0.65
   staff_wait_mean  ['16.74', '18.50', '16.83'] sd 0.81
   stuck_count      ['73.00', '63.00', '76.00'] sd 5.56
   robot_share      ['0.82', '0.83', '0.88'] sd 0.02
   turn_mean        ['32.97', '43.32', '36.17'] sd 4.33
repeat4(4本同時3回) fam_p2 staff 2 robots 2 seed 0
   entry_wait_mean  ['19.22', '14.59', '21.39'] sd 2.84
   staff_wait_mean  ['17.89', '13.27', '20.06'] sd 2.83
   stuck_count      ['73.00', '74.00', '92.00'] sd 8.73
   robot_share      ['0.86', '0.83', '0.81'] sd 0.02
   turn_mean        ['36.56', '32.21', '41.81'] sd 3.93
busy1(1本ずつ5回) fam_p1 staff 4 robots 1 seed 1
   entry_wait_mean  ['2.33', '2.29', '1.58', '2.31', '2.19'] sd 0.28
   staff_wait_mean  ['1.10', '1.06', '0.34', '1.08', '0.95'] sd 0.29
   stuck_count      ['5.00', '3.00', '1.00', '1.00', '1.00'] sd 1.60
   robot_share      ['0.68', '0.67', '0.70', '0.70', '0.71'] sd 0.01
   turn_mean        ['5.50', '5.08', '3.33', '6.04', '5.29'] sd 0.91
busy4(4本同時8回) fam_p1 staff 4 robots 1 seed 1
   entry_wait_mean  ['1.93', '1.65', '1.64', '2.00', '3.05', '2.82', '1.57', '1.62'] sd 0.54
   staff_wait_mean  ['0.68', '0.43', '0.39', '0.76', '1.81', '1.60', '0.36', '0.39'] sd 0.54
   stuck_count      ['5.00', '3.00', '8.00', '6.00', '11.00', '9.00', '2.00', '8.00'] sd 2.87
   robot_share      ['0.69', '0.68', '0.66', '0.66', '0.64', '0.66', '0.69', '0.65'] sd 0.02
   turn_mean        ['4.49', '3.49', '5.40', '4.56', '8.54', '6.85', '3.18', '4.80'] sd 1.66
== 線あり(cs52) と 線なし(cs53)：同じ条件・同じ種7
   a30_fam_p1_r2_s7_passon_sbret_ek_post entry 1.30 / 1.35 stuck 3 / 6
   a54_fam_p1_r2_s7_passon_sbret_ek_post entry 14.11 / 14.02 stuck 11 / 6
   st3_fam_p1_r2_s7_passon_sbret_ek_post entry 12.41 / 13.23 stuck 34 / 31
   st5_fam_p1_r2_s7_passon_sbret_ek_post entry 2.98 / 2.72 stuck 23 / 19
```

同じ種でくり返したときの入店待ちのずれ（標準偏差）は、③の1本ずつで0.28分、④の1本ずつで0.65分でした。4本同時に回すと0.54分・2.84分に広がります。

### 種を変えたときのずれと比べた var2.py（種の中央値どうしの比べ）

夜に回した6つの店の結果から、ロボットを1台足す前と後を、種10個の中央値どうしで比べました。今の夜の判定と同じ比べ方です。

```python
import csv,re,collections,statistics as st,random,os,json
L=os.path.expanduser('~/mnt/KIOXIA/r2r2r-data/service_sim/unity/unity_ledger_v1.csv')
R=list(csv.DictReader(open(L)))
random.seed(0)
NAME={'fam_p0':'②','fam_p1':'③','fam_p2':'④','fam_p3':'⑤','fam_p4':'⑥','fam_p5':'⑦'}
G=collections.defaultdict(list)
for r in R:
  if '_night' not in r['run_folder']: continue
  k=re.sub(r'_s\d+(_|$)',r'_s*\1',r['run_folder'])
  k=re.sub(r'_r\d_','_r#_',k)
  G[(k,int(r['robots']))].append(r)
def med(x): return st.median(x)
def boot(a,b,n=4000):
  d=[med(random.choices(b,k=len(b)))-med(random.choices(a,k=len(a))) for _ in range(n)]
  d.sort(); return d[int(.025*n)],d[int(.975*n)]
rows=[]
for (k,rb),rs in sorted(G.items()):
  if rb==0: continue
  base=G.get((k,rb-1))
  if not base or len(rs)<8 or len(base)<8: continue
  for m in ['entry_wait_mean']:
    a=[float(r[m]) for r in base]; b=[float(r[m]) for r in rs]
    lo,hi=boot(a,b)
    rows.append(dict(cond=k,store=NAME.get(rs[0]['store'],rs[0]['store']),staff=rs[0]['staff'],arr=rs[0]['arrivals_per_h'],step=f'{rb-1}→{rb}',n=f'{len(a)}/{len(b)}',med_a=med(a),med_b=med(b),diff=med(b)-med(a),lo=lo,hi=hi,
      sd_seed=st.pstdev(b),verdict=('縮む' if hi<0 else '延びる' if lo>0 else 'ぶれの範囲')))
json.dump(rows,open('robot_effect.json','w'),ensure_ascii=False,indent=1)
c=collections.Counter(r['verdict'] for r in rows); print(len(rows),c)
for r in rows: print('%-55s %s 人%s 客%s %s 中央 %.2f→%.2f 差%+.2f [%+.2f,%+.2f] %s  種sd %.2f'%(r['cond'][:55],r['store'],r['staff'],r['arr'],r['step'],r['med_a'],r['med_b'],r['diff'],r['lo'],r['hi'],r['verdict'],r['sd_seed']))
```

```text
76 Counter({'ぶれの範囲': 73, '延びる': 3})
20261027_night_cs21_fam_p1_r#_s*                        ③ 人4 客42 0→1 中央 4.08→4.56 差+0.48 [-6.87,+4.36] ぶれの範囲  種sd 4.22
20261027_night_cs21_fam_p1_r#_s*                        ③ 人4 客42 1→2 中央 4.56→3.04 差-1.53 [-4.64,+4.91] ぶれの範囲  種sd 4.62
20261027_night_cs21_fam_p5_r#_s*                        ⑦ 人4 客42 0→1 中央 4.22→4.98 差+0.76 [-6.71,+5.86] ぶれの範囲  種sd 3.91
20261027_night_cs21_fam_p5_r#_s*                        ⑦ 人4 客42 1→2 中央 4.98→2.89 差-2.09 [-7.19,+5.43] ぶれの範囲  種sd 3.82
20261028_night_cs25_fam_p1_r#_s*                        ③ 人4 客42 0→1 中央 4.19→3.04 差-1.15 [-7.14,+6.59] ぶれの範囲  種sd 4.32
20261028_night_cs25_fam_p1_r#_s*                        ③ 人4 客42 1→2 中央 3.04→2.98 差-0.06 [-7.28,+5.52] ぶれの範囲  種sd 3.97
20261028_night_cs25_fam_p5_r#_s*                        ⑦ 人4 客42 0→1 中央 4.49→3.60 差-0.89 [-7.11,+4.51] ぶれの範囲  種sd 3.68
20261028_night_cs25_fam_p5_r#_s*                        ⑦ 人4 客42 1→2 中央 3.60→3.57 差-0.02 [-5.63,+5.75] ぶれの範囲  種sd 4.89
20261029_night_cs26_fam_p1_r#_s*_passon                 ③ 人4 客42 0→1 中央 4.21→3.01 差-1.20 [-7.43,+3.75] ぶれの範囲  種sd 3.67
20261029_night_cs26_fam_p1_r#_s*_passon                 ③ 人4 客42 1→2 中央 3.01→4.17 差+1.16 [-3.85,+9.28] ぶれの範囲  種sd 4.53
20261029_night_cs26_fam_p5_r#_s*_passon                 ⑦ 人4 客42 0→1 中央 4.48→3.84 差-0.65 [-7.29,+8.16] ぶれの範囲  種sd 4.87
20261029_night_cs26_fam_p5_r#_s*_passon                 ⑦ 人4 客42 1→2 中央 3.84→4.81 差+0.97 [-8.11,+5.66] ぶれの範囲  種sd 3.52
20261030_night_cs29_fam_p2_r#_s*_passon                 ④ 人2 客28 0→1 中央 12.95→13.78 差+0.83 [-5.48,+9.82] ぶれの範囲  種sd 8.40
20261030_night_cs29_fam_p2_r#_s*_passon                 ④ 人2 客28 1→2 中央 13.78→26.85 差+13.06 [+1.91,+22.05] 延びる  種sd 11.06
20261101_night_cs33_fam_p1_r#_s*_passon_ruleb_sbret     ③ 人4 客42 0→1 中央 4.22→4.10 差-0.12 [-7.10,+5.43] ぶれの範囲  種sd 4.65
20261101_night_cs33_fam_p1_r#_s*_passon_ruleb_sbret     ③ 人4 客42 1→2 中央 4.10→4.13 差+0.03 [-6.39,+5.47] ぶれの範囲  種sd 4.59
20261101_night_cs33_fam_p5_r#_s*_passon_ruleb_sbret     ⑦ 人4 客42 0→1 中央 4.41→3.00 差-1.41 [-7.64,+5.36] ぶれの範囲  種sd 4.69
20261101_night_cs33_fam_p5_r#_s*_passon_ruleb_sbret     ⑦ 人4 客42 1→2 中央 3.00→4.02 差+1.01 [-5.28,+9.29] ぶれの範囲  種sd 5.30
20261102_night_cs34_fam_p1_r#_s*_passon_sbret           ③ 人4 客42 0→1 中央 4.09→5.00 差+0.91 [-6.40,+5.72] ぶれの範囲  種sd 4.16
20261102_night_cs34_fam_p1_r#_s*_passon_sbret           ③ 人4 客42 1→2 中央 5.00→3.99 差-1.01 [-5.64,+3.98] ぶれの範囲  種sd 4.23
20261102_night_cs34_fam_p5_r#_s*_passon_sbret           ⑦ 人4 客42 0→1 中央 4.49→3.22 差-1.27 [-7.25,+5.08] ぶれの範囲  種sd 4.65
20261102_night_cs34_fam_p5_r#_s*_passon_sbret           ⑦ 人4 客42 1→2 中央 3.22→3.56 差+0.34 [-6.04,+5.98] ぶれの範囲  種sd 4.88
20261103_night_cs36_fam_p3_r#_s*_passon_sbret           ⑤ 人4 客46 0→1 中央 8.14→6.60 差-1.54 [-9.10,+5.34] ぶれの範囲  種sd 5.08
20261103_night_cs36_fam_p3_r#_s*_passon_sbret           ⑤ 人4 客46 1→2 中央 6.60→5.46 差-1.15 [-6.20,+9.83] ぶれの範囲  種sd 6.68
20261104_night_cs38_fam_p4_r#_s*_passon_sbret           ⑥ 人3 客38 0→1 中央 9.60→6.04 差-3.56 [-12.75,+3.44] ぶれの範囲  種sd 4.63
20261104_night_cs38_fam_p4_r#_s*_passon_sbret           ⑥ 人3 客38 1→2 中央 6.04→4.83 差-1.21 [-6.69,+7.12] ぶれの範囲  種sd 4.73
20261105_night_cs40_fam_p0_r#_s*_passon_sbret           ② 人2 客19 0→1 中央 1.96→1.69 差-0.27 [-6.59,+4.64] ぶれの範囲  種sd 2.80
20261105_night_cs40_fam_p0_r#_s*_passon_sbret           ② 人2 客19 1→2 中央 1.69→2.10 差+0.41 [-4.88,+6.24] ぶれの範囲  種sd 3.38
20261106_night_a14_cs44_fam_p0_r#_s*_passon_sbret       ② 人2 客14 0→1 中央 1.28→1.25 差-0.04 [-0.64,+0.30] ぶれの範囲  種sd 0.33
20261106_night_a14_cs44_fam_p0_r#_s*_passon_sbret       ② 人2 客14 1→2 中央 1.25→1.45 差+0.21 [-0.23,+0.47] ぶれの範囲  種sd 0.30
20261106_night_a20_cs44_fam_p2_r#_s*_passon_sbret       ④ 人2 客20 0→1 中央 1.74→2.50 差+0.76 [-0.61,+2.61] ぶれの範囲  種sd 1.35
20261106_night_a20_cs44_fam_p2_r#_s*_passon_sbret       ④ 人2 客20 1→2 中央 2.50→4.05 差+1.55 [-0.58,+4.94] ぶれの範囲  種sd 4.19
20261106_night_a24_cs44_fam_p0_r#_s*_passon_sbret       ② 人2 客24 0→1 中央 11.17→11.37 差+0.20 [-9.44,+9.01] ぶれの範囲  種sd 7.40
20261106_night_a24_cs44_fam_p0_r#_s*_passon_sbret       ② 人2 客24 1→2 中央 11.37→10.69 差-0.68 [-8.86,+8.78] ぶれの範囲  種sd 6.86
20261106_night_a27_cs44_fam_p4_r#_s*_passon_sbret       ⑥ 人3 客27 0→1 中央 1.54→1.37 差-0.17 [-0.34,+0.14] ぶれの範囲  種sd 0.23
20261106_night_a27_cs44_fam_p4_r#_s*_passon_sbret       ⑥ 人3 客27 1→2 中央 1.37→1.35 差-0.02 [-0.29,+0.11] ぶれの範囲  種sd 0.17
20261106_night_a30_cs44_fam_p1_r#_s*_passon_sbret       ③ 人4 客30 0→1 中央 1.34→1.42 差+0.08 [-0.07,+0.23] ぶれの範囲  種sd 0.14
20261106_night_a30_cs44_fam_p1_r#_s*_passon_sbret       ③ 人4 客30 1→2 中央 1.42→1.42 差+0.00 [-0.14,+0.09] ぶれの範囲  種sd 0.11
20261106_night_a30_cs44_fam_p5_r#_s*_passon_sbret       ⑦ 人4 客30 0→1 中央 1.33→1.40 差+0.07 [-0.04,+0.22] ぶれの範囲  種sd 0.18
20261106_night_a30_cs44_fam_p5_r#_s*_passon_sbret       ⑦ 人4 客30 1→2 中央 1.40→1.43 差+0.03 [-0.13,+0.26] ぶれの範囲  種sd 0.21
20261106_night_a33_cs44_fam_p3_r#_s*_passon_sbret       ⑤ 人4 客33 0→1 中央 1.40→1.60 差+0.20 [-0.03,+0.38] ぶれの範囲  種sd 0.16
20261106_night_a33_cs44_fam_p3_r#_s*_passon_sbret       ⑤ 人4 客33 1→2 中央 1.60→1.54 差-0.06 [-0.24,+0.14] ぶれの範囲  種sd 0.31
20261106_night_a36_cs44_fam_p2_r#_s*_passon_sbret       ④ 人2 客36 0→1 中央 32.87→31.57 差-1.29 [-18.57,+17.12] ぶれの範囲  種sd 13.31
20261106_night_a36_cs44_fam_p2_r#_s*_passon_sbret       ④ 人2 客36 1→2 中央 31.57→45.92 差+14.35 [-1.50,+27.53] ぶれの範囲  種sd 15.57
20261106_night_a49_cs44_fam_p4_r#_s*_passon_sbret       ⑥ 人3 客49 0→1 中央 30.08→21.62 差-8.46 [-19.40,+3.45] ぶれの範囲  種sd 9.06
20261106_night_a49_cs44_fam_p4_r#_s*_passon_sbret       ⑥ 人3 客49 1→2 中央 21.62→19.76 差-1.86 [-11.69,+9.42] ぶれの範囲  種sd 9.27
20261106_night_a54_cs44_fam_p1_r#_s*_passon_sbret       ③ 人4 客54 0→1 中央 19.02→16.40 差-2.62 [-11.17,+6.30] ぶれの範囲  種sd 9.00
20261106_night_a54_cs44_fam_p1_r#_s*_passon_sbret       ③ 人4 客54 1→2 中央 16.40→15.81 差-0.59 [-9.32,+9.71] ぶれの範囲  種sd 8.60
20261106_night_a54_cs44_fam_p5_r#_s*_passon_sbret       ⑦ 人4 客54 0→1 中央 19.35→17.68 差-1.67 [-11.19,+6.32] ぶれの範囲  種sd 8.12
20261106_night_a54_cs44_fam_p5_r#_s*_passon_sbret       ⑦ 人4 客54 1→2 中央 17.68→16.57 差-1.12 [-10.07,+7.49] ぶれの範囲  種sd 7.37
20261106_night_a59_cs44_fam_p3_r#_s*_passon_sbret       ⑤ 人4 客59 0→1 中央 24.79→23.70 差-1.09 [-11.34,+7.89] ぶれの範囲  種sd 8.66
20261106_night_a59_cs44_fam_p3_r#_s*_passon_sbret       ⑤ 人4 客59 1→2 中央 23.70→24.37 差+0.67 [-9.36,+11.33] ぶれの範囲  種sd 9.65
20261107_night_st1_cs47_fam_p0_r#_s*_passon_sbret       ② 人1 客19 0→1 中央 26.01→17.83 差-8.18 [-28.19,+10.41] ぶれの範囲  種sd 12.56
20261107_night_st1_cs47_fam_p0_r#_s*_passon_sbret       ② 人1 客19 1→2 中央 17.83→23.98 差+6.15 [-17.30,+18.96] ぶれの範囲  種sd 12.96
20261107_night_st1_cs47_fam_p2_r#_s*_passon_sbret       ④ 人1 客28 0→1 中央 59.95→50.27 差-9.68 [-26.83,+5.05] ぶれの範囲  種sd 15.46
20261107_night_st1_cs47_fam_p2_r#_s*_passon_sbret       ④ 人1 客28 1→2 中央 50.27→70.97 差+20.70 [+5.77,+38.70] 延びる  種sd 16.80
20261107_night_st2_cs47_fam_p4_r#_s*_passon_sbret       ⑥ 人2 客38 0→1 中央 29.67→17.50 差-12.18 [-26.48,+2.08] ぶれの範囲  種sd 10.08
20261107_night_st2_cs47_fam_p4_r#_s*_passon_sbret       ⑥ 人2 客38 1→2 中央 17.50→19.03 差+1.53 [-12.64,+15.16] ぶれの範囲  種sd 10.22
20261107_night_st3_cs47_fam_p0_r#_s*_passon_sbret       ② 人3 客19 0→1 中央 1.42→1.45 差+0.03 [-2.51,+2.81] ぶれの範囲  種sd 1.87
20261107_night_st3_cs47_fam_p0_r#_s*_passon_sbret       ② 人3 客19 1→2 中央 1.45→1.55 差+0.11 [-2.61,+2.63] ぶれの範囲  種sd 1.54
20261107_night_st3_cs47_fam_p1_r#_s*_passon_sbret       ③ 人3 客42 0→1 中央 14.79→9.44 差-5.35 [-15.52,+8.51] ぶれの範囲  種sd 8.11
20261107_night_st3_cs47_fam_p1_r#_s*_passon_sbret       ③ 人3 客42 1→2 中央 9.44→13.07 差+3.63 [-11.63,+10.01] ぶれの範囲  種sd 7.60
20261107_night_st3_cs47_fam_p2_r#_s*_passon_sbret       ④ 人3 客28 0→1 中央 2.89→5.23 差+2.34 [-1.39,+5.73] ぶれの範囲  種sd 4.06
20261107_night_st3_cs47_fam_p2_r#_s*_passon_sbret       ④ 人3 客28 1→2 中央 5.23→12.66 差+7.43 [+2.62,+12.44] 延びる  種sd 5.48
20261107_night_st3_cs47_fam_p3_r#_s*_passon_sbret       ⑤ 人3 客46 0→1 中央 21.63→16.76 差-4.87 [-16.37,+6.41] ぶれの範囲  種sd 8.73
20261107_night_st3_cs47_fam_p3_r#_s*_passon_sbret       ⑤ 人3 客46 1→2 中央 16.76→17.47 差+0.71 [-8.58,+14.27] ぶれの範囲  種sd 9.31
20261107_night_st3_cs47_fam_p5_r#_s*_passon_sbret       ⑦ 人3 客42 0→1 中央 14.87→12.27 差-2.60 [-15.35,+7.89] ぶれの範囲  種sd 8.69
20261107_night_st3_cs47_fam_p5_r#_s*_passon_sbret       ⑦ 人3 客42 1→2 中央 12.27→10.18 差-2.09 [-12.48,+9.57] ぶれの範囲  種sd 6.78
20261107_night_st4_cs47_fam_p4_r#_s*_passon_sbret       ⑥ 人4 客38 0→1 中央 2.16→1.69 差-0.47 [-3.80,+2.77] ぶれの範囲  種sd 2.27
20261107_night_st4_cs47_fam_p4_r#_s*_passon_sbret       ⑥ 人4 客38 1→2 中央 1.69→1.65 差-0.05 [-3.35,+3.05] ぶれの範囲  種sd 2.20
20261107_night_st5_cs47_fam_p1_r#_s*_passon_sbret       ③ 人5 客42 0→1 中央 1.85→2.61 差+0.76 [-2.56,+4.62] ぶれの範囲  種sd 3.36
20261107_night_st5_cs47_fam_p1_r#_s*_passon_sbret       ③ 人5 客42 1→2 中央 2.61→3.06 差+0.45 [-4.51,+4.40] ぶれの範囲  種sd 3.08
20261107_night_st5_cs47_fam_p3_r#_s*_passon_sbret       ⑤ 人5 客46 0→1 中央 2.32→3.38 差+1.06 [-3.03,+5.00] ぶれの範囲  種sd 3.80
20261107_night_st5_cs47_fam_p3_r#_s*_passon_sbret       ⑤ 人5 客46 1→2 中央 3.38→4.95 差+1.56 [-3.76,+5.50] ぶれの範囲  種sd 3.84
20261107_night_st5_cs47_fam_p5_r#_s*_passon_sbret       ⑦ 人5 客42 0→1 中央 1.86→2.41 差+0.55 [-2.69,+4.62] ぶれの範囲  種sd 3.28
20261107_night_st5_cs47_fam_p5_r#_s*_passon_sbret       ⑦ 人5 客42 1→2 中央 2.41→2.70 差+0.29 [-3.89,+5.85] ぶれの範囲  種sd 3.40
```

種を変えたときの入店待ちのずれ（種sd）は、③の普通で約4〜5分、④では8〜16分あります。同じ種でくり返したときのずれは、その約10分の1以下です。中央値どうしの比べでは、76組のうち「縮む」「延びる」とはっきり言えたのは3組だけで、残りは「ぶれの範囲」でした。

### 同じ種どうしの差で比べ直した var3.py

同じ種は同じ客の来方なので、ロボット0台と1台を同じ種どうしで引き算すれば、客の来方の違いを消して、ロボットの効きだけを見られます。差の中央値に、くり返し引き直し（ブートストラップ）で95%の幅を付けました。

```python
import csv,re,collections,statistics as st,random,os,json
L=os.path.expanduser('~/mnt/KIOXIA/r2r2r-data/service_sim/unity/unity_ledger_v1.csv')
R=list(csv.DictReader(open(L))); random.seed(0)
NAME={'fam_p0':'②','fam_p1':'③','fam_p2':'④','fam_p3':'⑤','fam_p4':'⑥','fam_p5':'⑦'}
G=collections.defaultdict(dict)
for r in R:
  if '_night' not in r['run_folder']: continue
  k=re.sub(r'_s\d+(_|$)',r'_s*\1',r['run_folder']); k=re.sub(r'_r\d_','_r#_',k)
  G[(k,int(r['robots']))][int(r['seed'])]=r
out=[];m='entry_wait_mean'
for (k,rb),d in sorted(G.items()):
  if rb==0 or (k,rb-1) not in G: continue
  a=G[(k,rb-1)]; seeds=sorted(set(a)&set(d))
  if len(seeds)<8: continue
  diffs=[float(d[s][m])-float(a[s][m]) for s in seeds]
  bs=sorted(st.median(random.choices(diffs,k=len(diffs))) for _ in range(4000))
  lo,hi=bs[100],bs[3899]
  up=sum(x>0 for x in diffs); dn=sum(x<0 for x in diffs)
  v='縮む' if hi<0 else '延びる' if lo>0 else 'ぶれの範囲'
  r0=d[seeds[0]]
  out.append(dict(night=k.split('_cs')[0],store=NAME[r0['store']],staff=int(r0['staff']),arr=int(r0['arrivals_per_h']),step=f'{rb-1}→{rb}',n=len(seeds),med=st.median(diffs),lo=lo,hi=hi,up=up,down=dn,verdict=v))
json.dump(out,open('robot_effect_paired.json','w'),ensure_ascii=False,indent=1)
print(collections.Counter(o['verdict'] for o in out), len(out))
for o in out: print('%-22s %s 人%d 客%2d %s 種%2d 差の中央%+6.2f [%+6.2f,%+6.2f] 延%2d縮%2d %s'%(o['night'],o['store'],o['staff'],o['arr'],o['step'],o['n'],o['med'],o['lo'],o['hi'],o['up'],o['down'],o['verdict']))
```

```text
Counter({'ぶれの範囲': 39, '縮む': 24, '延びる': 13}) 76
20261027_night         ③ 人4 客42 0→1 種10 差の中央 -0.50 [ -1.96, -0.03] 延 2縮 8 縮む
20261027_night         ③ 人4 客42 1→2 種10 差の中央 -0.22 [ -1.25, +1.23] 延 4縮 6 ぶれの範囲
20261027_night         ⑦ 人4 客42 0→1 種10 差の中央 -0.28 [ -1.88, +0.25] 延 3縮 7 ぶれの範囲
20261027_night         ⑦ 人4 客42 1→2 種10 差の中央 -0.39 [ -2.01, -0.03] 延 2縮 8 縮む
20261028_night         ③ 人4 客42 0→1 種10 差の中央 -0.12 [ -2.01, +0.35] 延 4縮 6 ぶれの範囲
20261028_night         ③ 人4 客42 1→2 種10 差の中央 +0.03 [ -1.63, +1.18] 延 5縮 5 ぶれの範囲
20261028_night         ⑦ 人4 客42 0→1 種10 差の中央 -1.19 [ -2.27, +0.00] 延 3縮 7 ぶれの範囲
20261028_night         ⑦ 人4 客42 1→2 種10 差の中央 -0.11 [ -0.74, +0.75] 延 3縮 7 ぶれの範囲
20261029_night         ③ 人4 客42 0→1 種10 差の中央 -1.91 [ -2.57, -0.09] 延 2縮 8 縮む
20261029_night         ③ 人4 客42 1→2 種10 差の中央 +1.23 [ +0.02, +2.03] 延 8縮 2 延びる
20261029_night         ⑦ 人4 客42 0→1 種10 差の中央 -0.31 [ -2.43, +1.09] 延 4縮 6 ぶれの範囲
20261029_night         ⑦ 人4 客42 1→2 種10 差の中央 -0.31 [ -2.61, +0.50] 延 2縮 8 ぶれの範囲
20261030_night         ④ 人2 客28 0→1 種10 差の中央 +2.79 [ -1.18, +4.23] 延 7縮 3 ぶれの範囲
20261030_night         ④ 人2 客28 1→2 種10 差の中央+10.92 [ +7.33,+14.48] 延10縮 0 延びる
20261101_night         ③ 人4 客42 0→1 種10 差の中央 -0.13 [ -2.08, +0.28] 延 2縮 8 ぶれの範囲
20261101_night         ③ 人4 客42 1→2 種10 差の中央 +0.11 [ -1.43, +1.44] 延 6縮 4 ぶれの範囲
20261101_night         ⑦ 人4 客42 0→1 種10 差の中央 -1.03 [ -1.71, -0.00] 延 2縮 8 縮む
20261101_night         ⑦ 人4 客42 1→2 種10 差の中央 +0.80 [ +0.10, +1.58] 延 8縮 2 延びる
20261102_night         ③ 人4 客42 0→1 種10 差の中央 -0.17 [ -1.88, +0.24] 延 3縮 7 ぶれの範囲
20261102_night         ③ 人4 客42 1→2 種10 差の中央 -0.37 [ -1.57, +0.33] 延 3縮 7 ぶれの範囲
20261102_night         ⑦ 人4 客42 0→1 種10 差の中央 -1.19 [ -1.92, +0.18] 延 3縮 7 ぶれの範囲
20261102_night         ⑦ 人4 客42 1→2 種10 差の中央 -0.30 [ -0.60, +0.84] 延 4縮 6 ぶれの範囲
20261103_night         ⑤ 人4 客46 0→1 種10 差の中央 -1.52 [ -3.16, -0.45] 延 0縮10 縮む
20261103_night         ⑤ 人4 客46 1→2 種10 差の中央 +1.21 [ -0.81, +2.92] 延 8縮 2 ぶれの範囲
20261104_night         ⑥ 人3 客38 0→1 種10 差の中央 -4.29 [ -6.18, -1.78] 延 0縮10 縮む
20261104_night         ⑥ 人3 客38 1→2 種10 差の中央 -0.28 [ -1.39, +0.81] 延 3縮 7 ぶれの範囲
20261105_night         ② 人2 客19 0→1 種10 差の中央 -0.29 [ -0.89, -0.13] 延 0縮10 縮む
20261105_night         ② 人2 客19 1→2 種10 差の中央 +0.07 [ -0.19, +1.59] 延 7縮 3 ぶれの範囲
20261106_night_a14     ② 人2 客14 0→1 種10 差の中央 -0.10 [ -0.32, -0.03] 延 1縮 9 縮む
20261106_night_a14     ② 人2 客14 1→2 種10 差の中央 +0.07 [ +0.00, +0.22] 延 8縮 2 ぶれの範囲
20261106_night_a20     ④ 人2 客20 0→1 種10 差の中央 +0.75 [ -0.28, +1.38] 延 8縮 2 ぶれの範囲
20261106_night_a20     ④ 人2 客20 1→2 種10 差の中央 +1.65 [ +0.16, +4.72] 延 8縮 2 延びる
20261106_night_a24     ② 人2 客24 0→1 種10 差の中央 -0.61 [ -0.97, -0.27] 延 1縮 9 縮む
20261106_night_a24     ② 人2 客24 1→2 種10 差の中央 -0.26 [ -0.68, +0.63] 延 2縮 8 ぶれの範囲
20261106_night_a27     ⑥ 人3 客27 0→1 種10 差の中央 -0.18 [ -0.23, -0.06] 延 2縮 8 縮む
20261106_night_a27     ⑥ 人3 客27 1→2 種10 差の中央 -0.00 [ -0.10, +0.04] 延 5縮 5 ぶれの範囲
20261106_night_a30     ③ 人4 客30 0→1 種10 差の中央 +0.09 [ -0.05, +0.14] 延 7縮 3 ぶれの範囲
20261106_night_a30     ③ 人4 客30 1→2 種10 差の中央 -0.01 [ -0.13, +0.10] 延 5縮 5 ぶれの範囲
20261106_night_a30     ⑦ 人4 客30 0→1 種10 差の中央 +0.06 [ -0.02, +0.20] 延 6縮 4 ぶれの範囲
20261106_night_a30     ⑦ 人4 客30 1→2 種10 差の中央 +0.04 [ -0.07, +0.08] 延 7縮 3 ぶれの範囲
20261106_night_a33     ⑤ 人4 客33 0→1 種10 差の中央 +0.17 [ +0.13, +0.25] 延 9縮 1 延びる
20261106_night_a33     ⑤ 人4 客33 1→2 種10 差の中央 -0.03 [ -0.09, +0.09] 延 4縮 6 ぶれの範囲
20261106_night_a36     ④ 人2 客36 0→1 種10 差の中央 +1.22 [ -2.93, +4.11] 延 7縮 3 ぶれの範囲
20261106_night_a36     ④ 人2 客36 1→2 種10 差の中央+11.40 [ +6.06,+19.74] 延 9縮 1 延びる
20261106_night_a49     ⑥ 人3 客49 0→1 種10 差の中央 -8.31 [-10.00, -6.49] 延 0縮10 縮む
20261106_night_a49     ⑥ 人3 客49 1→2 種10 差の中央 -0.88 [ -2.13, -0.36] 延 1縮 9 縮む
20261106_night_a54     ③ 人4 客54 0→1 種10 差の中央 -2.65 [ -3.64, -1.60] 延 1縮 9 縮む
20261106_night_a54     ③ 人4 客54 1→2 種10 差の中央 +0.11 [ -1.70, +1.78] 延 5縮 5 ぶれの範囲
20261106_night_a54     ⑦ 人4 客54 0→1 種10 差の中央 -2.44 [ -3.61, -1.82] 延 0縮10 縮む
20261106_night_a54     ⑦ 人4 客54 1→2 種10 差の中央 +0.04 [ -2.08, +1.20] 延 5縮 5 ぶれの範囲
20261106_night_a59     ⑤ 人4 客59 0→1 種10 差の中央 -1.31 [ -2.52, -0.30] 延 2縮 8 縮む
20261106_night_a59     ⑤ 人4 客59 1→2 種10 差の中央 -0.09 [ -2.54, +1.29] 延 5縮 5 ぶれの範囲
20261107_night_st1     ② 人1 客19 0→1 種10 差の中央 -6.89 [ -9.96, -5.77] 延 0縮10 縮む
20261107_night_st1     ② 人1 客19 1→2 種10 差の中央 +0.50 [ -0.56, +3.33] 延 7縮 3 ぶれの範囲
20261107_night_st1     ④ 人1 客28 0→1 種10 差の中央 -9.79 [-15.42, -3.99] 延 0縮10 縮む
20261107_night_st1     ④ 人1 客28 1→2 種10 差の中央+17.05 [+12.92,+28.88] 延10縮 0 延びる
20261107_night_st2     ⑥ 人2 客38 0→1 種10 差の中央-12.50 [-13.33,-10.98] 延 0縮10 縮む
20261107_night_st2     ⑥ 人2 客38 1→2 種10 差の中央 +0.36 [ -0.73, +1.85] 延 6縮 4 ぶれの範囲
20261107_night_st3     ② 人3 客19 0→1 種10 差の中央 +0.03 [ -0.03, +0.34] 延 6縮 4 ぶれの範囲
20261107_night_st3     ② 人3 客19 1→2 種10 差の中央 +0.02 [ -0.32, +0.16] 延 5縮 5 ぶれの範囲
20261107_night_st3     ③ 人3 客42 0→1 種10 差の中央 -4.02 [ -5.66, -2.14] 延 0縮10 縮む
20261107_night_st3     ③ 人3 客42 1→2 種10 差の中央 +0.23 [ -1.51, +2.66] 延 5縮 5 ぶれの範囲
20261107_night_st3     ④ 人3 客28 0→1 種10 差の中央 +2.13 [ +0.89, +3.31] 延10縮 0 延びる
20261107_night_st3     ④ 人3 客28 1→2 種10 差の中央 +6.34 [ +4.26, +9.36] 延10縮 0 延びる
20261107_night_st3     ⑤ 人3 客46 0→1 種10 差の中央 -5.19 [ -6.27, -4.09] 延 0縮10 縮む
20261107_night_st3     ⑤ 人3 客46 1→2 種10 差の中央 +2.34 [ -1.25, +6.62] 延 6縮 4 ぶれの範囲
20261107_night_st3     ⑦ 人3 客42 0→1 種10 差の中央 -2.99 [ -5.15, -2.03] 延 1縮 9 縮む
20261107_night_st3     ⑦ 人3 客42 1→2 種10 差の中央 -1.04 [ -3.48, -0.07] 延 2縮 8 縮む
20261107_night_st4     ⑥ 人4 客38 0→1 種10 差の中央 -0.23 [ -1.35, -0.03] 延 2縮 8 縮む
20261107_night_st4     ⑥ 人4 客38 1→2 種10 差の中央 -0.08 [ -0.21, -0.01] 延 2縮 8 縮む
20261107_night_st5     ③ 人5 客42 0→1 種10 差の中央 +0.77 [ +0.02, +1.91] 延 9縮 1 延びる
20261107_night_st5     ③ 人5 客42 1→2 種10 差の中央 +0.05 [ -0.56, +0.34] 延 7縮 3 ぶれの範囲
20261107_night_st5     ⑤ 人5 客46 0→1 種10 差の中央 +0.93 [ +0.23, +1.44] 延10縮 0 延びる
20261107_night_st5     ⑤ 人5 客46 1→2 種10 差の中央 +0.29 [ -0.01, +1.27] 延 8縮 2 ぶれの範囲
20261107_night_st5     ⑦ 人5 客42 0→1 種10 差の中央 +0.44 [ +0.03, +1.26] 延 8縮 2 延びる
20261107_night_st5     ⑦ 人5 客42 1→2 種10 差の中央 +0.07 [ +0.01, +0.90] 延 9縮 1 延びる
```

- ねらい：夜の結果の差が、ロボットのせいなのか、ずれなのかを見分ける
- 変わったこと：Unity も回す数もそのままで、はっきり言えた組が76組中3組から37組（縮む24・延びる13）に増えた
- 見込める効果：夜の回す数（1条件10回）は足りていると分かった。「2台目はほとんど効かない」「スタッフが多い店ではロボットが少し邪魔になる」を、根拠つきで言える

見えてきたことは3つです。1台目は、スタッフが少なく混んでいる店でよく効きます（⑥スタッフ2人で −12.5分、②スタッフ1人で −6.9分。どちらも10種すべてで縮んだ）。2台目は、はっきり縮んだのが⑥の客49組/時と⑦スタッフ3人ぐらいです。④の「ロボットを足すと入店待ちが延びる」は、4回の夜すべてで10種中9〜10種が延びていて、ずれではありませんでした。

夜の判定の仕組み（judge_night.py）に、この比べ方を足すかは決めていません。

### 動きにばらつきがあること自体は、実際の店に近い

同じ条件でも、人やロボットの動きが毎回同じにならないのは、実際の店も同じです。そこで、必ず同じ数字にするのではなく、どこに・どれくらいのばらつきがあるのが実際の店に近いかを決めるのが本来の形だ、という話になりました。

前提の表（104行）を見直すと、会計90秒・卓を拭く40秒・案内45秒のようなスタッフの作業の時間は、すべて1つの値で固定でした。ばらつきがあるのは客の来方と、一時停止の長さ（3〜10秒）、デザートができるまで（2〜7分）だけです。実際には、会計が60秒の組も150秒の組もあります。

ただし、作業の時間にばらつきを入れると試算の版が変わり、試算のページの数字も変わります。幅の根拠もまだありません。そこで、今は試算には入れず、店で測ったばらつきを記録として残すところまでにしました。


## 作業ログ ― 観察シートに「早いとき・遅いとき」を足した

観察シートは、店のスタッフがスマホで作業の時間を測るページです（[観察シート](https://pixelstomotors.com/sim/serving-robot-observation)）。測った記録は LINE かメールで店長やロボットの担当に送り、届いた記録から、試算の前提を置き換える候補を作ります（LOG414・LOG415）。

### 決めたこと：試算に入れず、10回測ったら幅を出す

| 決めたこと | 中身 |
|---|---|
| 店の値で試算を回すか | 回さない（10月31日の決めごとのまま）。届いた記録は標準の値を直す候補にするだけ |
| 作業の時間のばらつきを試算に入れるか | 入れない。記録が10回以上たまって幅が分かってから決める |
| 幅を出す回数 | 1つの作業を10回測ったら |
| 入口で待った時間を測る作業を足すか | 足さない |

幅は「早いとき・遅いとき」と書き、中身は、測った値を並べて速い方の4分の1と遅い方の4分の1を外した範囲です（四分位）。店の人に分かる言い方にしました。

### 観察シートの画面と送る文を直した差分（コミット 8d70549）

```diff
commit 8d70549f4025f9877762ea0137302f5c7424b3b7
Author: PixelstoMotors <studio@adaptdesign.jp>
Date:   Sun Nov 1 19:21:23 2026 +0900

    観察シート：1つの作業を10回測ったら早いとき・遅いときも出す（画面・送る文・取り込みの値の案）
    
    - 速い方の4分の1と遅い方の4分の1を外した幅。シートと field_import.py で同じ計算
    - 試算・前提の表には入れない。記録として残すだけ
    
    Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01LevEXTjmDgrBZ74rhCRBtF

diff --git a/public/sim/serving-robot-observation.html b/public/sim/serving-robot-observation.html
index e5342b8..8007b94 100644
--- a/public/sim/serving-robot-observation.html
+++ b/public/sim/serving-robot-observation.html
@@ -267,6 +267,10 @@ function T(k){var a=TASKS.concat([ROBOT]);for(var i=0;i<a.length;i++)if(a[i].k==
 function cnt(k){return S.recs.filter(function(r){return r.k===k;}).length;}
 function jp(sec){sec=Math.max(0,Math.round(sec));var m=Math.floor(sec/60),s=sec%60;return m?(m+'分'+(s?s+'秒':'')):s+'秒';}
 function clock(ms){var s=Math.max(0,Math.floor(ms/1000));return Math.floor(s/60)+':'+String(s%60).padStart(2,'0');}
+var SPREAD=10;
+// 早いとき・遅いとき：速い方の4分の1と遅い方の4分の1を外した幅（取り込みの道具 field_import.py と同じ計算）。SPREAD 回に届くまでは出さない
+function qt(b,p){var h=(b.length-1)*p,i=Math.floor(h);return i+1<b.length?b[i]+(b[i+1]-b[i])*(h-i):b[i];}
+function spread(a){if(a.length<SPREAD)return null;var b=a.slice().sort(function(x,y){return x-y;});return [qt(b,.25),qt(b,.75)];}
 function med(a){if(!a.length)return null;var b=a.slice().sort(function(x,y){return x-y;}),m=Math.floor(b.length/2);return b.length%2?b[m]:(b[m-1]+b[m])/2;}
 function buzz(){try{navigator.vibrate&&navigator.vibrate(15);}catch(e){}}
 var toastTimer=null;
@@ -315,7 +319,7 @@ function hasRecs(){return !!(S.recs.length||S.arrivals.length||S.leaves.length);
 function renderSum(){
   var rows=tasks().filter(function(t){return cnt(t.k);}).map(function(t){var a=S.recs.filter(function(r){return r.k===t.k;});var m=med(a.map(function(r){return r.sec;}));
     var extra='';if(t.k==='robot'&&a.length){var st=a.reduce(function(s,r){return s+(r.stops||0);},0),hp=a.filter(function(r){return r.helps;}).length;extra='<small>止まった'+st+'回・人が動かした'+a.reduce(function(s,r){return s+(r.helps||0);},0)+'回</small>';}
-    return '<div class="row"><b>'+t.t+'</b><span class="v">'+(m==null?'<span class="num">—</span>':'<span class="num">'+jp(m)+'</span>')+'<small>'+a.length+'回測った'+(a.length&&a.length<GOAL?'（あと'+(GOAL-a.length)+'回）':'')+'</small>'+extra+'</span></div>';});
+    return '<div class="row"><b>'+t.t+'</b><span class="v">'+(m==null?'<span class="num">—</span>':'<span class="num">'+jp(m)+'</span>')+(function(w){return w?'<small>早いとき'+jp(w[0])+'・遅いとき'+jp(w[1])+'</small>':'';})(spread(a.map(function(r){return r.sec;})))+'<small>'+a.length+'回測った'+(a.length&&a.length<GOAL?'（あと'+(GOAL-a.length)+'回）':'')+'</small>'+extra+'</span></div>';});
   var f=flow();
   if(f){
     var sub=f.aRate==null?'<small>15分以上数えると、1時間あたりと回転率が出ます</small>':'<small>1時間に 来店 約'+Math.round(f.aRate)+'組・会計 約'+Math.round(f.dRate)+'組</small>';
@@ -379,7 +383,7 @@ $('fTables').addEventListener('input',function(e){S.store.tables=e.target.value=
 /* 送る */
 function summaryText(){
   var L=['観察シートの記録','目的：'+(PURPOSE[S.purpose]||''),'お店：'+(S.store.name||'（未入力）')+'　スタッフ'+S.store.staff+'人'+(S.purpose==='post'?'　ロボット'+S.store.robots+'台':''),'時間帯：'+(S.store.time||'')+(S.store.tags&&S.store.tags.length?'　'+S.store.tags.join('・'):''),(S.store.note?'メモ：'+S.store.note:'')].filter(Boolean);
-  tasks().forEach(function(t){var a=S.recs.filter(function(r){return r.k===t.k;}).map(function(r){return r.sec;});if(a.length)L.push(t.t+'：だいたい'+jp(med(a))+'（'+a.length+'回）');});
+  tasks().forEach(function(t){var a=S.recs.filter(function(r){return r.k===t.k;}).map(function(r){return r.sec;});if(a.length){var w=spread(a);L.push(t.t+'：だいたい'+jp(med(a))+(w?'・早いとき'+jp(w[0])+'・遅いとき'+jp(w[1]):'')+'（'+a.length+'回）');}});
   var f=flow();if(S.store.tables)L.splice(3,0,'卓の数：約'+S.store.tables+'卓');
   if(f){L.push('出入り：入ってきた'+f.a+'組・会計した'+f.d+'組'+(S.store.occ0!==''&&S.store.occ0!=null?'（数えはじめに'+S.store.occ0+'卓にお客さん）':''));
     if(f.aRate!=null)L.push('1時間あたり：来店 約'+Math.round(f.aRate)+'組・会計 約'+Math.round(f.dRate)+'組');
diff --git a/tools/serving_sim/README.md b/tools/serving_sim/README.md
index b77d472..5084bd0 100644
--- a/tools/serving_sim/README.md
+++ b/tools/serving_sim/README.md
@@ -69,6 +69,7 @@ python3 field_import.py <field のフォルダ> 届いた文.txt     # field の
 
 決まり（2026-10-31 エイイチと決めた）：
 - 値は中央値。店ごとに15回に届くまでは「参考」
+- 1つの店で10回以上測った作業は、早いとき・遅いとき（速い方の4分の1と遅い方の4分の1を外した幅）も出す。シートの画面・送る文と同じ計算。試算には入れず、記録として残す（2026-11-01 決定）
 - 標準の値を置き換える候補にするのは、2店以上・合わせて15回以上そろったとき。表は自動で書き換えない。proposal.md の案と差を見て、エイイチが承認してから直す
 - 表に入れるときは basis を「現場」、n に回数、source に店の数と期間を書く。店の名前は書かない
 
```

### 届いた記録を取り込む field_import.py（全文）

表の右に「店ごとの早いとき・遅いとき」の列を足しました。シートと同じ計算なので、同じ数字になります。

```python
"""観察シートの記録（LINE・メールで届いた文）を取り込み、試算の前提の表に入れる値の案を出す（2026-10-31）

使い方:
    python3 field_import.py <field のフォルダ> <届いた文.txt> [<届いた文.txt> ...]   # 取り込んで、案を作り直す
    python3 field_import.py <field のフォルダ>                                         # 案だけ作り直す

field のフォルダは iCloud の サービスロボット導入シミュレーション/field/（店のデータは GitHub に入れない）。
- raw/                  届いた文の原文（取り込んだときに写す。直さない）
- observations.csv      1行1回の記録（作業の時間・ロボットの往復）。同じ店・同じ日・同じ時刻・同じ作業の行は二重に入れない
- store_conditions.csv  1通の記録ごとの、その店の条件（スタッフ・卓の数・時間帯・ようす・来店のペースなど）。今は記録だけ
- proposal.md           前提の表に入れる値の案と、今の値との差（毎回作り直す）

決まり（2026-10-31 エイイチと決めた）:
- 1つの値は、測った回数の中央値で入れる
- 店ごとに15回に届くまでは「参考」
- 標準の値（assumptions.json）を置き換える候補にするのは、2店以上・合わせて15回以上そろったとき。
  自動では書き換えない。案と差を見て、エイイチが承認してから表を直す（basis を「現場」、n に回数、source に店の数と期間。店の名前は書かない）
- 店ごとの値で試算を回すのは、今はしない（記録だけ）
- 席へ案内は 2026-10-31 にシートの区切りを「席に着いて説明を始めたら〜テーブルを離れたら」に変えた（歩く時間を含まない＝guide_s と同じ定義）。
  それより前（2026-10-31 16:08 より前）の案内の記録は、歩く時間を含むので案に使わない
"""
import csv
import json
import os
import re
import shutil
import statistics
import sys
from datetime import date, datetime

HERE = os.path.dirname(os.path.abspath(__file__))
GOAL = 15          # 店ごとに「参考」を外す回数（シートの目安と同じ）
MIN_STORES = 2     # 標準の値の候補にする店の数
SPREAD_MIN = 10    # 店ごとに、早いとき・遅いとき（ばらつきの幅）を出す回数（シートと同じ）。試算には入れない、記録だけ
GUIDE_FROM = datetime(2026, 10, 31, 16, 8)   # これより前の案内の記録は、前の区切り（歩く時間を含む）。シートの直しが公開された時刻

# シートの作業の名前 → 前提の表の行（どれも中央値の秒をそのまま入れる）
TASK_TO_KEY = {
    "会計": "pay_s",
    "片付け": "clear_s",
    "テーブルを拭く": "wipe_s",
    "席へ案内": "guide_s",
    "呼び出し": "call_s",
    "料理を置く": "staff_place_s",
}
ROBOT_TASK = "ロボットの往復"
# ロボットの往復 → 前提の表の行（往復のうち、1回以上止まった・人が動かした往復の割合。試算も「1往復で起きる確率」）
ROBOT_TO_KEY = {"stops": "pause_rate", "helps": "stuck_rate"}

OBS_COLS = ["store", "date", "time", "kind", "sec", "stops", "helps", "raw_file"]
COND_COLS = ["store", "date", "purpose", "time_of_day", "tags", "staff", "robots", "tables",
             "arrived", "left", "arrive_per_h", "left_per_h", "turnover_per_h", "stay_min", "occ0", "note", "raw_file"]


def parse(text):
    """届いた文を読む。読めない行は飛ばす"""
    lines = [l.rstrip() for l in text.replace("\r\n", "\n").split("\n")]
    if not lines or "観察シートの記録" not in lines[0]:
        raise ValueError("観察シートの記録の文ではない（1行目が「観察シートの記録」でない）")
    info = {"store": "", "date": "", "purpose": "", "time_of_day": "", "tags": "", "staff": "", "robots": "",
            "tables": "", "note": "", "arrived": "", "left": "", "arrive_per_h": "", "left_per_h": "",
            "turnover_per_h": "", "stay_min": "", "occ0": ""}
    recs, sec = [], None
    for l in lines[1:]:
        if l.startswith("---"):
            sec = l
            continue
        if sec and "1回ずつの記録" in sec:
            m = re.match(r"(\d\d:\d\d:\d\d)\s+(\S+)\s+([\d.]+)秒(?:（止まった(\d+)回・人が動かした(\d+)回）)?", l)
            if m:
                recs.append({"time": m[1], "kind": m[2], "sec": float(m[3]),
                             "stops": m[4] or "", "helps": m[5] or ""})
            continue
        if sec:
            continue
        if l.startswith("日付："):
            y, mo, d = re.findall(r"\d+", l)[:3]
            info["date"] = f"{int(y):04d}-{int(mo):02d}-{int(d):02d}"
        elif l.startswith("目的："):
            info["purpose"] = l[3:]
        elif l.startswith("お店："):
            m = re.match(r"お店：(.*?)　スタッフ(\d+)人(?:　ロボット(\d+)台)?", l)
            if m:
                info["store"] = "" if m[1] == "（未入力）" else m[1].strip()
                info["staff"], info["robots"] = m[2], m[3] or ""
        elif l.startswith("卓の数："):
            info["tables"] = (re.findall(r"\d+", l) or [""])[0]
        elif l.startswith("時間帯："):
            a = l[4:].split("　", 1)
            info["time_of_day"], info["tags"] = a[0], (a[1] if len(a) > 1 else "")
        elif l.startswith("メモ："):
            info["note"] = l[3:]
        elif l.startswith("出入り："):
            n = re.findall(r"(\d+)組", l)
            if len(n) >= 2:
                info["arrived"], info["left"] = n[0], n[1]
            m = re.search(r"数えはじめに(\d+)卓", l)
            info["occ0"] = m[1] if m else ""
        elif l.startswith("1時間あたり："):
            n = re.findall(r"約(\d+)組", l)
            if len(n) >= 2:
                info["arrive_per_h"], info["left_per_h"] = n[0], n[1]
        elif l.startswith("回転率："):
            info["turnover_per_h"] = (re.findall(r"[\d.]+", l) or [""])[0]
        elif l.startswith("滞在時間の目安："):
            info["stay_min"] = (re.findall(r"\d+", l) or [""])[0]
    if not info["date"]:
        raise ValueError("日付の行がない")
    if not info["store"]:
        info["store"] = "名前なし_" + info["date"]
    return info, recs


def read_csv(path, cols):
    if not os.path.exists(path):
        return []
    with open(path, encoding="utf-8-sig", newline="") as f:
        return list(csv.DictReader(f))


def write_csv(path, cols, rows):
    with open(path + ".tmp", "w", encoding="utf-8-sig", newline="") as f:
        w = csv.DictWriter(f, fieldnames=cols)
        w.writeheader()
        w.writerows(rows)
    os.replace(path + ".tmp", path)


def take(field, src):
    text = open(src, encoding="utf-8").read()
    info, recs = parse(text)
    os.makedirs(os.path.join(field, "raw"), exist_ok=True)
    base = f"{info['date']}_{re.sub(r'[^0-9A-Za-zぁ-んァ-ヶ一-龠ー_]', '', info['store'])}"
    raw_name, i = base + ".txt", 1
    while os.path.exists(os.path.join(field, "raw", raw_name)):
        if open(os.path.join(field, "raw", raw_name), encoding="utf-8").read() == text:
            break      # 同じ文はもう取り込んである
        i += 1
        raw_name = f"{base}_{i}.txt"
    else:
        shutil.copyfile(src, os.path.join(field, "raw", raw_name))

    obs_path = os.path.join(field, "observations.csv")
    obs = read_csv(obs_path, OBS_COLS)
    seen = {(r["store"], r["date"], r["time"], r["kind"]) for r in obs}
    added = 0
    for r in recs:
        k = (info["store"], info["date"], r["time"], r["kind"])
        if k in seen:
            continue
        seen.add(k)
        obs.append({"store": info["store"], "date": info["date"], "time": r["time"], "kind": r["kind"],
                    "sec": r["sec"], "stops": r["stops"], "helps": r["helps"], "raw_file": raw_name})
        added += 1
    write_csv(obs_path, OBS_COLS, obs)

    cond_path = os.path.join(field, "store_conditions.csv")
    conds = [c for c in read_csv(cond_path, COND_COLS) if c["raw_file"] != raw_name]
    conds.append({**{c: info.get(c, "") for c in COND_COLS}, "raw_file": raw_name})
    write_csv(cond_path, COND_COLS, conds)
    print(f"{src}: {info['store']} {info['date']} → 記録 {len(recs)} 回（新しく {added} 回）・原文 raw/{raw_name}")


def spread(v):
    """早いとき・遅いとき：速い方の4分の1と遅い方の4分の1を外した幅（シートと同じ計算）。SPREAD_MIN 回に届かなければ None"""
    if len(v) < SPREAD_MIN:
        return None
    q = statistics.quantiles(v, n=4, method="inclusive")
    return q[0], q[2]


def fmt(v):
    return "—" if v is None else (f"{v:.3f}".rstrip("0").rstrip(".") if isinstance(v, float) else str(v))


def proposal(field):
    A = json.load(open(os.path.join(HERE, "assumptions.json"), encoding="utf-8"))
    obs = read_csv(os.path.join(field, "observations.csv"), OBS_COLS)
    L = ["# 前提の表に入れる値の案（観察シートの記録から）", "",
         f"作った日：{date.today().isoformat()}　記録 {len(obs)} 回", "",
         f"- 店ごとに {GOAL} 回に届くまでは「参考」。標準の値の候補は、{MIN_STORES}店以上・合わせて {GOAL} 回以上",
         "- 表はまだ書き換えていない。候補を表に入れるかはエイイチが決める（basis を「現場」、n に回数、source に店の数と期間。店の名前は書かない）",
         f"- 早いとき・遅いときは、1つの店で {SPREAD_MIN} 回以上測った作業だけ出す（速い方の4分の1と遅い方の4分の1を外した幅）。試算には入れず、記録として残す",
         "- 中央値で比べる。席へ案内は 2026-10-31 16:08 より前の記録を使わない（歩く時間を含む前の区切り）", ""]

    def rows_for(kind):
        out = [r for r in obs if r["kind"] == kind]
        if kind == "席へ案内":
            out = [r for r in out if datetime.fromisoformat(f"{r['date']}T{r['time']}") >= GUIDE_FROM]
        return out

    L += ["## 作業の時間（秒）", "",
          "| 行 | シートの作業 | 今の値（根拠） | 全体の中央値 | 店の数 | 回数 | 判定 | 店ごと（中央値・回数） | 店ごとの早いとき・遅いとき |",
          "|---|---|---|---|---|---|---|---|---|"]
    for task, key in TASK_TO_KEY.items():
        rs = rows_for(task)
        cur = A["common"][key]
        by = {}
        for r in rs:
            by.setdefault(r["store"], []).append(float(r["sec"]))
        allv = [float(r["sec"]) for r in rs]
        med = statistics.median(allv) if allv else None
        judge = "候補" if len(by) >= MIN_STORES and len(allv) >= GOAL else ("参考" if allv else "記録なし")
        per = "、".join(f"{s} {fmt(statistics.median(v))}秒・{len(v)}回{'' if len(v) >= GOAL else '（参考）'}" for s, v in by.items())
        sp = "、".join(f"{s} {fmt(round(w[0], 1))}〜{fmt(round(w[1], 1))}秒" for s, v in by.items() if (w := spread(v)))
        L.append(f"| `{key}` | {task} | {cur['value']}（{cur['basis']}） | {fmt(med)} | {len(by)} | {len(allv)} | {judge} | {per or '—'} | {sp or '—'} |")

    L += ["", "## ロボット（往復のうち、起きた往復の割合）", "",
          "| 行 | 数え方 | 今の値（根拠） | 全体 | 店の数 | 往復の回数 | 判定 | 店ごと |", "|---|---|---|---|---|---|---|---|"]
    rs = [r for r in obs if r["kind"] == ROBOT_TASK]
    for f, key in ROBOT_TO_KEY.items():
        cur = A["common"][key]
        by = {}
        for r in rs:
            by.setdefault(r["store"], []).append(1 if int(r[f] or 0) > 0 else 0)
        allv = [x for v in by.values() for x in v]
        rate = sum(allv) / len(allv) if allv else None
        judge = "候補" if len(by) >= MIN_STORES and len(allv) >= GOAL else ("参考" if allv else "記録なし")
        name = "1回以上止まった往復" if f == "stops" else "人が動かした往復"
        per = "、".join(f"{s} {fmt(sum(v) / len(v))}・{len(v)}往復" for s, v in by.items())
        L.append(f"| `{key}` | {name} | {cur['value']}（{cur['basis']}） | {fmt(rate)} | {len(by)} | {len(allv)} | {judge} | {per or '—'} |")
    secs = [float(r["sec"]) for r in rs]
    rw = spread(secs)
    L += ["", f"ロボットの往復の時間（答え合わせ用・表には入れない）：中央値 {fmt(statistics.median(secs)) if secs else '—'} 秒・{len(secs)} 往復"
          + (f"（早いとき {fmt(round(rw[0], 1))} 秒・遅いとき {fmt(round(rw[1], 1))} 秒。店をまたいだ値）" if rw else "")]

    conds = read_csv(os.path.join(field, "store_conditions.csv"), COND_COLS)
    L += ["", "## 店の条件（今は記録だけ。店ごとの値で試算は回さない）", "",
          "| 店 | 日付 | 時間帯・ようす | スタッフ | ロボット | 卓 | 来店／時 | 回転率／時 | 滞在の目安（分） |", "|---|---|---|---|---|---|---|---|---|"]
    for c in conds:
        L.append(f"| {c['store']} | {c['date']} | {c['time_of_day']} {c['tags']} | {c['staff']} | {c['robots'] or '—'} | {c['tables'] or '—'} "
                 f"| {c['arrive_per_h'] or '—'} | {c['turnover_per_h'] or '—'} | {c['stay_min'] or '—'} |")
    open(os.path.join(field, "proposal.md"), "w", encoding="utf-8").write("\n".join(L) + "\n")
    print("proposal.md を作り直した")


if __name__ == "__main__":
    field = sys.argv[1]
    os.makedirs(field, exist_ok=True)
    for src in sys.argv[2:]:
        take(field, src)
    proposal(field)
```

### 偽の記録で、画面・送る文・取り込みの3か所が同じ幅になるかを確かめた

会計12回・テーブルを拭く3回の偽の記録を入れて、画面・送る文・proposal.md を見ました。確かめに使ったスクリプトと出力です。

```javascript
const { chromium } = require('playwright');
(async()=>{const b=await chromium.launch();const p=await b.newPage({viewport:{width:375,height:900},deviceScaleFactor:2});
const errs=[];p.on('pageerror',e=>errs.push(e.message));
await p.goto('file:///home/claude/p2m-site/public/sim/serving-robot-observation.html');
const t0=Date.parse('2026-11-01T12:00:00');
const pay=[62,75,80,88,90,95,101,110,120,150,85,70].map((s,i)=>({id:'p'+i,k:'pay',sec:s+0.3,at:t0+i*60000}));
const wipe=[30,35,41].map((s,i)=>({id:'w'+i,k:'wipe',sec:s,at:t0+i*61000}));
await p.evaluate(([r])=>{localStorage.setItem('p2m-obs-sheet-v2',JSON.stringify({purpose:'post',store:{name:'',staff:3,robots:2,time:'',tags:[],note:'',tables:'',occ0:''},recs:r,arrivals:[],leaves:[]}));},[pay.concat(wipe)]);
await p.reload();
require("fs").writeFileSync("/tmp/claude-0/sheet.txt",await p.evaluate(()=>summaryText()));
await p.evaluate(()=>show('sList'));
await p.waitForTimeout(300);
const el=await p.$('#sum'); await el.screenshot({path:'/tmp/claude-0/sum.png'});
console.log('errors',errs);await b.close();})();
```

送る文（sheet_test.txt）：

```text
観察シートの記録
日付：2026/11/1
目的：ロボットを入れていて、改善したい
お店：（未入力）　スタッフ3人　ロボット2台
時間帯：夜（ディナー）
会計：だいたい1分29秒・早いとき1分19秒・遅いとき1分44秒（12回）
テーブルを拭く：だいたい35秒（3回）

--- 1回ずつの記録 ---
12:00:00　会計　62.3秒
12:00:00　テーブルを拭く　30秒
12:01:00　会計　75.3秒
12:01:01　テーブルを拭く　35秒
12:02:00　会計　80.3秒
12:02:02　テーブルを拭く　41秒
12:03:00　会計　88.3秒
12:04:00　会計　90.3秒
12:05:00　会計　95.3秒
12:06:00　会計　101.3秒
12:07:00　会計　110.3秒
12:08:00　会計　120.3秒
12:09:00　会計　150.3秒
12:10:00　会計　85.3秒
12:11:00　会計　70.3秒
```

取り込みの値の案（proposal_test.md）：

```text
# 前提の表に入れる値の案（観察シートの記録から）

作った日：2026-11-01　記録 15 回

- 店ごとに 15 回に届くまでは「参考」。標準の値の候補は、2店以上・合わせて 15 回以上
- 表はまだ書き換えていない。候補を表に入れるかはエイイチが決める（basis を「現場」、n に回数、source に店の数と期間。店の名前は書かない）
- 早いとき・遅いときは、1つの店で 10 回以上測った作業だけ出す（速い方の4分の1と遅い方の4分の1を外した幅）。試算には入れず、記録として残す
- 中央値で比べる。席へ案内は 2026-10-31 16:08 より前の記録を使わない（歩く時間を含む前の区切り）

## 作業の時間（秒）

| 行 | シートの作業 | 今の値（根拠） | 全体の中央値 | 店の数 | 回数 | 判定 | 店ごと（中央値・回数） | 店ごとの早いとき・遅いとき |
|---|---|---|---|---|---|---|---|---|
| `pay_s` | 会計 | 90（実感） | 89.3 | 1 | 12 | 参考 | 名前なし_2026-11-01 89.3秒・12回（参考） | 名前なし_2026-11-01 79〜103.5秒 |
| `clear_s` | 片付け | 40（仮置き） | — | 0 | 0 | 記録なし | — | — |
| `wipe_s` | テーブルを拭く | 40（仮置き） | 35 | 1 | 3 | 参考 | 名前なし_2026-11-01 35秒・3回（参考） | — |
| `guide_s` | 席へ案内 | 45（仮置き） | — | 0 | 0 | 記録なし | — | — |
| `call_s` | 呼び出し | 30（仮置き） | — | 0 | 0 | 記録なし | — | — |
| `staff_place_s` | 料理を置く | 10（仮置き） | — | 0 | 0 | 記録なし | — | — |

## ロボット（往復のうち、起きた往復の割合）

| 行 | 数え方 | 今の値（根拠） | 全体 | 店の数 | 往復の回数 | 判定 | 店ごと |
|---|---|---|---|---|---|---|---|
| `pause_rate` | 1回以上止まった往復 | 0.3（仮置き） | — | 0 | 0 | 記録なし | — |
| `stuck_rate` | 人が動かした往復 | 0.01（仮置き） | — | 0 | 0 | 記録なし | — |

ロボットの往復の時間（答え合わせ用・表には入れない）：中央値 — 秒・0 往復

## 店の条件（今は記録だけ。店ごとの値で試算は回さない）

| 店 | 日付 | 時間帯・ようす | スタッフ | ロボット | 卓 | 来店／時 | 回転率／時 | 滞在の目安（分） |
|---|---|---|---|---|---|---|---|---|
| 名前なし_2026-11-01 | 2026-11-01 | 夜（ディナー）  | 3 | 2 | — | — | — | — |
```

【画像】LOG423_07_sheet_spread.png 観察シートのまとめの画面。会計は1分29秒の下に「早いとき1分19秒・遅いとき1分44秒」と「12回測った（あと3回）」が出て、3回だけのテーブルを拭くには幅が出ていない

会計の幅は、画面・送る文が1分19秒〜1分44秒、取り込みが79〜103.5秒で、同じ値でした。公開したページに新しい表示が入っていることも確かめました。

- ねらい：店で測った作業時間のばらつきを、捨てずに残す
- 変わったこと：10回測った作業に「早いとき・遅いとき」が出る。送る文と値の案にも入る
- 見込める効果：記録が集まれば、作業の時間を1つの値ではなく幅として試算に入れるかを、店の数字で決められる。まだ記録は届いていない


## 作業ログ ― 配膳ロボットが止まった場所を、店の平面図に重ねた

ここからは、店の人がシミュレーションに求めることを先に決めてから進めました。ロボットを入れた店が望むのは、ロボットの煩わしさが減ること、忙しくてもスタッフの負担が減ること、お客さんの満足度が高いこと、回転が良くなり売上が上がるか安定すること、です。これを、どこで・なぜ止まるのか／何を変えれば良くなるのか／変えたらどれくらい良くなるのか／スタッフは楽になっているのか、の4つの材料にして順に作りました。

### 混んでいるとき、④は⑥の約3倍の時間ロボットが止まっていた

夜に回した10回（2026-10-29 の「混み具合を広げた夜」・cs:44）の stops.csv から、ロボットが止まった場所・時間・相手を取り出し、店の平面図の上に0.5mのマスで止まっていた時間を足し上げました。赤い点は、ロボットが動けなくなり、スタッフが動かしに行った場所です。

【画像】LOG423_01_stopmap_p2_vs_p4.png 混んでいるときのロボット2台。左の④は島の下の横の通路に赤い点が一列に並び、左右の縦の通路にも続く。右の⑥は赤い点がまばら

| 混んでいるとき・ロボット2台（ピーク2時間あたり・10回の平均） | ④ 効率重視・小さめ | ⑥ ゆったり・入口が横 |
|---|---|---|
| 動けなくなった回数 | 68回 | 10回 |
| 止まっていた時間の合計 | 114分 | 34分 |
| 動けなくなった相手 | もう1台のロボット 354・人が立ち止まっていた 159・スタッフとすれ違った 81・お客さんとすれ違った 40・椅子や荷物 30・分からない 14・通路の幅が足りない 1 | もう1台のロボット 75・スタッフ 8・お客さん 6・人が立ち止まっていた 5・分からない 2・椅子や荷物 2 |

（相手の数は10回分の合計）

④で詰まっていたのは、島の下の横の通路（幅約1.1m）と、島の左の縦の通路、パントリー横の縦の通路でした。どれも1本道で、すれ違う場所がありません。卓に向かう道がこの下の通路に集まるので、2台が向かい合って動けなくなります。

【画像】LOG423_02_stopmap_p2_r1_r2.png ④の混んでいるとき。左のロボット1台は動けなくなった回数22回・止まっていた合計39分、右の2台は68回・114分

【画像】LOG423_03_stopmap_p4_r1_r2.png ⑥の混んでいるとき。左のロボット1台は2回・10分、右の2台は10回・34分

### 止まった場所を描く stopmap.py（全文）

Mac で夜の stops.csv から必要な列だけを stops_extract.csv（35,653行）に書き出し、クラウドの作業環境で描きました。店の形は、サイトの公開している店の形（public/sim/layouts/）をそのまま読んでいます。

```python
# 配膳ロボットが止まった場所を、店の平面図の上に重ねる（Unity の夜の種10個ぶん）
import sys, csv, json, collections
sys.path.insert(0, '/home/claude/p2m-site/tools/sim_layouts')
import matplotlib; matplotlib.use('Agg')
import matplotlib.pyplot as plt
from matplotlib import font_manager
from matplotlib.patches import Rectangle
for f in font_manager.findSystemFonts():
    if 'NotoSansCJK-Regular' in f: font_manager.fontManager.addfont(f)
plt.rcParams['font.family'] = 'Noto Sans CJK JP'
import famires_v3 as F

SRC = sys.argv[1]
LAY = {'fam_p2': 'p2_efficient_narrow', 'fam_p4': 'p4_relaxed_side'}
NAME = {'fam_p2': '④ 効率重視・小さめの店', 'fam_p4': '⑥ ゆったり・入口が横の店'}
rows = list(csv.DictReader(open(SRC)))

def base(ax, L):
    try:
        F.draw(ax, L, [], {}, {})
    except Exception:
        pass
    for t in L['tables']:
        x0, y0, x1, y1 = F.fp(t)
        ax.add_patch(Rectangle((x0, y0), x1-x0, y1-y0, fc='#e9ecef', ec='#868e96', lw=0.6, zorder=3))

def panel(ax, L, sub, title, mx):
    base(ax, L)
    stuck = [r for r in sub if r['resolved'] == '動けなくなった']
    # 止まっていた時間の合計を 0.5m のマスで集める
    cell = collections.Counter()
    for r in sub:
        cell[(round(float(r['x'])*2)/2, round(float(r['z'])*2)/2)] += float(r['duration_s'])
    n0 = len({r['seed'] for r in sub}) or 1
    for (x, z), v in cell.items():
        ax.add_patch(Rectangle((x-0.25, z-0.25), 0.5, 0.5, fc='#f08c00', alpha=min(0.85, 0.08 + 0.8*(v/n0)/mx), ec='none', zorder=6))
    ax.scatter([float(r['x']) for r in stuck], [float(r['z']) for r in stuck], s=10, c='#c92a2a', zorder=7, linewidths=0)
    n = len({r['seed'] for r in sub}) or 1
    ax.set_title(f"{title}\n動けなくなった {len(stuck)/n:.0f}回（ピーク2時間あたり）・止まっていた合計 {sum(float(r['duration_s']) for r in sub)/n/60:.0f}分", fontsize=11)

def fig(label, store, out):
    L = json.load(open(f'/home/claude/p2m-site/public/sim/layouts/{LAY[store]}.json'))['layout']
    f, axs = plt.subplots(1, 2, figsize=(14, 6.4))
    subs = {rb: [r for r in rows if r['label'] == label and r['store'] == store and r['robots'] == rb] for rb in ['1', '2']}
    mx = 1
    for sub in subs.values():
        c = collections.Counter(); n0 = len({r['seed'] for r in sub}) or 1
        for r in sub: c[(round(float(r['x'])*2)/2, round(float(r['z'])*2)/2)] += float(r['duration_s']) / n0
        mx = max([mx] + list(c.values()))
    for ax, rb in zip(axs, ['1', '2']):
        panel(ax, L, subs[rb], f'ロボット{rb}台', mx)
    f.suptitle(f'{NAME[store]}・混んでいるとき：配膳ロボットが止まった場所（Unity・10回の平均）', fontsize=13, weight='bold')
    f.text(0.5, 0.02, 'だいだいの濃さ＝そのマスで止まっていた時間の合計　赤い点＝動けなくなり、スタッフが動かしに行った場所', ha='center', fontsize=10, color='#495057')
    f.tight_layout(rect=(0, 0.04, 1, 0.95)); f.savefig(out, dpi=130); print(out)

fig('a36', 'fam_p2', 'stopmap_p2_crowded.png')
fig('a49', 'fam_p4', 'stopmap_p4_crowded.png')

# ④と⑥を同じ物差しで並べる（混んでいるとき・2台）
def pair(out):
    f, axs = plt.subplots(1, 2, figsize=(14, 6.4))
    sets = [('a36', 'fam_p2'), ('a49', 'fam_p4')]
    subs = [[r for r in rows if r['label'] == l and r['store'] == s and r['robots'] == '2'] for l, s in sets]
    mx = 1
    for sub in subs:
        c = collections.Counter(); n0 = len({r['seed'] for r in sub}) or 1
        for r in sub: c[(round(float(r['x'])*2)/2, round(float(r['z'])*2)/2)] += float(r['duration_s']) / n0
        mx = max([mx] + list(c.values()))
    for ax, (l, s), sub in zip(axs, sets, subs):
        L = json.load(open(f'/home/claude/p2m-site/public/sim/layouts/{LAY[s]}.json'))['layout']
        panel(ax, L, sub, NAME[s], mx)
    f.suptitle('混んでいるとき・ロボット2台：配膳ロボットが止まった場所（Unity・10回の平均）', fontsize=13, weight='bold')
    f.text(0.5, 0.02, 'だいだいの濃さ＝そのマスで止まっていた時間の合計（左右で同じ物差し）　赤い点＝動けなくなり、スタッフが動かしに行った場所', ha='center', fontsize=10, color='#495057')
    f.tight_layout(rect=(0, 0.04, 1, 0.95)); f.savefig(out, dpi=130); print(out)
pair('stopmap_p2_vs_p4_crowded_r2.png')
for l, s in [('a36','fam_p2'),('a49','fam_p4')]:
    st = [r for r in rows if r['label']==l and r['store']==s and r['robots']=='2' and r['resolved']=='動けなくなった']
    print(s, collections.Counter(r['cause'] for r in st).most_common())
```

```text
$ python3 stopmap.py ".../unity/stop_map_20261101/stops_extract.csv"
stopmap_p2_crowded.png
stopmap_p4_crowded.png
stopmap_p2_vs_p4_crowded_r2.png
fam_p2 [('もう1台のロボット', 354), ('人が立ち止まっていた', 159), ('スタッフとすれ違った', 81), ('お客さんとすれ違った', 40), ('椅子・荷物・ベビーカー', 30), ('分からない', 14), ('通路の幅が足りない', 1)]
fam_p4 [('もう1台のロボット', 75), ('スタッフとすれ違った', 8), ('お客さんとすれ違った', 6), ('人が立ち止まっていた', 5), ('分からない', 2), ('椅子・荷物・ベビーカー', 2)]
```


## 作業ログ ― スタッフの負担と待ち時間を、6店 × 0〜2台で並べた

### 1台目は歩く距離を2〜3割減らし、2台目は動かしに行く回数を一気に増やす

店ごとに一番新しい夜の結果（10回の中央値）を、空いている・普通・混んでいるの3つで並べました。下の表は普通の混み具合で、0台→1台→2台の順です。

| 店（スタッフ） | 入店待ち（分） | 料理が届くまで（分） | 歩く距離（km/人） | 手がふさがる割合 | ロボットを動かしに行った回数 |
|---|---|---|---|---|---|
| ②（2人） | 2.0→1.7→2.1 | 0.9→1.0→1.0 | 2.3→1.7→1.7 | 66%→56%→58% | 0→0→12 |
| ③（4人） | 4.1→5.0→4.0 | 1.0→2.5→2.0 | 3.4→2.7→2.7 | 77%→73%→72% | 0→4→18 |
| ④（2人） | 13.0→13.8→26.8 | 1.3→2.1→3.5 | 3.4→2.5→3.1 | 84%→81%→89% | 0→13→78 |
| ⑤（4人） | 8.1→6.6→5.5 | 1.1→2.6→2.2 | 4.0→3.2→3.0 | 81%→80%→80% | 0→1→12 |
| ⑥（3人） | 9.6→6.0→4.8 | 1.1→2.1→1.3 | 3.7→2.6→2.4 | 83%→75%→75% | 0→1→8 |
| ⑦（4人） | 4.5→3.2→3.6 | 1.0→2.5→1.6 | 3.4→2.7→2.7 | 77%→74%→69% | 0→6→16 |

混んでいるときも傾向は同じです。

| 店（スタッフ） | 入店待ち（分） | 料理が届くまで（分） | 歩く距離（km/人） | 手がふさがる割合 | ロボットを動かしに行った回数 |
|---|---|---|---|---|---|
| ②（2人） | 11.2→11.4→10.7 | 1.0→2.0→1.4 | 2.8→2.2→2.2 | 75%→67%→66% | 0→13→24 |
| ③（4人） | 19.0→16.4→15.8 | 1.0→2.6→1.8 | 4.3→3.4→3.2 | 84%→79%→79% | 0→6→14 |
| ④（2人） | 32.9→31.6→45.9 | 1.3→2.3→2.9 | 4.5→3.3→3.4 | 87%→85%→87% | 0→21→64 |
| ⑤（4人） | 24.8→23.7→24.4 | 1.1→2.7→1.8 | 5.1→4.1→3.9 | 87%→84%→85% | 0→4→6 |
| ⑥（3人） | 30.1→21.6→19.8 | 1.2→2.1→1.3 | 4.9→3.5→3.2 | 89%→84%→83% | 0→1→12 |
| ⑦（4人） | 19.4→17.7→16.6 | 1.0→2.6→1.8 | 4.4→3.5→3.2 | 84%→79%→78% | 0→4→16 |

- スタッフの負担は、どの店でも1台目で減ります。歩く距離は2〜3割減り、④でも減りました。2台目ではほとんど減りません
- ロボットの煩わしさは、2台目で一気に増えます。1台なら動かしに行く回数は0〜21回、2台だと6〜78回です。④の普通の78回は、2時間で約1.5分に1回です
- お客さんの待ちでは、料理が届くまでが延びます。ほぼ全部の店で約1分から2〜2.5分になります。料理はロボットを優先し、ロボットが来るまで最大2分待ってからスタッフが運ぶ決まりにしているためです
- 入店待ちは店しだいで、⑤⑥は縮み、②③⑦はほぼ変わらず、④の2台は倍になります

通路の幅そのものより、すれ違える場所や回り道があるかが効いているようです。③と、③の細い通路を80cmから95cmに広げた⑦では、2台のときの動かしに行く回数が18回と16.5回で、ほとんど変わりません。④は上に幅約2.1mの広い通路があるのに、2台とも同じ下の狭い道を行き来していました。

### 負担と待ち時間を並べる burden.py（全文）

```python
import csv,re,collections,statistics as st,os,json
L=os.path.expanduser('~/mnt/KIOXIA/r2r2r-data/service_sim/unity/unity_ledger_v1.csv')
R=list(csv.DictReader(open(L)))
N={'fam_p0':'②','fam_p1':'③','fam_p2':'④','fam_p3':'⑤','fam_p4':'⑥','fam_p5':'⑦'}
# 普通の混み具合の夜（店ごとに一番新しい作り）
NORMAL={'fam_p1':'20261102_night','fam_p5':'20261102_night','fam_p3':'20261103_night','fam_p4':'20261104_night','fam_p0':'20261105_night','fam_p2':'20261030_night'}
LOW={'fam_p0':'a14','fam_p1':'a30','fam_p2':'a20','fam_p3':'a33','fam_p4':'a27','fam_p5':'a30'}
HIGH={'fam_p0':'a24','fam_p1':'a54','fam_p2':'a36','fam_p3':'a59','fam_p4':'a49','fam_p5':'a54'}
def pick(prefix,store):
  return [r for r in R if r['run_folder'].startswith(prefix) and r['store']==store and '_ruleb' not in r['run_folder']]
M=[('entry_wait_mean','入店待ち（分）'),('walk_km_per_staff','歩く距離（km/人）'),('staff_util','手がふさがる割合'),('rescues','動かしに行った回数'),('staff_yield_share_of_staff_time','よけていた時間の割合'),('dish_wait_mean','料理が届くまで（分）'),('gave_up','あきらめて帰った組')]
out=[]
for store in N:
  for cond,prefix in [('空いている','20261106_night_'+LOW[store]+'_'),('普通',NORMAL[store]+'_'),('混んでいる','20261106_night_'+HIGH[store]+'_')]:
    rs=pick(prefix,store)
    for rb in '012':
      sub=[r for r in rs if r['robots']==rb]
      if not sub: continue
      d=dict(store=N[store],cond=cond,robots=int(rb),n=len(sub),staff=int(sub[0]['staff']),arr=int(sub[0]['arrivals_per_h']),cs=sub[0]['unity_cs'])
      for k,lab in M:
        v=[float(r[k]) for r in sub if r[k] not in('','nan')]
        d[k]=st.median(v) if v else None
      out.append(d)
json.dump(out,open('burden.json','w'),ensure_ascii=False,indent=1)
for d in out: print(d['store'],d['cond'],'人',d['staff'],'台',d['robots'],'n',d['n'],' '.join('%s=%.2f'%(k[:8],d[k]) for k,_ in M if d[k] is not None))
```

出力の全量（burden.json）：

```json
[
 {
  "store": "②",
  "cond": "空いている",
  "robots": 0,
  "n": 10,
  "staff": 2,
  "arr": 14,
  "cs": "ServiceSim/cs:44",
  "entry_wait_mean": 1.28375,
  "walk_km_per_staff": 1.7913999999999999,
  "staff_util": 0.5306500000000001,
  "rescues": 0.0,
  "staff_yield_share_of_staff_time": 0.0,
  "dish_wait_mean": 0.69235,
  "gave_up": 2.0
 },
 {
  "store": "②",
  "cond": "空いている",
  "robots": 1,
  "n": 10,
  "staff": 2,
  "arr": 14,
  "cs": "ServiceSim/cs:44",
  "entry_wait_mean": 1.2465000000000002,
  "walk_km_per_staff": 1.3877000000000002,
  "staff_util": 0.44595,
  "rescues": 1.0,
  "staff_yield_share_of_staff_time": 0.0208,
  "dish_wait_mean": 1.0663,
  "gave_up": 3.0
 },
 {
  "store": "②",
  "cond": "空いている",
  "robots": 2,
  "n": 10,
  "staff": 2,
  "arr": 14,
  "cs": "ServiceSim/cs:44",
  "entry_wait_mean": 1.4529999999999998,
  "walk_km_per_staff": 1.4240499999999998,
  "staff_util": 0.46704999999999997,
  "rescues": 8.5,
  "staff_yield_share_of_staff_time": 0.0204,
  "dish_wait_mean": 0.8196,
  "gave_up": 9.0
 },
 {
  "store": "②",
  "cond": "普通",
  "robots": 0,
  "n": 10,
  "staff": 2,
  "arr": 19,
  "cs": "ServiceSim/cs:40",
  "entry_wait_mean": 1.961,
  "walk_km_per_staff": 2.2592,
  "staff_util": 0.65805,
  "rescues": 0.0,
  "staff_yield_share_of_staff_time": 0.0,
  "dish_wait_mean": 0.88725,
  "gave_up": 2.0
 },
 {
  "store": "②",
  "cond": "普通",
  "robots": 1,
  "n": 10,
  "staff": 2,
  "arr": 19,
  "cs": "ServiceSim/cs:40",
  "entry_wait_mean": 1.688,
  "walk_km_per_staff": 1.6810999999999998,
  "staff_util": 0.5577,
  "rescues": 0.0,
  "staff_yield_share_of_staff_time": 0.0186,
  "dish_wait_mean": 1.0446499999999999,
  "gave_up": 3.5
 },
 {
  "store": "②",
  "cond": "普通",
  "robots": 2,
  "n": 10,
  "staff": 2,
  "arr": 19,
  "cs": "ServiceSim/cs:40",
  "entry_wait_mean": 2.0976,
  "walk_km_per_staff": 1.6832,
  "staff_util": 0.58115,
  "rescues": 12.5,
  "staff_yield_share_of_staff_time": 0.0229,
  "dish_wait_mean": 1.03735,
  "gave_up": 10.5
 },
 {
  "store": "②",
  "cond": "混んでいる",
  "robots": 0,
  "n": 10,
  "staff": 2,
  "arr": 24,
  "cs": "ServiceSim/cs:44",
  "entry_wait_mean": 11.1709,
  "walk_km_per_staff": 2.8287,
  "staff_util": 0.7472000000000001,
  "rescues": 0.0,
  "staff_yield_share_of_staff_time": 0.0,
  "dish_wait_mean": 1.0215,
  "gave_up": 2.5
 },
 {
  "store": "②",
  "cond": "混んでいる",
  "robots": 1,
  "n": 10,
  "staff": 2,
  "arr": 24,
  "cs": "ServiceSim/cs:44",
  "entry_wait_mean": 11.36945,
  "walk_km_per_staff": 2.1944999999999997,
  "staff_util": 0.6716500000000001,
  "rescues": 13.0,
  "staff_yield_share_of_staff_time": 0.01545,
  "dish_wait_mean": 1.97655,
  "gave_up": 10.0
 },
 {
  "store": "②",
  "cond": "混んでいる",
  "robots": 2,
  "n": 10,
  "staff": 2,
  "arr": 24,
  "cs": "ServiceSim/cs:44",
  "entry_wait_mean": 10.68545,
  "walk_km_per_staff": 2.1841,
  "staff_util": 0.65545,
  "rescues": 23.5,
  "staff_yield_share_of_staff_time": 0.019549999999999998,
  "dish_wait_mean": 1.3937,
  "gave_up": 16.0
 },
 {
  "store": "③",
  "cond": "空いている",
  "robots": 0,
  "n": 10,
  "staff": 4,
  "arr": 30,
  "cs": "ServiceSim/cs:44",
  "entry_wait_mean": 1.34195,
  "walk_km_per_staff": 2.6948499999999997,
  "staff_util": 0.5931,
  "rescues": 0.0,
  "staff_yield_share_of_staff_time": 0.0,
  "dish_wait_mean": 0.64865,
  "gave_up": 7.0
 },
 {
  "store": "③",
  "cond": "空いている",
  "robots": 1,
  "n": 10,
  "staff": 4,
  "arr": 30,
  "cs": "ServiceSim/cs:44",
  "entry_wait_mean": 1.4193500000000001,
  "walk_km_per_staff": 2.12365,
  "staff_util": 0.54585,
  "rescues": 5.0,
  "staff_yield_share_of_staff_time": 0.03215,
  "dish_wait_mean": 2.15545,
  "gave_up": 28.0
 },
 {
  "store": "③",
  "cond": "空いている",
  "robots": 2,
  "n": 10,
  "staff": 4,
  "arr": 30,
  "cs": "ServiceSim/cs:44",
  "entry_wait_mean": 1.42245,
  "walk_km_per_staff": 1.98565,
  "staff_util": 0.5255000000000001,
  "rescues": 9.0,
  "staff_yield_share_of_staff_time": 0.03505,
  "dish_wait_mean": 1.39155,
  "gave_up": 31.0
 },
 {
  "store": "③",
  "cond": "普通",
  "robots": 0,
  "n": 10,
  "staff": 4,
  "arr": 42,
  "cs": "ServiceSim/cs:34",
  "entry_wait_mean": 4.08905,
  "walk_km_per_staff": 3.35695,
  "staff_util": 0.76705,
  "rescues": 0.0,
  "staff_yield_share_of_staff_time": 0.0,
  "dish_wait_mean": 0.97575,
  "gave_up": 12.5
 },
 {
  "store": "③",
  "cond": "普通",
  "robots": 1,
  "n": 10,
  "staff": 4,
  "arr": 42,
  "cs": "ServiceSim/cs:34",
  "entry_wait_mean": 5.0003,
  "walk_km_per_staff": 2.7123,
  "staff_util": 0.7263,
  "rescues": 3.5,
  "staff_yield_share_of_staff_time": 0.03855,
  "dish_wait_mean": 2.47645,
  "gave_up": 45.5
 },
 {
  "store": "③",
  "cond": "普通",
  "robots": 2,
  "n": 10,
  "staff": 4,
  "arr": 42,
  "cs": "ServiceSim/cs:34",
  "entry_wait_mean": 3.9932,
  "walk_km_per_staff": 2.6609999999999996,
  "staff_util": 0.7179500000000001,
  "rescues": 18.0,
  "staff_yield_share_of_staff_time": 0.0475,
  "dish_wait_mean": 1.9549500000000002,
  "gave_up": 51.5
 },
 {
  "store": "③",
  "cond": "混んでいる",
  "robots": 0,
  "n": 10,
  "staff": 4,
  "arr": 54,
  "cs": "ServiceSim/cs:44",
  "entry_wait_mean": 19.0169,
  "walk_km_per_staff": 4.27475,
  "staff_util": 0.83535,
  "rescues": 0.0,
  "staff_yield_share_of_staff_time": 0.0,
  "dish_wait_mean": 0.98035,
  "gave_up": 14.5
 },
 {
  "store": "③",
  "cond": "混んでいる",
  "robots": 1,
  "n": 10,
  "staff": 4,
  "arr": 54,
  "cs": "ServiceSim/cs:44",
  "entry_wait_mean": 16.39535,
  "walk_km_per_staff": 3.4289,
  "staff_util": 0.7863,
  "rescues": 6.0,
  "staff_yield_share_of_staff_time": 0.04335,
  "dish_wait_mean": 2.5858999999999996,
  "gave_up": 57.5
 },
 {
  "store": "③",
  "cond": "混んでいる",
  "robots": 2,
  "n": 10,
  "staff": 4,
  "arr": 54,
  "cs": "ServiceSim/cs:44",
  "entry_wait_mean": 15.80565,
  "walk_km_per_staff": 3.238,
  "staff_util": 0.79155,
  "rescues": 14.5,
  "staff_yield_share_of_staff_time": 0.0531,
  "dish_wait_mean": 1.8232,
  "gave_up": 74.5
 },
 {
  "store": "④",
  "cond": "空いている",
  "robots": 0,
  "n": 10,
  "staff": 2,
  "arr": 20,
  "cs": "ServiceSim/cs:44",
  "entry_wait_mean": 1.7405499999999998,
  "walk_km_per_staff": 2.7745499999999996,
  "staff_util": 0.7011499999999999,
  "rescues": 0.0,
  "staff_yield_share_of_staff_time": 0.0,
  "dish_wait_mean": 1.1260500000000002,
  "gave_up": 0.0
 },
 {
  "store": "④",
  "cond": "空いている",
  "robots": 1,
  "n": 10,
  "staff": 2,
  "arr": 20,
  "cs": "ServiceSim/cs:44",
  "entry_wait_mean": 2.5002,
  "walk_km_per_staff": 1.9114499999999999,
  "staff_util": 0.6778500000000001,
  "rescues": 11.0,
  "staff_yield_share_of_staff_time": 0.0137,
  "dish_wait_mean": 2.1147,
  "gave_up": 24.5
 },
 {
  "store": "④",
  "cond": "空いている",
  "robots": 2,
  "n": 10,
  "staff": 2,
  "arr": 20,
  "cs": "ServiceSim/cs:44",
  "entry_wait_mean": 4.04735,
  "walk_km_per_staff": 1.86785,
  "staff_util": 0.7531,
  "rescues": 40.0,
  "staff_yield_share_of_staff_time": 0.0207,
  "dish_wait_mean": 3.15365,
  "gave_up": 50.0
 },
 {
  "store": "④",
  "cond": "普通",
  "robots": 0,
  "n": 10,
  "staff": 2,
  "arr": 28,
  "cs": "ServiceSim/cs:29",
  "entry_wait_mean": 12.950050000000001,
  "walk_km_per_staff": 3.4234,
  "staff_util": 0.84385,
  "rescues": 0.0,
  "staff_yield_share_of_staff_time": 0.0,
  "dish_wait_mean": 1.2605,
  "gave_up": 0.0
 },
 {
  "store": "④",
  "cond": "普通",
  "robots": 1,
  "n": 10,
  "staff": 2,
  "arr": 28,
  "cs": "ServiceSim/cs:29",
  "entry_wait_mean": 13.7843,
  "walk_km_per_staff": 2.5121,
  "staff_util": 0.8098000000000001,
  "rescues": 13.0,
  "staff_yield_share_of_staff_time": 0.01525,
  "dish_wait_mean": 2.1000500000000004,
  "gave_up": 32.5
 },
 {
  "store": "④",
  "cond": "普通",
  "robots": 2,
  "n": 10,
  "staff": 2,
  "arr": 28,
  "cs": "ServiceSim/cs:29",
  "entry_wait_mean": 26.84815,
  "walk_km_per_staff": 3.08625,
  "staff_util": 0.8864,
  "rescues": 78.5,
  "staff_yield_share_of_staff_time": 0.0291,
  "dish_wait_mean": 3.4669499999999998,
  "gave_up": 95.5
 },
 {
  "store": "④",
  "cond": "混んでいる",
  "robots": 0,
  "n": 10,
  "staff": 2,
  "arr": 36,
  "cs": "ServiceSim/cs:44",
  "entry_wait_mean": 32.86845,
  "walk_km_per_staff": 4.5241,
  "staff_util": 0.8744000000000001,
  "rescues": 0.0,
  "staff_yield_share_of_staff_time": 0.0,
  "dish_wait_mean": 1.2844,
  "gave_up": 1.0
 },
 {
  "store": "④",
  "cond": "混んでいる",
  "robots": 1,
  "n": 10,
  "staff": 2,
  "arr": 36,
  "cs": "ServiceSim/cs:44",
  "entry_wait_mean": 31.5747,
  "walk_km_per_staff": 3.2913500000000004,
  "staff_util": 0.8525,
  "rescues": 21.0,
  "staff_yield_share_of_staff_time": 0.01795,
  "dish_wait_mean": 2.2622999999999998,
  "gave_up": 39.5
 },
 {
  "store": "④",
  "cond": "混んでいる",
  "robots": 2,
  "n": 10,
  "staff": 2,
  "arr": 36,
  "cs": "ServiceSim/cs:44",
  "entry_wait_mean": 45.924949999999995,
  "walk_km_per_staff": 3.4280999999999997,
  "staff_util": 0.8693,
  "rescues": 63.5,
  "staff_yield_share_of_staff_time": 0.0335,
  "dish_wait_mean": 2.87,
  "gave_up": 81.5
 },
 {
  "store": "⑤",
  "cond": "空いている",
  "robots": 0,
  "n": 10,
  "staff": 4,
  "arr": 33,
  "cs": "ServiceSim/cs:44",
  "entry_wait_mean": 1.40395,
  "walk_km_per_staff": 3.0769,
  "staff_util": 0.6718500000000001,
  "rescues": 0.0,
  "staff_yield_share_of_staff_time": 0.0,
  "dish_wait_mean": 0.7564,
  "gave_up": 0.5
 },
 {
  "store": "⑤",
  "cond": "空いている",
  "robots": 1,
  "n": 10,
  "staff": 4,
  "arr": 33,
  "cs": "ServiceSim/cs:44",
  "entry_wait_mean": 1.6046,
  "walk_km_per_staff": 2.4452499999999997,
  "staff_util": 0.6242000000000001,
  "rescues": 3.0,
  "staff_yield_share_of_staff_time": 0.04735,
  "dish_wait_mean": 2.3701499999999998,
  "gave_up": 30.0
 },
 {
  "store": "⑤",
  "cond": "空いている",
  "robots": 2,
  "n": 10,
  "staff": 4,
  "arr": 33,
  "cs": "ServiceSim/cs:44",
  "entry_wait_mean": 1.541,
  "walk_km_per_staff": 2.3354,
  "staff_util": 0.62495,
  "rescues": 7.5,
  "staff_yield_share_of_staff_time": 0.06935,
  "dish_wait_mean": 1.77385,
  "gave_up": 47.5
 },
 {
  "store": "⑤",
  "cond": "普通",
  "robots": 0,
  "n": 10,
  "staff": 4,
  "arr": 46,
  "cs": "ServiceSim/cs:36",
  "entry_wait_mean": 8.1423,
  "walk_km_per_staff": 4.021649999999999,
  "staff_util": 0.8069999999999999,
  "rescues": 0.0,
  "staff_yield_share_of_staff_time": 0.0,
  "dish_wait_mean": 1.1223999999999998,
  "gave_up": 2.0
 },
 {
  "store": "⑤",
  "cond": "普通",
  "robots": 1,
  "n": 10,
  "staff": 4,
  "arr": 46,
  "cs": "ServiceSim/cs:36",
  "entry_wait_mean": 6.60285,
  "walk_km_per_staff": 3.1871,
  "staff_util": 0.7967,
  "rescues": 1.0,
  "staff_yield_share_of_staff_time": 0.04725,
  "dish_wait_mean": 2.58765,
  "gave_up": 32.5
 },
 {
  "store": "⑤",
  "cond": "普通",
  "robots": 2,
  "n": 10,
  "staff": 4,
  "arr": 46,
  "cs": "ServiceSim/cs:36",
  "entry_wait_mean": 5.4556000000000004,
  "walk_km_per_staff": 3.0490500000000003,
  "staff_util": 0.80385,
  "rescues": 11.5,
  "staff_yield_share_of_staff_time": 0.06935,
  "dish_wait_mean": 2.1929999999999996,
  "gave_up": 79.5
 },
 {
  "store": "⑤",
  "cond": "混んでいる",
  "robots": 0,
  "n": 10,
  "staff": 4,
  "arr": 59,
  "cs": "ServiceSim/cs:44",
  "entry_wait_mean": 24.786749999999998,
  "walk_km_per_staff": 5.08015,
  "staff_util": 0.86625,
  "rescues": 0.0,
  "staff_yield_share_of_staff_time": 0.0,
  "dish_wait_mean": 1.06465,
  "gave_up": 2.0
 },
 {
  "store": "⑤",
  "cond": "混んでいる",
  "robots": 1,
  "n": 10,
  "staff": 4,
  "arr": 59,
  "cs": "ServiceSim/cs:44",
  "entry_wait_mean": 23.69945,
  "walk_km_per_staff": 4.0808,
  "staff_util": 0.8403499999999999,
  "rescues": 3.5,
  "staff_yield_share_of_staff_time": 0.05115,
  "dish_wait_mean": 2.6826,
  "gave_up": 44.5
 },
 {
  "store": "⑤",
  "cond": "混んでいる",
  "robots": 2,
  "n": 10,
  "staff": 4,
  "arr": 59,
  "cs": "ServiceSim/cs:44",
  "entry_wait_mean": 24.3696,
  "walk_km_per_staff": 3.9036,
  "staff_util": 0.8545,
  "rescues": 6.0,
  "staff_yield_share_of_staff_time": 0.07805000000000001,
  "dish_wait_mean": 1.7687,
  "gave_up": 84.5
 },
 {
  "store": "⑥",
  "cond": "空いている",
  "robots": 0,
  "n": 10,
  "staff": 3,
  "arr": 27,
  "cs": "ServiceSim/cs:44",
  "entry_wait_mean": 1.54245,
  "walk_km_per_staff": 2.80995,
  "staff_util": 0.6984999999999999,
  "rescues": 0.0,
  "staff_yield_share_of_staff_time": 0.0,
  "dish_wait_mean": 0.97385,
  "gave_up": 3.5
 },
 {
  "store": "⑥",
  "cond": "空いている",
  "robots": 1,
  "n": 10,
  "staff": 3,
  "arr": 27,
  "cs": "ServiceSim/cs:44",
  "entry_wait_mean": 1.3712,
  "walk_km_per_staff": 1.9684499999999998,
  "staff_util": 0.6144499999999999,
  "rescues": 0.5,
  "staff_yield_share_of_staff_time": 0.02955,
  "dish_wait_mean": 1.6629999999999998,
  "gave_up": 11.5
 },
 {
  "store": "⑥",
  "cond": "空いている",
  "robots": 2,
  "n": 10,
  "staff": 3,
  "arr": 27,
  "cs": "ServiceSim/cs:44",
  "entry_wait_mean": 1.3526500000000001,
  "walk_km_per_staff": 1.8644,
  "staff_util": 0.5930500000000001,
  "rescues": 4.0,
  "staff_yield_share_of_staff_time": 0.033549999999999996,
  "dish_wait_mean": 0.92635,
  "gave_up": 9.0
 },
 {
  "store": "⑥",
  "cond": "普通",
  "robots": 0,
  "n": 10,
  "staff": 3,
  "arr": 38,
  "cs": "ServiceSim/cs:38",
  "entry_wait_mean": 9.602350000000001,
  "walk_km_per_staff": 3.6864,
  "staff_util": 0.83175,
  "rescues": 0.0,
  "staff_yield_share_of_staff_time": 0.0,
  "dish_wait_mean": 1.1191,
  "gave_up": 7.0
 },
 {
  "store": "⑥",
  "cond": "普通",
  "robots": 1,
  "n": 10,
  "staff": 3,
  "arr": 38,
  "cs": "ServiceSim/cs:38",
  "entry_wait_mean": 6.04015,
  "walk_km_per_staff": 2.5532500000000002,
  "staff_util": 0.75265,
  "rescues": 1.0,
  "staff_yield_share_of_staff_time": 0.03415,
  "dish_wait_mean": 2.0911,
  "gave_up": 19.0
 },
 {
  "store": "⑥",
  "cond": "普通",
  "robots": 2,
  "n": 10,
  "staff": 3,
  "arr": 38,
  "cs": "ServiceSim/cs:38",
  "entry_wait_mean": 4.83355,
  "walk_km_per_staff": 2.3725,
  "staff_util": 0.75325,
  "rescues": 8.0,
  "staff_yield_share_of_staff_time": 0.0368,
  "dish_wait_mean": 1.2742,
  "gave_up": 28.5
 },
 {
  "store": "⑥",
  "cond": "混んでいる",
  "robots": 0,
  "n": 10,
  "staff": 3,
  "arr": 49,
  "cs": "ServiceSim/cs:44",
  "entry_wait_mean": 30.079,
  "walk_km_per_staff": 4.9336,
  "staff_util": 0.8887,
  "rescues": 0.0,
  "staff_yield_share_of_staff_time": 0.0,
  "dish_wait_mean": 1.17035,
  "gave_up": 7.0
 },
 {
  "store": "⑥",
  "cond": "混んでいる",
  "robots": 1,
  "n": 10,
  "staff": 3,
  "arr": 49,
  "cs": "ServiceSim/cs:44",
  "entry_wait_mean": 21.61845,
  "walk_km_per_staff": 3.46545,
  "staff_util": 0.8404499999999999,
  "rescues": 1.0,
  "staff_yield_share_of_staff_time": 0.03275,
  "dish_wait_mean": 2.06595,
  "gave_up": 23.0
 },
 {
  "store": "⑥",
  "cond": "混んでいる",
  "robots": 2,
  "n": 10,
  "staff": 3,
  "arr": 49,
  "cs": "ServiceSim/cs:44",
  "entry_wait_mean": 19.7631,
  "walk_km_per_staff": 3.2082,
  "staff_util": 0.8273999999999999,
  "rescues": 12.0,
  "staff_yield_share_of_staff_time": 0.038099999999999995,
  "dish_wait_mean": 1.3472499999999998,
  "gave_up": 28.5
 },
 {
  "store": "⑦",
  "cond": "空いている",
  "robots": 0,
  "n": 10,
  "staff": 4,
  "arr": 30,
  "cs": "ServiceSim/cs:44",
  "entry_wait_mean": 1.3286,
  "walk_km_per_staff": 2.73,
  "staff_util": 0.6032,
  "rescues": 0.0,
  "staff_yield_share_of_staff_time": 0.0,
  "dish_wait_mean": 0.6911499999999999,
  "gave_up": 8.0
 },
 {
  "store": "⑦",
  "cond": "空いている",
  "robots": 1,
  "n": 10,
  "staff": 4,
  "arr": 30,
  "cs": "ServiceSim/cs:44",
  "entry_wait_mean": 1.40275,
  "walk_km_per_staff": 2.1417,
  "staff_util": 0.53335,
  "rescues": 4.0,
  "staff_yield_share_of_staff_time": 0.024800000000000003,
  "dish_wait_mean": 1.9032499999999999,
  "gave_up": 31.5
 },
 {
  "store": "⑦",
  "cond": "空いている",
  "robots": 2,
  "n": 10,
  "staff": 4,
  "arr": 30,
  "cs": "ServiceSim/cs:44",
  "entry_wait_mean": 1.4339,
  "walk_km_per_staff": 2.0216,
  "staff_util": 0.5339499999999999,
  "rescues": 10.0,
  "staff_yield_share_of_staff_time": 0.032600000000000004,
  "dish_wait_mean": 1.56555,
  "gave_up": 30.0
 },
 {
  "store": "⑦",
  "cond": "普通",
  "robots": 0,
  "n": 10,
  "staff": 4,
  "arr": 42,
  "cs": "ServiceSim/cs:34",
  "entry_wait_mean": 4.4921500000000005,
  "walk_km_per_staff": 3.39475,
  "staff_util": 0.7738499999999999,
  "rescues": 0.0,
  "staff_yield_share_of_staff_time": 0.0,
  "dish_wait_mean": 0.9593,
  "gave_up": 13.0
 },
 {
  "store": "⑦",
  "cond": "普通",
  "robots": 1,
  "n": 10,
  "staff": 4,
  "arr": 42,
  "cs": "ServiceSim/cs:34",
  "entry_wait_mean": 3.2199,
  "walk_km_per_staff": 2.7402499999999996,
  "staff_util": 0.73925,
  "rescues": 5.5,
  "staff_yield_share_of_staff_time": 0.0334,
  "dish_wait_mean": 2.51495,
  "gave_up": 40.5
 },
 {
  "store": "⑦",
  "cond": "普通",
  "robots": 2,
  "n": 10,
  "staff": 4,
  "arr": 42,
  "cs": "ServiceSim/cs:34",
  "entry_wait_mean": 3.5588499999999996,
  "walk_km_per_staff": 2.6528,
  "staff_util": 0.69425,
  "rescues": 16.5,
  "staff_yield_share_of_staff_time": 0.044,
  "dish_wait_mean": 1.6071,
  "gave_up": 54.5
 },
 {
  "store": "⑦",
  "cond": "混んでいる",
  "robots": 0,
  "n": 10,
  "staff": 4,
  "arr": 54,
  "cs": "ServiceSim/cs:44",
  "entry_wait_mean": 19.351550000000003,
  "walk_km_per_staff": 4.35555,
  "staff_util": 0.8397,
  "rescues": 0.0,
  "staff_yield_share_of_staff_time": 0.0,
  "dish_wait_mean": 1.012,
  "gave_up": 18.0
 },
 {
  "store": "⑦",
  "cond": "混んでいる",
  "robots": 1,
  "n": 10,
  "staff": 4,
  "arr": 54,
  "cs": "ServiceSim/cs:44",
  "entry_wait_mean": 17.684350000000002,
  "walk_km_per_staff": 3.4888,
  "staff_util": 0.7928999999999999,
  "rescues": 3.5,
  "staff_yield_share_of_staff_time": 0.03755,
  "dish_wait_mean": 2.5556,
  "gave_up": 50.5
 },
 {
  "store": "⑦",
  "cond": "混んでいる",
  "robots": 2,
  "n": 10,
  "staff": 4,
  "arr": 54,
  "cs": "ServiceSim/cs:44",
  "entry_wait_mean": 16.56605,
  "walk_km_per_staff": 3.1859,
  "staff_util": 0.7793000000000001,
  "rescues": 16.0,
  "staff_yield_share_of_staff_time": 0.054849999999999996,
  "dish_wait_mean": 1.75415,
  "gave_up": 73.5
 }
]
```


## 作業ログ ― ④でロボットどうしが向かい合わないための3つの案を作った

### 下の通路は幅約1.1m、使われていない上の通路は約2.1m

④の平面図の数字を確かめると、島の下の横の通路は約1.1m、島の上の横の通路は約2.1m、左右の縦の通路は約1.2mでした。ロボットが下の通路を通るのは、受け渡し口から左の卓まで一番近いからです。Unity のロボットは、店の地図の上で一番近い道を自分で引くので、2台とも同じ狭い道を行き来します。

すれ違うときに片方が少しよけられる場所が1つあるだけで、「向かい合って2台とも動けない」が「片方が待って、すぐ通れる」に変わります。そこで、向かい合いを起こさない案と、向かい合ったら片方が待つ案を作りました。

【画像】LOG423_04_plan_traffic.png ④の平面図に描いた2つの案。左の案Aは、下の通路を左へ・左と真ん中の通路を上へ・上の通路を右へ・パントリー横を下へと1周する矢印。右の案Bは、下・左・パントリー横の3本の1本道と、角の4か所の待つ点

| 案 | 中身 | 席 | 代わりに起きること |
|---|---|---|---|
| 案A 一方通行 | 店の中を1周する向きを決め、その通路では逆向きに進まない | 減らさない | 遠回りになる卓が出る（パントリー横の T21〜T23 は、受け渡し口から約4m → 1周して約19m） |
| 案B 1本道は1台ずつ | 下・左・パントリー横の3本を1本道として扱い、向かいからロボットが来ていたら手前の待つ点で待つ | 減らさない | 待つ時間が出る |
| 案B' よけ場所 | 下の段の2人席 T26 を外し、下の通路の真ん中に1台分のよけ場所を作る（案Bと組み合わせる） | 2席減る（27卓・86席） | 待つ時間が出る。席が減るぶん、同じ来客では待ちが延びやすい |

図面を見ると、④は通路の途中に隙間がなく、通路の途中によけ場所を作るには席を外す必要がありました。案Bの待つ点は、席を減らさずに使える角の空き（4か所）にしています。待つ点は、ロボットの地図でどれもふだんの道（幅1.0m）が通れるマスで、障害物から0.59m以上離れていることを確かめました。

### 案を描いた plan_p2.py（全文）

```python
import sys, json
sys.path.insert(0, '/home/claude/p2m-site/tools/sim_layouts')
import matplotlib; matplotlib.use('Agg')
import matplotlib.pyplot as plt
from matplotlib import font_manager
from matplotlib.patches import Rectangle, FancyArrowPatch, Circle
for f in font_manager.findSystemFonts():
    if 'NotoSansCJK-Regular' in f: font_manager.fontManager.addfont(f)
plt.rcParams['font.family'] = 'Noto Sans CJK JP'
import famires_v3 as F
L = json.load(open('/home/claude/p2m-site/public/sim/layouts/p2_efficient_narrow.json'))['layout']
def base(ax):
    try: F.draw(ax, L, [], {}, {})
    except Exception: pass
    for t in L['tables']:
        x0,y0,x1,y1 = F.fp(t); ax.add_patch(Rectangle((x0,y0),x1-x0,y1-y0,fc='#e9ecef',ec='#868e96',lw=0.6,zorder=3))
        ax.text((x0+x1)/2,(y0+y1)/2,t['id'],ha='center',va='center',fontsize=6,color='#868e96',zorder=4)
def arrow(ax,a,b,c='#1c7ed6'):
    ax.add_patch(FancyArrowPatch(a,b,arrowstyle='-|>',mutation_scale=22,lw=3,color=c,zorder=8))
f, axs = plt.subplots(1,2,figsize=(14,6.6))
# A 一方通行
ax=axs[0]; base(ax)
Z=[('下の通路 ←',[1.4,1.8,8.8,2.9]),('左の通路 ↑',[1.4,2.9,2.6,8.45]),('真ん中の細い通路 ↑',[5.1,2.9,6.3,8.45]),('上の通路 →',[1.4,8.45,10.0,10.0]),('パントリー横 ↓',[8.8,2.9,10.0,8.45])]
for n,(x0,y0,x1,y1) in Z: ax.add_patch(Rectangle((x0,y0),x1-x0,y1-y0,fc='#a5d8ff',alpha=0.35,ec='none',zorder=5))
arrow(ax,(8.4,2.35),(2.2,2.35)); arrow(ax,(2.0,3.2),(2.0,8.0)); arrow(ax,(5.7,3.2),(5.7,8.0)); arrow(ax,(2.6,9.3),(9.2,9.3)); arrow(ax,(9.4,8.2),(9.4,3.2))
arrow(ax,(10.9,4.3),(9.6,2.7),'#2f9e44')
ax.set_title('案A 一方通行：店の中を1周する向きを決める\n（青＝向きを決める通路、緑＝受け渡し口から出る向き）',fontsize=11)
# B 待つ場所
ax=axs[1]; base(ax)
S=[('下の通路',[1.4,1.8,8.8,2.9]),('左の通路',[1.4,2.9,2.6,8.45]),('パントリー横',[8.8,2.9,10.0,8.45])]
for n,(x0,y0,x1,y1) in S: ax.add_patch(Rectangle((x0,y0),x1-x0,y1-y0,fc='#ffd8a8',alpha=0.45,ec='none',zorder=5))
P=[('①',0.75,1.6),('②',9.4,1.1),('③',2.0,9.3),('④',9.4,9.3)]
for n,x,y in P:
    ax.add_patch(Circle((x,y),0.4,fc='#e8590c',ec='white',lw=1.5,zorder=8)); ax.text(x,y,n,ha='center',va='center',color='white',fontsize=10,weight='bold',zorder=9)
ax.set_title('案B 1本道は1台ずつ：向かいから来ていたら、端の待つ場所で待つ\n（だいだい＝1本道、①〜④＝待つ場所。席は減らさない）',fontsize=11)
f.suptitle('④ 効率重視・小さめの店：ロボットどうしが向かい合わないための2つの案',fontsize=13,weight='bold')
f.tight_layout(rect=(0,0,1,0.95)); f.savefig('plan_p2_traffic.png',dpi=130)
```

### サイトの回し方の決まりに、2つの決まりを「切」で足した（params の unity_rules）

決まりの正はサイトです。サイトの params（public/sim/serving-robot-params.json）の unity_rules に、案Aと案B・B'の決まりを、既定は切で足しました。試算の数字は変えていません。

```json
{
 "traffic_one_way": {
  "value": false,
  "role": "site_rule",
  "stores_only": [
   "p2_efficient_narrow"
  ],
  "note": "（切）案A 一方通行（2026-11-01 エイイチと決めた。④で試す）。ロボットは zones の中では dir の向きにしか進まない（逆向きに進むマスは通れない扱い。横切るのはよい）。受け渡し口から出るときは下の通路へ向かう。店の中を1周する向き：下の通路は左へ、左と真ん中の細い通路は上へ、上の通路は右へ、パントリー横は下へ。遠回りになる卓（パントリー横の T21〜T23 など）が出るのは承知の上。zones の外（受け渡し口のまわり・下の段の卓の前など）は向きを決めない。試算の数字（卓までの距離）は変えない",
  "zones": {
   "p2_efficient_narrow": [
    {
     "label": "下の通路",
     "rect": [
      1.4,
      1.8,
      8.8,
      2.9
     ],
     "dir": [
      -1,
      0
     ]
    },
    {
     "label": "左の通路",
     "rect": [
      1.4,
      2.9,
      2.6,
      8.45
     ],
     "dir": [
      0,
      1
     ]
    },
    {
     "label": "真ん中の細い通路",
     "rect": [
      5.1,
      2.9,
      6.3,
      8.45
     ],
     "dir": [
      0,
      1
     ]
    },
    {
     "label": "上の通路",
     "rect": [
      1.4,
      8.45,
      10.0,
      10.0
     ],
     "dir": [
      1,
      0
     ]
    },
    {
     "label": "パントリー横",
     "rect": [
      8.8,
      2.9,
      10.0,
      8.45
     ],
     "dir": [
      0,
      -1
     ]
    }
   ]
  }
 },
 "traffic_single_lane": {
  "value": false,
  "role": "site_rule",
  "stores_only": [
   "p2_efficient_narrow",
   "p2_efficient_narrow_noT26"
  ],
  "note": "（切）案B 1本道は1台ずつ（2026-11-01 エイイチと決めた。④で試す）。lanes の中に、向かいの向きに進むロボットがいる（または入ろうとしている）あいだは入らず、手前の待つ点（wait_points のうち近い方）で待つ。同じ向きなら続いて入ってよい。同時に入ろうとしたら、料理を載せている方が先（今のよけ方の決まりと同じ）。同じなら先に待つ点に着いた方。案B'（p2_efficient_narrow_noT26：下の段の T26 を外した試しの店。public/sim/layouts/trials/p2_noT26/）では、下の通路は真ん中で2つに分かれ、外した所（refuge）がその間の待つ場所になる。席を減らさずにできるのは案B、席を2つ減らすのが案B'。試算の数字は変えない",
  "lanes": {
   "p2_efficient_narrow": [
    {
     "label": "下の通路",
     "rect": [
      1.4,
      1.8,
      8.8,
      2.9
     ],
     "wait_points": [
      [
       0.75,
       1.6
      ],
      [
       9.4,
       1.1
      ]
     ]
    },
    {
     "label": "左の通路",
     "rect": [
      1.4,
      2.9,
      2.6,
      8.45
     ],
     "wait_points": [
      [
       0.75,
       1.6
      ],
      [
       2.0,
       9.3
      ]
     ]
    },
    {
     "label": "パントリー横",
     "rect": [
      8.8,
      2.9,
      10.0,
      8.45
     ],
     "wait_points": [
      [
       9.4,
       1.1
      ],
      [
       9.4,
       9.3
      ]
     ]
    }
   ],
   "p2_efficient_narrow_noT26": [
    {
     "label": "下の通路の左半分",
     "rect": [
      1.4,
      1.8,
      5.3,
      2.9
     ],
     "wait_points": [
      [
       0.75,
       1.6
      ],
      [
       6.0,
       0.9
      ]
     ]
    },
    {
     "label": "下の通路の右半分",
     "rect": [
      6.7,
      1.8,
      8.8,
      2.9
     ],
     "wait_points": [
      [
       6.0,
       0.9
      ],
      [
       9.4,
       1.1
      ]
     ]
    },
    {
     "label": "左の通路",
     "rect": [
      1.4,
      2.9,
      2.6,
      8.45
     ],
     "wait_points": [
      [
       0.75,
       1.6
      ],
      [
       2.0,
       9.3
      ]
     ]
    },
    {
     "label": "パントリー横",
     "rect": [
      8.8,
      2.9,
      10.0,
      8.45
     ],
     "wait_points": [
      [
       9.4,
       1.1
      ],
      [
       9.4,
       9.3
      ]
     ]
    }
   ]
  },
  "refuge": {
   "p2_efficient_narrow_noT26": {
    "label": "外した T26 の跡（よけ場所）",
    "point": [
     6.0,
     0.9
    ],
    "rect": [
     5.3,
     0.0,
     6.7,
     1.8
    ]
   }
  },
  "wait_points_checked": "2026-11-01 ロボットの地図で、待つ点はどれもふだんの道（1.0m）が通れるマス（free_class=2）で、障害物から0.59m以上離れていることを確かめた"
 }
}
```

### 公開している④は変えず、T26 を外した店を別のフォルダに作った trial_p2_traffic.py（全文）

公開している店の形は基本変えない決まりなので、案B'の店は public/sim/layouts/trials/p2_noT26/ に別に作りました。作り方・決まりの確かめ・Unity 用の細かい形と地図は、6つの店と同じ規則です。卓の番号は④と同じまま（T26 だけ抜ける）にしました。

```python
"""④ 効率重視・小さめの店で、ロボットどうしが向かい合わないための試し（2026-11-01 エイイチと決めた）

案B'（席を1つ外して、下の通路の途中によけ場所を作る）の店の形を、公開している ④ とは別のフォルダに作る。
公開している店の形のデータ（public/sim/layouts/ の p2_efficient_narrow*）は変えない（2026-10-31 の決まり）。

- 下の段の2人席 T26 を外す（席 2 つ減る）。卓の番号は ④ と同じまま（T26 だけ抜ける）
- 外した所（下の通路の真ん中・真ん中の細い通路の下の口の前）を、ロボットのよけ場所にする
- 店の形の作り方・確かめ・Unity の細かい形と地図は famires_v3 と同じ規則で作る
- 出力：public/sim/layouts/trials/p2_noT26/（p2_efficient_narrow_noT26.json・_analysis.json・.png・unity/ の2つ）

使い方: python3 trial_p2_traffic.py
"""
import json, sys
from pathlib import Path
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import famires_v3 as F

HERE = Path(__file__).resolve().parent
OUT = HERE.parent.parent / "public" / "sim" / "layouts" / "trials" / "p2_noT26"
NM = "p2_efficient_narrow_noT26"
REMOVE = "T26"


def main():
    L = F.prepare(F.gen.p2_efficient_narrow())
    assert any(t["id"] == REMOVE for t in L["tables"])
    L["tables"] = [t for t in L["tables"] if t["id"] != REMOVE]
    L["name"] = L["name"] + "（試し：T26 を外してよけ場所）"
    v = F.check_rules(L)
    ctx = {}
    res_a, err_a = F.analyze(L, F.SETTINGS["res"], ctx)
    res_b, err_b = F.analyze(L, F.SETTINGS["check_res"])
    v += err_a + [e for e in err_b if e not in err_a]
    if v:
        for k, m in v:
            print("NG", k, m)
        sys.exit(1)
    OUT.mkdir(parents=True, exist_ok=True)
    gen_id = "trial-p2-noT26-" + F.generation_id()
    conv = [a["id"] for a, b in zip(res_a, res_b) if a["category"] != b["category"]]
    summ = F.summarize(L, res_a)
    fig, ax = plt.subplots(figsize=(14, 11), dpi=120)
    F.draw(ax, L, res_a, summ, conv)
    fig.savefig(OUT / f"{NM}.png", bbox_inches="tight", facecolor="white")
    F.export(L, res_a, summ, conv, NM, OUT, gen_id)
    U = F.export_unity(L, res_a, ctx, NM, OUT, gen_id)
    vr = F.check_register_clear(U)
    if vr:
        print("NG", vr); sys.exit(1)
    print("OK", gen_id, summ, "マス目の差", conv)


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

```text
$ python3 trial_p2_traffic.py
OK trial-p2-noT26-e578507fff7d {'name': '④ 効率重視型・狭い店・入口が右の壁（試し：T26 を外してよけ場所）', 'seats': 86, 'tables': 27, 'model': 'BellaBot', 'pass_width': 0.8, 'normal_width': 1.0, 'robot_tables': 27, 'robot_seats': 86, 'narrow_tables': 0, 'narrow_seats': 0, 'human_tables': 0, 'human_seats': 0, 'robot_dist_mean': 8.6, 'robot_dist_max': 13.92, 'human_walk_total': 0.0, 'human_walk_max': None} マス目の差 []
```

④（28卓）の卓までの平均距離は8.5m・最長13.92mで、T26 を外した店は8.6m・13.92mでした。

```text
commit f3a3888210d95f8c6e5be6d420dcb61636cd004d
Author: PixelstoMotors <studio@adaptdesign.jp>
Date:   Sun Nov 1 20:08:46 2026 +0900

    ④でロボットどうしが向かい合わない試し：一方通行（案A）・1本道は1台ずつ（案B）・T26を外したよけ場所（案B'）
    
    - params の unity_rules に traffic_one_way・traffic_single_lane を足した（既定は切。試算の数字は変えない）
    - 公開している店の形は変えず、案B' の店の形を trials/p2_noT26/ に別に作った
    
    Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01LevEXTjmDgrBZ74rhCRBtF

diff --git a/tools/sim_layouts/README.md b/tools/sim_layouts/README.md
index c495343..7dd1b31 100644
--- a/tools/sim_layouts/README.md
+++ b/tools/sim_layouts/README.md
@@ -42,6 +42,13 @@
 - 卓の数・止まる位置・距離は変わらない（椅子は外形の内側に置くだけ）。版 2e9fc773050a → dffc100b269c（②の部品と説明が増えただけで、6店とも数字は同じ。サイトの試算は計算し直していない）
 - まだないもの：人がロボットをよける逃げ場（refuges）
 
+## 試しの店の形（`public/sim/layouts/trials/`。2026-11-01）
+
+公開している店の形は変えずに、試しのためだけの店の形を別のフォルダに作る。作り方・確かめ・Unity の細かい形と地図は famires_v3 と同じ規則。
+
+- `trials/p2_noT26/`（`trial_p2_traffic.py`）：④から下の段の2人席 T26 を外し、下の通路の真ん中にロボットのよけ場所を作った店（案B'）。27卓・86席。卓の番号は④と同じまま（T26 だけ抜ける）。版は `trial-p2-noT26-…`
+- 一方通行（案A）と1本道は1台ずつ（案B・B'）の決まりは、params の `unity_rules.traffic_one_way`・`traffic_single_lane`（どちらも既定は切）
+
 ## 動かし方
 
 ```
```

### 依頼59「店4の一方通行と1本道は1台ずつを切り替えで入れる」の全文

```text
---
作業フォルダ: /Users/shimazueiichi/Robotics_UX/ServiceSim
上限時間: 180
---
# 依頼59：④で「一方通行（案A）」「1本道は1台ずつ（案B）」「T26 を外したよけ場所（案B'）」を切り替えで入れる（2026-11-01）

エイイチの決定（2026-11-01 20:07）：④ 効率重視・小さめの店では、混むとロボット2台が下の狭い通路などで向かい合って動けなくなり、スタッフが動かしに行く回数が2時間で60〜80回になる。向かい合わないための3つの案を、夜に種10個で比べる。この依頼は、その切り替えを入れて確かめるところまで。夜に回すのは次の依頼60。
**エイイチは寝ているかもしれない。人に聞かないと進めない所に当たったら、そこで止めて報告に書いて終わる。**

決まりの正はサイト（p2m-site の main、コミット f3a3888 以降）。Unity で決まりの形を変えない。

- `public/sim/serving-robot-params.json` の `unity_rules.traffic_one_way`（案A）と `unity_rules.traffic_single_lane`（案B・B'）。中身（通路の矩形・向き・待つ点）と note の文がそのまま決まり
- 案B' の店の形：`public/sim/layouts/trials/p2_noT26/unity/p2_efficient_narrow_noT26.json` と `_map.json`（④から下の段の T26 を外しただけ。卓の番号は④と同じで T26 だけ抜ける。27卓）
- 公開している④の店の形（`public/sim/layouts/unity/p2_efficient_narrow*.json`）は変わっていない

## 始める前に確かめること（1つでも違えば何も変えずに報告して終わる）

1. Unity エディタ・night_runner・rec_runner が動いていない。/Volumes/KIOXIA がマウントされている。unity_ledger_v1.lock がない
2. ServiceSim の保留が 0
3. サイトの params に `traffic_one_way` と `traffic_single_lane` があり、どちらも value が false

## やること

1. **取り込み**：いつもの取り込み方で、params の2つの決まりと、案B' の店の形（unity の2つのファイル）を取り込む。案B' の店は今の6店とは別の店として扱い、条件の値（スタッフ・来客・ロボットの台数）は④と同じものを使う（来客のペースも④のまま。卓が1つ減るぶん待ちが延びるのは承知の上）
2. **案A 一方通行**（切り替え `-ssOneWay`。既定は切）：ロボットが地図の上で道を引くとき、zones の中では dir の向きに進むマスだけを通れる扱いにする（逆向きに進むことができない。dir と直角に動くのはよい）。zones の外は今までどおり。回り込みの道を探すときも同じ扱い。行けなくなる卓が出たら、その卓の名前を報告に書く（直さない）
3. **案B 1本道は1台ずつ**（切り替え `-ssSingleLane`。既定は切）：lanes の中に向かいの向きに進むロボットがいる（入ろうとしている）あいだは入らず、近い方の待つ点で待つ。同じ向きなら続いて入ってよい。同時なら料理を載せている方が先、同じなら先に待つ点に着いた方。待っていた時間は、今の止まりの記録（stops.csv）とは分けて数えられるようにする（原因の名前を新しく足すなど。やり方は任せる。報告に書く）
4. **案B'**：店を案B' の店にし、`-ssSingleLane` を入れたときに p2_efficient_narrow_noT26 の lanes を使う。店の指定の仕方（引数の名前など）は任せる。出力フォルダ名で④と区別できるようにする
5. 3つの切り替えとも切のときは、今の動きと1つも変わらないこと
6. **確かめ**（画面なし・1本ずつ）：④・スタッフ2人・ロボット2台・混んでいる（来客 36組/時）・種0 で、今のまま・案A・案B・案B' の4回を回す。全員が帰ること、行けない卓がないこと、待つ点で待ちっぱなしにならないことを見る。4回の数字（入店待ち・料理の待ち・動けなくなった回数・30秒以上の止まり・案Bの待った時間の合計）を並べる
7. 確かめの4回のうち、案A・案B'の店の 900〜1200 秒の間のコマを1枚ずつ png にして、ロボットの向き・待つ点が決まりどおりかを目で見られるようにする（置き場所は night/20261101_traffic_check/）
8. check-in する。画面なしの実行ファイルを作り直し、build_id と保留なしを記録する

## 変えないもの

- Unity の ArticulationBody の Stiffness・Damping（絶対変更禁止）
- 公開している6店の店の形、ほかの切り替えの既定（すれ違い入・待機場所へ戻る入・案B切・入口番入・持ち場入）
- ロボットの速さ・よけ方の決まり・試算の数字
- 確かめの4回は台帳に追記してよい。ほかの台帳の行は変えない

## 報告

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

- 始める前の確かめ3つ
- 足した切り替えの名前と使い方、案B' の店の指定の仕方、案Bの待った時間の数え方
- 変えたファイル、cs 番号、build_id
- 確かめの4回の表と png の場所
- 行けなくなった卓・待ちっぱなし・全員が帰らなかった回があればその内容
- 依頼60（夜の80回）にそのまま進めてよい状態か。よくないなら、何が足りないか
- 決めてほしいこと（なければ「なし」）
```

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

```text
# 結果：20261101_59_店4の一方通行と1本道は1台ずつを切り替えで入れる

- 状態：完了
- 始めた時刻：2026-11-01 21:38
- 終わった時刻：2026-11-01 22:33（54分）
- 作業フォルダ：/Users/shimazueiichi/Robotics_UX/ServiceSim
- この続きを Claude Code で話すとき：作業フォルダで `claude --resume c7493ad8-d47b-4892-b16a-cefadf9ddc76`

## Claude Code の報告

**やったこと**
- 始める前の確かめ3つは、すべて合格でした（Unity エディタ・夜の実行係は動いていない、KIOXIA はマウント済み、lock なし、保留 0、サイトの params の2つの決まりはどちらも value が false）。
- サイトの main（f3a3888 の次の 3c292e9、公開サイトと md5 一致）から取り込みました。params の traffic_one_way・traffic_single_lane と、案B' の店（p2_efficient_narrow_noT26、27卓）です。案B' の店は別の店として足し、条件の値は fam_p2 のままにしています。
- 切り替えを3つ足しました。どれも既定は params の切です。
  - `-ssOneWay on`（案A）：出力フォルダ名に `_oneway`
  - `-ssSingleLane on`（案B）：`_lane`
  - `-ssStoreVariant noT26`（案B' の店）：`_noT26`
  - make_queue.py と night_runner.py に同じ引数を足し、judge_night.py では回を組み分ける鍵に足しました。
- 案Bの待った時間は、stops.csv に原因「1本道の順番待ち」の行として残し、ほかの止まりと分けて数えられるようにしました。summary.json にも合計を出します。止まった回数と「動けなくなった（60秒）」には数えません。
- 確かめの4回を回し、png を2枚作りました。どちらも確かめた回の記録（robot_track.csv・stops.csv）から真上の図を描いたもので、動画のコマではありません。録画で撮ったコマ版は作っていません。
  - night/20261101_traffic_check/oneway_t1080_records.png（案A、1080秒）
  - night/20261101_traffic_check/laneNoT26_t1100_records.png（案B'、1100秒）

**結果**
4回とも最後まで回り、全員が帰りました。行けない卓は 0 で、案Aでも 28卓すべてに道が引けました。

| 回 | 入店待ち（分） | 料理の待ち（分） | 動けなくなった（回） | 30秒以上の止まり（1本道の待ちを除く） | 1本道で待った合計（秒） |
|---|---|---|---|---|---|
| 今のまま | 26.76 | 3.03 | 56 | 73（73） | − |
| 案A | 26.30 | 3.48 | 57 | 82（82） | − |
| 案B | 24.90 | 3.03 | 50 | 69（63） | 1183.4 |
| 案B' | 19.90 | 2.23 | 30 | 58（53） | 869.8 |

- 種は1つずつなので、差がどこから来たかはまだ言えません。
- 最初（cs:54）の案Bでは、571秒の待ちっぱなしが出ました。原因は2つで、どちらも cs:55 で直し、案B・B' だけを回し直しました。
  - 相手の台が「入ってよい」を持ったまま1本道から遠く離れても、手放していなかった（コードの誤り）。
  - 待つ点が、1本道から出てくる台の道の上にあり、出てくる台が出られなかった。待っている台が相手の進む道の上にいるときだけ、今のよけ方で道を空けるようにしました。
- 直したあとも 200秒を超える待ちが案Bで4回、案B'で2回残りました。中身を見た2回は、相手の台が1本道の中でスタッフに4〜5分ふさがれていたもので、待っていた側は決まりどおりでした。そのあいだに待っていた台の往復が 600秒で打ち切られ、「待ったまま往復が終わった」が案Bで2回、案B'で1回あります。
- 待ちの記録の行が細切れになります（229行のうち 214行が 1秒以下）。秒数の合計は正しいですが、行の数は回数として使えません。原因は特定しきれていません。
- 3つとも切のときに今と変わらないことは、コードを読んで確かめました（新しく足した所は、どれも入切を見てから働きます）。同じ種でも回すたびに数字が揺れる作りなので、数字の一致では確かめられていません。
- 試しの回の1つが途中で止めるつもりが最後まで回り、台帳に1行書いてしまいました（1535行目）。消していません。台帳は 1534行から 1541行に増えましたが、前からの 1534行の md5 は控えと同じです。
- 依頼58 は、決めてほしいことの1が決まるまで、そのままは進めない方がよいです。道具としては今回と同じ形で回せます。
- cs:54・cs:55 を check-in しました。画面なしの実行ファイルの build_id は cs55_20261101_2224、保留は 0 です。

**変えたファイル**
- 新しく：Robot/TrafficRules.cs、Store/StoreLayout_p2_efficient_narrow_noT26.asset、Store/Layouts/trials/ の試しの店の2つ（それぞれ .meta つき）
- 変えた：Robot/GridPlanner.cs・ServingRobotController.cs、Experiment/ExperimentRunner.cs・HeadlessRun.cs・SimParams.cs・TripLogger.cs・serving-robot-params.json、Store/JsonStoreLayout.cs、Editor/ServiceSimSetup.cs、Scenes/ServiceSim_Family.unity、tools/make_queue.py・night_runner.py・judge_night.py
- 報告：claude_code_reports/20261101_traffic_p2_impl.txt
- 控え：unity/site_copy_9b8dae7ebf8c_f3a3888/

**決めてほしいこと**
1. 1本道に横から入るときの待ち方。受け渡し口の前からパントリー横の通路へ入るときは、近い方の待つ点が通路の向こう側になります（案Bでは、真ん中の細い通路から下の通路へ入るときも同じ）。決まりの文に書かれていないので、今は仮で「入る手前のその場で待つ」にしています。このままでよいか、別の待ち方にするか。
2. 待つ点 (9.4, 9.3)・(2.0, 9.3)・(9.4, 1.1) が、1本道から出てくる台の道の上にあります。待っている台を今のよけ方で動かす作りを足しましたが、これでよいか、待つ点の場所をサイトで見直すか。
3. 試しの回が台帳に書いた1行（1535行目）を残すか消すか。
4. 待った回数は、行の数の代わりに lane_holds（待つと決めた回数）で数えてよいか。
5. 試しのビルド Builds/traffic_pre・traffic_pre2 と、途中で止めた試しの回のフォルダ2つを消すか残すか。
```

### Claude Code の報告 20261101_traffic_p2_impl.txt（全文）

```text
====================================================================
④の一方通行（案A）・1本道は1台ずつ（案B）・T26 を外した店（案B'）を切り替えで入れ、4回で確かめた
依頼：20261101_59_店4の一方通行と1本道は1台ずつを切り替えで入れる.md（依頼57）
2026-11-01 21:38 〜 22:45　check-in：cs:54・cs:55　build_id cs55_20261101_2224（保留 0）
====================================================================

結論

  ・始める前の確かめ3つは、すべて合格だった。
  ・3つの切り替えを入れた（どれも既定は params の切）。-ssOneWay on／-ssSingleLane on／-ssStoreVariant noT26。
  ・確かめの4回（④・スタッフ2・ロボット2・36組/時・種0）は、4回とも completed・全員が帰った・行けない卓 0。
  ・案A は 28 卓すべてに道が引けた（受け渡し口→卓・卓→受け渡し口・卓→待機場所2つ）。
  ・案B・案B' では「待ったまま往復が打ち切られた（600 秒）」が残った（案B 2回・案B' 1回）。
    待っている側は決まりどおり待っており、相手の台が1本道の中でスタッフに4〜5分ふさがれて
    「動けなくなった」をくり返していたため。待ち同士でかみ合ったのではない（下の 5）。
  ・決まりの文に書かれていない所が1つあり、仮の作りで入れた（1本道に横から入るとき。下の「決めてほしいこと」1）。
    この仮の作りを決めてもらうまで、依頼58 はそのまま進めない方がよい。
  ・試しの回（smoke）のうち1回が最後まで回り、台帳に1行（1535行目）書いてしまった。消していない。


====================================================================
1. 始める前の確かめ（3つとも合格。21:38 ごろ）
====================================================================

  1. Unity エディタ：起動していない（Unity Hub だけ）。night_runner・rec_runner：動いていない。
     /Volumes/KIOXIA：マウントされている。unity_ledger_v1.lock：なし
  2. ServiceSim の保留：0（cs:53 head）
  3. サイトの params（p2m-site main 3c292e9＝f3a3888 の次。公開サイトとも md5 同じ）に
     unity_rules.traffic_one_way・traffic_single_lane があり、どちらも value false
     ④の公開の店の形（p2_efficient_narrow.json・_map.json）は今の写しと md5 同じ（変わっていない）

  台帳：始めたとき 1534 行・md5 ea67552d80fab29465b4e17b92ade111
    控え：archive/unity_ledger_v1_pre_traffic_20261101/unity_ledger_v1.csv（md5 同じ）


====================================================================
2. 取り込み
====================================================================

  写し元：https://pixelstomotors.com/sim/（21:40 に curl。GitHub main の同じファイルと md5 がすべて同じ）
  控え：unity/site_copy_9b8dae7ebf8c_f3a3888/（README.txt つき）
    serving-robot-params.json … 前の写しとの違いは unity_rules.traffic_one_way・traffic_single_lane が
                                 足されただけ（項目ごとに比べた。ほかの違い 0）
    案B' の店 … Assets/ServiceSim/Store/Layouts/trials/p2_efficient_narrow_noT26.json・_map.json
                （版 trial-p2-noT26-e578507fff7d。27卓。④との違いは T26 が無いことと routes・地図だけ。
                  places は④と同じ）
  案B' の店は別の店として StoreLayout_p2_efficient_narrow_noT26.asset を作り、シーンの HeadlessRun の店の一覧
  （7つめ）と ServiceSim/Setup に足した。条件の値は fam_p2 のまま（来客のペースも④のまま）。
  試しの店は summary.json に載らず、版も trial- で始まるので、版の突き合わせは
  「版が trial- で始まり地図の版と同じ／params とは、もとの④（summary.json の版 9b8dae7ebf8c）で突き合わせる」にした。
  卓の数（試算 28・建てた店 27）は、試しの店のときだけ「外した卓のぶん少ない」として回す（ログに1行出す）。


====================================================================
3. 足した切り替えと使い方
====================================================================

  画面なしの実行ファイルの引数（どれも付けなければ params の既定＝切。公開の店）
    -ssOneWay on|off        案A 一方通行。出力フォルダ名の末尾に _oneway
    -ssSingleLane on|off    案B 1本道は1台ずつ。_lane
    -ssStoreVariant noT26   案B' の店（fam_p2 の条件を p2_efficient_narrow_noT26 で回す）。_noT26
                            -ssSingleLane on と一緒に使うと、その店の lanes（4本）を使う
    名前の形：{夜}_{名札}_cs{N}_{店}_r{台数}_s{種}[_passon][_ruleb][_sbret][_ek][_post][_oneway][_lane][_noT26][_try{n}]
  夜の道具
    make_queue.py   --one-way on|off・--single-lane on|off・--store-variant noT26
    night_runner.py queue の one_way・single_lane・store_variant を渡す。--one-way・--single-lane で突き合わせ
    judge_night.py  一方通行・1本道・店の指定を条件の鍵に足した（入の回は今の基準データに無いので「基準なし」）
  記録
    run_info.json の traffic（入切・区画/1本道の数・行けない卓・店の指定）
    summary.json の traffic（入の回だけ。待った秒数の合計・1本道ごと・待つと決めた回数・横から入る所でその場で待った回数・
      よけて道を空けた回数・待ったまま往復が終わった回数・行けない卓）

  案A の作り：地図で道を引くとき、区画の中のマス（マスの真ん中が矩形の中）では、dir と逆向きに進む1歩
    （dir との内積が負。斜めも同じ）を通れない扱いにした。直角はよい。近道（曲がり角だけにする所）も同じ。
    回り込み・よけの道も同じ道探しを通るので同じ扱い。回の始めに全卓へ道を引いて、行けない卓を記録に残す。

  案B の作り（「やり方は任せる」とされた所を含む）
    ・向き：1本道の長い辺の軸の上で、その1本道の中を進む向き。軸の向きに 0.2 m 未満しか進まないなら向きなし
    ・入ろうとしている：先の道の上で、1本道に入るまでが 2.0 m 以下になったら「入る」か「待つ」かを決める。
      入ると決めた台は、入って出るまで「入ろうとしている」として持つ（あとから来た台は料理を載せていても待つ）。
      入る前に 1本道から 3.0 m（2.0＋1.0）より離れたら手放す
    ・同時：料理を載せている方（今のよけ方の順番 YieldRank）→ 先に待つ点に着いた方 → 号機の小さい方
    ・待つ点：入る点にいちばん近い待つ点。一度着いたら 0.5 m までは着いたままとみる
    ・卓で止まっている台の向き：卓のあとに向かう先までの道（地図で引く）で見る
    ・待つ点が、向かいの台のこれから進む道の上にあるときは、今のよけ方（よける場所へ動く）で道を空け、
      よけ終わったら待つ点へ戻る（cs:55 で足した。下の 5）
    ・【仮】横から入るとき（入る点が端から通路の幅より離れている）は、待つ点へ行かず、その場で止まって待つ

  案B の待った時間の数え方
    stops.csv に、原因「1本道の順番待ち」・終わり方「1本道の待つ点で待った」の行として残す（ほかの止まりと分けて数えられる）。
    summary.json の traffic.lane_wait_total_s にも合計を出す。止まった回数・動けなくなった（60 秒）には数えない
    （細い通路の順番待ちと同じ扱い）。
    注意：待っている台が押されて 0.5 秒ごとに少し動くことがあり、行が細切れになる（案B cs:55 で 229 行のうち 214 行が 1 秒以下）。
    秒数の合計は正しいが、行の数は「待った回数」に使えない。回数は traffic.lane_holds（待つと決めた回数）を使う。
    細切れになる原因は特定しきれていない。


====================================================================
4. 変えたファイル・cs・build_id
====================================================================

  cs:54（23 ファイル）
    新しく  Robot/TrafficRules.cs（.meta）・Store/StoreLayout_p2_efficient_narrow_noT26.asset（.meta）・
            Store/Layouts/trials/（.meta）と p2_efficient_narrow_noT26.json・_map.json（.meta）
    変えた  Robot/GridPlanner.cs（一方通行）・Robot/ServingRobotController.cs（1本道）・
            Experiment/ExperimentRunner.cs・HeadlessRun.cs・SimParams.cs・TripLogger.cs・serving-robot-params.json・
            Store/JsonStoreLayout.cs（試しの店）・Editor/ServiceSimSetup.cs・Scenes/ServiceSim_Family.unity（店の一覧に1つ）・
            tools/make_queue.py・night_runner.py・judge_night.py
  cs:55（2 ファイル）Robot/ServingRobotController.cs・Robot/TrafficRules.cs（1本道の直し2つ。下の 5）
  画面なしの実行ファイル：Builds/headless　build_id cs55_20261101_2224・unity_cs ServiceSim/cs:55・pending_changes 空
  保留：cs:55 のあと 0
  ArticulationBody・ロボットの速さ・よけ方の決まり・試算の数字・公開の6店の形・ほかの切り替えの既定は変えていない。

  3つとも切のとき：新しく足した所はどれも入切を見てから働くので、切の回は今までのコードの道を通る
  （道探しは一方通行の印が無いときの分かれ道を今までどおり通る。1本道の判断は最初の行で抜ける）。
  run_info.json に traffic の項目が1つ増えるだけ。数字の一致では確かめていない
  （同じ種・同じ実行ファイルでも、くり返すと数字が揺れる作りのため。unity_variance_check_20261101）。


====================================================================
5. 確かめの4回（画面なし・1本ずつ）
====================================================================

  ④ fam_p2_s2_r2_a36・種0・すれ違い入・待機場所へ戻る入・入口番入・持ち場入・案B（回し方の決まり）切
  記録：night/20261101_traffic_check/（runner_log.txt・runs.csv・queue.json・queue_lane55.json・logs/・judge.*）

  | 回 | cs | 入店待ち（分） | 料理の待ち（分） | 動けなくなった（回） | 30秒以上の止まり（うち1本道の待ちを除く） | 1本道の待った時間の合計（秒） | 全員が帰った | 行けない卓 |
  |---|---|---|---|---|---|---|---|---|
  | 今のまま | 54 | 26.76 | 3.03 | 56 | 73（73） | − | はい | − |
  | 案A 一方通行 | 54 | 26.30 | 3.48 | 57 | 82（82） | − | はい | 0 |
  | 案B 1本道は1台ずつ | 55 | 24.90 | 3.03 | 50 | 69（63） | 1183.4 | はい | 0 |
  | 案B' T26 を外した店 | 55 | 19.90 | 2.23 | 30 | 58（53） | 869.8 | はい | 0 |

  そのほか（今のまま／案A／案B／案B'）
    往復 100／100／94／105　打ち切り（600 秒）10／11／11／6　入れた組 57／57／57／57
    店の時間（分）232.8／264.4／237.1／220.6
    案B：待つと決めた 18 回（横から入る所でその場 2）・いちばん長い待ち 287.3 秒・よけて道を空けた 0・待ったまま往復が終わった 2
         1本道ごと 下の通路 396.7 秒／左の通路 438.7 秒／パントリー横 348.0 秒
    案B'：待つと決めた 33 回（横 3）・いちばん長い待ち 287.3 秒・よけて道を空けた 2・待ったまま往復が終わった 1
         1本道ごと 下の左半分 0／下の右半分 12.2／左の通路 402.2／パントリー横 455.5 秒
  種1つずつなので、差がどこから来たかは言えない。

  最初の cs:54 で回した案B・案B'（台帳に残っている。直す前）
    案B  入店待ち 24.65・料理 3.54・動けなくなった 50・30秒以上 70（61）・待った合計 3113.1 秒・いちばん長い待ち 571.1 秒
    案B' 入店待ち 23.10・料理 2.07・動けなくなった 46・30秒以上 65（60）・待った合計 834.3 秒・いちばん長い待ち 287.3 秒
  見つけたことと直したこと（cs:55）
    ・相手の台が「入ってよい」を持ったまま、1本道から 6 m 以上離れて（入口の近く）立ち往生し、
      こちらが 520 秒待った → 入る前に離れたら手放すようにした（コードの誤り）
    ・待つ点が1本道の出口の道の上にあり、出てくる台が出られずに止まった
      → 待っている台が相手の進む道の上にいるときだけ、今のよけ方で道を空けるようにした
  直したあとも残った長い待ち（200 秒以上）：案B 4回・案B' 2回。中身を見た2回（店の時刻 1447 秒・2203 秒）は、
    相手の台が1本道の中（左の通路 (1.98, 4.04)・パントリー横 (9.41, 7.88)）でスタッフ Staff02 に4〜5分ふさがれ、
    「動けなくなった」をくり返していた。待っている側は決まりどおり待っていた。
    待ったまま往復が終わった（案B 2・案B' 1）は、この待ちのあいだに自分の往復が 600 秒で打ち切られたもの。

  png（900〜1200 秒の1コマ。確かめの回の robot_track.csv・stops.csv から真上を描いた。動画のコマではない）
    night/20261101_traffic_check/oneway_t1080_records.png　案A・店の時刻 1080 秒
      赤い枠と矢印が一方通行の区画と向き。2号機は下の通路を左へ、1号機は下の通路を左へ進んでから左の通路を上がって T02 に着いた
    night/20261101_traffic_check/laneNoT26_t1100_records.png　案B'・店の時刻 1100 秒
      緑の枠が1本道、緑の丸が待つ点。2号機が左下の待つ点 (0.80, 1.69) で、左の通路の中の T13 に止まっている1号機が出るのを待っている


====================================================================
6. 依頼58（夜の80回）にそのまま進めてよいか
====================================================================

  そのままは進めない方がよい。足りないもの：
    1. 1本道に横から入るときの待ち方を決めてもらうこと（今は仮で「その場で待つ」）。
       受け渡し口から出るどの往復も、パントリー横の1本道に横から入るので、案B・B' の数字に効く
    2. 待った回数の数え方（行が細切れになる。回数は lane_holds を使うか）
  道具としては回せる状態：make_queue.py を4回（今のまま・--one-way on・--single-lane on・--single-lane on --store-variant noT26）
  呼んで queue をまとめれば、night_runner.py でそのまま回る（今回の確かめもこの形で回した）。
  judge_night.py の「30秒以上の止まり」は、1本道の順番待ちの行も数える（今の決まりのまま。変えていない）。


====================================================================
7. 残したもの・触ったもの
====================================================================

  KIOXIA：台帳 1534 → 1541 行（追記だけ。先頭 1534 行の md5 は控えと同じ）
    1535 行め 20261101_smoke59b_cs53_fam_p2_r2_s0_passon_sbret_ek_post_oneway（試しの回。3分で止めるつもりが、216 秒で最後まで回った）
    1536〜1539 行め 確かめの4回（cs:54）、1540〜1541 行め 案B・案B' の回し直し（cs:55）
  出力フォルダ：上の7回ぶん＋途中で止めた試しの回 2 つ
    20261101_smoke59_cs53_fam_p2_r2_s0_passon_sbret_ek_post_stopped（引数の渡し方を誤り、切り替えが効いていなかった回）
    20261101_smoke59b_cs53_fam_p2_r2_s0_passon_sbret_ek_post_lane_noT26_stopped
  プロジェクト（VCS の外）：Builds/traffic_pre・Builds/traffic_pre2（コンパイルの確かめ）・Builds/headless（cs:55 に作り直した）
  iCloud：unity/site_copy_9b8dae7ebf8c_f3a3888/・この報告
  png の動画のコマ版（録画用の実行ファイルで撮る）は作っていない。


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

  1. 1本道に横から入るときの待ち方。決まりは「近い方の待つ点で待つ」だが、受け渡し口の前からパントリー横の通路へ入るとき
     （入る点が y 4.0〜4.4 あたり）は近い方の待つ点 (9.4, 1.1) が1本道の向こう側になり、待つ点へ行くには1本道に入る必要がある。
     案B では真ん中の細い通路から下の通路へ入るときも同じ。今は仮で「入る手前のその場で止まって待つ」にした。
     このままでよいか、別の待ち方（待機場所で待つ・横から入る所用の待つ点をサイトで足す など）にするか。
  2. 待つ点 (9.4, 9.3)・(2.0, 9.3)・(9.4, 1.1) が、1本道から出てくる台の道の上にある。
     そのとき待っている台を今のよけ方で動かす作りを足した（cs:55）。この足し方でよいか、待つ点の場所をサイトで見直すか。
  3. 試しの回が台帳に書いた1行（1535 行め・20261101_smoke59b_…_oneway）を残すか消すか。
  4. 待った回数は、行の数ではなく lane_holds（待つと決めた回数）で数えてよいか。
  5. 残ったもの（Builds/traffic_pre・traffic_pre2・途中で止めた試しの回のフォルダ2つ）を消すか残すか。
```

確かめの1回ずつ（④・混んでいる・ロボット2台・種0）では、案B'がよさそうに見えました。最初の版では、相手が1本道を離れても「入ってよい」を手放さないコードの誤りと、待つ点が出てくる台の道の上にある問題で、571秒の待ちっぱなしが出ていて、Claude Code が直して回し直しています。

【画像】LOG423_05_oneway_check.png 案Aの確かめの回の店の1080秒。赤い枠と矢印が向きを決めた通路で、1号機は左の通路を上へ、2号機は下の通路を左へ進んでいる

【画像】LOG423_06_lane_noT26_check.png 案B'の確かめの回の店の1100秒。1本道の区切りと待つ点、T26 を外した跡のよけ場所

### 決めた5つ（依頼59の「決めてほしいこと」）

| 決めてほしいこと | 決めたこと |
|---|---|
| 1本道に横から入るとき、決めた待つ点が通路の向こう側になる | 今の仮の作り「入る手前のその場で待つ」のまま。実際のロボットも入口の手前で止まる |
| 待つ点が、1本道から出てくる台の道の上にある | 今夜は、待っている台が今のよけ方で道を空ける作りのまま。待つ点の場所は結果を見てから |
| 試しの回が台帳に書いた1行 | 残す（測定データは消さない） |
| 待った回数の数え方 | 待つと決めた回数（lane_holds）で数える |
| 試しのビルドと途中で止めた回のフォルダ | 今は残す |


## 作業ログ ― 3つの案を夜に80回回し、1台とも比べた

### 依頼60は、決めごとが残っていたので回さずに止まった

依頼60は、始める前の確かめに「依頼59の報告に、進めてよいと書いてある」を入れていました。依頼59の報告は「決めてほしいことが決まるまで、そのままは進めない方がよい」だったので、依頼60は80回を回さず、台帳も変えずに止まりました。決めた5つを書き足して、依頼60bとして出し直しています。

```text
---
作業フォルダ: /Users/shimazueiichi/Robotics_UX/ServiceSim
上限時間: 330
---
# 依頼60：④の「向かい合わない3案」を今のままと並べて80回回す（夜の作業・2026-11-01）

エイイチの決定（2026-11-01 20:07）：④で、一方通行（案A）・1本道は1台ずつ（案B）・T26 を外したよけ場所（案B'）を、今のままと同じ種で比べる。比べ方は、同じ種どうしの差で見る。
**エイイチは寝ている。人に聞かないと進めない所に当たったら、回さずに報告に書いて終わる。**

## 始める前に確かめること（1つでも違えば回さずに報告して終わる）

1. 依頼59（20261101_59_店4の一方通行と1本道は1台ずつを切り替えで入れる）が「完了」で終わっていて、報告 claude_code_reports/20261101_traffic_p2_impl.txt に「依頼60に進めてよい」と書いてある
2. 画面なしの実行ファイルが依頼59で作り直した版（build_id が報告と同じ）で、保留なし
3. Unity エディタが起動していない。/Volumes/KIOXIA がマウントされている。unity_ledger_v1.lock がない

## 台帳の退避（回す前に自分で行う）

`/Volumes/KIOXIA/r2r2r-data/service_sim/unity/unity_ledger_v1.csv` を
`/Volumes/KIOXIA/r2r2r-data/service_sim/unity/archive/unity_ledger_v1_pretraffic_20261101/unity_ledger_v1.csv` にコピーする（コピーだけ。移動・上書きしない。フォルダが既にあれば止めて報告）。行数と md5 を控え、元と同じかを確かめる。

## 回す

夜の名前 20261108_traffic。④（fam_p2）・スタッフ2人・ロボット2台 × 来客2つ（普通 28組/時・混んでいる 36組/時）× 4通り × 種0〜9 ＝ 80回。同時に回す本数は 4。記録は night/20261108_traffic/。

| 4通り | 店 | 切り替え |
|---|---|---|
| 今のまま | ④ | どちらも切 |
| 案A 一方通行 | ④ | `-ssOneWay` |
| 案B 1本道は1台ずつ | ④ | `-ssSingleLane` |
| 案B' よけ場所 | ④から T26 を外した店 | `-ssSingleLane` |

（切り替え・店の指定の名前は、依頼59で決めたものを使う）

- ほかの切り替えは今の標準のまま（すれ違い入・待機場所へ戻る入・案B切・入口番入・持ち場入）
- 「今のまま」も、比べを同じ作りにそろえるためにこの版で回し直す
- 判定（judge_night.py）は回さなくてよい（基準データに同じ条件がないため）
- 回している間はほかの作業をしない。Ctrl+C で止めない。途中で止まっても回し直さない
- 作業フォルダに queue の一時ファイルを残さない

## 報告

依頼箱の結果に加えて、claude_code_reports に 20261108_night_traffic_p2.txt として書く（場所は依頼59と同じ）。

- 始める前の確かめ3つと、台帳の退避（行数・md5）
- runner_log のまとめ（completed・失敗・止まった回・最長の実時間）
- 台帳の行数（退避＋80 になるはず）と、先頭の行の md5 が退避と同じか
- 来客 × 4通りごとの種10個の中央値（最小〜最大）：入店待ち・料理の待ち・動けなくなった回数（スタッフが動かしに行った回数）・30秒以上の止まり（1往復あたり）・案B/B'で待った時間の合計・往復の数・スタッフ1人の歩く距離・スタッフの手がふさがっている割合・ロボットが運んだ皿の割合
- 全員が帰らなかった回・失敗した回があればその名前（原因の調べはしない）
- 決めてほしいこと（なければ「なし」）
```

```text
# 結果：20261101_60_店4の向かい合わない3案を夜に80回回す

- 状態：完了
- 始めた時刻：2026-11-01 22:33
- 終わった時刻：2026-11-01 22:35（3分）
- 作業フォルダ：/Users/shimazueiichi/Robotics_UX/ServiceSim
- この続きを Claude Code で話すとき：作業フォルダで `claude --resume 2b3f0b6e-21e6-4563-8575-e15e09c33100`

## Claude Code の報告

**やったこと**
回す前に、始める前の確かめ3つを順に見ました。その結果、確かめ1が合わなかったので、80回は回していません。台帳の退避もしていません。回す前の手順なので、止めた時点で行わないことにしました。台帳・出力フォルダ・プロジェクトは何も変えていません。止めた理由を報告ファイルに書きました。

**結果**
- 確かめ1（合わない）：依頼57の報告 `20261101_traffic_p2_impl.txt` には「依頼58に進めてよい」とは書かれていません。6章には逆に「そのままは進めない方がよい」とあり、決めてほしいことが5つ残っています。依頼箱で依頼57が「完了」になっているかは、こちらからは見えませんでした。
- 確かめ2（合う）：画面なしの実行ファイルは build_id cs55_20261101_2224（ServiceSim/cs:55）で、依頼57の報告と同じです。保留はありません。
- 確かめ3（合う）：Unity エディタと night_runner は動いていません。/Volumes/KIOXIA はマウントされていて、unity_ledger_v1.lock はありません。
- 台帳は今1541行で、依頼57の終わりと同じです。依頼58の退避先 `archive/unity_ledger_v1_pretraffic_20261101` はまだありません。依頼57の控え（pre_traffic）とは名前が別なので、ぶつかりません。
- runner_log・中央値などの数字は、回していないのでありません。

**変えたファイル**
- 新しく作ったもの：`…/サービスロボット導入シミュレーション/unity/claude_code_reports/20261108_night_traffic_p2.txt`（止めた理由の報告）
- そのほかは変えていません。

**決めてほしいこと**
1. 依頼57の報告8章に残っている5つを決めてほしいです。とくに次の2つです。
   - 1本道に横から入るときの待ち方。今は仮で「入る手前のその場で待つ」にしています。
   - 待った回数を lane_holds（待つと決めた回数）で数えてよいか。
2. 仮の作りのまま80回回してよいなら、その旨を書いて依頼58を出し直してほしいです。確かめ1の条件を外すか、依頼57の報告に「依頼58に進めてよい」を足すかのどちらかが要ります。
```

```text
====================================================================
依頼58：④の向かい合わない3案を夜に80回回す ― 回さずに終えた
2026-11-01　夜の名前 20261108_traffic（回していない）
====================================================================

結論
  始める前の確かめ 1 が合わなかったので、回さずに終えた。
  台帳の退避もしていない（回す前の手順のため）。台帳・出力フォルダ・プロジェクトは何も変えていない。

1. 始める前の確かめ
  1. 依頼57 の報告 20261101_traffic_p2_impl.txt … 合わない
     「依頼58に進めてよい」とは書かれていない。6 章に
     「そのままは進めない方がよい」と書かれ、決めてほしいことが5つ残っている
     （横から1本道に入るときの待ち方〈今は仮〉・待つ点が出口の道の上にある件・
       試しの回の台帳1535行めの扱い・待った回数を lane_holds で数えるか・残った Builds と試しのフォルダ）。
     依頼箱で依頼57 が「完了」かどうかは、こちらからは見えなかった。
  2. 画面なしの実行ファイル … 合う（参考）
     Builds/headless/build_info.json：build_id cs55_20261101_2224・ServiceSim/cs:55・pending_changes 空
     （依頼57 の報告と同じ）
  3. Unity エディタ・night_runner：動いていない。/Volumes/KIOXIA：マウントされている。
     unity_ledger_v1.lock：なし … 合う（参考）

  台帳：今 1541 行（依頼57 の報告の終わりと同じ）。退避はしていない。
  参考：archive/ には依頼57 の控え unity_ledger_v1_pre_traffic_20261101 があり、
        依頼58 の退避先 unity_ledger_v1_pretraffic_20261101 はまだ無い（名前が別なので、ぶつからない）。

2. runner_log・台帳の行数・種ごとの中央値
  回していないので、なし。

3. 決めてほしいこと
  1. 依頼57 の報告 8 章の5つ（とくに 1 の横から入るときの待ち方と 4 の待った回数の数え方）を決めること。
     決まれば、依頼57 の続きで作りを直すか、仮のまま依頼58 を回すかを依頼してほしい。
  2. 仮の作りのまま 80 回回してよいなら、そう書いた依頼を出し直してほしい
     （確かめの条件「依頼58に進めてよい」を外す、またはその一文を報告に足す）。
```

### 依頼60b「店4の向かい合わない3案を夜に80回回す」の全文

```text
---
作業フォルダ: /Users/shimazueiichi/Robotics_UX/ServiceSim
上限時間: 330
---
# 依頼60b：④の「向かい合わない3案」を今のままと並べて80回回す（夜の作業・2026-11-01）

エイイチの決定（2026-11-01 20:07）：④で、一方通行（案A）・1本道は1台ずつ（案B）・T26 を外したよけ場所（案B'）を、今のままと同じ種で比べる。比べ方は、同じ種どうしの差で見る。
**エイイチは寝ている。人に聞かないと進めない所に当たったら、回さずに報告に書いて終わる。**

## エイイチが決めたこと（2026-11-01 22:39。依頼59の報告の「決めてほしいこと」5つ）

1. 1本道に横から入るときは、今の仮の作り「入る手前のその場で待つ」のままでよい
2. 待つ点が1本道から出てくる台の道の上にある件は、今夜は依頼59で足した作り（待っている台が今のよけ方で道を空ける）のままでよい。待つ点の場所の見直しは結果を見てから
3. 試しの回が台帳に書いた1行（1535行目）は残す
4. 待った回数は lane_holds（待つと決めた回数）で数えてよい
5. 試しのビルド（Builds/traffic_pre・traffic_pre2）と途中で止めた試しの回のフォルダは、今は残す（消さない）

## 始める前に確かめること（1つでも違えば回さずに報告して終わる）

1. 依頼59（20261101_59_店4の一方通行と1本道は1台ずつを切り替えで入れる）が「完了」で終わっている（報告 claude_code_reports/20261101_traffic_p2_impl.txt がある）。「依頼60に進めてよい」の一文はなくてよい（上の5つで決めたため）
2. 画面なしの実行ファイルが依頼59で作り直した版（build_id cs55_20261101_2224）で、保留なし
3. Unity エディタが起動していない。/Volumes/KIOXIA がマウントされている。unity_ledger_v1.lock がない

## 台帳の退避（回す前に自分で行う）

`/Volumes/KIOXIA/r2r2r-data/service_sim/unity/unity_ledger_v1.csv` を
`/Volumes/KIOXIA/r2r2r-data/service_sim/unity/archive/unity_ledger_v1_pretraffic_20261101/unity_ledger_v1.csv` にコピーする（コピーだけ。移動・上書きしない。フォルダが既にあれば止めて報告）。行数と md5 を控え、元と同じかを確かめる。

## 回す

夜の名前 20261108_traffic。④（fam_p2）・スタッフ2人・ロボット2台 × 来客2つ（普通 28組/時・混んでいる 36組/時）× 4通り × 種0〜9 ＝ 80回。同時に回す本数は 4。記録は night/20261108_traffic/。

| 4通り | 店 | 切り替え |
|---|---|---|
| 今のまま | ④ | どちらも切 |
| 案A 一方通行 | ④ | `-ssOneWay` |
| 案B 1本道は1台ずつ | ④ | `-ssSingleLane` |
| 案B' よけ場所 | ④から T26 を外した店 | `-ssSingleLane` |

（切り替え・店の指定の名前は、依頼59で決めたものを使う）

- ほかの切り替えは今の標準のまま（すれ違い入・待機場所へ戻る入・案B切・入口番入・持ち場入）
- 「今のまま」も、比べを同じ作りにそろえるためにこの版で回し直す
- 判定（judge_night.py）は回さなくてよい（基準データに同じ条件がないため）
- 回している間はほかの作業をしない。Ctrl+C で止めない。途中で止まっても回し直さない
- 作業フォルダに queue の一時ファイルを残さない

## 報告

依頼箱の結果に加えて、claude_code_reports に 20261108_night_traffic_p2_b.txt として書く（場所は依頼59と同じ）。

- 始める前の確かめ3つと、台帳の退避（行数・md5）
- runner_log のまとめ（completed・失敗・止まった回・最長の実時間）
- 台帳の行数（退避＋80 になるはず）と、先頭の行の md5 が退避と同じか
- 案B・B'の待った回数（lane_holds）と、200秒を超えた待ち・待ったまま往復が打ち切られた回の数
- 来客 × 4通りごとの種10個の中央値（最小〜最大）：入店待ち・料理の待ち・動けなくなった回数（スタッフが動かしに行った回数）・30秒以上の止まり（1往復あたり）・案B/B'で待った時間の合計・往復の数・スタッフ1人の歩く距離・スタッフの手がふさがっている割合・ロボットが運んだ皿の割合
- 全員が帰らなかった回・失敗した回があればその名前（原因の調べはしない）
- 決めてほしいこと（なければ「なし」）
```

### 依頼60bの結果（依頼箱の報告）と、Claude Code の報告 20261108_night_traffic_p2_b.txt（全文）

80回すべて最後まで回り、全員が帰りました。実時間は1回平均5.7分・最長8.6分で、全体は23:15〜01:10でした。

```text
# 結果：20261101_60b_店4の向かい合わない3案を夜に80回回す

- 状態：完了
- 始めた時刻：2026-11-01 23:11
- 終わった時刻：2026-11-02 01:13（122分）
- 作業フォルダ：/Users/shimazueiichi/Robotics_UX/ServiceSim
- この続きを Claude Code で話すとき：作業フォルダで `claude --resume c4c0baba-91e9-4bec-b5b8-07db8101b8c9`
- 止められた操作：1件（いちばん下に一覧）

## Claude Code の報告

**やったこと**
- 始める前の確かめ3つは、すべて合格でした。
  - 依頼59は「済み」で終わっていて、報告もあります。
  - 画面なしの実行ファイルは cs55_20261101_2224 で、保留はありません。
  - Unity エディタは起動しておらず、KIOXIA はマウントされていて、lock はありませんでした。
- 台帳を archive/unity_ledger_v1_pretraffic_20261101/ にコピーしました。元も控えも 1541行、md5 995542102319d77d1416b7c6abfe0619 で同じです。
- 4通りの並びを作って1つにまとめ、80回を同時4本で回しました。止まっても回し直さない設定にしています。
  - 夜の名前に「20261108_traffic」は使えなかったため（8桁の日付しか受け付けない）、夜は 20261108、記録のフォルダ名を night/20261108_traffic/ にしました。
- 判定は回さなくてよいとされていましたが、実行係が終わりに自動で呼ぶ作りのため judge.md が出ています（80回とも「基準なし」）。
- 回す前に、実際には回さず打つコマンドだけを見る確かめをしました。そのとき出た記録は runner_log_dryrun.txt という名前で残しています。

**結果**
- 80回すべて最後まで回りました。失敗・止まった回・回し直しは 0、全員が帰らなかった回も 0 です。
  - 1回の実時間は平均 5.7分、最長 8.6分でした。
  - 案Aで行けない卓は 0 でした。
- 台帳は 1621行（退避の 1541＋80）で、先頭 1541行の md5 は退避と同じです。
- 種10個の中央値、カッコ内は同じ種どうしの差（案−今のまま）の中央値です。

| 来客 | 通り | 入店待ち（分） | 料理の待ち（分） | 動けなくなった（回） | 30秒以上の止まり（1往復あたり） |
|---|---|---|---|---|---|
| 28 | 今のまま | 25.06 | 3.47 | 53.5 | 0.727 |
| 28 | 案A | 24.16（+0.43） | 3.38（+0.10） | 57.5（−0.5） | 0.876 |
| 28 | 案B | 17.58（−7.31） | 1.96（−1.81） | 29.0（−20.5） | 0.472 |
| 28 | 案B' | 20.00（−5.27） | 2.58（−1.06） | 40.0（−13.5） | 0.624 |
| 36 | 今のまま | 43.65 | 3.19 | 74.5 | 0.821 |
| 36 | 案A | 56.49（+11.40） | 4.17（+1.09） | 92.0（+16.0） | 1.152 |
| 36 | 案B | 42.12（−4.45） | 2.48（−0.86） | 55.0（−18.0） | 0.612 |
| 36 | 案B' | 39.88（−6.99） | 2.45（−1.06） | 44.0（−29.5） | 0.493 |

- 案B・B'は、どちらの来客でも、入店待ち・料理の待ち・動けなくなった回数が、10種のうち7〜10種で今のままより減りました。
- 案Aは、普通ではほぼ同じでした。混んでいるときは悪くなり、入店待ちが減った種は 4/10、動けなくなった回数が減った種は 2/10 でした。
- 案B・B'の待ちです。

| 来客 | 通り | 待った回数（lane_holds）の合計 | 200秒を超えた待ち | 待ったまま往復が打ち切られた |
|---|---|---|---|---|
| 28 | 案B | 213 | 8 | 2 |
| 28 | 案B' | 256 | 11 | 2 |
| 36 | 案B | 324 | 17 | 2 |
| 36 | 案B' | 252 | 12 | 3 |

  - いちばん長い待ちは 550.9秒でした。
  - 200秒を超えた待ちの数は、記録の行が細切れになることがあるため、少なめに出ている見込みです。
- 往復の数・歩く距離・手がふさがっている割合・ロボットが運んだ皿の割合・最小〜最大は、報告ファイルの5章と6章に書きました。
- 差がどこから来たかの調べはしていません。

**変えたファイル**
- プロジェクトのコード・params・ビルドは変えていません（保留 0 のまま）。
- 新しく：claude_code_reports/20261108_night_traffic_p2_b.txt
- KIOXIA：台帳に 80行追記、archive/unity_ledger_v1_pretraffic_20261101/、出力フォルダ 80個、night/20261108_traffic/

**決めてほしいこと**
1. 並びを作るための一時ファイルを /tmp/p2m_work60b.m4vn/ に置きました（作業フォルダの外です）。終わってから消そうとしましたが、消すコマンドが許可されず残っています。消してよいか、決めてください。中の queue.json と同じものは night/20261108_traffic/ に写されています。

## 止められた操作

- {"tool_name": "Bash", "tool_use_id": "toolu_01Vp3MH4Y24ZyAKQtMPgCTgo", "tool_input": {"command": "rm -rf /tmp/p2m_work60b.m4vn; ls /tmp | grep p2m_work60b; ls /Volumes/KIOXIA/r2r2r-data/service_sim/unity/*.lock 2>&1 | head -1", "description": "Remove temp queue work folder"}}
```

```text
====================================================================
④の「向かい合わない3案」を今のままと並べて80回回した（夜 20261108_traffic）
依頼：20261101_60b_店4の向かい合わない3案を夜に80回回す.md
2026-11-01 23:15 〜 2026-11-02 01:10　build_id cs55_20261101_2224（ServiceSim/cs:55・保留 0）
====================================================================

結論

  ・80回すべて completed。失敗 0・止まった回 0。全員が帰らなかった回 0。案A で行けない卓 0（20回とも）。
  ・同じ種どうしの差で見ると、案B（1本道は1台ずつ）と案B'（T26 を外した店）は、普通・混んでいるのどちらでも
    入店待ち・料理の待ち・動けなくなった回数が、10種のうち8〜10種で今のままより減った。
  ・案A（一方通行）は、普通では今のままとほぼ同じ、混んでいるでは悪くなった（入店待ち +11.4 分、動けなくなった +16 回が差の中央値。
    減った種は2〜4/10）。
  ・案B・B' では 200 秒を超える待ちがまだ出ている（いちばん長い待ち 550.9 秒）。待ったまま往復が打ち切られた回も 9 回ある。
  ・種10個の結果で、差がどこから来たかの調べはしていない。


====================================================================
1. 始める前の確かめ（3つとも合格。23:00 ごろ）
====================================================================

  1. 依頼59：依頼箱の「済み」にあり、結果の json は subtype success。報告 claude_code_reports/20261101_traffic_p2_impl.txt がある
  2. Builds/headless/build_info.json：build_id cs55_20261101_2224・unity_cs ServiceSim/cs:55・pending_changes 空。
     cm status も cs:55 head で保留なし
  3. Unity エディタ：起動していない（Unity Hub と Unity.Licensing だけ）。night_runner・rec_runner も動いていない。
     /Volumes/KIOXIA：マウントされている。unity_ledger_v1.lock：なし

  台帳の退避
    archive/unity_ledger_v1_pretraffic_20261101/ は無かったので作り、コピーした（cp -p。移動・上書きなし）
    元・控えとも 1541 行・md5 995542102319d77d1416b7c6abfe0619（同じ）
    ※依頼59 の控え archive/unity_ledger_v1_pre_traffic_20261101/（「pre_」つき）は別のフォルダで、触っていない


====================================================================
2. 回し方
====================================================================

  ④ fam_p2・スタッフ2・ロボット2：fam_p2_s2_r2_a28（普通 28組/時）・fam_p2_s2_r2_a36（混んでいる 36組/時）× 4通り × 種0〜9
  make_queue.py を4回（--one-way off --single-lane off／--one-way on／--single-lane on／--single-lane on --store-variant noT26）
  呼んで、種 → 来客 → 4通り の順にまとめた queue（80回）を作った。ほかの切り替えは標準（すれ違い入・案B切・待機場所へ戻る入・
  入口番入・持ち場入。4つの queue で同じことを確かめた）。
  night_runner.py --parallel 4 --night 20261108 --record-name 20261108_traffic --label tr --no-retry
    （--night は8桁の形しか受け付けないので、夜の名前 20261108_traffic は記録のフォルダ名として渡した。
      出力フォルダ名は 20261108_tr_cs55_…・20261108_tr_a36_cs55_…）
  記録：night/20261108_traffic/（runner_log.txt・runs.csv・queue.json・logs/・judge.md・judge.csv）
  判定は求められていないが、night_runner が終わりに自動で呼ぶ作りのため judge.md が出ている（80回とも「基準なし」）。
  回す前に --dry-run で打つコマンドを確かめた。そのとき書かれた runner_log.txt は runner_log_dryrun.txt に名前を変えて残した。
  queue の一時ファイルは /tmp/p2m_work60b.m4vn/ に置いた（作業フォルダには置いていない）。
  終わってから消そうとしたが、消すコマンドが許可されず残っている（queue 5つ・runner_stdout.txt・まとめ用の py 2つ。
  queue.json と同じものは night/20261108_traffic/queue.json に写されている）。


====================================================================
3. runner_log のまとめ・台帳
====================================================================

  completed 80・失敗 0・止まった回 0・やり直し 0
  実時間 平均 5.7 分・最長 8.6 分（全体 23:15:26 〜 01:10:33）
  台帳：1621 行（退避 1541 ＋ 80）。先頭 1541 行の md5 995542102319d77d1416b7c6abfe0619（退避と同じ）
  全員が帰らなかった回：なし　失敗した回：なし


====================================================================
4. 案B・B' の待ち
====================================================================

  （回ごとの値の中央値（最小〜最大）・合計。200秒超えは stops.csv の「1本道の順番待ち」の行で 200 秒を超えた数。
    行が細切れになることがあるので（依頼59 の報告）、この数は少なめに出ている見込み）

  | 来客 | 案 | 待つと決めた回数 lane_holds | うち横から入る所でその場 | 200秒を超えた待ち | 待ったまま往復が終わった | いちばん長い待ち（秒） |
  |---|---|---|---|---|---|---|
  | 普通 28 | B  | 21.5（12〜31）計213 | 計43 | 計8（7回で出た） | 計2（2回） | 中央 282.4・最大 508.1 |
  | 普通 28 | B' | 21.0（12〜55）計256 | 計28 | 計11（5回） | 計2（1回） | 中央 198.8・最大 439.8 |
  | 混む 36 | B  | 28.5（22〜66）計324 | 計50 | 計17（6回） | 計2（2回） | 中央 214.2・最大 470.9 |
  | 混む 36 | B' | 26.0（16〜34）計252 | 計45 | 計12（8回） | 計3（3回） | 中央 342.3・最大 550.9 |

  待っている台がよけて道を空けた回数：B 普通 11・B' 普通 11・B 混む 8・B' 混む 9（合計）


====================================================================
5. 来客 × 4通りの、種10個の中央値（最小〜最大）
====================================================================

  30秒以上の止まりは stops.csv の 30 秒以上の行 ÷ 往復（judge_night.py と同じ数え方）。（ ）の中の「除く」は1本道の順番待ちの行を除いた値。
  動けなくなった回数＝台帳の stuck_count（80回とも rescues＝スタッフが動かしに行った回数と同じ数）。

  普通 28組/時
  | | 今のまま | 案A 一方通行 | 案B 1本道は1台ずつ | 案B' T26 を外した店 |
  |---|---|---|---|---|
  | 入店待ち（分） | 25.06（11.91〜41.16） | 24.16（11.90〜42.09） | 17.58（7.04〜37.30） | 20.00（8.60〜38.59） |
  | 料理の待ち（分） | 3.47（2.71〜5.63） | 3.38（1.66〜5.00） | 1.96（1.38〜3.00） | 2.58（1.91〜3.61） |
  | 動けなくなった（回） | 53.5（43〜81） | 57.5（44〜100） | 29.0（20〜81） | 40.0（24〜60） |
  | 30秒以上の止まり／往復 | 0.727（0.555〜1.358） | 0.876（0.490〜1.385） | 0.472（0.327〜0.908） 除く 0.430 | 0.624（0.462〜0.736） 除く 0.601 |
  | 1本道で待った合計（秒） | − | − | 684.0（219.6〜918.7） | 548.2（145.7〜1884.7） |
  | 往復 | 93.0（71〜119） | 88.5（82〜145） | 102.5（91〜132） | 94.0（79〜127） |
  | スタッフ1人の歩く距離（km） | 2.632（2.367〜3.216） | 2.603（2.303〜3.208） | 2.420（2.047〜3.412） | 2.530（2.296〜3.212） |
  | スタッフの手がふさがっている割合 | 0.847（0.822〜0.936） | 0.836（0.776〜0.881） | 0.817（0.735〜0.867） | 0.840（0.808〜0.893） |
  | ロボットが運んだ皿の割合 | 0.886（0.771〜0.943） | 0.888（0.831〜0.966） | 0.915（0.849〜0.975） | 0.899（0.838〜0.917） |
  | 往復の打ち切り（600秒） | 9.5（6〜17） | 8.0（3〜16） | 4.5（1〜11） | 7.5（2〜12） |

  混んでいる 36組/時
  | | 今のまま | 案A 一方通行 | 案B 1本道は1台ずつ | 案B' T26 を外した店 |
  |---|---|---|---|---|
  | 入店待ち（分） | 43.65（21.78〜85.51） | 56.49（33.72〜76.46） | 42.12（17.90〜64.69） | 39.88（20.81〜66.00） |
  | 料理の待ち（分） | 3.19（2.37〜4.46） | 4.17（2.67〜6.42） | 2.48（1.77〜3.27） | 2.45（1.41〜3.83） |
  | 動けなくなった（回） | 74.5（56〜102） | 92.0（47〜137） | 55.0（42〜75） | 44.0（20〜72） |
  | 30秒以上の止まり／往復 | 0.821（0.560〜0.935） | 1.152（0.632〜1.371） | 0.612（0.468〜0.903） 除く 0.573 | 0.493（0.315〜0.935） 除く 0.468 |
  | 1本道で待った合計（秒） | − | − | 802.2（210.6〜2088.3） | 819.3（158.2〜1475.1） |
  | 往復 | 128.0（99〜141） | 113.5（90〜147） | 129.0（89〜158） | 133.5（93〜176） |
  | スタッフ1人の歩く距離（km） | 3.455（2.763〜4.275） | 3.765（2.878〜4.306） | 3.361（2.672〜3.894） | 3.233（2.693〜3.910） |
  | スタッフの手がふさがっている割合 | 0.888（0.847〜0.916） | 0.876（0.841〜0.930） | 0.880（0.821〜0.903） | 0.881（0.816〜0.906） |
  | ロボットが運んだ皿の割合 | 0.871（0.820〜0.949） | 0.839（0.746〜0.909） | 0.906（0.799〜0.943） | 0.920（0.836〜0.983） |
  | 往復の打ち切り（600秒） | 12.5（9〜19） | 15.0（4〜27） | 7.0（3〜13） | 8.0（2〜15） |


====================================================================
6. 同じ種どうしの差（案 − 今のまま。10種の差の中央値／今のままより減った種の数）
====================================================================

  | | 来客 | 案A | 案B | 案B' |
  |---|---|---|---|---|
  | 入店待ち（分） | 28 | +0.43（4/10） | −7.31（10/10） | −5.27（9/10） |
  | | 36 | +11.40（4/10） | −4.45（8/10） | −6.99（8/10） |
  | 料理の待ち（分） | 28 | +0.10（4/10） | −1.81（9/10） | −1.06（9/10） |
  | | 36 | +1.09（3/10） | −0.86（8/10） | −1.06（7/10） |
  | 動けなくなった（回） | 28 | −0.5（5/10） | −20.5（8/10） | −13.5（8/10） |
  | | 36 | +16.0（2/10） | −18.0（10/10） | −29.5（8/10） |
  | 歩く距離（km） | 28 | +0.021（4/10） | −0.194（8/10） | −0.080（9/10） |
  | | 36 | +0.211（2/10） | −0.103（9/10） | −0.199（9/10） |
  | 手がふさがっている割合 | 28 | −0.019（8/10） | −0.047（10/10） | −0.015（8/10） |
  | | 36 | −0.009（6/10） | −0.014（6/10） | −0.007（7/10） |
  | ロボットが運んだ皿の割合 | 28 | +0.002（5/10） | +0.029（2/10） | +0.017（3/10） |
  | | 36 | −0.020（7/10） | +0.023（4/10） | +0.037（4/10） |
  | 往復 | 28 | +0.5（4/10） | +17.0（1/10） | +8.0（3/10） |
  | | 36 | −9.5（8/10） | +6.0（2/10） | +12.5（3/10） |

  同じ種・同じ実行ファイルでもくり返すと数字が揺れる作り（unity_variance_check_20261101）なので、
  1種ごとの差には揺れのぶんも入っている。


====================================================================
7. 残したもの・触ったもの
====================================================================

  KIOXIA：台帳 1541 → 1621 行（追記だけ）・archive/unity_ledger_v1_pretraffic_20261101/・出力フォルダ 80・night/20261108_traffic/
  プロジェクト：変えていない（コード・params・ビルドとも。保留 0 のまま）
  iCloud：この報告


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

  1. /tmp/p2m_work60b.m4vn/（一時ファイル。作業フォルダの外）を消してよいか。消すコマンドが許可されなかったので残している。
```

### 1本道は1台ずつは効き、一方通行は混むと悪くなった

同じ種どうしの差で比べました（④・スタッフ2人・ロボット2台・10回の真ん中の値。かっこは今のままとの差）。

| | 入店待ち | ロボットを動かしに行った回数 | 料理が届くまで | 歩く距離 |
|---|---|---|---|---|
| 普通（28組/時） | | | | |
| 今のまま | 25.1分 | 53.5回 | 3.5分 | 2.63km |
| 案A 一方通行 | 24.2分（+0.4・減った種4/10） | 57.5回（−0.5） | 3.4分（+0.1） | 2.60km |
| 案B 1本道は1台ずつ | 17.6分（−7.3・10/10） | 29.0回（−20.5・8/10） | 2.0分（−1.8・9/10） | 2.42km |
| 案B' よけ場所 | 20.0分（−5.3・9/10） | 40.0回（−13.5・8/10） | 2.6分（−1.1・9/10） | 2.53km |
| 混んでいる（36組/時） | | | | |
| 今のまま | 43.7分 | 74.5回 | 3.2分 | 3.46km |
| 案A 一方通行 | 56.5分（+11.4・4/10） | 92.0回（+16.0・2/10） | 4.2分（+1.1） | 3.77km |
| 案B 1本道は1台ずつ | 42.1分（−4.5・8/10） | 55.0回（−18.0・10/10） | 2.5分（−0.9・8/10） | 3.36km |
| 案B' よけ場所 | 39.9分（−7.0・8/10） | 44.0回（−29.5・8/10） | 2.5分（−1.1・7/10） | 3.23km |

- 一方通行は、向かい合いは減りますが、遠回りで通路にいる時間が延び、人とぶつかる回数が増えます。混んでいるときは入店待ちも世話も悪くなりました
- 1本道は1台ずつは、どちらの混み具合でもはっきり効きます。世話が3〜4割減り、料理も早く届きます。席は減らしません。代わりに、ロボットが1本道の手前で待つ時間が、2時間で合計11〜13分（中央値）あります
- よけ場所（席を2つ外す）は、混んでいるときに最もよく効き、動かしに行く回数がほぼ半分になります。普通の混み具合では、案Bの方が上でした

- ねらい：2台入れてしまった狭い店が、すぐとれる直し方を見つける
- 変わったこと：1本道は1台ずつで、普通の混み具合の入店待ちが25.1分→17.6分、動かしに行く回数が53.5回→29.0回（同じ種どうしの差の中央値は −7.3分・−20.5回）
- 見込める効果：席を減らさずに、ロボットの世話を3〜4割減らせる見込み。実際の店で同じになるかは、店で動かしに行った回数を測って確かめる

### 1台と比べると、どの案でも2台は1台に届かなかった traffic_compare.py

0台・1台は、前の夜（普通は cs:29、混んでいるは cs:44）の結果です。2台の今のままは、前の版と今の版（cs:55）で入店待ち26.9分と25.1分・45.9分と43.7分と近いので、版の違いは小さいと見ていますが、きっちりそろえてはいません。

```python
# ④ 効率重視・小さめの店：向かい合わない3案（夜 20261108_traffic・80回）を、今のまま・1台・0台と同じ種どうしで比べる
# 使い方: python3 traffic_compare.py   （台帳は KIOXIA の unity_ledger_v1.csv を読むだけ）
import csv, json, os, glob, random, statistics as st, collections
U = os.path.expanduser('~/mnt/KIOXIA/r2r2r-data/service_sim/unity')
R = list(csv.DictReader(open(f'{U}/unity_ledger_v1.csv')))
random.seed(0)

def var(f):
    return "案B'" if '_noT26' in f else '案B' if '_lane' in f else '案A' if '_oneway' in f else '今のまま'

def pick(prefix, rb, a, want=None):
    out = {}
    for r in R:
        f = r['run_folder']
        if not f.startswith(prefix) or r['store'] != 'fam_p2' or r['robots'] != rb or r['arrivals_per_h'] != a or '_ruleb' in f:
            continue
        if want and var(f) != want:
            continue
        out[int(r['seed'])] = r
    return out

M = [('entry_wait_mean', '入店待ち（分）'), ('rescues', '動かしに行った回数'), ('dish_wait_mean', '料理が届くまで（分）'),
     ('walk_km_per_staff', '歩く距離（km/人）'), ('staff_util', '手がふさがる割合')]

def ci(df):
    bs = sorted(st.median(random.choices(df, k=len(df))) for _ in range(3000))
    return bs[75], bs[2924]

result = {}
for a, old in [('28', '20261030_night'), ('36', '20261106_night_a36')]:
    print(f'\n==== 来客 {a}組/時（④・スタッフ2人） ====')
    sets = collections.OrderedDict()
    sets['0台（前の版）'] = pick(old, '0', a)
    sets['1台（前の版）'] = pick(old, '1', a)
    sets['2台（前の版）'] = pick(old, '2', a)
    for v in ['今のまま', '案A', '案B', "案B'"]:
        sets[f'2台 {v}'] = pick('20261108_tr', '2', a, v)
    base = sets['2台 今のまま']; one = sets['1台（前の版）']
    for n, d in sets.items():
        cs = d[min(d)]['unity_cs'] if d else ''
        row = {'n': len(d), 'cs': cs}
        txt = []
        for k, lab in M:
            v = st.median(float(d[i][k]) for i in d)
            row[k] = v
            txt.append(f'{lab} {v:.2f}')
        if n.startswith('2台 ') and n != '2台 今のまま':
            s = sorted(set(d) & set(base))
            for k, lab in M:
                df = [float(d[i][k]) - float(base[i][k]) for i in s]
                lo, hi = ci(df)
                row[k + '_vs_now'] = [st.median(df), lo, hi, sum(x < 0 for x in df), len(s)]
                txt.append(f'今のままとの差（{lab}） 中央{st.median(df):+.2f} 95%[{lo:+.2f},{hi:+.2f}] 減った種{sum(x < 0 for x in df)}/{len(s)}')
        if n != '1台（前の版）':
            s = sorted(set(d) & set(one))
            df = [float(d[i]['entry_wait_mean']) - float(one[i]['entry_wait_mean']) for i in s]
            row['entry_vs_one'] = [st.median(df), sum(x < 0 for x in df), len(s)]
            txt.append(f'1台との差（入店待ち） 中央{st.median(df):+.1f} 1台より短い種{sum(x < 0 for x in df)}/{len(s)}')
        print(f'-- {n}（{len(d)}回・{cs}）'); print('   ' + '\n   '.join(txt))
        result[f'{a}|{n}'] = row

# 1本道で待った時間（案B・B'）
print('\n==== 1本道で待った時間（summary.json の traffic） ====')
G = collections.defaultdict(list)
for f in glob.glob(f'{U}/20261108_tr*_lane*/summary.json'):
    t = json.load(open(f))['traffic']; a = '36' if '_a36_' in f else '28'; v = "案B'" if 'noT26' in f else '案B'
    G[(a, v)].append(t)
for (a, v), ts in sorted(G.items()):
    tot = [x['lane_wait_total_s'] for x in ts]
    print(f'来客{a} {v}: 待った合計 中央{st.median(tot):.0f}秒 最大{max(tot):.0f}秒 / 1回の最長 {max(x["lane_wait_max_s"] for x in ts):.0f}秒 / '
          f'待つと決めた回数 中央{st.median(x["lane_holds"] for x in ts)} / 終わりに待ったまま {sum(x["still_holding_at_end"] for x in ts)}回 / 行けない卓 {sum(len(x["unreachable_tables"]) for x in ts)}')
json.dump(result, open('traffic_compare.json', 'w'), ensure_ascii=False, indent=1)
```

```text

==== 来客 28組/時（④・スタッフ2人） ====
-- 0台（前の版）（10回・ServiceSim/cs:29）
   入店待ち（分） 12.95
   動かしに行った回数 0.00
   料理が届くまで（分） 1.26
   歩く距離（km/人） 3.42
   手がふさがる割合 0.84
   1台との差（入店待ち） 中央-2.8 1台より短い種7/10
-- 1台（前の版）（10回・ServiceSim/cs:29）
   入店待ち（分） 13.78
   動かしに行った回数 13.00
   料理が届くまで（分） 2.10
   歩く距離（km/人） 2.51
   手がふさがる割合 0.81
-- 2台（前の版）（10回・ServiceSim/cs:29）
   入店待ち（分） 26.85
   動かしに行った回数 78.50
   料理が届くまで（分） 3.47
   歩く距離（km/人） 3.09
   手がふさがる割合 0.89
   1台との差（入店待ち） 中央+10.9 1台より短い種0/10
-- 2台 今のまま（10回・ServiceSim/cs:55）
   入店待ち（分） 25.06
   動かしに行った回数 53.50
   料理が届くまで（分） 3.47
   歩く距離（km/人） 2.63
   手がふさがる割合 0.85
   1台との差（入店待ち） 中央+9.2 1台より短い種1/10
-- 2台 案A（10回・ServiceSim/cs:55）
   入店待ち（分） 24.16
   動かしに行った回数 57.50
   料理が届くまで（分） 3.38
   歩く距離（km/人） 2.60
   手がふさがる割合 0.84
   今のままとの差（入店待ち（分）） 中央+0.43 95%[-2.92,+2.84] 減った種4/10
   今のままとの差（動かしに行った回数） 中央-0.50 95%[-5.00,+13.00] 減った種5/10
   今のままとの差（料理が届くまで（分）） 中央+0.10 95%[-1.66,+0.35] 減った種4/10
   今のままとの差（歩く距離（km/人）） 中央+0.02 95%[-0.05,+0.11] 減った種4/10
   今のままとの差（手がふさがる割合） 中央-0.02 95%[-0.05,+0.00] 減った種8/10
   1台との差（入店待ち） 中央+8.4 1台より短い種0/10
-- 2台 案B（10回・ServiceSim/cs:55）
   入店待ち（分） 17.58
   動かしに行った回数 29.00
   料理が届くまで（分） 1.96
   歩く距離（km/人） 2.42
   手がふさがる割合 0.82
   今のままとの差（入店待ち（分）） 中央-7.31 95%[-9.60,-3.62] 減った種10/10
   今のままとの差（動かしに行った回数） 中央-20.50 95%[-33.00,-5.00] 減った種8/10
   今のままとの差（料理が届くまで（分）） 中央-1.81 95%[-2.35,-1.07] 減った種9/10
   今のままとの差（歩く距離（km/人）） 中央-0.19 95%[-0.26,-0.04] 減った種8/10
   今のままとの差（手がふさがる割合） 中央-0.05 95%[-0.06,-0.02] 減った種10/10
   1台との差（入店待ち） 中央+2.9 1台より短い種3/10
-- 2台 案B'（10回・ServiceSim/cs:55）
   入店待ち（分） 20.00
   動かしに行った回数 40.00
   料理が届くまで（分） 2.58
   歩く距離（km/人） 2.53
   手がふさがる割合 0.84
   今のままとの差（入店待ち（分）） 中央-5.27 95%[-9.18,-1.43] 減った種9/10
   今のままとの差（動かしに行った回数） 中央-13.50 95%[-25.50,-5.00] 減った種8/10
   今のままとの差（料理が届くまで（分）） 中央-1.06 95%[-2.18,-0.31] 減った種9/10
   今のままとの差（歩く距離（km/人）） 中央-0.08 95%[-0.23,-0.02] 減った種9/10
   今のままとの差（手がふさがる割合） 中央-0.02 95%[-0.04,-0.00] 減った種8/10
   1台との差（入店待ち） 中央+3.3 1台より短い種2/10

==== 来客 36組/時（④・スタッフ2人） ====
-- 0台（前の版）（10回・ServiceSim/cs:44）
   入店待ち（分） 32.87
   動かしに行った回数 0.00
   料理が届くまで（分） 1.28
   歩く距離（km/人） 4.52
   手がふさがる割合 0.87
   1台との差（入店待ち） 中央-1.2 1台より短い種7/10
-- 1台（前の版）（10回・ServiceSim/cs:44）
   入店待ち（分） 31.57
   動かしに行った回数 21.00
   料理が届くまで（分） 2.26
   歩く距離（km/人） 3.29
   手がふさがる割合 0.85
-- 2台（前の版）（10回・ServiceSim/cs:44）
   入店待ち（分） 45.92
   動かしに行った回数 63.50
   料理が届くまで（分） 2.87
   歩く距離（km/人） 3.43
   手がふさがる割合 0.87
   1台との差（入店待ち） 中央+11.4 1台より短い種1/10
-- 2台 今のまま（10回・ServiceSim/cs:55）
   入店待ち（分） 43.65
   動かしに行った回数 74.50
   料理が届くまで（分） 3.19
   歩く距離（km/人） 3.46
   手がふさがる割合 0.89
   1台との差（入店待ち） 中央+14.8 1台より短い種0/10
-- 2台 案A（10回・ServiceSim/cs:55）
   入店待ち（分） 56.49
   動かしに行った回数 92.00
   料理が届くまで（分） 4.17
   歩く距離（km/人） 3.76
   手がふさがる割合 0.88
   今のままとの差（入店待ち（分）） 中央+11.40 95%[-6.18,+17.79] 減った種4/10
   今のままとの差（動かしに行った回数） 中央+16.00 95%[-0.50,+44.00] 減った種2/10
   今のままとの差（料理が届くまで（分）） 中央+1.09 95%[-0.22,+2.39] 減った種3/10
   今のままとの差（歩く距離（km/人）） 中央+0.21 95%[+0.03,+0.39] 減った種2/10
   今のままとの差（手がふさがる割合） 中央-0.01 95%[-0.03,+0.02] 減った種6/10
   1台との差（入店待ち） 中央+17.8 1台より短い種0/10
-- 2台 案B（10回・ServiceSim/cs:55）
   入店待ち（分） 42.12
   動かしに行った回数 55.00
   料理が届くまで（分） 2.48
   歩く距離（km/人） 3.36
   手がふさがる割合 0.88
   今のままとの差（入店待ち（分）） 中央-4.45 95%[-9.30,-1.47] 減った種8/10
   今のままとの差（動かしに行った回数） 中央-18.00 95%[-30.00,-10.00] 減った種10/10
   今のままとの差（料理が届くまで（分）） 中央-0.86 95%[-1.15,-0.09] 減った種8/10
   今のままとの差（歩く距離（km/人）） 中央-0.10 95%[-0.23,-0.07] 減った種9/10
   今のままとの差（手がふさがる割合） 中央-0.01 95%[-0.04,+0.01] 減った種6/10
   1台との差（入店待ち） 中央+7.8 1台より短い種1/10
-- 2台 案B'（10回・ServiceSim/cs:55）
   入店待ち（分） 39.88
   動かしに行った回数 44.00
   料理が届くまで（分） 2.45
   歩く距離（km/人） 3.23
   手がふさがる割合 0.88
   今のままとの差（入店待ち（分）） 中央-6.99 95%[-13.69,-0.22] 減った種8/10
   今のままとの差（動かしに行った回数） 中央-29.50 95%[-45.00,-13.00] 減った種8/10
   今のままとの差（料理が届くまで（分）） 中央-1.05 95%[-1.39,+0.04] 減った種7/10
   今のままとの差（歩く距離（km/人）） 中央-0.20 95%[-0.27,-0.06] 減った種9/10
   今のままとの差（手がふさがる割合） 中央-0.01 95%[-0.03,+0.01] 減った種7/10
   1台との差（入店待ち） 中央+5.9 1台より短い種1/10

==== 1本道で待った時間（summary.json の traffic） ====
来客28 案B: 待った合計 中央684秒 最大919秒 / 1回の最長 508秒 / 待つと決めた回数 中央21.5 / 終わりに待ったまま 2回 / 行けない卓 0
来客28 案B': 待った合計 中央548秒 最大1885秒 / 1回の最長 440秒 / 待つと決めた回数 中央21.0 / 終わりに待ったまま 2回 / 行けない卓 0
来客36 案B: 待った合計 中央802秒 最大2088秒 / 1回の最長 471秒 / 待つと決めた回数 中央28.5 / 終わりに待ったまま 2回 / 行けない卓 0
来客36 案B': 待った合計 中央819秒 最大1475秒 / 1回の最長 551秒 / 待つと決めた回数 中央26.0 / 終わりに待ったまま 3回 / 行けない卓 0
```

| ④・スタッフ2人 | 入店待ち（普通） | 入店待ち（混んでいる） | 動かしに行った回数（普通／混む） | 歩く距離（普通） |
|---|---|---|---|---|
| 0台 | 13.0分 | 32.9分 | 0／0回 | 3.42km |
| 1台 | 13.8分 | 31.6分 | 13／21回 | 2.51km |
| 2台 今のまま | 25.1分 | 43.7分 | 53.5／74.5回 | 2.63km |
| 2台 案B 1本道は1台ずつ | 17.6分 | 42.1分 | 29／55回 | 2.42km |
| 2台 案B' よけ場所 | 20.0分 | 39.9分 | 40／44回 | 2.53km |

同じ種どうしで、2台が1台より入店待ちが短かった種は、案Bでも10個中1〜3個でした。④では、1台なら入店待ちは0台とほぼ同じのまま、スタッフの歩く距離が約3割減り、動かしに行く回数も2時間で13〜21回に収まります。2台目は、決まりを工夫しても1台を超えませんでした。


## 狭い店の配膳ロボットについて、店に伝えられること

**狭い店は1台から始める。もう2台入れているなら、まず「1本道には1台ずつ入る」決まりを入れる。それでも混む時間帯の世話が多ければ、2人席を1つ外してよけ場所を作る。一方通行は遠回りになり、かえって悪くなる。**

残っていることを書いておきます。

- 1台・0台の数字は前の版の夜のもの。記事や提案で数字を並べるなら、今の版（cs:55）で1台・0台を回し直してそろえる
- 案B・B'で、1回の待ちが7〜9分になる回がある。中身を見た回は、1本道の中の相手がスタッフにふさがれていた。終わったときに待ったままの回が40回中9回あった
- 料理が届くまでが、ロボットを入れると約1分から2〜2.5分に延びる。「ロボットが来るまで最大2分待つ」決まりから来ているので、この待ちを短くしたらどうなるかは、次に試せる打ち手
- 夜の判定に、同じ種どうしの比べ方を足すかは決めていない
- 作業の時間のばらつきは、店の記録が10回以上たまってから試算に入れるかを決める

### 置き場所

| もの | 場所 |
|---|---|
| ずれの比べ（var1〜3.py と出力） | iCloud のサービスロボット導入シミュレーション/unity/unity_variance_check_20261101/ |
| 止まった場所の図・負担の表・夜の比べ（stopmap.py・plan_p2.py・burden.py・traffic_compare.py と出力） | 同じ unity/stop_map_20261101/ |
| この記事に載せた材料の写し | 同じ LOG423_sources/ |
| 案B'の店の形 | GitHub の p2m-site の public/sim/layouts/trials/p2_noT26/ |
| 回し方の決まり | GitHub の p2m-site の public/sim/serving-robot-params.json の unity_rules |
| 夜の80回の記録 | KIOXIA の r2r2r-data/service_sim/unity/night/20261108_traffic/ と 20261108_tr_* の80フォルダ・unity_ledger_v1.csv |
| 確かめの4回と画像 | KIOXIA の night/20261101_traffic_check/ |
