# cm diff cs:41 cs:42 （ServiceSim）
# 1. cm diff の出力そのまま

C "Assets/ServiceSim/Recording/SimTimeRecorder.cs"

# 2. 中身の違い（補足。上の各ファイルを cm cat <path>#cs:N で取り出し、diff -u で比べたもの）

--- Assets/ServiceSim/Recording/SimTimeRecorder.cs#cs:41
+++ Assets/ServiceSim/Recording/SimTimeRecorder.cs#cs:42
@@ -24,8 +24,11 @@
     ///   ・自分で作った真上のカメラ（正投影・ふだんは切）を、撮るコマだけ
     ///     RenderTexture に描いて読み、ffmpeg に生のまま渡して mp4 にする。
     ///     画面の上の文字（OnGUI）は RenderTexture に入らない。
-    ///   ・店のもの・人・ロボット・光・ほかのカメラには触らない。
+    ///   ・店のもの・人・ロボット・ほかのカメラには触らない。
     ///     見た目は Main Camera の設定（背景色・映すもの）を写して使う。
+    ///   ・光：影を落とす Directional Light（Setup の Sun）の高さだけを上げて影を短くする
+    ///     （2026-10-29・依頼 28。地面からの角度 52° → 75°。方角・やわらかさ・濃さ・色・明るさは今のまま）。
+    ///     撮り始めるときに変える。-ssRecord の無い回（夜の回・確かめの回・エディタの Play）では変わらない。
     ///
     /// 引数
     ///   -ssRecord {名前の頭}     … mp4 の名前の頭（例 20261029_1930_lpdemo_after）
@@ -33,6 +36,11 @@
     ///   -ssRecFps 30 -ssRecCrf 16
     ///   -ssRecFfmpeg {ffmpeg のパス}
     ///   -ssRecView name,x0,x1,y0,y1,w,h   … 撮る範囲（店の座標 m）と大きさ。何本でも
+    ///   -ssRecSunPitch 75                 … 録画のときの光の地面からの角度（既定 75。依頼 28）
+    ///   -ssRecStillAt 1500 -ssRecStillPitches 70,75,80
+    ///                                     … 静止画だけ書く回（依頼 28）。店の時刻がこの秒に来たコマを、
+    ///                                       光の角度ごとに PNG で書いて、回を途中で終える（mp4 は作らない。
+    ///                                       台帳には書かれず、記録は {run_stem}_stopped に残る）
     ///
     /// 置き場所：その回の出力フォルダ（HeadlessRun が決めた run_stem）の下の recordings/。
     ///   recordings/ がもうあれば撮らない（上書きしない）。
@@ -40,6 +48,8 @@
     public class SimTimeRecorder : MonoBehaviour
     {
         const float CamHeight = 60f;
+        /// <summary>録画のときの光の地面からの角度（依頼 28。ふだんの Sun は 52°）</summary>
+        const float DefaultSunPitch = 75f;
 
         class View
         {
@@ -59,6 +69,10 @@
         string _prefix = "", _ffmpeg = "ffmpeg";
         float _from = 900f, _to = 1650f, _step = 0.2f;
         int _fps = 30, _crf = 16;
+        float _sunPitch = DefaultSunPitch;
+        float _stillAt = -1f;                       // 0 以上なら静止画だけ書く回
+        readonly List<float> _stillPitches = new List<float>();
+        Light _sun;
         readonly List<View> _views = new List<View>();
 
         ExperimentRunner _runner;
@@ -93,6 +107,16 @@
             _step = F(Find(argv, "-ssRecStep"), _step);
             _fps = (int)F(Find(argv, "-ssRecFps"), _fps);
             _crf = (int)F(Find(argv, "-ssRecCrf"), _crf);
+            _sunPitch = F(Find(argv, "-ssRecSunPitch"), _sunPitch);
+            _stillAt = F(Find(argv, "-ssRecStillAt"), -1f);
+            string pitches = Find(argv, "-ssRecStillPitches");
+            if (pitches != null)
+                foreach (string p in pitches.Split(',')) _stillPitches.Add(F(p, _sunPitch));
+            if (_stillAt >= 0f)
+            {
+                if (_stillPitches.Count == 0) _stillPitches.Add(_sunPitch);
+                _from = _stillAt; _to = _stillAt;
+            }
 
             for (int i = 0; i < argv.Length - 1; i++)
             {
@@ -123,6 +147,12 @@
                 _failed = true;
                 return;
             }
+            if (_stillAt >= 0f)
+            {
+                Log("静止画だけ書きます: 店の時刻 " + _stillAt + " 秒・光の角度 " + string.Join(",", _stillPitches) +
+                    "°・" + _views.Count + " 本。書いたら回を途中で終えます");
+                return;
+            }
             Log("撮ります: 店の時刻 " + _from + "〜" + _to + " 秒・" + _step + " 秒ごと・" + _fps + " コマ/秒・CRF " + _crf +
                 "・" + _views.Count + " 本");
         }
@@ -143,6 +173,8 @@
 
             if (!_started && !Begin()) { _failed = true; return; }
 
+            if (_stillAt >= 0f) { WriteStills(t); return; }
+
             for (int i = 0; i < _views.Count; i++) Grab(_views[i]);
             _csv.Write(_frames.ToString(CultureInfo.InvariantCulture));
             _csv.Write(',');
@@ -173,6 +205,10 @@
             }
             Directory.CreateDirectory(_dir);
 
