--- cs:140/Service/ServiceOps.cs
+++ cs:141/Service/ServiceOps.cs
@@ -279,6 +279,15 @@
         readonly Dictionary<ServingRobotController, List<DishOrder>> _lastBatch =
             new Dictionary<ServingRobotController, List<DishOrder>>();
 
+        /// <summary>
+        /// 止まる所の番号 → その所で降ろす皿（依頼 v8 の 1）。
+        ///
+        /// StartRun に渡す stops を作るループは SlotOf が null の皿を飛ばすので、
+        /// 「止まる所の番号＝batch の番号」とは限らない。だからこの対応表で引く。
+        /// </summary>
+        readonly Dictionary<ServingRobotController, List<DishOrder>> _stopDishes =
+            new Dictionary<ServingRobotController, List<DishOrder>>();
+
         int _nextDishId = 1;
 
         // ---- 記録（TripLogger と ExperimentRunner が読む）----
@@ -616,6 +625,8 @@
             _pass.Clear();
             _jobs.Clear();
             _carrying.Clear();
+            _stopDishes.Clear();        // 止まる所と皿の対応表（依頼 v8 の 1）
+            _lastBatch.Clear();
             dishWaitMin.Clear();
             seatWaitMin.Clear();
             turnMin.Clear();
@@ -778,14 +789,21 @@
                 if (spots.Count > 0) r.SetStandbyPoint(spots[(_robots.Count - 1) % spots.Count]);
             }
             r.TripCompleted += OnRobotRunDone;
+            r.StopArrived += OnRobotStopArrived;     // 卓に着いた時点で届いたにする（依頼 v8 の 1）
         }
 
         public void ClearRobots()
         {
             for (int i = 0; i < _robots.Count; i++)
-                if (_robots[i] != null) _robots[i].TripCompleted -= OnRobotRunDone;
+            {
+                if (_robots[i] == null) continue;
+                _robots[i].TripCompleted -= OnRobotRunDone;
+                _robots[i].StopArrived -= OnRobotStopArrived;
+            }
             _robots.Clear();
             _carrying.Clear();
+            _stopDishes.Clear();
+            _lastBatch.Clear();
         }
 
         /// <summary>店の中の時刻を進める（ExperimentRunner が毎コマ呼ぶ）</summary>
@@ -1530,7 +1548,10 @@
                 List<DishOrder> batch = PickBatch(r);
                 if (batch.Count == 0) continue;
 
+                // 止まる所と皿を、同じ並びで組む（依頼 v8 の 1）。
+                // SlotOf が null の皿は止まる所を作れないので、どちらからも外す
                 List<ServingRobotController.RunStop> stops = new List<ServingRobotController.RunStop>();
+                List<DishOrder> stopDishes = new List<DishOrder>();
                 for (int k = 0; k < batch.Count; k++)
                 {
                     TableSlot s = SlotOf(batch[k].tableIndex);
@@ -1541,10 +1562,12 @@
                     stop.aisleEntrance = s.aisleEntrance;
                     stop.aisleZone = s.aisleZone;
                     stops.Add(stop);
+                    stopDishes.Add(batch[k]);
                 }
                 if (stops.Count == 0) continue;
 
                 _carrying[r] = batch;
+                _stopDishes[r] = stopDishes;
                 r.StartRun(stops, store.PassPoint, NowS);
             }
         }
@@ -1599,8 +1622,37 @@
         static float Flat(Vector3 d) { d.y = 0f; return d.magnitude; }
 
         /// <summary>
-        /// ロボットの1走りが終わった。届けた料理を記録し、
-        /// 届けられなかった料理は受け渡し口の先頭に戻す。
+        /// ロボットが止まる所（卓）に着いた（依頼 v8 の 1）。
+        ///
+        /// サイトの試算と同じで、**卓に着いた時点でその皿は届いた**。
+        /// 受け渡しの 25 秒（RobotSpec.tableDwellSec）はこのあとなので、
+        /// 客は受け渡し口へ戻るのを待たずに食べ始められる
+        /// （前は往復の最後にまとめて届いたにしていて、料理の待ちが
+        ///  1皿あたり 1.0〜1.3 分長く出ていた）。
+        /// </summary>
+        void OnRobotStopArrived(ServingRobotController r, int stopIndex)
+        {
+            List<DishOrder> stopDishes;
+            if (!_stopDishes.TryGetValue(r, out stopDishes) || stopDishes == null) return;
+            if (stopIndex < 0 || stopIndex >= stopDishes.Count) return;
+
+            DishArrived(stopDishes[stopIndex], true);
+        }
+
+        /// <summary>
+        /// ロボットの1走りぶんの片づけが終わった（依頼 v8 の 2）。
+        ///
+        /// ExperimentRunner は trips.csv を書くときに LastBatchOf を読むので、
+        /// ServingRobotController.TripCompleted を直に受けると、呼ばれる順番によって
+        /// **1つ前の往復の皿**を読んでしまっていた。
+        /// ここで片づけを終えてから出すこのイベントを受ければ、順番に頼らずに済む。
+        /// </summary>
+        public event System.Action<ServingRobotController> RunRecorded;
+
+        /// <summary>
+        /// ロボットの1走りが終わった。
+        /// 卓に着いた皿はもう届いているので何もしない。
+        /// まだ届いていない皿（arrivedS &lt; 0）だけを受け渡し口の先頭へ戻す。
         /// </summary>
         void OnRobotRunDone(ServingRobotController r)
         {
@@ -1609,6 +1661,7 @@
 
             _lastBatch[r] = batch;      // trips.csv を書くために残す
             _carrying[r] = new List<DishOrder>();
+            _stopDishes[r] = new List<DishOrder>();
 
             // なぜ届けられなかったか。ロボットが持っている結果から決める。
             // v5 3章：動けなくなってもスタッフが動かせば走りは続くので、
@@ -1618,12 +1671,16 @@
                        : r.Stuck ? "動けなくなった"
                        : "着かなかった";
 
-            int done = Mathf.Clamp(r.StopsCompleted, 0, batch.Count);
             for (int i = 0; i < batch.Count; i++)
             {
-                if (i < done) DishArrived(batch[i], true);
-                else RequeueDish(batch[i], r.robotId, why);
+                // 卓に着いた時点で DishArrived してあるので、arrivedS で見分ける
+                // （StopsCompleted の数で数えない。止まる所と皿の並びは対応表で持っている）
+                if (batch[i].arrivedS >= 0f) continue;
+                RequeueDish(batch[i], r.robotId, why);
             }
+
+            // 片づけが終わってから知らせる（ここで LastBatchOf を読んでよい）
+            if (RunRecorded != null) RunRecorded(r);
         }
 
         /// <summary>そのロボットがいま載せている料理</summary>
