---
title: "サービスロボット導入シミュレーション Log#20 | 38卓の店でも、詰まるのは片付けだった"
slug: standby-jam-every-store
date: 2026-10-28
day: 398
meta_description: "38卓のファミレスをUnityの3Dで再現しても、配膳ロボットを足すと入店待ちは縮んだが、詰まるのはレジではなく片付けと案内だった。建てる前に図面を直し、30回回して確かめた。"
tags: [サービスロボット導入シミュレーション, 配膳ロボット, 飲食店, Unity, シミュレーション, 店の形, 店舗レイアウト]
series: "導入シミュレーション"
---

38卓・158席のファミレスを Unity の3Dで再現したシミュレーションでも、配膳ロボットを足すと入店待ちは縮みましたが、詰まるのは会計のレジではなく、片付けと案内でした。ロボットは0台・1台・2台で走らせました。お客さんが店に来てから席に着くまでの待ちは、9.6分、6.0分、4.8分と縮みました。ロボットを足すと待ちが縮むという向きは、サイトに公開している試算と同じです。ところが、店が詰まる場所は今回も試算と違いました。試算ではレジの順番待ちで詰まり、3Dの店では「席は空いているのに、片付けと案内が追いつかずに待つ」ところで詰まりました。

配膳ロボットを入れると店がどう変わるかを、2つのやり方で調べています。1つはサイトの試算で、卓までの距離・料理の数・スタッフの人数から待ち時間を計算します。もう1つは Unity（ゲームを作る道具）で作った3Dのファミレスで、お客さん・スタッフ・ロボットを1人ずつ動かすシミュレーションです。どちらも、コードで作った同じ「店の形」（卓・壁・通路の配置のデータ）を読みます。実在の店ではなく、仮の店です。

今回の店（⑥ゆったり型・入口が左の壁）は、3Dの店に建てる前に図面を見て、2か所を直しました。1つはドリンクバーです。右側の卓へ行くロボットの道が、お客さんがドリンクを取るために立つ所を通っていたので、ドリンクバーを左へ0.8m寄せ、右の端に2.0mの抜け道を作りました。もう1つはレジです。レジの向きは縦のまま右へ0.8m、下の壁まで寄せ、入口の前に場所を空けました。直したあとの30回では、ロボット2台のときにロボットが30秒以上止まった122回のうち、ドリンクバーのまわりは5回、入口・レジのまわりは1回でした。

残った詰まりのうちいちばん多かったのは、ロボットの待機場所の中の60回で、ほぼ全部がロボットどうしです。**待機場所が受け渡し口のすぐ横にある形は、これまでの5店すべてに共通していて、どの店でも同じ所で詰まっています。** 店ごとに図面を直しても消えない詰まりなので、店の形の決め方の問題として記録しました。

| やったこと | なぜ | 結果 |
|---|---|---|
| ⑥の図面にロボットの道を重ねて見た | ⑤では建てたあとに図面の問題に気づき、2回建て直したため | 気になる所を3つ挙げた |
| ドリンクバーを左へ0.8m寄せた | 右側の卓への道が、ドリンクを取る人の立つ所を通っていた | 右の端の抜け道 1.2m→2.0m |
| レジを縦のまま右へ0.8m・下の壁まで寄せた | 入口の前に、案内を待つ人の場所を空けるため | レジの上と左の島の間 2.0m→2.7m |
| ⑥の試算だけ計算し直した | 卓までの距離が4卓で変わったため | 数字の動きは乱数のぶれの大きさ |
| 3Dの店に⑥を建てて1回ずつ回した | 建てられるか、止まる所はどこかを見るため | 建て直しなしで建った |
| ⑥を種10個ずつ、計30回回した | 1回ずつでは来る客の数がぶれるため | 入れた組の数は試算とそろった |

3Dの店で回した結果は次のとおりです（種10個の中央値。右は試算）。

| | ロボット0台 | 1台 | 2台 |
|---|---|---|---|
| 入店待ち（分） | 9.6 / 7.0 | 6.0 / 3.1 | 4.8 / 1.9 |
| そのうち、席は空いているのに待った（分） | 8.4 / 4.2 | 4.8 / 1.0 | 3.5 / 0.6 |
| 会計待ち（分） | 3.0 / 12.1 | 3.0 / 8.5 | 3.3 / 7.0 |
| 料理の待ち（分） | 1.1 / 0.8 | 2.1 / 2.0 | 1.3 / 0.8 |
| ロボットが運んだ皿の割合 | − | 0.83 / 0.80 | 0.96 / 1.00 |

試算では、食べ終えたお客さんはレジの順番が来るまで卓に座ったまま待ちます。レジが1つだと会計待ちが7〜12分に延び、その分だけ卓が空かず、次のお客さんが待ちます。3Dの店では会計待ちは3分前後でレジは詰まらず、代わりに「席は空いているのに待った」時間が試算の2〜6倍になりました。3Dの店のスタッフは、片付け・案内・会計・料理運びを1人ずつ順番にこなし、店の中を歩きます。その分、片付けが後回しになっています。⑤（46卓）でも③⑦（42卓）でも同じ形のずれが出ていて、店の大きさが変わっても、試算＝レジ、3Dの店＝片付けと案内、という分かれ方は変わりませんでした。

この記事の後半は作業ログです。図面を見て気になる所を挙げたところから、ドリンクバーとレジを動かす案の比べ、試算の計算し直し、3Dの店での1回ずつの確かめと30回の結果までを、コード・依頼文・報告・出力とあわせて載せます。

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

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


## 作業ログ ― ⑥の図面に、受け渡し口から38卓へのロボットの道を重ねた

⑤（46卓）では、3Dの店に建てて1回目の確かめを待っている間に図面の問題に気づき、店の形を直して2回建て直しました。⑥は、その前に図面を見ることにしました。

この記事で書く効果は、すべて3Dの店で測った値です。直す前の形では3Dの店を回していないので、前後の比べはありません。直したあとの値だけを書きます。

⑥は24m×17mの店で、卓38・席158です。受け渡し口（料理が出てくる所）はパントリーの下の端にあり、ロボットは受け渡し口の前の1点から38卓へ出ていきます。受け渡し口の右にロボット3台の待機場所があります。入口は左の壁で、入口の右にレジ、左下に待合があります。

【画像】LOG398_01_store6_routes_before.png 直す前の⑥の図面と、受け渡し口から38卓へのロボットの道。赤い枠は、図面から気になった3か所（ドリンクバーのまわり、受け渡し口と待機場所、入口・レジのすぐ上）

図面から気になったのは次の3か所です。

| 場所 | 図面で分かったこと |
|---|---|
| ①ドリンクバーのまわり | ドリンクバーの上は、お客さんがドリンクを取るために立つ・並ぶ所。右側の卓（T30・T38）へ行く道がそこを通る。下の通路は1.6mで、T27〜T29 にロボットが止まると上下に0.55mずつしか残らない |
| ②受け渡し口と待機場所 | 受け渡し口の前から待機場所まで1.2m。③⑦④と同じ形 |
| ③入口・レジのすぐ上 | 左の通路（T05〜T13）の入口が、入口とレジのすぐ上にある。38卓中7卓の道がここを通る（⑤は直したあとで46卓中10卓） |

⑤のときのような、受け渡し口から抜けられない隙間はありませんでした。左の2本の通路（1.5m・両側に席）は、⑤と同じ造りです。

### 図面とロボットの道を描いたスクリプト draw.py

```python
import json, sys, matplotlib; matplotlib.use('Agg')
import matplotlib.pyplot as plt, matplotlib.patches as P
from matplotlib import font_manager as fm
fp=fm.FontProperties(fname='/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc')
B='/home/claude/p2m-site/public/sim/layouts/'
L=json.load(open(B+'p4_relaxed_side.json'))['layout']
U=json.load(open(B+'unity/p4_relaxed_side.json'))
W,D=L['outer']['w'],L['outer']['d']
fig,ax=plt.subplots(figsize=(14,10.5))
ax.add_patch(P.Rectangle((0,0),W,D,fill=False,lw=2))
e=L['entrance']; ax.plot([0,0],[e['a0'],e['a1']],color='w',lw=4); ax.text(-0.3,(e['a0']+e['a1'])/2,'入口',fontproperties=fp,ha='right',va='center',fontsize=10)
for r in L['rooms']:
    x0,y0,x1,y1=r['rect']; ax.add_patch(P.Rectangle((x0,y0),x1-x0,y1-y0,fc={'back':'#e4e4e0','guest':'#f1e6d6','drink':'#d8e8f4'}[r['kind']],ec='#999'))
    ax.text((x0+x1)/2,(y0+y1)/2,r['label'],ha='center',va='center',fontproperties=fp,fontsize=8)
for w in L['walls']:
    x0,y0,x1,y1=w['rect']; ax.add_patch(P.Rectangle((x0,y0),x1-x0,y1-y0,fc='#555',ec='none'))
for k in U['robot_keepout']:
    x0,y0,x1,y1=k['rect']; ax.add_patch(P.Rectangle((x0,y0),x1-x0,y1-y0,fc='none',ec='#c33',ls=':',lw=1.2))
for t in L['tables']:
    ax.add_patch(P.Rectangle((t['x'],t['y']),t['w'],t['h'],fc='w',ec='k',lw=.8))
    ax.text(t['x']+t['w']/2,t['y']+t['h']/2,t['id'][1:]+'\n'+str(t['seats'])+'人',ha='center',va='center',fontsize=6,fontproperties=fp)
d=L['dish_up'][0]['rect']; ax.add_patch(P.Rectangle((d[0],d[1]),d[2]-d[0],d[3]-d[1],fc='#333'))
s=L['robot_standby']['rect']; ax.add_patch(P.Rectangle((s[0],s[1]),s[2]-s[0],s[3]-s[1],fc='none',ec='#2a5cc8',ls='--',lw=1.5)); ax.text((s[0]+s[2])/2,s[1]-0.25,'ロボット待機(3台)',ha='center',fontproperties=fp,fontsize=8,color='#2a5cc8')
st=L['start']; ax.plot(*st,'o',color='#2a5cc8',ms=7); ax.text(st[0],st[1]-0.45,'受け渡し口の前',ha='center',fontproperties=fp,fontsize=8)
if len(sys.argv)>1:
    for k,v in U['routes']['from_pass'].items():
        xs,ys=zip(*v['points']); ax.plot(xs,ys,color='#2a5cc8',alpha=.35,lw=1.2)
for p in U['places']:
    if p['kind'] in ('StaffStandby',): ax.plot(*p['point'],'s',color='#2a7a2a',ms=4)
ax.set_xlim(-1.5,W+.5); ax.set_ylim(-.8,D+.5); ax.set_aspect('equal')
ax.set_xticks(range(0,int(W)+1,1)); ax.set_yticks(range(0,int(D)+1,1)); ax.grid(alpha=.15); ax.tick_params(labelsize=7)
ax.set_title(L['name']+('（青線：受け渡し口から各卓へのロボットの道）' if len(sys.argv)>1 else ''),fontproperties=fp,fontsize=12)
plt.tight_layout(); plt.savefig(sys.argv[1] if len(sys.argv)>1 else 'p4_plan.png',dpi=120)
```

### 気になる所に印を付けたスクリプト draw_mark.py

draw.py の中身を読み込み、図の保存の直前に赤い枠と説明を足しています。

```python
import json, sys, matplotlib; matplotlib.use('Agg')
import matplotlib.pyplot as plt, matplotlib.patches as P
from matplotlib import font_manager as fm
fp=fm.FontProperties(fname='/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc')
B='/home/claude/p2m-site/public/sim/layouts/'
L=json.load(open(B+'p4_relaxed_side.json'))['layout']
U=json.load(open(B+'unity/p4_relaxed_side.json'))
W,D=L['outer']['w'],L['outer']['d']
fig,ax=plt.subplots(figsize=(14,10.5))
ax.add_patch(P.Rectangle((0,0),W,D,fill=False,lw=2))
e=L['entrance']; ax.plot([0,0],[e['a0'],e['a1']],color='w',lw=4); ax.text(-0.3,(e['a0']+e['a1'])/2,'入口',fontproperties=fp,ha='right',va='center',fontsize=10)
for r in L['rooms']:
    x0,y0,x1,y1=r['rect']; ax.add_patch(P.Rectangle((x0,y0),x1-x0,y1-y0,fc={'back':'#e4e4e0','guest':'#f1e6d6','drink':'#d8e8f4'}[r['kind']],ec='#999'))
    ax.text((x0+x1)/2,(y0+y1)/2,r['label'],ha='center',va='center',fontproperties=fp,fontsize=8)
for w in L['walls']:
    x0,y0,x1,y1=w['rect']; ax.add_patch(P.Rectangle((x0,y0),x1-x0,y1-y0,fc='#555',ec='none'))
for k in U['robot_keepout']:
    x0,y0,x1,y1=k['rect']; ax.add_patch(P.Rectangle((x0,y0),x1-x0,y1-y0,fc='none',ec='#c33',ls=':',lw=1.2))
for t in L['tables']:
    ax.add_patch(P.Rectangle((t['x'],t['y']),t['w'],t['h'],fc='w',ec='k',lw=.8))
    ax.text(t['x']+t['w']/2,t['y']+t['h']/2,t['id'][1:]+'\n'+str(t['seats'])+'人',ha='center',va='center',fontsize=6,fontproperties=fp)
d=L['dish_up'][0]['rect']; ax.add_patch(P.Rectangle((d[0],d[1]),d[2]-d[0],d[3]-d[1],fc='#333'))
s=L['robot_standby']['rect']; ax.add_patch(P.Rectangle((s[0],s[1]),s[2]-s[0],s[3]-s[1],fc='none',ec='#2a5cc8',ls='--',lw=1.5)); ax.text((s[0]+s[2])/2,s[1]-0.25,'ロボット待機(3台)',ha='center',fontproperties=fp,fontsize=8,color='#2a5cc8')
st=L['start']; ax.plot(*st,'o',color='#2a5cc8',ms=7); ax.text(st[0],st[1]-0.45,'受け渡し口の前',ha='center',fontproperties=fp,fontsize=8)
if len(sys.argv)>1:
    for k,v in U['routes']['from_pass'].items():
        xs,ys=zip(*v['points']); ax.plot(xs,ys,color='#2a5cc8',alpha=.35,lw=1.2)
for p in U['places']:
    if p['kind'] in ('StaffStandby',): ax.plot(*p['point'],'s',color='#2a7a2a',ms=4)
ax.set_xlim(-1.5,W+.5); ax.set_ylim(-.8,D+.5); ax.set_aspect('equal')
ax.set_xticks(range(0,int(W)+1,1)); ax.set_yticks(range(0,int(D)+1,1)); ax.grid(alpha=.15); ax.tick_params(labelsize=7)
ax.set_title(L['name']+('（青線：受け渡し口から各卓へのロボットの道）' if len(sys.argv)>1 else ''),fontproperties=fp,fontsize=12)

def mark(n,box,txt,tx,ty):
    x0,y0,x1,y1=box
    ax.add_patch(P.Rectangle((x0,y0),x1-x0,y1-y0,fc='#d33',alpha=.12,ec='#d33',lw=1.8))
    ax.text(tx,ty,n+' '+txt,fontproperties=fp,fontsize=9.5,color='#b00',ha='left',va='center',bbox=dict(fc='w',ec='#d33',lw=.8,pad=2))
mark('①',(15.6,1.8,20.4,5.65),'ドリンクバーの上下が両方ふさがりやすい\n上：客が立つ・並ぶ帯を T30・T38 への道が通る\n下：T27〜T29 に止まると左右0.55mしか残らない',13.2,-0.45)
mark('②',(13.3,5.3,17.9,6.5),'受け渡し口の前と待機場所が1.2m（③⑦④と同じ形）',15.0,7.0)
mark('③',(0.6,3.2,4.6,5.0),'左の通路(T05〜T13)の入口が、入口・レジのすぐ上\n38卓中7卓の道がここを通る（⑤は直した後で46卓中10卓）',0.2,3.75)
plt.tight_layout(); plt.savefig(sys.argv[1] if len(sys.argv)>1 else 'p4_plan.png',dpi=120)
```

### 気になる所を数えたスクリプト check.py

道が入口・レジのまわりとドリンクバーの上下を通るかを、道の線分を100に分けて調べました。

```python
import json
B='/home/claude/p2m-site/public/sim/layouts/'
U=json.load(open(B+'unity/p4_relaxed_side.json'))
fp=U['routes']['from_pass']
def through(pts,box):
    x0,y0,x1,y1=box
    for p,q in zip(pts,pts[1:]):
        for i in range(101):
            x=p[0]+(q[0]-p[0])*i/100; y=p[1]+(q[1]-p[1])*i/100
            if x0<=x<=x1 and y0<=y<=y1: return True
    return False
zones={'入口・レジのまわり(x<4.6,y<5.0)':(0,0,4.6,5.0),
 'ドリンクバーの上(客が立つ・並ぶ帯 x15.6-20.4,y4.1-5.65)':(15.6,4.1,20.4,5.65),
 'ドリンクバーの下(x15.6-20.4,y1.8-3.4)':(15.6,1.8,20.4,3.4)}
for n,b in zones.items():
    h=[k for k,v in fp.items() if through(v['points'],b)]
    print(n,len(h),h)
# 細い通路に止まったときの残り幅
T={t['id']:t for t in U['tables']}
for a in U['aisles']:
    x0,y0,x1,y1=a['rect']; ts=[k for k,t in T.items() if t.get('aisle')==a['id']]
    seats=sum(T[k]['seats'] for k in ts)
    xs=[T[k]['stop'][0] for k in ts]
    rem=[max(x0-0, (x1-(x+0.25)), ((x-0.25)-x0)) for x in xs]
    print(a['id'],'幅',a['width'],'卓',len(ts),'席',seats,'止まる位置x',sorted(set(xs)),'止まったときの横の残り(最大)',round(max(rem),2),'最小',round(min(rem),2))
# ドリンクバーの下で止まったとき
for k in ('T27','T28','T29'):
    y=T[k]['stop'][1]; print(k,'止まると上の残り',round(3.4-(y+0.25),2),'下の残り',round((y-0.25)-1.8,2))
print('出発点から待機場所の左端', 15.5-14.3)
tot=sum(t['seats'] for t in U['tables']); print('卓',len(T),'席',tot)
```

#### check.py の出力

```text
入口・レジのまわり(x<4.6,y<5.0) 7 ['T05', 'T06', 'T07', 'T08', 'T10', 'T11', 'T12']
ドリンクバーの上(客が立つ・並ぶ帯 x15.6-20.4,y4.1-5.65) 2 ['T30', 'T38']
ドリンクバーの下(x15.6-20.4,y1.8-3.4) 3 ['T27', 'T28', 'T29']
A01 幅 1.5 卓 10 席 42 止まる位置x [1.988, 2.212] 止まったときの横の残り(最大) 1.2 最小 1.2
A02 幅 1.5 卓 8 席 32 止まる位置x [6.038, 6.262] 止まったときの横の残り(最大) 5.25 最小 5.25
T27 止まると上の残り 0.56 下の残り 0.54
T28 止まると上の残り 0.56 下の残り 0.54
T29 止まると上の残り 0.56 下の残り 0.54
出発点から待機場所の左端 1.1999999999999993
卓 38 席 158
```

細い通路に止まったときの横の残りは、計算の式が間違っていました（`max(x0-0, ...)` で、通路の左の端の座標そのものを残りとして数えていた）。左右それぞれの残りを出し直したのが次のスクリプトです。

#### 細い通路の横の残りを出し直したスクリプト aisle_rem.py

```python
import json
U=json.load(open('/home/claude/p2m-site/public/sim/layouts/unity/p4_relaxed_side.json'))
T={t['id']:t for t in U['tables']}
for a in U['aisles']:
    x0,y0,x1,y1=a['rect']
    for x in sorted(set(T[k]['stop'][0] for k in T if T[k].get('aisle')==a['id'])):
        print(a['id'],x,'左の残り',round(x-0.25-x0,2),'右の残り',round(x1-x-0.25,2))
# ⑤ の同じ数字（比べ）
U5=json.load(open('/home/claude/p2m-site/public/sim/layouts/unity/p3_relaxed_center.json'))
T5={t['id']:t for t in U5['tables']}
for a in U5['aisles']:
    ts=[k for k in T5 if T5[k].get('aisle')==a['id']]; print('⑤',a['id'],a['width'],len(ts),'卓', a['rect'])
for a in json.load(open('/home/claude/p2m-site/public/sim/layouts/unity/p1_efficient_corner.json'))['aisles']: print('③',a['id'],a['width'],a['rect'])
```

#### aisle_rem.py の出力

