サービスロボット導入シミュレーション Log#25 | 配膳ロボットの効き目は、ファミレスのスタッフ数で変わる

この記事の素材(コード・ログ・データ 6ファイル・まとめてダウンロードあり)

配膳ロボットを入れたファミレスで待ち時間がどれだけ縮むかは、スタッフの人数で変わります。Unity の3Dで再現したファミレス6店を一晩360回回すと、ロボット0台ではスタッフの人数を変えても、サイトの試算(計算で出す待ち時間)とほぼ同じ入店待ちになりました。ところが、スタッフが少ない店に配膳ロボットを2台足しても、入店待ちは試算ほど縮みませんでした。42卓の店をスタッフ3人で回すと、Unity は14.8分から13.1分、試算は10.6分から4.0分です。

配膳ロボットを検討する店の多くは、人が足りないから入れます。人が少ない店ほどロボットが助けになる、と考えるのが自然です。試算もそう答えていました。ところが、人とロボットが同じ通路を歩く3Dの店では、人の少ない店ほど、試算との差が大きく出ました。ロボットを足すと、片付けや案内にかかる時間が延び、動けなくなったロボットを人が動かしに行く手間も増えます。その手間が、人の少ない店ではそのまま入店待ちに響きます。小さめの店(④)では、スタッフを3人にしても、ロボットを足すほど入店待ちが延びました(2.9分 → 5.2分 → 12.7分)。

このシリーズでは、同じ6つのファミレスを2つの道具で回しています。数字の正はサイトの試算で、Unity は通路で起きることを測る役です。これまで Unity は、スタッフを店ごとの真ん中の人数(2〜4人)でしか回していませんでした。今回は人数を少なく・多くして回しました。

やったことなぜ結果
6店の待機場所で、ロボットどうしが詰まる原因を数えた
6店すべてで、同じ所に止まりが集まっていた
1台分の幅0.8mが狭い。メーカーの設置ガイドでは約0.92m。今は直さず記録だけ
ロボットが動けなくなり、人が動かしに行く割合を店ごとに数えた
ロボットがいるとスタッフの仕事が増える理由を知るため
試算は1往復あたり0.01、Unity は1台で0.02〜0.22・2台で0.04〜0.57。今は記録だけ
Unity が読む条件に、スタッフが少ない・多いの36条件を足した
試算はスタッフの人数を3通り選べるが、Unity は1通りだった
条件は54件から90件に。前からある54件は変えていない
6店 × スタッフ少ない・多い × ロボット0・1・2台 × 種10個、360回を回した
人数によって、ロボットの効き目がどう変わるかを見るため
0台は試算とそろう。人が少ないと、ロボットを足しても試算ほど縮まない
比較動画の素材を6店 × 11条件の66本、夜に録画し、④のロボット0台と2台を並べた動画を作った
数字と文章だけでは、ロボットを足しても待ちが縮まない様子が伝わりにくい
2台の店ではロボットが通路で止まり、スタッフが動かしに行く場面が続く

下の図で、店とスタッフの人数を切り替えると、ロボット0・1・2台の入店待ちを Unity と試算で並べて見られます。

下の動画は、④の狭い店をスタッフ3人で回した Unity の店を、ロボット0台(左)と2台(右)で並べたものです(30倍速)。2台の店では、白い配膳ロボットが通路で止まり、青いスタッフが動かしに行く場面が何度も出ます。

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

店の立体図(席・キッチン・配膳ロボットの通り道)導入前シミュレーション配膳ロボットを、導入前に自分の店舗でシミュレーション店舗の図面と人数から、何人で回るか・どこで止まるか・月いくらかかるかを出します

作業ログ ― 6店の待機場所で、ロボットどうしが詰まる原因を数えた

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

言葉何かこのシリーズでの使い方
サイトの試算
Python の SimPy(出来事を時刻の順に進める、待ち行列のシミュレーションの道具)で書いた計算(haizen_sim.py)
店・スタッフの人数・ロボットの台数・混み具合の組み合わせ32,850通りを、それぞれ40回回した平均を 試算のページ に出している。数字の正。今の版は v2.9
Unity
ゲームなどを作る3Dの開発環境(Unity)
サイトの店の形どおりに店を建て、スタッフ・お客さん・ロボットを通路で歩かせて、止まる回数や待ち時間を測る
依頼箱
Claude が書いた依頼を、Mac の見張り係が Claude Code(コードを書いて動かす AI)に自動で渡す仕組み
Unity を動かす作業はすべてここから頼む。夜のあいだも続けて回せる
種
乱数の出発点。同じ種なら同じお客さんの来方になる
Unity は1つの条件を種10個で回し、10回の中央値(真ん中の値)で見る
台帳
Unity の回ごとの記録を1行ずつ足していく表(unity_ledger_v1.csv)
回す前に控えを取り、前からある行が変わっていないことを md5(ファイルの指紋)で確かめる
BellaBot
Pudu Robotics の配膳ロボット。猫の顔の画面が付いた機種で、日本のファミレスでもよく見かける
店の形を作るときの基準の機種。通れる通路の幅(80cm)も、この機種の公表値から決めた
待機場所
料理の受け渡し口の近くで、仕事のないロボットが並んで待つ場所
店の形のデータに、店ごとに3台分(④は2台分)を置いている
動けなくなった
Unity のロボットが60秒止まったまま動けない状態
Unity ではスタッフが動かしに行く。試算では「立ち往生」として、1往復あたりの割合(stuck_rate)で入れている

店の番号と形は次のとおりです。③ 効率重視・入口が角(42卓)、⑦ ③の列の間の通路を95cmにした店、④ 効率重視・狭い店・入口が右の壁、⑤ ゆったり型・入口が正面の中央、⑥ ゆったり型・入口が左の壁、② 定番の郊外型(昔働いていたファミレスを記憶から起こした店)。

待機場所の中の30秒以上の止まりを、行き先ごとに分けた standby_jam_count.py

前の夜(20261102〜20261106)の2台の回で、待機場所の四角の中で30秒以上止まったものを、止まったロボットがどこへ向かっていたかで分けました。四角と受け渡し口の点は、各店の店の形から取った値です。KIOXIA の service_sim/unity で動かしました。

import csv,glob,json,collections,os
rects={'fam_p1':(16.5,18.9,5.65,6.45,(15.3,5.95)),'fam_p5':(16.5,18.9,5.65,6.45,(15.3,5.95)),'fam_p2':(12.0,13.6,4.15,4.95,(11.05,4.45)),
'fam_p3':(14.2,16.6,8.55,9.35,(12.5,8.85)),'fam_p4':(15.5,17.9,5.65,6.45,(14.3,5.95)),'fam_p0':(12.2,14.6,5.1,5.9,(10.9,5.45))}
name={'fam_p1':'③','fam_p5':'⑦','fam_p2':'④','fam_p3':'⑤','fam_p4':'⑥','fam_p0':'②'}
for st,(x0,x1,z0,z1,fp) in rects.items():
  C=collections.Counter(); D=collections.Counter(); slots=None; n=0; dur=0
  for r in glob.glob(f'2026110[2-6]_night*_{st}_r2_s*'):
    if not os.path.exists(r+'/stops.csv'): continue
    if slots is None:
      tr=list(csv.DictReader(open(r+'/robot_track.csv')))[:2]
      slots=[(t['x'],t['z']) for t in tr]
    for s in csv.DictReader(open(r+'/stops.csv')):
      x,z=float(s['x']),float(s['z'])
      if not(x0<=x<=x1 and z0<=z<=z1): continue
      if float(s['duration_s'])<30: continue
      n+=1; dur+=float(s['duration_s'])
      to=s['solve_to']; fr=s['solve_from']
      tx,tz=map(float,to.split(';')) if to else (0,0)
      kind='受け渡し口へ' if abs(tx-fp[0])<0.3 and abs(tz-fp[1])<0.3 else ('待機の点へ' if x0<=tx<=x1 and z0<=tz<=z1 else ('卓など外へ' if to else '不明'))
      C[kind]+=1; D[(kind,s['cause'],s['cause_detail'][:12])]+=1
  print(name[st],st,'2台の初期位置',slots,'30秒以上',n,'平均',round(dur/max(n,1)),'秒',dict(C))
  for k,v in D.most_common(5): print('   ',k,v)
③ fam_p1 2台の初期位置 [('16.9', '6.05'), ('17.7', '6.05')] 30秒以上 112 平均 55 秒 {'卓など外へ': 35, '待機の点へ': 72, '受け渡し口へ': 5}
    ('待機の点へ', 'もう1台のロボット', 'ServingRobot') 70
    ('卓など外へ', 'もう1台のロボット', 'ServingRobot') 32
    ('卓など外へ', 'もう1台のロボット', '') 3
    ('受け渡し口へ', 'もう1台のロボット', 'ServingRobot') 3
    ('待機の点へ', 'もう1台のロボット', '') 2
