diff --git a/src/lib/servingSim.ts b/src/lib/servingSim.ts index 8e59738..352dc49 100644 --- a/src/lib/servingSim.ts +++ b/src/lib/servingSim.ts @@ -60,7 +60,9 @@ export const headline = { tables: fam.tables, arrivals: famArr, base, less, same // 2台入れたとき、店が回る最少のスタッフが何人減るか(店すべて × 混み具合3つ × 使い方3つ) const twoRobots = { none: 0, one: 0, onePeak: [] as number[], total: 0 }; -for (const [key, s] of Object.entries(STORES)) { +// 図面のある大型店(2026-11-02 に足した ⑧⑨)は、LP の数字を変えないよう数えない +const LP_STORES = Object.entries(STORES).filter(([, s]) => s.base !== 'large'); +for (const [key, s] of LP_STORES) { for (const arr of s.arrivals) for (const mode of MODES) { const a = minStaff(key, 0, arr, mode), b = minStaff(key, 2, arr, mode); if (a == null || b == null) continue; @@ -78,7 +80,7 @@ export const staffTable = BASIC_STORES.map((k) => ({ })); // 図面のある店6つ:真ん中の人数・普通の混み具合で、ロボット0台 → 2台 -export const plans = Object.keys(STORES).filter((k) => STORES[k].layout).map((k) => { +export const plans = Object.keys(STORES).filter((k) => STORES[k].layout && STORES[k].base === 'family').map((k) => { const s = STORES[k], st = s.staff[1], arr = s.arrivals[1]; const a = find(k, st, 0, arr)!, b = find(k, st, 2, arr)!; const label: string = s.label.replace(/^標準ファミレス(|)$/g, ''); diff --git a/tools/serving_sim/export_params.py b/tools/serving_sim/export_params.py index c563326..724548c 100644 --- a/tools/serving_sim/export_params.py +++ b/tools/serving_sim/export_params.py @@ -5,7 +5,7 @@ Unity はこの JSON を読み、値を手で写さない(LOG359 のずれ9「 使い方: python3 export_params.py results.json <出力先> (出力先は p2m-site の public/sim/serving-robot-params.json。unity_rules は出力先の今の値を引き継ぐ) - common:店に共通の値。role が "unity_measures" の値は Unity で測ってサイトに戻すための仮の値なので、Unity は読まない -- stores:Unity で回す店(fam_p1・fam_p5・fam_p2・fam_p3・fam_p4・fam_p0)の値。registers はレジの数(v2.8) +- stores:Unity で回す店(fam_p1・fam_p5・fam_p2・fam_p3・fam_p4・fam_p0、2026-11-02 から大型店の large_p6・large_p7)の値。registers はレジの数(v2.8) - unity_conditions:Unity で回す条件(0〜17番は店ごとに混み具合が普通の3つ。18〜53番は2026-10-29に足した空いている・混んでいるの各3つ。54〜89番は2026-10-30に足したスタッフが少ない・多いの各3つ(混み具合は普通)。計90条件)と、同じ条件の試算の結果(40回の平均)。Unity の店はレジの内側に店員が1人だけなので、レジは1つ """ import json @@ -74,10 +74,29 @@ for k in UNITY_STORES: r = rows[h.key(c)] conds.append({"condition": c, "site_result": {m: r[m] for m in r if m not in c}}) +# 2026-11-02 エイイチと決めた:図面のある大型店(⑧⑨)も Unity で回す。前の90条件(0〜89番)は並びも中身も変えず、後ろに足す。 +# 店ごとに 普通0・1・2台 → 空いている0・1・2台 → 混んでいる0・1・2台 → 少ない0・1・2台 → 多い0・1・2台(15条件 × 2店 = 90〜119番)。 +# Unity の店はレジの内側に店員が1人だけなので、大型店もレジ1つの条件にする(サイトの大型店の最初の数は2つ) +UNITY_STORES_LARGE = ["large_p6", "large_p7"] +gen_large = {g["generation"] for g in json.load(open(h.LAYOUT_DIR_LARGE + "/summary.json", encoding="utf-8"))} +assert len(gen_large) == 1, gen_large +for k in UNITY_STORES_LARGE: + stores[k] = dict(h.STORES[k]) + s = h.STORES[k] + for staff, arr in [(s["staff"][1], s["arrivals"][1]), (s["staff"][1], s["arrivals"][0]), (s["staff"][1], s["arrivals"][2]), + (s["staff"][0], s["arrivals"][1]), (s["staff"][2], 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", "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(), + "layout_generation_large": gen_large.pop(), + "layout_dirs": {"fam_": "public/sim/layouts/(unity/ に建てるための形)", "large_": "public/sim/layouts/large/(unity/ に建てるための形。2026-11-02 に足した大型店⑧⑨)"}, "source": "p2m-site の public/sim/serving-robot.html と同じ試算(haizen_sim.py)と結果(results.json)", "roles": NOTE, "time_window": "空の店から始め、peak_min 分だけ選んだペースで来客し、全員が帰るまで回す(max_extra_min で打ち切り)", diff --git a/tools/serving_sim/haizen_sim.py b/tools/serving_sim/haizen_sim.py index 65206de..05bc265 100644 --- a/tools/serving_sim/haizen_sim.py +++ b/tools/serving_sim/haizen_sim.py @@ -122,13 +122,15 @@ def _scale(vals, t, base_t): return [max(1, round(v * t / base_t)) for v in vals] -def layout_stores(): - """layouts/ の店の形から、標準ファミレスをもとにした店を作る""" +def layout_stores(layout_dir=None, labels=None, base_key="family", prefix="fam_"): + """layouts/ の店の形から、もとにする店(標準ファミレス・大型店)をもとにした店を作る""" + layout_dir = LAYOUT_DIR if layout_dir is None else layout_dir + labels = LAYOUT_LABELS if labels is None else labels out = {} - base = STORES["family"] - names = sorted(f for f in _os.listdir(LAYOUT_DIR) if f.endswith("_analysis.json")) if _os.path.isdir(LAYOUT_DIR) else [] + base = STORES[base_key] + names = sorted(f for f in _os.listdir(layout_dir) if f.endswith("_analysis.json")) if _os.path.isdir(layout_dir) else [] for f in names: - a = json.load(open(_os.path.join(LAYOUT_DIR, f), encoding="utf-8")) + a = json.load(open(_os.path.join(layout_dir, f), encoding="utf-8")) nm = f.split("_")[0] dists = [t["dist"] for t in a["tables"]] if any(d is None for d in dists): @@ -137,16 +139,21 @@ def layout_stores(): c = max(2, round(base["staff"][1] * t / base["tables"])) # 真ん中の人数 st = {**base} st.update({ - "label": LAYOUT_LABELS.get(nm, nm), "base": "family", "layout": nm, + "label": labels.get(nm, nm), "base": base_key, "layout": nm, "tables": t, "dist_m": (round(min(dists), 1), round(max(dists), 1)), "dist_list": dists, "arrivals": _scale(base["arrivals"], t, base["tables"]), "staff": [c - 1, c, c + 1], "staff_all": list(range(max(1, c - 2), c + 5)), }) - out["fam_" + nm] = st + out[prefix + nm] = st return out STORES.update(layout_stores()) +# 図面のある大型店(2026-11-02):大型店をもとに、public/sim/layouts/large/ の店の形から作る。前からある店の条件と並びは変えない(後ろに足す) +_LG = A["layout_stores"].get("large") +if _LG: + LAYOUT_DIR_LARGE = _os.environ.get("SERVING_SIM_LAYOUTS_LARGE", _os.path.normpath(_os.path.join(_HERE, _LG["layout_dir"]))) + STORES.update(layout_stores(LAYOUT_DIR_LARGE, dict(_LG["labels"]), _LG["base"], _LG["prefix"])) ROBOTS = A["choices"]["robots"]["value"] # 最初に見せる台数 ROBOTS_ALL = A["choices"]["robots_all"]["value"] # 「その他」で選べる台数(総当たりはこちら) diff --git a/tools/sim_layouts/unity_export.py b/tools/sim_layouts/unity_export.py index 323f459..7c9f9ee 100644 --- a/tools/sim_layouts/unity_export.py +++ b/tools/sim_layouts/unity_export.py @@ -505,7 +505,9 @@ def places(L, grid, standby_slots, aisles): if not k: continue if k == "Toilet": - dz = L["toilet_doors"][0] + # トイレが2つ以上の店(大型店⑧⑨。2026-11-02)では、そのトイレの部屋に接する入口を使う。1つの店は前と同じ + rx0, ry0, rx1, ry1 = r["rect"] + dz = min(L["toilet_doors"], key=lambda d: math.hypot(max(rx0 - d[2], 0, d[0] - rx1), max(ry0 - d[3], 0, d[1] - ry1))) 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))) @@ -515,6 +517,10 @@ def places(L, grid, standby_slots, aisles): elif k == "Kitchen": continue elif k == "Register": + # レジが2つ以上の店(大型店⑧⑨。2026-11-02)では、会計に立つ点と並ぶ点は1つ目のレジだけに置く。 + # Unity の店はレジ内の店員が1人で、レジ1つの条件で回すため(2つ目のレジは置くだけ。部屋としては建てる)。1つの店は前と同じ + if any(P_["kind"] == "Register" for P_ in P): + continue # 客の側は、レジ内(店員の立つ所)の反対側 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]