```text
A01 1.988 左の残り 0.54 右の残り 0.46
A01 2.212 左の残り 0.76 右の残り 0.24
A02 6.038 左の残り 0.54 右の残り 0.46
A02 6.262 左の残り 0.76 右の残り 0.24
⑤ A01 1.5 11 卓 [1.2, 1.4, 2.7, 12.65]
⑤ A02 1.5 7 卓 [5.25, 3.4, 6.75, 12.65]
⑤ A03 1.5 13 卓 [23.3, 1.4, 24.8, 16.2]
③ A01 1.2 [1.2, 4.6, 2.4, 12.2]
③ A02 1.2 [4.9, 4.8, 6.1, 14.05]
③ A03 1.2 [8.6, 4.8, 9.8, 14.05]
```

左の2本の通路（A01・A02）は、ロボットが1台止まると、横の残りは0.24〜0.76mで、ほかのロボットは抜けられません。ただ、⑤の3本の通路も同じ1.5mで、⑤は夜の30回を最後まで回せています。⑥だけの問題ではないので、ここは直していません。


## 作業ログ ― ドリンクバーを左へ0.8m寄せ、右の端に抜け道を作った

図面を見て、まずドリンクバーを左へ寄せる直しから始めることにしました。寄せる幅を決めるために、店の形の作り方のコード（サイトの `famires_v3.py`）をそのまま使い、ドリンクバーだけを動かした形で、決まりの確かめと卓までの距離の計算を回しました。

### 寄せる幅を変えて、決まりと道を確かめたスクリプト try_dx.py

```python
"""⑥のドリンクバーを左へ dx m 寄せた形を、サイトの決まりと解析で確かめる（書き出しは作業用フォルダだけ）"""
import sys, json, copy
sys.path.insert(0, '/home/claude/p2m-site/tools/sim_layouts')
from pathlib import Path
import famires_v3 as F, famires_gen2 as G
def through(pts, box):
    x0,y0,x1,y1=box
    for p,q in zip(pts,pts[1:]):
        for i in range(101):
            x=p[0]+(q[0]-p[0])*i/100; y=p[1]+(q[1]-p[1])*i/100
            if x0<=x<=x1 and y0<=y<=y1: return True
    return False
for dx in [float(a) for a in sys.argv[1:]]:
    L = G.p4_relaxed_side()
    for r in L['rooms']:
        if r['kind']=='drink': r['rect']=[round(r['rect'][0]-dx,3),r['rect'][1],round(r['rect'][2]-dx,3),r['rect'][3]]; db=r['rect']
    L = F.prepare(L)
    v = F.check_rules(L)
    if v: print('dx',dx,'決まり違反',v); continue
    ctx={}; ra,ea=F.analyze(L,F.SETTINGS['res'],ctx); rb,eb=F.analyze(L,F.SETTINGS['check_res'])
    if ea or eb: print('dx',dx,'解析の違反',ea,eb); continue
    summ=F.summarize(L,ra)
    out=Path(f'out_dx{dx}'); out.mkdir(exist_ok=True)
    U=F.export_unity(L,ra,ctx,'p4',out,'try')
    vr=F.check_register_clear(U)
    fp=U['routes']['from_pass']
    x0,y0,x1,y1=db
    up=(x0-0.4,y1,x1+0.4,5.65); low=(x0-0.4,1.8,x1+0.4,y0)
    q=[p['point'] for p in U['places'] if p['kind']=='DrinkBarQueue']
    print(f"dx={dx} ドリンクバー {db} 卓までの距離 平均{summ['robot_dist_mean']} 最長{summ['robot_dist_max']} 分類 robot{summ['robot_tables']} narrow{summ['narrow_tables']} human{summ['human_tables']} レジ{vr}")
    print('   上の帯を通る', [k for k,r in fp.items() if through(r['points'],up)])
    print('   下を通る', [k for k,r in fp.items() if through(r['points'],low)])
    print('   右端と T38 の間', round(21.2-x1,2), ' 並ぶ点', q, ' 出発点(14.3,5.95)との距離', [round(((a-14.3)**2+(b-5.95)**2)**.5,2) for a,b in q])
    json.dump(dict(L=L), open(out/'layout.json','w'), ensure_ascii=False)
```

#### try_dx.py の出力

```text
$ python3 try_dx.py 0 0.8 1.2 1.6 2.0
dx=0.0 ドリンクバー [16.0, 3.4, 20.0, 4.1] 卓までの距離 平均10.7 最長18.38 分類 robot38 narrow0 human0 レジ[]
   上の帯を通る ['T30', 'T38']
   下を通る ['T27', 'T28', 'T29']
   右端と T38 の間 1.2  並ぶ点 [[18.0, 5.4]]  出発点(14.3,5.95)との距離 [3.74]
dx=0.8 ドリンクバー [15.2, 3.4, 19.2, 4.1] 卓までの距離 平均10.7 最長18.38 分類 robot38 narrow0 human0 レジ[]
   上の帯を通る ['T29', 'T30', 'T38']
   下を通る ['T26', 'T27', 'T28']
   右端と T38 の間 2.0  並ぶ点 []  出発点(14.3,5.95)との距離 []
dx=1.2 ドリンクバー [14.8, 3.4, 18.8, 4.1] 卓までの距離 平均10.7 最長18.38 分類 robot38 narrow0 human0 レジ[]
   上の帯を通る ['T29', 'T30', 'T38']
   下を通る ['T26', 'T27', 'T28']
   右端と T38 の間 2.4  並ぶ点 []  出発点(14.3,5.95)との距離 []
dx=1.6 ドリンクバー [14.4, 3.4, 18.4, 4.1] 卓までの距離 平均10.7 最長18.38 分類 robot38 narrow0 human0 レジ[]
   上の帯を通る ['T26', 'T27', 'T28', 'T29', 'T30', 'T35', 'T36', 'T37', 'T38']
   下を通る ['T26', 'T27']
   右端と T38 の間 2.8  並ぶ点 []  出発点(14.3,5.95)との距離 []
dx=2.0 ドリンクバー [14.0, 3.4, 18.0, 4.1] 卓までの距離 平均10.7 最長18.38 分類 robot38 narrow0 human0 レジ[]
   上の帯を通る ['T26', 'T27', 'T28', 'T29', 'T30', 'T31', 'T32', 'T33', 'T34', 'T35', 'T36', 'T37', 'T38']
   下を通る ['T26', 'T27']
   右端と T38 の間 3.2  並ぶ点 []  出発点(14.3,5.95)との距離 []

$ python3 try_dx.py 0.4
dx=0.4 ドリンクバー [15.6, 3.4, 19.6, 4.1] 卓までの距離 平均10.7 最長18.38 分類 robot38 narrow0 human0 レジ[]
   上の帯を通る ['T29', 'T30', 'T38']
   下を通る ['T27', 'T28']
   右端と T38 の間 1.6  並ぶ点 [[20.9, 3.75]]  出発点(14.3,5.95)との距離 [6.96]
```

どの幅でも決まり違反はなく、38卓すべてにロボットが届き、卓までの距離の平均と最長は変わりませんでした。分かったことは3つです。

- 寄せると右の端と T38 の間が広がり、T29・T30・T38 へ行くロボットはその広くなった所から下りるようになる
- ドリンクバーの下の通路の狭さは消えない。下の6人席の列にドリンクバーが重なっている限り、そこに止まったロボットの上下は0.55mずつ。ふさぐ卓が T27〜T29 から T26〜T28 へずれるだけ
- 1.6m以上寄せると、ドリンクバーの前に立つお客さんが受け渡し口の前の真下に来て、下の卓へ行く9〜13本の道とぶつかる

0.4mのときは、ドリンクバーの前（立つ側）が上ではなく右の端に選ばれ、立つ点が2つ同じ所に重なりました。前を選ぶ規則が、上の帯の狭さで右を選んだためです。0.4mは候補から外しました。

#### ドリンクバーの前に立つ点と卓までの距離を並べたスクリプト stand_points.py

```python
import json
for dx in ('0.0','0.4','0.8','1.2','1.6'):
    try: U=json.load(open(f'out_dx{dx}/unity/p4.json'))
    except: continue
    st=[p['point'] for p in U['places'] if p['kind'].startswith('DrinkBar')]
    d={t['id']:t['dist'] for t in U['tables']}
    print(dx, st, {k:d[k] for k in ('T26','T27','T28','T29','T30','T38')})
```

#### stand_points.py の出力

```text
0.0 [[18.0, 4.5], [16.5, 4.5], [17.5, 4.5], [18.5, 4.5], [19.5, 4.5], [18.0, 5.4]] {'T26': 3.62, 'T27': 4.79, 'T28': 6.64, 'T29': 8.49, 'T30': 10.22, 'T38': 6.43}
0.4 [[20.0, 3.75], [20.0, 3.75], [20.9, 3.75]] {'T26': 3.62, 'T27': 5.02, 'T28': 6.87, 'T29': 8.35, 'T30': 9.99, 'T38': 6.43}
0.8 [[17.2, 4.5], [15.7, 4.5], [16.7, 4.5], [17.7, 4.5], [18.7, 4.5]] {'T26': 3.62, 'T27': 5.25, 'T28': 7.1, 'T29': 8.12, 'T30': 9.75, 'T38': 6.43}
1.2 [[16.8, 4.5], [15.3, 4.5], [16.3, 4.5], [17.3, 4.5], [18.3, 4.5]] {'T26': 3.64, 'T27': 5.49, 'T28': 7.34, 'T29': 7.88, 'T30': 9.52, 'T38': 6.43}
1.6 [[16.4, 4.5], [14.9, 4.5], [15.9, 4.5], [16.9, 4.5], [17.9, 4.5]] {'T26': 4.2, 'T27': 6.05, 'T28': 7.0, 'T29': 7.65, 'T30': 9.43, 'T38': 6.43}
```

0.8m・1.2m では、並ぶ点（ドリンクバーの真ん中の前から外へ並べる点）が消えました。ドリンクバーの真ん中が待機場所の真下に来て、障害物から0.25m以上離れた所に1つ目の点が置けなくなったためです。

### 今・0.8m・1.2m の3つを描いて比べた

【画像】LOG398_02_store6_drinkbar_options.png ドリンクバーを今のまま・左へ0.8m・左へ1.2m寄せた3つの形。濃い青線は右側の6卓（T26〜T30・T38）へのロボットの道、橙の丸はドリンクバーの前に立つ点、橙の三角は並ぶ点

#### 3つの形を描いたスクリプト draw3.py

```python
import json, matplotlib; matplotlib.use('Agg')
import matplotlib.pyplot as plt, matplotlib.patches as P
from matplotlib import font_manager as fm
fp=fm.FontProperties(fname='/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc')
def panel(ax,dx,title):
    L=json.load(open(f'out_dx{dx}/layout.json'))['L']; U=json.load(open(f'out_dx{dx}/unity/p4.json'))
    ax.add_patch(P.Rectangle((0,0),24,17,fill=False,lw=1.5))
    for r in L['rooms']:
        x0,y0,x1,y1=r['rect']; ax.add_patch(P.Rectangle((x0,y0),x1-x0,y1-y0,fc={'back':'#e4e4e0','guest':'#f1e6d6','drink':'#9cc7ea'}[r['kind']],ec='#999'))
        if r['kind']=='drink': ax.text((x0+x1)/2,(y0+y1)/2,'ドリンクバー',ha='center',va='center',fontproperties=fp,fontsize=8)
    for w in L['walls']:
        x0,y0,x1,y1=w['rect']; ax.add_patch(P.Rectangle((x0,y0),x1-x0,y1-y0,fc='#555',ec='none'))
    for t in L['tables']:
        ax.add_patch(P.Rectangle((t['x'],t['y']),t['w'],t['h'],fc='w',ec='k',lw=.6))
        ax.text(t['x']+t['w']/2,t['y']+t['h']/2,t['id'][1:],ha='center',va='center',fontsize=6,clip_on=True)
    s=L['robot_standby']['rect']; ax.add_patch(P.Rectangle((s[0],s[1]),s[2]-s[0],s[3]-s[1],fc='none',ec='#2a5cc8',ls='--',lw=1.2))
    d=L['dish_up'][0]['rect']; ax.add_patch(P.Rectangle((d[0],d[1]),d[2]-d[0],d[3]-d[1],fc='#333'))
    for k,v in U['routes']['from_pass'].items():
        xs,ys=zip(*v['points']); ax.plot(xs,ys,color='#2a5cc8',alpha=.45 if k in ('T26','T27','T28','T29','T30','T38') else .12,lw=1.3)
    for p in U['places']:
        if p['kind']=='DrinkBarStand': ax.plot(*p['point'],'o',color='#e8590c',ms=5)
        if p['kind']=='DrinkBarQueue': ax.plot(*p['point'],'^',color='#e8590c',ms=6)
    ax.plot(*L['start'],'o',color='#2a5cc8',ms=6)
    ax.set_xlim(9.5,24.3); ax.set_ylim(-.3,8.0); ax.set_aspect('equal')
    ax.set_xticks(range(10,25,1)); ax.set_yticks(range(0,11,1)); ax.grid(alpha=.15); ax.tick_params(labelsize=6)
    ax.set_title(title,fontproperties=fp,fontsize=10)
fig,axs=plt.subplots(1,3,figsize=(19,5.6))
panel(axs[0],'0.0','今（16.0〜20.0m）\n右端とT38の間 1.2m')
panel(axs[1],'0.8','案1：左へ0.8m（15.2〜19.2m）\n右端とT38の間 2.0m')
panel(axs[2],'1.2','案2：左へ1.2m（14.8〜18.8m）\n右端とT38の間 2.4m')
fig.text(0.5,0.01,'濃い青線：右側の6卓（T26〜T30・T38）へのロボットの道　橙の丸：ドリンクバーの前に立つ点　橙の三角：並ぶ点（案1・案2では待機場所に近すぎて置けない）',ha='center',fontproperties=fp,fontsize=9)
plt.tight_layout(rect=(0,0.05,1,0.95)); plt.savefig('store6_drinkbar_left_options.png',dpi=120)
```

案1（0.8m）にしました。右の端は2.0mあれば、止まっているロボットの横を別のロボットが抜けられます。1.2mだと、ドリンクバーの左の端が、受け渡し口の前から下りる道のすぐ横に来ます。並ぶ点は、今と同じ所（待機場所のすぐ右）に残すことにしました。

- ねらい：右側の卓（T29・T30・T38）へ行くロボットが、ドリンクを取る人の立つ所を通らずに、右の端から回れるようにする
- 変わったこと：右の端と T38 の間 1.2m→2.0m。卓までの距離は T29 8.49→8.12m・T30 10.22→9.75m、T27 4.79→5.25m・T28 6.64→7.10m（平均10.7m・最長18.4mは同じ）
- 見込める効果：直したあとの30回で、ロボット2台のときのドリンクバーのまわり（x 14.8〜19.6m・y 1.8〜5.65m）の30秒以上の止まりは、種10個の合計で122回中5回だった。直す前の形では回していないので、前後の比べはない

### 並ぶ点が置けないときは、ドリンクバーの前の幅の中で左右へずらす

並ぶ点を「今と同じ所」に残すため、店の形の作り方（`unity_export.py`）に決まりを1つ足しました。真ん中の前の1つ目の点が置けないときだけ、ドリンクバーの前の幅の中で0.05mずつ左右へずらし、最初に置ける所から並べます。⑥は、前と同じ (18.0, 5.4) に来ました。ほかの5店は真ん中の前に置けているので、変わりません。

#### 場所の点を作る関数 unity_export.py の places（直した版）

```python
def places(L, grid, standby_slots, aisles):
    W, D = L["outer"]["w"], L["outer"]["d"]

    def free(x, y):   # 障害物までの余裕（m）。店の外は −1
        cy, cx = grid.cell(x, y)
        if not (0 <= cy < grid.ny and 0 <= cx < grid.nx):
            return -1.0
        return float(grid.clear_person[cy, cx])   # 場所の点は人が立つ所なので、人の余裕で見る
    P = []
    st = L["start"]
    P.append(dict(kind="FoodPass", label="受け渡し口の前（出発点）", rect=L["dish_up"][0]["rect"], point=list(st)))
    P.append(dict(kind="DishReturn", label="下げ膳の返却（受け渡し口と同じ点）", rect=L["dish_up"][0]["rect"], point=list(st)))
    e = L["entrance"]
    m = (e["a0"] + e["a1"]) / 2
    ep = {"bottom": (m, 0.5), "top": (m, D - 0.5), "left": (0.5, m), "right": (W - 0.5, m)}[e["side"]]
    P.append(dict(kind="Entrance", label="入口", point=[r2(ep[0]), r2(ep[1])]))
    kinds = {"パントリー": "Pantry", "待合": "Waiting", "レジ": "Register", "レジ内": "RegisterBack", "トイレ": "Toilet",
             "ドリンクバー": "DrinkBar", "キッチン": "Kitchen"}
    for r in L["rooms"]:
        k = kinds.get(r["label"])
        if not k:
            continue
        if k == "Toilet":
            dz = L["toilet_doors"][0]
            P.append(dict(kind=k, label="トイレの入口の前", rect=r["rect"], point=[r2((dz[0] + dz[2]) / 2), r2((dz[1] + dz[3]) / 2)]))
        elif k == "Pantry":
            P.append(dict(kind=k, label="パントリー（受け渡し口の前と同じ点）", rect=r["rect"], point=list(st)))
        elif k == "RegisterBack":
            x0, y0, x1, y1 = r["rect"]
            P.append(dict(kind=k, label="レジ内（店員が立つ所）", rect=r["rect"], point=[r2((x0 + x1) / 2), r2((y0 + y1) / 2)]))
        elif k == "Kitchen":
            continue
        elif k == "Register":
            # 客の側は、レジ内（店員の立つ所）の反対側
            back = [b["rect"] for b in L["rooms"] if b["label"] == "レジ内"]
            side = _opposite_side(r["rect"], back[0]) if back else front_point(r["rect"], free)[0]
            pt, nrm = _side_point(r["rect"], side, 0.4)
            P.append(dict(kind=k, label="レジの前（会計に立つ点）", rect=r["rect"], point=pt, front=side))
            for q in range(1, REG_QUEUE + 1):   # 並ぶ点：会計に立つ点から外へ 0.5m ずつ。障害物から0.25m未満・細い通路に入る所で打ち切る
                qx, qy = pt[0] + nrm[0] * 0.5 * q, pt[1] + nrm[1] * 0.5 * q
                if free(qx, qy) < 0.25 or _in_aisle(aisles, qx, qy):
                    break
                P.append(dict(kind="RegisterQueue", label=f"レジに並ぶ点 {q}", point=[r2(qx), r2(qy)]))
        elif k == "DrinkBar":
            side, _ = front_point(r["rect"], free)
            x0, y0, x1, y1 = r["rect"]
            horiz = side in ("bottom", "top")
            length = (x1 - x0) if horiz else (y1 - y0)
            n = max(1, int(length / 0.9))   # 前に立つ点：0.9m に1人
            pt, nrm = _side_point(r["rect"], side, 0.4)
            P.append(dict(kind=k, label="ドリンクバーの前", rect=r["rect"], point=pt, front=side))
            for q in range(n):
                f = (q + 0.5) / n
                base = (x0 + (x1 - x0) * f, y0 if side == "bottom" else y1) if horiz else (x0 if side == "left" else x1, y0 + (y1 - y0) * f)
                P.append(dict(kind="DrinkBarStand", label=f"ドリンクバーの前に立つ点 {q + 1}", point=[r2(base[0] + nrm[0] * 0.4), r2(base[1] + nrm[1] * 0.4)]))
            # 並ぶ列の付け根：ふつうは真ん中の前。真ん中の前の1つ目の点が置けないとき（障害物から0.25m未満・細い通路）は、
            # ドリンクバーの前の幅の中で0.05mずつ左右へずらし、最初に置ける所にする（2026-10-28 エイイチと決めた。
            # ⑥のドリンクバーを左へ0.8m寄せたら、真ん中の前が待機場所に近すぎて並ぶ点が消えたため。⑥は待機場所の右＝前と同じ所に来る）
            tang = (1.0, 0.0) if horiz else (0.0, 1.0)
            lo, hi = ((x0, x1) if horiz else (y0, y1))
            c0 = pt[0] if horiz else pt[1]

            def q_ok(px, py):
                return free(px, py) >= 0.25 and not _in_aisle(aisles, px, py)
            first = (pt[0] + nrm[0] * 0.9, pt[1] + nrm[1] * 0.9)
            if not q_ok(*first):
                for i in range(1, int((hi - lo) / 0.05) + 1):
                    hit = None
                    for sgn in (1, -1):
                        c = c0 + sgn * 0.05 * i
                        if lo <= c <= hi:
                            cand = (first[0] + tang[0] * sgn * 0.05 * i, first[1] + tang[1] * sgn * 0.05 * i)
                            if q_ok(*cand):
                                hit = (pt[0] + tang[0] * sgn * 0.05 * i, pt[1] + tang[1] * sgn * 0.05 * i)
                                break
                    if hit:
                        pt = [hit[0], hit[1]]
                        break
            for q in range(1, DRINK_QUEUE + 1):   # 並ぶ点：付け根から外へ。障害物から0.25m未満・細い通路に入る所で打ち切る
                qx, qy = pt[0] + nrm[0] * (0.4 + 0.5 * q), pt[1] + nrm[1] * (0.4 + 0.5 * q)
                if free(qx, qy) < 0.25 or _in_aisle(aisles, qx, qy):
                    break
                P.append(dict(kind="DrinkBarQueue", label=f"ドリンクバーに並ぶ点 {q}", point=[r2(qx), r2(qy)]))
        else:
            side, pt = front_point(r["rect"], free)
            P.append(dict(kind=k, label=r["label"] + "の前", rect=r["rect"], point=pt, front=side))
    # スタッフが待つ点：受け渡し口の前の横（待機場所と反対の側）に、パントリーの壁ぞいへ 0.6m おき（仮の規則）
    for i, pt in enumerate(staff_standby_points(L, free, aisles)):
        P.append(dict(kind="StaffStandby", label=f"スタッフが待つ点 {i + 1}", point=pt))
    for i, s in enumerate(standby_slots):
        P.append(dict(kind="RobotStandby", label=f"ロボットの待機場所 {i + 1}", rect=rect(*s), point=[r2((s[0] + s[2]) / 2), r2((s[1] + s[3]) / 2)]))
    return P



# ---------------------------------------------------------------- ロボットの道順（2026-10-22 エイイチと決めた）
```