⑦ fam_p5 2台の初期位置 [('16.9', '6.05'), ('17.7', '6.05')] 30秒以上 139 平均 55 秒 {'卓など外へ': 35, '待機の点へ': 102, '受け渡し口へ': 2}
    ('待機の点へ', 'もう1台のロボット', 'ServingRobot') 98
    ('卓など外へ', 'もう1台のロボット', 'ServingRobot') 32
    ('卓など外へ', 'もう1台のロボット', '') 3
    ('待機の点へ', 'もう1台のロボット', '') 3
    ('受け渡し口へ', '分からない', '') 2
④ fam_p2 2台の初期位置 [('12.4', '4.55'), ('13.2', '4.55')] 30秒以上 186 平均 57 秒 {'卓など外へ': 38, '待機の点へ': 133, '受け渡し口へ': 15}
    ('待機の点へ', 'もう1台のロボット', 'ServingRobot') 132
    ('卓など外へ', 'もう1台のロボット', 'ServingRobot') 37
    ('受け渡し口へ', 'もう1台のロボット', 'ServingRobot') 7
    ('受け渡し口へ', '分からない', '') 6
    ('卓など外へ', 'もう1台のロボット', '') 1
⑤ fam_p3 2台の初期位置 [('14.6', '8.95'), ('15.4', '8.95')] 30秒以上 87 平均 55 秒 {'待機の点へ': 61, '受け渡し口へ': 11, '卓など外へ': 15}
    ('待機の点へ', 'もう1台のロボット', 'ServingRobot') 47
    ('卓など外へ', 'もう1台のロボット', 'ServingRobot') 13
    ('待機の点へ', '人が立ち止まっていた', 'Guest(Clone)') 6
    ('受け渡し口へ', 'もう1台のロボット', 'ServingRobot') 4
    ('受け渡し口へ', '人が立ち止まっていた', 'Staff02') 2
⑥ fam_p4 2台の初期位置 [('15.9', '6.05'), ('16.7', '6.05')] 30秒以上 205 平均 54 秒 {'待機の点へ': 126, '卓など外へ': 76, '受け渡し口へ': 3}
    ('待機の点へ', 'もう1台のロボット', 'ServingRobot') 119
    ('卓など外へ', 'もう1台のロボット', 'ServingRobot') 74
    ('待機の点へ', 'もう1台のロボット', '') 6
    ('卓など外へ', 'もう1台のロボット', '') 2
    ('受け渡し口へ', '分からない', '') 2
② fam_p0 2台の初期位置 [('12.6', '5.5'), ('13.4', '5.5')] 30秒以上 166 平均 56 秒 {'待機の点へ': 123, '卓など外へ': 41, '受け渡し口へ': 2}
    ('待機の点へ', 'もう1台のロボット', 'ServingRobot') 119
    ('卓など外へ', 'もう1台のロボット', 'ServingRobot') 40
    ('待機の点へ', 'もう1台のロボット', '') 4
    ('受け渡し口へ', 'もう1台のロボット', 'ServingRobot') 2
    ('卓など外へ', 'もう1台のロボット', '') 1

6店とも、止まりのほとんどは「もう1台のロボット」が理由で、いちばん多いのは待機の点(自分の待つ場所)へ戻ろうとして止まったものでした。止まっている長さは平均54〜57秒で、60秒の「動けなくなった」の手前まで続いています。

止まったときにもう1台がどこにいたかを見た standby_jam_pair.py

止まったロボットの位置・行き先と、同じ時刻のもう1台の位置と状態を並べました。③と②で動かしました(引数は店と待機場所の四角)。

import csv,glob,collections,bisect,sys
st=sys.argv[1]; x0,x1,z0,z1=map(float,sys.argv[2:6])
C=collections.Counter(); ex=[]
for r in sorted(glob.glob(f'2026110[2-6]_night*_{st}_r2_s*'))[:40]:
  try: tr=list(csv.DictReader(open(r+'/robot_track.csv')))
  except: continue
  T=collections.defaultdict(list)
  for t in tr: T[t['robot_id']].append((float(t['t_s']),float(t['x']),float(t['z']),t['phase']))
  for s in csv.DictReader(open(r+'/stops.csv')):
    x,z=float(s['x']),float(s['z'])
    if not(x0<=x<=x1 and z0<=z<=z1) or float(s['duration_s'])<30: continue
    other=[k for k in T if k!=s['robot_id']][0]; L=T[other]; ts=[a[0] for a in L]
    i=min(bisect.bisect_left(ts,float(s['t_s'])),len(L)-1); o=L[i]
    to=s['solve_to']
    key=(f"自分({x:.1f},{z:.1f})", f"行き先{to}", f"相手({o[1]:.1f},{o[2]:.1f}){o[3]}")
    C[key]+=1
for k,v in C.most_common(12): print(v,k)
$ python3 standby_jam_pair.py fam_p1 16.5 18.9 5.65 6.45
27 ('自分(16.6,5.9)', '行き先16.9;6.05', '相手(17.5,5.9)往復おわり')
21 ('自分(16.5,5.9)', '行き先16.9;6.05', '相手(17.5,6.0)往復おわり')
6 ('自分(16.7,6.0)', '行き先16.9;6.05', '相手(17.6,5.8)往復おわり')
3 ('自分(16.6,5.7)', '行き先16.9;6.05', '相手(17.5,6.0)往復おわり')
3 ('自分(16.6,5.9)', '行き先15.963;5.313', '相手(17.5,5.9)往復おわり')
3 ('自分(16.6,6.1)', '行き先16.9;6.05', '相手(17.5,6.0)往復おわり')
3 ('自分(16.5,5.9)', '行き先16.9;6.05', '相手(17.5,6.1)往復おわり')
2 ('自分(16.6,5.8)', '行き先16.9;6.05', '相手(17.5,6.1)往復おわり')
2 ('自分(16.5,5.9)', '行き先15.963;5.338', '相手(17.5,6.0)往復おわり')
2 ('自分(16.5,5.8)', '行き先16.9;6.05', '相手(17.5,6.0)往復おわり')
2 ('自分(16.5,5.9)', '行き先16.488;5.363', '相手(17.5,6.0)往復おわり')
2 ('自分(16.5,5.9)', '行き先16.9;6.05', '相手(17.5,5.9)往復おわり')

$ python3 standby_jam_pair.py fam_p0 12.2 14.6 5.1 5.9
41 ('自分(12.3,5.5)', '行き先12.6;5.5', '相手(13.2,5.5)往復おわり')
23 ('自分(12.2,5.5)', '行き先12.6;5.5', '相手(13.2,5.5)往復おわり')
21 ('自分(12.3,5.5)', '行き先12.6;5.5', '相手(13.2,5.4)往復おわり')
11 ('自分(12.2,5.3)', '行き先12.6;5.5', '相手(13.2,5.5)往復おわり')
6 ('自分(12.4,5.5)', '行き先12.6;5.5', '相手(13.3,5.3)往復おわり')
5 ('自分(12.2,5.4)', '行き先12.6;5.5', '相手(13.2,5.5)往復おわり')
5 ('自分(12.3,5.5)', '行き先12.413;4.888', '相手(13.2,5.5)往復おわり')
4 ('自分(12.3,5.5)', '行き先12.438;4.888', '相手(13.2,5.5)往復おわり')
3 ('自分(12.3,5.5)', '行き先12.6;5.5', '相手(13.2,5.4)往復おわり')
3 ('自分(12.2,5.5)', '行き先12.438;4.888', '相手(13.2,5.5)往復おわり')
3 ('自分(12.3,5.5)', '行き先11.588;4.813', '相手(13.2,5.4)往復おわり')
3 ('自分(12.2,5.5)', '行き先12.6;5.5', '相手(13.2,5.4)往復おわり')

形は2店とも同じでした。往復を終えたロボットが、受け渡し口に近い1番目の場所(③なら x16.9)へ入ろうとして、手前(x16.5〜16.6)で止まります。そのとき、もう1台も「往復おわり」の状態で、2番目の場所(x17.7)の0.1〜0.2m手前(x17.5〜17.6)にいます。2台とも自分の場所に着き切らないまま、近すぎてお互いに動けなくなっていました。

待機場所に戻ったロボットが止まる位置と、止まりの終わり方 standby_park_gap.py

