using System.Collections.Generic;
using UnityEngine;
namespace ServiceSim
{
///
/// 回す条件と、書き出す場所。実験に関する数字と場所はすべてこの .asset に置く。
/// 条件を順に回すのは ExperimentRunner(段階2で作る)。
///
[CreateAssetMenu(menuName = "ServiceSim/Experiment Plan", fileName = "ExperimentPlan")]
public class ExperimentPlan : ScriptableObject
{
public struct Condition
{
public string id;
public int aisleWidthIndex;
public float aisleWidthM;
public CrowdLevel crowd;
public int robots;
}
[Header("この回の名前")]
public string runName = "family_v0";
[Header("回す条件")]
[Tooltip("細い通路の幅(StoreLayout.aisleWidthOptions の番号)")]
public int[] aisleWidthIndices = { 0, 1, 2 };
[Tooltip("混み具合")]
public CrowdLevel[] crowdLevels = { CrowdLevel.Empty, CrowdLevel.Normal, CrowdLevel.Busy };
[Tooltip("ロボットの台数")]
public int[] robotCounts = { 1, 2 };
[Header("1条件で回す量")]
[Tooltip("1条件・1つの種で回す往復の数。台数を合わせた合計")]
public int tripsPerCondition = 100;
[Tooltip("乱数の種")]
public int[] seeds = { 0, 1, 2, 3, 4 };
[Tooltip("時間の進みを速める倍率")]
public float timeScale = 1f;
[Header("書き出す場所")]
[Tooltip("KIOXIA がつながっているかを確かめる場所")]
public string kioxiaVolumePath = "/Volumes/KIOXIA";
[Tooltip("測ったデータの置き場所。この下に {YYYYMMDD}_{run_name}/ を作る")]
public string kioxiaOutputRoot = "/Volumes/KIOXIA/r2r2r-data/service_sim/unity";
[Header("段階2:1条件だけ試す")]
[Tooltip("細い通路の幅(StoreLayout.aisleWidthOptions の番号)")]
public int trialAisleWidthIndex = 1;
[Tooltip("混み具合")]
public CrowdLevel trialCrowd = CrowdLevel.Normal;
[Tooltip("ロボットの台数")]
public int trialRobots = 2;
[Tooltip("乱数の種")]
public int trialSeed = 0;
[Tooltip("回す往復の数(台数を合わせた合計)。timeScale 1 で20分程度に収まる数にする")]
public int trialTrips = 20;
[Tooltip("速めるときの倍率")]
public float trialTimeScaleFast = 10f;
[Tooltip("1コマの時間を固定して回すときの、1コマの長さ(秒)")]
public float trialCaptureDeltaTime = 0.02f;
[Tooltip("実時間でこれを超えたら打ち切る(秒)。保険")]
public float trialMaxWallSeconds = 2400f;
[Tooltip("段階2の置き場所。Plastic のワークスペースと git の外にする(測定データとして残さない)")]
public string trialOutputRoot = "/Users/shimazueiichi/ServiceSim_tmp";
[Header("v2:1回の実行の区切り")]
[Tooltip("1回の実行の長さ(店の中の分)。試算の peak_min と同じ")]
public float measureMinutes = 120f;
[Tooltip("慣らしの長さ(店の中の分)。この分は計測に数えない")]
public float warmupMinutes = 15f;
[Header("v2:速く回す")]
[Tooltip("段階3で使う1コマの長さ(秒)。0.05 で約24倍速")]
public float measureCaptureDeltaTime = 0.05f;
[Tooltip("確かめ用に使う1コマの長さ(秒)。検査1・2でこちらと比べる")]
public float referenceCaptureDeltaTime = 0.02f;
[Tooltip("回している間、カメラを切って描画を止める。計測を速くするため初期値は ON")]
public bool disableRenderingWhileRunning = true;
[Tooltip("回している間も頭の上の字幕を出す(B11 を一時的に外す)。" +
"人がロボットをよける様子を見て絵を撮るときだけ ON にする。計測は遅くなる")]
public bool showLabelsWhileRunning;
[Tooltip("キッチンの屋根のモニターを出す(2026-10-23)。" +
"板1枚と文字だけなので、測る回でも出す。倍速が落ちるなど問題があれば切る")]
public bool showKitchenMonitor = true;
[Header("v2:段階3(18条件 × 種)")]
[Tooltip("段階3の置き場所。この下に {YYYYMMDD}_{run_name}/{condition}_s{seed}/ を作る")]
public string stage3OutputRoot = "/Users/shimazueiichi/ServiceSim_tmp/stage3";
[Tooltip("すでに書き出してある条件は飛ばす(途中から再開する)")]
public bool stage3Resume = true;
[Header("スクリーンショット")]
[Tooltip("段階1の報告に使う画面の置き場所")]
public string screenshotDir = "/Users/shimazueiichi/Library/Mobile Documents/com~apple~CloudDocs/デスクトップ/ADAPT DESIGN/じぶんの/PixelstoMotors/デジタルツイン学習R2R2R/サービスロボット導入シミュレーション/unity/screens";
public int screenshotWidth = 1920;
public int screenshotHeight = 1080;
/// 条件の数(通路の幅 × 混み具合 × 台数)
public int ConditionCount
{
get
{
int w = aisleWidthIndices != null ? aisleWidthIndices.Length : 0;
int c = crowdLevels != null ? crowdLevels.Length : 0;
int r = robotCounts != null ? robotCounts.Length : 0;
return w * c * r;
}
}
/// 18条件を順に並べる
public List BuildConditions(StoreLayout layout)
{
List list = new List();
if (aisleWidthIndices == null || crowdLevels == null || robotCounts == null) return list;
for (int wi = 0; wi < aisleWidthIndices.Length; wi++)
{
int widthIndex = aisleWidthIndices[wi];
float widthM = 0f;
if (layout != null && layout.aisleWidthOptions != null &&
widthIndex >= 0 && widthIndex < layout.aisleWidthOptions.Length)
widthM = layout.aisleWidthOptions[widthIndex];
for (int ci = 0; ci < crowdLevels.Length; ci++)
{
for (int ri = 0; ri < robotCounts.Length; ri++)
{
Condition c = new Condition();
c.aisleWidthIndex = widthIndex;
c.aisleWidthM = widthM;
c.crowd = crowdLevels[ci];
c.robots = robotCounts[ri];
c.id = ConditionId(widthM, c.crowd, c.robots);
list.Add(c);
}
}
}
return list;
}
public static string ConditionId(float aisleWidthM, CrowdLevel crowd, int robots)
{
int mm = Mathf.RoundToInt(aisleWidthM * 100f);
return "w" + mm.ToString("000") + "_" + CrowdName(crowd) + "_r" + robots.ToString();
}
public static string CrowdName(CrowdLevel crowd)
{
switch (crowd)
{
case CrowdLevel.Empty: return "empty";
case CrowdLevel.Busy: return "busy";
default: return "normal";
}
}
}
}