## 作業ログ ― レジを縦のまま右へ0.8m、下の壁まで寄せた

ドリンクバーの案を見ていた間に、レジについて別の案が出ました。

> レジを右に寄せて、下の壁際によせたら？そうしたら入り口近くにスペースできるんじゃない？

レジはこれまで、入口から少し離れた所（x 2.8〜4.4m・y 0.7〜3.2m）に縦向きで置き、お客さんは入口の側に立って、入口へ向かって並ぶ形でした。この形は保ったまま、右へ0.4m寄せる案と0.8m寄せる案を、ドリンクバーを0.8m寄せた形の上で回しました。

### レジの位置を変えて確かめたスクリプト try_reg2.py

```python
import sys, json
sys.path.insert(0, '/home/claude/p2m-site/tools/sim_layouts')
from pathlib import Path
import importlib, famires_v3 as F, famires_gen2 as G
def through(pts, box):
    x0,y0,x1,y1=box
    for p,q in zip(pts,pts[1:]):
        for i in range(101):
            x=p[0]+(q[0]-p[0])*i/100; y=p[1]+(q[1]-p[1])*i/100
            if x0<=x<=x1 and y0<=y<=y1: return True
    return False
V={'now':None,'R1':3.2,'R2':3.6}
for name in sys.argv[1:]:
    L=G.p4_relaxed_side()
    if V[name]:
        a=V[name]
        for r in L['rooms']:
            if r['label']=='レジ': r['rect']=[a,0.0,round(a+0.7,2),2.5]
            if r['label']=='レジ内': r['rect']=[round(a+0.7,2),0.0,round(a+1.6,2),2.5]
    L=F.prepare(L); v=F.check_rules(L)
    if v: print(name,'違反',v); continue
    ctx={}; ra,ea=F.analyze(L,F.SETTINGS['res'],ctx); rb,eb=F.analyze(L,F.SETTINGS['check_res'])
    if ea or eb: print(name,'解析違反',ea,eb); continue
    s=F.summarize(L,ra); out=Path('reg_'+name); out.mkdir(exist_ok=True)
    U=F.export_unity(L,ra,ctx,'p4',out,'try'); vr=F.check_register_clear(U)
    fp=U['routes']['from_pass']
    pl={p['kind']:[] for p in U['places']}
    for p in U['places']: pl[p['kind']].append(p['point'])
    print(name,'距離',s['robot_dist_mean'],s['robot_dist_max'],'分類',s['robot_tables'],'レジ違反',vr)
    print('  入口まわり(x<4.6,y<5)を通る道',[k for k,r in fp.items() if through(r['points'],(0,0,4.6,5.0))])
    print('  レジ',pl.get('Register'),'並ぶ',pl.get('RegisterQueue'),'keepout',U['robot_keepout'],'待合',pl.get('Waiting'),'ドリンク並ぶ',pl.get('DrinkBarQueue'))
    d={t['id']:t['dist'] for t in U['tables']}; print('  ',{k:d[k] for k in ('T05','T10','T14','T31','T32')})
    json.dump(dict(L=L),open(out/'layout.json','w'),ensure_ascii=False)
```

#### try_reg2.py の出力

```text
$ python3 try_reg2.py R1 R2
R1 距離 10.7 18.38 分類 38 レジ違反 []
  入口まわり(x<4.6,y<5)を通る道 ['T05', 'T06', 'T07', 'T08', 'T10', 'T11', 'T12']
  レジ [[2.8, 1.25]] 並ぶ [[2.3, 1.25], [1.8, 1.25], [1.3, 1.25]] keepout [{'label': '会計の列（ロボットは通らない）', 'rect': [1.05, 0.75, 3.2, 1.75]}] 待合 [[1.8, 0.9]] ドリンク並ぶ [[18.0, 5.4]]
   {'T05': 12.95, 'T10': 13.12, 'T14': 9.16, 'T31': 8.64, 'T32': 7.54}
R2 距離 10.7 18.38 分類 38 レジ違反 []
  入口まわり(x<4.6,y<5)を通る道 ['T05', 'T06', 'T07', 'T08', 'T10', 'T11', 'T12']
  レジ [[3.2, 1.25]] 並ぶ [[2.7, 1.25], [2.2, 1.25], [1.7, 1.25]] keepout [{'label': '会計の列（ロボットは通らない）', 'rect': [1.45, 0.75, 3.6, 1.75]}] 待合 [[1.8, 0.9]] ドリンク並ぶ [[18.0, 5.4]]
   {'T05': 12.95, 'T10': 13.12, 'T14': 9.16, 'T31': 8.64, 'T32': 7.54}
```

どちらも決まり違反はなく、ロボットの道も卓までの距離も変わりませんでした。左の通路へ行く道は、もともとレジの上の y 4.9m あたりを通っているためです。変わるのは人の動きです。

【画像】LOG398_03_store6_register_options.png レジの今の位置と、縦のまま右へ0.4m・0.8m寄せて下の壁につけた2案。橙の丸は会計に立つ点、三角は並ぶ点、紫の四角は案内を待つ人が立つ点、赤の点線はロボットが通らない会計の列

#### 3つの位置を描いたスクリプト draw_reg2.py

```python
import json, matplotlib; matplotlib.use('Agg')
import matplotlib.pyplot as plt, matplotlib.patches as P
from matplotlib import font_manager as fm
fp=fm.FontProperties(fname='/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc')
def panel(ax,d,title):
    L=json.load(open(f'{d}/layout.json'))['L']; U=json.load(open(f'{d}/unity/p4.json'))
    ax.add_patch(P.Rectangle((0,0),24,17,fill=False,lw=1.5))
    e=L['entrance']; ax.plot([0,0],[e['a0'],e['a1']],color='w',lw=5); ax.text(-0.2,2.2,'入口',fontproperties=fp,ha='right',va='center',fontsize=9)
    for r in L['rooms']:
        x0,y0,x1,y1=r['rect']; ax.add_patch(P.Rectangle((x0,y0),x1-x0,y1-y0,fc={'back':'#e4e4e0','guest':'#f1e6d6','drink':'#9cc7ea'}[r['kind']],ec='#999'))
        if r['label'] in ('レジ','レジ内','待合'): ax.text((x0+x1)/2,(y0+y1)/2,r['label'],ha='center',va='center',fontproperties=fp,fontsize=8,clip_on=True)
    for w in L['walls']:
        x0,y0,x1,y1=w['rect']; ax.add_patch(P.Rectangle((x0,y0),x1-x0,y1-y0,fc='#555',ec='none'))
    for t in L['tables']:
        ax.add_patch(P.Rectangle((t['x'],t['y']),t['w'],t['h'],fc='w',ec='k',lw=.6))
        ax.text(t['x']+t['w']/2,t['y']+t['h']/2,t['id'][1:],ha='center',va='center',fontsize=6,clip_on=True)
    for k in U['robot_keepout']:
        x0,y0,x1,y1=k['rect']; ax.add_patch(P.Rectangle((x0,y0),x1-x0,y1-y0,fc='none',ec='#c33',ls=':',lw=1.3))
    for k,v in U['routes']['from_pass'].items():
        xs,ys=zip(*v['points']); ax.plot(xs,ys,color='#2a5cc8',alpha=.35,lw=1.2)
    for p in U['places']:
        if p['kind']=='Register': ax.plot(*p['point'],'o',color='#e8590c',ms=6)
        if p['kind']=='RegisterQueue': ax.plot(*p['point'],'^',color='#e8590c',ms=6)
        if p['kind']=='Waiting': ax.plot(*p['point'],'s',color='#7a4b9c',ms=6)
    ax.set_xlim(-1.2,10.5); ax.set_ylim(-.3,8.0); ax.set_aspect('equal')
    ax.set_xticks(range(0,11)); ax.set_yticks(range(0,9)); ax.grid(alpha=.15); ax.tick_params(labelsize=7)
    ax.set_title(title,fontproperties=fp,fontsize=10)
fig,axs=plt.subplots(1,3,figsize=(19,5.4))
panel(axs[0],'reg_now','今：レジ x 2.8〜4.4・y 0.7〜3.2\nレジの上と島の下の間 2.0m')
panel(axs[1],'reg_R1','案1：右へ0.4m・下の壁まで（x 3.2〜4.8・y 0〜2.5）\nレジの上と島の下の間 2.7m')
panel(axs[2],'reg_R2','案2：右へ0.8m・下の壁まで（x 3.6〜5.2・y 0〜2.5）\nレジの上と島の下の間 2.7m')
fig.text(0.5,0.02,'橙の丸：会計に立つ点　橙の三角：並ぶ点　紫の四角：待合の前（案内を待つ客が立つ点）　赤の点線：会計の列（ロボットは通らない）　青線：ロボットの道',ha='center',fontproperties=fp,fontsize=9)
plt.tight_layout(rect=(0,0.05,1,0.93)); plt.savefig('store6_register_option2.png',dpi=120)
```

案2（右へ0.8m）にしました。会計の列が入口から少し離れます。

- ねらい：入口の前に、入ってきたお客さんと案内するスタッフが立てる場所を空ける
- 変わったこと：レジの上と左の島の下の間 2.0m→2.7m。入口の上のほう（y 2.5〜3.4m）とレジの上が空いた。ロボットの道と卓までの距離は変わらない
- 見込める効果：直したあとの30回で、入口・レジのまわり（x 5.2mより左・y 5.0mより下）のロボットの30秒以上の止まりは、2台の種10個の合計で1回。人どうしの詰まりは0回。直す前の形では回していないので、前後の比べはない

1つ残したことがあります。会計の列（y 1.25m）が、待合の前の点（1.8, 0.9）の0.35m上に来ます。案内を待つお客さんと会計に並ぶお客さんが重なり得ます。待合は動かさず、3Dの店で見ることにしました。

### 店の形の作り方 famires_gen2.py の⑥の部分（直した版）

```python
def p4_relaxed_side():
    W, D = 24.0, 17.0
    rooms, walls, dish, sb, start = boh_right(W, D, 13.0, 6.5, 14.5)
    toilet, twalls, tdoor = toilet_block([9.4, 14.8, 12.4, 17.0], "bottom", W, D)   # 上の壁の真ん中。入口は下向き
    rooms += [toilet]
    walls += twalls
    rooms += [dict(label="待合", rect=[0.6, 0.0, 3.0, 0.5], kind="guest"),
              # 縦のカウンター。客は入口の側（左）に立ち、入口へ向かって並ぶ。下の通路をふさがない（2026-10-23 エイイチと決めた）
              # 2026-10-28：縦のまま右へ0.8m・下の壁まで（前は x 2.8〜4.4・y 0.7〜3.2）。レジの上と左の島の下の間が2.0m→2.7mになり、入口の前が空く
              *register([3.6, 0.0, 4.3, 2.5], [4.3, 0.0, 5.2, 2.5]),
              # ドリンクバーは左へ0.8m（2026-10-28 エイイチと決めた。前は 16.0〜20.0m で、右端と右の L字席 T38 の間が1.2mしかなく、
              # 右側の卓へ行くロボットが、客が立つ・並ぶドリンクバーの上の帯を通っていた。右端の間を2.0mにした。1.6m以上寄せると
              # 前に立つ客が受け渡し口の前の真下に来るので0.8m。下の6人席の列の前の通路の狭さ（止まると上下0.55m）は残る）
              dict(label="ドリンクバー", rect=[15.2, 3.4, 19.2, 4.1], kind="drink")]
    t = strip(0.3, 15.2, "h", 4, seats=6, walls=walls)    # 家族用の6人ボックス（上の壁ぞい）
    t += strip(0.0, 5.2, "v", 5, walls=walls)              # 左の壁ぞい
    t += island(2.7, 5.6, "v", 4, walls=walls)
    t += island(6.75, 5.6, "v", 4, walls=walls)
    t += strip(24.0 - 5 * UNIT, 0.0, "h", 5, seats=6, walls=walls)  # 家族用の6人ボックス（右下・右の壁まで）
    t += two_seat_row(6.6, 0.0, "bottom", 7)
    t += [lsofa(21.2, 3.0, 2.8, 3.5, flip=True, mirror=True)]   # 下のボックスとの間を1.2mあける
    return dict(name="⑥ ゆったり型・入口が左の壁", style="ゆったり", outer=dict(w=W, d=D),
                entrance=dict(side="left", a0=1.0, a1=3.4), rooms=rooms, walls=walls, toilet_doors=[tdoor],
                dish_up=dish, robot_standby=sb, start=start, tables=t)
```

### 店の形の説明 README.md に足した決まり

```markdown
- ドリンクバーに並ぶ点は、ふつうは真ん中の前から外へ並べる。真ん中の前の1つ目の点が置けないとき（障害物から0.25m未満・細い通路）だけ、ドリンクバーの前の幅の中で0.05mずつ左右へずらし、最初に置ける所から並べる（2026-10-28。⑥のドリンクバーを左へ寄せたら、真ん中の前が待機場所に近すぎて並ぶ点が消えたため。⑥は待機場所の右＝前と同じ所 x18.0・y5.4 に来る。ほかの店は前と同じ）

## 決まり（2026-10-28 エイイチと決めたこと：⑥のドリンクバーとレジ）

- ⑥のドリンクバーを左へ0.8m（16.0〜20.0m → 15.2〜19.2m）。右端と右の L字席（T38）の間が1.2mしかなく、右側の卓（T29・T30・T38）へ行くロボットが、客が立つ・並ぶドリンクバーの上の帯を通っていた。右端の間を2.0mにした。1.6m以上寄せると、前に立つ客が受け渡し口の前の真下に来て、下の卓へ行く道とぶつかる。下の6人席の列の前の通路の狭さ（ロボットが止まると上下0.55m）は残る
- ⑥のレジは縦向きのまま右へ0.8m・下の壁まで（x 2.8〜4.4・y 0.7〜3.2 → x 3.6〜5.2・y 0〜2.5）。レジの上と左の島の下の間が2.0m→2.7mになり、入口の前が空く。客は今までどおり入口の側に立ち、入口へ向かって並ぶ
- 記録だけ：会計の列（y 1.25）が待合の前の点（1.8, 0.9）の0.35m上に来て、案内を待つ客と会計に並ぶ客が重なる。待合は動かしていない（Unity で見る）
- ⑥の卓までの距離：T27 4.79→5.25・T28 6.64→7.10・T29 8.49→8.12・T30 10.22→9.75m（平均10.7m・最長18.4mは同じ）。⑥以外は版の番号だけ
```

### 6店の店の形を作り直した famires_v3.py の出力

```text
[OK] p0_memory: {'name': '② 定番の郊外型', 'seats': 88, 'tables': 19, 'model': 'BellaBot', 'pass_width': 0.8, 'normal_width': 1.0, 'robot_tables': 19, 'robot_seats': 88, 'narrow_tables': 0, 'narrow_seats': 0, 'human_tables': 0, 'human_seats': 0, 'robot_dist_mean': 8.6, 'robot_dist_max': 13.78, 'human_walk_total': 0.0, 'human_walk_max': None}  マス目の差: []
[OK] p1_efficient_corner: {'name': '③ 効率重視型・入口が左下の角', 'seats': 160, 'tables': 42, 'model': 'BellaBot', 'pass_width': 0.8, 'normal_width': 1.0, 'robot_tables': 42, 'robot_seats': 160, 'narrow_tables': 0, 'narrow_seats': 0, 'human_tables': 0, 'human_seats': 0, 'robot_dist_mean': 10.6, 'robot_dist_max': 20.14, 'human_walk_total': 0.0, 'human_walk_max': None}  マス目の差: []
[OK] p2_efficient_narrow: {'name': '④ 効率重視型・狭い店・入口が右の壁', 'seats': 88, 'tables': 28, 'model': 'BellaBot', 'pass_width': 0.8, 'normal_width': 1.0, 'robot_tables': 28, 'robot_seats': 88, 'narrow_tables': 0, 'narrow_seats': 0, 'human_tables': 0, 'human_seats': 0, 'robot_dist_mean': 8.5, 'robot_dist_max': 13.92, 'human_walk_total': 0.0, 'human_walk_max': None}  マス目の差: []
[OK] p3_relaxed_center: {'name': '⑤ ゆったり型・入口が正面の中央', 'seats': 200, 'tables': 46, 'model': 'BellaBot', 'pass_width': 0.8, 'normal_width': 1.0, 'robot_tables': 46, 'robot_seats': 200, 'narrow_tables': 0, 'narrow_seats': 0, 'human_tables': 0, 'human_seats': 0, 'robot_dist_mean': 12.5, 'robot_dist_max': 18.8, 'human_walk_total': 0.0, 'human_walk_max': None}  マス目の差: []
[OK] p4_relaxed_side: {'name': '⑥ ゆったり型・入口が左の壁', 'seats': 158, 'tables': 38, 'model': 'BellaBot', 'pass_width': 0.8, 'normal_width': 1.0, 'robot_tables': 38, 'robot_seats': 158, 'narrow_tables': 0, 'narrow_seats': 0, 'human_tables': 0, 'human_seats': 0, 'robot_dist_mean': 10.7, 'robot_dist_max': 18.38, 'human_walk_total': 0.0, 'human_walk_max': None}  マス目の差: []
[OK] p5_crammed: {'name': '⑦ ③の列の間の通路を95cmに（卓と席数は③と同じ）', 'seats': 160, 'tables': 42, 'model': 'BellaBot', 'pass_width': 0.8, 'normal_width': 1.0, 'robot_tables': 18, 'robot_seats': 64, 'narrow_tables': 24, 'narrow_seats': 96, 'human_tables': 0, 'human_seats': 0, 'robot_dist_mean': 10.8, 'robot_dist_max': 20.17, 'human_walk_total': 0.0, 'human_walk_max': None}  マス目の差: []
```

#### 確かめの仕組みを確かめた test_famires_v3.py の出力