+            _sun = FindSun();
+            if (_sun == null) Log("影を落とす Directional Light が1つに決まりません。光は変えずに撮ります");
+            else SetSunPitch(_sunPitch);
+
             Camera main = Camera.main;
             for (int i = 0; i < _views.Count; i++)
             {
@@ -195,6 +231,7 @@
                 c.targetTexture = v.rt;
                 v.cam = c;
                 v.tex = new Texture2D(v.w, v.h, TextureFormat.RGB24, false);
+                if (_stillAt >= 0f) continue;   // 静止画だけの回は ffmpeg を立てない
 
                 v.mp4 = Path.Combine(_dir, _prefix + "_" + v.name + ".mp4");
                 string args = "-hide_banner -loglevel error -y -f rawvideo -pix_fmt rgb24 -s " + v.w + "x" + v.h +
@@ -220,6 +257,62 @@
             return true;
         }
 
+        /// <summary>影を落とす Directional Light（Setup の Sun）。1つに決まらなければ null</summary>
+        static Light FindSun()
+        {
+            Light found = null;
+            foreach (Light l in FindObjectsOfType<Light>())
+            {
+                if (l.type != LightType.Directional || l.shadows == LightShadows.None) continue;
+                if (found != null) return null;
+                found = l;
+            }
+            return found;
+        }
+
+        /// <summary>地面からの角度だけを変える（方角＝y の回転・z の回転はそのまま）</summary>
+        void SetSunPitch(float pitch)
+        {
+            if (_sun == null) return;
+            Vector3 e = _sun.transform.eulerAngles;
+            float before = e.x;
+            _sun.transform.rotation = Quaternion.Euler(pitch, e.y, e.z);
+            Log("光の角度: " + _sun.name + " " + before.ToString("0.#", CultureInfo.InvariantCulture) + "° → " +
+                pitch.ToString("0.#", CultureInfo.InvariantCulture) + "°（方角 " +
+                e.y.ToString("0.#", CultureInfo.InvariantCulture) + "° のまま）");
+        }
+
+        /// <summary>静止画だけの回：光の角度ごとに PNG を書いて、回を途中で終える</summary>
+        void WriteStills(float t)
+        {
+            _done = true;
+            StringBuilder note = new StringBuilder();
+            for (int k = 0; k < _stillPitches.Count; k++)
+            {
+                float pitch = _stillPitches[k];
+                SetSunPitch(pitch);
+                for (int i = 0; i < _views.Count; i++)
+                {
+                    View v = _views[i];
+                    v.cam.Render();
+                    RenderTexture prev = RenderTexture.active;
+                    RenderTexture.active = v.rt;
+                    v.tex.ReadPixels(new Rect(0, 0, v.w, v.h), 0, 0, false);
+                    v.tex.Apply(false);
+                    RenderTexture.active = prev;
+                    string png = Path.Combine(_dir, _prefix + "_" + v.name + "_t" +
+                                              t.ToString("0.##", CultureInfo.InvariantCulture) + "_sun" +
+                                              pitch.ToString("0.#", CultureInfo.InvariantCulture) + ".png");
+                    File.WriteAllBytes(png, v.tex.EncodeToPNG());
+                    note.Append("\n  ").Append(png);
+                }
+            }
+            for (int i = 0; i < _views.Count; i++) if (_views[i].rt != null) _views[i].rt.Release();
+            Log("静止画を書きました（店の時刻 " + t.ToString("0.###", CultureInfo.InvariantCulture) + " 秒）" + note +
+                "\n  回を途中で終えます（台帳には書きません）");
+            Application.Quit(0);
+        }
+
         void Grab(View v)
         {
             if (v.stdin == null) return;
