using System.Collections.Generic;
using UnityEngine;
namespace ServiceSim
{
///
/// サイトの試算の値(serving-robot-params.json)を読む .asset。
///
/// 2026-10-22 にエイイチと決めた「何について正か」(unity_design_v0.md の 0 章):
/// 条件(スタッフ・来客・ロボットの台数)と、店の回し方の決まりと時間は **サイトが正**。
/// なので、店の回し方の数字は .asset に手で写さず、この JSON から読む。
///
/// role(meta.roles)の扱い:
/// site_rule … サイトが正。Unity はこの値を使う
/// unity_measures … **読まない**(Unity で測ってサイトに戻すための仮の値)
/// site_only … 使わない(試算の中だけの設定)
/// unity_measures の名前(robot_speed / pause_rate / pause_s / stuck_rate)は、
/// まちがって使わないように、読もうとしたら例外ではなく「読まない」として弾く。
///
/// 版の突き合わせ: meta.layout_generation が、店の形の JSON の meta.generation と
/// 違ったら回さない(Ready が false になる)。
///
[CreateAssetMenu(menuName = "ServiceSim/Sim Params", fileName = "SimParams")]
public class SimParams : ScriptableObject
{
/// Unity が読まない値(unity_measures)の名前
public static readonly string[] UnityMeasureKeys =
{ "robot_speed", "pause_rate", "pause_s", "stuck_rate" };
[Header("読む JSON(サイトから写したもの)")]
public TextAsset paramsJson;
[Header("写した記録(報告に出す)")]
public string fetchedAt = "";
public string sourceUrl = "";
// ---- 読んだ中身(Load のたびに作り直す)----
JsonValue _root;
readonly List _problems = new List();
string _layoutGeneration = "";
string _model = "";
public string LayoutGeneration { get { return _layoutGeneration; } }
public string Model { get { return _model; } }
public bool Ready { get { return _root != null && _problems.Count == 0; } }
public IList Problems { get { return _problems; } }
/// 1 つの条件(unity_conditions の 1 件)
public class Condition
{
public string store; // fam_p1 / fam_p5
public int staff;
public int robots;
public int arrivalsPerHour;
public string mode; // serve(上げ膳だけ)
public int trays; // 1 往復で運ぶ卓の数
public string fragileBy; // any(倒れやすい物もロボットが運ぶ)
public readonly Dictionary siteResult = new Dictionary();
/// 書き出しのフォルダ名に使う名前
public string Id
{
get { return store + "_s" + staff + "_r" + robots + "_a" + arrivalsPerHour; }
}
}
readonly List _conditions = new List();
public IList Conditions { get { return _conditions; } }
///
/// JSON を読む。storeGeneration に店の形の JSON の meta.generation を渡すと、
/// meta.layout_generation と突き合わせる(違えば Ready が false)。
///
public bool Load(string storeGeneration)
{
_problems.Clear();
_conditions.Clear();
_root = null;
_layoutGeneration = "";
_model = "";
if (paramsJson == null)
{
_problems.Add("serving-robot-params.json(paramsJson)が入っていません");
return false;
}
string err;
JsonValue root = JsonValue.Parse(paramsJson.text, out err);
if (root == null || !root.IsObject)
{
_problems.Add("serving-robot-params.json を読めません: " + err);
return false;
}
_root = root;
JsonValue meta = root["meta"];
_layoutGeneration = meta["layout_generation"].S("");
_model = meta["model"].S("");
if (string.IsNullOrEmpty(_layoutGeneration))
_problems.Add("meta.layout_generation がありません");
else if (!string.IsNullOrEmpty(storeGeneration) && _layoutGeneration != storeGeneration)
_problems.Add("版の番号が店の形の JSON と違います: 試算 " + _layoutGeneration +
" / 店の形 " + storeGeneration + "(写し直してください)");
// ---- 条件 ----
JsonValue list = root["unity_conditions"];
for (int i = 0; i < list.Count; i++)
{
JsonValue c = list[i]["condition"];
Condition cond = new Condition();
cond.store = c["store"].S("");
cond.staff = c["staff"].I(0);
cond.robots = c["robots"].I(0);
cond.arrivalsPerHour = c["arrivals_per_h"].I(0);
cond.mode = c["mode"].S("serve");
cond.trays = c["trays"].I(1);
cond.fragileBy = c["fragile_by"].S("any");
JsonValue sr = list[i]["site_result"];
foreach (string k in sr.Keys) cond.siteResult[k] = sr[k].F(0f);
if (string.IsNullOrEmpty(cond.store) || cond.staff <= 0)
_problems.Add("unity_conditions[" + i + "] の中身が足りません");
_conditions.Add(cond);
}
if (_conditions.Count == 0) _problems.Add("unity_conditions が空です");
return Ready;
}
// ---------------- 値の読み出し ----------------
///
/// common の値(role=site_rule のものだけ)。
/// unity_measures / site_only を読もうとしたら、使わずに fallback を返して問題に残す。
///
public float SiteRule(string key, float fallback)
{
JsonValue v = Common(key);
if (v == null) return fallback;
return v["value"].F(fallback);
}
public float[] SiteRuleArray(string key, float[] fallback)
{
JsonValue v = Common(key);
if (v == null) return fallback;
JsonValue a = v["value"];
if (!a.IsArray) return fallback;
float[] r = new float[a.Count];
for (int i = 0; i < a.Count; i++) r[i] = a[i].F(0f);
return r;
}
/// common の 1 件。role が site_rule でなければ null(使わない)
JsonValue Common(string key)
{
if (_root == null) return null;
JsonValue v = _root["common"][key];
if (v.IsNull)
{
_problems.Add("common." + key + " がありません");
return null;
}
string role = v["role"].S("");
if (role == "site_rule") return v;
_problems.Add("common." + key + " は role=" + role + " なので Unity は読みません");
return null;
}
/// stores.{key} の 1 件(fam_p1 / fam_p5)
public JsonValue Store(string storeKey)
{
if (_root == null) return null;
JsonValue s = _root["stores"][storeKey];
if (s.IsNull)
{
_problems.Add("stores." + storeKey + " がありません");
return null;
}
return s;
}
public float StoreValue(string storeKey, string key, float fallback)
{
JsonValue s = Store(storeKey);
if (s == null) return fallback;
JsonValue v = s[key];
return v.kind == JsonValue.Kind.Number ? v.F(fallback) : fallback;
}
/// stores.{key} の配列([最短, 最頻, 最長] など)
public float[] StoreArray(string storeKey, string key, float[] fallback)
{
JsonValue s = Store(storeKey);
if (s == null) return fallback;
JsonValue a = s[key];
if (!a.IsArray) return fallback;
float[] r = new float[a.Count];
for (int i = 0; i < a.Count; i++) r[i] = a[i].F(0f);
return r;
}
///
/// 配列の配列の、index 番めを [最短, 最頻, 最長] で取る
/// (first_dishes は [[5,8,12],[8,12,18]] の形)
///
public Vector3 StoreTriangleAt(string storeKey, string key, int index, Vector3 fallback)
{
JsonValue s = Store(storeKey);
if (s == null) return fallback;
JsonValue a = s[key];
if (!a.IsArray || index < 0 || index >= a.Count) return fallback;
JsonValue t = a[index];
if (!t.IsArray || t.Count < 3) return fallback;
return new Vector3(t[0].F(0f), t[1].F(0f), t[2].F(0f));
}
/// [最短, 最頻, 最長] を Vector3 で
public Vector3 StoreTriangle(string storeKey, string key, Vector3 fallback)
{
float[] a = StoreArray(storeKey, key, null);
if (a == null || a.Length < 3) return fallback;
return new Vector3(a[0], a[1], a[2]);
}
/// 真ん中の値(arrivals・staff の「普通」)
public int StoreMiddle(string storeKey, string key, int fallback)
{
float[] a = StoreArray(storeKey, key, null);
if (a == null || a.Length == 0) return fallback;
return Mathf.RoundToInt(a[a.Length / 2]);
}
/// 何番めの条件(_conditions の並びのまま)。範囲外なら null
public Condition ConditionAt(int index)
{
return index >= 0 && index < _conditions.Count ? _conditions[index] : null;
}
/// metrics の名前と意味(meta.metrics)
public string MetricNote(string key)
{
if (_root == null) return "";
return _root["meta"]["metrics"][key].S("");
}
public List MetricKeys()
{
List keys = new List();
if (_root == null) return keys;
foreach (string k in _root["meta"]["metrics"].Keys) keys.Add(k);
return keys;
}
/// 報告に出す覚え書き
public string Report()
{
System.Text.StringBuilder sb = new System.Text.StringBuilder();
sb.AppendLine("サイトの試算の値(serving-robot-params.json)");
sb.AppendLine(" ファイル: " + (paramsJson != null ? paramsJson.name : "(未設定)"));
sb.AppendLine(" 写した日時: " + (string.IsNullOrEmpty(fetchedAt) ? "(未記入)" : fetchedAt));
sb.AppendLine(" 写し元: " + (string.IsNullOrEmpty(sourceUrl) ? "(未記入)" : sourceUrl));
sb.AppendLine(" 試算の版 (meta.model): " + _model +
" / 店の形の版 (meta.layout_generation): " + _layoutGeneration);
sb.AppendLine(" 条件: " + _conditions.Count + " 件");
for (int i = 0; i < _conditions.Count; i++)
{
Condition c = _conditions[i];
sb.AppendLine(" " + c.Id + " スタッフ " + c.staff + " / ロボット " + c.robots +
" / " + c.arrivalsPerHour + " 組毎時 / " + c.mode +
" / 1往復 " + c.trays + " 卓 / 倒れやすい物 " + c.fragileBy);
}
sb.AppendLine(" Unity が読まない値 (unity_measures): " +
string.Join(" / ", UnityMeasureKeys));
if (_problems.Count == 0) sb.AppendLine(" 合わないもの: なし");
else
{
sb.AppendLine(" 合わないもの: " + _problems.Count + " 件");
for (int i = 0; i < _problems.Count; i++) sb.AppendLine(" - " + _problems[i]);
}
return sb.ToString().TrimEnd();
}
}
}