```text
OK  80cmの通路はBellaBotで通れる（マス目0.025・ずらし0.0）
OK  77cmの通路はBellaBotで通れない（マス目0.025・ずらし0.0）
OK  75cmの通路はServiで通れる（マス目0.025・ずらし0.0）
OK  80cmの通路はBellaBotで通れる（マス目0.025・ずらし0.013）
OK  77cmの通路はBellaBotで通れない（マス目0.025・ずらし0.013）
OK  75cmの通路はServiで通れる（マス目0.025・ずらし0.013）
OK  80cmの通路はBellaBotで通れる（マス目0.025・ずらし0.031）
OK  77cmの通路はBellaBotで通れない（マス目0.025・ずらし0.031）
OK  75cmの通路はServiで通れる（マス目0.025・ずらし0.031）
OK  80cmの通路はBellaBotで通れる（マス目0.0125・ずらし0.0）
OK  77cmの通路はBellaBotで通れない（マス目0.0125・ずらし0.0）
OK  75cmの通路はServiで通れる（マス目0.0125・ずらし0.0）
OK  80cmの通路はBellaBotで通れる（マス目0.0125・ずらし0.013）
OK  77cmの通路はBellaBotで通れない（マス目0.0125・ずらし0.013）
OK  75cmの通路はServiで通れる（マス目0.0125・ずらし0.013）
OK  80cmの通路はBellaBotで通れる（マス目0.0125・ずらし0.031）
OK  77cmの通路はBellaBotで通れない（マス目0.0125・ずらし0.031）
OK  75cmの通路はServiで通れる（マス目0.0125・ずらし0.031）
OK  横向きのボックスの左右（ベンチの背）は座る側の候補にしない
OK  縦向きのボックスの上下（ベンチの背）は座る側の候補にしない
OK  店の外にはみ出した卓を見つける
OK  トイレの入口の前の壁を見つける
OK  トイレの壁でへだてた1mの席は違反にしない
OK  壁がなければ、トイレからちょうど1mの席を見つける
OK  卓に食い込んだ壁を見つける
OK  ゆったり型の仕切り（背もたれの上）は違反にしない
OK  座る側の前をふさがれた卓を見つける
OK  出発点がふさがれていたら止める
OK  待合がないのを見つける
OK  ⑦は③と同じ卓の数・種類・席数
OK  L字席（flip=False・mirror=False）の止まってよい辺は、開いた角とソファの端0.6mまで
OK  L字席（flip=False・mirror=True）の止まってよい辺は、開いた角とソファの端0.6mまで
OK  L字席（flip=True・mirror=False）の止まってよい辺は、開いた角とソファの端0.6mまで
OK  L字席（flip=True・mirror=True）の止まってよい辺は、開いた角とソファの端0.6mまで
OK  ⓪T02 の止まる位置は、開いた角か、そこからソファの端に沿って0.6m以内
OK  ⓪の待機場所は、1台ずつ受け渡し口の前まで出られる
OK  2人席（ベンチがbottom）は、ベンチの側とベンチの横を止まる辺にしない
OK  2人席（ベンチがtop）は、ベンチの側とベンチの横を止まる辺にしない
OK  2人席（ベンチがleft）は、ベンチの側とベンチの横を止まる辺にしない
OK  2人席（ベンチがright）は、ベンチの側とベンチの横を止まる辺にしない
OK  直す前の⓪の待機場所（2.2×0.7mに3台）を見つける
OK  待機場所の台数 100 を見つける
OK  待機場所の台数 0 を見つける
OK  待機場所の台数 -1 を見つける
OK  待機場所の台数 '3' を見つける
OK  待機場所の台数 2.5 を見つける
OK  待機場所の台数 True を見つける
OK  待機場所の前をふさぐと、真ん中の台だけ「出られない」を見つける
OK  待機場所の前と両横をふさぐと、3台とも「出られない」を見つける
OK  反例1：レジとレジ内を消すと見つける
OK  反例2：トイレの壁をほぼ全部外すと「切れ目」を見つける
OK  反例3：トイレの入口の前を「トイレの壁」という名前の壁でふさいでも見つける
OK  反例4：家具の真ん中に置いた接合扱いの仕切りを見つける
OK  反例5：待機場所100台を見つける
OK  反例6：⓪の建物の外を消すと、輪郭と合わないのを見つける
OK  反例7：輪郭を2m四方にすると見つける
OK  ⓪の輪郭から欠けを消すと、建物の外と合わないのを見つける
OK  今の店 p0_memory は決まりを守っている
OK  今の店 p1_efficient_corner は決まりを守っている
OK  今の店 p2_efficient_narrow は決まりを守っている
OK  今の店 p3_relaxed_center は決まりを守っている
OK  今の店 p4_relaxed_side は決まりを守っている
OK  今の店 p5_crammed は決まりを守っている
OK  ベンチの背に立つ仕切りは認める
OK  ベンチの背でも0.08mより深い食い込みは認めない
OK  トイレの目隠し：中心の線だけ当たる短い壁では違反にする（4本の線）
OK  トイレの目隠し：4本とも当たる壁なら違反にしない
OK  線分が厚さ2mmの壁を横切るのを見つける
OK  線分の端が壁に触れるだけなら、通ったとみなさない
OK  線分が壁から離れていれば、通ったとみなさない
OK  201番目だけ見通せる止まる位置も見つける
[OK] p2_efficient_narrow: {'name': '④ 効率重視型・狭い店・入口が右の壁', 'seats': 88, 'tables': 28, 'model': 'BellaBot', 'pass_width': 0.8, 'normal_width': 1.0, 'robot_tables': 28, 'robot_seats': 88, 'narrow_tables': 0, 'narrow_seats': 0, 'human_tables': 0, 'human_seats': 0, 'robot_dist_mean': 8.5, 'robot_dist_max': 13.92, 'human_walk_total': 0.0, 'human_walk_max': None}  マス目の差: []
OK  成功したときは、layout・analysis・summary に同じ版の番号が入る
[NG] p2_efficient_narrow: ④ 効率重視型・狭い店・入口が右の壁
   - 入口まわり: レジがない

違反があるので、/tmp/tmpf29dsget には何も書いていない（前の版のまま）
OK  違反があると止まり、出力先のファイルは前の版のまま（作業用のフォルダも残らない）
[OK] p2_efficient_narrow: {'name': '④ 効率重視型・狭い店・入口が右の壁', 'seats': 88, 'tables': 28, 'model': 'BellaBot', 'pass_width': 0.8, 'normal_width': 1.0, 'robot_tables': 28, 'robot_seats': 88, 'narrow_tables': 0, 'narrow_seats': 0, 'human_tables': 0, 'human_seats': 0, 'robot_dist_mean': 8.5, 'robot_dist_max': 13.92, 'human_walk_total': 0.0, 'human_walk_max': None}  マス目の差: []

出力先へ入れ替える途中で失敗したので、/tmp/tmpf29dsget を前の版に戻した
OK  入れ替えの途中で失敗すると、出力先は前の版に戻る（作業用のフォルダも残らない）
OK  反例A：トイレの壁の1cmの切れ目を見つける
OK  反例B：トイレの入口の内側の1cmのふさぎを見つける
OK  反例C：トイレの入口の前の奥行き0.1mを見つける
OK  反例D：輪郭の幅1cmの切れ込み（建物の外なし）を見つける
OK  輪郭に斜めの辺があれば止める
OK  出入り口Aを壁でふさぐと見つける
OK  p1：細い通路は3本で、幅はどれも1.2m
OK  p1：卓の数・止まる位置・距離が analysis と同じ
OK  p1：細い通路を通る卓は、止まる位置がその通路の区画の中
OK  p1：細い通路の入口は、受け渡し口の前から行ける端
OK  p1：全部の卓に部品（天板・座る位置）があり、座る位置の数が席数と同じ
OK  p1：場所の点がそろっている
OK  p1：スタッフが待つ点・並ぶ点・立つ点は、障害物から0.2m以上離れ、細い通路に入らない
OK  p1：席に着く点は障害物から0.2m以上離れ、どの卓にも1つ以上ある
OK  p1：レジ内は通れる部屋（スタッフだけ）、ほかの部屋は通れない箱
OK  p1：受け渡し口から全卓・卓から卓の全組に道順がある
OK  p1：ふだんの道か狭い道かは、試算の卓の分け方と同じ（robot→normal、narrow→narrow）
OK  p1：道順は受け渡し口の前から始まり、止まる位置で終わる
OK  p1：道順のどの区間も、その幅で通れるマス目だけを通る
OK  p1：会計に立つ点・並ぶ点は、ロボットの道から0.75m以上
OK  p1：会計の列が1つあり、ロボットは通れず、人は通れる
OK  p1：会計に立つ点・並ぶ点は会計の列の中
OK  p5：細い通路は3本で、幅はどれも0.95m
OK  p5：卓の数・止まる位置・距離が analysis と同じ
OK  p5：細い通路を通る卓は、止まる位置がその通路の区画の中
OK  p5：細い通路の入口は、受け渡し口の前から行ける端
OK  p5：全部の卓に部品（天板・座る位置）があり、座る位置の数が席数と同じ
OK  p5：場所の点がそろっている
OK  p5：スタッフが待つ点・並ぶ点・立つ点は、障害物から0.2m以上離れ、細い通路に入らない
OK  p5：席に着く点は障害物から0.2m以上離れ、どの卓にも1つ以上ある
OK  p5：レジ内は通れる部屋（スタッフだけ）、ほかの部屋は通れない箱
OK  p5：受け渡し口から全卓・卓から卓の全組に道順がある
OK  p5：ふだんの道か狭い道かは、試算の卓の分け方と同じ（robot→normal、narrow→narrow）
OK  p5：道順は受け渡し口の前から始まり、止まる位置で終わる
OK  p5：道順のどの区間も、その幅で通れるマス目だけを通る
OK  p5：会計に立つ点・並ぶ点は、ロボットの道から0.75m以上
OK  p5：会計の列が1つあり、ロボットは通れず、人は通れる
OK  p5：会計に立つ点・並ぶ点は会計の列の中
OK  p1：地図を読み戻すと、マス目の数が同じ
OK  p1：地図を2回作ると同じ文字列（毎回同じ）
OK  p1：障害物までの距離は、計算の値と0.5mm以内
OK  p1：試算でふだんの道が通れるマスは、地図でも2（待機場所を床にした分だけ増える）
OK  p1：会計の列のマスは、地図では通れない
OK  p1：地図の上でも、ふだんの道で行ける卓は試算の robot の卓と同じ、狭い道でだけ行ける卓は narrow の卓と同じ
OK  p1：どの待機場所の真ん中にも、受け渡し口の前から地図の上で行ける（通れる幅の道）
OK  p5：地図を読み戻すと、マス目の数が同じ
OK  p5：地図を2回作ると同じ文字列（毎回同じ）
OK  p5：障害物までの距離は、計算の値と0.5mm以内
OK  p5：試算でふだんの道が通れるマスは、地図でも2（待機場所を床にした分だけ増える）
OK  p5：会計の列のマスは、地図では通れない
OK  p5：地図の上でも、ふだんの道で行ける卓は試算の robot の卓と同じ、狭い道でだけ行ける卓は narrow の卓と同じ
OK  p5：どの待機場所の真ん中にも、受け渡し口の前から地図の上で行ける（通れる幅の道）
OK  p0：地図を読み戻すと、マス目の数が同じ
OK  p0：地図を2回作ると同じ文字列（毎回同じ）
OK  p0：障害物までの距離は、計算の値と0.5mm以内
OK  p0：試算でふだんの道が通れるマスは、地図でも2（待機場所を床にした分だけ増える）
OK  p0：会計の列のマスは、地図では通れない
OK  p0：地図の上でも、ふだんの道で行ける卓は試算の robot の卓と同じ、狭い道でだけ行ける卓は narrow の卓と同じ
OK  p0：どの待機場所の真ん中にも、受け渡し口の前から地図の上で行ける（通れる幅の道）
OK  会計の列がないと、②は会計に立つ点の近くをロボットが通り、確かめに引っかかる
OK  ボックス席の列は、すき間なく並んだものだけをまとめる

すべて OK
```

### ⑥以外は版の番号だけ変わったことを確かめたスクリプト check_gen_only.py

店の形の版の番号は、作り方のコードから決まります。コードを変えたので6店とも番号は変わりますが、中身は⑥だけ変わっているはずです。前の版のファイルの番号だけを新しい番号に置き換えて、今のファイルと同じかを比べました。

```python
import json,subprocess
new=json.load(open('public/sim/layouts/summary.json'))[0]['generation']
print('新しい版',new)
fs=subprocess.check_output(['git','diff','--name-only','public/sim/layouts']).decode().split()
for f in fs:
    old=subprocess.check_output(['git','show','HEAD:'+f]).decode()
    cur=open(f).read()
    same = old.replace('d7bb919590d4',new)==cur
    print(('版だけ' if same else '中身も変わった'),f)
```

#### check_gen_only.py の出力

```text
新しい版 2e9fc773050a
版だけ public/sim/layouts/p0_memory.json
版だけ public/sim/layouts/p0_memory_analysis.json
版だけ public/sim/layouts/p1_efficient_corner.json
版だけ public/sim/layouts/p1_efficient_corner_analysis.json
版だけ public/sim/layouts/p2_efficient_narrow.json
版だけ public/sim/layouts/p2_efficient_narrow_analysis.json
版だけ public/sim/layouts/p3_relaxed_center.json
版だけ public/sim/layouts/p3_relaxed_center_analysis.json
中身も変わった public/sim/layouts/p4_relaxed_side.json
中身も変わった public/sim/layouts/p4_relaxed_side_analysis.json
版だけ public/sim/layouts/p5_crammed.json
版だけ public/sim/layouts/p5_crammed_analysis.json
版だけ public/sim/layouts/summary.json
版だけ public/sim/layouts/unity/p0_memory.json
版だけ public/sim/layouts/unity/p0_memory_map.json
版だけ public/sim/layouts/unity/p1_efficient_corner.json
版だけ public/sim/layouts/unity/p1_efficient_corner_map.json
版だけ public/sim/layouts/unity/p2_efficient_narrow.json
版だけ public/sim/layouts/unity/p2_efficient_narrow_map.json
版だけ public/sim/layouts/unity/p3_relaxed_center.json
版だけ public/sim/layouts/unity/p3_relaxed_center_map.json
中身も変わった public/sim/layouts/unity/p4_relaxed_side.json
中身も変わった public/sim/layouts/unity/p4_relaxed_side_map.json
版だけ public/sim/layouts/unity/p5_crammed.json
版だけ public/sim/layouts/unity/p5_crammed_map.json
```

#### ⑥の卓までの距離とレジ・並ぶ点を確かめたスクリプト dist_change.py

```python
import json,subprocess
f='public/sim/layouts/p4_relaxed_side_analysis.json'
o=json.loads(subprocess.check_output(['git','show','HEAD:'+f])); n=json.load(open(f))
for a,b in zip(o['tables'],n['tables']):
    if a.get('dist')!=b.get('dist') or a['category']!=b['category']: print(a['id'],a.get('dist'),'→',b.get('dist'),b['category'])
print(o['summary']['robot_dist_mean'],n['summary']['robot_dist_mean'])
U=json.load(open('public/sim/layouts/unity/p4_relaxed_side.json'))
print([ (p['kind'],p['point']) for p in U['places'] if p['kind'] in ('Register','RegisterQueue','Waiting','DrinkBarQueue')], U['robot_keepout'])
```

#### dist_change.py の出力

```text
T27 4.79 → 5.25 robot
T28 6.64 → 7.1 robot
T29 8.49 → 8.12 robot
T30 10.22 → 9.75 robot
10.7 10.7
[('Waiting', [1.8, 0.9]), ('Register', [3.2, 1.25]), ('RegisterQueue', [2.7, 1.25]), ('RegisterQueue', [2.2, 1.25]), ('RegisterQueue', [1.7, 1.25]), ('DrinkBarQueue', [18.0, 5.4])] [{'label': '会計の列（ロボットは通らない）', 'rect': [1.45, 0.75, 3.6, 1.75]}]
```


## 作業ログ ― 卓までの距離が4卓で変わったので、⑥の試算だけ計算し直した

試算は、店の種類・スタッフの人数・ロボットの台数・来客のペースなどの組み合わせ32,850条件を、1条件40回ずつ回した平均です。前提が変わった⑥の3,066条件だけを計算し直し、ほかは前の結果を使い回しました。Claude のクラウドの作業環境（2コア・simpy 4.1.1）で約22分でした。

### haizen_sim.py の実行ログ

```text
32850 conditions, 29784 reused, 3066 to run
  100/3066
  200/3066
  300/3066
  400/3066
  500/3066
  600/3066
  700/3066
  800/3066
  900/3066
  1000/3066
  1100/3066
  1200/3066
  1300/3066
  1400/3066
  1500/3066
  1600/3066
  1700/3066
  1800/3066
  1900/3066
  2000/3066
  2100/3066
  2200/3066
  2300/3066
  2400/3066
  2500/3066
  2600/3066
  2700/3066
  2800/3066
  2900/3066
  3000/3066
32850 conditions written
```

### 直す前と後の試算を比べたスクリプト compare_p4_site.py

```python
"""⑥の試算を、店の形を直す前（results_before.json）と後（results.json）で比べる（2026-10-28）
使い方: python3 compare_p4_site.py   （haizen_sim.py と同じフォルダで）"""
import json, haizen_sim as h
A = {h.key(r): r for r in json.load(open('results_before.json'))['rows']}
B = {h.key(r): r for r in json.load(open('results.json'))['rows']}
ch = {}
for k in B:
    if A[k] != B[k]:
        ch[k[0]] = ch.get(k[0], 0) + 1
print(ch)
s = h.STORES['fam_p4']
for st in s['staff']:
    for rb in (0, 1, 2):
        c = {'store': 'fam_p4', 'staff': st, 'robots': rb, 'arrivals_per_h': 38, 'mode': 'serve', 'trays': 3 if rb else 1, 'fragile_by': 'any', 'registers': 1}
        a = A[h.key(c)]; b = B[h.key(c)]
        print(st, rb, ' '.join(f"{m}:{a[m]:.2f}→{b[m]:.2f}" for m in ('entry_wait_mean', 'staff_wait_mean', 'dish_wait_mean', 'pay_wait_mean', 'robot_share')))
```

#### compare_p4_site.py の出力

```text
{'fam_p4': 3066}
2 0 entry_wait_mean:26.45→26.66 staff_wait_mean:20.41→20.38 dish_wait_mean:1.38→1.38 pay_wait_mean:12.49→12.35 robot_share:0.00→0.00
2 1 entry_wait_mean:12.94→12.76 staff_wait_mean:8.50→8.38 dish_wait_mean:1.81→1.80 pay_wait_mean:15.86→15.93 robot_share:0.89→0.89
2 2 entry_wait_mean:10.94→11.02 staff_wait_mean:7.20→6.02 dish_wait_mean:0.72→0.72 pay_wait_mean:16.51→18.13 robot_share:1.00→1.00
3 0 entry_wait_mean:6.31→6.95 staff_wait_mean:3.77→4.20 dish_wait_mean:0.73→0.75 pay_wait_mean:11.73→12.05 robot_share:0.00→0.00
3 1 entry_wait_mean:3.15→3.12 staff_wait_mean:0.89→1.00 dish_wait_mean:1.93→1.97 pay_wait_mean:8.24→8.47 robot_share:0.80→0.80
3 2 entry_wait_mean:2.34→1.89 staff_wait_mean:0.70→0.63 dish_wait_mean:0.85→0.82 pay_wait_mean:7.70→6.99 robot_share:1.00→1.00
4 0 entry_wait_mean:1.92→1.71 staff_wait_mean:0.61→0.64 dish_wait_mean:0.38→0.39 pay_wait_mean:6.35→6.51 robot_share:0.00→0.00
4 1 entry_wait_mean:2.36→1.44 staff_wait_mean:0.53→0.41 dish_wait_mean:1.86→1.81 pay_wait_mean:5.02→4.59 robot_share:0.76→0.77
4 2 entry_wait_mean:1.38→1.12 staff_wait_mean:0.36→0.39 dish_wait_mean:0.84→0.86 pay_wait_mean:4.30→4.22 robot_share:1.00→1.00
```

3Dの店で回す条件（スタッフ3人・1時間38組）では、入店待ちは 0台 6.3→7.0分・1台 3.2→3.1分・2台 2.3→1.9分でした。卓までの距離は4卓で0.4〜0.5m変わっただけなので、この差は40回の平均の乱数のぶれの大きさです。

### ページの⑥だけを差し替えるスクリプト update_page_p4.py

公開中の試算のページは、全条件の結果と店の平面図を1つのデータとして持っています。⑥の行だけが変わったことを確かめてから結果を入れ替え、⑥の平面図を作り直しました。

