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();
}
}
}