③で、待機場所の近くにいるロボットの位置を0.1m単位で数え、30秒以上の止まりのときのいちばん近い障害物までの距離と、止まりの終わり方を数えました。

import csv,glob,collections
P=collections.Counter(); G=[]; res=collections.Counter()
for r in sorted(glob.glob('2026110[2-6]_night*_fam_p1_r2_s*'))[:40]:
  try: tr=csv.DictReader(open(r+'/robot_track.csv'))
  except: continue
  for t in tr:
    if t['phase'] in('往復おわり','待機') and 16.3<float(t['x'])<19 and 5.5<float(t['z'])<6.6: P[(t['phase'],round(float(t['x']),1),round(float(t['z']),1))]+=1
  for s in csv.DictReader(open(r+'/stops.csv')):
    x,z=float(s['x']),float(s['z'])
    if 16.5<=x<=18.9 and 5.65<=z<=6.45 and float(s['duration_s'])>=30:
      G.append(float(s['min_gap_cm'])); res[(s['resolved'],round(float(s['duration_s'])/10)*10)]+=1
for k,v in P.most_common(10): print(v,k)
G.sort(); print('min_gap_cm', G[0],G[len(G)//2],G[-1]); print(res.most_common(8))
8936 ('往復おわり', 16.7, 6.0)
7355 ('往復おわり', 17.6, 5.8)
5175 ('待機', 17.7, 6.0)
4852 ('往復おわり', 17.6, 5.9)
3933 ('待機', 16.9, 6.0)
3842 ('往復おわり', 17.5, 6.0)
3488 ('往復おわり', 16.8, 5.9)
3352 ('往復おわり', 17.9, 5.9)
3113 ('往復おわり', 16.7, 5.9)
2197 ('往復おわり', 17.5, 5.9)
min_gap_cm 19.0635 37.8247 111.3066
[(('動けなくなった', 60), 75), (('よける場所へ動いた', 40), 17), (('よける場所へ動いた', 50), 6), (('よける場所へ動いた', 60), 5), (('よける場所へ動いた', 30), 4), (('自分で動き出した', 60), 1), (('回り込んで動き出した', 60), 1), (('回り込んで動き出した', 40), 1)]

「往復おわり」のまま、場所の中心(x16.9・x17.7)から0.1〜0.2mずれた所にいる時間が長く、30秒以上の止まり110回のうち75回は60秒の「動けなくなった」で終わっていました。

BellaBot Pro の設置ガイドでは、隣のロボットとの間を35cm空ける

店の形を作る決まりでは、待機場所の1台分の大きさを0.8m×0.8m以上にしています(famires_v3.py の standby_slot=0.8)。2台の中心の間が0.8mなので、どちらかが10〜20cmずれて止まるだけで、もう1台が入れなくなります。

Pudu の BellaBot Pro の設置ガイド(v1・2024年6月24日)には、待機場所の決まりが書かれています。

項目ガイドの値
機体の大きさ
570mm × 550mm × 1290mm
待機場所で2台を並べるときの間
35cm
待機場所の後ろの壁から15cm
待機場所の横の壁から35cm
走れる最小の通路の幅65cm
2台が正面からすれ違うのに勧める幅
1.6m より広く

機体の幅57cmに隣との間35cmを足すと、1台分の幅は約0.92mになります。今の0.8mでは足りていません。店の形の決まりにある BellaBot の機体の幅は0.50mで、ガイドの BellaBot Pro の0.57mより小さい値でした。

1台分を0.95mにした場合を、店の形の作り方で試した standby_pitch_try.py

店の形を作るコード(p2m-site の tools/sim_layouts/famires_v3.py)は変えずに、読み込んだ店の形の待機場所の四角だけを、1台分0.95mの長さにのばして、決まりの確かめと卓までの距離の計算を回しました。のばす向きは、受け渡し口から遠い側です。tools/sim_layouts/ の中で動かします。

import sys, json, copy
sys.path.insert(0, '.')
import famires_v3 as v
PITCH=float(sys.argv[1]) if len(sys.argv)>1 else 0.95
base='../../public/sim/layouts/'
for nm, fn in v.BUILDS:
    L = v.prepare(fn())
    sb = L["robot_standby"]; x0,y0,x1,y1 = sb["rect"]; n=sb["slots"]
    dish = L["dish_up"][0]["rect"]; dx=(dish[0]+dish[2])/2
    need = n*PITCH
    horiz = (x1-x0) >= (y1-y0)
    cur = (x1-x0) if horiz else (y1-y0)
    ext = round(need-cur, 3)
    if horiz:
        if abs(x0-dx) < abs(x1-dx): sb["rect"]=[x0,y0,round(x1+ext,3),y1]; side='右へ'
        else: sb["rect"]=[round(x0-ext,3),y0,x1,y1]; side='左へ'
    else:
        sb["rect"]=[x0,y0,x1,round(y1+ext,3)]; side='上へ'
    vio = v.check_rules(L)
    old = json.load(open(base+nm+'_analysis.json'))['tables']
    msg=''
    if not vio:
        try:
            res,err = v.analyze(L, v.SETTINGS['res']); vio += err
            ch=[(a['id'],a['category'],b['category'],round(a.get('dist') or 0,2),round(b.get('dist') or 0,2)) for a,b in zip(res,old)
                if a['category']!=b['category'] or abs((a.get('dist') or 0)-(b.get('dist') or 0))>0.005]
            msg=f'距離・分け方が変わる卓 {len(ch)}: {ch[:6]}'
        except ValueError as e: vio.append(('出発点',str(e)))
    print(nm, L['name'], f'{n}台 {cur:.2f}m→{need:.2f}m({side}{ext:+.2f}m)', 'rect', sb['rect'])
    for k,m in vio[:6]: print('   NG', k, m)
    if msg: print('   ', msg)

1台分0.95mのとき:

$ python3 standby_pitch_try.py 0.95
p0_memory ② 定番の郊外型 3台 2.40m→2.85m(右へ+0.45m) rect [12.2, 5.1, 15.05, 5.9]
    距離・分け方が変わる卓 0: []
p1_efficient_corner ③ 効率重視型・入口が左下の角 3台 2.40m→2.85m(右へ+0.45m) rect [16.5, 5.65, 19.35, 6.45]
    距離・分け方が変わる卓 0: []
p2_efficient_narrow ④ 効率重視型・狭い店・入口が右の壁 2台 1.60m→1.90m(右へ+0.30m) rect [12.0, 4.15, 13.9, 4.95]
    距離・分け方が変わる卓 0: []
p3_relaxed_center ⑤ ゆったり型・入口が正面の中央 3台 2.40m→2.85m(右へ+0.45m) rect [14.2, 8.55, 17.05, 9.35]
    距離・分け方が変わる卓 7: [('T25', 'robot', 'robot', 17.54, 17.31), ('T26', 'robot', 'robot', 15.69, 15.46), ('T27', 'robot', 'robot', 15.09, 14.86), ('T28', 'robot', 'robot', 15.58, 15.34), ('T33', 'robot', 'robot', 9.1, 8.97), ('T37', 'robot', 'robot', 17.3, 17.07)]
p4_relaxed_side ⑥ ゆったり型・入口が左の壁 3台 2.40m→2.85m(右へ+0.45m) rect [15.5, 5.65, 18.35, 6.45]
    距離・分け方が変わる卓 0: []
p5_crammed ⑦ ③の列の間の通路を95cmに(卓と席数は③と同じ) 3台 2.40m→2.85m(右へ+0.45m) rect [16.5, 5.65, 19.35, 6.45]
    距離・分け方が変わる卓 0: []

比べる相手(公開中の analysis)と同じ計算になっているかを、今の幅のまま(1台分0.8m・のばす長さ0)で回して確かめました。⑤も変わる卓は0でした。

$ python3 standby_pitch_try.py 0.8 | grep -A1 p3_
p3_relaxed_center ⑤ ゆったり型・入口が正面の中央 3台 2.40m→2.40m(右へ-0.00m) rect [14.2, 8.55, 16.6, 9.35]
    距離・分け方が変わる卓 0: []

⑤だけ、右側の卓までの距離が延びる standby_pitch_p3_detail.py

⑤で距離が変わる卓を、止まる位置と一緒に出しました。

import sys, json
sys.path.insert(0,'.')
import famires_v3 as v
L=v.prepare(dict(v.BUILDS)['p3_relaxed_center']())
L['robot_standby']['rect']=[14.2,8.55,17.05,9.35]
res,_=v.analyze(L,v.SETTINGS['res'])
old={t['id']:t for t in json.load(open('../../public/sim/layouts/p3_relaxed_center_analysis.json'))['tables']}
for a in res:
    b=old[a['id']]
    if abs(a['dist']-b['dist'])>0.005: print(a['id'],a['stop'],b['stop'],a['dist'],b['dist'],a['side'],b['side'])
print('start',L['start'])
for t in L['tables']:
    if t['id'] in('T33','T25'): print(t)
print([ (r['label'],r['rect']) for r in L['rooms']][:12])
T25 [np.float64(24.013), np.float64(10.488)] [24.013, 10.488] 17.54 17.31 left left
T26 [np.float64(24.013), np.float64(12.338)] [24.013, 12.338] 15.69 15.46 left left
T27 [np.float64(24.013), np.float64(13.338)] [24.013, 13.338] 15.09 14.86 left left
T28 [np.float64(24.013), np.float64(14.513)] [24.013, 14.513] 15.58 15.34 left left
T33 [np.float64(19.963), np.float64(10.962)] [19.963, 10.962] 9.1 8.97 left left
T37 [np.float64(23.788), np.float64(10.638)] [23.788, 10.638] 17.3 17.07 right right
T38 [np.float64(23.788), np.float64(12.488)] [23.788, 12.488] 15.45 15.22 right right
start [12.5, 8.85]
{'type': 'booth', 'axis': 'v', 'x': 24.8, 'y': 8.8, 'w': 1.2, 'h': 1.85, 'seats': 4, 'id': 'T25'}
{'type': 'booth', 'axis': 'v', 'x': 20.75, 'y': 10.8, 'w': 1.2, 'h': 1.85, 'seats': 4, 'id': 'T33'}
[('パントリー', [7.5, 9.4, 17.5, 10.6]), ('キッチン', [7.5, 10.6, 17.5, 14.0]), ('洗い場', [7.5, 14.0, 12.5, 17.0]), ('冷蔵・冷凍・保存', [12.5, 14.0, 17.5, 17.0]), ('更衣室・社員トイレ', [17.5, 14.5, 21.5, 17.0]), ('トイレ', [0.0, 14.6, 3.0, 17.0]), ('レジ', [11.2, 0.3, 11.9, 2.8]), ('レジ内', [10.4, 0.3, 11.2, 2.8]), ('待合', [15.4, 0.3, 15.9, 2.3]), ('ドリンクバー', [15.0, 4.6, 18.6, 5.3])]

止まる位置は変わらず、右側の7卓(T25〜T28・T33・T37・T38)で距離が0.13〜0.23m延びます。⑤はパントリーの右端が x17.5 で、のばした待機場所の端(x17.05)との間が狭くなり、受け渡し口から右へ抜ける道が少し回り道になるためです。直すなら、⑤だけ試算を計算し直すことになります。

待機場所は、今は直さず記録だけにした

1台分を0.95mにする案、2台のときは1番と3番の場所に置く案(3台以上で同じことが残る)、記録だけにする案を並べ、記録だけにすることになりました。直すときの候補は「1台分を0.95mにして、⑤だけ試算を計算し直す」です。原因・ガイドの値・試した結果は、ずれの一覧 unity/unity_stageC_gaps_v1.md に書き足しました。

作業ログ ― ロボットが動けなくなり、人が動かしに行く割合を店ごとに数えた

この記事のコード・ログ・データは、1ファイルずつ下に置いています。全文はリンク先にあります。

ファイルの一覧(6本)

01_stuck_rate_by_store.py.txt ― 1往復あたりの「動けなくなった」の数を、場所と理由で分けた stuck_rate_by_store.py・3,141字

02_sec03.md ― 依頼32「スタッフの人数を広げて夜を回す」の全文・4,242字

03_sec03.txt ― Claude Code の報告 20261107_night_staff.txt の全文・47,677字

04_sec05.md ― 依頼38「比較動画の素材をストックする」の全文・3,091字

05_sec05.txt ― Claude Code の報告 20261030_rec_stock.txt の全文・49,508字

06_make_compare.py.txt ― 比較動画を作る make_compare.py・4,038字

ロボットがいると、スタッフの仕事が増える理由のいちばん大きいもの

前の記録(LOG405)で、④の混んでいる日・ロボット2台では、スタッフが1組あたりに使う時間が試算より約46%長く、いちばん大きいのは「ロボットを動かしに行く」(1組あたり30.6秒)でした。試算にも、ロボットが動けなくなってスタッフが動かしに行く「立ち往生」が入っていて、1往復あたり0.01の割合、動かす作業は15秒です。Unity で実際にどれくらい起きているかを、混み具合を広げた夜(20261106)で店ごとに数えました。

1往復あたりの「動けなくなった」の数を、場所と理由で分けた stuck_rate_by_store.py

import csv,glob,collections,json
R={'fam_p1':(16.5,18.9,5.65,6.45,15.3,5.95),'fam_p5':(16.5,18.9,5.65,6.45,15.3,5.95),'fam_p2':(12.0,13.6,4.15,4.95,11.05,4.45),'fam_p3':(14.2,16.6,8.55,9.35,12.5,8.85),'fam_p4':(15.5,17.9,5.65,6.45,14.3,5.95),'fam_p0':(12.2,14.6,5.1,5.9,10.9,5.45)}
nm={'fam_p1':'③','fam_p5':'⑦','fam_p2':'④','fam_p3':'⑤','fam_p4':'⑥','fam_p0':'②'}
for st,(x0,x1,z0,z1,px,pz) in R.items():
  for rob in ('r1','r2'):
    for a in sorted(set(d.split('_')[2] for d in glob.glob(f'20261106_night_a*_cs44_{st}_{rob}_s*'))):
      C=collections.Counter(); trips=0; groups=0; n=0
      for r in glob.glob(f'20261106_night_{a}_cs44_{st}_{rob}_s*'):
        n+=1
        trips+=sum(1 for _ in open(r+'/trips.csv'))-1
        groups+=sum(1 for _ in open(r+'/groups.csv'))-1
        for s in csv.DictReader(open(r+'/stops.csv')):
          if s['resolved']!='動けなくなった': continue
          x,z=float(s['x']),float(s['z'])
          k='待機場所' if x0<=x<=x1 and z0<=z<=z1 else ('受け渡し口2.5m' if (x-px)**2+(z-pz)**2<=6.25 else 'そのほか')
          C[(k,'ロボどうし' if 'ロボット' in s['cause'] else '人など')]+=1
      tot=sum(C.values())
      print(nm[st],rob,a,f'種{n} 往復{trips} 組{groups} 動けなくなった{tot}(1往復あたり{tot/trips:.3f})', dict(C))
01_stuck_rate_by_store.py.txt3,141字・24行・text
③ r1 a30 種10 往復646 組589 動けなくなった48(1往復あたり0.074) {('そのほか', '人など'): 47, ('受け渡し口2.5m', '人など'): 1}
③ r1 a54 種10 往復798 組1081 動けなくなった77(1往復あたり0.096) {('そのほか', '人など'): 76, ('受け渡し口2.5m', '人など'): 1}
③ r2 a30 種10 往復1051 組589 動けなくなった99(1往復あたり0.094) {('そのほか', '人など'): 50, ('待機場所', 'ロボどうし'): 27, ('受け渡し口2.5m', '人など'): 2, ('受け渡し口2.5m', 'ロボどうし'): 10, ('待機場所', '人など'): 1, ('そのほか', 'ロボどうし'): 9}
③ r2 a54 種10 往復1520 組1081 動けなくなった150(1往復あたり0.099) {('そのほか', '人など'): 78, ('受け渡し口2.5m', 'ロボどうし'): 17, ('待機場所', 'ロボどうし'): 17, ('そのほか', 'ロボどうし'): 33, ('受け渡し口2.5m', '人など'): 5}
⑦ r1 a30 種10 往復653 組589 動けなくなった44(1往復あたり0.067) {('そのほか', '人など'): 44}
⑦ r1 a54 種10 往復809 組1081 動けなくなった50(1往復あたり0.062) {('そのほか', '人など'): 50}
⑦ r2 a30 種10 往復984 組589 動けなくなった109(1往復あたり0.111) {('そのほか', '人など'): 50, ('待機場所', 'ロボどうし'): 34, ('そのほか', 'ロボどうし'): 14, ('受け渡し口2.5m', 'ロボどうし'): 4, ('受け渡し口2.5m', '人など'): 7}
⑦ r2 a54 種10 往復1600 組1081 動けなくなった153(1往復あたり0.096) {('そのほか', 'ロボどうし'): 8, ('そのほか', '人など'): 95, ('待機場所', 'ロボどうし'): 43, ('受け渡し口2.5m', 'ロボどうし'): 1, ('受け渡し口2.5m', '人など'): 6}
別のタブで開く保存
店1台・空いている1台・混んでいる2台・空いている2台・混んでいる
③0.0740.0960.0940.099
⑦0.0670.0620.1110.096
④0.2060.2210.5730.526
⑤0.0500.0470.0730.038
⑥0.0240.0190.0380.056
②0.0190.1890.1860.225

試算の0.01に対して、Unity は1台で2〜22倍、2台で4〜57倍でした。2台の分には、上の待機場所の詰まりを含む「ロボットどうし」の止まりが入っています。

1台のときに動けなくなった理由と場所 stuck_cause_one_robot.py

1台ならロボットどうしの止まりはありません。多かった3つ(④混んでいる・②混んでいる・③混んでいる)で、理由と場所(2m四方のマス)を数えました。

import csv,glob,collections
for st,a in (('fam_p2','a36'),('fam_p0','a24'),('fam_p1','a54')):
  C=collections.Counter(); M=collections.Counter(); D=collections.Counter()
  for r in glob.glob(f'20261106_night_{a}_cs44_{st}_r1_s*'):
    for s in csv.DictReader(open(r+'/stops.csv')):
      if s['resolved']!='動けなくなった': continue
      C[s['cause']]+=1; D[s['cause_detail'].split('(')[0][:10]]+=1
      M[(int(float(s['x'])//2*2),int(float(s['z'])//2*2), 'narrow' if s['in_narrow']=='1' else '')]+=1
  print(st,a,dict(C)); print('   ',D.most_common(6)); print('   ',M.most_common(5))
fam_p2 a36 {'スタッフとすれ違った': 44, '人が立ち止まっていた': 138, '分からない': 8, 'お客さんとすれ違った': 28, '椅子・荷物・ベビーカー': 4}
    [('Staff02', 76), ('Staff01', 71), ('Guest', 63), ('', 8), ('Table01/Ch', 4)]
    [((4, 2, ''), 71), ((2, 2, ''), 59), ((6, 2, ''), 17), ((8, 4, ''), 17), ((2, 8, ''), 13)]
fam_p0 a24 {'人が立ち止まっていた': 132, 'お客さんとすれ違った': 3, 'スタッフとすれ違った': 3}
    [('Guest', 123), ('Staff01', 12), ('', 2), ('Staff02', 1)]
    [((12, 0, ''), 114), ((12, 2, ''), 23), ((8, 4, ''), 1)]
fam_p1 a54 {'人が立ち止まっていた': 43, 'スタッフとすれ違った': 24, 'お客さんとすれ違った': 10}
    [('Guest', 20), ('Staff01', 20), ('Staff03', 13), ('Staff04', 13), ('Staff02', 11)]
    [((8, 4, ''), 24), ((4, 4, ''), 12), ((0, 8, ''), 10), ((2, 4, ''), 7), ((0, 4, ''), 5)]

1台の分は人が原因で、場所が偏っていました。②の混んでいる日は138回のうち114回が入口・レジのまわり(x12〜14・y0〜2)で、待っているお客さんが立ち止まっていました。④の混んでいる日は、下の左(x2〜6・y2〜4)で、スタッフとお客さんが立ち止まっていました。

試算の立ち往生の割合は、0.01のままにした

試算に Unity の値を入れる案(1台の分だけ店ごとに入れる・2台の分まで店ごとに入れる)と、記録だけにする案を並べ、記録だけにすることになりました。2台の分には、2026-10-24 に「Unity の作りの問題」として扱うと決めたロボットどうしの止まりが混ざっています。1台の分も、店と場所でこれだけ偏るなら、平均を入れると偏りが見えなくなります。数えた値は、ずれの一覧に書き足しました。

作業ログ ― 6店のスタッフの人数を「少ない・多い」に広げて、Unity で360回回した

Unity が読む条件のファイルに、スタッフの人数の36条件を足した

Unity は、サイトの public/sim/serving-robot-params.json を読んで条件を決めます(値を手で写さないため)。書き出しの export_params.py(試算 v2.9 のフォルダ)に、店ごとに「スタッフが少ない 0・1・2台 → 多い 0・1・2台」の6条件を後ろに足しました。混み具合は普通のままです。

  • ねらい:ロボットの効き目がスタッフの人数でどう変わるかを、3Dの店でも確かめる。最終的には、どの人数でも Unity で回せるようにする
  • 変わったこと:Unity が読む条件が54件から90件に。前の54件の条件は並びも中身も同じで、変わったのは試算の結果の値(v2.8 → v2.9)だけ
  • 見込める効果:Unity が回せる組み合わせが、混み具合3通りに加えてスタッフの人数3通りになり、試算の表の見方(人数を変えて何人で回るかを見る)と同じ切り口で比べられる
--- _前の版_08db8a0d067c_v29/export_params_before_staff_20261030.py	2026-10-30 02:22:48.911808481 +0000
+++ export_params.py	2026-10-30 02:22:48.930820661 +0000
@@ -6,7 +6,7 @@
 使い方: python3 export_params.py results.json <出力先>   (出力先は p2m-site の public/sim/serving-robot-params.json)
 - common:店に共通の値。role が "unity_measures" の値は Unity で測ってサイトに戻すための仮の値なので、Unity は読まない
 - stores:Unity で回す店(fam_p1・fam_p5・fam_p2・fam_p3・fam_p4・fam_p0)の値。registers はレジの数(v2.8)
-- unity_conditions:Unity で回す条件(0〜17番は店ごとに混み具合が普通の3つ。18〜53番は2026-10-29に足した空いている・混んでいるの各3つ。計54条件)と、同じ条件の試算の結果(40回の平均)。Unity の店はレジの内側に店員が1人だけなので、レジは1つ
+- unity_conditions:Unity で回す条件(0〜17番は店ごとに混み具合が普通の3つ。18〜53番は2026-10-29に足した空いている・混んでいるの各3つ。54〜89番は2026-10-30に足したスタッフが少ない・多いの各3つ(混み具合は普通)。計90条件)と、同じ条件の試算の結果(40回の平均)。Unity の店はレジの内側に店員が1人だけなので、レジは1つ
 """
 import json
 import sys
@@ -59,6 +59,18 @@
         for robots in (0, 1, 2):
             c = {"store": k, "staff": staff, "robots": robots, "arrivals_per_h": arr, "mode": "serve",
                  "trays": 3 if robots else 1, "fragile_by": "any", "registers": UNITY_REGISTERS}
+            r = rows[h.key(c)]
+            conds.append({"condition": c, "site_result": {m: r[m] for m in r if m not in c}})
+
+# 2026-10-30 エイイチと決めた:スタッフの人数を「少ない・多い」にも広げて夜に回す(段階4の中で1回)。
+# 前の54条件(0〜53番)は並びも中身も変えず、後ろに足す(店ごとに 少ない0・1・2台 → 多い0・1・2台。混み具合は普通)
+for k in UNITY_STORES:
+    s = h.STORES[k]
+    arr = s["arrivals"][1]          # 普通のまま
+    for staff in (s["staff"][0], s["staff"][2]):   # 少ない・多い
+        for robots in (0, 1, 2):
+            c = {"store": k, "staff": staff, "robots": robots, "arrivals_per_h": arr, "mode": "serve",
+                 "trays": 3 if robots else 1, "fragile_by": "any", "registers": UNITY_REGISTERS}
             r = rows[h.key(c)]
             conds.append({"condition": c, "site_result": {m: r[m] for m in r if m not in c}})
 

書き出したファイルを、前の54件のファイルと比べました。

$ python3 export_params.py results.json params_staff.json
54 90 first54 same True {'meta': True, 'common': True, 'unity_rules': True, 'stores': True}
fam_p1 staff 3 r 0 arr 42 entry 10.64 staffwait 9.86 dish 0.88
fam_p1 staff 3 r 1 arr 42 entry 5.62 staffwait 4.37 dish 2.14
fam_p1 staff 3 r 2 arr 42 entry 4.0 staffwait 3.09 dish 0.92
fam_p1 staff 5 r 0 arr 42 entry 1.04 staffwait 0.31 dish 0.27
fam_p1 staff 5 r 1 arr 42 entry 1.08 staffwait 0.3 dish 1.87
fam_p1 staff 5 r 2 arr 42 entry 0.92 staffwait 0.27 dish 0.96
fam_p5 staff 3 r 0 arr 42 entry 12.02 staffwait 11.16 dish 0.9
fam_p5 staff 3 r 1 arr 42 entry 6.8 staffwait 5.23 dish 2.2
fam_p5 staff 3 r 2 arr 42 entry 4.15 staffwait 2.96 dish 0.94
fam_p5 staff 5 r 0 arr 42 entry 1.44 staffwait 0.44 dish 0.27
fam_p5 staff 5 r 1 arr 42 entry 1.67 staffwait 0.39 dish 1.91
fam_p5 staff 5 r 2 arr 42 entry 1.34 staffwait 0.33 dish 0.99
fam_p2 staff 1 r 0 arr 28 entry 52.32 staffwait 45.62 dish 4.5
fam_p2 staff 1 r 1 arr 28 entry 33.09 staffwait 31.01 dish 1.0
fam_p2 staff 1 r 2 arr 28 entry 34.94 staffwait 32.03 dish 0.41
fam_p2 staff 3 r 0 arr 28 entry 2.41 staffwait 1.25 dish 0.47
fam_p2 staff 3 r 1 arr 28 entry 1.28 staffwait 0.5 dish 1.33
fam_p2 staff 3 r 2 arr 28 entry 1.04 staffwait 0.37 dish 0.5
fam_p3 staff 3 r 0 arr 46 entry 19.17 staffwait 17.37 dish 1.12
fam_p3 staff 3 r 1 arr 46 entry 11.09 staffwait 8.82 dish 2.32
fam_p3 staff 3 r 2 arr 46 entry 8.61 staffwait 6.48 dish 1.09
fam_p3 staff 5 r 0 arr 46 entry 2.34 staffwait 0.8 dish 0.41
fam_p3 staff 5 r 1 arr 46 entry 2.58 staffwait 0.56 dish 2.05
fam_p3 staff 5 r 2 arr 46 entry 2.54 staffwait 0.48 dish 1.24
fam_p4 staff 2 r 0 arr 38 entry 29.82 staffwait 27.37 dish 1.72
fam_p4 staff 2 r 1 arr 38 entry 15.29 staffwait 13.96 dish 1.85
fam_p4 staff 2 r 2 arr 38 entry 14.48 staffwait 13.17 dish 0.7
fam_p4 staff 4 r 0 arr 38 entry 1.64 staffwait 1.04 dish 0.51
fam_p4 staff 4 r 1 arr 38 entry 1.33 staffwait 0.42 dish 1.84
fam_p4 staff 4 r 2 arr 38 entry 0.71 staffwait 0.26 dish 0.87
fam_p0 staff 1 r 0 arr 19 entry 24.45 staffwait 21.48 dish 2.06
fam_p0 staff 1 r 1 arr 19 entry 13.55 staffwait 11.76 dish 0.78
fam_p0 staff 1 r 2 arr 19 entry 13.45 staffwait 11.96 dish 0.37
fam_p0 staff 3 r 0 arr 19 entry 1.02 staffwait 0.31 dish 0.23
fam_p0 staff 3 r 1 arr 19 entry 1.24 staffwait 0.3 dish 0.92
fam_p0 staff 3 r 2 arr 19 entry 0.99 staffwait 0.24 dish 0.41

試算 v2.8 のときの写し(54件)と v2.9 の54件を比べると、違いは meta と common の model の名前(v2.8 → v2.9)と、試算の結果の値だけでした。v2.9 は、会計のスタッフをお客さんがレジに並んだ順に付ける形に試算を直した版です(LOG405)。Unity の会計の頼み方に試算を合わせた版なので、Unity の動きは変えません。

54 90
meta ['model']
common ['model']
conds same condition keys True
v2.8 v2.9
会計はレジ1つにつき1組ずつ(pay_s 秒)。食べ終えた組は卓に座ったまま順番を待つ。Unity の条件はすべてレジ1つ(v2.8)
会計はレジ1つにつき1組ずつ(pay_s 秒)。食べ終えた組は卓に座ったまま順番を待つ。Unity の条件はすべてレジ1つ(v2.8)

前の54件のファイルは simulation/v2_9/_前の版_08db8a0d067c_v29/ に控え、新しいファイルを p2m-site に入れて公開しました(commit d73b98d)。公開されたファイルが90件・v2.9 になっていることを確かめてから、依頼を出しました。台帳は回す前に KIOXIA の archive/unity_ledger_v1_prenight_20261107_staff/ に控えました(1032行・md5 52e433713d4f7d41dfa8ccfb889492cd)。

依頼32「スタッフの人数を広げて夜を回す」の全文

依頼箱の「確認待ち」に置き、11:41に「新規」へ移して始めました。

02_sec03.md4,242字・70行・markdown
---
作業フォルダ: /Users/shimazueiichi/Robotics_UX/ServiceSim
上限時間: 720
---
# 依頼:試算 v2.9 の条件を写し、6店のスタッフの人数を「少ない・多い」に広げて、夜(360回)を回す(2026-10-30)

エイイチと決めたこと:今までの Unity は、6店ともスタッフが「真ん中の人数」だけだった。サイトの試算はスタッフの人数を3通り選べるので、Unity でも少ない・多いを回す(段階4の中で1回。最終的にはどの人数でも回せるようにするための準備)。混み具合は普通。
別のタブで開く保存

依頼32 の結果(依頼箱の報告)

11:41 に始まり、19:12 に終わりました(451分)。360回とも最後まで回り、失敗は0です。

# 結果:20261030_32_スタッフの人数を広げて夜を回す

- 状態:完了
- 始めた時刻:2026-10-30 11:41
- 終わった時刻:2026-10-30 19:12(451分)
- 作業フォルダ:/Users/shimazueiichi/Robotics_UX/ServiceSim
- この続きを Claude Code で話すとき:作業フォルダで `claude --resume cdce3e4b-34ea-4913-8439-d5393057b561`

## Claude Code の報告

**やったこと**
- 始める前の確かめを5つ行い、5つとも合格でした(エディタは起動していない・鍵ファイルなし・台帳 1032 行で md5 は退避と同じ・保留 0・サイトは 90 件で v2.9・08db8a0d067c)。
- サイトの params を cp でそのまま写しました(cs:46)。前の写しと JSON で比べると、変わっていたのは次の3つだけでした。
  - meta と common の model(v2.8 → v2.9)
  - 0〜53 番の site_result の値(条件は同じ)
  - 54〜89 番の追加
- 実行ファイルを作り直しました。最初に作ったもの(cs:46)は、「試算の版 v2.8 を待っている」という確かめで、回し始める前に止まりました。依頼にはない変更ですが、SimParams.asset の expectModel を v2.8 → v2.9 に書き換えました(1行だけ・cs:47・コードには触っていません)。作り直した build_id は cs47_20261030_1147 です。実行ファイルが 90 件の条件を知っていることを確かめました。
- make_queue.py に `--staff few,many` を足しました(cs:48)。真ん中でない回の名札は `night_st{人数}` です。
  - 前の make_queue.py +前の params と、新しい方どうしで並びを作って比べました。何も付けない 60 回、--stores だけ各 30 回、--crowd、--conditions のどれも同じでした。違うのは params の場所と版の名前の欄だけです。
  - 足さずに古い make_queue.py で新しい params を使うと、60 回が 180 回に、30 回が 90 回に増えていました。
- 夜 20261107(360 回)を、スタッフが少ない 180 回 → 多い 180 回の順で回しました。そのあと queue_staff.json を消しました。

**結果**
- 11:48:48〜19:01:50(7時間13分)でした。
  - completed 360、失敗・止まった回・やり直しは 0 です。
  - 全員が帰らなかった回(打ち切られた回)は 0 です。スタッフ1人の回も、全部で全員が帰りました。
  - 最長の実時間は 9.3 分(④スタッフ1人・2台・種6)、平均は 4.7 分です。
- 台帳は 1032 → 1392 行になりました。先頭 1032 行の md5 は退避と同じです。
- スタッフが少ないと、どの店でも入店待ちが試算より長くなりました。
  - ④1人・2台:70.97 分(試算 34.94)
  - ③3人・2台:13.07 分(試算 4.00)
- スタッフが多いと、③⑦⑤では会計待ちが試算より長くなりました。
  - ⑤5人・0台:12.75 分(試算 10.00)
- ロボットを1台増やしたときの向きが、種の過半数で試算と逆になったのは6か所です。
  - ④少ない:1→2台の料理の待ち(10 種中 7)
  - ④多い:0→1台の入店待ち(10 種中 10)
  - ④多い:1→2台の入店待ち(10 種中 10)
  - ④多い:1→2台の料理の待ち(10 種中 8)
  - ⑤少ない:1→2台の入店待ち(10 種中 6)
  - ②少ない:1→2台の料理の待ち(10 種中 6)
- スタッフ1人の回(④②)では、どの台数でも片付けがいちばん長くなりました。
  - 例:④0台は片付け 108 分>会計 84 分>配膳 68 分
  - 2番目は会計です。ただし④2台だけは案内でした。
- judge.md はできました。360 回とも「基準なし」です。
- 気づいたこと(原因は調べていません):17:31〜17:42 の 11 分間、runner_log に何も書かれていません。その時間に回っていた1回(⑤5人・2台・種5)は、runs.csv の実時間が 6.3 分なのに、台帳の wall_seconds は 17.5 分になっています。結果は completed で、全員が帰りました。
- 表(店 × スタッフの人数 × 台数ごとに、種10個の中央値[最小〜最大]・試算 v2.9・普通の夜の中央値)と、向きの内訳は報告ファイルの7章にあります。

**変えたファイル**
- `Assets/ServiceSim/Experiment/serving-robot-params.json`(cs:46)
- `Assets/ServiceSim/Experiment/SimParams.asset` の expectModel 1行(cs:47)
- `tools/make_queue.py`(cs:48。--staff の追加だけ)
- `unity/site_copy_08db8a0d067c_d73b98d/`(新しい控え)
- `claude_code_reports/20261107_night_staff.txt` と `20261107_night_staff_files/`(集計に使ったもの・表・差分)
- 台帳には 360 行を追記しました。記録は `night/20261107_staff/` にあります。

**決めてほしいこと**
- 試算の版の確かめ(expectModel)を v2.9 に書き換えてよかったか。依頼では「Unity は変えない・v2.9 で回す」と決まっていたので書き換えましたが、もともとは版が違えば回さないための確かめです。よくなければ、cs:47 を戻して、今夜の 360 回をどう扱うかを決めてください。
- SimParams.asset の fetchedAt と sourceUrl の欄は、前から古いまま(2026-10-25・site_copy_v28)です。今回は変えていません。直すかどうかを決めてください。

Claude Code の報告 20261107_night_staff.txt の全文

03_sec03.txt47,677字・712行・text
====================================================================
6店のスタッフの人数を「少ない・多い」に広げた夜 360 回
依頼:20261030_32_スタッフの人数を広げて夜を回す.md
夜の名前 20261107 記録 night/20261107_staff/ 回した日時 2026-10-30 11:48〜19:01
build_id cs47_20261030_1147(ServiceSim/cs:47・保留 0) make_queue.py の追加は cs:48
====================================================================

結論
別のタブで開く保存

ロボット0台なら、スタッフの人数を変えても Unity と試算の入店待ちはそろった

入店待ち(店に来てから案内されて席に着くまで・分)を、ロボット0台で並べました。Unity は種10個の中央値、試算は v2.9 の40回の平均です。真ん中の人数の Unity は、前の夜(混み具合は普通)の中央値です。

店少ない:Unity/試算真ん中:Unity/試算多い:Unity/試算
③(3・4・5人)14.8/10.64.1/4.11.9/1.0
⑦(3・4・5人)14.9/12.04.5/3.11.9/1.4
④(1・2・3人)60.0/52.313.0/10.92.9/2.4
⑤(3・4・5人)21.6/19.28.1/7.52.3/2.3
⑥(2・3・4人)29.7/29.89.6/7.92.2/1.6
②(1・2・3人)26.0/24.52.0/2.61.4/1.0

人数が1人減ると入店待ちが何倍にもなる向きも、その大きさも、2つの道具でほぼそろっています。スタッフ1人の④②も、全員が帰るまで回り切りました。人だけで回す店の動きは、試算でも3Dの店でも同じように出ています。

スタッフが少ない店では、ロボットを足しても入店待ちが試算ほど縮まない

同じスタッフが少ない条件で、ロボット0台から2台にしたときに入店待ちが何分縮んだかを並べました。

店(スタッフ)Unity 0台 → 2台試算 0台 → 2台
Unity で縮んだ分 ÷ 試算で縮んだ分
③(3人)
14.8 → 13.1(−1.7)
10.6 → 4.0(−6.6)26%
⑦(3人)
14.9 → 10.2(−4.7)
12.0 → 4.2(−7.9)59%
④(1人)
60.0 → 71.0(+11.0)
52.3 → 34.9(−17.4)
延びた
⑤(3人)
21.6 → 17.5(−4.2)
19.2 → 8.6(−10.6)
39%
⑥(2人)
29.7 → 19.0(−10.6)
29.8 → 14.5(−15.3)
69%
②(1人)
26.0 → 24.0(−2.0)
24.5 → 13.5(−11.0)
18%

試算は、人が足りない店ほどロボットで大きく縮む計算です。Unity も縮む向きは同じ店が多いものの、縮み方は試算の2〜7割でした。報告の「向き」の表では、ロボットを1台増やしたときの入店待ちが種の過半数で試算と逆になったのは、⑤少ない 1→2台(10種中6)と、下の④多いの2か所でした。

スタッフが多い店は、ロボットがいなくても入店待ちが1〜3分台で、ロボットを足しても大きくは変わりません。ここは試算も同じです。

④は、スタッフを3人にしても、ロボットを足すと入店待ちが延びた

④(3人)0台1台2台
Unity 入店待ち2.95.212.7
試算 入店待ち2.41.31.0
Unity 席は空いているのに待った
1.33.39.8
Unity 料理の待ち0.82.33.7

0→1台・1→2台とも、種10個すべてで試算と逆の向きでした。④は狭い店で、前の夜でも、ロボットが動けなくなる割合が6店でいちばん多い店です(上の作業ログで2台 0.53〜0.57)。動けなくなったロボットを人が動かしに行くあいだ、片付けと案内が止まり、席は空いているのに待つ時間が延びます。

スタッフ1人の店では、片付けにいちばん長く時間を使っていた

スタッフ1人の④②で、スタッフが何をしていた時間が長いかを、Unity の記録(60秒ごとの仕事ごとの人数)から足しました(分・種10個の中央値)。

待機案内配膳呼び出し会計片付けロボット救出
④ 0台11626820841080
④ 1台1676324881183
④ 2台10945268712524
② 0台2038451658760
② 1台313921460752
② 2台304121560808

ロボットを入れると配膳の時間はほぼなくなります(④ 68分 → 3〜5分)。ところが④では、そのぶん案内(62 → 94分)と片付け(108 → 125分)が延び、ロボット救出が24分入りました。空いた手が、そのまま次のお客さんの案内に回っていません。

Claude Code が書き換えた試算の版の確かめ(expectModel)は、そのまま使う

Unity には、読んだ条件のファイルの試算の版が、待っている版と違えば回し始めない確かめがあります。Claude Code はこれを v2.8 から v2.9 に1行書き換えてから回しました(cs:47)。v2.9 の条件で回すと決めて出した依頼なので、そのまま使います。写した日と写し元の欄(fetchedAt・sourceUrl)が前回の値のままになっている件は、次に Unity へ依頼するときに一緒に直してもらいます。

17:31〜17:42 の11分間、実行の記録が止まっていて、その間の1回は台帳の実時間が17.5分(ふだんは6.3分)になっていました。その回も最後まで回り、全員が帰っています。原因は調べていません。

作業ログ ― 混み具合とスタッフの人数を広げたところで区切る

ここまでで、Unity は6店で、混み具合3通りとスタッフの人数3通りを、ロボット0・1・2台で回せるようになりました。台帳は1392行です。

ずれ今の扱い
待機場所のまわりの詰まり(6店すべて)
原因は1台分の幅0.8m。直すなら0.95m(⑤だけ試算を計算し直す)。今は記録だけ
ロボットが動けなくなる割合(試算 0.01、Unity 0.02〜0.57)
試算は0.01のまま。今は記録だけ
スタッフが少ない店で、ロボットを足しても試算ほど縮まない
縮み方は試算の2〜7割。④は延びる
混んでいるとき、Unity のほうが入店待ちが長い
片付け1回の長さの差(135秒・123秒)として記録(LOG405)

ロボットのいない店は、混み具合を変えても人数を変えても、試算と3Dの店がほぼ同じ答えを出すようになりました。残っているずれは、どれもロボットが人と同じ通路にいることから出ています。次は、スタッフが少ない店でロボットの効き目が縮む分を、片付け・案内の延びとロボット救出の時間に分けて数えます。そのあと、比較動画の素材のストックを録画し、最初の比較動画を作りました(下の2つの作業ログ)。

作業ログ ― 比較動画の素材を、6店 × 11条件の66本、夜に録画した

データと文章だけでは伝わりにくいので、比較動画を載せていく

ここまでの記録は、表と数字が中心でした。ロボットを足しても入店待ちが縮まらない様子は、数字だけでは伝わりにくいと考え、Unity の店を録画した比較動画を記事に載せていくことにしました。

比較動画は、使いたくなったときにすぐ作れるよう、素材を先にいろいろなパターンで撮って保管しておきます。録画は Mac で Unity を描くので、昼のほかの作業と重ならないよう、夜に回します。

  • ねらい:記事やLPで比べたい組み合わせが出たとき、その日のうちに比較動画を作れるようにする
  • 変わったこと:6店 × 11条件の録画が手元にそろい、同じ店の11本は同じお客さんの来方(同じ種)で撮ってある
  • 見込める効果:比較動画1本を、録画の待ちなしで作れる(④の0台・2台の1本は、録画を選んでから約6分で書き出せた)
撮った条件(店ごと)ロボット
普通の混み具合・真ん中の人数0・1・2台
スタッフが少ない(混み具合は普通)
0・2台
混んでいる(真ん中の人数)0・2台
スタッフが多い(混み具合は普通)
0・2台
空いている(真ん中の人数)0・2台

依頼38「比較動画の素材をストックする」の全文

依頼箱の番号は、はじめ35にしていましたが、ロボットアームの依頼が35〜37を使っていたので、38に付け直しました。21時に「新規」へ移し、21:02 に始まりました。

04_sec05.md3,091字・77行・markdown
---
作業フォルダ: /Users/shimazueiichi/Robotics_UX/ServiceSim
上限時間: 720
---
# 依頼:比較動画の素材のストックを録画する(6店 × 11条件。夜・2026-10-30)

エイイチと決めたこと:データとテキストだけでは分かりにくいので、比較動画を記事に載せていく。比較動画は、いざというときにすぐ作れるよう、素材をいろいろなパターンで先に撮って保管しておく。録画は昼の作業と重ならないよう、夜に回す。
別のタブで開く保存

Claude Code の報告 20261030_rec_stock.txt の全文

録画は 21:11 に始まり、翌朝 06:11 に66本とも終わりました(2本ずつ同時・9.0時間)。依頼箱の結果のファイルは、記録を書いた時点ではまだ出ていなかったので、報告ファイルだけを載せます。

05_sec05.txt49,508字・499行・text
====================================================================
比較動画の素材のストックを録画した(6店 × 11条件 × 種1つ = 66 回)
依頼:20261030_38_比較動画の素材をストックする.md
2026-10-30 21:11 〜 10-31 06:11 check-in:cs:49(録画のしくみだけ) 最後の状態:cs:49 head・保留 0
====================================================================

結論
別のタブで開く保存

録画した回の数字は、画面なしで回した回とロボットがいると合わない

録画した回は、画面なしで回した同じ条件・同じ種の回と、数字が完全には合いませんでした。ロボット0台なら入店待ちの差は中央値0.09分ですが、ロボットがいる回では最大8.72分ずれます(今の店の形の版で比べた26回)。前の録画(LP 用に②を撮った依頼26)でも、画面ありの回は途中から画面なしの回とずれていました。原因は調べていません。

報告の「決めてほしいこと」は、次のように決めました。

決めること決めたこと
種が2つ同じ近さで並んだときの選び方
番号の小さい方のまま(③⑦ 種7・④⑤⑥ 種1・② 種8)。どちらも真ん中に近いので撮り直さない
動画に添える数字
その録画の回の数字(rec_index.csv)を使う。動画の中の動きと数字が一致するため
普通・真ん中の人数の画面なしの回を、今の店の形の版で回し直すか
今は回し直さない。動画には録画の回の数字を使うので、比べる相手として急がない
試しの回のフォルダ・録画用の実行ファイルなどの残り
消さずに残す(撮り直すときに使う)

作業ログ ― 録画のストックから、④のロボット0台と2台を並べた比較動画を作った

④ 効率重視・狭い店、スタッフ3人で、ロボット0台と2台を左右に並べた

最初の1本は、④の狭い店をスタッフ3人で回した回にしました。初めは③のスタッフ3人で作る予定でしたが、撮った③の回(種7)では、ロボット2台で入店待ちが15.1分から6.3分に大きく縮んでいました。種10個の中央値(14.8分 → 13.1分)とは違う向きに見えるので、この記事で分かったこと(ロボットを足すと入店待ちが延びる店がある)がはっきり出る④を選びました。

④ スタッフ3人・種1(録画の回)
ロボット0台ロボット2台
入店待ち(平均)4.0分12.6分
ロボットが動けなくなった0回56回
回の長さ(店の時刻)約2時間49分約3時間13分

2台の店では、白いロボットが通路で止まり、青いスタッフがそこへ向かう場面が繰り返し出ます。そのあいだ、上の帯の入店待ちの組と片付け待ちの卓の数が、0台の店より多いまま続きます。店の時刻2:05の時点で、0台の店は入店待ちの組0・片付け待ちの卓0、2台の店は入店待ちの組3・片付け待ちの卓5・動けなくなった累計33回でした。

比較動画を作る make_compare.py

録画のストックの2本を選び、半分の大きさにして左右に並べ、上下に帯を付けて書き出します。上の帯の数字は、その回の timeline.csv(60秒ごと)と stops.csv(動けなくなった時刻)から1分ごとに出し、録画の _frames.csv で店の時刻と動画の時刻を合わせます。録画は0.5秒ごと・30コマ/秒(15倍速)なので、1コマおきに使って30倍速にしました。字幕の形式(ASS)で文字を重ねています。

06_make_compare.py.txt4,038字・64行・python
"""サービスロボット導入シミュレーションの録画ストック(night/20261030_rec)から、2本を左右に並べた比較動画を作る(2026-10-31)
使い方: python3 make_compare.py <左の回の名前> <右の回の名前> <左の見出し> <右の見出し> <上の説明> <出力の mp4> [倍速 既定2]
- 録画は 0.5 秒ごと・30 コマ/秒(15 倍速)。倍速 2 なら 1 コマおきに使い 30 倍速
- 上に、店の時刻・入店を待っている組・片付け待ちの卓・動けなくなった(累計)を1分ごとに出す(timeline.csv と stops.csv から)
- 最後に、その回の入店待ちの平均(rec_index.csv)を出す
"""
import csv, sys, os, subprocess, bisect
U = os.environ.get('SIM_UNITY', '/Volumes/KIOXIA/r2r2r-data/service_sim/unity')
別のタブで開く保存

比較動画を書き出したコマンドと、できた動画の確かめ

$ export SIM_UNITY=/mnt/user-data/uploads/KIOXIA/r2r2r-data/service_sim/unity
$ python3 make_compare.py 20261030_rec_st3_cs49_fam_p2_r0_s1_passon_sbret 20261030_rec_st3_cs49_fam_p2_r2_s1_passon_sbret "ロボット0台" "ロボット2台" "④ 効率重視・狭い店 スタッフ3人・1時間28組 同じお客さんの来方(種1)で並べた Unity の3Dシミュレーション 30倍速" "$W/p2_st3_r0_vs_r2.mp4" 2
ffmpeg -y -hide_banner -loglevel error -i ...
/tmp/claude-0/-home-claude/6b82a170-2fde-52f3-8d47-7b6b5e561574/scratchpad/cmp/p2_st3_r0_vs_r2.mp4 390.8秒 13106632

$ ffprobe -v error -show_entries format=duration,size:stream=codec_name,width,height,nb_frames,r_frame_rate -of compact p2_st3_r0_vs_r2.mp4
stream|codec_name=h264|width=2560|height=1100|r_frame_rate=30/1|nb_frames=11724
format|duration=390.800000|size=13106632

書き出した動画から、真ん中(250秒)と終わりの手前(385秒)のコマを取り出して、帯の数字・色の見本・左右の区切りが出ていることを見ました。

Mac の作業環境では書き出せず、クラウドの作業環境で作った

はじめは Mac 連携の作業環境で書き出そうとしましたが、2つのことで止まりました。

  • 字幕を重ねる部品(libass)が、KIOXIA の上に置いた字幕のファイルを読めなかった(Could not create a libass track when reading file)。字幕のファイルを /tmp に書いてから読む形に直した
  • 書き出しには数分かかるが、Mac 連携の作業環境は1回の呼び出しが終わると、後ろで動かしていた ffmpeg も止まってしまう

そこで、録画2本と timeline.csv・stops.csv・_frames.csv・rec_index.csv をクラウドの作業環境に写して書き出し、できた動画を KIOXIA の videos/service_sim_compare/ に戻しました。作り方のスクリプトは videos/_tools/make_compare.py に置き、店の録画の場所は環境変数 SIM_UNITY で切り替えられるようにしました(何も指定しなければ /Volumes/KIOXIA/r2r2r-data/service_sim/unity)。