```python
"""⑥の店の形を直したとき（2026-10-28）、公開中の試算のページの⑥だけを差し替える。
使い方: python3 update_page_p4.py <公開中のページ> <新しい results.json> <前の p4 の店の形> <新しい p4 の店の形> <新しい summary.json> <出力するページ>
- const D の r（結果）：⑥（fam_p4）の行だけが変わっていることを確かめてから、全部を新しい結果に入れ替える
- const D の stores.fam_p4：卓までの距離（dist_m・dist_mean）を新しい値に
- const D の plans.fam_p4：⑥はドリンクバーとレジの位置が変わったので、平面図をまるごと作り直す
  （作り方は build_page_v23.py と同じ。前の店の形から作った平面図が、公開中のページと同じになることを先に確かめる）
- const D の layout_generation：新しい版の番号に
ほかの店・費用の欄・画面の文は変えない"""
import json, sys
import haizen_sim as h

live, res, old_lay, new_lay, summ, out = sys.argv[1:7]
s = open(live, encoding="utf-8").read()
N = json.load(open(res, encoding="utf-8"))
assert N["common"] == json.loads(json.dumps(h.COMMON))
lines = [l for l in s.split("\n") if l.startswith("const D = ")]
assert len(lines) == 1
D = json.loads(lines[0][len("const D = "):].rstrip(";"))
cond = D["k"][:8]
mets = D["k"][8:-1]
conds = list(h.conditions())
assert len(N["rows"]) == len(conds) == len(D["r"])
rows, changed = [], {}
for c, r, o in zip(conds, N["rows"], D["r"]):
    assert all(r[k] == c[k] for k in cond)
    row = [r[k] for k in cond] + [round(r[k], 3) for k in mets] + [1 if r["staff_wait_mean"] >= h.NG_MIN else 0]
    assert o[:8] == row[:8]
    if o != row:
        changed[c["store"]] = changed.get(c["store"], 0) + 1
    rows.append(row)
print("結果が変わった行:", changed)
assert set(changed) <= {"fam_p4"}, changed
D["r"] = rows

st = h.STORES["fam_p4"]
D["stores"]["fam_p4"]["dist_m"] = list(st["dist_m"])
D["stores"]["fam_p4"]["dist_mean"] = round(sum(st["dist_list"]) / len(st["dist_list"]), 1)
assert D["stores"]["fam_p4"]["tables"] == st["tables"] == 38
print("⑥の距離:", D["stores"]["fam_p4"]["dist_m"], D["stores"]["fam_p4"]["dist_mean"])

q = lambda a: [round(v, 2) for v in a]
def plan(path):
    L = json.load(open(path, encoding="utf-8"))["layout"]
    W, Dd = L["outer"]["w"], L["outer"]["d"]
    return json.loads(json.dumps({
        "w": W, "d": Dd,
        "outline": L.get("outline") or [[0, 0], [W, 0], [W, Dd], [0, Dd]],
        "rooms": [q(r["rect"]) + [r["kind"], r["label"]] for r in L["rooms"] if r["kind"] != "outside"],
        "walls": [q(w["rect"]) for w in L["walls"]],
        "tables": [q([t["x"], t["y"], t["x"] + t["w"], t["y"] + t["h"]]) + [t["seats"]] for t in L["tables"]],
        "dish": [q(d["rect"]) for d in L["dish_up"]],
        "standby": q(L["robot_standby"]["rect"]),
        "ent": [L["entrance"]["side"], L["entrance"]["a0"], L["entrance"]["a1"]],
    }))
assert plan(old_lay) == D["plans"]["fam_p4"], "前の店の形から作った平面図が公開中のページと違う"
D["plans"]["fam_p4"] = plan(new_lay)
print("⑥の平面図を作り直した（卓", len(D["plans"]["fam_p4"]["tables"]), "）")

gens = {g["generation"] for g in json.load(open(summ, encoding="utf-8"))}
assert len(gens) == 1
D["layout_generation"] = gens.pop()
s = s.replace(lines[0], "const D = " + json.dumps(D, ensure_ascii=False, separators=(",", ":")) + ";")
open(out, "w", encoding="utf-8").write(s)
print("書いた:", out, "版", D["layout_generation"])
```

### Unity が読む条件を書き出すスクリプト export_params.py（⑥を足した版）

```python
"""試算（haizen_sim.py）の条件と店の回し方の値を、Unity が読む JSON に書き出す（2026-10-22。v2.8 で2026-10-25 にレジの数を足した）。

「何について正か」（unity_design_v0.md の 0 章）で、条件と店の回し方の決まり・時間・数字の定義はサイトが正。
Unity はこの JSON を読み、値を手で写さない（LOG359 のずれ9「値の手写し」をなくすため）。

使い方: 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）の値。registers はレジの数（v2.8）
- unity_conditions：Unity で回す条件（店ごとに3つ。③⑦④⑤⑥で15条件）と、同じ条件の試算の結果（40回の平均）。Unity の店はレジの内側に店員が1人だけなので、レジは1つ
"""
import json
import sys

import haizen_sim as h

res_path, out = sys.argv[1], sys.argv[2]
R = json.load(open(res_path, encoding="utf-8"))
gen = {g["generation"] for g in json.load(open(h.LAYOUT_DIR + "/summary.json", encoding="utf-8"))}
assert len(gen) == 1, gen

ROLE = {
    "robot_speed": "unity_measures", "pause_rate": "unity_measures", "pause_s": "unity_measures", "stuck_rate": "unity_measures",
    "hop_m": "site_only",          # 試算の近似（1往復で複数卓を回るときの寄り道）。Unity は実際の道を走るので使わない
    "sustain": "site_only", "warmup_min": "site_only", "util_sample_s": "site_only", "model": "site_only",
}
NOTE = {
    "unity_measures": "Unity で測ってサイトに戻すための仮の値。Unity は読まない",
    "site_only": "試算の中だけの設定。Unity は使わない",
    "site_rule": "サイトが正。Unity はこの値を使う",
}
common = {k: {"value": v, "role": ROLE.get(k, "site_rule")} for k, v in h.COMMON.items()}

UNITY_STORES = ["fam_p1", "fam_p5", "fam_p2", "fam_p3", "fam_p4"]   # ④は2026-10-27に追加（スタッフ2人・28組/時）。⑤は2026-10-28に追加（スタッフ4人・46組/時）。⑥も2026-10-28に追加（スタッフ3人・38組/時）
UNITY_REGISTERS = 1
stores = {}
for k in UNITY_STORES:
    s = dict(h.STORES[k])
    stores[k] = s

conds = []
rows = {h.key(r): r for r in R["rows"]}
for k in UNITY_STORES:
    s = h.STORES[k]
    staff = s["staff"][1]           # 真ん中の人数
    arr = s["arrivals"][1]          # 普通
    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",   # ロボット0台の試算は trays=1 で持っている（使い方・段の数は関係ない）
             "registers": UNITY_REGISTERS}
        r = rows[h.key(c)]
        conds.append({"condition": c, "site_result": {m: r[m] for m in r if m not in c}})

D = {
    "meta": {
        "schema_version": "serving-robot-params/0.4",
        "model": h.COMMON["model"], "reps": h.REPS, "ng_min": h.NG_MIN, "layout_generation": gen.pop(),
        "source": "p2m-site の public/sim/serving-robot.html と同じ試算（haizen_sim.py）と結果（results.json）",
        "roles": NOTE,
        "time_window": "空の店から始め、peak_min 分だけ選んだペースで来客し、全員が帰るまで回す（max_extra_min で打ち切り）",
        "registers": "会計はレジ1つにつき1組ずつ（pay_s 秒）。食べ終えた組は卓に座ったまま順番を待つ。Unity の条件はすべてレジ1つ（v2.8）",
        "metrics": {
            "entry_wait_mean": "入店待ち：店に来てから案内されて卓に着くまで（分）",
            "full_wait_mean": "そのうち満席で待った時間（分）",
            "staff_wait_mean": "そのうち席は空いているのに片付け・案内が追いつかず待った時間（分）。ng_min 以上なら「店が回らない」",
            "dish_wait_mean": "料理ができてから卓に届くまで（分）", "dish_wait_p90": "同じく上位10%の境目（分）",
            "cold_share": "cold_min 分を超えて届いた皿の割合", "turn_mean": "客が帰ってから卓が使えるまで（分）",
            "walk_km_per_staff": "スタッフ1人が歩いた距離（km）", "staff_util": "スタッフの手がふさがっている割合",
            "robot_util": "ロボットがふさがっている割合", "robot_share": "ロボットが運んだ皿の割合",
            "rescues": "立ち往生でスタッフが動かしに行った回数", "pauses": "一時停止の回数", "groups": "入れた組の数",
            "pay_wait_mean": "食べ終えてから会計が始まるまで（分）。レジの順番とスタッフの手が空くのを待つ（v2.8）",
        },
    },
    "common": common,
    # 2026-10-27 エイイチと決めた回し方の決まり（案B）。試算の数字には効かない（試算の料理は卓の区別がなく、受け渡し口で積む時間も0秒で順番待ちがないため）。
    # 受け渡し口でのロボットどうしのぶつかりは通路で起きることなので Unity が測る。Unity はこの値を使う（切り替えで入れ、まず④で確かめる）
    "unity_rules": {
        # 2026-10-28 エイイチと決めた：待機場所へ戻るを標準に。案B（受け渡し口は1台ずつ・同じ卓は1台）は切に戻す
        # （④・③⑦の夜で、待機場所が受け渡し口から1.5m以内にあるため2台が互いを待ち、往復が減ったため。待機場所との距離の決め方を考え直すまで使わない）
        "standby_return": {"value": True, "role": "site_rule",
                           "note": "往復を終えたロボットは、受け渡し口の点で止まらず、店の形の RobotStandby（1台に1つ）へ下がって待つ"},
        "pass_one_robot": {"value": False, "role": "site_rule",
                           "note": "（切）受け渡し口の前に入れるロボットは1台だけ。前の1台が出発点から pass_clear_m 離れるまで、次の1台は待機場所で待つ"},
        "pass_clear_m": {"value": 1.5, "role": "site_rule", "note": "仮の値（pass_one_robot が切のあいだは使わない）"},
        "same_table_one_robot": {"value": False, "role": "site_rule",
                                 "note": "（切）その卓へ向かっているロボットがいる間は、その卓の料理を別のロボットに積まない"},
    },
    "stores": stores,
    "unity_conditions": conds,
}
json.dump(D, open(out, "w", encoding="utf-8"), ensure_ascii=False, indent=1)
print(out, len(conds), "conditions / generation", D["meta"]["layout_generation"])
for c in conds:
    print(" ", c["condition"], {m: c["site_result"].get(m) for m in ("entry_wait_mean", "staff_wait_mean", "dish_wait_mean", "pay_wait_mean")})
```

#### ページの差し替え・条件の書き出し・ページの確かめの出力

```text
$ python3 update_page_p4.py ../../p2m-site/public/sim/serving-robot.html results.json p4_before.json p4_after.json ../../p2m-site/public/sim/layouts/summary.json serving-robot.v28e.html
結果が変わった行: {'fam_p4': 3066}
⑥の距離: [3.6, 18.4] 10.7
⑥の平面図を作り直した（卓 38 ）
書いた: serving-robot.v28e.html 版 2e9fc773050a

$ python3 export_params.py results.json serving-robot-params.new.json
/tmp/claude-0/-home-claude/0c16993b-2ddd-5d32-af2a-73934f1ff276/scratchpad/sim/p_check.json 15 conditions / generation 2e9fc773050a
  {'store': 'fam_p1', 'staff': 4, 'robots': 0, 'arrivals_per_h': 42, 'mode': 'serve', 'trays': 1, 'fragile_by': 'any', 'registers': 1} {'entry_wait_mean': 3.301, 'staff_wait_mean': 0.9547, 'dish_wait_mean': 0.459, 'pay_wait_mean': 12.2785}
  {'store': 'fam_p1', 'staff': 4, 'robots': 1, 'arrivals_per_h': 42, 'mode': 'serve', 'trays': 3, 'fragile_by': 'any', 'registers': 1} {'entry_wait_mean': 2.5992, 'staff_wait_mean': 0.572, 'dish_wait_mean': 1.9487, 'pay_wait_mean': 7.9893}
  {'store': 'fam_p1', 'staff': 4, 'robots': 2, 'arrivals_per_h': 42, 'mode': 'serve', 'trays': 3, 'fragile_by': 'any', 'registers': 1} {'entry_wait_mean': 1.1788, 'staff_wait_mean': 0.3853, 'dish_wait_mean': 0.9488, 'pay_wait_mean': 7.9844}
  {'store': 'fam_p5', 'staff': 4, 'robots': 0, 'arrivals_per_h': 42, 'mode': 'serve', 'trays': 1, 'fragile_by': 'any', 'registers': 1} {'entry_wait_mean': 2.7562, 'staff_wait_mean': 0.8929, 'dish_wait_mean': 0.4478, 'pay_wait_mean': 11.7376}
  {'store': 'fam_p5', 'staff': 4, 'robots': 1, 'arrivals_per_h': 42, 'mode': 'serve', 'trays': 3, 'fragile_by': 'any', 'registers': 1} {'entry_wait_mean': 1.815, 'staff_wait_mean': 0.4851, 'dish_wait_mean': 1.9698, 'pay_wait_mean': 8.0624}
  {'store': 'fam_p5', 'staff': 4, 'robots': 2, 'arrivals_per_h': 42, 'mode': 'serve', 'trays': 3, 'fragile_by': 'any', 'registers': 1} {'entry_wait_mean': 1.9669, 'staff_wait_mean': 0.4479, 'dish_wait_mean': 0.9839, 'pay_wait_mean': 7.9555}
  {'store': 'fam_p2', 'staff': 2, 'robots': 0, 'arrivals_per_h': 28, 'mode': 'serve', 'trays': 1, 'fragile_by': 'any', 'registers': 1} {'entry_wait_mean': 10.392, 'staff_wait_mean': 8.3693, 'dish_wait_mean': 0.8848, 'pay_wait_mean': 4.1977}
  {'store': 'fam_p2', 'staff': 2, 'robots': 1, 'arrivals_per_h': 28, 'mode': 'serve', 'trays': 3, 'fragile_by': 'any', 'registers': 1} {'entry_wait_mean': 2.9349, 'staff_wait_mean': 2.0141, 'dish_wait_mean': 1.2778, 'pay_wait_mean': 4.9134}
  {'store': 'fam_p2', 'staff': 2, 'robots': 2, 'arrivals_per_h': 28, 'mode': 'serve', 'trays': 3, 'fragile_by': 'any', 'registers': 1} {'entry_wait_mean': 3.4597, 'staff_wait_mean': 1.9914, 'dish_wait_mean': 0.4732, 'pay_wait_mean': 5.2536}
  {'store': 'fam_p3', 'staff': 4, 'robots': 0, 'arrivals_per_h': 46, 'mode': 'serve', 'trays': 1, 'fragile_by': 'any', 'registers': 1} {'entry_wait_mean': 6.2179, 'staff_wait_mean': 1.497, 'dish_wait_mean': 0.5741, 'pay_wait_mean': 18.8084}
  {'store': 'fam_p3', 'staff': 4, 'robots': 1, 'arrivals_per_h': 46, 'mode': 'serve', 'trays': 3, 'fragile_by': 'any', 'registers': 1} {'entry_wait_mean': 3.4227, 'staff_wait_mean': 0.7679, 'dish_wait_mean': 2.1415, 'pay_wait_mean': 12.9545}
  {'store': 'fam_p3', 'staff': 4, 'robots': 2, 'arrivals_per_h': 46, 'mode': 'serve', 'trays': 3, 'fragile_by': 'any', 'registers': 1} {'entry_wait_mean': 3.6696, 'staff_wait_mean': 0.6882, 'dish_wait_mean': 1.2411, 'pay_wait_mean': 12.9954}
  {'store': 'fam_p4', 'staff': 3, 'robots': 0, 'arrivals_per_h': 38, 'mode': 'serve', 'trays': 1, 'fragile_by': 'any', 'registers': 1} {'entry_wait_mean': 6.9454, 'staff_wait_mean': 4.1998, 'dish_wait_mean': 0.7526, 'pay_wait_mean': 12.0466}
  {'store': 'fam_p4', 'staff': 3, 'robots': 1, 'arrivals_per_h': 38, 'mode': 'serve', 'trays': 3, 'fragile_by': 'any', 'registers': 1} {'entry_wait_mean': 3.116, 'staff_wait_mean': 1.0043, 'dish_wait_mean': 1.9737, 'pay_wait_mean': 8.4695}
  {'store': 'fam_p4', 'staff': 3, 'robots': 2, 'arrivals_per_h': 38, 'mode': 'serve', 'trays': 3, 'fragile_by': 'any', 'registers': 1} {'entry_wait_mean': 1.8939, 'staff_wait_mean': 0.6314, 'dish_wait_mean': 0.8188, 'pay_wait_mean': 6.9917}

$ （前の params と比べた）
変わった鍵: ['meta', 'stores', 'unity_conditions']
layout_generation: d7bb919590d4 → 2e9fc773050a
meta で変わったもの: ['layout_generation']
common 同じ: True / unity_rules 同じ: True / 前の12件が同じ: True / 件数 15 / 前からある店で変わった店: []
{'store': 'fam_p4', 'staff': 3, 'robots': 0, 'arrivals_per_h': 38, 'mode': 'serve', 'trays': 1, 'fragile_by': 'any', 'registers': 1} {'entry_wait_mean': 6.95, 'staff_wait_mean': 4.2, 'dish_wait_mean': 0.75, 'pay_wait_mean': 12.05}
{'store': 'fam_p4', 'staff': 3, 'robots': 1, 'arrivals_per_h': 38, 'mode': 'serve', 'trays': 3, 'fragile_by': 'any', 'registers': 1} {'entry_wait_mean': 3.12, 'staff_wait_mean': 1.0, 'dish_wait_mean': 1.97, 'pay_wait_mean': 8.47}
{'store': 'fam_p4', 'staff': 3, 'robots': 2, 'arrivals_per_h': 38, 'mode': 'serve', 'trays': 3, 'fragile_by': 'any', 'registers': 1} {'entry_wait_mean': 1.89, 'staff_wait_mean': 0.63, 'dish_wait_mean': 0.82, 'pay_wait_mean': 6.99}

$ node test_page_v28.js serving-robot.v28e.html
rendered 37800 / レジが詰まるが出た画面 7083 / 回らないが出た画面 5911 / 5台以上の注意 10800
```

ページの全37,800画面を描き、欠け・undefined・NaN がないことを確かめてから、店の形・ページ・条件を1つの commit（p2m-site 536bd8e）で公開しました。


## 作業ログ ― 3Dの店に⑥を建て、1回ずつ回した

3Dの店の作業は、Mac の「依頼箱」（依頼の文を置くと、Mac で Claude Code が自動で実行して報告を書き戻す仕組み）に依頼を置いて進めました。台帳（3Dの店の回の記録の CSV）は、依頼の前に Claude が KIOXIA の archive に写して退避しています。

### 依頼文：⑥を建てて確かめる（20261028_21）

