using System.Collections.Generic; using UnityEngine; namespace ServiceSim { /// /// サイトの試算の値(SimParams)を、その回だけ ServiceSpec / PeopleSpec / RobotSpec に入れる。 /// /// 2026-10-22 にエイイチと決めた「何について正か」(unity_design_v0.md の 0 章): /// 条件(スタッフ・来客・ロボットの台数)と、店の回し方の決まりと時間は **サイトが正**。 /// .asset に手で写した値は使わない。 /// /// .asset を書き換えたままにしないため、入れる前の値を覚えて、回し終わりに戻す /// (停止点(C1)と同じやり方)。実際に使った値は report で報告に出す。 /// /// role の扱いは SimParams と同じ。unity_measures(robot_speed・pause_rate・pause_s・ /// stuck_rate)は読まない。ロボットの速さと止まり方は、今の RobotSpec とセンサーのまま。 /// /// v2.7(2026-10-23)で、料理の割り当ての決まりが変わった。 /// common.robot_first_wait_s(role=site_rule)を ServiceSpec.robotFirstWaitSec に入れる。 /// 読めなければ Problems に残して回さない(値を手で写さないため)。 /// 決まりの中身は ServiceOps.PutDish と ReleaseDishesToStaff を見る。 /// public class SiteValues { /// サイトに値はあるが、Unity では形にしていないもの(報告に出す) public static readonly string[] NotModelled = { "common.load_s(15 秒): 下げ膳だけで使う値。上げ膳(mode=serve)ではサイトも 0 秒なので、" + "PeopleSpec.staffDwellAtPassSec を 0 にして回す", "common.guest_load_s(下げ膳で客が皿を載せる 20 秒): 今回の条件は mode=serve(上げ膳だけ)なので使わない", "common.dish_extra_m(0 分): 0 なので差は出ない", "stores.extra_orders(0)・stores.extra_cook: 追加注文が 0 なので使わない", "stores.staff_all・stores.arrivals・stores.dist_m・stores.dist_list: 条件の範囲と道のりの確かめに使う値(回し方には入れない)", }; readonly List _lines = new List(); readonly List _problems = new List(); ServiceSpec _service; PeopleSpec _people; RobotSpec _robot; bool _saved; // ---- 入れる前の値 ---- int _sArrivals, _sTrays, _sBusTrips; bool _sFragileByRobot; float _sPeak, _sGuide, _sPay, _sCall, _sPlace, _sClear, _sWipe, _sRescue, _sMaxExtra; float _sRobotFirstWait; float _sCold, _sCallRate, _sFragilePerGroup; Vector3 _sFragileCook, _sEat, _sDish1, _sDish2; int _pStaffCount; float _pStaffSpeed, _pDwellAtPass; float _rTableDwell; /// 実際に入れた値(報告に出す) public IList Lines { get { return _lines; } } /// 足りない・読めない値(1つでもあれば回さない) public IList Problems { get { return _problems; } } public bool Ok { get { return _problems.Count == 0; } } /// /// その条件の値を入れる。storeKey は "fam_p1" / "fam_p5"。 /// 戻り値 false なら、足りない値があるので回してはいけない。 /// public bool Apply(SimParams p, string storeKey, SimParams.Condition cond, ServiceSpec service, PeopleSpec people, RobotSpec robot) { _lines.Clear(); _problems.Clear(); if (p == null) { _problems.Add("SimParams が入っていません"); return false; } if (cond == null) { _problems.Add("条件(unity_conditions の1件)が選ばれていません"); return false; } if (service == null || people == null || robot == null) { _problems.Add("ServiceSpec / PeopleSpec / RobotSpec が入っていません"); return false; } if (p.Store(storeKey) == null) { _problems.Add("stores." + storeKey + " がありません"); return false; } Save(service, people, robot); // ---- 条件(unity_conditions)---- service.arrivalsPerHour = cond.arrivalsPerHour; service.trays = Mathf.Max(1, cond.trays); service.fragileByRobot = cond.fragileBy == "any"; people.staffCount = cond.staff; Line("条件 arrivals_per_h", cond.arrivalsPerHour + " 組/時"); Line("条件 trays", service.trays + " 卓/往復"); Line("条件 fragile_by", cond.fragileBy + "(倒れやすい物をロボットが運ぶ: " + (service.fragileByRobot ? "はい" : "いいえ") + ")"); Line("条件 staff", cond.staff + " 人"); Line("条件 robots", cond.robots + " 台"); Line("条件 mode", cond.mode); if (cond.mode != "serve") _problems.Add("mode が serve ではありません(" + cond.mode + ")。Unity は上げ膳だけを作っています"); // ---- common(role=site_rule のものだけ)---- service.guideSec = Rule(p, "guide_s", service.guideSec); service.paySec = Rule(p, "pay_s", service.paySec); service.callSec = Rule(p, "call_s", service.callSec); service.staffPlaceSec = Rule(p, "staff_place_s", service.staffPlaceSec); service.robotFirstWaitSec = Rule(p, "robot_first_wait_s", service.robotFirstWaitSec); service.clearSec = Rule(p, "clear_s", service.clearSec); service.wipeSec = Rule(p, "wipe_s", service.wipeSec); service.rescueSec = Rule(p, "rescue_s", service.rescueSec); service.maxExtraMin = Rule(p, "max_extra_min", service.maxExtraMin); service.coldMin = Rule(p, "cold_min", service.coldMin); people.staffSpeed = Rule(p, "staff_speed", people.staffSpeed); robot.tableDwellSec = Rule(p, "robot_handoff_s", robot.tableDwellSec); // 上げ膳(mode=serve)では、サイトはロボットに載せる時間を 0 秒にしている // (common.load_s 15 秒は下げ膳だけで使う値)。そこにそろえる people.staffDwellAtPassSec = 0f; Line("受け渡し口で載せる時間", "0 秒(上げ膳なので load_s は使わない)"); float[] fc = p.SiteRuleArray("fragile_cook", null); if (fc != null && fc.Length >= 3) { service.fragileCookMin = new Vector3(fc[0], fc[1], fc[2]); Line("common.fragile_cook", Tri(service.fragileCookMin) + " 分"); } else _problems.Add("common.fragile_cook を読めません"); // ---- stores.{storeKey} ---- service.peakMinutes = StoreValue(p, storeKey, "peak_min", service.peakMinutes); service.callRate = StoreValue(p, storeKey, "call_rate", service.callRate); service.fragilePerGroup = StoreValue(p, storeKey, "fragile", service.fragilePerGroup); service.busTrips = Mathf.RoundToInt( StoreValue(p, storeKey, "bus_trips", service.busTrips)); service.eatMin = Triangle(p, storeKey, "eat_min", service.eatMin); service.firstDish1CookMin = TriangleAt(p, storeKey, "first_dishes", 0, service.firstDish1CookMin); service.firstDish2CookMin = TriangleAt(p, storeKey, "first_dishes", 1, service.firstDish2CookMin); // 卓の数は店の形の JSON から建てている。数が合わなければ、どちらかが古い int tables = Mathf.RoundToInt(StoreValue(p, storeKey, "tables", 0f)); Line("stores.tables", tables + " 卓(店の形の JSON と合うかは建てたあとに確かめる)"); _saved = true; return Ok; } /// 入れる前の値に戻す(回し終わり・Play を抜けたとき) public void Restore() { if (!_saved) return; _saved = false; if (_service != null) { _service.arrivalsPerHour = _sArrivals; _service.trays = _sTrays; _service.busTrips = _sBusTrips; _service.fragileByRobot = _sFragileByRobot; _service.peakMinutes = _sPeak; _service.guideSec = _sGuide; _service.paySec = _sPay; _service.callSec = _sCall; _service.staffPlaceSec = _sPlace; _service.robotFirstWaitSec = _sRobotFirstWait; _service.clearSec = _sClear; _service.wipeSec = _sWipe; _service.rescueSec = _sRescue; _service.maxExtraMin = _sMaxExtra; _service.coldMin = _sCold; _service.callRate = _sCallRate; _service.fragilePerGroup = _sFragilePerGroup; _service.fragileCookMin = _sFragileCook; _service.eatMin = _sEat; _service.firstDish1CookMin = _sDish1; _service.firstDish2CookMin = _sDish2; } if (_people != null) { _people.staffCount = _pStaffCount; _people.staffSpeed = _pStaffSpeed; _people.staffDwellAtPassSec = _pDwellAtPass; } if (_robot != null) _robot.tableDwellSec = _rTableDwell; } void Save(ServiceSpec service, PeopleSpec people, RobotSpec robot) { if (_saved) return; // 条件を続けて回すときは、いちばん最初の値を覚えておく _service = service; _people = people; _robot = robot; _sArrivals = service.arrivalsPerHour; _sTrays = service.trays; _sBusTrips = service.busTrips; _sFragileByRobot = service.fragileByRobot; _sPeak = service.peakMinutes; _sGuide = service.guideSec; _sPay = service.paySec; _sCall = service.callSec; _sPlace = service.staffPlaceSec; _sRobotFirstWait = service.robotFirstWaitSec; _sClear = service.clearSec; _sWipe = service.wipeSec; _sRescue = service.rescueSec; _sMaxExtra = service.maxExtraMin; _sCold = service.coldMin; _sCallRate = service.callRate; _sFragilePerGroup = service.fragilePerGroup; _sFragileCook = service.fragileCookMin; _sEat = service.eatMin; _sDish1 = service.firstDish1CookMin; _sDish2 = service.firstDish2CookMin; _pStaffCount = people.staffCount; _pStaffSpeed = people.staffSpeed; _pDwellAtPass = people.staffDwellAtPassSec; _rTableDwell = robot.tableDwellSec; } // ---------------- 読み出し(読めなければ問題に残す)---------------- float Rule(SimParams p, string key, float fallback) { int before = p.Problems.Count; float v = p.SiteRule(key, float.NaN); if (float.IsNaN(v) || p.Problems.Count > before) { _problems.Add("common." + key + " を読めません(role が site_rule か確かめてください)"); return fallback; } Line("common." + key, v.ToString("0.###")); return v; } float StoreValue(SimParams p, string storeKey, string key, float fallback) { float v = p.StoreValue(storeKey, key, float.NaN); if (float.IsNaN(v)) { _problems.Add("stores." + storeKey + "." + key + " を読めません"); return fallback; } Line("stores." + key, v.ToString("0.###")); return v; } Vector3 Triangle(SimParams p, string storeKey, string key, Vector3 fallback) { Vector3 v = p.StoreTriangle(storeKey, key, new Vector3(float.NaN, 0f, 0f)); if (float.IsNaN(v.x)) { _problems.Add("stores." + storeKey + "." + key + " を読めません"); return fallback; } Line("stores." + key, Tri(v) + " 分"); return v; } Vector3 TriangleAt(SimParams p, string storeKey, string key, int index, Vector3 fallback) { Vector3 v = p.StoreTriangleAt(storeKey, key, index, new Vector3(float.NaN, 0f, 0f)); if (float.IsNaN(v.x)) { _problems.Add("stores." + storeKey + "." + key + "[" + index + "] を読めません"); return fallback; } Line("stores." + key + "[" + index + "]", Tri(v) + " 分"); return v; } static string Tri(Vector3 v) { return v.x.ToString("0.###") + " / " + v.y.ToString("0.###") + " / " + v.z.ToString("0.###"); } void Line(string key, string value) { _lines.Add(key + " = " + value); } /// 報告に出す覚え書き public string Report() { System.Text.StringBuilder sb = new System.Text.StringBuilder(); sb.AppendLine("サイトの試算から入れた値(この回だけ。.asset は回し終わりに戻す)"); for (int i = 0; i < _lines.Count; i++) sb.AppendLine(" " + _lines[i]); sb.AppendLine(" Unity が読まない値(unity_measures): " + string.Join(" / ", SimParams.UnityMeasureKeys)); sb.AppendLine(" サイトにあるが Unity で形にしていないもの:"); for (int i = 0; i < NotModelled.Length; i++) sb.AppendLine(" - " + NotModelled[i]); if (_problems.Count > 0) { sb.AppendLine(" 読めなかった値: " + _problems.Count + " 件"); for (int i = 0; i < _problems.Count; i++) sb.AppendLine(" - " + _problems[i]); } return sb.ToString().TrimEnd(); } } }