```markdown
---
作業フォルダ: /Users/shimazueiichi/Robotics_UX/ServiceSim
上限時間: 180
---
# 依頼：⑥ ゆったり・入口が左の壁の店（fam_p4）を Unity に建て、1回ずつ回して確かめる（2026-10-28）

エイイチの決定（2026-10-28）：⑤はここで区切り（LOG397）、次は⑥。④⑤のときと同じく、今回は⑥だけ。

サイト（正）はもう直した（p2m-site commit 536bd8e。店の形の版 d7bb919590d4 → 2e9fc773050a）。
- ⑥は建てる前に図面を見て、2か所を直した（エイイチと決めた）
  - ドリンクバーを左へ0.8m（16.0〜20.0m → 15.2〜19.2m）。右端と右の L字席 T38 の間 1.2m → 2.0m
  - レジを縦向きのまま右へ0.8m・下の壁まで（x 2.8〜4.4・y 0.7〜3.2 → x 3.6〜5.2・y 0〜2.5）。客は今までどおり入口の側に立ち、入口へ向かって並ぶ
- ドリンクバーに並ぶ点の作り方を1つ足した：真ん中の前の1つ目の点が置けないときだけ、ドリンクバーの前の幅の中で左右へずらす。⑥は前と同じ所（18.0, 5.4）に来る。②③④⑤⑦の店の形は版の番号だけ変わり、中身は同じ
- ⑥の試算だけ計算し直した（ほかの店の数字は変わっていない）
- `public/sim/serving-robot-params.json`：Unity で回す店に fam_p4 を足した。unity_conditions 12件 → 15件。③⑦④⑤の12件・common・unity_rules は前と同じ。meta.layout_generation は 2e9fc773050a
- ⑥の3条件：スタッフ3人・38組/時・ロボット0・1・2台・上げ膳・1往復3卓（0台は trays 1）・レジ1つ。38卓・158席
- 試算では、⑥の会計待ちは0台 12.1・1台 8.5・2台 7.0分（0台だけ「レジが詰まる」）
- 図面で分かっていて、直さずに残したこと（Unity で見る。これだけで手を止めなくてよい）
  - 待機場所が受け渡し口の前から1.2m（③⑦④と同じ形）
  - ドリンクバーの下の通路（6人席 T26〜T28 の前）は1.6mで、ロボットが止まると上下0.55mずつしか残らない
  - 会計の列（y 1.25）が待合の前の点（1.8, 0.9）の0.35m上に来る（案内を待つ客と会計に並ぶ客が重なり得る）
- 台帳の退避は Claude が済ませた：`/Volumes/KIOXIA/r2r2r-data/service_sim/unity/archive/unity_ledger_v1_pre_p4check_20261028/unity_ledger_v1.csv`（598行・md5 b8be20af5210641661f8b40b52fd120b）

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

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

## やること

1. サイトの params と、③⑦④⑤⑥の店の形（`public/sim/layouts/unity/` の p1・p5・p2・p3・p4 の `.json` と `_map.json`）を、今までと同じやり方でプロジェクトに写す（手で値を写さない）。generation が 2e9fc773050a でそろっているか確かめる。③⑦④⑤は、中身が前の写しと版の番号以外同じであることを確かめる
2. ⑥を③⑦④⑤と同じコード・同じ規則で建てる（地図の上で自分で道を引く動き方・すれ違い入・待機場所へ戻る 入・案B 切・店員優先のよけ方・逃がし弁・モニター、すべて今の標準のまま）。⑥のためだけの手直しはしない
   - ⑥の店の形のせいで建てられない・回らないところ（届かない卓、待機場所・レジ・並ぶ点が置けない、NavMesh の切れ目など）があっても、Unity 側で形を直さない。場所と理由を「決めてほしいこと」に書く（直すならサイトが先）
3. make_queue.py で `--stores fam_p4` が使えることを確かめる。**指定しないときは今までどおり ③⑦の60回**。judge_night.py は変えない（⑥は基準データがないので「基準なし」になってよい）
4. check-in して、画面なしの実行ファイルを作り直す（保留 0）
5. 確かめの3回を回す：⑥ × ロボット0・1・2台 × 種0、今の標準のまま。ラベル `p4check`。同時に回す本数は4のまま
   - 台帳には3行が足される（追記だけ）。終わったら行数（598 → 601 のはず）と、先頭598行の md5 が退避と同じかを確かめる
6. make_queue の dry-run で、何も指定しないとき③⑦の60回のままであることを確かめる（台帳に書かない）
7. 作業フォルダに queue の一時ファイルを残さない

## 変えないもの

- 店の回し方・人とロボットの動き・速さ・Stiffness などの物理の設定・同時に回す本数（N=4）
- 台帳の列（59列）と今までの行。今までの night/ と出力の記録
- 本番の夜は回さない（⑥の夜は、確かめの結果をエイイチが見てから決める）

## 報告

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

- 始める前の確かめ5つの結果
- 入れた cs 番号・build_id と保留の有無・変えたファイル
- ⑥を建てて分かったこと（卓の数38・止まる位置・待機場所・レジ・スタッフが待つ点・並ぶ点（レジとドリンクバー）が置けたか、置けなかった所）
- 3回それぞれ：最後まで回ったか・全員帰ったか・実時間・入れた組の数・入店待ち・そのうち席は空いているのに待った時間・料理の待ち・会計待ち・ロボットが運んだ皿の割合・往復の数・30秒以上の止まり（1往復あたり）・動けなくなった（60秒）・卓を空けた・瞬間移動・打ち切り。試算の同じ条件の値（params の site_result）と並べる。差の理由の調べはしなくてよい
- 2台の回の30秒以上の止まりの場所と理由：受け渡し口の点から 2.5 m 以内の数（待機場所での止まりを除いた数も）／ドリンクバーのまわり（x 14.8〜19.6・y 1.8〜5.65）の数／入口・レジのまわり（x が 5.2 m より左で、y が 5.0 m より下）の数／左の2本の通路（x 1.2〜2.7 と 5.25〜6.75、y 5.2〜14.5）の数
- 入口・レジのまわり（x が 5.2 m より左で、y が 2.5 m より下）で、人（客・スタッフ）が立ち止まった回数と主な理由（待合とレジの列の重なりを見るため。分かる範囲でよい）
- 台帳の行数と先頭598行の md5
- dry-run で③⑦が60回のままか
- **エイイチが画面で⑥を見る手順**（Unity を開いてから、どのシーン・メニューで⑥の、どの条件を流せるか。短く）
- 決めてほしいこと（なければ「なし」）
```

### 報告：20261028_1935_p4_build_check.txt

```text
====================================================================
⑥ ゆったり・入口が左の壁の店（fam_p4）を建てて、1回ずつ回して確かめた
依頼：20261028_21_店6を建てて確かめる.md
2026-10-28 19:35　check-in：cs:37・cs:38　build_id cs38_20261028_1923（保留 0）
====================================================================

結論

  ・始める前の確かめ5つは、すべて合格だった。
  ・公開版（版 2e9fc773050a）の params と ③⑦④⑤⑥の店の形を写した。版はすべてそろっていた。
    ③⑦④⑤は前の写しと版の番号以外同じ。params は fam_p4 と条件3件（12〜14 番）が足されただけ。
  ・⑥は③⑦④⑤と同じコードで建った（合わないもの なし）。卓 38・席 158、止まる位置 38 / 38 が NavMesh の上、
    受け渡し口から全卓へ道がつながり、入口から全卓の席へ歩ける。確かめ A も全卓・待機場所3つとも引けた。
  ・check-in して画面なしの実行ファイルを作り直し、確かめの3回（⑥ × 0・1・2台 × 種0）を回した。
    3回とも completed・全員帰った。打ち切りは 2台の回で 3。
  ・台帳 598 → 601 行（追記だけ）。先頭 598 行の md5 は退避と同じ。
  ・何も指定しない make_queue は③⑦の60回のまま。


====================================================================
1. 始める前の確かめ（5つとも合格。19:15 ごろ）
====================================================================

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


====================================================================
2. 写したもの（やること 1）
====================================================================

  写し元：https://pixelstomotors.com/sim/（2026-10-28 19:17 に curl で取得）
    控え：unity/site_copy_2e9fc773050a/（params・summary.json・unity/ の6店の JSON と地図・README.txt）
  版の番号：params の layout_generation、6店の JSON と地図の meta.generation、summary.json がすべて 2e9fc773050a
  プロジェクトの今の写し（cs:36）との違い
    p1・p5・p2・p3 の JSON と地図（8本）：版の番号以外の違い 0
    summary.json：版の番号以外の違い 0
    params：stores.fam_p4 と unity_conditions の 12〜14 番（⑥の 0・1・2 台）が足されただけ。
            common・unity_rules・0〜11 番・stores のほかの店は同じ。meta で変わったのは layout_generation だけ
  どれも cp でそのまま写した（手で値を写していない）。p4 の JSON と地図は新しく足した。


====================================================================
3. 入れた cs・build_id・変えたファイル
====================================================================

  check-in：cs:37 と cs:38（保留 0）
    cs:37 には足したファイル6つだけが入った。変えたファイル15件は、コマンドの渡し方の誤り（zsh でファイル名が
    1つにつながった）で入らなかったので、すぐに cs:38 として入れ直した。
    実行ファイルと3回は cs:38 から作ったので、cs:37 だけの状態では何も回していない。
  画面なしの実行ファイル：build_id cs38_20261028_1923 / unity_cs ServiceSim/cs:38 / pending_changes 空
                         Build Finished, Result: Success・error CS 0

  変えたファイル
    cs:37（足した）
      Assets/ServiceSim/Store/Layouts/p4_relaxed_side.json・_map.json（＋.meta）   公開版の写し
      Assets/ServiceSim/Store/StoreLayout_p4_relaxed_side.asset（＋.meta）         ⑤の .asset を写し、JSON・地図・名前・写し元を
                                                                                   差し替えた（通路の幅は JSON と同じ 1.5 m）
    cs:38（変えた）
      Assets/ServiceSim/Experiment/serving-robot-params.json   公開版の写し
      Assets/ServiceSim/Store/Layouts/summary.json・p1/p5/p2/p3 の JSON と地図   公開版の写し（版の番号だけ変わった）
      Assets/ServiceSim/Editor/ServiceSimSetup.cs       P4LayoutPath・HeadlessRun.layouts に⑥
      Assets/ServiceSim/Editor/ServiceSimSiteRun.cs     ⑥の3条件（unity_conditions の 12・13・14）
      Assets/ServiceSim/Editor/ServiceSimJsonStore.cs   「⑥ p4_relaxed_side を建てて確かめる」・版と試算の値の確かめに⑥
      Assets/ServiceSim/Editor/ServiceSimMapPlanner.cs  「⑥ 確かめ A」
      Assets/ServiceSim/Scenes/ServiceSim_Family.unity  HeadlessRun.layouts に⑥（1行）
    どれも⑤を足したときと同じ形（名前と番号の差し替えだけ）。⑥のためだけの手直しはしていない。

  手で戻したもの（check-in していない）
    建てて確かめる処理を回すと ProjectSettings/NavMeshAreas.asset が書き換わったので cm undo で戻した（④⑤と同じ）。
    Mat_RobotKeepout.mat も変更ありになったが中身は同じだったので戻した。

  make_queue.py・night_runner.py・judge_night.py は変えていない。


====================================================================
4. ⑥を建てて分かったこと（JSON Store「⑥ を建てて確かめる」・Map Planner「⑥ 確かめ A」）
====================================================================

  数         卓：Unity 38 / JSON 38 / summary.json 38 で一致。席 158 で一致
             細い通路 2 本（A01 x 1.2〜2.7・A02 x 5.25〜6.75、いずれも 1.5 m）。外寸 24 × 17 m
             地図のマスの数は JSON の counts と一致。合わないもの：なし
  止まる位置  38 か所すべてロボット用の NavMesh の上（乗らない 0）。地図でも 38 卓すべて通れるマス
  道順       受け渡し口から全 38 卓、すべての区間がつながった。
             JSON の dist との差は平均 −0.44 m・最大 1.0 m（T01）。30% を超えた卓は 0
  席         入口 (0.5, 2.2) から全 38 卓の席に着く点まで歩ける（入れない卓 0）
  場所の点    26 か所すべて NavMesh に乗った（乗らない 0）。置けなかった所はない
               受け渡し口・返却口・パントリー (14.3, 5.95)／入口 (0.5, 2.2)／待合の前 (1.8, 0.9)／トイレ (10.9, 14.2)
               レジ 1 (3.2, 1.25)・会計の列 3 (2.7 / 2.2 / 1.7, 1.25)・レジの内側 (4.75, 1.25)
                 レジの内側は人用・ロボット用とも受け渡し口から 11.06 m で行ける
               ドリンクバー（立つ点 4：x 15.7〜18.7・y 4.5／並ぶ点 1：(18.0, 5.4)）
               ロボットの待機場所 3：(15.9, 6.05)・(16.7, 6.05)・(17.5, 6.05)
                 地図では3つとも「通れる幅だけ」のマス（④⑤と同じ）。確かめ A で3つとも道が引けた
               スタッフが待つ点 6：x 12.75・y 6.81〜9.89（スタッフ3人なので1人1点）
  行き止まり  細い通路を1本ずつふさいでも、ほかの全卓に届く（ロボット・人とも）
  確かめ A   引けない卓 0 / 38。狭い道を使う卓 0（サイトの routes も 0 で一致）。
             長さの比 最小 0.964 / 平均 1.011 / 最大 1.044

  気づいたこと（直していない。調べてもいない）
    ・レジの内側：確かめが「入口（西のふち）0 m ← 0.7 m 未満」と出す。レジを下の壁まで寄せたので、
      西はレジ台・南は壁になり、入るのは上（y 2.5）からになる。受け渡し口から行けて、回でもレジの内側に
      入れなかった人は 0（register_back_intruders 0）。ただし会計の位置がずれた回が 1台 4・2台 1 ある
      （register_pay_off_spot）。確かめの「西のふち」の見方がこの形に合っていないだけかもしれない
    ・細い通路の人用の幅：A01・A02 とも焼いた面のいちばん狭いところ 0.98 m（計算上 1.1 m）で、確かめは
      「直す必要あり」と出す。ロボット用は計算どおり。行き止まり・席に入れるかの確かめは通っている
    ・ロボット用 NavMesh の「受け渡し口からつながる面」が 32.34 m2、「つながっていない面」が 118.63 m2 と出た
      （⑤は 0.8 m2）。人用は 159.07 m2 すべてつながっている。
      一方で、NavMesh の上の道の確かめ（全 38 卓つながる・届かない卓 0）は通っており、回でもロボットは全卓に運べた。
      この数え方は「頂点を共有する三角形」だけをつなぐので、面の切り方によっては少なく出る。
      本当に切れ目があるのかは調べていない
  書き出し（unity/screens/）
    20261028_p4_relaxed_side_json_check.txt / _table_dist.csv
    20261028_1919_map_planner_checkA_p4.txt / map_plan の CSV


====================================================================
5. 確かめの3回（⑥ × 0・1・2台 × 種0・すれ違い入・待機場所へ戻る入・案B切・ラベル p4check・同時 4 本）
====================================================================

  python3 tools/make_queue.py --stores fam_p4 --seeds 0 --label p4check --out /tmp/p4c/queue_p4check.json
  python3 tools/night_runner.py --queue /tmp/p4c/queue_p4check.json --parallel 4 --night 20261028
      --label p4check --record-name 20261028_p4check
  出力 20261028_p4check_cs38_fam_p4_r{0,1,2}_s0_passon_sbret
  記録 night/20261028_p4check/（runner 終了コード 0・completed 3・失敗 0）
  judge.md：基準なし 3（⑥の基準データがないため。依頼のとおり）

  左が Unity、右が試算（params の site_result）。単位は分。割合は 0〜1
                                   0台                 1台                 2台
  最後まで回ったか                 completed           completed           completed
  全員帰ったか                     はい                はい                はい
  実時間                           2.8 分              2.9 分              3.0 分
  （参考）店の時間                 174.8 分            175.0 分            171.8 分
  入れた組の数                     59 / 76.1           59 / 75.4           59 / 73.9
  入店待ち                         1.89 / 6.95         1.71 / 3.12         1.44 / 1.89
   うち席は空いているのに待った     0.09 / 3.71         0 / 0.72            0 / 0.40
  料理の待ち                       1.21 / 0.75         2.18 / 1.97         1.00 / 0.82
  会計待ち                         1.93 / 12.05        2.99 / 8.47         1.78 / 6.99
  ロボットが運んだ皿の割合         0 / 0               0.865 / 0.800       0.986 / 0.999
  往復の数                         0                   68                  127
  30秒以上の止まり                 0                   6                   23
   （1往復あたり）                 0                   0.088               0.181
  動けなくなった（60秒）           0                   6                   15
  卓を空けた（逃がし弁）           0                   0                   0
  瞬間移動                         0                   32                  37
  打ち切り                         0                   0                   3
  （参考）あきらめた               7                   23                  18
  （参考）冷めた皿の割合           0.027 / 0.0005      0.291 / 0.132       0.034 / 0.005
  （参考）待機場所へ戻った／出た   0 / 0               68 / 68             126 / 125（戻れず受け渡し口へ 0）
  （参考）ドリンクバーで並んだ／あきらめた  9 / 7      13 / 8              11 / 3

  目立つ点（差の理由は調べていない。依頼のとおり）
    ・入れた組の数が3回とも 59 組で、試算（74〜76 組）より少ない（⑤でも 71 対 91〜95 だった）
    ・会計待ちは Unity 1.8〜3.0 分で、試算の 0台 12.1 分のような「レジが詰まる」形にはならなかった
    ・1台の回は冷めた皿の割合が 0.29（試算 0.13）
    ・2台の回の 30 秒以上の止まりは 23 で、⑤（9）・往復あたりでも多い（下の 6 章）

  2台の回の 30 秒以上の止まり（23 回）の場所
    受け渡し口の点 (14.3, 5.95) から 2.5 m 以内            12 回（すべて「もう1台のロボット」）
     うち待機場所での止まりを除いた数                      3 回（3 回とも (14.79, 6.06)：受け渡し口と待機場所1の間）
       （待機場所＝店の形の RobotStandby の四角：x 15.5〜16.3・16.3〜17.1・17.1〜17.9、y 5.65〜6.45。
        この四角の中での止まりは 9 回。ほぼ待機場所1 (15.6〜15.9, 5.9〜6.06) で、うち 7 回は 60 秒の「動けなくなった」）
    ドリンクバーのまわり（x 14.8〜19.6・y 1.8〜5.65）      0 回
    入口・レジのまわり（x < 5.2・y < 5.0）                0 回
    左の2本の通路（x 1.2〜2.7 と 5.25〜6.75、y 5.2〜14.5） 2 回（A01 (2.21, 6.86) と A02 の上の端 (6.42, 13.45)。どちらも客とすれ違った）
    上のどれにも入らない所                                 9 回
      (8.5〜9.3, 13.6〜14.4)：トイレの入口の前 (10.9, 14.2) の左。もう1台のロボット 1・スタッフ 2・人が立ち止まっていた 4・客 1、
      (6.90, 4.94)：A02 の下の入口の下。もう1台のロボット 1
    理由ごと（23 回）：もう1台のロボット 14・人が立ち止まっていた 4・お客さんとすれ違った 3・スタッフとすれ違った 2

  入口・レジのまわり（x < 5.2・y < 2.5）で人（客・スタッフ）が立ち止まった回数
    人どうしで詰まった（people_jams）：0台 0・1台 0・2台 0
    スタッフがロボットに道をゆずって待った（yields）：0台 0・1台 4（計 126 秒）・2台 5（計 86 秒）
      場所はどれも (1.5〜2.1, 2.22) あたり（待合の前・会計の列の上）。ロボットの通った跡はこの範囲に入っていない
    あきらめた・移した（give_ups）：1台の回で 2（スタッフが案内を 60 秒であきらめた 1・会計の客が並ぶ点を移した 1、
      どちらも (2.2〜2.6, 2.22) あたり）。0台・2台は 0
    客どうしの接触・詰まりとして記録されたものはなく、待合の客と会計の列の重なりは、
    今の記録の上では目立った止まりとしては出ていない（レジのまわりの人数は最大 8〜9 人・平均 2.1〜2.3 人）


====================================================================
6. 台帳
====================================================================

  行数：598 → 601（3 行足した。追記だけ。3行とも unity_cs ServiceSim/cs:38・layout_generation 2e9fc773050a）
  先頭 598 行の md5：b8be20af5210641661f8b40b52fd120b（退避と同じ）
  列は今までどおり（59 列）。鍵ファイルは残っていない


====================================================================
7. make_queue（台帳には書いていない）
====================================================================

  --stores fam_p4 --seeds 0 --label p4check → fam_p4_s3_r{0,1,2}_a38 の 3 回
    （0台 trays 1・1台と2台 trays 3・レジ 1・すれ違い入・待機場所へ戻る入・案B切）。make_queue.py はコードを変えずに使えた
  何も指定しない（cs:38 のあとで /tmp に作った）
    → fam_p1・fam_p5 × 0・1・2 台 × 種 0〜9 ＝ 60 回。
       night/20261102/queue.json（v5 の夜）と、回の並びと中身が同じ（label を除く）
  作業フォルダに queue の一時ファイルは置いていない（/tmp/p4c/ にだけ作った）。最後の保留 0（cs:38 head）。


====================================================================
8. エイイチが画面で⑥を見る手順
====================================================================

  1. Unity Hub から ServiceSim（/Users/shimazueiichi/Robotics_UX/ServiceSim）を開き、
     シーン Assets/ServiceSim/Scenes/ServiceSim_Family.unity を開く
  2. 建てるだけで見る：ServiceSim → JSON Store → ⑥ p4_relaxed_side を建てて確かめる
  3. 回して見る：ServiceSim → Site Run → ⑥ スタッフ3人・ロボット0台 / 1台 / 2台
     Play に入って回り、終わると自動で抜ける。すれ違い入・待機場所へ戻る入・案B切（今の標準）。
     種は ExperimentPlan の trialSeed。出力は KIOXIA に出る（台帳には書かない）
  ※ 待機場所1での止まりを見るなら 2台の回（店の時間で 28 分・36 分・113 分・156 分ごろ）


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

  1. ⑥の夜を回すかどうか（依頼のとおり、本番の夜は回していない）。
  2. 2台の回で、30 秒以上の止まり 23 のうち 9 が待機場所1の中、3 が受け渡し口と待機場所1の間で、
     すべて「もう1台のロボット」（60 秒で動けなくなった 7 を含む）。待機場所が受け渡し口の前から 1.2 m の形
     （図面で分かっていて残したこと）が効いているように見える。このまま夜を回すか、先にサイトの形を見直すか。
  3. 入れた組の数が 59 組で、試算（74〜76 組）より少ない。会計待ちも試算のように詰まらなかった（⑤と同じ傾向）。
  4. 4 章の気づいたこと（レジの内側の「西のふち 0 m」、細い通路の人用の幅 0.98 m、
     ロボット用 NavMesh の「つながっていない面」118.63 m2）を調べるかどうか。
     どれも回は最後まで回っているが、店の形のことなので、直すならサイトが先。
```

### ⑥は建て直しなしで建ち、2台の止まりは受け渡し口と待機場所の間に集まった

⑥は③⑦④⑤と同じコードで建ち、⑥のための手直しは要りませんでした。⑤では、建てる前の確かめで通路の端の点が外壁の上にあることが分かり、サイトの作り方を直して2回建て直しています。

1回ずつの3回では、入れた組の数が3回とも59組で、試算（74〜76組）より少なくなりました。⑤でも1回目は71組と少なく、夜の種10個で見ると種0だけのことでした。2台の回の30秒以上の止まり23回のうち12回は受け渡し口の点から2.5m以内で、理由はすべて「もう1台のロボット」でした。そのうち9回は待機場所の中です。

建てたときの確かめで、3つの表示が出ました。レジを下の壁まで寄せたので入るのが上からだけになったレジの内側、人が歩く NavMesh（歩ける床の面）で細い通路のいちばん狭いところが0.98mになったこと、ロボット用の NavMesh で受け渡し口とつながっていない面が大きく出たことです。どれも回は最後まで回り、ロボットは全卓に料理を運べています。⑤と同じく、記録だけにしました。


## 作業ログ ― ⑥を種10個ずつ、計30回回した

確かめの3回を見て、⑥の夜（ロボット0・1・2台 × 種0〜9 の30回）を回すことにしました。受け渡し口と待機場所の間の詰まりは、待機場所と受け渡し口の距離が③⑦④と同じ形なので、⑥だけ直さずに夜の数で見ます。

### 依頼文：⑥の夜を回す（20261028_22）

````markdown
---
作業フォルダ: /Users/shimazueiichi/Robotics_UX/ServiceSim
上限時間: 180
---
# 依頼：⑥の夜（30回）を回す（2026-10-28）

エイイチが「⑥の夜を回す」と決めた（2026-10-28 19:52）。⑥（fam_p4・店の形 2e9fc773050a）× ロボット0・1・2台 × 種0〜9、今の標準（すれ違い入・待機場所へ戻る 入・案B 切）。
依頼 21 の報告（20261028_1935_p4_build_check.txt）の「決めてほしいこと」への答え：
- 1：回す（この依頼）
- 2：受け渡し口と待機場所1の間の詰まりは、今は店の形を直さず、この夜の種10個で数を見る（待機場所と受け渡し口の距離は③⑦④も同じ形のため）
- 3：入れた組の数が59組と少ない件は、先に調べず、この夜の種10個で見る
- 4：建てたときの3つの表示（レジの内側の「西のふち 0 m」、細い通路の人用の幅 0.98 m、ロボット用 NavMesh の「つながっていない面」118.63 m2）は、今は直さず記録だけ

台帳の退避は Claude が済ませた。
- 退避先：/Volumes/KIOXIA/r2r2r-data/service_sim/unity/archive/unity_ledger_v1_prenight_20261104_p4/unity_ledger_v1.csv
- 601 行・md5 bd712cbae6008066b75da7d91670a360

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

1. Unity エディタが起動していない（起動していたら閉じずに報告して終わる）
2. /Volumes/KIOXIA がマウントされている。鍵ファイル unity_ledger_v1.lock がない
3. 本物の台帳の md5 が上の退避と同じ
4. ServiceSim の保留が 0 で、実行ファイルの build_info が cs38_20261028_1923・保留なし

## 回す

作業フォルダで、次の2つを順に実行する。night_runner.py は終わるまで待つ。

```
python3 tools/make_queue.py --stores fam_p4 --out queue_p4.json
python3 tools/night_runner.py --queue queue_p4.json --parallel 4 --night 20261104 --record-name 20261104_p4
```

- 夜の名前は 20261104。記録は night/20261104_p4/ に置く。オプションの名前が違えば、同じ意味になるように合わせる
- 並ぶのは 30 回（⑥ × 0・1・2台 × 種0〜9・すれ違い入・待機場所へ戻る 入・案B 切）であることを、回す前に queue_p4.json で確かめる。30 回でなければ回さずに報告
- 回している間は、ほかの作業をしない。Assets・tools・設定ファイルを開かない・変えない。Unity を開かない
- Ctrl+C で止めない。途中で止まっても、同じ夜を回し直さない（そのまま報告する）
- 終わったら queue_p4.json を作業フォルダに残さない（night/20261104_p4/ に写しがあればそれでよい）

## 報告

依頼箱の結果に加えて、claude_code_reports に 20261104_night_p4.txt として書く（場所：~/Library/Mobile Documents/com~apple~CloudDocs/デスクトップ/ADAPT DESIGN/じぶんの/PixelstoMotors/デジタルツイン学習R2R2R/サービスロボット導入シミュレーション/unity/claude_code_reports/）。
- 始める前の確かめ4つの結果
- runner_log のまとめ（completed の回数・失敗・止まった回・最長の実時間）
- 台帳の行数（601 → 631 になるはず）と、先頭 601 行の md5 が退避と同じか
- 全員が帰らなかった回があれば、その回の名前（原因の調べはしない）
- 台数ごと（0・1・2台）の種10個の中央値と最小・最大：入店待ち・そのうち席は空いているのに待った時間・料理の待ち・会計待ち・入れた組の数・ロボットが運んだ皿の割合・往復の数・30秒以上の止まり（1往復あたり）・動けなくなった（60秒）・瞬間移動・打ち切り・卓を空けた・冷めた皿の割合。試算の値（params の site_result）も並べる
- 種ごとの入れた組の数（種0〜9。台数で変わらなければ1行でよい）と、試算の組の数（約74〜76組）との比
- 2台の30秒以上の止まりを、理由ごと・場所ごとに、種10個の合計で。場所は次の分け方（重なる所は上の方に数える）
  - 待機場所の四角の中（x 15.5〜17.9・y 5.65〜6.45）
  - 受け渡し口の点 (14.3, 5.95) から 2.5 m 以内（待機場所の中を除く）
  - ドリンクバーのまわり（x 14.8〜19.6・y 1.8〜5.65）
  - 入口・レジのまわり（x が 5.2 m より左で、y が 5.0 m より下）
  - 左の2本の通路（x 1.2〜2.7 と 5.25〜6.75、y 5.2〜14.5）
  - スタッフが待つ点の近く 1.0 m 以内
  - そのほか（多い所があれば 2 m 四方のマスで上位3つ）
- 入口・レジのまわり（x が 5.2 m より左で、y が 2.5 m より下）で、スタッフがロボットに道をゆずって待った回数・人どうしの詰まり・あきらめた／移した回数（種10個の合計。台数ごと）
- ロボットを1台増やしたときの向き（入店待ち・料理の待ち）が、種の過半数で試算と同じか逆か（judge と同じ数え方）
- judge.md ができたか（⑥は基準がないので「基準なし」でよい。中身の判断はしない）
````

### 夜の実行ログ runner_log.txt

```text
2026-10-28 19:54:48  === 夜の実行係を始めます（night 20261104・同時 4 本・上限 90.0 分）===
2026-10-28 19:54:48  並べた回: queue_p4.json（30 回）
2026-10-28 19:54:48  台帳: /Volumes/KIOXIA/r2r2r-data/service_sim/unity/unity_ledger_v1.csv（59 列）
2026-10-28 19:54:48  版: ServiceSim/cs:38（Builds/headless/build_info.json）→ 出力フォルダの字 cs38
2026-10-28 19:54:48  並べた回の試算の版: v2.8 / 店の形の版: 2e9fc773050a
2026-10-28 19:54:48  スリープを止めました（caffeinate pid 98277）
2026-10-28 19:54:48  並べた回を写しました: /Volumes/KIOXIA/r2r2r-data/service_sim/unity/night/20261104_p4/queue.json
2026-10-28 19:54:48  並べた回 30 / もう completed の回を飛ばす 0 / 回す 30
2026-10-28 19:54:48  始めました（1本目・同時 1）: fam_p4_s3_r0_a38 種0 すれ違い入 決まり案B切 待機場所へ戻る入 → 20261104_night_cs38_fam_p4_r0_s0_passon_sbret（pid 98278）
2026-10-28 19:54:53  始めました（2本目・同時 2）: fam_p4_s3_r1_a38 種0 すれ違い入 決まり案B切 待機場所へ戻る入 → 20261104_night_cs38_fam_p4_r1_s0_passon_sbret（pid 98293）
2026-10-28 19:54:58  始めました（3本目・同時 3）: fam_p4_s3_r2_a38 種0 すれ違い入 決まり案B切 待機場所へ戻る入 → 20261104_night_cs38_fam_p4_r2_s0_passon_sbret（pid 98305）
2026-10-28 19:55:03  始めました（4本目・同時 4）: fam_p4_s3_r0_a38 種1 すれ違い入 決まり案B切 待機場所へ戻る入 → 20261104_night_cs38_fam_p4_r0_s1_passon_sbret（pid 98326）
2026-10-28 19:58:03  おわり: 20261104_night_cs38_fam_p4_r0_s0_passon_sbret / completed / 終了コード 0 / 実時間 3.3 分
2026-10-28 19:58:03  始めました（5本目・同時 4）: fam_p4_s3_r1_a38 種1 すれ違い入 決まり案B切 待機場所へ戻る入 → 20261104_night_cs38_fam_p4_r1_s1_passon_sbret（pid 98472）
2026-10-28 19:58:23  おわり: 20261104_night_cs38_fam_p4_r1_s0_passon_sbret / completed / 終了コード 0 / 実時間 3.5 分
2026-10-28 19:58:23  始めました（6本目・同時 4）: fam_p4_s3_r2_a38 種1 すれ違い入 決まり案B切 待機場所へ戻る入 → 20261104_night_cs38_fam_p4_r2_s1_passon_sbret（pid 98483）
2026-10-28 19:58:43  おわり: 20261104_night_cs38_fam_p4_r2_s0_passon_sbret / completed / 終了コード 0 / 実時間 3.8 分
2026-10-28 19:58:43  始めました（7本目・同時 4）: fam_p4_s3_r0_a38 種2 すれ違い入 決まり案B切 待機場所へ戻る入 → 20261104_night_cs38_fam_p4_r0_s2_passon_sbret（pid 98492）
2026-10-28 19:58:53  おわり: 20261104_night_cs38_fam_p4_r0_s1_passon_sbret / completed / 終了コード 0 / 実時間 3.8 分
2026-10-28 19:58:53  始めました（8本目・同時 4）: fam_p4_s3_r1_a38 種2 すれ違い入 決まり案B切 待機場所へ戻る入 → 20261104_night_cs38_fam_p4_r1_s2_passon_sbret（pid 98497）
2026-10-28 20:01:53  おわり: 20261104_night_cs38_fam_p4_r1_s1_passon_sbret / completed / 終了コード 0 / 実時間 3.8 分
2026-10-28 20:01:53  始めました（9本目・同時 4）: fam_p4_s3_r2_a38 種2 すれ違い入 決まり案B切 待機場所へ戻る入 → 20261104_night_cs38_fam_p4_r2_s2_passon_sbret（pid 98609）
2026-10-28 20:02:13  おわり: 20261104_night_cs38_fam_p4_r2_s1_passon_sbret / completed / 終了コード 0 / 実時間 3.8 分
2026-10-28 20:02:13  始めました（10本目・同時 4）: fam_p4_s3_r0_a38 種3 すれ違い入 決まり案B切 待機場所へ戻る入 → 20261104_night_cs38_fam_p4_r0_s3_passon_sbret（pid 98627）
2026-10-28 20:03:23  おわり: 20261104_night_cs38_fam_p4_r0_s2_passon_sbret / completed / 終了コード 0 / 実時間 4.7 分
2026-10-28 20:03:23  始めました（11本目・同時 4）: fam_p4_s3_r1_a38 種3 すれ違い入 決まり案B切 待機場所へ戻る入 → 20261104_night_cs38_fam_p4_r1_s3_passon_sbret（pid 98668）
2026-10-28 20:03:33  おわり: 20261104_night_cs38_fam_p4_r1_s2_passon_sbret / completed / 終了コード 0 / 実時間 4.7 分
2026-10-28 20:03:33  始めました（12本目・同時 4）: fam_p4_s3_r2_a38 種3 すれ違い入 決まり案B切 待機場所へ戻る入 → 20261104_night_cs38_fam_p4_r2_s3_passon_sbret（pid 98687）
2026-10-28 20:06:08  おわり: 20261104_night_cs38_fam_p4_r0_s3_passon_sbret / completed / 終了コード 0 / 実時間 3.9 分
2026-10-28 20:06:08  始めました（13本目・同時 4）: fam_p4_s3_r0_a38 種4 すれ違い入 決まり案B切 待機場所へ戻る入 → 20261104_night_cs38_fam_p4_r0_s4_passon_sbret（pid 98832）
2026-10-28 20:06:53  おわり: 20261104_night_cs38_fam_p4_r2_s2_passon_sbret / completed / 終了コード 0 / 実時間 5.0 分
2026-10-28 20:06:53  始めました（14本目・同時 4）: fam_p4_s3_r1_a38 種4 すれ違い入 決まり案B切 待機場所へ戻る入 → 20261104_night_cs38_fam_p4_r1_s4_passon_sbret（pid 98860）
2026-10-28 20:07:08  おわり: 20261104_night_cs38_fam_p4_r1_s3_passon_sbret / completed / 終了コード 0 / 実時間 3.8 分
2026-10-28 20:07:08  始めました（15本目・同時 4）: fam_p4_s3_r2_a38 種4 すれ違い入 決まり案B切 待機場所へ戻る入 → 20261104_night_cs38_fam_p4_r2_s4_passon_sbret（pid 98867）
2026-10-28 20:07:28  おわり: 20261104_night_cs38_fam_p4_r2_s3_passon_sbret / completed / 終了コード 0 / 実時間 3.9 分
2026-10-28 20:07:28  始めました（16本目・同時 4）: fam_p4_s3_r0_a38 種5 すれ違い入 決まり案B切 待機場所へ戻る入 → 20261104_night_cs38_fam_p4_r0_s5_passon_sbret（pid 98875）
2026-10-28 20:10:23  おわり: 20261104_night_cs38_fam_p4_r0_s4_passon_sbret / completed / 終了コード 0 / 実時間 4.3 分
2026-10-28 20:10:23  始めました（17本目・同時 4）: fam_p4_s3_r1_a38 種5 すれ違い入 決まり案B切 待機場所へ戻る入 → 20261104_night_cs38_fam_p4_r1_s5_passon_sbret（pid 98990）
2026-10-28 20:10:43  おわり: 20261104_night_cs38_fam_p4_r0_s5_passon_sbret / completed / 終了コード 0 / 実時間 3.3 分
2026-10-28 20:10:43  始めました（18本目・同時 4）: fam_p4_s3_r2_a38 種5 すれ違い入 決まり案B切 待機場所へ戻る入 → 20261104_night_cs38_fam_p4_r2_s5_passon_sbret（pid 98998）
2026-10-28 20:11:08  おわり: 20261104_night_cs38_fam_p4_r1_s4_passon_sbret / completed / 終了コード 0 / 実時間 4.3 分
2026-10-28 20:11:08  始めました（19本目・同時 4）: fam_p4_s3_r0_a38 種6 すれ違い入 決まり案B切 待機場所へ戻る入 → 20261104_night_cs38_fam_p4_r0_s6_passon_sbret（pid 99015）
2026-10-28 20:11:49  おわり: 20261104_night_cs38_fam_p4_r2_s4_passon_sbret / completed / 終了コード 0 / 実時間 4.7 分
2026-10-28 20:11:49  始めました（20本目・同時 4）: fam_p4_s3_r1_a38 種6 すれ違い入 決まり案B切 待機場所へ戻る入 → 20261104_night_cs38_fam_p4_r1_s6_passon_sbret（pid 99035）
2026-10-28 20:13:59  おわり: 20261104_night_cs38_fam_p4_r1_s5_passon_sbret / completed / 終了コード 0 / 実時間 3.6 分
2026-10-28 20:13:59  始めました（21本目・同時 4）: fam_p4_s3_r2_a38 種6 すれ違い入 決まり案B切 待機場所へ戻る入 → 20261104_night_cs38_fam_p4_r2_s6_passon_sbret（pid 99140）
2026-10-28 20:14:29  おわり: 20261104_night_cs38_fam_p4_r2_s5_passon_sbret / completed / 終了コード 0 / 実時間 3.8 分
2026-10-28 20:14:29  始めました（22本目・同時 4）: fam_p4_s3_r0_a38 種7 すれ違い入 決まり案B切 待機場所へ戻る入 → 20261104_night_cs38_fam_p4_r0_s7_passon_sbret（pid 99190）
2026-10-28 20:16:09  おわり: 20261104_night_cs38_fam_p4_r0_s6_passon_sbret / completed / 終了コード 0 / 実時間 5.0 分
2026-10-28 20:16:09  始めました（23本目・同時 4）: fam_p4_s3_r1_a38 種7 すれ違い入 決まり案B切 待機場所へ戻る入 → 20261104_night_cs38_fam_p4_r1_s7_passon_sbret（pid 99268）
2026-10-28 20:16:49  おわり: 20261104_night_cs38_fam_p4_r1_s6_passon_sbret / completed / 終了コード 0 / 実時間 5.0 分
2026-10-28 20:16:49  始めました（24本目・同時 4）: fam_p4_s3_r2_a38 種7 すれ違い入 決まり案B切 待機場所へ戻る入 → 20261104_night_cs38_fam_p4_r2_s7_passon_sbret（pid 99295）
2026-10-28 20:18:34  おわり: 20261104_night_cs38_fam_p4_r0_s7_passon_sbret / completed / 終了コード 0 / 実時間 4.1 分
2026-10-28 20:18:34  始めました（25本目・同時 4）: fam_p4_s3_r0_a38 種8 すれ違い入 決まり案B切 待機場所へ戻る入 → 20261104_night_cs38_fam_p4_r0_s8_passon_sbret（pid 99379）
2026-10-28 20:19:14  おわり: 20261104_night_cs38_fam_p4_r2_s6_passon_sbret / completed / 終了コード 0 / 実時間 5.3 分
2026-10-28 20:19:14  始めました（26本目・同時 4）: fam_p4_s3_r1_a38 種8 すれ違い入 決まり案B切 待機場所へ戻る入 → 20261104_night_cs38_fam_p4_r1_s8_passon_sbret（pid 99423）
2026-10-28 20:20:04  おわり: 20261104_night_cs38_fam_p4_r1_s7_passon_sbret / completed / 終了コード 0 / 実時間 3.9 分
2026-10-28 20:20:04  始めました（27本目・同時 4）: fam_p4_s3_r2_a38 種8 すれ違い入 決まり案B切 待機場所へ戻る入 → 20261104_night_cs38_fam_p4_r2_s8_passon_sbret（pid 99474）
2026-10-28 20:20:54  おわり: 20261104_night_cs38_fam_p4_r2_s7_passon_sbret / completed / 終了コード 0 / 実時間 4.1 分
2026-10-28 20:20:54  始めました（28本目・同時 4）: fam_p4_s3_r0_a38 種9 すれ違い入 決まり案B切 待機場所へ戻る入 → 20261104_night_cs38_fam_p4_r0_s9_passon_sbret（pid 99520）
2026-10-28 20:22:29  おわり: 20261104_night_cs38_fam_p4_r0_s8_passon_sbret / completed / 終了コード 0 / 実時間 3.9 分
2026-10-28 20:22:29  始めました（29本目・同時 4）: fam_p4_s3_r1_a38 種9 すれ違い入 決まり案B切 待機場所へ戻る入 → 20261104_night_cs38_fam_p4_r1_s9_passon_sbret（pid 99578）
2026-10-28 20:23:14  おわり: 20261104_night_cs38_fam_p4_r1_s8_passon_sbret / completed / 終了コード 0 / 実時間 4.0 分
2026-10-28 20:23:14  始めました（30本目・同時 4）: fam_p4_s3_r2_a38 種9 すれ違い入 決まり案B切 待機場所へ戻る入 → 20261104_night_cs38_fam_p4_r2_s9_passon_sbret（pid 99604）
2026-10-28 20:24:19  おわり: 20261104_night_cs38_fam_p4_r2_s8_passon_sbret / completed / 終了コード 0 / 実時間 4.3 分
2026-10-28 20:25:04  おわり: 20261104_night_cs38_fam_p4_r0_s9_passon_sbret / completed / 終了コード 0 / 実時間 4.2 分
2026-10-28 20:26:04  おわり: 20261104_night_cs38_fam_p4_r1_s9_passon_sbret / completed / 終了コード 0 / 実時間 3.6 分
2026-10-28 20:26:44  おわり: 20261104_night_cs38_fam_p4_r2_s9_passon_sbret / completed / 終了コード 0 / 実時間 3.5 分
2026-10-28 20:26:44  --- まとめ ---
2026-10-28 20:26:44    completed: 30 回
2026-10-28 20:26:44    completed の実時間: 平均 4.1 分 / 最長 5.3 分
2026-10-28 20:26:44  判定を呼びます（/Volumes/KIOXIA/r2r2r-data/service_sim/unity で）: /opt/homebrew/Caskroom/miniforge/base/bin/python3 /Users/shimazueiichi/Robotics_UX/ServiceSim/tools/judge_night.py --runs 20261104_night_* --out night/20261104_p4 --label-prefix 20261104_night
2026-10-28 20:26:45  判定を書きました: /Volumes/KIOXIA/r2r2r-data/service_sim/unity/night/20261104_p4/judge.md
2026-10-28 20:26:45  スリープの設定を戻しました
2026-10-28 20:26:45  === 夜の実行係を終わります（終了コード 0）===
```

### 判定 judge.md

```markdown
# 夜の結果の振り分け（20261104_night_*）

基準データ: 20261102_night_cs34_*（60回）／振り分けた回: 30回

切り替えの入切（条件の一部。同じ入切の基準の回とだけ比べる）: 今回 すれ違い入・待機場所へ戻る入・案B切／基準 すれ違い入・待機場所へ戻る入・案B切

| 振り分け | 回数 |
|---|---|
| 正常 | 0 |
| 試算を見直す候補 | 0 |
| Unity の作り | 0 |
| 回の失敗 | 0 |
| 基準なし | 30 |

## 回の失敗

- なし

## 基準なし（基準データに同じ条件・同じ入切の回がないので判定していない）

- fam_p4 スタッフ3 ロボット0台・すれ違い入・待機場所へ戻る入・案B切
- fam_p4 スタッフ3 ロボット1台・すれ違い入・待機場所へ戻る入・案B切
- fam_p4 スタッフ3 ロボット2台・すれ違い入・待機場所へ戻る入・案B切

## Unity の作り（種の中央値が、基準の同じ条件の最大を超えた）

- なし

## 試算を見直す候補

### ロボットを増やしたときの向きが、種の過半数で試算と逆

- なし

### Unity÷試算 の大きさが、基準の同じ条件の範囲から外れた

- なし

## 記録（印にしない）：試算との差の大きさ（条件ごとの種の中央値・Unity÷試算）

| 条件 | 種の数 | 入店待ち | 料理の待ち | 席は空いているのに待った |
|---|---|---|---|---|
| fam_p4 スタッフ3 ロボット0台・すれ違い入・待機場所へ戻る入・案B切 | 10 | 1.38 | 1.49 | 2.00 |
| fam_p4 スタッフ3 ロボット1台・すれ違い入・待機場所へ戻る入・案B切 | 10 | 1.94 | 1.06 | 4.82 |
| fam_p4 スタッフ3 ロボット2台・すれ違い入・待機場所へ戻る入・案B切 | 10 | 2.55 | 1.56 | 5.53 |

## すれ違い 入 と 切 の比べ（同じ夜・同じ条件・種の中央値。印にしない）

| 条件 | 入店待ち | 料理の待ち | 席は空いているのに待った | 動けなくなった | あきらめた | ロボットが運んだ割合 |
|---|---|---|---|---|---|---|
| fam_p4 スタッフ3 ロボット0台・すれ違い切・待機場所へ戻る入・案B切（0回） | - | - | - | - | - | - |
| fam_p4 スタッフ3 ロボット0台・すれ違い入・待機場所へ戻る入・案B切（10回） | 9.60 | 1.12 | 8.42 | 0.00 | 7.00 | 0.00 |
| fam_p4 スタッフ3 ロボット1台・すれ違い切・待機場所へ戻る入・案B切（0回） | - | - | - | - | - | - |
| fam_p4 スタッフ3 ロボット1台・すれ違い入・待機場所へ戻る入・案B切（10回） | 6.04 | 2.09 | 4.84 | 1.00 | 19.00 | 0.83 |
| fam_p4 スタッフ3 ロボット2台・すれ違い切・待機場所へ戻る入・案B切（0回） | - | - | - | - | - | - |
| fam_p4 スタッフ3 ロボット2台・すれ違い入・待機場所へ戻る入・案B切（10回） | 4.83 | 1.27 | 3.49 | 8.00 | 28.50 | 0.96 |

線: 立ち往生 30秒以上の止まり／向きは 5%・0.2分より小さい変化を数えない／外れは基準の同じ条件の最小〜最大
```

### 報告：20261104_night_p4.txt

```text
⑥の夜（20261104_p4・30回）の報告 — Claude Code（2026-10-28 20:35）
依頼：20261028_22_店6の夜を回す.md
版：cs38_20261028_1923・店の形 2e9fc773050a・試算 v2.8・すれ違い入・待機場所へ戻る 入・案B 切・スタッフ3人・38組/時・レジ1つ
記録：/Volumes/KIOXIA/r2r2r-data/service_sim/unity/night/20261104_p4/（runner_log.txt・runs.csv・queue.json・judge.md・judge.csv・logs）

■ 始める前の確かめ（4つとも合格）
1 Unity エディタ：起動していない（Unity Hub だけが起動していた。エディタ本体のプロセスはなし）
2 KIOXIA：マウントあり。unity_ledger_v1.lock なし
3 本物の台帳：601 行・md5 bd712cbae6008066b75da7d91670a360（退避と同じ）
4 cm status：保留 0。build_info：build_id cs38_20261028_1923・pending_changes 空
並び：queue_p4.json で 30 回（fam_p4 × 0・1・2台 × 種0〜9・すれ違い入・待機場所へ戻る入・案B切）を確かめてから回した
回したコマンド：依頼のとおり（オプション名の読みかえなし）

■ runner_log のまとめ
19:54:48 開始 → 20:26:44 終了（終了コード 0）
completed 30 回／失敗 0／止まった回 0／やり直し 0
実時間 平均 4.1 分・最長 5.3 分（r2 種6）
queue_p4.json は night/20261104_p4/queue.json と中身が同じことを確かめてから作業フォルダから消した

■ 台帳
601 → 631 行。先頭 601 行の md5 bd712cbae6008066b75da7d91670a360（退避と同じ）。終了後に鍵ファイルなし

■ 全員が帰らなかった回：なし（30 回とも everyone_left = 1）

■ 台数ごと：種10個の中央値（最小〜最大）／試算（params の site_result）
                  0台                        1台                        2台
入店待ち(分)      9.60(1.58〜21.84)/6.95     6.04(1.57〜14.55)/3.12     4.83(1.53〜14.66)/1.89
席空き待ち(分)    8.42(0.44〜16.38)/4.20     4.84(0.43〜9.05)/1.00      3.49(0.37〜10.20)/0.63
料理の待ち(分)    1.12(1.01〜1.27)/0.75      2.09(1.84〜2.49)/1.97      1.27(1.01〜1.83)/0.82
会計待ち(分)      3.03(1.89〜3.92)/12.05     3.00(2.06〜5.16)/8.47      3.31(2.09〜4.78)/6.99
入れた組          73.5(59〜88)/76.12         73.5(59〜88)/75.40         73.5(59〜88)/73.92
運んだ割合        0/0                        0.83(0.79〜0.87)/0.80      0.96(0.89〜0.99)/1.00
往復              0                          79(66〜91)                 137.5(123〜163)
30秒以上の止まり/往復 -                      0.03(0.00〜0.10)           0.09(0.03〜0.14)
 （回数）         0                          2.5(0〜9)                  12.5(5〜18)
動けなくなった(60秒) 0                       1(0〜5)                    8(3〜16)
瞬間移動          1(0〜4)                    25.5(20〜35)               28.5(17〜41)
打ち切り          0                          0(0〜1)                    1.5(0〜7)
卓を空けた        0                          0                          0(0〜1)
冷めた皿の割合    0.01(0.00〜0.05)/0.00      0.21(0.13〜0.31)/0.13      0.07(0.03〜0.14)/0.00
（席空き待ち＝台帳の staff_wait_mean。往復・止まり・動けなくなった・瞬間移動・打ち切り・卓を空けたは試算に同じ名前の値がないので試算の欄なし）

■ 種ごとの入れた組の数（台数で変わらない）
種0〜9：59, 74, 87, 70, 81, 63, 88, 72, 73, 83（中央値 73.5）
試算 73.92〜76.12 との比：0.78〜0.80, 0.97〜1.00, 1.14〜1.18, 0.92〜0.95, 1.06〜1.10, 0.83〜0.85, 1.16〜1.19, 0.95〜0.97, 0.96〜0.99, 1.09〜1.12（中央値で 0.97〜0.99）
→ 依頼 21 で 59 組だったのは種0 のこと

■ 2台の30秒以上の止まり（種10個の合計 122）
場所：待機場所の四角の中 60／受け渡し口の点から2.5m以内 9／ドリンクバーのまわり 5／入口・レジのまわり 1／左の2本の通路 7／スタッフが待つ点の近く 0／そのほか 40
理由：もう1台のロボット 77／スタッフとすれ違った 17／お客さんとすれ違った 15／人が立ち止まっていた 12／分からない 1
場所ごとの理由：
  待機場所：もう1台のロボット 59・お客さん 1
  受け渡し口2.5m：スタッフ 7・もう1台 1・お客さん 1
  ドリンクバー：もう1台 4・人が立ち止まっていた 1
  入口・レジ：お客さん 1
  左の2本の通路：お客さん 5・人が立ち止まっていた 1・スタッフ 1
  そのほか：もう1台 13・人が立ち止まっていた 10・スタッフ 9・お客さん 7・分からない 1
そのほかの 2m 四方のマスで多い所：x6〜8・y4〜6 27（人が立ち止まっていた 9・スタッフ 7・お客さん 6・もう1台 4・分からない 1）／x20〜22・y2〜4 8（すべてもう1台）／x8〜10・y12〜14 2
（スタッフが待つ点は店の形の StaffStandby 6点すべてで数えた。座標の y は記録の z）

■ 入口・レジのまわり（x < 5.2・y < 2.5）種10個の合計
            スタッフがロボットにゆずって待った  人どうしの詰まり  あきらめた  移した
0台         0                                 0                 0           0
1台         44                                0                 3           0
2台         37                                0                 0           1
（ゆずり＝yields.csv で who がスタッフ・to_whom がロボット。詰まり＝people_jams.csv、あきらめた／移した＝give_ups.csv の action。どれも記録の位置で数えた）

■ ロボットを1台増やしたときの向き（種ごとに比べ、5%・0.2分より小さい変化は数えない）
入店待ち 0→1台：試算は減る／Unity 同じ 8・逆 0・数えない 2 → 過半数で同じ
入店待ち 1→2台：試算は減る／Unity 同じ 5・逆 3・数えない 2 → 10個のうちの過半数（6）には届かない。数えた8個の中では同じが多い
料理の待ち 0→1台：試算は増える／Unity 同じ 10 → 過半数で同じ
料理の待ち 1→2台：試算は減る／Unity 同じ 10 → 過半数で同じ
（judge.md は⑥に基準がないので向きの印を付けていない。上の数は judge と同じ線で Claude Code が数えたもの）

■ judge.md：できた（night/20261104_p4/judge.md）。30 回とも「基準なし」

■ 決めてほしいこと
- 入店待ち 1→2台 の向きは「同じ5・逆3・数えない2」。過半数を「10個のうち」で見るか「数えた回のうち」で見るかで答えが変わる（judge の数え方の決まりに従ってほしい）
```

### 来る客の数は試算とそろい、59組は種0だけだった

種10個の入れた組の数は59〜88組、中央値73.5組で、試算の74〜76組とほぼ同じでした。確かめの3回で59組だったのは種0のことで、⑤のときと同じです。

### ロボットを足すと待ちが縮む向きは、試算と同じだった

入店待ちは、0台から1台にすると種10個のうち8個で縮み（逆0、変化が小さく数えない2）、1台から2台にすると5個で縮みました（逆3、数えない2）。料理の待ちは、0台→1台で延び、1台→2台で縮む向きが、10個すべてで試算と同じでした。判定の決まりは「種の過半数で試算と逆」のときに印を付けるもので、逆は最大3個なので印は付いていません。

### 試算はレジで詰まり、3Dの店は片付けと案内で詰まる

試算の会計待ちは7〜12分、3Dの店は3分前後です。代わりに、席は空いているのに待った時間が、3Dの店では 0台 8.4分（試算4.2分）・1台 4.8分（1.0分）・2台 3.5分（0.6分）でした。judge.md の記録の表でも、Unity÷試算 は入店待ちで1.4〜2.6倍、席は空いているのに待った時間で2.0〜5.5倍です。

⑤・③⑦と同じ形のずれです。試算ではスタッフの手は「仕事の数×時間」でふさがるだけですが、3Dの店のスタッフは店の中を歩き、片付けと案内を後回しにしています。このずれは、③⑦のときに決めたとおり、試算に入れず記録だけにしています。

### 2台のときに残った詰まりは、待機場所の中と左の島の下の入口

【画像】LOG398_04_store6_night_stops.png 直した⑥の図面に、ロボット2台の夜（種10個）の30秒以上の止まり122回を場所ごとに重ねた図。待機場所の中が60回、左の島の下の入口（x 6〜8m・y 4〜6m）が27回

#### 止まりの数を図に重ねたスクリプト draw_after.py

```python
"""直した⑥の図面に、ロボットの道と、2台の夜（種10個）の30秒以上の止まりの数を場所ごとに重ねる（2026-10-28）
止まりの数は 20261104_night_p4.txt（Claude Code の報告）の「2台の30秒以上の止まり」から写した"""
import json, matplotlib; matplotlib.use('Agg')
import matplotlib.pyplot as plt, matplotlib.patches as P
from matplotlib import font_manager as fm
fp=fm.FontProperties(fname='/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc')
B='/home/claude/p2m-site/public/sim/layouts/'
L=json.load(open(B+'p4_relaxed_side.json'))['layout']
U=json.load(open(B+'unity/p4_relaxed_side.json'))
W,D=L['outer']['w'],L['outer']['d']
fig,ax=plt.subplots(figsize=(14,10.5))
ax.add_patch(P.Rectangle((0,0),W,D,fill=False,lw=2))
e=L['entrance']; ax.plot([0,0],[e['a0'],e['a1']],color='w',lw=4); ax.text(-0.3,(e['a0']+e['a1'])/2,'入口',fontproperties=fp,ha='right',va='center',fontsize=10)
for r in L['rooms']:
    x0,y0,x1,y1=r['rect']; ax.add_patch(P.Rectangle((x0,y0),x1-x0,y1-y0,fc={'back':'#e4e4e0','guest':'#f1e6d6','drink':'#d8e8f4'}[r['kind']],ec='#999'))
    ax.text((x0+x1)/2,(y0+y1)/2,r['label'],ha='center',va='center',fontproperties=fp,fontsize=8)
for w in L['walls']:
    x0,y0,x1,y1=w['rect']; ax.add_patch(P.Rectangle((x0,y0),x1-x0,y1-y0,fc='#555',ec='none'))
for t in L['tables']:
    ax.add_patch(P.Rectangle((t['x'],t['y']),t['w'],t['h'],fc='w',ec='k',lw=.8))
    ax.text(t['x']+t['w']/2,t['y']+t['h']/2,t['id'][1:],ha='center',va='center',fontsize=6)
d=L['dish_up'][0]['rect']; ax.add_patch(P.Rectangle((d[0],d[1]),d[2]-d[0],d[3]-d[1],fc='#333'))
s=L['robot_standby']['rect']; ax.add_patch(P.Rectangle((s[0],s[1]),s[2]-s[0],s[3]-s[1],fc='none',ec='#2a5cc8',ls='--',lw=1.5))
for k,v in U['routes']['from_pass'].items():
    xs,ys=zip(*v['points']); ax.plot(xs,ys,color='#2a5cc8',alpha=.3,lw=1.1)
Z=[('待機場所の中',(15.5,5.65,17.9,6.45),60,(18.1,6.9)),
   ('受け渡し口から2.5m以内',(11.8,3.45,16.8,8.45),9,(9.9,3.1)),
   ('ドリンクバーのまわり',(14.8,1.8,19.6,5.65),5,(17.4,1.95)),
   ('入口・レジのまわり',(0,0,5.2,5.0),1,(0.15,4.55)),
   ('左の2本の通路',(1.2,5.2,2.7,14.5),7,(2.9,14.75)),
   ('島の下の入口（x6〜8・y4〜6）',(6,4,8,6),27,(6.1,3.55))]
for name,(x0,y0,x1,y1),n,(tx,ty) in Z:
    ax.add_patch(P.Rectangle((x0,y0),x1-x0,y1-y0,fc='#d33',alpha=min(.45,.05+n/140),ec='#d33',lw=1.4))
    ax.text(tx,ty,f'{name} {n}回',fontproperties=fp,fontsize=9.5,color='#b00',bbox=dict(fc='w',ec='#d33',lw=.8,pad=2))
ax.add_patch(P.Rectangle((1.2,5.2),1.5,9.3,fc='#d33',alpha=.12,ec='#d33',lw=1.4))
ax.add_patch(P.Rectangle((5.25,5.2),1.5,9.3,fc='#d33',alpha=.12,ec='#d33',lw=1.4))
ax.set_xlim(-1.5,W+.5); ax.set_ylim(-.8,D+.5); ax.set_aspect('equal')
ax.set_xticks(range(0,int(W)+1,1)); ax.set_yticks(range(0,int(D)+1,1)); ax.grid(alpha=.15); ax.tick_params(labelsize=7)
ax.set_title('直した⑥と、ロボット2台の夜（種10個）の30秒以上の止まり 計122回のうちの場所ごとの数',fontproperties=fp,fontsize=12)
plt.tight_layout(); plt.savefig('store6_04_night_stops.png',dpi=120)
```

| 場所 | 30秒以上の止まり | おもな理由 |
|---|---|---|
| 待機場所の中 | 60回 | もう1台のロボット 59 |
| 左の島の下の入口（x 6〜8m・y 4〜6m） | 27回 | 人が立ち止まっていた 9・スタッフ 7・お客さん 6・もう1台 4 |
| 受け渡し口から2.5m以内（待機場所の中を除く） | 9回 | スタッフ 7 |
| 左の2本の通路 | 7回 | お客さん 5 |
| ドリンクバーのまわり | 5回 | もう1台 4 |
| 入口・レジのまわり | 1回 | お客さん 1 |

【図解】LOG398_store6_drinkbar_register.html

待機場所の中の60回は、ほぼ全部がロボットどうしです。往復を終えて待機場所へ戻るロボットと、待機場所から受け渡し口へ出るロボットが、1.2mの間で出会っています。③⑦④⑤でも、受け渡し口と待機場所のまわりは止まりの多い所でした。**店ごとに図面を直しても、待機場所が受け渡し口のすぐ横にあるという決め方が同じなので、同じ詰まりが出ます。**

左の島の下の入口は、受け渡し口から左側の卓へ行くロボットの道と、入口から左の席へ向かうお客さん・案内するスタッフの道が交わる所です。ロボットの道は y 4.9m あたりを1本で走っていて、ここだけ人の動きと重なります。

入口・レジのまわりでは、スタッフがロボットに道をゆずって待ったのが、1台で44回、2台で37回ありました。人どうしの詰まりは0回で、待合と会計の列の重なりは、今の記録の上では止まりとして出ていません。


## 作業ログ ― ⑥はここで区切る

④⑤と同じく、⑥もここで区切りにしました。残った詰まりは直し続けず、記録して調べる対象にします。直すなら、店の形（サイト）を先に直します。

| 決めたこと・残したこと | 中身 |
|---|---|
| ⑥の店の形 | ドリンクバーを左へ0.8m、レジを縦のまま右へ0.8m・下の壁まで。版 2e9fc773050a |
| ドリンクバーに並ぶ点 | 真ん中の前に置けないときは、前の幅の中で左右へずらす（サイトの作り方の決まり） |
| ⑥の試算 | ⑥の3,066条件だけ計算し直した（v2.8 のまま） |
| Unity で回す店 | ③⑦④⑤⑥の15条件（serving-robot-params.json） |
| 記録だけにしたもの | 試算＝レジ・3Dの店＝片付けと案内、という詰まる所のずれ。待機場所の中と左の島の下の入口の詰まり。待合と会計の列の重なり。建てたときの3つの表示 |
| 5店に共通の詰まり | 待機場所が受け渡し口のすぐ横（1.2〜1.5m）にあり、戻るロボットと出るロボットが出会う。店ごとの直しではなく、待機場所の決め方の問題として記録した |
